-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Control style with a flag #62
Comments
There's a way to do this that doesn't require multiple passes of parsing. The trick is getting access to the appropriate Here are a couple examples to choose from. Both of them dynamically apply a help text style and strict mode based on a boolean option given on the command line. Note that both examples presume the following custom styles (but you can obviously plug in whatever styles you want): const chalk = require('chalk')
const styleVerbose = {
hints: s => chalk.dim(s)
}
const styleCompact = {
hints: s => ''
} Example 1: Extend Api to force custom checkPros:
Cons:
// issue62a.js
// extend Api to force check handler to always run (and at every level)
const Api = require('sywac/api')
class AlwaysCheck extends Api {
constructor (opts) {
super(opts)
// apply static check handler at every level
this.check(argv => {
this.style(argv.compact ? styleCompact : styleVerbose)
this.strict(argv.strict)
})
}
shouldCoerceAndCheck (context) {
// force check handler to always run
return true
}
// this only needed if using commands
newChild (commandName, childOptions) {
return new AlwaysCheck(Object.assign({
factories: this._factories,
utils: this.utils,
pathLib: this.pathLib,
fsLib: this.fsLib,
name: this.name + ' ' + commandName,
parentName: this.name,
modulesSeen: this._modulesSeen.slice(),
helpOpts: this._assignHelpOpts({}, this.helpOpts),
showHelpByDefault: this._showHelpByDefault,
strictMode: this._strictMode
}, childOptions))
}
}
new AlwaysCheck()
.command('yo', argv => console.log('yo:', argv))
.boolean('--compact', { desc: 'Output help text without hints' })
.boolean('--strict', { desc: 'Error on unknown flags/args' })
.help('--help')
.outputSettings({ maxWidth: 55 })
.showHelpByDefault()
.parseAndExit() $ node issue62a.js --help
Usage: issue62a <command> [options]
Commands:
yo
Options:
--compact Output help text without hints [boolean]
--strict Error on unknown flags/args [boolean]
--help Show help [commands: help] [boolean] $ node issue62a.js --help --compact
Usage: issue62a <command> [options]
Commands:
yo
Options:
--compact Output help text without hints
--strict Error on unknown flags/args
--help Show help $ node issue62a.js yo -x
yo: { x: true, _: [], compact: false, strict: false, help: false } $ node issue62a.js yo -x --strict
Usage: issue62a yo [options]
Options:
--compact Output help text without hints [boolean]
--strict Error on unknown flags/args [boolean]
--help Show help [commands: help] [boolean]
Unknown options: -x $ node issue62a.js yo -x --strict --compact
Usage: issue62a yo [options]
Options:
--compact Output help text without hints
--strict Error on unknown flags/args
--help Show help
Unknown options: -x Example 2: Use custom types to override postParse logicPros:
Cons:
// issue62b.js
// define functions to apply config at different levels
function applyStyle (api, compact) {
api.style(compact ? styleCompact : styleVerbose)
}
function applyStrict (api, strict) {
api.strict(strict)
}
// define custom boolean type (for top-level api)
const TypeBoolean = require('sywac/types/boolean')
class DynamicConfig extends TypeBoolean {
constructor (opts, api, apply) {
super(opts)
this.api = api
this.apply = apply
}
postParse (context) {
this.apply(this.api, this.getValue(context))
return super.postParse(context)
}
}
// define custom command type (for lower level apis)
// this only needed if using commands
const TypeCommand = require('sywac/types/command')
class CustomCommand extends TypeCommand {
setValue (context, value) {
super.setValue(context, value)
if (value) {
applyStyle(this.api, context.argv.compact)
applyStrict(this.api, context.argv.strict)
}
}
}
// register factory for custom command type, apply custom boolean types
const api = require('sywac')
api
.registerFactory('commandType', opts => new CustomCommand(opts))
.command('yo', argv => console.log('yo:', argv))
.custom(new DynamicConfig({
flags: '--compact',
desc: 'Output help text without hints'
}, api, applyStyle))
.custom(new DynamicConfig({
flags: '--strict',
desc: 'Error on unknown flags/args'
}, api, applyStrict))
.help('--help')
.outputSettings({ maxWidth: 55 })
.showHelpByDefault()
.parseAndExit() $ node issue62b.js --help
Usage: issue62b <command> [options]
Commands:
yo
Options:
--compact Output help text without hints [boolean]
--strict Error on unknown flags/args [boolean]
--help Show help [commands: help] [boolean] $ node issue62b.js --help --compact
Usage: issue62b <command> [options]
Commands:
yo
Options:
--compact Output help text without hints
--strict Error on unknown flags/args
--help Show help $ node issue62b.js yo -x
yo: { x: true, _: [], compact: false, strict: false, help: false } $ node issue62b.js yo -x --strict
Usage: issue62b yo [options]
Options:
--compact Output help text without hints [boolean]
--strict Error on unknown flags/args [boolean]
--help Show help [commands: help] [boolean]
Unknown options: -x $ node issue62b.js yo -x --strict --compact
Usage: issue62b yo [options]
Options:
--compact Output help text without hints
--strict Error on unknown flags/args
--help Show help
Unknown options: -x Let me know if you have any questions. |
Thanks for the very detailed write-up! I guess for me, it's hard to justify your two examples because I don't think people could write those without reading the source. (Unless they were copied verbatim from an "Advanced Examples" section in the docs.) (My approach isn't as streamlined, but I think you could explain it to someone that had never looked at the Sywac source.) I don't know any change is required but something that would be really impressive, I think, is if globally all "synchronous" config was allowed to take functions instead of statics/objects. An example of this in action is the https://github.com/terkelg/prompts API - all config properties can take a function which is evaluated lazily. An example of how this might look: sywac
.boolean('compact')
.boolean('strict')
.strict(argv => argv.strict)
.style(argv => argv.compact ? styleCompact : styleNormal)
.parseAndExit() (Lots of details being glossed over here - when exactly is stuff evaluated, what parameters would be passed, etc.) An absurd example: sywac
.string('flag')
.string(argv => argv.flag)
.strict()
.parseAndExit() This of course is pretty cool but I don't see how it could work in practice. |
My goal in sharing code examples is just to see/prove if a possible solution exists. I agree that, many times, the examples are advanced, and I don't really expect most CLI authors to figure them out on their own, but at least it shows that the framework is extensible enough to support most use-cases. Otherwise, I'd like to improve sywac's api to make practical use-cases easy to implement.
I'm definitely open to this idea and would be willing to explore it. |
I was looking into how to control output styling with a flag.
It seems the best bet is probably to do two different passes of sywac - the first only checks for the flag (for example, a "compact" flag, that uses a condensed style instead of a nice colorful style), and the second does the complete, complicated API of your app.
However, since the parsing happens asynchronously, you actually need to set up the second one inside an async function... something like this:
Is there a simpler method than this for using an incoming flag to control an aspect of the synchronous configuration of your API? (A similar example might be a program with a hundred flags, one of which is actually
--strict
itself - turning on strict mode to warn if some other flag is misspelled.)The text was updated successfully, but these errors were encountered: