feat(): Add command parser for fns docstring
This commit is contained in:
parent
d5de0bfe54
commit
64815d15f6
72
log
72
log
|
@ -11,57 +11,47 @@ class Log extends This {
|
||||||
super.init(); // initialize commander with overridden version and description
|
super.init(); // initialize commander with overridden version and description
|
||||||
}
|
}
|
||||||
|
|
||||||
echo(str) {
|
echo(message) {
|
||||||
console.log(str);
|
/**
|
||||||
|
* @command
|
||||||
|
* @argument('<message>')
|
||||||
|
*/
|
||||||
|
console.log(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
success(str) {
|
success(message) {
|
||||||
console.log(this._echoInGreen(str));
|
/**
|
||||||
|
* @command
|
||||||
|
* @argument('<message>')
|
||||||
|
*/
|
||||||
|
console.log(this._echoInGreen(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
info(str) {
|
info(message) {
|
||||||
console.log(`[INFO] ${str}`);
|
/**
|
||||||
|
* @command
|
||||||
|
* @argument('<message>')
|
||||||
|
*/
|
||||||
|
console.log(`[INFO] ${message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
warn(str) {
|
warn(message) {
|
||||||
console.warn(`[WARN] ${this._echoInYellow(str)}`);
|
/**
|
||||||
|
* @command
|
||||||
|
* @argument('<message>')
|
||||||
|
*/
|
||||||
|
console.warn(`[WARN] ${this._echoInYellow(message)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
error(str) {
|
error(message) {
|
||||||
console.error(`[ERR] ${this._echoInRed(str)}`);
|
/**
|
||||||
|
* @command
|
||||||
|
* @argument('<message>')
|
||||||
|
*/
|
||||||
|
console.error(`[ERR] ${this._echoInRed(message)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
this.program.command('echo')
|
|
||||||
.argument('<message>')
|
|
||||||
.action(message => {
|
|
||||||
this.echo(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.program.command('success')
|
|
||||||
.argument('<message>')
|
|
||||||
.action(message => {
|
|
||||||
this.success(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.program.command('info')
|
|
||||||
.argument('<message>')
|
|
||||||
.action(message => {
|
|
||||||
this.info(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.program.command('warn')
|
|
||||||
.argument('<message>')
|
|
||||||
.action(message => {
|
|
||||||
this.warn(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.program.command('error')
|
|
||||||
.argument('<message>')
|
|
||||||
.action(message => {
|
|
||||||
this.error(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.program.parse();
|
this.program.parse();
|
||||||
|
|
||||||
if (Object.keys(this.program.opts()).length || this.program.args.length) {
|
if (Object.keys(this.program.opts()).length || this.program.args.length) {
|
||||||
|
@ -78,4 +68,4 @@ class Log extends This {
|
||||||
|
|
||||||
// main
|
// main
|
||||||
const log = new Log();
|
const log = new Log();
|
||||||
log.start();
|
log.start();
|
||||||
|
|
61
pm
61
pm
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
const This = require('./this');
|
const This = require('./this');
|
||||||
|
const util = require('util');
|
||||||
|
|
||||||
// Example of subcommands which are implemented as stand-alone executable files.
|
// Example of subcommands which are implemented as stand-alone executable files.
|
||||||
//
|
//
|
||||||
|
@ -17,21 +18,34 @@ class Pm extends This {
|
||||||
}
|
}
|
||||||
|
|
||||||
install() {
|
install() {
|
||||||
this.program.command('install [name]', 'install one or more packages').alias('i');
|
/**
|
||||||
|
* @command('install one or more packages')
|
||||||
|
* @alias('i')
|
||||||
|
*/
|
||||||
|
// Calls stand-alone excutable `pm-install` because of @command(<description>) in docstring
|
||||||
}
|
}
|
||||||
|
|
||||||
search() {
|
search() {
|
||||||
this.program.command('search [query]', 'search with optional query').alias('s');
|
/**
|
||||||
|
* @command('search with optional query')
|
||||||
|
* @alias('s')
|
||||||
|
*/
|
||||||
|
// Calls stand-alone excutable `pm-search` because of @command(<description>) in docstring
|
||||||
}
|
}
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
this.program.command('update', 'update installed packages', {
|
/**
|
||||||
executableFile: 'myUpdateSubCommand',
|
* @command('update installed packages')
|
||||||
});
|
* @executable('myUpdateSubCommand')
|
||||||
|
*/
|
||||||
|
// Calls stand-alone excutable `myUpdateSubCommand` because of @command(<description>) in docstring
|
||||||
}
|
}
|
||||||
|
|
||||||
list() {
|
list() {
|
||||||
this.program.command('list', 'list packages installed', { isDefault: false });
|
/**
|
||||||
|
* @command('list packages installed')
|
||||||
|
*/
|
||||||
|
// Calls stand-alone excutable `pm-list` because of @command(<description>) in docstring
|
||||||
}
|
}
|
||||||
|
|
||||||
// override This.discovery()
|
// override This.discovery()
|
||||||
|
@ -104,30 +118,21 @@ class Pm extends This {
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
// Usage
|
this.program.parse();
|
||||||
const pm = new Pm();
|
|
||||||
// Output: Methods of Pm: [ 'methodOne', 'methodTwo' ]
|
|
||||||
// Properties of Pm: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0
|
|
||||||
//pm.discovery();
|
|
||||||
|
|
||||||
// this.program
|
if (Object.keys(this.program.opts()).length || this.program.args.length) {
|
||||||
// .name(this.scriptName)
|
// Debugging commander options and arguments
|
||||||
// .version('0.0.1')
|
const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true });
|
||||||
// .description('Fake package manager')
|
const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true });
|
||||||
// .command('install [name]', 'install one or more packages')
|
this.execCmd(`${this.workingDir}/log echo 'Options: ${opts}'`);
|
||||||
// .alias('i')
|
this.execCmd(`${this.workingDir}/log echo 'Remaining arguments: ${args}'`);
|
||||||
// .command('search [query]', 'search with optional query')
|
} else {
|
||||||
// .alias('s')
|
this.program.outputHelp();
|
||||||
// .command('update', 'update installed packages', {
|
}
|
||||||
// executableFile: 'myUpdateSubCommand',
|
|
||||||
// })
|
|
||||||
// .command('list', 'list packages installed', { isDefault: false });
|
|
||||||
|
|
||||||
//this.program.parse();
|
|
||||||
|
|
||||||
// applies logDecorator to this.discovery() and binds it to 'this' to maintain the correct context
|
// applies logDecorator to this.discovery() and binds it to 'this' to maintain the correct context
|
||||||
const logDecoratedDiscovery = this._logDecorator(this.discovery).bind(this);
|
//const logDecoratedDiscovery = this._logDecorator(this.discovery).bind(this);
|
||||||
logDecoratedDiscovery();
|
//logDecoratedDiscovery();
|
||||||
|
|
||||||
// Try the following on macOS or Linux:
|
// Try the following on macOS or Linux:
|
||||||
// ./examples/pm
|
// ./examples/pm
|
||||||
|
@ -148,4 +153,4 @@ const pm = new Pm();
|
||||||
// logDecoratedDiscovery();
|
// logDecoratedDiscovery();
|
||||||
|
|
||||||
pm.start();
|
pm.start();
|
||||||
pm.selfTest();
|
//pm.selfTest();
|
||||||
|
|
100
this
100
this
|
@ -7,7 +7,7 @@ const shell = require('shelljs');
|
||||||
class This {
|
class This {
|
||||||
constructor() {
|
constructor() {
|
||||||
this.version = '0.0.1'; // Default version, can be overridden in subclasses
|
this.version = '0.0.1'; // Default version, can be overridden in subclasses
|
||||||
this.description = 'This is the parent class all other scripts should extend.'
|
this.description = 'This is the parent class all other scripts should extend.';
|
||||||
this.scriptName = path.parse(process.argv[1]).base;
|
this.scriptName = path.parse(process.argv[1]).base;
|
||||||
this.workingDir = path.parse(process.argv[1]).dir;
|
this.workingDir = path.parse(process.argv[1]).dir;
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,11 @@ class This {
|
||||||
// Highlight errors in color.
|
// Highlight errors in color.
|
||||||
outputError: (str, write) => write(this._echoInRed(str)),
|
outputError: (str, write) => write(this._echoInRed(str)),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// dynamically add all methods with appropriate docstring to commander
|
||||||
|
this.listMethods().forEach((method) => {
|
||||||
|
this._parseDocStringToCmd(this[method]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getClassName() {
|
getClassName() {
|
||||||
|
@ -90,11 +95,102 @@ class This {
|
||||||
Error.captureStackTrace(err, this._getCurrentFunctionName);
|
Error.captureStackTrace(err, this._getCurrentFunctionName);
|
||||||
|
|
||||||
// Extract the function name from the stack trace
|
// Extract the function name from the stack trace
|
||||||
const callerName = err.stack.split("\n")[1].trim().split(" ")[1];
|
const callerName = err.stack.split('\n')[1].trim().split(' ')[1];
|
||||||
|
|
||||||
return callerName;
|
return callerName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Utility function to extract docstring from a function, e.g.
|
||||||
|
//
|
||||||
|
// echo(str) {
|
||||||
|
// /**
|
||||||
|
// @command
|
||||||
|
// @argument('<message>')
|
||||||
|
// */
|
||||||
|
// console.log(str);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Attention:
|
||||||
|
// In JavaScript, when a script is executed as a bash script with a shebang (#!/usr/bin/env node),
|
||||||
|
// the toString method on functions does not include comments preceding the function definition.
|
||||||
|
_getFunctionDocString(fn) {
|
||||||
|
const fnStr = fn.toString();
|
||||||
|
const docStringMatch = fnStr.match(/\/\*[\s\S]*?\*\//);
|
||||||
|
return docStringMatch ? docStringMatch[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parser function to convert docstring to commander statement
|
||||||
|
_parseDocStringToCmd(fn) {
|
||||||
|
const docString = this._getFunctionDocString(fn);
|
||||||
|
if (!docString) {
|
||||||
|
// No docstring found
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract command and arguments
|
||||||
|
const commandMatch = docString.match(/@command(?:\('([^']*)'\))?/);
|
||||||
|
const aliasMatch = docString.match(/@alias\('(.+?)'\)/);
|
||||||
|
const argumentsMatch = /@argument\('(.+?)'\)/g;
|
||||||
|
const optionsMatch = /@option\('(.+?)'\)/g;
|
||||||
|
const defaultMatch = docString.match(/@default/);
|
||||||
|
const executableMatch = docString.match(/@executable\('(.+?)'\)/);
|
||||||
|
|
||||||
|
if (!commandMatch) {
|
||||||
|
// No @command tag found
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandName = fn.name;
|
||||||
|
const commandDescription = commandMatch ? commandMatch[1] : '';
|
||||||
|
const alias = aliasMatch ? aliasMatch[1] : '';
|
||||||
|
const defaultCommand = defaultMatch ? true : false;
|
||||||
|
const executable = executableMatch ? executableMatch[1] : '';
|
||||||
|
|
||||||
|
// Generate the commander statement
|
||||||
|
// Get the function reference
|
||||||
|
const func = this[commandName].bind(this);
|
||||||
|
|
||||||
|
// Create a command
|
||||||
|
// When `.command()` is invoked with a description argument,
|
||||||
|
// this tells Commander that you're going to use a stand-alone executable for the subcommand.
|
||||||
|
let command;
|
||||||
|
if (commandDescription) {
|
||||||
|
command = this.program.command(commandName, commandDescription, {
|
||||||
|
isDefault: defaultCommand,
|
||||||
|
executableFile: executable,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (alias) {
|
||||||
|
command.alias(alias);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
command = this.program.command(commandName, { isDefault: defaultCommand });
|
||||||
|
|
||||||
|
// Set the action for the command
|
||||||
|
command.action((arg) => {
|
||||||
|
func(arg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the commandName expects arguments, we add them here
|
||||||
|
let argMatch;
|
||||||
|
while ((argMatch = argumentsMatch.exec(docString)) !== null) {
|
||||||
|
command.argument(argMatch[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the commandName expects options, we add them here
|
||||||
|
let optMatch;
|
||||||
|
while ((optMatch = optionsMatch.exec(docString)) !== null) {
|
||||||
|
command.option(optMatch[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_addMethodsToClass(cls, metadata) {
|
||||||
|
if (cls) {
|
||||||
|
cls.commands.push(metadata);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Higher-order function to decorate other functions and provide logging
|
// Higher-order function to decorate other functions and provide logging
|
||||||
_logDecorator(fn) {
|
_logDecorator(fn) {
|
||||||
return function (...args) {
|
return function (...args) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user