c-blake / cligen

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

How can I customize the usage string for `dispatchMulti`? #107

Closed kaushalmodi closed 5 years ago

kaushalmodi commented 5 years ago

Hello,

For a single dispatch, I like using a custom usage string pattern like below:

dispatch(ntangle,
  usage = "\nNAME\n  ntangle - $doc\n" &
  "USAGE\n  $command $args\n\n" &
  "OPTIONS\n$options\n" &
  "URL\n  " & url & "\n")

But how can I do that for the top-most level command in dispatchMulti?

I tried doing below but that of course didn't work:

dispatchMulti([subProc1], [subProc2],
  usage = "\nNAME\n  ntangle - $doc\n" &
  "USAGE\n  $command $args\n\n" &
  "OPTIONS\n$options\n" &
  "URL\n  " & url & "\n")

My main purpose to specify the usage is so that I can add a brief description of what the command does, and put in the url of the source at the end.

c-blake commented 5 years ago

Internally, a multiple dispatch command is just a generated proc that does the sub-command dispatching which is itself auto-wrapped by dispatchGen. My hope had been to be able to tune top-level command dispatchGen parameters (like your usage need) via the "multi" subcommand as in test/FullyAutoMulti.nim. That fails to compile in a test I just tried (because the keyword usage gets sent twice to the generated call to dispatchGen).

I'm not sure if it ever worked. { I really like my top-level default and haven't tested tweaking it. :-) } In my code I tried to "provide a default", but let the caller override it via this paramPresent/paramValue protocol that seems to work in other situations. Let me look into this a little.

kaushalmodi commented 5 years ago

In my code I tried to "provide a default", but let the caller override it via this paramPresent/paramValue protocol that seems to work in other situations. Let me look into this a little.

Thank you. Just to better demonstrate the need for usage, I have this little utility called p4x. If a user new to this does p4x -h, they get:

Usage:

  p4x {CMD}  [sub-command options & parameters]

where {CMD} is one of:

  help      print comprehensive or per-cmd help
  ..

There, I would like to give a 1-line description of what p4x exactly is.

c-blake commented 5 years ago

Oh, I get it. What might really be best for your use case is doc being somewhere in the top level usage template. { doc is a dispatchGen keyword param to replace the ## comment-inferred documentation for the overall function of a wrapped proc. When I first started Nim had a bug where this ## AST nodes weren't reliable, but it also made sense to let CLI authors override what the API said since CLI users and API users are at least potentially distinct subpopulations of humans. }

With some kind of $doc interpolation, you wouldn't have to update the usage or your doc when you add new subcommands. I just tested that and it doesn't work either, though. :-) I think I may understand my bug, though.

kaushalmodi commented 5 years ago

What might really be best for your use case is doc being somewhere in the top level usage template.

Yes, that would work.

As a bonus, can the project url be parsed from a special doc comment, and if found, can that be auto-inserted at an appropriate place in the default help?

c-blake commented 5 years ago

Maybe. I might do some staticRead of a .nimble file and provide some docFromNimble proc the way test/Version.nim does. Or we could do both that and a docFromElsewhere proc and the CLI author can just put a doc=docFromX() in the [ "multi", .. ] section of their dispatchMulti. That's all the "easy" part. First thing is to get [ "multi", doc="foo" ] working.

c-blake commented 5 years ago

Once I get everything working properly, you should be able both to set doc and override usage to say where $doc goes. In the interest of you needing to do "least of all", though, where do you think that one-liner should go?

The message is a bunch of sub-commands probably "summarized" by the $doc, and summaries earlier make sense since they give context to the details. So, it's mostly a question of before vs after "Usage:\n". I sort of feel like before looks best, as in:

Convert Helix Version Control / Perforce (p4)-ztag output to JSON
Usage:
  p4ztag_to_json {CMD} [sub-command options & parameters]
where {CMD} is one of:
 ...

