2011-11-22 14:29:29 +00:00
|
|
|
/*
|
|
|
|
|
|
|
|
Recipe files consist of recipe lines consisting of a marker, a colon and the pathname of an ingredient:
|
|
|
|
|
|
|
|
marker: pathname
|
|
|
|
|
|
|
|
The pathname is interpreted relative to the directory containing the recipe file.
|
|
|
|
|
|
|
|
The special marker "recipe" is used to load a sub-recipe file.
|
|
|
|
|
|
|
|
The special marker "template" is used to identify the HTML template. The HTML template contains
|
|
|
|
markers in two different forms:
|
|
|
|
|
|
|
|
<!--@@marker@@-->
|
|
|
|
<!--@@marker@@-->
|
|
|
|
|
|
|
|
Recipe processing is in two parts. First the recipe file is parsed and the referenced files are loaded into tiddlers.
|
|
|
|
Second, the template is processed by replacing the markers with the text of the tiddlers indicated in the recipe file.
|
|
|
|
|
|
|
|
The recipe is parsed into the 'ingredients' hashmap like this:
|
|
|
|
|
|
|
|
this.ingredients = {
|
|
|
|
"marker1": [Tiddler1,Tiddler2,Tiddler3,...],
|
|
|
|
"marker2": [TiddlerA,TiddlerB,TiddlerC,...],
|
|
|
|
....
|
|
|
|
};
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
2011-11-27 09:46:02 +00:00
|
|
|
var Tiddler = require("./Tiddler.js").Tiddler,
|
2011-11-22 17:42:03 +00:00
|
|
|
tiddlerInput = require("./TiddlerInput.js"),
|
|
|
|
tiddlerOutput = require("./TiddlerOutput.js"),
|
|
|
|
utils = require("./Utils.js"),
|
2011-11-27 09:46:02 +00:00
|
|
|
TiddlyWiki = require("./TiddlyWiki.js").TiddlyWiki,
|
2011-11-28 13:47:38 +00:00
|
|
|
retrieveFile = require("./FileRetriever.js").retrieveFile,
|
2011-11-22 14:29:29 +00:00
|
|
|
fs = require("fs"),
|
|
|
|
path = require("path"),
|
|
|
|
util = require("util");
|
|
|
|
|
2011-11-28 13:47:38 +00:00
|
|
|
// Create a new Recipe object from the specified recipe file, storing the tiddlers in a specified TiddlyWiki store. Invoke
|
|
|
|
// the callback function when all of the referenced tiddlers and recipes have been loaded successfully
|
|
|
|
var Recipe = function(store,filepath,callback) {
|
2011-11-22 14:29:29 +00:00
|
|
|
this.store = store; // Save a reference to the store
|
|
|
|
this.ingredients = {}; // Hashmap of array of ingredients
|
2011-11-28 13:47:38 +00:00
|
|
|
this.callback = callback;
|
|
|
|
this.fetchCount = 0;
|
2011-11-28 15:15:35 +00:00
|
|
|
this.readRecipe(filepath,process.cwd()); // Read the recipe file
|
2011-11-22 14:29:29 +00:00
|
|
|
}
|
|
|
|
|
2011-11-28 13:47:38 +00:00
|
|
|
// The fetch counter is used to keep track of the number of asynchronous requests outstanding
|
|
|
|
Recipe.prototype.incFetchCount = function() {
|
|
|
|
this.fetchCount++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// When the fetch counter reaches zero, all the results are in, so invoke the recipe callback
|
|
|
|
Recipe.prototype.decFetchCount = function() {
|
|
|
|
if(--this.fetchCount === 0) {
|
|
|
|
this.callback();
|
2011-11-22 14:29:29 +00:00
|
|
|
}
|
2011-11-28 13:47:38 +00:00
|
|
|
}
|
2011-11-22 14:29:29 +00:00
|
|
|
|
|
|
|
// Process the contents of a recipe file
|
2011-11-28 15:15:35 +00:00
|
|
|
Recipe.prototype.readRecipe = function(filepath,contextPath) {
|
|
|
|
var me = this;
|
2011-11-28 13:47:38 +00:00
|
|
|
this.incFetchCount();
|
2011-11-30 11:41:26 +00:00
|
|
|
var rf = retrieveFile(filepath, contextPath, function(err, data) {
|
2011-11-28 13:47:38 +00:00
|
|
|
if (err) throw err;
|
2011-11-30 11:41:26 +00:00
|
|
|
me.processRecipe(data,rf.path);
|
2011-11-28 13:47:38 +00:00
|
|
|
me.decFetchCount();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2011-11-28 15:15:35 +00:00
|
|
|
Recipe.prototype.processRecipe = function (data,contextPath) {
|
2011-11-28 13:47:38 +00:00
|
|
|
var me = this;
|
|
|
|
data.split("\n").forEach(function(line) {
|
2011-11-22 14:29:29 +00:00
|
|
|
var p = line.indexOf(":");
|
|
|
|
if(p !== -1) {
|
|
|
|
var marker = line.substr(0, p).trim(),
|
|
|
|
value = line.substr(p+1).trim();
|
|
|
|
if(marker === "recipe") {
|
2011-11-28 15:15:35 +00:00
|
|
|
me.readRecipe(value,contextPath);
|
2011-11-22 14:29:29 +00:00
|
|
|
} else {
|
2011-11-28 13:47:38 +00:00
|
|
|
if(!(marker in me.ingredients)) {
|
|
|
|
me.ingredients[marker] = [];
|
|
|
|
}
|
2011-11-30 11:41:26 +00:00
|
|
|
var ingredientLocation = me.ingredients[marker].push(null) - 1;
|
2011-11-28 15:15:35 +00:00
|
|
|
me.readIngredient(value,contextPath,function(fields) {
|
2011-11-28 13:47:38 +00:00
|
|
|
var postProcess = me.readIngredientPostProcess[marker];
|
|
|
|
if(postProcess)
|
|
|
|
fields = postProcess(fields);
|
|
|
|
var ingredientTiddler = new Tiddler(fields);
|
|
|
|
me.store.addTiddler(ingredientTiddler);
|
|
|
|
me.ingredients[marker][ingredientLocation] = ingredientTiddler;
|
|
|
|
});
|
2011-11-22 14:29:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Special post-processing required for certain ingredient types
|
|
|
|
Recipe.prototype.readIngredientPostProcess = {
|
|
|
|
"shadow": function(fields) {
|
|
|
|
// Add ".shadow" to the name of shadow tiddlers
|
|
|
|
fields.title = fields.title + ".shadow";
|
|
|
|
return fields;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Read an ingredient file and return it as a hashmap of tiddler fields. Also read the .meta file, if present
|
2011-11-28 15:15:35 +00:00
|
|
|
Recipe.prototype.readIngredient = function(filepath,contextPath,callback) {
|
2011-11-30 11:41:26 +00:00
|
|
|
var me = this;
|
2011-11-28 13:47:38 +00:00
|
|
|
me.incFetchCount();
|
2011-11-22 14:29:29 +00:00
|
|
|
// Read the tiddler file
|
2011-11-30 11:41:26 +00:00
|
|
|
var rf = retrieveFile(filepath,contextPath,function(err,data) {
|
2011-11-28 13:47:38 +00:00
|
|
|
if (err) throw err;
|
2011-11-30 11:41:26 +00:00
|
|
|
var fields = {
|
|
|
|
title: rf.basename
|
|
|
|
};
|
|
|
|
fields = tiddlerInput.parseTiddler(data,rf.extname,fields);
|
2011-11-28 13:47:38 +00:00
|
|
|
// Check for the .meta file
|
2011-11-28 15:15:35 +00:00
|
|
|
var metafile = filepath + ".meta";
|
2011-11-28 13:47:38 +00:00
|
|
|
me.incFetchCount();
|
2011-11-28 15:15:35 +00:00
|
|
|
retrieveFile(metafile,contextPath,function(err,data) {
|
2011-11-29 18:27:03 +00:00
|
|
|
if(err && err.code !== "ENOENT" && err.code !== "404") {
|
2011-11-28 13:47:38 +00:00
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
if(!err) {
|
|
|
|
fields = tiddlerInput.parseMetaDataBlock(data,fields);
|
|
|
|
}
|
|
|
|
callback(fields);
|
|
|
|
me.decFetchCount();
|
|
|
|
});
|
|
|
|
me.decFetchCount();
|
|
|
|
});
|
2011-11-22 14:29:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Return a string of the cooked recipe
|
|
|
|
Recipe.prototype.cook = function() {
|
2011-11-28 17:04:39 +00:00
|
|
|
var template = this.ingredients.template ? this.ingredients.template[0].fields.text : "",
|
|
|
|
out = [],
|
|
|
|
me = this;
|
2011-11-22 14:29:29 +00:00
|
|
|
template.split("\n").forEach(function(line) {
|
|
|
|
var templateRegExp = /^(?:<!--@@(.*)@@-->)|(?:<!--@@(.*)@@-->)$/gi;
|
|
|
|
var match = templateRegExp.exec(line);
|
|
|
|
if(match) {
|
|
|
|
var marker = match[1] === undefined ? match[2] : match[1];
|
|
|
|
me.outputIngredient(out,marker);
|
|
|
|
} else {
|
|
|
|
out.push(line);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return out.join("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Output all the tiddlers in the recipe with a particular marker
|
|
|
|
Recipe.prototype.outputIngredient = function(out,marker) {
|
2011-11-28 17:04:39 +00:00
|
|
|
var ingredient = this.ingredients[marker],
|
|
|
|
outputType = Recipe.ingredientOutputMapper[marker] || "raw",
|
|
|
|
outputter = Recipe.ingredientOutputter[outputType];
|
2011-11-22 14:29:29 +00:00
|
|
|
if(outputter && ingredient) {
|
|
|
|
outputter(out,ingredient);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allows for specialised processing for certain markers
|
|
|
|
Recipe.ingredientOutputMapper = {
|
|
|
|
tiddler: "div",
|
|
|
|
js: "javascript",
|
|
|
|
jsdeprecated: "javascript",
|
|
|
|
jquery: "javascript",
|
|
|
|
shadow: "shadow"
|
|
|
|
};
|
|
|
|
|
|
|
|
Recipe.ingredientOutputter = {
|
|
|
|
raw: function(out,ingredient) {
|
|
|
|
// The default is just to output the raw text of the tiddler, ignoring any metadata
|
|
|
|
for(var t=0; t<ingredient.length; t++) {
|
|
|
|
var tid = ingredient[t];
|
|
|
|
// For compatibility with cook.rb, remove one trailing \n from tiddler
|
|
|
|
var text = tid.fields.text;
|
|
|
|
text = text.charAt(text.length-1) === "\n" ? text.substr(0,text.length-1) : text;
|
|
|
|
out.push(text);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
div: function(out,ingredient) {
|
|
|
|
// Ordinary tiddlers are output as a <DIV>
|
|
|
|
for(var t=0; t<ingredient.length; t++) {
|
|
|
|
var tid = ingredient[t];
|
2011-11-25 13:27:40 +00:00
|
|
|
out.push(tiddlerOutput.outputTiddlerDiv(tid));
|
2011-11-22 14:29:29 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
javascript: function(out,ingredient) {
|
|
|
|
// Lines starting with //# are removed from javascript tiddlers
|
|
|
|
for(var t=0; t<ingredient.length; t++) {
|
2011-11-28 17:04:39 +00:00
|
|
|
var tid = ingredient[t],
|
|
|
|
text = tid.fields.text;
|
2011-11-22 14:29:29 +00:00
|
|
|
// For compatibility with cook.rb, remove one trailing \n from tiddler
|
|
|
|
text = text.charAt(text.length-1) === "\n" ? text.substr(0,text.length-1) : text;
|
|
|
|
var lines = text.split("\n");
|
|
|
|
for(var line=0; line<lines.length; line++) {
|
|
|
|
var commentRegExp = /^\s*\/\/#/gi;
|
|
|
|
if(!commentRegExp.test(lines[line])) {
|
|
|
|
out.push(lines[line]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
shadow: function(out,ingredient) {
|
|
|
|
// Shadows are output as a <DIV> with the the ".shadow" suffix removed from the title
|
|
|
|
for(var t=0; t<ingredient.length; t++) {
|
2011-11-28 17:04:39 +00:00
|
|
|
var tid = ingredient[t],
|
|
|
|
title = tid.fields.title,
|
|
|
|
tweakedTiddler;
|
2011-11-22 14:29:29 +00:00
|
|
|
if(title.indexOf(".shadow") === title.length - 7) {
|
2011-11-27 09:46:02 +00:00
|
|
|
tweakedTiddler = new Tiddler(tid,{
|
2011-11-22 14:29:29 +00:00
|
|
|
title: title.substr(0, title.length-7)
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
tweakedTiddler = tid;
|
|
|
|
}
|
2011-11-25 13:27:40 +00:00
|
|
|
out.push(tiddlerOutput.outputTiddlerDiv(tweakedTiddler));
|
2011-11-22 14:29:29 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.Recipe = Recipe;
|
|
|
|
|