From 4a6501778a9ca32e36bfd6ca9a601b8388a08745 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 19 Oct 2024 15:54:31 +0100 Subject: [PATCH] Add support for prompts within build scripts --- core/modules/commander.js | 177 +++++++++++++++++++------------ core/modules/startup/commands.js | 6 +- core/modules/utils/utils.js | 58 +++++++++- editions/tw5.com/tiddlywiki.info | 6 ++ 4 files changed, 178 insertions(+), 69 deletions(-) diff --git a/core/modules/commander.js b/core/modules/commander.js index 4d43c8dc2..ce7d17087 100644 --- a/core/modules/commander.js +++ b/core/modules/commander.js @@ -16,7 +16,7 @@ The $tw.Commander class is a command interpreter Parse a sequence of commands commandTokens: an array of command string tokens wiki: reference to the wiki store object - streams: {output:, error:}, each of which has a write(string) method + streams: {output:, input:, error:} callback: a callback invoked as callback(err) where err is null if there was no error */ var Commander = function(commandTokens,callback,wiki,streams) { @@ -65,52 +65,95 @@ Commander.prototype.execute = function() { }; /* -Returns the next string token without consuming it, or null if there are none left +Returns the next string token without consuming it, or null if there are none left. Callback invoked(err,data) */ -Commander.prototype.peekNextToken = function() { +Commander.prototype.peekNextToken = function(callback) { + var self = this; if(this.nextToken >= this.commandTokens.length) { - return null; + return callback(null,null); } else { - return this.stringifyToken(this.nextToken); + return this.stringifyToken(this.nextToken,function(err,data) { + if(!err) { + // Save the stringified token for next time so that we don't run prompts twice + self.commandTokens[self.nextToken] = data; + } + callback(err,data); + }); } }; /* -Returns and consumes the next string token, or null if there are none left +Returns and consumes the next string token, or null if there are none left. Callback invoked(err,data) */ -Commander.prototype.getNextToken = function() { +Commander.prototype.getNextToken = function(callback) { if(this.nextToken >= this.commandTokens.length) { - return null; + return callback(null,null); } else { - return this.stringifyToken(this.nextToken++); + return this.stringifyToken(this.nextToken++,callback); } }; +/* +Returns and consumes the string tokens until the end of the token stream or the first token that starts with "--". +Callback invoked(err,tokenArray) +*/ +Commander.prototype.getTokensUntilCommand = function(callback) { + var self = this, + tokens = []; + function processNextToken() { + self.peekNextToken(function(err,data) { + if(err) { + return callback(err); + } + if(!data || data.substr(0,2) === "--") { + return callback(null,tokens); + } else { + self.getNextToken(function(err,data) { + if(err) { + return callback(err); + } + tokens.push(data); + processNextToken(); + }); + } + }); + } + processNextToken(); +}; /* -Returns a specified stringified token, or null if the index does not exist +Returns a specified stringified token, or null if the index does not exist. Callback invoked(err,data) */ -Commander.prototype.stringifyToken = function(index) { +Commander.prototype.stringifyToken = function(index,callback) { + var self = this; if(index >= this.commandTokens.length) { - return null; + return callback(null,null); } else { var token = this.commandTokens[index]; if(typeof token === "string") { - return token; + return callback(null,token); } else if(typeof token === "object") { switch(token.type) { case "filter": - return this.wiki.filterTiddlers(token.text)[0] || ""; - break; + return callback(null,this.wiki.filterTiddlers(token.text)[0] || ""); case "wikified": - return this.wiki.renderText("text/plain","text/vnd.tiddlywiki",token.text,{ + return callback(null,this.wiki.renderText("text/plain","text/vnd.tiddlywiki",token.text,{ parseAsInline: false, parentWidget: $tw.rootWidget + })); + case "prompt": + $tw.utils.terminalQuestion({ + promptText: token.prompt || "Please enter a value", + defaultResult: token["default"] || "", + callback: function(err,userText) { + callback(err,userText); + }, + input: self.streams.input, + output: self.streams.output, }); break; default: throw "Unknown dynamic command token type: " + token.type; - break; } } } @@ -122,62 +165,64 @@ Execute the next command in the sequence Commander.prototype.executeNextCommand = function() { var self = this; // Get and check the command token - var commandName = this.getNextToken(); - if(!commandName) { - return this.callback(null); - } - if(commandName.substr(0,2) !== "--") { - this.callback("Missing command: " + commandName); - } else { - commandName = commandName.substr(2); // Trim off the -- - // Accumulate the parameters to the command - var params = [], - nextToken = this.peekNextToken(); - while(typeof nextToken === "string" && nextToken.substr(0,2) !== "--") { - params.push(this.getNextToken()); - nextToken = this.peekNextToken(); + var commandName = this.getNextToken(function(err,commandName) { + if(err) { + return self.callback(err); } - // Get the command info - var command = $tw.commands[commandName], - c,err; - if(!command) { - this.callback("Unknown command: " + commandName); + if(!commandName) { + return self.callback(null); + } + if(commandName.substr(0,2) !== "--") { + return self.callback("Missing command: " + commandName); } else { - if(this.verbose) { - this.streams.output.write("Executing command: " + commandName + " " + params.join(" ") + "\n"); - } - // Parse named parameters if required - if(command.info.namedParameterMode) { - params = this.extractNamedParameters(params,command.info.mandatoryParameters); - if(typeof params === "string") { - return this.callback(params); - } - } - if(command.info.synchronous) { - // Synchronous command - c = new command.Command(params,this); - err = c.execute(); + commandName = commandName.substr(2); // Trim off the -- + // Get the parameters to the command + self.getTokensUntilCommand(function(err,params) { if(err) { - this.callback(err); + return self.callback(err); + } + var command = $tw.commands[commandName], + c,err; + if(!command) { + self.callback("Unknown command: " + commandName); } else { - this.executeNextCommand(); - } - } else { - // Asynchronous command - c = new command.Command(params,this,function(err) { - if(err) { - self.callback(err); - } else { - self.executeNextCommand(); + if(self.verbose) { + self.streams.output.write("Executing command: " + commandName + " " + params.join(" ") + "\n"); + } + // Parse named parameters if required + if(command.info.namedParameterMode) { + params = self.extractNamedParameters(params,command.info.mandatoryParameters); + if(typeof params === "string") { + return self.callback(params); + } + } + if(command.info.synchronous) { + // Synchronous command + c = new command.Command(params,self); + err = c.execute(); + if(err) { + self.callback(err); + } else { + self.executeNextCommand(); + } + } else { + // Asynchronous command + c = new command.Command(params,self,function(err) { + if(err) { + self.callback(err); + } else { + self.executeNextCommand(); + } + }); + err = c.execute(); + if(err) { + self.callback(err); + } } - }); - err = c.execute(); - if(err) { - this.callback(err); } - } + }); } - } + }); }; /* diff --git a/core/modules/startup/commands.js b/core/modules/startup/commands.js index bd8b4afb3..d30a96fa5 100644 --- a/core/modules/startup/commands.js +++ b/core/modules/startup/commands.js @@ -29,7 +29,11 @@ exports.startup = function(callback) { callback(); }, $tw.wiki, - {output: process.stdout, error: process.stderr} + { + output: process.stdout, + input: process.stdin, + error: process.stderr + } ); commander.execute(); }; diff --git a/core/modules/utils/utils.js b/core/modules/utils/utils.js index 234de0c75..8cd6032b9 100644 --- a/core/modules/utils/utils.js +++ b/core/modules/utils/utils.js @@ -21,6 +21,50 @@ exports.log = function(text,colour) { console.log($tw.node ? exports.terminalColour(colour) + text + exports.terminalColour() : text); }; +/* +Prompts the user for input and handles the input using a callback function. Options: +promptText: Prompt text +defaultResult: Default result returned if the user types enter +callback: callback invoked (err,usertext) +input: optional input stream +output: optional output stream +*/ +exports.terminalQuestion = function(options) { + var readline = require("readline"), + promptText = options.promptText, + defaultResult = options.defaultResult, + callback = options.callback, + inputStream = options.input || process.stdin, + outputStream = options.output || process.stdout; + var rl = readline.createInterface({ + input: inputStream, + output: outputStream + }); + // Handle errors on the input stream + rl.on("error",function(err) { + rl.close(); + callback(err); // Pass the error to the callback + }); + // Prompt user for input + var prompt = exports.terminalColourText(promptText,"yellow"); + if(defaultResult) { + prompt += exports.terminalColourText(" (" + defaultResult + ")","blue"); + } + prompt += exports.terminalColourText(": "); + rl.question(prompt,function(input) { + // Use default value for the empty string + if(input === "" && defaultResult) { + input = defaultResult; + } + callback(null,input); // Pass the input to the callback + rl.close(); + }); +}; + +exports.terminalColourText = function(text,colour) { + return exports.terminalColour(colour) + text + exports.terminalColour("reset"); +}; + exports.terminalColour = function(colour) { if(!$tw.browser && $tw.node && process.stdout.isTTY) { if(colour) { @@ -36,14 +80,24 @@ exports.terminalColour = function(colour) { }; exports.terminalColourLookup = { + "reset": "0;0", "black": "0;30", "red": "0;31", "green": "0;32", - "brown/orange": "0;33", + "yellow": "0;33", + "brown/orange": "0;33", // Backwards compatbility "blue": "0;34", "purple": "0;35", "cyan": "0;36", - "light gray": "0;37" + "white": "0;37", + "gray": "0;90", + "light red": "0;91", + "light green": "0;92", + "light yellow": "0;93", + "light blue": "0;94", + "light purple": "0;95", + "light cyan": "0;96", + "light gray": "0;97" }; /* diff --git a/editions/tw5.com/tiddlywiki.info b/editions/tw5.com/tiddlywiki.info index 467efc836..f63c440a1 100644 --- a/editions/tw5.com/tiddlywiki.info +++ b/editions/tw5.com/tiddlywiki.info @@ -33,6 +33,12 @@ { "type": "filter", "text": "[!match[5.3.6-prerelease]then[text/html]else[text/plain]]" + }, + "dinghy", + { + "type": "prompt", + "prompt": "Please enter the name of your new wiki", + "default": "untitled" } ], "index": [