(I've been a little of two minds about the extra blank lines in there).

The other question of where we try to grab/default $doc from is tricky as it kind of depends on how verbose the CLI author wants the usage message/how verbose various text might be. description out of .nimble is on the one hand text that was "engineered to be a useful, terse one-line summary".

Another idea might be to default doc to the module doc comment. That's probably what you meant. In your case something like

The ``p4ztag_to_json`` module converts the Perforce P4 "Ztag" format to JSON.

The pros of that are that it's pretty unlikely a single nim file would compile to more than one dispatchMulti, and also pretty likely that any overall module doc comment would be the right sort of level of description for the whole group of multi-commands. But it also might be really expansive about methods and context. So, we might want to limit to just the first paragraph or something (or at least have an option for that). That formatting might also be RST for nim doc instead of plain text which might confuse a CLI user or look ugly.

I'm also not sure if there is a way to get at that module doc comment from within a macro. The nim doc stuff are all special compiler passes. macros.lineinfo gives us the path to the source file. So, it might be possible to staticExec out to some whole customizable pipeline that is given the name of the source file. Anyway, the short of it is that a CLI author might have to say doc=docFromModule() anyway with some proc that takes at least a few parameters (1st paragraph only, reformat, etc). So, it might be best to have CLI authors "forced to choose a source" for that text than to try to default it to anywhere/anything but "".

These are just some of my thoughts on the matter..Not trying to be a last word on anything.

kaushalmodi commented 5 years ago

Just for reference, this is what I have for my ntangle project:

NAME
  ntangle - Command-line utility for Tangling of Org mode documents

USAGE
  ntangle [optional-params] [orgFilesOrDirs: string...]

OPTIONS
  -h, --help                  print this cligen-erated help
  --help-syntax               advanced: prepend,plurals,..
  --version      bool  false  print version

URL
  https://github.com/OrgTangle/ntangle

It inspired from perl docs I believe.


where do you think that one-liner should go?

I like your proposal, except that ..

(I've been a little of two minds about the extra blank lines in there).

I like newlines between usage sections .. so may be the below? :)

Convert Helix Version Control / Perforce (p4)-ztag output to JSON

Usage:
  p4ztag_to_json {CMD} [sub-command options & parameters]

where {CMD} is one of:

description out of .nimble is on the one hand text that was "engineered to be a useful, terse one-line summary".

I would be fine with that.

So, we might want to limit to just the first paragraph or something (or at least have an option for that).

But I like the parsing of module doc comment, if that's even possible. A cligen user would then need to take care to not use RST in the first paragraph.

Anyway, the short of it is that a CLI author might have to say doc=docFromModule() anyway with some proc that takes at least a few parameters (1st paragraph only, reformat, etc).

No issues with that! I am already liking how I can specify how to construct the --version string. So this will be something similar, I believe? clCfg.doc = ..?

c-blake commented 5 years ago

Ok. I kind of like your blank line proposal. That's who pijul help works, too. So, I will probably switch to that as part of fixing this issue (which may take a couple days).

As far as not using RST..Well, part of the idea of cligen is that the CLI may just be a wrapper around some elsewise-maintained API module, in principle maybe even by two independent people/projects. So, I don't like solutions like "the CLI author can just avoid xyz" when the xyz may not be under their control. Preferred instead are some filtering mechanisms where the CLI author can adapt whatever the API author did/does in the future to the needs of the CLI. { This 3 level aspect of cligen is not something a lot of people really internalize because they operate on sub-sets of the 3-levels..I myself am API author, CLI author, and CLI user for everything I do with cligen, but I try to put on my layering abstraction hat in questions like this. } So, some kind of rst2ascii filter run out of staticExec is the sort of solution I would tilt towards.

The sub-command dispatcher is its own proc, not unlike one you might just write with its own (currently buggy) dispatchGen call. That subcommand dispatcher proc doesn't really have parameters with default values. It supports --help and maybe --version just in a "can type those options anywhere and get something useful" sort of way. So, of the various interpolating things in usage (command, args, doc, options) maybe only $command and $doc really make sense. Presently, the usage used by that one dispatchGen of the subcmd dispatcher is inflexible. It just hardwires command to basename(srcfile) and has nothing else. It should probably interpolate just command and doc from a ["multi", ..] overridable usage. That's what I'm working on now.

To make an overlong response even longer, in the larger world, these "multi-commands" are really not nearly as "standardized" as single-issue commands (not that even thing like --long=, -l= vs -l=,--long= or even -h for --help are so uniform! --help is thankfully very standard, but that's about it). cvs was the first multi-command I ever heard of back in the early 90s. svn copied that and then the DVCS crowd of hg/git/etc. I guess docker picked up on that which might be creating some converts and busybox is some other family. And maybe nim itself, though I think it is more of "mix" than a strict sub-command structure. nim help does nothing.

Even so, all the multi-dispatch commands in common use probably number in the "low 10s" rather than "many thousands" of regular commands in my /usr/bin. So, there's just at least two or three orders of magnitude less guidance as to "what people do or expect to happen". Sane default behavior becomes a highly personal thing, dragged around by individuals' experience of their many iterations over very small samples. A "help" subcmd seems mostly standard, but git help doesn't list all available sub-commands unless you say git help -a. For others help -a doesn't work at all. In cligen the idea that ./test/MultMultMult a c y --ho=2 "just works" might blow some minds [ in maybe both good & bad ways ;-) ].

Something that may interest you is that I've been considering adding an automatic version subcommand much like the help subcommand, but only if clCfg.version is set. hg|git|docker version all work the same anyway..more so than their respective help systems. It's more rare than a help subcommand, though. multi help is about as universal as single --help.

c-blake commented 5 years ago

cligen HEAD seems to be working for me. Lets you override a usage template for dispatchMulti (and doc and cmdName, though cmdName becomes $command in the template as with regular dispatch).

The default template clUseMulti puts a $doc right at the beginning. test/FullyAutoMulti.nim seems to work fine, anyway.

I decided the "where" clause looked better tight up against it's introducer, but if you hate that you can either change the template via ["multi", usage=x] or you can try to persuade me to not use "where" at all but a "SubCommands:\n" style section header.

Assuming this all works for you, I think this issue is probably ready to close except for possibly a few minor refinements:

1) Try writing a proc to get a doc line out of the module doc comment.

2) There are a couple of other help dumps (search for "subcommand-opts" in cligen.nim) that are not templated yet. We could maybe add a usage2 and usage3 or something. I'm not sure how popular comprehensive help mode even is, though.

3) We could potentially make that burb from "no args at all" to "comprehensive help" into another interpolation variable, but I kind of feel like that material is pretty helpful to people new to multi-commands.

kaushalmodi commented 5 years ago

The default template clUseMulti puts a $doc right at the beginning. test/FullyAutoMulti.nim seems to work fine, anyway.

Thanks. Following the changes in that test worked for me too!

Btw TIL that the "multi" string in there is special : https://github.com/c-blake/cligen/blob/de0ed585766b63696d90d817e4bfd19e40e32e15/test/FullyAutoMulti.nim#L34

I decided the "where" clause looked better tight up against it's introducer, but if you hate that you can either change the template via ["multi", usage=x]

I don't have a strong opinion about that missing blank line before the "where" clause. If it irks me, I will use the usage key. So thanks for adding that too... Or I might need to use usage anyways to add the URL bit.


Try writing a proc to get a doc line out of the module doc comment.

This will be nice.

Please feel free to close this issue.

c-blake commented 5 years ago

Ok. Closing. Also, if you like the location of the doc line in my default usage you can just use doc = "my doc line". But, yeah, you can just write the entire message in usage. There could be trouble if you want a literal substring that looks like an interpolation variable, though, like $command. Maybe there's a way to quote that $ in strutils.%?

kaushalmodi commented 5 years ago

Or I might need to use usage anyways to add the URL bit.

