/* Recipe files consist of recipe lines consisting of a marker, a colon and the pathname of an ingredient: marker: filepath The filepath 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@@--> Recipe processing is in four parts: 1) The recipe file is parsed and any subrecipe files loaded recursively into this structure: this.recipe = [ {marker: , filepath: , contextPath: }, ... {marker: , filepath: , contextPath: }, [ {marker: , filepath: , contextPath: }, ... {marker: , filepath: , contextPath: }, ] ]; 2) The tiddler files referenced by the recipe structure are loaded into it as an additional 'tiddlers' member that contains an array of hashmaps of tiddler field values. 3) The recipe is scanned to create a hashmap of markers and their associated tiddlers. In cases where more than one tiddler with the same title is assigned to a marker, the one that is later in the recipe file wins. At this point tiddlers are placed in the store so that they can be referenced by title this.markers = { : [,,...], : [,,...], ... } 4) Finally, the template is processed by replacing the markers with the text of the associated tiddlers */ /*jslint node: true */ "use strict"; var Tiddler = require("./Tiddler.js").Tiddler, WikiTextRenderer = require("./WikiTextRenderer").WikiTextRenderer, utils = require("./Utils.js"), retrieveFile = require("./FileRetriever.js").retrieveFile, fs = require("fs"), path = require("path"), util = require("util"), async = require("async"); var Recipe = function(options,callback) { var me = this; this.filepath = options.filepath; this.store = options.store; this.tiddlerConverters = options.tiddlerConverters; this.callback = callback; this.recipe = []; this.markers = {}; this.recipeQueue = async.queue(function(task,callback) { retrieveFile(task.filepath,task.contextPath,function(err,data) { if(err) { callback(err); } else { me.processRecipeFile(task.recipe,data.text,data.path); callback(null); } }); },1); this.tiddlerQueue = async.queue(function(task,callback) { me.readTiddlerFile(task.filepath,task.contextPath,function(err,data) { if(err) { callback(err); } else { task.recipeLine.tiddlers = data; callback(null); } }); },1); this.recipeQueue.drain = function() { me.loadTiddlerFiles(me.recipe); }; this.tiddlerQueue.drain = function() { me.chooseTiddlers(me.recipe); me.sortTiddlersForMarker("tiddler"); me.callback(null); }; this.recipeQueue.push({filepath: this.filepath, contextPath: process.cwd(), recipe: this.recipe}); }; Recipe.prototype.loadTiddlerFiles = function(recipe) { for(var r=0; r)|(?:<!--@@(.*)@@-->)$/gi; var match = templateRegExp.exec(line); if(match) { var marker = match[1] === undefined ? match[2] : match[1]; me.outputTiddlersForMarker(out,marker); } else { out.push(line); } }); return out.join("\n"); }; // Output all the tiddlers in the recipe with a particular marker Recipe.prototype.outputTiddlersForMarker = function(out,marker) { var tiddlers = this.markers[marker], outputType = Recipe.tiddlerOutputMapper[marker] || "raw", outputter = Recipe.tiddlerOutputter[outputType]; if(!tiddlers) { tiddlers = []; } if(outputter) { outputter.call(this,out,tiddlers); } }; // Allows for specialised processing for certain markers Recipe.tiddlerOutputMapper = { tiddler: "div", js: "javascript", jsdeprecated: "javascript", jquery: "javascript", shadow: "shadow", title: "title" }; Recipe.tiddlerOutputter = { raw: function(out,tiddlers) { // The default is just to output the raw text of the tiddler, ignoring any metadata for(var t=0; t for(var t=0; t" + utils.htmlEncode(tiddler.fields.title) + "\n"; s += "" + utils.htmlEncode(me.renderTiddler(tiddler.fields.title,"text/plain")) + "\n"; var i; if(tiddler.fields.tags) { for(i=0; i\n"; } } s += "" + uri + "#" + encodeURIComponent(encodeTiddlyLink(tiddler.fields.title)) + "\n"; if(tiddler.fields.modified) { s +="" + tiddler.fields.modified.toUTCString() + "\n"; } return s; }, getRssTiddlers = function(sortField,excludeTag) { var r = []; me.store.forEachTiddler(function(title,tiddler) { if(!tiddler.hasTag(excludeTag)) { r.push(tiddler); } }); r.sort(function(a,b) { var aa = a.fields[sortField] || 0, bb = b.fields[sortField] || 0; if(aa < bb) { return -1; } else { if(aa > bb) { return 1; } else { return 0; } } }); return r; }; // Assemble the header s.push("<" + "?xml version=\"1.0\"?" + ">"); s.push(""); s.push(""); s.push("" + utils.htmlEncode(this.renderTiddler("SiteTitle","text/plain")) + ""); if(u) s.push("" + utils.htmlEncode(u) + ""); s.push("" + utils.htmlEncode(this.renderTiddler("SiteSubtitle","text/plain")) + ""); //s.push("" + config.locale + ""); s.push("" + d.toUTCString() + ""); s.push("" + d.toUTCString() + ""); s.push("http://blogs.law.harvard.edu/tech/rss"); s.push("https://github.com/Jermolene/cook.js"); // The body var tiddlers = getRssTiddlers("modified","excludeLists"); var i,n = numRssItems > tiddlers.length ? 0 : tiddlers.length-numRssItems; for(i=tiddlers.length-1; i>=n; i--) { s.push("\n" + tiddlerToRssItem(tiddlers[i],u) + "\n"); } // And footer s.push(""); s.push(""); // Save it all return s.join("\n"); }; Recipe.prototype.renderTiddler = function(title,type) { var r = new WikiTextRenderer(this.store.getTiddler(title).getParseTree(),this.store,title); return r.render(type); }; exports.Recipe = Recipe;