Skip to content
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

Add .commandDir(dir) to API to apply all command modules from a relative directory #494

Merged
merged 6 commits into from May 30, 2016

Conversation

nexdrew
Copy link
Member

@nexdrew nexdrew commented May 3, 2016

This replaces #463, opting to require modules from a single directory at a time via require-directory (instead of supporting globs).

The implementation is slightly trickier than I originally thought, but it allows a consumer to do things like the following.

Example setup

directory structure:

cli.js
commands/
  people.js
  places.js
  things.js
  common_cmds/
    details.js
    search.js
  people_cmds/
    call.js
  places_cmds/
    nav.js

cli.js:

#!/usr/bin/env node
require('yargs')
  // apply people.js, places.js, things.js but not subdirectories
  .commandDir('commands')
  .help()
  .wrap(40)
  .argv

commands/people.js:

exports.command = 'people <command>'
exports.desc = 'Work with people'
exports.builder = function (yargs) {
  // apply commands in subdirectories
  return yargs.commandDir('common_cmds').commandDir('people_cmds')
}
exports.handler = function (argv) {}

commands/places.js:

exports.command = 'places <command>'
exports.desc = 'Work with places'
exports.builder = function (yargs) {
  // you can even share the same subcommands e.g. common_cmds
  return yargs.commandDir('common_cmds').commandDir('places_cmds')
}
exports.handler = function (argv) {}

commands/things.js:

exports.command = 'things <command>'
exports.desc = 'Work with things'
exports.builder = function (yargs) {
  return yargs.commandDir('common_cmds')
}
exports.handler = function (argv) {}

commands/common_cmds/details.js:

exports.command = 'details <name>'
exports.desc = 'Get details by name'
exports.builder = {}
exports.handler = function (argv) {
  // parent command accessible via argv._ as always
  console.log('getting %s details for name: %s', argv._[0], argv.name)
}

Example CLI

$ ./cli.js --help
Commands:
  people <command>  Work with people
  places <command>  Work with places
  things <command>  Work with things

Options:
  --help  Show help            [boolean]
$ ./cli.js people --help
cli.js people <command>

Commands:
  details <name>  Get details by name
  search <query>  Search via query
  call <person>   Call someone

Options:
  --help  Show help            [boolean]
$ ./cli.js people details       
cli.js people details <name>

Options:
  --help  Show help            [boolean]

Not enough non-option arguments: got 0, need at least 1
$ ./cli.js people details Andrew
getting people details for name: Andrew

API

This adds one method to the API: .commandDir(dir, [opts])

dir is a directory name which can/should be relative to the module calling the method.

opts is an options object that is passed to require-directory, allowing consumers to customize the behavior. In the initial implementation, the following opts are valid:

  • opts.recurse: boolean, default false

    Should yargs require command modules from all nested directories? The default is false so subcommands can be nested instead of flattened.

  • opts.extensions: array of strings, default ['js']

    The types of files to include when requiring.

  • opts.visit: function

    A synchronous function that is called for each command module encountered. Accepts commandObject, pathToFile, and filename as arguments. Returns commandObject to include the command; any falsey value to exclude/skip it.

  • opts.include: RegExp or function

    Whitelist certain modules. See require-directory whitelisting for details.

  • opts.exclude: RegExp or function

    Blacklist certain modules. See require-directory blacklisting for details.

Notes

