From 94cc6816d594e58fa6e89456a90358daa276c90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Sat, 29 Jun 2024 11:11:48 +0200 Subject: [PATCH 01/12] feat(): Begin implementing class 'This' --- myUpdateSubCommand | 38 ++++++++++++++------- package.json | 2 +- pm | 66 ++++++++++++++++++++++------------- pm-install | 85 ++++++++++++++++++++++++++++------------------ pm-list | 29 ++++++++++++---- pm-search | 29 ++++++++++++---- pnpm-lock.yaml | 70 +++++++++++++++++++++++++------------- this | 39 +++++++++++++++++++++ 8 files changed, 249 insertions(+), 109 deletions(-) create mode 100644 this diff --git a/myUpdateSubCommand b/myUpdateSubCommand index 626480a..70cef5d 100755 --- a/myUpdateSubCommand +++ b/myUpdateSubCommand @@ -1,19 +1,31 @@ #!/usr/bin/env node +const This = require('./this'); const shell = require('shelljs'); -// 1) Use shell object with its methods -if (!shell.which('git')) { - shell.echo('Sorry, this script requires git'); - shell.exit(1); +class MyUpdateSubCommand extends This { + constructor() { + super(); + this.version = '0.0.1'; + } + + start() { + // Usage + const myUpdateSubCommand = new MyUpdateSubCommand(); + // Output: Methods of ExampleClass: [ 'methodOne', 'methodTwo' ] + // Properties of ExampleClass: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 + // myUpdateSubCommand.discovery(); + + if (!shell.which('git')) { + shell.echo('Sorry, this script requires git'); + shell.exit(1); + } else { + shell.echo('Woohoo! You have git installed!'); + shell.exit(0); + } + } } -// 2) or the following 'with' statement to specify that the `shell` object is the default object. -// The statements following the with statement refer to the echo methods, without specifying -// an object. JavaScript assumes the `shell` object for these references. -// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with -with (shell) { - echo('hello world'); - echo('-n', 'no newline at end -> '); - let str = echo('hello world'); -} \ No newline at end of file +// main +const myUpdateSubCommand = new MyUpdateSubCommand(); +myUpdateSubCommand.start(); diff --git a/package.json b/package.json index 2180896..7768209 100644 --- a/package.json +++ b/package.json @@ -10,4 +10,4 @@ "devDependencies": { "prettier": "^3.3.2" } -} \ No newline at end of file +} diff --git a/pm b/pm index 26c499d..16d6d4e 100755 --- a/pm +++ b/pm @@ -1,7 +1,7 @@ #!/usr/bin/env node -const path = require('path'); -const { Command } = require('commander'); +const This = require('./this'); +const {Command} = require('commander'); const program = new Command(); // Example of subcommands which are implemented as stand-alone executable files. @@ -10,28 +10,46 @@ const program = new Command(); // this tells Commander that you're going to use a stand-alone executable for the subcommand. // // Only `install` and `list` are implemented, see pm-install and pm-list.js +class Pm extends This { + constructor() { + super(); + this.version = '0.0.1'; + } -program - .name(path.parse(process.argv[1]).base) - .version('0.0.1') - .description('Fake package manager') - .command('install [name]', 'install one or more packages') - .alias('i') - .command('search [query]', 'search with optional query') - .alias('s') - .command('update', 'update installed packages', { - executableFile: 'myUpdateSubCommand', - }) - .command('list', 'list packages installed', { isDefault: false }); + start() { + // Usage + const pm = new Pm(); + // Output: Methods of ExampleClass: [ 'methodOne', 'methodTwo' ] + // Properties of ExampleClass: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 + // pm.discovery(); -program.parse(); + program + .name(this.scriptName) + .version('0.0.1') + .description('Fake package manager') + .command('install [name]', 'install one or more packages') + .alias('i') + .command('search [query]', 'search with optional query') + .alias('s') + .command('update', 'update installed packages', { + executableFile: 'myUpdateSubCommand', + }) + .command('list', 'list packages installed', {isDefault: false}); -// Try the following on macOS or Linux: -// ./examples/pm -// -// Try the following: -// ./pm -// ./pm help install -// ./pm install -h -// ./pm install foo bar baz -// ./pm install foo bar baz --force \ No newline at end of file + program.parse(); + + // Try the following on macOS or Linux: + // ./examples/pm + // + // Try the following: + // ./pm + // ./pm help install + // ./pm install -h + // ./pm install foo bar baz + // ./pm install foo bar baz --force + } +} + +// main +const pm = new Pm(); +pm.start(); diff --git a/pm-install b/pm-install index 3c80325..a0a2282 100755 --- a/pm-install +++ b/pm-install @@ -1,42 +1,61 @@ #!/usr/bin/env node -const path = require('path'); -const { Command } = require('commander'); +const This = require('./this'); +const {Command} = require('commander'); const program = new Command(); const errorColor = (str) => { // Add ANSI escape codes to display text in red. return `\x1b[31m${str}\x1b[0m`; +}; + +class PmInstall extends This { + constructor() { + super(); + this.version = '0.0.1'; + } + + start() { + // Usage + const pmInstall = new PmInstall(); + // Output: Methods of ExampleClass: [ 'methodOne', 'methodTwo' ] + // Properties of ExampleClass: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 + pmInstall.discovery(); + + program + .name(this.scriptName) + .version('0.0.1') + .description('Install package with fake package manager') + .usage('-n 3 32 -l x y z -- op') + .configureOutput({ + // Visibly override write routines as example! + //writeOut: (str) => process.stdout.write(`[OUT] ${str}`), + writeErr: (str) => process.stdout.write(`[ERR] ${str}`), + // Highlight errors in color. + outputError: (str, write) => write(errorColor(str)), + }); + + program + .option('-n, --number ', 'specify numbers') + .option('-l, --letter [letters...]', 'specify letters') + .parse(); + + // console.log('options array'); + // console.log(program.options.map(o => o.flags)); + + // console.log('visible options'); + // const helper = program.createHelp(); + // console.log(helper.visibleOptions(program).map(o => o.flags)); + + if (Object.keys(program.opts()).length || program.args.length) { + console.log('Options: ', program.opts()); + console.log('Remaining arguments: ', program.args); + } else { + program.outputHelp(); + } + } } -program - .name(path.parse(process.argv[1]).base) - .version('0.0.1') - .description('Install package with fake package manager') - .usage("-n 3 32 -l x y z -- op") - .configureOutput({ - // Visibly override write routines as example! - //writeOut: (str) => process.stdout.write(`[OUT] ${str}`), - writeErr: (str) => process.stdout.write(`[ERR] ${str}`), - // Highlight errors in color. - outputError: (str, write) => write(errorColor(str)) - }); - -program - .option('-n, --number ', 'specify numbers') - .option('-l, --letter [letters...]', 'specify letters') - .parse(); - -// console.log('options array'); -// console.log(program.options.map(o => o.flags)); - -// console.log('visible options'); -// const helper = program.createHelp(); -// console.log(helper.visibleOptions(program).map(o => o.flags)); - -if (Object.keys(program.opts()).length || program.args.length) { - console.log('Options: ', program.opts()); - console.log('Remaining arguments: ', program.args); -} else { - program.outputHelp(); -} \ No newline at end of file +// main +const pmInstall = new PmInstall(); +pmInstall.start(); diff --git a/pm-list b/pm-list index 4236bcd..3381913 100755 --- a/pm-list +++ b/pm-list @@ -1,11 +1,26 @@ #!/usr/bin/env node -const path = require('path'); -const { Command } = require('commander'); +const This = require('./this'); +const {Command} = require('commander'); const program = new Command(); -program - .name(path.parse(process.argv[1]).base) - .version('0.0.1') - .description('List packages of fake package manager') - .outputHelp(); +class PmList extends This { + constructor() { + super(); + this.version = '0.0.1'; + } + + start() { + // Usage + const pmList = new PmList(); + // Output: Methods of ExampleClass: [ 'methodOne', 'methodTwo' ] + // Properties of ExampleClass: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 + // pmList.discovery(); + + program.name(this.scriptName).version('0.0.1').description('List packages of fake package manager').outputHelp(); + } +} + +// main +const pmList = new PmList(); +pmList.start(); diff --git a/pm-search b/pm-search index dad72e6..8d8ac3f 100755 --- a/pm-search +++ b/pm-search @@ -1,11 +1,26 @@ #!/usr/bin/env node -const path = require('path'); -const { Command } = require('commander'); +const This = require('./this'); +const {Command} = require('commander'); const program = new Command(); -program - .name(path.parse(process.argv[1]).base) - .version('0.0.1') - .description('Search packages of fake package manager') - .outputHelp(); +class PmSearch extends This { + constructor() { + super(); + this.version = '0.0.1'; + } + + start() { + // Usage + const pmSearch = new PmSearch(); + // Output: Methods of ExampleClass: [ 'methodOne', 'methodTwo' ] + // Properties of ExampleClass: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 + // pmSearch.discovery(); + + program.name(this.scriptName).version('0.0.1').description('Search packages of fake package manager').outputHelp(); + } +} + +// main +const pmSearch = new PmSearch(); +pmSearch.start(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7162869..f6145c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,37 +21,43 @@ devDependencies: version: 3.3.2 packages: - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + resolution: + {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: false /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + resolution: + {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 dev: false /commander@12.1.0: - resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + resolution: + {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} dev: false /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: + {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: false /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + resolution: + {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: false /function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + resolution: + {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} dev: false /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + resolution: + {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported dependencies: fs.realpath: 1.0.0 @@ -63,14 +69,16 @@ packages: dev: false /hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + resolution: + {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 dev: false /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + resolution: + {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. dependencies: once: 1.4.0 @@ -78,62 +86,73 @@ packages: dev: false /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + resolution: + {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: false /interpret@1.4.0: - resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} + resolution: + {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} engines: {node: '>= 0.10'} dev: false /is-core-module@2.14.0: - resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} + resolution: + {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 dev: false /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + resolution: + {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 dev: false /omelette@0.4.17: - resolution: {integrity: sha512-UlU69G6Bhu0XFjw3tjFZ0qyiMUjAOR+rdzblA1nLQ8xiqFtxOVlkhM39BlgTpLFx9fxkm6rnxNNRsS5GxE/yww==} + resolution: + {integrity: sha512-UlU69G6Bhu0XFjw3tjFZ0qyiMUjAOR+rdzblA1nLQ8xiqFtxOVlkhM39BlgTpLFx9fxkm6rnxNNRsS5GxE/yww==} engines: {node: '>=0.8.0'} dev: false /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + resolution: + {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 dev: false /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + resolution: + {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} dev: false /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + resolution: + {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: false /prettier@3.3.2: - resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} + resolution: + {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} engines: {node: '>=14'} hasBin: true dev: true /rechoir@0.6.2: - resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} + resolution: + {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} dependencies: resolve: 1.22.8 dev: false /resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + resolution: + {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true dependencies: is-core-module: 2.14.0 @@ -142,7 +161,8 @@ packages: dev: false /shelljs@0.8.5: - resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} + resolution: + {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} engines: {node: '>=4'} hasBin: true dependencies: @@ -152,10 +172,12 @@ packages: dev: false /supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + resolution: + {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} dev: false /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + resolution: + {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: false diff --git a/this b/this new file mode 100644 index 0000000..c3d1ac1 --- /dev/null +++ b/this @@ -0,0 +1,39 @@ +#!/usr/bin/env node + +const path = require('path'); + +class This { + constructor() { + this.version = '0.0.1'; // Default version, can be overridden in subclasses + this.scriptName = path.parse(process.argv[1]).base; + } + + getClassName() { + return this.constructor.name; + } + + listMethods() { + const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter( + (prop) => typeof this[prop] === 'function' && prop !== 'constructor' && !prop.startsWith('_') + ); + return methods; + } + + listProperties() { + const properties = Object.keys(this).filter((prop) => typeof this[prop] !== 'function'); + return properties; + } + + discovery() { + console.log(`My name is '${this.getClassName()}' and I have the version: ${this.version}`); + console.log(`My methods are:`, this.listMethods()); + console.log(`My properties are:`, this.listProperties()); + } + + // Method to create a context manager for this instance + withContext(callback) { + callback(this); + } +} + +module.exports = This; -- 2.45.1 From e77fab3b84fbb6168e8d71bfda03f1cc4c60dc4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Sat, 29 Jun 2024 12:20:57 +0200 Subject: [PATCH 02/12] feat(): Add higher-order fn to decorate other fns for extended logging --- pm | 47 ++++++++++++++++++++++++++++++++--------------- this | 10 ++++++++++ 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/pm b/pm index 16d6d4e..71ee66d 100755 --- a/pm +++ b/pm @@ -1,8 +1,7 @@ #!/usr/bin/env node const This = require('./this'); -const {Command} = require('commander'); -const program = new Command(); +const { Command } = require('commander'); // Example of subcommands which are implemented as stand-alone executable files. // @@ -14,6 +13,16 @@ class Pm extends This { constructor() { super(); this.version = '0.0.1'; + this.description = 'Fake package manager'; + + // implement commander abilities + this.program = new Command().name(this.scriptName).version(this.version).description(this.description); + } + + // override This.discovery() + discovery() { + const methods = this.listMethods(); + return methods; } start() { @@ -23,20 +32,24 @@ class Pm extends This { // Properties of ExampleClass: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 // pm.discovery(); - program - .name(this.scriptName) - .version('0.0.1') - .description('Fake package manager') - .command('install [name]', 'install one or more packages') - .alias('i') - .command('search [query]', 'search with optional query') - .alias('s') - .command('update', 'update installed packages', { - executableFile: 'myUpdateSubCommand', - }) - .command('list', 'list packages installed', {isDefault: false}); + // this.program + // .name(this.scriptName) + // .version('0.0.1') + // .description('Fake package manager') + // .command('install [name]', 'install one or more packages') + // .alias('i') + // .command('search [query]', 'search with optional query') + // .alias('s') + // .command('update', 'update installed packages', { + // executableFile: 'myUpdateSubCommand', + // }) + // .command('list', 'list packages installed', { isDefault: false }); - program.parse(); + //this.program.parse(); + + // applies logDecorator to this.discovery() and binds it to 'this' to maintain the correct context + const logDecoratedDiscovery = this._logDecorator(this.discovery).bind(this); + logDecoratedDiscovery(); // Try the following on macOS or Linux: // ./examples/pm @@ -52,4 +65,8 @@ class Pm extends This { // main const pm = new Pm(); +// // applies logDecorator to this.discovery() and binds it to the instance to maintain the correct context +// const logDecoratedDiscovery = pm._logDecorator(pm.discovery).bind(pm); +// logDecoratedDiscovery(); + pm.start(); diff --git a/this b/this index c3d1ac1..ad53fd2 100644 --- a/this +++ b/this @@ -34,6 +34,16 @@ class This { withContext(callback) { callback(this); } + + // Higher-order function to decorate other functions and provide logging + _logDecorator(fn) { + return function (...args) { + console.log(`Calling ${fn.name} with arguments:`, args); + const result = fn.apply(this, args); // Use apply to maintain context + console.log(`Result of ${fn.name}:`, result); + return result; + }; + } } module.exports = This; -- 2.45.1 From b3b2505a9782ef6df01b9489ff727d207da8ad86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Sat, 29 Jun 2024 12:22:07 +0200 Subject: [PATCH 03/12] chore(): Add bracketSpacing in code style --- .prettierrc.json | 2 +- pm-install | 4 +-- pm-list | 2 +- pm-search | 2 +- pnpm-lock.yaml | 66 ++++++++++++++++++++++++------------------------ 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/.prettierrc.json b/.prettierrc.json index 2707301..1d97e3f 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -5,7 +5,7 @@ "semi": true, "singleQuote": true, "trailingComma": "es5", - "bracketSpacing": false, + "bracketSpacing": true, "arrowParens": "always", "endOfLine": "lf" } diff --git a/pm-install b/pm-install index a0a2282..1f42d60 100755 --- a/pm-install +++ b/pm-install @@ -1,7 +1,7 @@ #!/usr/bin/env node const This = require('./this'); -const {Command} = require('commander'); +const { Command } = require('commander'); const program = new Command(); const errorColor = (str) => { @@ -20,7 +20,7 @@ class PmInstall extends This { const pmInstall = new PmInstall(); // Output: Methods of ExampleClass: [ 'methodOne', 'methodTwo' ] // Properties of ExampleClass: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 - pmInstall.discovery(); + // pmInstall.discovery(); program .name(this.scriptName) diff --git a/pm-list b/pm-list index 3381913..2ef0455 100755 --- a/pm-list +++ b/pm-list @@ -1,7 +1,7 @@ #!/usr/bin/env node const This = require('./this'); -const {Command} = require('commander'); +const { Command } = require('commander'); const program = new Command(); class PmList extends This { diff --git a/pm-search b/pm-search index 8d8ac3f..f2fea7e 100755 --- a/pm-search +++ b/pm-search @@ -1,7 +1,7 @@ #!/usr/bin/env node const This = require('./this'); -const {Command} = require('commander'); +const { Command } = require('commander'); const program = new Command(); class PmSearch extends This { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6145c5..a6ff437 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,12 +23,12 @@ devDependencies: packages: /balanced-match@1.0.2: resolution: - {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + { integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== } dev: false /brace-expansion@1.1.11: resolution: - {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + { integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== } dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 @@ -36,28 +36,28 @@ packages: /commander@12.1.0: resolution: - {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} - engines: {node: '>=18'} + { integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== } + engines: { node: '>=18' } dev: false /concat-map@0.0.1: resolution: - {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + { integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== } dev: false /fs.realpath@1.0.0: resolution: - {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + { integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== } dev: false /function-bind@1.1.2: resolution: - {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + { integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== } dev: false /glob@7.2.3: resolution: - {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + { integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== } deprecated: Glob versions prior to v9 are no longer supported dependencies: fs.realpath: 1.0.0 @@ -70,15 +70,15 @@ packages: /hasown@2.0.2: resolution: - {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} + { integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== } + engines: { node: '>= 0.4' } dependencies: function-bind: 1.1.2 dev: false /inflight@1.0.6: resolution: - {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + { integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== } deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. dependencies: once: 1.4.0 @@ -87,72 +87,72 @@ packages: /inherits@2.0.4: resolution: - {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + { integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== } dev: false /interpret@1.4.0: resolution: - {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} - engines: {node: '>= 0.10'} + { integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== } + engines: { node: '>= 0.10' } dev: false /is-core-module@2.14.0: resolution: - {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} - engines: {node: '>= 0.4'} + { integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A== } + engines: { node: '>= 0.4' } dependencies: hasown: 2.0.2 dev: false /minimatch@3.1.2: resolution: - {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + { integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== } dependencies: brace-expansion: 1.1.11 dev: false /omelette@0.4.17: resolution: - {integrity: sha512-UlU69G6Bhu0XFjw3tjFZ0qyiMUjAOR+rdzblA1nLQ8xiqFtxOVlkhM39BlgTpLFx9fxkm6rnxNNRsS5GxE/yww==} - engines: {node: '>=0.8.0'} + { integrity: sha512-UlU69G6Bhu0XFjw3tjFZ0qyiMUjAOR+rdzblA1nLQ8xiqFtxOVlkhM39BlgTpLFx9fxkm6rnxNNRsS5GxE/yww== } + engines: { node: '>=0.8.0' } dev: false /once@1.4.0: resolution: - {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + { integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== } dependencies: wrappy: 1.0.2 dev: false /path-is-absolute@1.0.1: resolution: - {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} + { integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== } + engines: { node: '>=0.10.0' } dev: false /path-parse@1.0.7: resolution: - {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + { integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== } dev: false /prettier@3.3.2: resolution: - {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} - engines: {node: '>=14'} + { integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA== } + engines: { node: '>=14' } hasBin: true dev: true /rechoir@0.6.2: resolution: - {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} - engines: {node: '>= 0.10'} + { integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== } + engines: { node: '>= 0.10' } dependencies: resolve: 1.22.8 dev: false /resolve@1.22.8: resolution: - {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + { integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== } hasBin: true dependencies: is-core-module: 2.14.0 @@ -162,8 +162,8 @@ packages: /shelljs@0.8.5: resolution: - {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} - engines: {node: '>=4'} + { integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== } + engines: { node: '>=4' } hasBin: true dependencies: glob: 7.2.3 @@ -173,11 +173,11 @@ packages: /supports-preserve-symlinks-flag@1.0.0: resolution: - {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} + { integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== } + engines: { node: '>= 0.4' } dev: false /wrappy@1.0.2: resolution: - {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + { integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== } dev: false -- 2.45.1 From 4f15c610199ddcb5598f3c8c8c51f5a0f5cb1729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Sat, 29 Jun 2024 16:02:43 +0200 Subject: [PATCH 04/12] feat(): Create `log` script and add to `pm-install` --- log | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ pm-install | 43 +++++++++------------------------ this | 54 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 31 deletions(-) create mode 100755 log diff --git a/log b/log new file mode 100755 index 0000000..e3c9afc --- /dev/null +++ b/log @@ -0,0 +1,71 @@ +#!/usr/bin/env node + +const This = require('./this'); +const util = require('util'); + +class Log extends This { + constructor() { + super(); + this.version = '0.0.1'; + this.description = 'Helper script for building log messages'; + super.init(); // initialize commander with overridden version and description + } + + echo(str) { + console.log(str); + } + + info(str) { + console.log(`[INFO] ${str}`); + } + + warn(str) { + console.warn(`[WARN] ${this._echoInYellow(str)}`); + } + + error(str) { + console.error(`[ERR] ${this._echoInRed(str)}`); + } + + start() { + this.program.command('echo') + .argument('') + .action(message => { + this.echo(message); + }); + + this.program.command('info') + .argument('') + .action(message => { + this.info(message); + }); + + this.program.command('warn') + .argument('') + .action(message => { + this.warn(message); + }); + + this.program.command('error') + .argument('') + .action(message => { + this.error(message); + }); + + this.program.parse(); + + if (Object.keys(this.program.opts()).length || this.program.args.length) { + //// Debugging commander options and arguments + const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); + const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); + //this.echo(`Options: ${opts}`); + //this.echo(`Remaining arguments: ${args}`); + } else { + this.program.outputHelp(); + } + } +} + +// main +const log = new Log(); +log.start(); \ No newline at end of file diff --git a/pm-install b/pm-install index 1f42d60..42ec793 100755 --- a/pm-install +++ b/pm-install @@ -1,18 +1,14 @@ #!/usr/bin/env node const This = require('./this'); -const { Command } = require('commander'); -const program = new Command(); - -const errorColor = (str) => { - // Add ANSI escape codes to display text in red. - return `\x1b[31m${str}\x1b[0m`; -}; +const util = require('util'); class PmInstall extends This { constructor() { super(); this.version = '0.0.1'; + this.description = 'Install package with fake package manager' + super.init(); // initialize commander with overridden version and description } start() { @@ -22,36 +18,21 @@ class PmInstall extends This { // Properties of ExampleClass: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 // pmInstall.discovery(); - program - .name(this.scriptName) - .version('0.0.1') - .description('Install package with fake package manager') + this.program .usage('-n 3 32 -l x y z -- op') - .configureOutput({ - // Visibly override write routines as example! - //writeOut: (str) => process.stdout.write(`[OUT] ${str}`), - writeErr: (str) => process.stdout.write(`[ERR] ${str}`), - // Highlight errors in color. - outputError: (str, write) => write(errorColor(str)), - }); - - program .option('-n, --number ', 'specify numbers') .option('-l, --letter [letters...]', 'specify letters') - .parse(); - // console.log('options array'); - // console.log(program.options.map(o => o.flags)); + this.program.parse(); - // console.log('visible options'); - // const helper = program.createHelp(); - // console.log(helper.visibleOptions(program).map(o => o.flags)); - - if (Object.keys(program.opts()).length || program.args.length) { - console.log('Options: ', program.opts()); - console.log('Remaining arguments: ', program.args); + if (Object.keys(this.program.opts()).length || this.program.args.length) { + // Debugging commander options and arguments + const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); + const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); + this.execCmd(`./log echo 'Options: ${opts}'`); + this.execCmd(`./log echo 'Remaining arguments: ${args}'`); } else { - program.outputHelp(); + this.program.outputHelp(); } } } diff --git a/this b/this index ad53fd2..5d6dc6f 100644 --- a/this +++ b/this @@ -1,13 +1,31 @@ #!/usr/bin/env node const path = require('path'); +const { Command } = require('commander'); +const shell = require('shelljs'); class This { constructor() { 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.scriptName = path.parse(process.argv[1]).base; } + init() { + // implement commander abilities + this.program = new Command() + .name(this.scriptName) + .version(this.version) + .description(this.description) + .configureOutput({ + // Visibly override write routines as example! + //writeOut: (str) => process.stdout.write(`[OUT] ${str}`), + //writeErr: (str) => process.stdout.write(`[ERR] ${str}`), + // Highlight errors in color. + outputError: (str, write) => write(this._echoInRed(str)), + }); + } + getClassName() { return this.constructor.name; } @@ -35,6 +53,42 @@ class This { callback(this); } + start() { + // Not implemented, so it needs to be overridden! + console.warn(this._echoInYellow(`Method ${this._getCurrentFunctionName()} not implemented!`)); + } + + execCmd(cmd) { + // Run external tool synchronously + if (shell.exec(cmd).code !== 0) { + shell.echo('[ERR] ' + this._echoInRed(`Command '${cmd}' failed`)); + shell.exit(1); + } + } + + _echoInRed(str) { + // Add ANSI escape codes to display text in red. + return `\x1b[31m${str}\x1b[0m`; + } + + _echoInYellow(str) { + // Add ANSI escape codes to display text in yellow. + return `\x1b[33m${str}\x1b[0m`; + } + + _getCurrentFunctionName() { + // Create an Error object (but don't throw it) + const err = new Error(); + + // Extract the current stack trace + Error.captureStackTrace(err, this._getCurrentFunctionName); + + // Extract the function name from the stack trace + const callerName = err.stack.split("\n")[1].trim().split(" ")[1]; + + return callerName; + } + // Higher-order function to decorate other functions and provide logging _logDecorator(fn) { return function (...args) { -- 2.45.1 From d5de0bfe544ce3bddfeec22c7ca20004cdd9a273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Sat, 29 Jun 2024 16:46:39 +0200 Subject: [PATCH 05/12] feat(): Implement base test fns for `pm` script. --- log | 10 ++++++ pm | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++---- pm-install | 4 +-- this | 6 ++++ 4 files changed, 103 insertions(+), 8 deletions(-) diff --git a/log b/log index e3c9afc..aaa8967 100755 --- a/log +++ b/log @@ -15,6 +15,10 @@ class Log extends This { console.log(str); } + success(str) { + console.log(this._echoInGreen(str)); + } + info(str) { console.log(`[INFO] ${str}`); } @@ -34,6 +38,12 @@ class Log extends This { this.echo(message); }); + this.program.command('success') + .argument('') + .action(message => { + this.success(message); + }); + this.program.command('info') .argument('') .action(message => { diff --git a/pm b/pm index 71ee66d..92b8c10 100755 --- a/pm +++ b/pm @@ -1,7 +1,6 @@ #!/usr/bin/env node const This = require('./this'); -const { Command } = require('commander'); // Example of subcommands which are implemented as stand-alone executable files. // @@ -14,9 +13,25 @@ class Pm extends This { super(); this.version = '0.0.1'; this.description = 'Fake package manager'; + super.init(); // initialize commander with overridden version and description + } - // implement commander abilities - this.program = new Command().name(this.scriptName).version(this.version).description(this.description); + install() { + this.program.command('install [name]', 'install one or more packages').alias('i'); + } + + search() { + this.program.command('search [query]', 'search with optional query').alias('s'); + } + + update() { + this.program.command('update', 'update installed packages', { + executableFile: 'myUpdateSubCommand', + }); + } + + list() { + this.program.command('list', 'list packages installed', { isDefault: false }); } // override This.discovery() @@ -25,12 +40,75 @@ class Pm extends This { return methods; } + // Basic test methods + _testListMethod() { + const pm = new Pm(); + const pmMethods = pm.listMethods(); + + const testMethods = + pmMethods.includes('install') && + pmMethods.includes('search') && + pmMethods.includes('update') && + pmMethods.includes('list') && + pmMethods.includes('discovery') && + pmMethods.includes('start'); + const testInternalMethods = !pmMethods.includes('_testListMethod'); + + console.assert(testMethods, 'Pm.listMethod() failed'); + console.assert( + testInternalMethods, + 'Pm.listMethod() for internal methods failed. Classes with _ should be ignored' + ); + + if (testMethods && testInternalMethods) { + this.execCmd(`${this.workingDir}/log success 'testListMethod passed'`); + } else { + this.execCmd(`${this.workingDir}/log error 'testListMethod failed'`); + } + } + + _testListProperties() { + const pm = new Pm(); + const pmProperties = pm.listProperties(); + + const testProperties = + pmProperties.includes('version') && pmProperties.includes('description') && pmProperties.includes('program'); + console.assert(testProperties, 'Pm.listProperties() failed'); + + if (testProperties) { + this.execCmd(`${this.workingDir}/log success 'testListProperties passed'`); + } else { + this.execCmd(`${this.workingDir}/log error 'testListProperties failed'`); + } + } + + _testGetClassName() { + const pm = new Pm(); + const pmClassName = pm.getClassName(); + + const testClassName = pmClassName === 'Pm'; + console.assert(testClassName, 'Pm.getClassName() failed'); + + if (testClassName) { + this.execCmd(`${this.workingDir}/log success 'testGetClassName passed'`); + } else { + this.execCmd(`${this.workingDir}/log error 'testGetClassName failed'`); + } + } + + selfTest() { + // Run tests + this._testListMethod(); + this._testListProperties(); + this._testGetClassName(); + } + start() { // Usage const pm = new Pm(); - // Output: Methods of ExampleClass: [ 'methodOne', 'methodTwo' ] - // Properties of ExampleClass: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 - // pm.discovery(); + // Output: Methods of Pm: [ 'methodOne', 'methodTwo' ] + // Properties of Pm: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 + //pm.discovery(); // this.program // .name(this.scriptName) @@ -70,3 +148,4 @@ const pm = new Pm(); // logDecoratedDiscovery(); pm.start(); +pm.selfTest(); diff --git a/pm-install b/pm-install index 42ec793..acdf0ce 100755 --- a/pm-install +++ b/pm-install @@ -29,8 +29,8 @@ class PmInstall extends This { // Debugging commander options and arguments const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); - this.execCmd(`./log echo 'Options: ${opts}'`); - this.execCmd(`./log echo 'Remaining arguments: ${args}'`); + this.execCmd(`${this.workingDir}/log echo 'Options: ${opts}'`); + this.execCmd(`${this.workingDir}/log echo 'Remaining arguments: ${args}'`); } else { this.program.outputHelp(); } diff --git a/this b/this index 5d6dc6f..6147b84 100644 --- a/this +++ b/this @@ -9,6 +9,7 @@ class This { 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.scriptName = path.parse(process.argv[1]).base; + this.workingDir = path.parse(process.argv[1]).dir; } init() { @@ -76,6 +77,11 @@ class This { return `\x1b[33m${str}\x1b[0m`; } + _echoInGreen(str) { + // Add ANSI escape codes to display text in green. + return `\x1b[32m${str}\x1b[0m`; + } + _getCurrentFunctionName() { // Create an Error object (but don't throw it) const err = new Error(); -- 2.45.1 From 64815d15f60612ebebf58d1b6a047a8dfc5ad12d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Sat, 29 Jun 2024 23:23:25 +0200 Subject: [PATCH 06/12] feat(): Add command parser for fns docstring --- log | 72 ++++++++++++++++++------------------------ pm | 61 +++++++++++++++++++----------------- this | 100 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 162 insertions(+), 71 deletions(-) diff --git a/log b/log index aaa8967..21f3a31 100755 --- a/log +++ b/log @@ -11,57 +11,47 @@ class Log extends This { super.init(); // initialize commander with overridden version and description } - echo(str) { - console.log(str); + echo(message) { + /** + * @command + * @argument('') + */ + console.log(message); } - success(str) { - console.log(this._echoInGreen(str)); + success(message) { + /** + * @command + * @argument('') + */ + console.log(this._echoInGreen(message)); } - info(str) { - console.log(`[INFO] ${str}`); + info(message) { + /** + * @command + * @argument('') + */ + console.log(`[INFO] ${message}`); } - warn(str) { - console.warn(`[WARN] ${this._echoInYellow(str)}`); + warn(message) { + /** + * @command + * @argument('') + */ + console.warn(`[WARN] ${this._echoInYellow(message)}`); } - error(str) { - console.error(`[ERR] ${this._echoInRed(str)}`); + error(message) { + /** + * @command + * @argument('') + */ + console.error(`[ERR] ${this._echoInRed(message)}`); } start() { - this.program.command('echo') - .argument('') - .action(message => { - this.echo(message); - }); - - this.program.command('success') - .argument('') - .action(message => { - this.success(message); - }); - - this.program.command('info') - .argument('') - .action(message => { - this.info(message); - }); - - this.program.command('warn') - .argument('') - .action(message => { - this.warn(message); - }); - - this.program.command('error') - .argument('') - .action(message => { - this.error(message); - }); - this.program.parse(); if (Object.keys(this.program.opts()).length || this.program.args.length) { @@ -78,4 +68,4 @@ class Log extends This { // main const log = new Log(); -log.start(); \ No newline at end of file +log.start(); diff --git a/pm b/pm index 92b8c10..2cc3798 100755 --- a/pm +++ b/pm @@ -1,6 +1,7 @@ #!/usr/bin/env node const This = require('./this'); +const util = require('util'); // Example of subcommands which are implemented as stand-alone executable files. // @@ -17,21 +18,34 @@ class Pm extends This { } 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() in docstring } 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() in docstring } update() { - this.program.command('update', 'update installed packages', { - executableFile: 'myUpdateSubCommand', - }); + /** + * @command('update installed packages') + * @executable('myUpdateSubCommand') + */ + // Calls stand-alone excutable `myUpdateSubCommand` because of @command() in docstring } list() { - this.program.command('list', 'list packages installed', { isDefault: false }); + /** + * @command('list packages installed') + */ + // Calls stand-alone excutable `pm-list` because of @command() in docstring } // override This.discovery() @@ -104,30 +118,21 @@ class Pm extends This { } start() { - // Usage - 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.parse(); - // this.program - // .name(this.scriptName) - // .version('0.0.1') - // .description('Fake package manager') - // .command('install [name]', 'install one or more packages') - // .alias('i') - // .command('search [query]', 'search with optional query') - // .alias('s') - // .command('update', 'update installed packages', { - // executableFile: 'myUpdateSubCommand', - // }) - // .command('list', 'list packages installed', { isDefault: false }); - - //this.program.parse(); + if (Object.keys(this.program.opts()).length || this.program.args.length) { + // Debugging commander options and arguments + const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); + const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); + this.execCmd(`${this.workingDir}/log echo 'Options: ${opts}'`); + this.execCmd(`${this.workingDir}/log echo 'Remaining arguments: ${args}'`); + } else { + this.program.outputHelp(); + } // applies logDecorator to this.discovery() and binds it to 'this' to maintain the correct context - const logDecoratedDiscovery = this._logDecorator(this.discovery).bind(this); - logDecoratedDiscovery(); + //const logDecoratedDiscovery = this._logDecorator(this.discovery).bind(this); + //logDecoratedDiscovery(); // Try the following on macOS or Linux: // ./examples/pm @@ -148,4 +153,4 @@ const pm = new Pm(); // logDecoratedDiscovery(); pm.start(); -pm.selfTest(); +//pm.selfTest(); diff --git a/this b/this index 6147b84..dc8c866 100644 --- a/this +++ b/this @@ -7,7 +7,7 @@ const shell = require('shelljs'); class This { constructor() { 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.workingDir = path.parse(process.argv[1]).dir; } @@ -25,6 +25,11 @@ class This { // Highlight errors in color. 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() { @@ -90,11 +95,102 @@ class This { Error.captureStackTrace(err, this._getCurrentFunctionName); // 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; } + // Utility function to extract docstring from a function, e.g. + // + // echo(str) { + // /** + // @command + // @argument('') + // */ + // 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 _logDecorator(fn) { return function (...args) { -- 2.45.1 From e29f085961c3b4ce656f935f928793918c257b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Sun, 30 Jun 2024 15:28:25 +0200 Subject: [PATCH 07/12] feat(): Optimize docstring parser for commander and make depending changes --- README.md | 9 ++ log | 18 +++- package-lock.json | 230 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 - pm | 31 +------ pm-install | 32 ++----- pm-list | 12 +-- pnpm-lock.yaml | 183 ------------------------------------ this | 155 ++++++++++++++++++++----------- 9 files changed, 368 insertions(+), 303 deletions(-) create mode 100644 package-lock.json delete mode 100644 pnpm-lock.yaml diff --git a/README.md b/README.md index 8793fc2..72c68ed 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,12 @@ https://unix.stackexchange.com/questions/65235/universal-node-js-shebang ## Set location for npm packages with repl sessions https://sabljakovich.medium.com/how-to-use-npm-packages-with-node-js-repl-sessions-cd77300ebfe2 + +## Dependencies versionn + +The iSH-App on iOS has the following latest versions available + +- nodejs v16.11.1 +- npm v7.17.0 + +These versions are deprecated, but if we want to use these scripts on iSH, we have to make sure they run on that outdated versions. diff --git a/log b/log index 21f3a31..7c648dd 100755 --- a/log +++ b/log @@ -13,7 +13,8 @@ class Log extends This { echo(message) { /** - * @command + * @program() + * @command() * @argument('') */ console.log(message); @@ -21,7 +22,8 @@ class Log extends This { success(message) { /** - * @command + * @program() + * @command() * @argument('') */ console.log(this._echoInGreen(message)); @@ -29,7 +31,8 @@ class Log extends This { info(message) { /** - * @command + * @program() + * @command() * @argument('') */ console.log(`[INFO] ${message}`); @@ -37,7 +40,8 @@ class Log extends This { warn(message) { /** - * @command + * @program() + * @command() * @argument('') */ console.warn(`[WARN] ${this._echoInYellow(message)}`); @@ -45,13 +49,17 @@ class Log extends This { error(message) { /** - * @command + * @program() + * @command() * @argument('') */ console.error(`[ERR] ${this._echoInRed(message)}`); } start() { + /** + * @program() + */ this.program.parse(); if (Object.keys(this.program.opts()).length || this.program.args.length) { diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..89be523 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,230 @@ +{ + "name": "once-nodejs", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "commander": "^12.1.0", + "shelljs": "^0.8.5" + }, + "devDependencies": { + "prettier": "^3.3.2" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "engines": { + "node": ">=18" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + } + } +} diff --git a/package.json b/package.json index 7768209..8700838 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ }, "dependencies": { "commander": "^12.1.0", - "omelette": "^0.4.17", "shelljs": "^0.8.5" }, "devDependencies": { diff --git a/pm b/pm index 2cc3798..b0e6159 100755 --- a/pm +++ b/pm @@ -1,7 +1,6 @@ #!/usr/bin/env node const This = require('./this'); -const util = require('util'); // Example of subcommands which are implemented as stand-alone executable files. // @@ -19,6 +18,7 @@ class Pm extends This { install() { /** + * @program() * @command('install one or more packages') * @alias('i') */ @@ -27,6 +27,7 @@ class Pm extends This { search() { /** + * @program() * @command('search with optional query') * @alias('s') */ @@ -35,6 +36,7 @@ class Pm extends This { update() { /** + * @program() * @command('update installed packages') * @executable('myUpdateSubCommand') */ @@ -43,6 +45,7 @@ class Pm extends This { list() { /** + * @program() * @command('list packages installed') */ // Calls stand-alone excutable `pm-list` because of @command() in docstring @@ -118,31 +121,7 @@ class Pm extends This { } start() { - this.program.parse(); - - if (Object.keys(this.program.opts()).length || this.program.args.length) { - // Debugging commander options and arguments - const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); - const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); - this.execCmd(`${this.workingDir}/log echo 'Options: ${opts}'`); - this.execCmd(`${this.workingDir}/log echo 'Remaining arguments: ${args}'`); - } else { - this.program.outputHelp(); - } - - // applies logDecorator to this.discovery() and binds it to 'this' to maintain the correct context - //const logDecoratedDiscovery = this._logDecorator(this.discovery).bind(this); - //logDecoratedDiscovery(); - - // Try the following on macOS or Linux: - // ./examples/pm - // - // Try the following: - // ./pm - // ./pm help install - // ./pm install -h - // ./pm install foo bar baz - // ./pm install foo bar baz --force + super.start(); } } diff --git a/pm-install b/pm-install index acdf0ce..653761a 100755 --- a/pm-install +++ b/pm-install @@ -1,39 +1,23 @@ #!/usr/bin/env node const This = require('./this'); -const util = require('util'); class PmInstall extends This { constructor() { super(); this.version = '0.0.1'; - this.description = 'Install package with fake package manager' + this.description = 'Install package with fake package manager'; super.init(); // initialize commander with overridden version and description } start() { - // Usage - const pmInstall = new PmInstall(); - // Output: Methods of ExampleClass: [ 'methodOne', 'methodTwo' ] - // Properties of ExampleClass: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 - // pmInstall.discovery(); - - this.program - .usage('-n 3 32 -l x y z -- op') - .option('-n, --number ', 'specify numbers') - .option('-l, --letter [letters...]', 'specify letters') - - this.program.parse(); - - if (Object.keys(this.program.opts()).length || this.program.args.length) { - // Debugging commander options and arguments - const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); - const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); - this.execCmd(`${this.workingDir}/log echo 'Options: ${opts}'`); - this.execCmd(`${this.workingDir}/log echo 'Remaining arguments: ${args}'`); - } else { - this.program.outputHelp(); - } + /** + * @program() + * @usage('-n 3 32 -l x y z -- op') + * @option('-n, --number ', 'specify numbers') + * @option('-l, --letter [letters...]', 'specify letters') + */ + super.start(); } } diff --git a/pm-list b/pm-list index 2ef0455..9e0f6e2 100755 --- a/pm-list +++ b/pm-list @@ -1,23 +1,17 @@ #!/usr/bin/env node const This = require('./this'); -const { Command } = require('commander'); -const program = new Command(); class PmList extends This { constructor() { super(); this.version = '0.0.1'; + this.description = 'List packages of fake package manager'; + super.init(); // initialize commander with overridden version and description } start() { - // Usage - const pmList = new PmList(); - // Output: Methods of ExampleClass: [ 'methodOne', 'methodTwo' ] - // Properties of ExampleClass: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 - // pmList.discovery(); - - program.name(this.scriptName).version('0.0.1').description('List packages of fake package manager').outputHelp(); + super.start(); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml deleted file mode 100644 index a6ff437..0000000 --- a/pnpm-lock.yaml +++ /dev/null @@ -1,183 +0,0 @@ -lockfileVersion: '6.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -dependencies: - commander: - specifier: ^12.1.0 - version: 12.1.0 - omelette: - specifier: ^0.4.17 - version: 0.4.17 - shelljs: - specifier: ^0.8.5 - version: 0.8.5 - -devDependencies: - prettier: - specifier: ^3.3.2 - version: 3.3.2 - -packages: - /balanced-match@1.0.2: - resolution: - { integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== } - dev: false - - /brace-expansion@1.1.11: - resolution: - { integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== } - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: false - - /commander@12.1.0: - resolution: - { integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== } - engines: { node: '>=18' } - dev: false - - /concat-map@0.0.1: - resolution: - { integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== } - dev: false - - /fs.realpath@1.0.0: - resolution: - { integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== } - dev: false - - /function-bind@1.1.2: - resolution: - { integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== } - dev: false - - /glob@7.2.3: - resolution: - { integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== } - deprecated: Glob versions prior to v9 are no longer supported - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: false - - /hasown@2.0.2: - resolution: - { integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== } - engines: { node: '>= 0.4' } - dependencies: - function-bind: 1.1.2 - dev: false - - /inflight@1.0.6: - resolution: - { integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== } - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: false - - /inherits@2.0.4: - resolution: - { integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== } - dev: false - - /interpret@1.4.0: - resolution: - { integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== } - engines: { node: '>= 0.10' } - dev: false - - /is-core-module@2.14.0: - resolution: - { integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A== } - engines: { node: '>= 0.4' } - dependencies: - hasown: 2.0.2 - dev: false - - /minimatch@3.1.2: - resolution: - { integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== } - dependencies: - brace-expansion: 1.1.11 - dev: false - - /omelette@0.4.17: - resolution: - { integrity: sha512-UlU69G6Bhu0XFjw3tjFZ0qyiMUjAOR+rdzblA1nLQ8xiqFtxOVlkhM39BlgTpLFx9fxkm6rnxNNRsS5GxE/yww== } - engines: { node: '>=0.8.0' } - dev: false - - /once@1.4.0: - resolution: - { integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== } - dependencies: - wrappy: 1.0.2 - dev: false - - /path-is-absolute@1.0.1: - resolution: - { integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== } - engines: { node: '>=0.10.0' } - dev: false - - /path-parse@1.0.7: - resolution: - { integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== } - dev: false - - /prettier@3.3.2: - resolution: - { integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA== } - engines: { node: '>=14' } - hasBin: true - dev: true - - /rechoir@0.6.2: - resolution: - { integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== } - engines: { node: '>= 0.10' } - dependencies: - resolve: 1.22.8 - dev: false - - /resolve@1.22.8: - resolution: - { integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== } - hasBin: true - dependencies: - is-core-module: 2.14.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: false - - /shelljs@0.8.5: - resolution: - { integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== } - engines: { node: '>=4' } - hasBin: true - dependencies: - glob: 7.2.3 - interpret: 1.4.0 - rechoir: 0.6.2 - dev: false - - /supports-preserve-symlinks-flag@1.0.0: - resolution: - { integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== } - engines: { node: '>= 0.4' } - dev: false - - /wrappy@1.0.2: - resolution: - { integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== } - dev: false diff --git a/this b/this index dc8c866..53959ad 100644 --- a/this +++ b/this @@ -3,6 +3,7 @@ const path = require('path'); const { Command } = require('commander'); const shell = require('shelljs'); +const util = require('util'); class This { constructor() { @@ -60,8 +61,20 @@ class This { } start() { - // Not implemented, so it needs to be overridden! - console.warn(this._echoInYellow(`Method ${this._getCurrentFunctionName()} not implemented!`)); + /** + * @program() + */ + this.program.parse(); + + if (Object.keys(this.program.opts()).length || this.program.args.length) { + // Debugging commander options and arguments + const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); + const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); + this.execCmd(`${this.workingDir}/log echo 'Options: ${opts}'`); + this.execCmd(`${this.workingDir}/log echo 'Remaining arguments: ${args}'`); + } else { + this.program.outputHelp(); + } } execCmd(cmd) { @@ -102,11 +115,22 @@ class This { // Utility function to extract docstring from a function, e.g. // + // start() { + // /** + // * @program() + // * @usage([options] [command]) + // * @option('-d, --debug', 'Enable debug mode') + // .. + // } + // + // // echo(str) { // /** - // @command - // @argument('') - // */ + // * @program() + // * @command() + // * @argument('') + // * @option('-c, --color ', 'Prints in color mode') + // */ // console.log(str); // } // @@ -128,66 +152,87 @@ class This { } // 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\('(.+?)'\)/); + const programRegex = /@program\(\)/; + const usageRegex = /@usage\('(.+?)'\)/; + const commandRegex = /@command(?:\('(.+?)'\))?/; + const aliasRegex = /@alias\('(.+?)'\)/; + const argumentsRegex = /@argument\('(.+?)'\)/g; + const optionsRegex = /@option\('(?.+?)',\s*'(?.+?)'(?:,\s*'(?.+?)')?\)/g; + const defaultRegex = /@default/; + const executableRegex = /@executable\('(.+?)'\)/; - if (!commandMatch) { - // No @command tag found + // get regex matches with capture groups + const programMatch = docString.match(programRegex); + const usageMatch = docString.match(usageRegex); + const commandMatch = docString.match(commandRegex); + const aliasMatch = docString.match(aliasRegex); + const defaultMatch = docString.match(defaultRegex); + const executableMatch = docString.match(executableRegex); + + if (!programMatch) { + // No @program() 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] : ''; + if (commandMatch) { + // Generate the commander statement + 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); + // 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, - }); + // Create a command + // See https://github.com/tj/commander.js?tab=readme-ov-file#quick-start + // 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) { + // build stand-alone command + command = this.program.command(commandName, commandDescription, { + isDefault: defaultCommand, + executableFile: executable, + }); - if (alias) { - command.alias(alias); + if (alias) { + command.alias(alias); + } + } else { + // build command with function name as action + 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 + (docString.match(argumentsRegex) || []) + .map((e) => e.replace(argumentsRegex, '$1')) + .forEach((arg) => { + command.argument(arg); + }); + + // If the commandName expects options, we add them here + // See https://github.com/tj/commander.js?tab=readme-ov-file#options + for (const match of docString.matchAll(optionsRegex)) { + command.option(match.groups['flag'], match.groups['description'], match.groups['defaultValue']); } } else { - command = this.program.command(commandName, { isDefault: defaultCommand }); + // Add usage + if (usageMatch) { + this.program.usage(usageMatch[1]); + } - // 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); + // If the program expects options, we add them here + // See https://github.com/tj/commander.js?tab=readme-ov-file#options + for (const match of docString.matchAll(optionsRegex)) { + this.program.option(match.groups['flag'], match.groups['description'], match.groups['defaultValue']); + } } } -- 2.45.1 From d6336f483691cc660d5968f7a80a853ec31c129d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Sun, 30 Jun 2024 19:49:12 +0200 Subject: [PATCH 08/12] fix(): Downgrade commander and override default exit behaviour --- README.md | 2 ++ log | 14 +++++++++----- package-lock.json | 10 +++++----- package.json | 2 +- pm-search | 12 +++--------- this | 10 +++++++--- 6 files changed, 27 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 72c68ed..a671a84 100644 --- a/README.md +++ b/README.md @@ -24,3 +24,5 @@ The iSH-App on iOS has the following latest versions available - npm v7.17.0 These versions are deprecated, but if we want to use these scripts on iSH, we have to make sure they run on that outdated versions. + +- only commander v11 supports nodejs v16 diff --git a/log b/log index 7c648dd..207c7fb 100755 --- a/log +++ b/log @@ -60,16 +60,20 @@ class Log extends This { /** * @program() */ - this.program.parse(); + this.program.exitOverride(); + + try { + this.program.parse(); + } catch (err) { + // custom processing... + } if (Object.keys(this.program.opts()).length || this.program.args.length) { //// Debugging commander options and arguments const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); - //this.echo(`Options: ${opts}`); - //this.echo(`Remaining arguments: ${args}`); - } else { - this.program.outputHelp(); + this.echo(`Options: ${opts}`); + this.echo(`Remaining arguments: ${args}`); } } } diff --git a/package-lock.json b/package-lock.json index 89be523..7a143f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "dependencies": { - "commander": "^12.1.0", + "commander": "^11.1.0", "shelljs": "^0.8.5" }, "devDependencies": { @@ -27,11 +27,11 @@ } }, "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "engines": { - "node": ">=18" + "node": ">=16" } }, "node_modules/concat-map": { diff --git a/package.json b/package.json index 8700838..548c75d 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "format": "prettier --ignore-path .gitignore --write \"./*\"" }, "dependencies": { - "commander": "^12.1.0", + "commander": "^11.1.0", "shelljs": "^0.8.5" }, "devDependencies": { diff --git a/pm-search b/pm-search index f2fea7e..2a3a7d8 100755 --- a/pm-search +++ b/pm-search @@ -1,23 +1,17 @@ #!/usr/bin/env node const This = require('./this'); -const { Command } = require('commander'); -const program = new Command(); class PmSearch extends This { constructor() { super(); this.version = '0.0.1'; + this.description = 'Search packages of fake package manager'; + super.init(); // initialize commander with overridden version and description } start() { - // Usage - const pmSearch = new PmSearch(); - // Output: Methods of ExampleClass: [ 'methodOne', 'methodTwo' ] - // Properties of ExampleClass: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 - // pmSearch.discovery(); - - program.name(this.scriptName).version('0.0.1').description('Search packages of fake package manager').outputHelp(); + super.start(); } } diff --git a/this b/this index 53959ad..0105ed9 100644 --- a/this +++ b/this @@ -64,7 +64,13 @@ class This { /** * @program() */ - this.program.parse(); + this.program.exitOverride(); + + try { + this.program.parse(); + } catch (err) { + // custom processing... + } if (Object.keys(this.program.opts()).length || this.program.args.length) { // Debugging commander options and arguments @@ -72,8 +78,6 @@ class This { const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); this.execCmd(`${this.workingDir}/log echo 'Options: ${opts}'`); this.execCmd(`${this.workingDir}/log echo 'Remaining arguments: ${args}'`); - } else { - this.program.outputHelp(); } } -- 2.45.1 From a09d0605934696d46c977085e4b7e275e65286c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Sun, 30 Jun 2024 21:05:05 +0200 Subject: [PATCH 09/12] fix(): Rename fn discovery(), add debugLevel and rework command parser --- log | 32 +++++++++++++++++----------- myUpdateSubCommand | 2 +- pm | 12 +++++------ this | 52 +++++++++++++++++++++++++++++++++------------- 4 files changed, 64 insertions(+), 34 deletions(-) diff --git a/log b/log index 207c7fb..581ad3f 100755 --- a/log +++ b/log @@ -60,20 +60,28 @@ class Log extends This { /** * @program() */ - this.program.exitOverride(); + if (this.debugLevel > 2) { + this.program.exitOverride(); - try { + try { + this.program.parse(); + } catch (err) { + // this.echo('\n'); + // this.error(err); + process.exit(1); + } + + if (Object.keys(this.program.opts()).length || this.program.args.length) { + //// Debugging commander options and arguments + const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); + const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); + this.echo('\n'); + this.echo(`Options: ${opts}`); + this.echo(`Remaining arguments: ${args}`); + } + } else { + // don't debug log if level is < 3 or the output will be hard to read this.program.parse(); - } catch (err) { - // custom processing... - } - - if (Object.keys(this.program.opts()).length || this.program.args.length) { - //// Debugging commander options and arguments - const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); - const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); - this.echo(`Options: ${opts}`); - this.echo(`Remaining arguments: ${args}`); } } } diff --git a/myUpdateSubCommand b/myUpdateSubCommand index 70cef5d..a970293 100755 --- a/myUpdateSubCommand +++ b/myUpdateSubCommand @@ -14,7 +14,7 @@ class MyUpdateSubCommand extends This { const myUpdateSubCommand = new MyUpdateSubCommand(); // Output: Methods of ExampleClass: [ 'methodOne', 'methodTwo' ] // Properties of ExampleClass: [ 'propertyOne', 'propertyTwo', 'version' ] and Version: 1.1.0 - // myUpdateSubCommand.discovery(); + // myUpdateSubCommand.discover(); if (!shell.which('git')) { shell.echo('Sorry, this script requires git'); diff --git a/pm b/pm index b0e6159..4f8c614 100755 --- a/pm +++ b/pm @@ -51,8 +51,8 @@ class Pm extends This { // Calls stand-alone excutable `pm-list` because of @command() in docstring } - // override This.discovery() - discovery() { + // override This.discover() + discover() { const methods = this.listMethods(); return methods; } @@ -67,7 +67,7 @@ class Pm extends This { pmMethods.includes('search') && pmMethods.includes('update') && pmMethods.includes('list') && - pmMethods.includes('discovery') && + pmMethods.includes('discover') && pmMethods.includes('start'); const testInternalMethods = !pmMethods.includes('_testListMethod'); @@ -127,9 +127,9 @@ class Pm extends This { // main const pm = new Pm(); -// // applies logDecorator to this.discovery() and binds it to the instance to maintain the correct context -// const logDecoratedDiscovery = pm._logDecorator(pm.discovery).bind(pm); -// logDecoratedDiscovery(); +// // applies logDecorator to this.discover() and binds it to the instance to maintain the correct context +// const logDecoratedDiscover = pm._logDecorator(pm.discover).bind(pm); +// logDecoratedDiscover(); pm.start(); //pm.selfTest(); diff --git a/this b/this index 0105ed9..f5d2a4f 100644 --- a/this +++ b/this @@ -11,6 +11,7 @@ class This { this.description = 'This is the parent class all other scripts should extend.'; this.scriptName = path.parse(process.argv[1]).base; this.workingDir = path.parse(process.argv[1]).dir; + this.debugLevel = 0; } init() { @@ -49,10 +50,14 @@ class This { return properties; } - discovery() { - console.log(`My name is '${this.getClassName()}' and I have the version: ${this.version}`); - console.log(`My methods are:`, this.listMethods()); - console.log(`My properties are:`, this.listProperties()); + discover() { + const methods = util.inspect(this.listMethods(), { depth: null, colors: true, showHidden: true }); + const properties = util.inspect(this.listProperties(), { depth: null, colors: true, showHidden: true }); + this.execCmd( + `${this.workingDir}/log echo 'My name is "${this.getClassName()}" and I have the version: ${this.version}'` + ); + this.execCmd(`${this.workingDir}/log echo 'My methods are: ${methods}'`); + this.execCmd(`${this.workingDir}/log echo 'My properties are: ${properties}'`); } // Method to create a context manager for this instance @@ -64,20 +69,37 @@ class This { /** * @program() */ - this.program.exitOverride(); + if (this.debugLevel > 0) { + this.program.exitOverride(); - try { + try { + this.program.parse(); + + // show help even if there are no commands registered + if (this.program.commands.length == 0) { + this.program.outputHelp(); + } + } catch (err) { + // this.execCmd(`${this.workingDir}/log echo '\n'`); + // this.execCmd(`${this.workingDir}/log error '${err}'`); + process.exit(1); + } + + if (Object.keys(this.program.opts()).length || this.program.args.length) { + // Debugging commander options and arguments + const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); + const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); + this.execCmd(`${this.workingDir}/log echo '\n'`); + this.execCmd(`${this.workingDir}/log echo 'Options: ${opts}'`); + this.execCmd(`${this.workingDir}/log echo 'Remaining arguments: ${args}'`); + } + } else { this.program.parse(); - } catch (err) { - // custom processing... - } - if (Object.keys(this.program.opts()).length || this.program.args.length) { - // Debugging commander options and arguments - const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); - const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); - this.execCmd(`${this.workingDir}/log echo 'Options: ${opts}'`); - this.execCmd(`${this.workingDir}/log echo 'Remaining arguments: ${args}'`); + // show help even if there are no commands registered + if (this.program.commands.length == 0) { + this.program.outputHelp(); + } } } -- 2.45.1 From 4c114c7ee4777ba0225999d7da404588c958d94a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Sun, 30 Jun 2024 22:40:34 +0200 Subject: [PATCH 10/12] feat(): Begin bash completion functions and `os` script --- completion/bash_template.sh | 50 ++++++++++++++++++++++++++++++++ os | 44 ++++++++++++++++++++++++++++ this | 57 +++++++++++++++++++------------------ 3 files changed, 123 insertions(+), 28 deletions(-) create mode 100644 completion/bash_template.sh create mode 100755 os diff --git a/completion/bash_template.sh b/completion/bash_template.sh new file mode 100644 index 0000000..62636a1 --- /dev/null +++ b/completion/bash_template.sh @@ -0,0 +1,50 @@ +[[ "$1" != "" ]] || { + echo "Usage: source bash_template.sh " +} + +[[ "$1" = "" ]] || { + _pm() + { + local cur prev + local IFS=$'\n' + + # Retrieve the original value + local width=$(bind -v | sed -n 's/^set completion-display-width //p') + + if [[ $width -ne 0 ]]; then + # Change the readline variable + bind "set completion-display-width 0" + # On the first tab press, expand a common prefix + # On the second tab press, list out options + # Any subsequent tab presses cycle through these options + bind "set show-all-if-ambiguous on" + bind "set menu-complete-display-prefix on" + bind "TAB: menu-complete" + bind "set colored-completion-prefix on" + bind "set colored-stats on" + + # Set up PROMPT_COMMAND to reset itself to its current value + PROMPT_COMMAND="PROMPT_COMMAND=$(printf %q "$PROMPT_COMMAND")" + + # Set up PROMPT_COMMAND to reset the readline variable + PROMPT_COMMAND+="; bind 'set completion-display-width $width'" + fi + + cur=${COMP_WORDS[COMP_CWORD]} + prev=${COMP_WORDS[COMP_CWORD-1]} + + # Get all options from omelette + #COMPREPLY=( $(compgen -W '$(./'$1' --compbash --compgen "${COMP_CWORD}" "${prev}" "${COMP_LINE}")' -- "$cur") ) + + # Get all options from commander + COMPREPLY=( $(compgen -W '$(./'$1' compgen --shell bash)' -- "$cur") ) + + # If no options available, print files and folders + if [ ${#COMPREPLY[@]} -eq 0 ]; then + COMPREPLY=( $(compgen -f -o default -o plusdirs $cur) ) + fi + + return 0 + } + complete -F _pm $1 +} \ No newline at end of file diff --git a/os b/os new file mode 100755 index 0000000..2e7b1ca --- /dev/null +++ b/os @@ -0,0 +1,44 @@ +#!/usr/bin/env node + +const This = require('./this'); + +class Os extends This { + constructor() { + super(); + this.version = '0.0.1'; + this.description = 'Shows informations about the operating system'; + super.init(); // initialize commander with overridden version and description + } + + info() { + /** + * @program() + * @command() + */ + console.warn(`Function ${this._getCurrentFunctionName()} not implemented`); + } + + check() { + /** + * @program() + * @command() + */ + console.warn(`Function ${this._getCurrentFunctionName()} not implemented`); + } + + checkEnv() { + /** + * @program() + * @command() + */ + console.warn(`Function ${this._getCurrentFunctionName()} not implemented`); + } + + start() { + super.start(); + } +} + +// main +const os = new Os(); +os.start(); diff --git a/this b/this index f5d2a4f..020e1a2 100644 --- a/this +++ b/this @@ -1,5 +1,6 @@ #!/usr/bin/env node +const os = require('os'); const path = require('path'); const { Command } = require('commander'); const shell = require('shelljs'); @@ -28,6 +29,16 @@ class This { outputError: (str, write) => write(this._echoInRed(str)), }); + // add functions for shell completion + this.program + .command('compgen') + .option('-s, --shell ', 'You can select between [bash]', 'bash') + .action((arg) => { + const words = this.listMethods(); + console.log(words.join(os.EOL)); + return process.exit(); + }); + // dynamically add all methods with appropriate docstring to commander this.listMethods().forEach((method) => { this._parseDocStringToCmd(this[method]); @@ -40,7 +51,7 @@ class This { listMethods() { const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter( - (prop) => typeof this[prop] === 'function' && prop !== 'constructor' && !prop.startsWith('_') + (prop) => typeof this[prop] === 'function' && prop !== 'constructor' && prop !== 'start' && !prop.startsWith('_') ); return methods; } @@ -69,37 +80,27 @@ class This { /** * @program() */ - if (this.debugLevel > 0) { - this.program.exitOverride(); + this.program.exitOverride(); - try { - this.program.parse(); - - // show help even if there are no commands registered - if (this.program.commands.length == 0) { - this.program.outputHelp(); - } - } catch (err) { - // this.execCmd(`${this.workingDir}/log echo '\n'`); - // this.execCmd(`${this.workingDir}/log error '${err}'`); - process.exit(1); - } - - if (Object.keys(this.program.opts()).length || this.program.args.length) { - // Debugging commander options and arguments - const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); - const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); - this.execCmd(`${this.workingDir}/log echo '\n'`); - this.execCmd(`${this.workingDir}/log echo 'Options: ${opts}'`); - this.execCmd(`${this.workingDir}/log echo 'Remaining arguments: ${args}'`); - } - } else { + try { this.program.parse(); - // show help even if there are no commands registered - if (this.program.commands.length == 0) { - this.program.outputHelp(); + if (this.debugLevel == 0) { + process.exit(1); } + } catch (err) { + // this.execCmd(`${this.workingDir}/log echo '\n'`); + // this.execCmd(`${this.workingDir}/log error '${err}'`); + process.exit(1); + } + + if (Object.keys(this.program.opts()).length || this.program.args.length) { + // Debugging commander options and arguments + const opts = util.inspect(this.program.opts(), { depth: null, colors: true, showHidden: true }); + const args = util.inspect(this.program.args, { depth: null, colors: true, showHidden: true }); + this.execCmd(`${this.workingDir}/log echo '\n'`); + this.execCmd(`${this.workingDir}/log echo 'Options: ${opts}'`); + this.execCmd(`${this.workingDir}/log echo 'Remaining arguments: ${args}'`); } } -- 2.45.1 From 6679297c2ff6cdc52792993b9da0e3d01e3c8021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Tue, 2 Jul 2024 23:18:23 +0200 Subject: [PATCH 11/12] feat(): Begin `init/oosh` installer script --- init/oosh | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 init/oosh diff --git a/init/oosh b/init/oosh new file mode 100644 index 0000000..6f0741a --- /dev/null +++ b/init/oosh @@ -0,0 +1,11 @@ +#!/usr/bin/env -iS HOME=${HOME} sh +#http://www.etalabs.net/sh_tricks.html + +OOSH_DIR=${HOME}/oosh + +apk add bash bash-completion curl git nodejs-current npm +#mkdir -p $OOSH_DIR +git clone https://gitea.starconnect.ch/Public/nodejs-bash-completion.git $OOSH_DIR +cd $OOSH_DIR +git checkout dev +npm install -- 2.45.1 From c069f1f2327c9b71ad2db4cc63f896bf1236f796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Da=C3=9Fler?= Date: Tue, 2 Jul 2024 23:37:20 +0200 Subject: [PATCH 12/12] fix(): Compgen reply --- this | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/this b/this index 020e1a2..516d4dd 100644 --- a/this +++ b/this @@ -29,20 +29,24 @@ class This { outputError: (str, write) => write(this._echoInRed(str)), }); + // dynamically add all methods with appropriate docstring to commander + let cmds = []; + this.listMethods().forEach((method) => { + let cmd = this._parseDocStringToCmd(this[method]); + // exclude start function as commandline cmd, because its the scripts main entry point + if (cmd && cmd != 'start') { + cmds.push(cmd); + } + }); + // add functions for shell completion this.program .command('compgen') .option('-s, --shell ', 'You can select between [bash]', 'bash') .action((arg) => { - const words = this.listMethods(); - console.log(words.join(os.EOL)); + console.log(cmds.join(os.EOL)); return process.exit(); }); - - // dynamically add all methods with appropriate docstring to commander - this.listMethods().forEach((method) => { - this._parseDocStringToCmd(this[method]); - }); } getClassName() { @@ -51,7 +55,7 @@ class This { listMethods() { const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter( - (prop) => typeof this[prop] === 'function' && prop !== 'constructor' && prop !== 'start' && !prop.startsWith('_') + (prop) => typeof this[prop] === 'function' && prop !== 'constructor' && !prop.startsWith('_') ); return methods; } @@ -261,6 +265,9 @@ class This { this.program.option(match.groups['flag'], match.groups['description'], match.groups['defaultValue']); } } + + // return name of function, if docstring could be parsed successfully + return fn.name; } // Higher-order function to decorate other functions and provide logging -- 2.45.1