(function(){ /* TiddlyWiki command line interface */ /*jslint node: true */ "use strict"; var App = require("./js/App.js").App, WikiStore = require("./js/WikiStore.js").WikiStore, LocalFileSync = require("./js/LocalFileSync.js").LocalFileSync, Tiddler = require("./js/Tiddler.js").Tiddler, Recipe = require("./js/Recipe.js").Recipe, tiddlerInput = require("./js/TiddlerInput.js"), tiddlerOutput = require("./js/TiddlerOutput.js"), util = require("util"), fs = require("fs"), url = require("url"), path = require("path"), aync = require("async"), http = require("http"); var app = new App(); var parseOptions = function(args,defaultSwitch) { var result = [], a = 0, switchRegExp = /^--([\S]+)$/gi; while(a < args.length) { switchRegExp.lastIndex = 0; var m = switchRegExp.exec(args[a]); if(m) { a++; var switchArgs = []; switchRegExp.lastIndex = 0; while(a < args.length && !switchRegExp.test(args[a])) { switchArgs.push(args[a++]); switchRegExp.lastIndex = 0; } result.push({switchName: m[1], args: switchArgs}); } else { result.push({switchName: defaultSwitch, args: [args[a++]]}); } } return result; }; var switches = parseOptions(Array.prototype.slice.call(process.argv,2),"dummy"), verbose = false, recipe = null, localFileSync = null, lastRecipeFilepath = null, currSwitch = 0; /* Each command line switch is represented by a function that takes a string array of arguments and a callback to be invoked when the switch processing has completed. The only argument to the callback is an error code, or null for success. */ var commandLineSwitches = { recipe: { args: {min: 1, max: 1}, handler: function(args,callback) { if(recipe) { callback("--recipe: Cannot process more than one recipe file"); } else { lastRecipeFilepath = args[0]; recipe = new Recipe({ filepath: args[0], store: app.store },function(err) { callback(err); }); } } }, dumpstore: { args: {min: 0, max: 0}, handler: function(args,callback) { console.log("Store is:\n%s",util.inspect(app.store,false,10)); process.nextTick(function() {callback(null);}); } }, dumprecipe: { args: {min: 0, max: 0}, handler: function(args,callback) { console.log("Recipe is:\n%s",util.inspect(recipe,false,10)); process.nextTick(function() {callback(null);}); } }, load: { args: {min: 1, max: 1}, handler: function(args,callback) { fs.readFile(args[0],"utf8",function(err,data) { if(err) { callback(err); } else { var fields = {title: args[0]}, extname = path.extname(args[0]), type = extname === ".html" ? "application/x-tiddlywiki" : extname; var tiddlers = app.store.deserializeTiddlers(type,data,fields); for(var t=0; t<tiddlers.length; t++) { app.store.addTiddler(new Tiddler(tiddlers[t])); } callback(null); } }); } }, store: { args: {min: 1, max: 1}, handler: function(args,callback) { localFileSync = new LocalFileSync(args[0],app.store,function() { callback(null); }); } }, links: { args: {min: 1, max: 1}, handler: function(args,callback) { var type = args[0]; app.store.linkMassager = function(linkInfo) { switch(type) { case "none": if(!linkInfo.isExternal) { linkInfo.suppressLink = true; } break; } }; process.nextTick(function() {callback(null);}); } }, savewiki: { args: {min: 1, max: 1}, handler: function(args,callback) { if(!recipe) { callback("--savewiki requires a recipe to be loaded first"); } fs.writeFile(path.resolve(args[0],"index.html"),recipe.cook(),"utf8",function(err) { if(err) { callback(err); } else { fs.writeFile(path.resolve(args[0],"index.xml"),recipe.cookRss(),"utf8",function(err) { callback(err); }); } }); } }, savetiddler: { args: {min: 2, max: 3}, handler: function(args,callback) { var title = args[0], filename = args[1], type = args[2] || "text/html"; fs.writeFileSync(filename,app.store.renderTiddler(type,title),"utf8"); } }, savetiddlers: { args: {min: 1, max: 2}, handler: function(args,callback) { var outdir = args[0], recipe = []; app.store.forEachTiddler(function(title,tiddler) { var filename = encodeURIComponent(tiddler.title.replace(/ /g,"_")) + ".tid"; fs.writeFileSync(path.resolve(outdir,filename),app.store.serializeTiddlers([tiddler],"application/x-tiddler")[0].data,"utf8"); recipe.push("tiddler: " + filename + "\n"); }); fs.writeFileSync(path.join(args[0],"split.recipe"),recipe.join("")); process.nextTick(function() {callback(null);}); } }, savehtml: { args: {min: 1, max: 1}, handler: function(args,callback) { var outdir = args[0]; app.store.forEachTiddler(function(title,tiddler) { var filename = encodeURIComponent(title.replace(/ /g,"_")) + ".html"; fs.writeFileSync(path.resolve(outdir,filename),app.store.renderTiddler("text/html",title),"utf8"); }); process.nextTick(function() {callback(null);}); } }, servewiki: { args: {min: 0, max: 1}, handler: function(args,callback) { if(!lastRecipeFilepath) { callback("--servewiki must be preceded by a --recipe"); } var port = args.length > 0 ? args[0] : 8000; http.createServer(function(request, response) { var path = url.parse(request.url).pathname; switch(request.method) { case "PUT": var data = ""; request.on("data",function(chunk) { data += chunk.toString(); }); request.on("end",function() { var title = decodeURIComponent(path.substr(1)); app.store.addTiddler(new Tiddler(JSON.parse(data),{title: title})); response.writeHead(204, "OK"); response.end(); }); break; case "DELETE": app.store.deleteTiddler(decodeURIComponent(path.substr(1))); response.writeHead(204, "OK"); response.end(); break; case "GET": if(path === "/") { response.writeHead(200, {"Content-Type": "text/html"}); response.end(recipe.cook(), "utf8"); } else { response.writeHead(404); response.end(); } break; } }).listen(port); process.nextTick(function() {callback(null);}); } }, servetiddlers: { args: {min: 0, max: 1}, handler: function(args,callback) { var port = args.length > 0 ? args[0] : 8000; http.createServer(function (request, response) { var title = decodeURIComponent(url.parse(request.url).pathname.substr(1)), tiddler = app.store.getTiddler(title); if(tiddler) { response.writeHead(200, {"Content-Type": "text/html"}); response.end(app.store.renderTiddler("text/html",title),"utf8"); } else { response.writeHead(404); response.end(); } }).listen(port); process.nextTick(function() {callback(null);}); } }, verbose: { args: {min: 0, max: 0}, handler: function(args,callback) { verbose = true; process.nextTick(function() {callback(null);}); } }, wikitest: { args: {min: 1, max: 2}, handler: function(args,callback) { var testdirectory = args[0], saveResults = args[1] === "save", files = fs.readdirSync(testdirectory), titles = [], f,t,extname,basename; for(f=0; f<files.length; f++) { extname = path.extname(files[f]); if(extname === ".tid") { var tiddlers = app.store.deserializeTiddlers(extname,fs.readFileSync(path.resolve(testdirectory,files[f]),"utf8")); if(tiddlers.length > 1) { throw "Cannot use .JSON files"; } app.store.addTiddler(new Tiddler(tiddlers[0])); titles.push(tiddlers[0].title); } } for(t=0; t<titles.length; t++) { var htmlFilename = path.resolve(testdirectory,titles[t] + ".html"), plainFilename = path.resolve(testdirectory,titles[t] + ".txt"), htmlTarget = fs.readFileSync(htmlFilename,"utf8"), plainTarget = fs.readFileSync(plainFilename,"utf8"), tiddler = app.store.getTiddler(titles[t]), htmlRender = app.store.renderTiddler("text/html",titles[t]), plainRender = app.store.renderTiddler("text/plain",titles[t]); if(saveResults) { // Save results fs.writeFileSync(htmlFilename,htmlRender,"utf8"); fs.writeFileSync(plainFilename,plainRender,"utf8"); } else { // Report results if(htmlTarget !== htmlRender) { console.error("Tiddler %s html error\nTarget: %s\nFound: %s\n",titles[t],htmlTarget,htmlRender); } if(plainTarget !== plainRender) { console.error("Tiddler %s plain text error\nTarget: %s\nFound: %s\n",titles[t],plainTarget,plainRender); } } } process.nextTick(function() {callback(null);}); } } }; var processNextSwitch = function() { if(currSwitch < switches.length) { var s = switches[currSwitch++], csw = commandLineSwitches[s.switchName]; if(!csw) { throw "Unknown command line switch --" + s.switchName; } if(s.args.length < csw.args.min) { throw "Command line switch --" + s.switchName + " should have a minimum of " + csw.args.min + " arguments"; } if(s.args.length > csw.args.max) { throw "Command line switch --" + s.switchName + " should have a maximum of " + csw.args.max + " arguments"; } if(verbose) { console.log("Processing --" + s.switchName + " " + s.args.join(" ")); } csw.handler(s.args,function (err) { if(err) { throw "Error while executing option '--" + s.switchName + "' was:\n" + err; } process.nextTick(processNextSwitch); }); } }; process.nextTick(processNextSwitch); })();