c-blake / cligen

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

stopWords may be insufficient to achieve stopWords goals #123

Closed disruptek closed 4 years ago

disruptek commented 4 years ago

I'm trying to make -- work in the conventional way, using the stopWords parameter. I wanted a command-line syntax like foo -a -b bar -- biz -x -y but I feel like I've simply overlooked an easier way to accomplish this, as stopWords don't seem to help all that much.

Here is test/MultiFlag.nim:

proc demo(alpha=true, beta=2, verb=false, args: seq[string]): int =
  ## demo entry point with varied, meaningless parameters.
  echo "alpha:", alpha, " beta:", beta, " verb:", verb
  for i, arg in args: echo "positional[", i, "]: ", repr(arg)
  return 42

when isMainModule:
  import cligen
  dispatch(demo, stopWords = @[ "hi", "ho" ])

An example invocation:

$ ./MultiFlag one two hi three ho four
alpha:true beta:2 verb:false
positional[0]: 0x7ffb2ff2d170"one"
positional[1]: 0x7ffb2ff2d2b0"two"
positional[2]: 0x7ffb2ff2d468"hi"
positional[3]: 0x7ffb2ff2d5a8"three"
positional[4]: 0x7ffb2ff2d760"ho"
positional[5]: 0x7ffb2ff2d8a0"four"

I modified this test to use stopWords=@["--", "ho"]:

$ ./MultiFlag one two -- three ho four
alpha:true beta:2 verb:false
positional[0]: 0x7fbc2c702170"one"
positional[1]: 0x7fbc2c7022b0"two"
positional[2]: 0x7fbc2c7023f0"three"
positional[3]: 0x7fbc2c7025a8"ho"
positional[4]: 0x7fbc2c7026e8"four"

Here it's actually swallowing the stop word and I can't tell where my "extra" arguments begin and end, or if they exist at all, for that matter.

c-blake commented 4 years ago

stopWords was more about subcommands than "nested commands" as you seem to be trying to do. They may be the same or they may be just different enough to cause trouble. Conventionally, -- is more a separator between "possibly options" and "no longer possibly options" to the primary command, as in dups -lsumm foo -- -bar to, say, allow filenames beginning with -. -- is actually implemented in parseopt3, IIRC. So, your putting it in stopWords seems at least redundant. Anyway, we might be able to get stopWords or -- in some combination to work as you need it to. I am mostly clarifying its original intent. It isn't quite your use case. I may have to think about your use case a bit.

c-blake commented 4 years ago

If it's just a CLI then a maybe easy/overlooked way to achieve what you want might be able to do your own split("--") of commandLineParams() probably eating that delimiter as part of a custom mergeParams(). Your mergeParams() would just send only the first half through to cligen, say, and package up the second half in a higher scope (could be global or maybe just higher) var to be consulted later.

If it's helpful to see a concrete example of params shenanigans, one of the fancier mergeParams() that I've written myself is the one in https://github.com/c-blake/procs (search procs.nim for mergeParams). That might spur some other creativity on your part, as well.

c-blake commented 4 years ago

(If it's not just a CLI but an API-CLI hybrid as per the kind of inspiring use case of cligen then what the semantics of cmdA ... -- cmdB ... -- cmdC ... in terms of Nim code even are would need more careful consideration.)

c-blake commented 4 years ago

Oh, and I should also have mentioned that my hypothetical proc split(sq: seq[string], dlm: string="--", eatDlm=true): seq[seq[string]] is something you would have to implement yourself, though that should be easy. If you ask nicely I can add something like that to all the various support libs I put in cligen.

disruptek commented 4 years ago

I did look at mergeParams but there was something that turned me off there. I think there's a cligen example in which you supply the input arguments for cligen parsing, which is probably the cleanest solution.

Here's the program: https://github.com/disruptek/golden

And an example usage is, eg. when we benchmark koch:

$ golden --json koch -- boot -d:danger

but I realized I could just subtract the number of post--- arguments from the list of arguments that remain after cligen has processed them, since with the stopWord, it should not adjust the remainder of the sequence.

https://github.com/disruptek/golden/blob/master/src/golden.nim#L105

So I guess that as long as stopWords behavior doesn't change, I've got a trivial solution. Thanks for cligen and thanks for having my back! :smile:

c-blake commented 4 years ago

I see. You want to wrap an array of command lines. Your current solution looks ok for just 1 command requiring arguments/options.

However, the theory of operation of your program to me suggests that your sources parameter is really a seq[seq[string]] all the time, conceptually. From your loop over targets and appearsBenchmarkable, it seems you think you can automagically simplify that for CLI invokers some/most of the time, but there are other times when that's hard and you'll need the user to delimit things. Is that right? If so, maybe what would be best is two syntaxes - long form:

golden [gopts] -- cmd1 opts1 args1 -- cmd2 opts2 args2

and then a short form when your automagic can work:

golden [goldenopts] cmd1 identifiably-non-cmd-args1 cmd2

Your current solution for just 1 command then becomes a special case of the long form syntax for one wrapped command. The only limitation of the long form is that no commands can actually take -- as an argument themselves. You could take a user-supplied delimiter. E.g.,

golden --delim="" -- cmd1 * "" cmd2

(Note that the empty string is an illegal filename. So, * cannot create it.)

cligen should always leave any -* after the very first -- alone since that is the main purposes of --. So, I doubt you need any cligen modifications if you like the above idea and "--" is a fine default for that delimiter string.

Of course, if there is literally no string guaranteed absent in sub-argument lists of a given golden invocation then perfect generality is lost. That's probably very rare, though. The only upshot is you cannot bundle such commands together into just one golden call which also isn't so bad.

You would probably want that split I mentioned to simplify some proc parse() taking param sources: seq[string] over to a local variable seq[seq[string]] as in let targets = parse(sources). (I would probably say let sources = parse(sources) these days.) This would also keep your proc golden callable as an API. You might also want to provide/export a join matching that split so that an import golden Nim caller could say golden(sources=cmds.join("--")).

Besides probably simplifying downstream code for you, the above long/short form syntax separation idea (both producing seq[seq[string]]) might also help you keep more cleanly separate any "interpreter deciding benchmarkability time" and "time actually running benchmarks". E.g., if testing if some arg is "a file or not" could decide benchmarkability and that file is on a network file system and that network is shared and you need to test he file/hit the net every time then that risks adding artificial noise.

disruptek commented 4 years ago

That's good thinking, but it's a pretty sophisticated solution for a simple program. If you want to invoke the program with multiple command-lines, simply execute it, y'know, on multiple commmand-lines. :wink:

The feature we want to target here is the running of multiple programs with the same set of command-line arguments, not the fact that we can vary those options. We already get that for free. The typical benchmark of multiple inputs might be something like this:

$ golden benchmarks/*.nim -- cpp -d:danger -d:temp:/tmpfs -d:data:/some/input -d:owner:$USER

To put this another way, if I'm going to compose a different command-line for each program, I may as well just delineate each such filename and set of arguments with ;golden, instead of --:

$ ;golden prog1 -d:thing1 ;golden prog2 -d:thing2 ;golden prog3 -d:thing3

c-blake commented 4 years ago

Ah. I see. I think I slightly misunderstood the idea you were after. Anyway, I may add that split and join to cligen this weekend for anyone wanting to parse an "array of cmdlines". It may be helpful to someone.

disruptek commented 4 years ago

Cool, yes, that'll be helpful. Thanks again,