I was trying this ..

  dispatchMulti(
    [ "multi",
      usage = "\nNAME\n  p4x (pitchforks) - " & docLine & "\n" &
        "USAGE\n  $command $args\n\n" &
        "OPTIONS\n$options\n" &
        "URL\n  " & url & "\n" ],

..

but that gives me this error:

cligen.nim(832)          p4x
cligen.nim(510)          multiSubs
cligen.nim(743)          multi
cligen.nim(653)          topLevelHelp
strutils.nim(2711)       %
strutils.nim(2657)       addf
strutils.nim(2600)       invalidFormatString
Error: unhandled exception: invalid format string [ValueError]

Let me play with which of the dispatch-available special $ vars are available with dispatchMulti too.

c-blake commented 5 years ago

There is no $options for the "multi" usage. Only $command, $doc, $subcmds, $ifVersion. See clUseMulti definition in cligen.nim.

c-blake commented 5 years ago

I could add $options that expanded to the empty string, but really there aren't any useful options to the multi-cmd dispatch proc (besides -h/--help and maybe --version). "multi", usage= is not really a cut&paste target for single-dispatch usages. This should be documented better, obviously, but you were part of the feature discussion.

kaushalmodi commented 5 years ago

Thanks.

I tried adding $subcmds, but that gives an error too:

  dispatchMulti(
    [ "multi",
      usage = "$subcmds" ],

..
cligen.nim(832)          p4x
cligen.nim(362)          multiSubs
strutils.nim(2711)       %
strutils.nim(2657)       addf
strutils.nim(2600)       invalidFormatString
Error: unhandled exception: invalid format string [ValueError]
kaushalmodi commented 5 years ago

hmm.. order matters...

works

Update: Doesn't work; the last "multi" element is ineffective in this case.

dispatchMulti(
    [ subcmd1 ],
    [ subcmd2 ],
    [ "multi",
      usage = "$subcmds" ]
)

doesn't work (gives above error)


dispatchMulti(
    [ "multi",
      usage = "$subcmds" ],
    [ subcmd1 ],
    [ subcmd2 ]
 )
c-blake commented 5 years ago

Yeah. Order matters. There is at least one place I only check [0] for that nnkStrLit node type of the first bracket slot.

kaushalmodi commented 5 years ago

Actually, I was wrong .. while putting ["multi",..] at the end compiles, that whole array is ineffective... the usage I specify in there is not used at all.

c-blake commented 5 years ago

I could probably do some work to allow [ "multi" ] anywhere. To me it makes most sense to me visually at the top since it organizes the subsequent sub-commands. Anyway, it sounds like the at-the-top form is working for you.

kaushalmodi commented 5 years ago

Yes, it makes sense to be only at the top; just that $subcmds doesn't work when "multi" is at top.

c-blake commented 5 years ago

Huh. I just confirmed that myself. Not sure why. I will look into this tomorrow. Re-opening until I fix that.

c-blake commented 5 years ago

Oh, I see the issue. It's that doc and usage are being passed through to the dispatchGen of the "proc multi" that does the dispatching but that template doesn't understand either $subcmds or $ifVersion. This will take a bit to fix, but I'll do it with fresher eyes in my morning. The pass through only happens with explicit passing in the ["multi",..] bracket. So, for tonight you only have doc that works. Can't quite change the usage template.

c-blake commented 5 years ago

I think this all works now ("multi" still required to be first slot).

kaushalmodi commented 5 years ago

Thank you! I confirm the fix. There was also another issue introduced from my side.. I was doing something like:

usage = "something something $ifVersion" &
  "URL" & url

I forgot the "\n" in there, so the usage got constructed as something something $ifVersionURL" & url, and the $ifVersionURL gave:

cligen.nim(510)          p4x
cligen.nim(653)          topLevelHelp
strutils.nim(2711)       %
strutils.nim(2657)       addf
strutils.nim(2600)       invalidFormatString
Error: unhandled exception: invalid format string [ValueError]

Now I am bash-style wrapping all the internal cligen vars .. so ${ifVersion} and so on.

c-blake commented 5 years ago

Yeah. I had to do ${doc}Usage:". Also, just now I added that docFrom Module idea as the default if the CLI author specifies no ["multi", doc=x]. So, you may be able to only tweak usage. Or if you like my clUseMulti do nothing at all.

On this sort of topic, though, do you know is there a way to "quote" the $ so that if a doc comment just happens to include the literal substring $command we don't interpolate it by accident? { Probably in strutils.% documentation somewhere. I realize I'm just being lazy asking. }

c-blake commented 5 years ago

Looks like double-dollar - $$ is the way to quote it. Undocumented - I had to read the parser. ;-)