This also includes a change that allows a command module to only define command and description, which better supports requiring JSON modules (though I'm not sure why someone would want to do that) and fulfills (at least) a part of #492.

I considered adding support for using the filename of a command module as its command string if the module itself doesn't define command, but I think it'd be nice to support this for all command modules (i.e. not just when using .commandDir()), so I'd like to tackle that separately/subsequently.

I don't have strong feelings about the method name commandDir. Would be happy to change it if folks like something else better. As always, feedback is welcomed!

Still lacking

  • Tests!
  • README update

@coveralls
Copy link

coveralls commented May 3, 2016

Coverage Status

Coverage decreased (-1.9%) to 98.089% when pulling 8b2374d on command-dir into 82d4412 on master.

@bcoe
Copy link
Member

bcoe commented May 14, 2016

@nexdrew I love this work, don't suppose we could get a test in the mix :)

@nexdrew nexdrew mentioned this pull request May 19, 2016
@AGresvig
Copy link

Yes, this is exactly what I had hoped for - I think the chosen approach makes perfect sense and complements the functionality provided by command modules really well. Love it.
Please let this find it's way into master in the near future.

Great work @nexdrew

@nexdrew
Copy link
Member Author

nexdrew commented May 19, 2016

@AGresvig Thanks! I'll get some tests and docs written up within the next couple days so we can merge this safely.

@coveralls
Copy link

coveralls commented May 23, 2016

Coverage Status

Coverage remained the same at 100.0% when pulling 1d566c8 on command-dir into a54c742 on master.

@nexdrew nexdrew changed the title [WIP] Add .commandDir(dir) to API to apply all command modules from a relative directory Add .commandDir(dir) to API to apply all command modules from a relative directory May 24, 2016
@coveralls
Copy link

coveralls commented May 24, 2016

Coverage Status

Coverage remained the same at 100.0% when pulling 2b7d691 on command-dir into a54c742 on master.

@nexdrew
Copy link
Member Author

nexdrew commented May 25, 2016

@bcoe @maxrimue I believe this is ready for final review.

self.addHandler(cmd.command, extractDesc(cmd), cmd.builder, cmd.handler)
} else {
self.addHandler(cmd.command, extractDesc(cmd))
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused here, is allowing commands without builder and handler functions an essential feature for .commandDir() or is this unrelated?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maxrimue Good question. This could certainly be done in a separate PR (maybe as a precursor) but is just something I ran into when testing with require-directory.

A couple reasons:

  1. yargs currently blows up when you give it a command module/object that does not have all the expected properties of a command module - this is something that should probably be fixed in general
  2. require-directory looks for and loads json files as well as js and coffee modules by default, which I have overridden to be just js (since a JSON "command module" cannot support a handler function by itself), but if folks want to define "commands" via JSON files, then they will need this change to support an option of extensions: ['js', 'json'] (or similar)

I don't really think no. 2 is a big concern, unless folks are dynamically generating their commands as JSON (not sure why you would), but is more of a "why not?".

I view no. 1 as a bigger problem that we should address, either before or with #492. So figured I'd kill two birds with one stone here. I can easily be persuaded otherwise.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's address this in #429 👍

@maxrimue
Copy link
Member

Looks awesome, well done! Could we perhaps make some variables constants since there are some good reasons for doing so in yargs in general as shown in #502?

@nexdrew
Copy link
Member Author

nexdrew commented May 27, 2016

Could we perhaps make some variables constants since there are some good reasons for doing so in yargs in general as shown in #502?

@maxrimue Ooh, good catch! I think there are probably a couple places I could use const in here. I'll review that and push whatever changes are compatible with tests.

@nexdrew nexdrew mentioned this pull request May 27, 2016
@nexdrew
Copy link
Member Author

nexdrew commented May 27, 2016

@maxrimue Swapped var for const in commit 0d14096.

@maxrimue
Copy link
Member

@nexdrew Awesome! LGTM

@coveralls
Copy link

coveralls commented May 27, 2016

Coverage Status

Coverage remained the same at 100.0% when pulling 0d14096 on command-dir into 5dd1e63 on master.

@@ -19,6 +19,7 @@
"os-locale": "^1.4.0",
"pkg-conf": "^1.1.2",
"read-pkg-up": "^1.0.1",
"require-directory": "^2.1.1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate that this only pull in once dependency in total \o/

@bcoe bcoe merged commit b299dff into master May 30, 2016
@nexdrew nexdrew deleted the command-dir branch May 30, 2016 02:19
@bcoe
Copy link
Member

bcoe commented Jul 9, 2016

@nexdrew please give npm cache clear; npm i yargs@next a spin I'm feeling ready to publish 4.8.0 once windows tests are passing.

@bcoe
Copy link
Member

bcoe commented Jul 9, 2016

@AGresvig @maxrimue would love to also have you try out the next candidate release too 👍

npm cache clear; npm i yargs@next

@AGresvig
Copy link

@bcoe OK will do!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants