mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-11-27 03:57:21 +00:00
Converted recipe handling to use async IO
Part of the preparation for supporting reading recipes and ingredients over HTTP
This commit is contained in:
parent
a2831eb203
commit
5314fda2ca
5
cook.js
5
cook.js
@ -10,6 +10,7 @@ var filename = process.argv[2];
|
|||||||
|
|
||||||
var store = new TiddlyWiki();
|
var store = new TiddlyWiki();
|
||||||
|
|
||||||
var theRecipe = new Recipe(store,filename);
|
var theRecipe = new Recipe(store,filename,function() {
|
||||||
|
process.stdout.write(theRecipe.cook());
|
||||||
|
});
|
||||||
|
|
||||||
process.stdout.write(theRecipe.cook());
|
|
||||||
|
23
js/FileRetriever.js
Normal file
23
js/FileRetriever.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
FileRetriever can asynchronously retrieve files from HTTP URLs or the local file system. It incorporates
|
||||||
|
throttling so that we don't get error EMFILE "Too many open files".
|
||||||
|
*/
|
||||||
|
|
||||||
|
var fs = require("fs"),
|
||||||
|
utils = require("./Utils.js");
|
||||||
|
|
||||||
|
var FileRetriever = exports;
|
||||||
|
|
||||||
|
var fileRequestQueue = utils.queue(function(task,callback) {
|
||||||
|
fs.readFile(task.filepath,"utf8", function(err,data) {
|
||||||
|
callback(err,data);
|
||||||
|
});
|
||||||
|
},10);
|
||||||
|
|
||||||
|
|
||||||
|
// Retrieve a file given a filepath specifier and a context path. If the filepath isn't an absolute
|
||||||
|
// filepath or an absolute URL, then it is interpreted relative to the context path, which can also be
|
||||||
|
// a filepath or a URL. On completion, the callback function is called as callback(err,data)
|
||||||
|
FileRetriever.retrieveFile = function(filepath,contextPath,callback) {
|
||||||
|
fileRequestQueue.push({filepath: filepath},callback);
|
||||||
|
}
|
99
js/Recipe.js
99
js/Recipe.js
@ -32,33 +32,48 @@ var Tiddler = require("./Tiddler.js").Tiddler,
|
|||||||
tiddlerOutput = require("./TiddlerOutput.js"),
|
tiddlerOutput = require("./TiddlerOutput.js"),
|
||||||
utils = require("./Utils.js"),
|
utils = require("./Utils.js"),
|
||||||
TiddlyWiki = require("./TiddlyWiki.js").TiddlyWiki,
|
TiddlyWiki = require("./TiddlyWiki.js").TiddlyWiki,
|
||||||
|
retrieveFile = require("./FileRetriever.js").retrieveFile,
|
||||||
fs = require("fs"),
|
fs = require("fs"),
|
||||||
path = require("path"),
|
path = require("path"),
|
||||||
util = require("util");
|
util = require("util");
|
||||||
|
|
||||||
// Create a new Recipe object from the specified recipe file, storing the tiddlers in a specified TiddlyWiki store
|
// Create a new Recipe object from the specified recipe file, storing the tiddlers in a specified TiddlyWiki store. Invoke
|
||||||
var Recipe = function(store,filepath) {
|
// the callback function when all of the referenced tiddlers and recipes have been loaded successfully
|
||||||
|
var Recipe = function(store,filepath,callback) {
|
||||||
this.store = store; // Save a reference to the store
|
this.store = store; // Save a reference to the store
|
||||||
this.ingredients = {}; // Hashmap of array of ingredients
|
this.ingredients = {}; // Hashmap of array of ingredients
|
||||||
|
this.callback = callback;
|
||||||
|
this.fetchCount = 0;
|
||||||
this.readRecipe(filepath); // Read the recipe file
|
this.readRecipe(filepath); // Read the recipe file
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specialised configuration and handlers for particular ingredient markers
|
// The fetch counter is used to keep track of the number of asynchronous requests outstanding
|
||||||
var specialMarkers = {
|
Recipe.prototype.incFetchCount = function() {
|
||||||
shadow: {
|
this.fetchCount++;
|
||||||
readIngredientPostProcess: function(fields) {
|
}
|
||||||
// Add ".shadow" to the name of shadow tiddlers
|
|
||||||
fields.title = fields.title + ".shadow";
|
// When the fetch counter reaches zero, all the results are in, so invoke the recipe callback
|
||||||
return fields;
|
Recipe.prototype.decFetchCount = function() {
|
||||||
}
|
if(--this.fetchCount === 0) {
|
||||||
|
this.callback();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
// Process the contents of a recipe file
|
// Process the contents of a recipe file
|
||||||
Recipe.prototype.readRecipe = function(filepath) {
|
Recipe.prototype.readRecipe = function(filepath) {
|
||||||
var dirname = path.dirname(filepath),
|
var dirname = path.dirname(filepath),
|
||||||
me = this;
|
me = this;
|
||||||
fs.readFileSync(filepath,"utf8").split("\n").forEach(function(line) {
|
this.incFetchCount();
|
||||||
|
retrieveFile(filepath, null, function(err, data) {
|
||||||
|
if (err) throw err;
|
||||||
|
me.processRecipe(data,dirname);
|
||||||
|
me.decFetchCount();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Recipe.prototype.processRecipe = function (data,dirname) {
|
||||||
|
var me = this;
|
||||||
|
data.split("\n").forEach(function(line) {
|
||||||
var p = line.indexOf(":");
|
var p = line.indexOf(":");
|
||||||
if(p !== -1) {
|
if(p !== -1) {
|
||||||
var marker = line.substr(0, p).trim(),
|
var marker = line.substr(0, p).trim(),
|
||||||
@ -66,11 +81,19 @@ Recipe.prototype.readRecipe = function(filepath) {
|
|||||||
if(marker === "recipe") {
|
if(marker === "recipe") {
|
||||||
me.readRecipe(path.resolve(dirname,value));
|
me.readRecipe(path.resolve(dirname,value));
|
||||||
} else {
|
} else {
|
||||||
var fields = me.readIngredient(dirname,value),
|
if(!(marker in me.ingredients)) {
|
||||||
postProcess = me.readIngredientPostProcess[marker];
|
me.ingredients[marker] = [];
|
||||||
if(postProcess)
|
}
|
||||||
fields = postProcess(fields);
|
var ingredientLocation = me.ingredients[marker].length;
|
||||||
me.addIngredient(marker,fields);
|
me.ingredients[marker][ingredientLocation] = null;
|
||||||
|
me.readIngredient(dirname,value,function(fields) {
|
||||||
|
var postProcess = me.readIngredientPostProcess[marker];
|
||||||
|
if(postProcess)
|
||||||
|
fields = postProcess(fields);
|
||||||
|
var ingredientTiddler = new Tiddler(fields);
|
||||||
|
me.store.addTiddler(ingredientTiddler);
|
||||||
|
me.ingredients[marker][ingredientLocation] = ingredientTiddler;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -85,32 +108,35 @@ Recipe.prototype.readIngredientPostProcess = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Recipe.prototype.addIngredient = function(marker,tiddlerFields) {
|
|
||||||
var ingredientTiddler = new Tiddler(tiddlerFields);
|
|
||||||
this.store.addTiddler(ingredientTiddler);
|
|
||||||
if(marker in this.ingredients) {
|
|
||||||
this.ingredients[marker].push(ingredientTiddler);
|
|
||||||
} else {
|
|
||||||
this.ingredients[marker] = [ingredientTiddler];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read an ingredient file and return it as a hashmap of tiddler fields. Also read the .meta file, if present
|
// Read an ingredient file and return it as a hashmap of tiddler fields. Also read the .meta file, if present
|
||||||
Recipe.prototype.readIngredient = function(dirname,filepath) {
|
Recipe.prototype.readIngredient = function(dirname,filepath,callback) {
|
||||||
var fullpath = path.resolve(dirname,filepath),
|
var me = this,
|
||||||
|
fullpath = path.resolve(dirname,filepath),
|
||||||
extname = path.extname(filepath),
|
extname = path.extname(filepath),
|
||||||
basename = path.basename(filepath,extname),
|
basename = path.basename(filepath,extname),
|
||||||
fields = {
|
fields = {
|
||||||
title: basename
|
title: basename
|
||||||
};
|
};
|
||||||
|
me.incFetchCount();
|
||||||
// Read the tiddler file
|
// Read the tiddler file
|
||||||
fields = tiddlerInput.parseTiddler(fs.readFileSync(fullpath,"utf8"),extname,fields);
|
retrieveFile(fullpath,null,function(err,data) {
|
||||||
// Check for the .meta file
|
if (err) throw err;
|
||||||
var metafile = fullpath + ".meta";
|
fields = tiddlerInput.parseTiddler(data,extname,fields);
|
||||||
if(path.existsSync(metafile)) {
|
// Check for the .meta file
|
||||||
fields = tiddlerInput.parseMetaDataBlock(fs.readFileSync(metafile,"utf8"),fields);
|
var metafile = fullpath + ".meta";
|
||||||
}
|
me.incFetchCount();
|
||||||
return fields;
|
retrieveFile(metafile,null,function(err,data) {
|
||||||
|
if(err && err.code !== 'ENOENT') {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
if(!err) {
|
||||||
|
fields = tiddlerInput.parseMetaDataBlock(data,fields);
|
||||||
|
}
|
||||||
|
callback(fields);
|
||||||
|
me.decFetchCount();
|
||||||
|
});
|
||||||
|
me.decFetchCount();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a string of the cooked recipe
|
// Return a string of the cooked recipe
|
||||||
@ -128,7 +154,6 @@ Recipe.prototype.cook = function() {
|
|||||||
out.push(line);
|
out.push(line);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// out.push("\nRecipe:\n" + util.inspect(this.ingredients,false,4));
|
|
||||||
return out.join("\n");
|
return out.join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
39
js/Utils.js
39
js/Utils.js
@ -72,3 +72,42 @@ utils.htmlDecode = function(s)
|
|||||||
return s.replace(/</mg,"<").replace(/>/mg,">").replace(/"/mg,"\"").replace(/&/mg,"&");
|
return s.replace(/</mg,"<").replace(/>/mg,">").replace(/"/mg,"\"").replace(/&/mg,"&");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Adapted from async.js, https://github.com/caolan/async
|
||||||
|
// Creates a queue of tasks for an asyncronous worker function with a specified maximum number of concurrent operations.
|
||||||
|
// q = utils.queue(function(taskData,callback) {
|
||||||
|
// fs.readFile(taskData.filename,"uft8",function(err,data) {
|
||||||
|
// callback(err,data);
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// q.push(taskData,callback) is used to queue a new task
|
||||||
|
utils.queue = function(worker, concurrency) {
|
||||||
|
var workers = 0;
|
||||||
|
var q = {
|
||||||
|
tasks: [],
|
||||||
|
concurrency: concurrency,
|
||||||
|
push: function (data, callback) {
|
||||||
|
q.tasks.push({data: data, callback: callback});
|
||||||
|
process.nextTick(q.process);
|
||||||
|
},
|
||||||
|
process: function () {
|
||||||
|
if (workers < q.concurrency && q.tasks.length) {
|
||||||
|
var task = q.tasks.shift();
|
||||||
|
workers += 1;
|
||||||
|
worker(task.data, function () {
|
||||||
|
workers -= 1;
|
||||||
|
if (task.callback) {
|
||||||
|
task.callback.apply(task, arguments);
|
||||||
|
}
|
||||||
|
q.process();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
length: function () {
|
||||||
|
return q.tasks.length;
|
||||||
|
},
|
||||||
|
running: function () {
|
||||||
|
return workers;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return q;
|
||||||
|
};
|
||||||
|
@ -12,11 +12,12 @@ var TiddlyWiki = require("./js/TiddlyWiki.js").TiddlyWiki,
|
|||||||
|
|
||||||
var filename = process.argv[2];
|
var filename = process.argv[2];
|
||||||
|
|
||||||
http.createServer(function (request, response) {
|
http.createServer(function(request, response) {
|
||||||
response.writeHead(200, {"Content-Type": "text/html"});
|
response.writeHead(200, {"Content-Type": "text/html"});
|
||||||
var store = new TiddlyWiki();
|
var store = new TiddlyWiki();
|
||||||
var theRecipe = new Recipe(store,filename);
|
var theRecipe = new Recipe(store,filename,function() {
|
||||||
response.end(theRecipe.cook(), "utf-8");
|
response.end(theRecipe.cook(), "utf-8");
|
||||||
|
});
|
||||||
}).listen(8000);
|
}).listen(8000);
|
||||||
|
|
||||||
sys.puts("Server running at http://127.0.0.1:8000/");
|
sys.puts("Server running at http://127.0.0.1:8000/");
|
||||||
|
2
test/data/samples/tiddlers/Test1.tiddler.meta
Normal file
2
test/data/samples/tiddlers/Test1.tiddler.meta
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
tags: one two three four five
|
||||||
|
modifier: jermolene
|
Loading…
Reference in New Issue
Block a user