From 50118dbe138dee24d2037431d77db1a19280a3b5 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 19 Oct 2024 12:26:11 +0100 Subject: [PATCH 1/8] Initial commit This commit adds support for dynamic tokens within build commands. See the tiddlywiki.info file. Also adds an echo command to make debugging easier. I also intend to add a node type for prompting the user for a string. --- core/modules/commander.js | 145 +++++++++++++++++++++---------- core/modules/commands/echo.js | 32 +++++++ editions/tw5.com/tiddlywiki.info | 12 +++ 3 files changed, 142 insertions(+), 47 deletions(-) create mode 100644 core/modules/commands/echo.js diff --git a/core/modules/commander.js b/core/modules/commander.js index b55679a2e..4d43c8dc2 100644 --- a/core/modules/commander.js +++ b/core/modules/commander.js @@ -64,65 +64,116 @@ Commander.prototype.execute = function() { this.executeNextCommand(); }; +/* +Returns the next string token without consuming it, or null if there are none left +*/ +Commander.prototype.peekNextToken = function() { + if(this.nextToken >= this.commandTokens.length) { + return null; + } else { + return this.stringifyToken(this.nextToken); + } +}; + +/* +Returns and consumes the next string token, or null if there are none left +*/ +Commander.prototype.getNextToken = function() { + if(this.nextToken >= this.commandTokens.length) { + return null; + } else { + return this.stringifyToken(this.nextToken++); + } +}; + + +/* +Returns a specified stringified token, or null if the index does not exist +*/ +Commander.prototype.stringifyToken = function(index) { + if(index >= this.commandTokens.length) { + return null; + } else { + var token = this.commandTokens[index]; + if(typeof token === "string") { + return token; + } else if(typeof token === "object") { + switch(token.type) { + case "filter": + return this.wiki.filterTiddlers(token.text)[0] || ""; + break; + case "wikified": + return this.wiki.renderText("text/plain","text/vnd.tiddlywiki",token.text,{ + parseAsInline: false, + parentWidget: $tw.rootWidget + }); + break; + default: + throw "Unknown dynamic command token type: " + token.type; + break; + } + } + } +}; + /* Execute the next command in the sequence */ Commander.prototype.executeNextCommand = function() { var self = this; - // Invoke the callback if there are no more commands - if(this.nextToken >= this.commandTokens.length) { - this.callback(null); + // 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 { - // Get and check the command token - var commandName = this.commandTokens[this.nextToken++]; - if(commandName.substr(0,2) !== "--") { - this.callback("Missing command: " + commandName); + 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(); + } + // Get the command info + var command = $tw.commands[commandName], + c,err; + if(!command) { + this.callback("Unknown command: " + commandName); } else { - commandName = commandName.substr(2); // Trim off the -- - // Accumulate the parameters to the command - var params = []; - while(this.nextToken < this.commandTokens.length && - this.commandTokens[this.nextToken].substr(0,2) !== "--") { - params.push(this.commandTokens[this.nextToken++]); + if(this.verbose) { + this.streams.output.write("Executing command: " + commandName + " " + params.join(" ") + "\n"); } - // Get the command info - var command = $tw.commands[commandName], - c,err; - if(!command) { - this.callback("Unknown 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); } - // 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(); - if(err) { - this.callback(err); - } else { - this.executeNextCommand(); - } + } + if(command.info.synchronous) { + // Synchronous command + c = new command.Command(params,this); + err = c.execute(); + if(err) { + this.callback(err); } else { - // Asynchronous command - c = new command.Command(params,this,function(err) { - if(err) { - self.callback(err); - } else { - self.executeNextCommand(); - } - }); - err = c.execute(); + this.executeNextCommand(); + } + } else { + // Asynchronous command + c = new command.Command(params,this,function(err) { if(err) { - this.callback(err); + self.callback(err); + } else { + self.executeNextCommand(); } + }); + err = c.execute(); + if(err) { + this.callback(err); } } } diff --git a/core/modules/commands/echo.js b/core/modules/commands/echo.js new file mode 100644 index 000000000..366cd8a53 --- /dev/null +++ b/core/modules/commands/echo.js @@ -0,0 +1,32 @@ +/*\ +title: $:/core/modules/commands/echo.js +type: application/javascript +module-type: command + +Command to echo input parameters + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +exports.info = { + name: "echo", + synchronous: true +}; + +var Command = function(params,commander) { + this.params = params; + this.commander = commander; +}; + +Command.prototype.execute = function() { + this.commander.streams.output.write(JSON.stringify(this.params,null,4) + "\n"); + return null; +}; + +exports.Command = Command; + +})(); diff --git a/editions/tw5.com/tiddlywiki.info b/editions/tw5.com/tiddlywiki.info index 2f3ddade8..467efc836 100644 --- a/editions/tw5.com/tiddlywiki.info +++ b/editions/tw5.com/tiddlywiki.info @@ -23,6 +23,18 @@ "languages": [ ], "build": { + "dynamic": [ + "--echo","testing", + { + "type": "wikified", + "text": "<>-prod.html" + }, + "thingy", + { + "type": "filter", + "text": "[!match[5.3.6-prerelease]then[text/html]else[text/plain]]" + } + ], "index": [ "--savetiddlers","[tag[external-image]]","images", "--render","[tag[external-text]]","[encodeuricomponent[]addprefix[text/]addsuffix[.tid]]","text/plain","$:/core/templates/tid-tiddler", From 4a6501778a9ca32e36bfd6ca9a601b8388a08745 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 19 Oct 2024 15:54:31 +0100 Subject: [PATCH 2/8] 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": [ From 5233d7264291680fc262e9a630fbbe3a6aeb97af Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 19 Oct 2024 16:13:28 +0100 Subject: [PATCH 3/8] Documentation --- core/language/en-GB/Help/echo.tid | 8 +++ core/modules/commander.js | 2 +- .../tw5.com/tiddlers/commands/EchoCommand.tid | 8 +++ .../tiddlers/nodejs/tiddlywiki.info_Files.tid | 51 ++++++++++++++++--- editions/tw5.com/tiddlywiki.info | 2 +- 5 files changed, 62 insertions(+), 9 deletions(-) create mode 100644 core/language/en-GB/Help/echo.tid create mode 100644 editions/tw5.com/tiddlers/commands/EchoCommand.tid diff --git a/core/language/en-GB/Help/echo.tid b/core/language/en-GB/Help/echo.tid new file mode 100644 index 000000000..5e8ac35ac --- /dev/null +++ b/core/language/en-GB/Help/echo.tid @@ -0,0 +1,8 @@ +title: $:/language/Help/echo +description: Displays all the passed arguments + +Displays all the passed arguments to a command. Useful for debugging. + +``` +--echo * +``` diff --git a/core/modules/commander.js b/core/modules/commander.js index ce7d17087..f99fe70f1 100644 --- a/core/modules/commander.js +++ b/core/modules/commander.js @@ -136,7 +136,7 @@ Commander.prototype.stringifyToken = function(index,callback) { switch(token.type) { case "filter": return callback(null,this.wiki.filterTiddlers(token.text)[0] || ""); - case "wikified": + case "wikify": return callback(null,this.wiki.renderText("text/plain","text/vnd.tiddlywiki",token.text,{ parseAsInline: false, parentWidget: $tw.rootWidget diff --git a/editions/tw5.com/tiddlers/commands/EchoCommand.tid b/editions/tw5.com/tiddlers/commands/EchoCommand.tid new file mode 100644 index 000000000..81ea698ba --- /dev/null +++ b/editions/tw5.com/tiddlers/commands/EchoCommand.tid @@ -0,0 +1,8 @@ +caption: echo +created: 20241019150907690 +modified: 20241019150907690 +tags: Commands +title: EchoCommand +type: text/vnd.tiddlywiki + +{{$:/language/Help/echo}} diff --git a/editions/tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid b/editions/tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid index dddcd3106..fee95b89d 100644 --- a/editions/tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid +++ b/editions/tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid @@ -1,17 +1,17 @@ created: 20161015114042793 -modified: 20211114101249016 +modified: 20241019145819938 tags: TiddlyWikiFolders [[TiddlyWiki on Node.js]] title: tiddlywiki.info Files type: text/vnd.tiddlywiki [[TiddlyWikiFolders]] are configured with a single `tiddlywiki.info` file in the root of the wiki folder. It should contain a JSON object comprising the following properties: -* ''plugins'' - an array of plugin names to be included in the wiki -* ''themes'' - an array of theme names to be included in the wiki -* ''languages'' - an array of language names to be included in the wiki -* ''includeWikis'' - an array of references to external wiki folders to be included in the wiki -* ''build'' - a hashmap of named build targets, each defined by an array of command tokens (see BuildCommand) -* ''config'' - an optional hashmap of configuration options (see below) +* ''plugins'' - optional array of plugin names to be included in the wiki +* ''themes'' - optional array of theme names to be included in the wiki +* ''languages'' - optional array of language names to be included in the wiki +* ''includeWikis'' - optional array of references to external wiki folders to be included in the wiki +* ''build'' - optional hashmap of named build targets, each defined by an array of command tokens (see BuildCommand) +* ''config'' - optional hashmap of configuration options (see below) !!! ''includeWikis'' @@ -22,8 +22,45 @@ The entries in the ''includeWikis'' array can be either a string specifying the !!! ''build'' +The ''build'' property contains a hashmap of named build targets. Each of the build targets is defined as an array of command tokens. + Note that the build targets of included wikis are merged if a target of that name isn't defined in the current `tiddlywiki.info` file. +Command tokens can be a simple string or a command token object with a ''type'' property. The following types are defined to allow the command token to be dynamically defined: + +* ''filter'': the value of the first result of a filter expression specified in the ''text'' property of the command token object +* ''wikify'': the result of wikifying a text string specified in the ''text'' property of the command token object +* ''prompt'': the result of prompting the user for a string. The ''prompt'' property of the command token object specifies the textual prompt to be used. The optional ''default'' property specifies a string value to be used if the user presses enter in response to the prompt + +The EchoCommand is useful for debugging complex dynamic command tokens. + +For example: + +``` +"build": { + "dynamic": [ + "--echo","testing", + "the following argument is wikified", + { + "type": "wikify", + "text": "<>-prod.html" + }, + "the following argument is a filter result", + { + "type": "filter", + "text": "[!match[5.3.6-prerelease]then[text/html]else[text/plain]]" + }, + "the following argument was provided by the user", + { + "type": "prompt", + "prompt": "Please enter some text and type enter", + "default": "Nothing" + } + ], +... +``` + + !!! ''config'' Configuration options include: diff --git a/editions/tw5.com/tiddlywiki.info b/editions/tw5.com/tiddlywiki.info index f63c440a1..f7a2b336e 100644 --- a/editions/tw5.com/tiddlywiki.info +++ b/editions/tw5.com/tiddlywiki.info @@ -26,7 +26,7 @@ "dynamic": [ "--echo","testing", { - "type": "wikified", + "type": "wikify", "text": "<>-prod.html" }, "thingy", From 2d89228d25e6bbe22517cf12965d6677d512cdf8 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 19 Oct 2024 16:16:01 +0100 Subject: [PATCH 4/8] Example should match the documentation --- editions/tw5.com/tiddlywiki.info | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/editions/tw5.com/tiddlywiki.info b/editions/tw5.com/tiddlywiki.info index f7a2b336e..14ee18e1e 100644 --- a/editions/tw5.com/tiddlywiki.info +++ b/editions/tw5.com/tiddlywiki.info @@ -23,22 +23,23 @@ "languages": [ ], "build": { - "dynamic": [ + "dynamic-example": [ "--echo","testing", + "the following argument is wikified", { "type": "wikify", "text": "<>-prod.html" }, - "thingy", + "the following argument is a filter result", { "type": "filter", "text": "[!match[5.3.6-prerelease]then[text/html]else[text/plain]]" }, - "dinghy", + "the following argument was provided by the user", { "type": "prompt", - "prompt": "Please enter the name of your new wiki", - "default": "untitled" + "prompt": "Please enter some text and type enter", + "default": "Nothing" } ], "index": [ From bd86723b72e9d679debab7c2bc8a50331b2d4fb3 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sat, 19 Oct 2024 17:05:34 +0100 Subject: [PATCH 5/8] Enhance terminal colour support --- core/modules/utils/utils.js | 94 +++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 25 deletions(-) diff --git a/core/modules/utils/utils.js b/core/modules/utils/utils.js index 8cd6032b9..42fd6155e 100644 --- a/core/modules/utils/utils.js +++ b/core/modules/utils/utils.js @@ -46,9 +46,10 @@ exports.terminalQuestion = function(options) { callback(err); // Pass the error to the callback }); // Prompt user for input - var prompt = exports.terminalColourText(promptText,"yellow"); + var prompt = exports.terminalColourText(promptText,["yellow","bold"]); if(defaultResult) { - prompt += exports.terminalColourText(" (" + defaultResult + ")","blue"); + prompt += " "; + prompt += exports.terminalColourText("(" + defaultResult + ")",["blue","underline"]); } prompt += exports.terminalColourText(": "); rl.question(prompt,function(input) { @@ -61,43 +62,86 @@ exports.terminalQuestion = function(options) { }); }; +/* +Wrap a string in colour codes. Colour can be an array +*/ exports.terminalColourText = function(text,colour) { - return exports.terminalColour(colour) + text + exports.terminalColour("reset"); + if(!$tw.utils.isArray(colour)) { + colour = [colour]; + } + $tw.utils.each(colour,function(code) { + text = exports.terminalColour(code) + text + exports.terminalColour(code,true); + }); + return text; }; -exports.terminalColour = function(colour) { +/* +Returns a terminal colour string. Set "closed" to true to return the closing code +*/ +exports.terminalColour = function(colour,closed) { if(!$tw.browser && $tw.node && process.stdout.isTTY) { if(colour) { var code = exports.terminalColourLookup[colour]; if(code) { - return "\x1b[" + code + "m"; + return "\x1b[" + code[closed ? 1 : 0] + "m"; } - } else { - return "\x1b[0m"; // Cancel colour } + return "\x1b[0m"; // Reset } return ""; }; exports.terminalColourLookup = { - "reset": "0;0", - "black": "0;30", - "red": "0;31", - "green": "0;32", - "yellow": "0;33", - "brown/orange": "0;33", // Backwards compatbility - "blue": "0;34", - "purple": "0;35", - "cyan": "0;36", - "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" + // Modifiers + "reset": [0,0], + "bold": [1,22], + "dim": [2,22], + "italic": [3,23], + "underline": [4,24], + "overline": [53,55], + "inverse": [7,27], + "hidden": [8,28], + "strikethrough": [9,29], + // Colours + "black": [30,39], + "red": [31,39], + "green": [32,39], + "yellow": [33,39], + "brown/orange": [33,39], // Backwards compatbility + "blue": [34,39], + "magenta": [35,39], // Backwards compatbility + "purple": [35,39], + "cyan": [36,39], + "white": [37,39], + "gray": [90,39], + "light red": [91,39], + "light green": [92,39], + "light yellow": [93,39], + "light blue": [94,39], + "light magenta": [95,39], + "light purple": [95,39], // Backwards compatbility + "light cyan": [96,39], + "light gray": [97,39], + // Background colours + "black background": [40,49], + "red background": [41,49], + "green background": [42,49], + "yellow background": [43,49], + "brown/orange background": [43,49], // Backwards compatbility + "blue background": [44,49], + "magenta background": [45,49], // Backwards compatbility + "purple background": [45,49], + "cyan background": [46,49], + "white background": [47,49], + "gray background": [100,49], + "light red background": [101,49], + "light green background": [102,49], + "light yellow background": [103,49], + "light blue background": [104,49], + "light magenta background": [105,49], + "light purple background": [105,49], // Backwards compatbility + "light cyan background": [106,49], + "light gray background": [107,49] }; /* From 8b2afd1cc23dccd87225916c8fc867ddd7f31da9 Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 20 Oct 2024 12:55:11 +0100 Subject: [PATCH 6/8] Fix bug processing empty string tokens --- core/modules/commander.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/modules/commander.js b/core/modules/commander.js index f99fe70f1..24d6ff6d5 100644 --- a/core/modules/commander.js +++ b/core/modules/commander.js @@ -105,7 +105,7 @@ Commander.prototype.getTokensUntilCommand = function(callback) { if(err) { return callback(err); } - if(!data || data.substr(0,2) === "--") { + if(data === null || data.substr(0,2) === "--") { return callback(null,tokens); } else { self.getNextToken(function(err,data) { From b31e2cd0db7b79bd5671a43d8557debafbcc3bbd Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 20 Oct 2024 12:55:21 +0100 Subject: [PATCH 7/8] Mark version in docs --- editions/tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editions/tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid b/editions/tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid index fee95b89d..1438e6880 100644 --- a/editions/tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid +++ b/editions/tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid @@ -26,7 +26,7 @@ The ''build'' property contains a hashmap of named build targets. Each of the bu Note that the build targets of included wikis are merged if a target of that name isn't defined in the current `tiddlywiki.info` file. -Command tokens can be a simple string or a command token object with a ''type'' property. The following types are defined to allow the command token to be dynamically defined: +Command tokens can be a simple string or <<.from-version "5.3.6">> a command token object with a ''type'' property. The following types are defined to allow the command token to be dynamically defined: * ''filter'': the value of the first result of a filter expression specified in the ''text'' property of the command token object * ''wikify'': the result of wikifying a text string specified in the ''text'' property of the command token object From e3714929f99c26a56387a9ed5827c5443f00ec9a Mon Sep 17 00:00:00 2001 From: Jeremy Ruston Date: Sun, 20 Oct 2024 13:02:47 +0100 Subject: [PATCH 8/8] Add support for a transformFilter for prompts --- core/modules/commander.js | 9 ++++++++- .../tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid | 2 +- editions/tw5.com/tiddlywiki.info | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/core/modules/commander.js b/core/modules/commander.js index 24d6ff6d5..1a58fbace 100644 --- a/core/modules/commander.js +++ b/core/modules/commander.js @@ -146,7 +146,14 @@ Commander.prototype.stringifyToken = function(index,callback) { promptText: token.prompt || "Please enter a value", defaultResult: token["default"] || "", callback: function(err,userText) { - callback(err,userText); + if(err) { + callback(err); + } else { + if(token.transformFilter) { + userText = self.wiki.filterTiddlers(token.transformFilter,null,self.wiki.makeTiddlerIterator([userText]))[0] || ""; + } + callback(null,userText); + } }, input: self.streams.input, output: self.streams.output, diff --git a/editions/tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid b/editions/tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid index 1438e6880..48d5ec4f8 100644 --- a/editions/tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid +++ b/editions/tw5.com/tiddlers/nodejs/tiddlywiki.info_Files.tid @@ -30,7 +30,7 @@ Command tokens can be a simple string or <<.from-version "5.3.6">> a command tok * ''filter'': the value of the first result of a filter expression specified in the ''text'' property of the command token object * ''wikify'': the result of wikifying a text string specified in the ''text'' property of the command token object -* ''prompt'': the result of prompting the user for a string. The ''prompt'' property of the command token object specifies the textual prompt to be used. The optional ''default'' property specifies a string value to be used if the user presses enter in response to the prompt +* ''prompt'': the result of prompting the user for a string. The ''prompt'' property of the command token object specifies the textual prompt to be used. The optional ''default'' property specifies a string value to be used if the user presses enter in response to the prompt. The optional ''transformFilter'' property specifies a filter to be applied to the user input to transform it into a command token. The string input by the user is available in the variable `user-input` The EchoCommand is useful for debugging complex dynamic command tokens. diff --git a/editions/tw5.com/tiddlywiki.info b/editions/tw5.com/tiddlywiki.info index 14ee18e1e..f9507b76a 100644 --- a/editions/tw5.com/tiddlywiki.info +++ b/editions/tw5.com/tiddlywiki.info @@ -39,7 +39,8 @@ { "type": "prompt", "prompt": "Please enter some text and type enter", - "default": "Nothing" + "default": "Nothing", + "transformFilter": "[addprefix[testing ]]" } ], "index": [