c-blake / cligen

Nim library to infer/generate command-line-interfaces / option / argument parsing; Docs at
https://c-blake.github.io/cligen/
ISC License
508 stars 24 forks source link

How should I create 'global' options ? #128

Closed mech422 closed 4 years ago

mech422 commented 4 years ago

Hi!

I'm trying to parse command lines like the following:

./foo --debug --logLvl=5 database initialize foo.tbl ./foo --debug --logLvl=3 database purge --older_then 1/1/2016 foo.tbl ./foo database list ./foo database list 'foo*.tbl' ./foo --debug --logLvl=1 cache purge ./foo --help

I found the mergeparams stuff and the mult(i)mult(i)mult(i) stuff.... but I'm stumped as to how to get the --debug and --logLvl options to appear BEFORE any of the sub-commands? I'm also not sure how I'd pass their values thru to the sub-commands - I think it would be thru the mergeNames of dispatchMultiGen, but I'm not sure.

My current test is based off MultMultMult and looks like:

when isMainModule:
  import cligen
  include cligen/mergeCfgEnv
  clCfg.version = "0.0.1"

  # most deeply nested
  dispatchMultiGen([ "cobbler", doc = "fooby - this doesn't show. Have to place it at next level up" ],
                   [ yikes, doc= "apple cobbler foo doc shows for steve apple cobbler help?",
                     # NOTE: we mergeNames all the higher lvl commands with ours
                     mergeNames = @["steve", "apple", "cobbler" ]])

  # middle level
  dispatchMultiGen([ "apple" ],
                   [ demo, help = { "verb": "on=chatty, off=quiet" },
                     mergeNames = @["steve", "apple" ] ],
                   [ show, cmdName="print", short = { "gamma": 'z' },
                     mergeNames = @["steve", "apple" ] ],
                   # this doc shows for 'steve apple help'
                   [ cobbler, doc = "apple cobbler SUB-SUB-SUB commands",
                              stopWords = @[ "yikes" ],
                              suppress = @[ "usage", "prefix" ],
                              # NOTE: we mergeNames all the higher lvl commands with ours
                              mergeNames = @["steve", "apple" ] ])

  # top level commands 
  # dispatchMulti([globals, doc = docFromModuleOf(steve.demo) ],
  dispatchMulti(["multi", doc = docFromModuleOf(steve.demo) ],
                [ apple, doc = "apple SUB-SUB commands",
                         stopWords = @["demo", "show", "cobbler" ],
                         suppress = @[ "usage", "prefix" ] ],
                [ whoa, echoResult=true ],
                [ nelly, noAutoEcho=true ])

Any pointers in the right direction would be much appreciated! Thanks!

c-blake commented 4 years ago

There isn't really a way to do this. Partly this is because there isn't really a de facto standard for what options mean in various parts of subcommand syntax. E.g., what does maincmd --g1 sub1 --g2 sub2 --xyz mean? Partly this is because it isn't clear what to do with the data from global options, though the two questions relate.

Another problem is that part of the core idea of cligen is to extract/lift the CLI from the APIs. These global options you want aren't really part of the API calls/procs in those leaf subcommands. The cleanest mapping I can think of might be to put them all in some kind of global or scoped var object. Even then, if API calls accessed them, that would make these API calls "CLI only" and not let them be Nim callable via import/regular invocation. I consider that an undesirable state of affairs. cligen may best conceived as "dual maintenance" of API-CLI frameworks.

What I might recommend for this kind of thing is os.getEnv. Then you could say:

DEBUG=1 LOGLVL=3 ./foo database init

(or you might just have a non-default value of $LOGLVL imply DEBUG).

c-blake commented 4 years ago

Another way to say all that might be that you seem to be starting from the CLI syntax you want rather than starting from the API calls you want. That probably isn't the best way to think of a cligen API-CLI library-app. It is true that sometimes the code can be only a CLI app not a library-app, but the framework and mechanisms are setup to work in that general case.

mech422 commented 4 years ago

Hmm.... personally I would have thought: maincmd --g1 sub1 --g2 sub2.... was invalid syntax as --g2 is only a valid option for the main(global) scope...but thats just me

I was going to do env overrides in MergeParams (as you've suggest in the past :-) ) so that's probably a good enough work around for now.

Yeah - I understand cligen is API focused...

Thanks for your (really FAST!) help!

c-blake commented 4 years ago

Well, I think that --g1 --g2 should be invalid. It was just an example to make one think what should it mean were it not invalid if you can forgive the double negatives. A proc is a well-defined scope, but how a nested subcommand would map onto Nim-nested scopes/behavior/naming/storage is all pretty murky.

My suggestion was not mergeParams, but just regular old os.getEnv for these global options (with you calling parseInt yourself). The idea would be you set some higher-scoped or global var(s) based on them and then just access from within your leaf API procs. mergeParams is really more about how cligen-generated dispatchers assemble the argv[] they will parse. That said, you may want to call the variable FOO_LOGLVL and be careful to not have a subcommand also named loglvl to avoid conflicts with the default mergeParams naming scheme.

mech422 commented 4 years ago

yeah...I just meant I was going to have env overrides anyway, so it wouldn't be 'strange' to have to do the globals that way.

I also freely admit I'm abusing the hell out of your poor lil cligen... But thats because its the best command line parser I've found for Nim :-)

I just assume I can put a square peg in a round hole if I use a big enough hammer :-)

Thanks again for all the help, and the awesome library!

c-blake commented 4 years ago

You're welcome. Good luck! (I like it, too. :-) )