c-blake commented 5 years ago

It looks like the ${} is also undocumented.

kaushalmodi commented 5 years ago

The usage support was really needed for me. Thanks for adding it.

I now have:

NAME
  p4x (pitchforks)
    Wrapper around the Perforce p4 binary that cuts down the redundancy and makes life colorful.

USAGE
  p4x {SUBCMD}  [sub-command options, parameters]

SUBCOMMANDS
  help      print comprehensive or per-cmd help
  edit      Open / Check out one or more files for editing.
  opened    List opened files.
  revert    Revert one or more files. By default, only unmodified files are reverted.
  diff      Diff files with respect to the workarea.
  changes   List changelists.
  sync      Sync workarea.
  files     List files in depot.
  filelog   Show details about files.
  describe  Describe changelists.

p4x {-h|--help} or with no args at all prints this message.
p4x --help-syntax gives general cligen syntax help.
Run "p4x {help SUBCMD|SUBCMD --help}" to see help for just SUBCMD.
Run "p4x help" to get *comprehensive* help.
Top-level --version also available

URL
  https://gitlab.company.com/user/p4x
kaushalmodi commented 5 years ago

Also, just now I added that docFrom Module idea as the default if the CLI author specifies no ["multi", doc=x].

Thanks! I just git pulled and tried that and it works.

Just a minor nitpick.. can you please call .strip() on that parsed doc from module into the internal variable $doc.. it's adding few extra newlines at the end.

If not, it's not a big deal; I'll use the new docFromModule.

c-blake commented 5 years ago

Well, I already call strip(), but I add a double newline in that doc (as well as the demo one in test/FullyAutoMulti.nim) to get a blank between the one-liner/brief summary and "Usage:" since the default usage template is set up to look good with either empty string $doc or populated $doc.

kaushalmodi commented 5 years ago

I am turning out to be a pain..

the signature of docFromModule is proc docFromModule*(n: NimNode): string =.. so I cannot use it directly in my package it seems. How do I get the NimNode?

kaushalmodi commented 5 years ago

ok.. I will also use ${doc}USAGE as you do then. Earlier I had ${doc}\nUSAGE.

Update: This works: http://ix.io/1Kxt, but I wish I didn't need to stick the USAGE bit right next to ${doc} (I later do [ "multi", usage = topLvlUse.join("\n") ]).

c-blake commented 5 years ago

Yeah. That's why I have protocol be default to the doc comment. Users who have a non-empty module doc comment now need to actively suppress it with ["multi", doc=""] (or I guess an empty line at top of file). I don't really like that either, but I didn't see an easy workaround. Totally open to suggestions on this, though. If you can come up with a proc we can call from the ["multi", doc=x] context I will happily include it (and might change the protocol).

c-blake commented 5 years ago

As for "${doc}USAGE", I am open to suggestions but I don't see a simpler solution that supports both empty and populated $doc, both of which seem likely cases. I could try some $ifDoc, but then it gets even uglier. I could put a newline in there, but then that mandates an initial blank for an empty $doc.

kaushalmodi commented 5 years ago

Yeah, I see the reason to do ${doc}USAGE. I am fine with it because the most importantly, it works as I want! :)

c-blake commented 5 years ago

Cool. It just seems easier to have users put in however many blanks they want - zero, one, two, than for me to try to take them out later in some conditional way.

Let me know if you come up with a ["multi", doc=docFromMod()] that works. If we want to change this it would be ideal to do it before the next cligen release. I didn't try very hard. I had this macros.lineinfo thing I had already used to default cmdName for dispatchMulti to the basename of the source file. There may be other entry points to that data.

c-blake commented 5 years ago

Also, I would not be opposed to putting definitions of clUse, clUsage, clUseMulti into some separate file under cligen/defaults.nim (or something) and having a "library" of definitions available like clUseMulti2, clUseMulti3, etc. That could simplify the call to ["multi", usage=clUseMulti3]. Or we could name them ["multi", usage=clUseMultiPerlDoc] or whatever.

kaushalmodi commented 5 years ago

having a "library" of definitions available like clUseMulti2, clUseMulti3, etc.

I'd like that. I've become a fan of enum indexed array. So you can have a const clMultiUsage*: array[ClMultiUsage, string] = [ "..", ..], where ClMultiUsage is an enum with values clmuDflt, clmuPerlDoc, ..

User then uses .. usage=clMultiUsage[clmuPerlDoc], ...

c-blake commented 5 years ago

Well, some 3rd party cannot extend my enum sequence, but they can extend the prefixed naming convention. So, if the 2..4 versions distributed with cligen aren't enough, but they want to make it look similar, they can call it clMultiUsageMine and put it somewhere convenient for import/include if they have several multi-commands. If they want it to look dissimilar, they can do that, too. Plus, just the varname style is shorter.

I'm not against enum indexed arrays, though. I just put one in cligen for the new style hTabCols*: seq[ClHelpCol]. I just think for this specific context they're not quite as good as just a variable name.

c-blake commented 5 years ago

Well, I went ahead and pushed something along the lines of what I am thinking. I think you can say (roughly) this to get your current message:

usage = clUseMultiPerlish &
          "\n\nURI\n  " & uriFromNimble(nimbl) &
          "\n\nAUTHOR\n  " & authorFromNimble(nimbl)

I know it does not look very good when ${doc} is empty string, but eh..People can use their own string template or just use another clUseMultiX. All templates looking good with all inputs feels like an overreach. Also, obviously you could inline string literal data instead of using those two new nimble extractors.

Also, maybe now that there are 4 Nimble metadata extractors it should just become fromNimble(field: string, data: string). Those calls are all almost brand new. So, I doubt it even needs a deprecation.

c-blake commented 5 years ago

I just cleaned up the growing manyFromNimble mess. So, my above example would now become:

usage = clUseMultiPerlish &
          "\n\nURI\n  " & fromNimble("uri", nimbl) &
          "\n\nAUTHOR\n  " & fromNimble("author", nimbl)

Let me know if you'd like any changes to clUseMultiPerlish.

c-blake commented 5 years ago

We can let this sit for the weekend, but unless you have a docFromModule() impl idea or change request for clUseMultiPerlish by, oh, Monday or so, I am going to punch a new release around then. ["multi",doc=""] is a pretty terse suppression mechanism (as well as good to know for such users to learn how to set doc to whatever) and module doc comment is probably the right default, much like proc doc comment is for that situation. I'm less certain about clUseMultiPerlish, but honestly it's mostly a string just for you. So, feel free to issue a PR or just tell me what would be better. (That's less time sensitive than settling on the new doc-defaulting protocol.) Have a good weekend.

kaushalmodi commented 5 years ago

This Friday turned out to be a whole lot busier than I expected (at work). I intended to try out the new clUseMultiPerlish (nice name) and give my feedback, but I never got to it.

It read through your changes and they look good. I had earlier suggested enum-indexed array because I did not think that you intended to open up the help string library for 3rd party. So what you have now makes perfect sense.

unless you have a docFromModule() impl idea

I have almost zero experience dealing with macros, so I won't be able to give any valuable idea regarding that.

or change request for clUseMultiPerlish

As soon as I am able to update cligen and try it out, I will definitely give my feedback.

But don't hold off the release for that.

Have a good weekend :)

c-blake commented 5 years ago

I think staticRead could work for docFromMod (just as it does for fromNimble) except getting the source file name in the first place. Thinking about it a little more, it isn't so hard. We could do a template sourceOf(pro: typed{nkSym}) or some static string or something and probably have the proc call that. I really didn't try hard to do it before switching tactics. About the only upside to all that jazz over the current docFromModule is just A) allow "parameterization" of what paragraph boundaries mean and how much to take (more natural to pass than set in clCfg?) and B) make docFromModule callers specify some specific symbol to ID the module in case there are variety of sources of modules defining symbols. But B) is a mixed bag - it's also nice to not have CLI author's have to say anything and get a good default. Inside the dispatchMulti, I can just take "brackets[1]" (or even search for the first wrapped proc belonging to a module with a non-empty 1st paragraph), but on the flip side there's no way to give docFromMod(sym) a default parameter equal to some user-defined symbol.

So, mostly it comes down to how likely to be correct we think the current default behavior is/how unnatural parametrizing it through clCfg would be. The current way is "less work on the lazy CLI author but more of a guestimate". It's hard to assess such likelihood in a vacuum and I don't really have a mailing list of users to ask. It's probably an ok default. We can see if anyone complains, I guess. I don't know how many dispatchMulti users there even are. I feel like I'm getting close to a near feature complete 1.0 sort of release..Next month or two, really. So, I'm being a little more conscious of interfaces staying mostly backward compatible moving forward.

Things like clUseMultiPerlish can mutate all around forever, though. I don't think help format changes would ever constitute a breaking change (though any change may be disliked by someone).

c-blake commented 5 years ago

Well, I made docFromModuleOf (note "Of") work the way we had talked about. If a user wants to define their own, they can just define a new string to string extractor like cligen/macUt.summaryOfModule(sourceContents) and also define their own macro docFromMod*(sym: typed{nkSym}): untyped to call it and then use it in their doc= line.

In terms of the default summaryOfModule, looking at the Nim stdlib all the files start with some single-pound '#' marked copyright notice and blank line. So, we might want to change the logic there to skip all such non-code before the first double-sharp '##' doc comment and call the first "doc paragraph" the first blank-ish thing in that block. I don't know how many Nim modules in the wild will start with a paragraph that is a useful summary in the context of a dispatchMulti generated command.

For your code, you'll probably just tune your top of module comment text to work right, though. Such a "general functionality summary" to kick off any top-of-module description doesn't strike me as likely to be perceived as bad style by many. So, even if you didn't control that module text, it's unlikely a request to change the text to something like that would be received badly. { And, of course, if you don't want to cede control of your help message to a 3rd party you can just set doc="whatever". }

kaushalmodi commented 5 years ago

I just got a chance to get back to my computer.

I quickly tried out the update, and clUseMultiPerlish looks great!

I also added the extra uri and author (and even version) info to it as suggested from your snippet. Note the I cannot put custom keywords like uri in the .nimble .. nimble doesn't like it. I get error when doing nimble install if the .nimble has any custom keywords.


For your code, you'll probably just tune your top of module comment text to work right, though.

I am already doing that, so not an issue :)

You have put in a whole lot of work from my feature request, and I'm really grateful for this. Thanks!

kaushalmodi commented 5 years ago

I also tried out docFromModuleOf as:

  dispatchMulti(
    [ "multi", doc = docFromModuleOf(<MODULENAME>), usage = topLvlUse ],

..

May I suggest updating the readme to clarify that docFromModuleOf take identifier input where the identifier can be the module name (i.e. for a package named foo.nim, the identifer would be foo), or a an exported proc identifier in that module (e.g. foo.exportedProc1). I see that you have the example of using MODULE.PROC in your tests.

c-blake commented 5 years ago

Glad you like clUseMultiPerlish. docFromModuleOf probably works with any identifier - type name, proc name, template, module, iterator, etc. I think it's just any "symbol" defined in the module you want to select, module-qualified or unqualified. I try to suggest that in the release notes and the doc comment for docFromModuleOf by saying mySym/mySymbol. I assume you meant RELEASE_NOTES.md not README.md. I'll try to make it more explicit. That a module name works probably relates to one of my issues over at the Nim compiler: https://github.com/nim-lang/Nim/issues/11052 where Araq told us that modules contain themselves intentionally which was news to me.

c-blake commented 5 years ago

Actually, while docFromModuleOf works with various idents, it always gets the source file for the caller of dispatchMulti, not the module defining the target symbol (those are different if you are wrapping an imported symbol). So, I just dropped documentation/highlighting of that entirely until maybe someday we get a better impl and punched a release.