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

Better SYNOPSIS section #42

Closed
edwintorok opened this issue Jan 7, 2016 · 25 comments
Closed

Better SYNOPSIS section #42

edwintorok opened this issue Jan 7, 2016 · 25 comments

Comments

@edwintorok
Copy link

I'd like to have a Help (Compact, None) that prints output similar to running git, darcs, or opam without any arguments, i.e. a list of subcommands and a one-line description for each, enumerating all optional flags in the usage line and a message on how to get the full help.

This can be useful to get a quick overview if you have a command with lots of subcommands or lots of flag (for example if you remember what a command/flag does but not what its name was); and it'd also look more familiar to what usual Unix commands do on --help.

I wouldn't mind if I'd have to do this formatting in my application if Cmdliner provided me with a way to access the flag/subcommand names and their documentation from a Term.t, so I'm opening this bug for discussion.

@dbuenzli
Copy link
Owner

dbuenzli commented Jan 7, 2016

My position on this is here and here.

@edwintorok
Copy link
Author

Fair enough. Would it be possible for the SYNOPSIS in the manpage to list all optional flag instead of a generic [OPTIONS] though? That'd solve part of my problem with getting a compact output, and I could retitle this bug to be just about that.

@dbuenzli
Copy link
Owner

dbuenzli commented Jan 7, 2016

The problem is that it is a little bit difficult to control from a layout point of view, consider e.g. only the common options a subcommand like opam has. We could add new arguments to Arg.info to control this but I'm a little bit reluctant to add more cruft to the API.

Did you consider defining the SYNOPSIS section yourself ? See the third paragraph here.

@edwintorok
Copy link
Author

Ok that could work if I could somehow get access to or $(substitute) the subcommands' auto-generated SYNOPSIS so I can include it in the main command's SYNOPSIS.
See man darcs or man duplicity to see what I'm refering to: main manpage's SYNOPSIS lists each subcommands' one per line. There are less layout problems with enumerating just the subcommands, than with enumerating all options. What do you think?

@dbuenzli
Copy link
Owner

dbuenzli commented Jan 7, 2016

In fact it is true that most man pages out there do list quite a few of their options in their synopsis and that cmdliner's man page [OPTION]... is a little bit dull. Do you think I should simply dump all the options out there ? It seems e.g. that git man pages do that, even both short and long options for the same option.

What you propose may be feasible but I don't have these things in my head I'll see this once I get to work on it again.

@edwintorok
Copy link
Author

I think at least the subcommands should be listed together somewhere (perhaps SYNOPSIS).
For dumping the arguments there is a POSIX convention although it doesn't cover long arguments.

Actually cmdliner is not alone in using [OPTION]...: gengetopt for C does that too, along with the compact output on --help (however it doesn't generate a manpage).

Take your time to think about it and we can pick up the discussion when you're on cmdliner again. What may look like the right layout for one application may not work so well for another.

@dbuenzli dbuenzli changed the title support a compact --help output or custom formatting of help message Better SYNOPSIS section Feb 1, 2017
dbuenzli added a commit that referenced this issue Feb 2, 2017
@dbuenzli
Copy link
Owner

dbuenzli commented Feb 3, 2017

I think at least the subcommands should be listed together somewhere (perhaps SYNOPSIS).
For dumping the arguments there is a POSIX convention although it doesn't cover long arguments.

I'm still a little bit unsure what to do here.

Programs tend to have dozens of commands (e.g. man git will not list those in the SYNOPSIS) and/or cli flags these days and I'm a bit wary that automatically typesetting these will lead to unreadable synopses

Personally I don't find the git man page synopses with all the options listed readable and I tend to actually skip that info and/or use my pager search to reach to an option or the OPTIONS section.

Maybe we should leave the task of making usable man synopses to humans.

Note that a side effect of 618a3d3 means more precise synopses for positional arguments.

@edwintorok
Copy link
Author

edwintorok commented Feb 3, 2017

Would it be possible to provide a function that folds over the components of a 'a Term.t? That might allow each user of cmdliner to decide how best to format the synposis for their application, and how much detail to show. (perhaps with some predefined combinators for various level of detail: abbreviated, subcommands, positional args, optional long args, optional short args, etc.).

Or maybe this should be decided at Term.info and Arg.info time, by some more optional arguments?

@dbuenzli
Copy link
Owner

dbuenzli commented Feb 3, 2017

(perhaps with some predefined combinators for various level of detail: abbreviated, subcommands, positional args, optional long args, optional short args, etc.).

As mentioned in my position linked earlier I'm not very fond of configurability. Because it's not in favour of the end-user for which uniformity is more important as it allows her to derive consistent patterns and form habits.

However providing an API to be able to extract data from terms is certainly something that is needed if only to solve issue #1.

But then for typesetting your manpage the ~man argument of term information should have been 'a Term.t -> block list; so many things seem wrong in the cmdliner API.

@edwintorok
Copy link
Author

My concern with just specifying the synposis manually as a string is that it might get out of date (command-line flags could get removed/added, etc.). It is hard to come up with a single rule that would work for all applications' synopsis, so how about cmdliner would only verify the user supplied synposis, but not automatically generate it. For example if user writes this in synopsis:

grep [OPTIONS] [-e PATTERN | -f FILE] [FILE...]

Then cmdliner would check that -e is a valid option that takes one parameter and its docv matches PATTERN, similarly for -f. For [FILE...] it would check that 0 positional arguments are valid, and that something like pos_all was used ,etc.

Then the user would be free to write synposis like the one for rsync, assuming cmdliner is able to find the proper place to start parsing (right after the name of the application, in this case rsync):

       Access via remote shell:
         Pull: rsync [OPTION...] [USER@]HOST:SRC... [DEST]
         Push: rsync [OPTION...] SRC... [USER@]HOST:DEST

A similar mechanism would be useful for writing an example section (I often find it much easier to learn a command if it has good examples of its main use-cases, especially if it has a lot of flags).

This is by no means easy to implement though, what do you think?

@dbuenzli
Copy link
Owner

dbuenzli commented Feb 3, 2017

This is by no means easy to implement though, what do you think?

If you take the idea that we can extract info for terms and that you write the man page in a closure then we can reverse the idea. You'd have:

let man t = 
  let eopt_syn = Term.opt_synopsis "e" t in 
  let eopt = Term.opt "e" t in 
  [ `S Manpage.s_synopsis; 
     `P (strf "$(tname) %s" eopt_syn); 
     `S Manpage.s_examples;
     `P "To search for pattern mysearch issue:";
     `P ("$(mname) %s mysearchpattern" eopt); ]

and these Term.{opt,opt_synopsis} would raise Invalid_argument if the opt is absent (i.e. if the cli interface changed). By generating the manpage in your test suite you'd catch whenever documentation gets out of sync.

But these sprintfs may not be very convenient for doc authors.

@dbuenzli
Copy link
Owner

dbuenzli commented Feb 3, 2017

But these sprintfs may not be very convenient for doc authors.

A but this we can handle by defining a substitution environment that has to be returned by the fun.

let man t = 
  let ctx = Manvars.(opt_synopsis empty "esyn" ~opt:"e" t) in 
  let ctx = Manvars.(opt ctx "eopt" ~opt:"e" t) in
  ctx,
  [ `S Manpage.s_synopsis; 
    `P "$(tname) $(esyn)"; 
    `S Manpage.s_examples;
    `P "To search for pattern mysearch issue:";
    `P "$(mname) $(eopt) mysearchpattern"; ]

@dbuenzli
Copy link
Owner

dbuenzli commented Feb 3, 2017

I'm a little bit tempted by this man function because for #75 and #67 I wanted to add new optional arguments to terms. But then I could also simply provide good formattting combinators for these sections that you invoke in the man function. #74 is a little bit different as it needs to integrate with the sort of env vars that is done from the argument terms.

@edwintorok
Copy link
Author

I like the idea of substitution, one needs to use it anyway for things like tname or mname when writing a manpage. What if you'd predefine substitutions for all arguments to reduce the amount of boilerplate one has to write, e.g.:

let man t = 
  [ `S Manpage.s_synopsis; 
    `P "$(tname) $(OPTIONS)"; (* OPTIONS predefined to part of the default synopsis *)
    `P "$(tname) $([-e])"; (* here '[-e]' was predefined to be the same as eopt_syn above, i.e. [-e PATTERN]*)
    `S Manpage.s_examples;
    `P "To search for pattern mysearch issue:";
    example("$(mname) $(-e) mysearchpattern"; ]) (* here '-e' was predefined to be '-e' if it exists *)

example could call Term.eval ~argv, where argv would be the string after $(mname) split into an array, and check it doesn't return Error. One can still write examples that are not validated by using `P directly.

@dbuenzli
Copy link
Owner

dbuenzli commented Feb 3, 2017

What if you'd predefine substitutions for all arguments to reduce the amount of boilerplate one has to write

A yes of course, I could do that, you then don't even need the man function.

@dbuenzli
Copy link
Owner

dbuenzli commented Feb 5, 2017

So here's a tentative. The first goal is to make sure that what you refer to in the documentation actually exists in the program (hence the [Invalid_argument] raises). The second goal is that this should be sufficient to recreate what cmdliner does for you automatically without pain if you want full control (in fact it would be good if cmdliner's would use this internally)

The idea is to be able to project the term, argument and environment variable information OCaml values the client defines in the manpages and be able to access their components and derived information via an optional selector following a dot.

A few things that are not covered/problematic:

  • Need to be able to escape dots.
  • No way to access the information value of position arguments, I didn't give it a strong thought yet.

Commands

  • $(tname) current term information (command).
  • $(mname) main term information (program).
  • $(cmd), information of term named cmd raises Invalid_argument if no term information cmd exists in the evaluation.

The selector are:

  • $(cmd), default selector, term name rendered in bold
  • $(cmd.opts), optional arguments synopsis as generated by cmdliner (if any).
  • $(cmd.args), positional argument synopsis as generated by cmdliner (if any).
  • $(cmd.doc), the markedup term's doc string.
  • $(cmd.version), the version string.
  • $(cmd.--opt), information of option --opt of cmd, raises Invalid_argument if it doesn't exist.
  • $(cmd.envs.var), information of environment variable named var, raises Invalid_argument if it doesn't exist.

Optional arguments

  • $(cmd.--opt), default selector, option rendered in bold.
  • $(cmd.--opt.doc), the marked up option's doc string.
  • $(cmd.--opt.docv), the marked up option's value meta variable.
  • $(cmd.--opt.syn), option as would be rendered in a synopsis (e.g. mention the option's value --opt=VAL)
  • $(cmd.-opt.label), option item's label as rendered by cmdliner in the option's documentation item.
  • $(cmd.--opt.env), the argument's environment variable, raises Invalid_argument if it doesn't exist.

Environment variables

  • .var, the environment's variable name, rendered in bold.
  • .doc, the marked up option's doc string.

@edwintorok
Copy link
Author

To see if I understood this right, is this translation of your earlier example code correct?

  [ `S Manpage.s_synopsis; 
    `P "$(tname) $(tname.-e.syn)"; 
    `S Manpage.s_examples;
    `P "To search for pattern mysearch issue:";
    `P "$(mname) $(mname.-e) $(b,mysearchpattern)"; ]

(or maybe $(mname) $(subcmd) $(subcmd.-e) $(b,mysearchpattern) if I am dealing with a command that has a subcmd subcommand?)

I think it is good that the substitutions are fairly comprehensive, though it could get overwhelming trying to pick the right one when actually writing the manpage. Have you considered adding a debug mode?
(e.g. something like Term.debug : ?subst:(var:string -> value:string -> unit)-> ?paragraph:(orig:string -> substituted:string -> unit) -> 'a t info -> unit that can be used to print the map of substitutions and the results).

@dbuenzli
Copy link
Owner

dbuenzli commented Feb 5, 2017

is this translation of your earlier example code correct?

Yes.

Have you considered adding a debug mode?

Not really. I'm not sure that's really needed the debug mode is to actually ask for the manpage and see the result, errors will throw. As for what you are allowed to write it's only a matter of looking at your term definitions.

@hcarty
Copy link

hcarty commented Feb 5, 2017

As for what you are allowed to write it's only a matter of looking at your term definitions.

What do you suggest in the case of terms provided by upstream libraries? Would you recommend those libraries libraries provide terms with matching man fragments?

@dbuenzli
Copy link
Owner

dbuenzli commented Feb 5, 2017

Would you recommend those libraries libraries provide terms with matching man fragments?

Anything can do. Your library can provide man fragments or simply function that defines arguments. In the latter case you should simply allow the client to specify the man page section in which the arguments should be listed see for example here.

@dbuenzli
Copy link
Owner

dbuenzli commented Mar 1, 2017

One thing that should be mentioned in the synopsis is required optional arguments (#82)

@dbuenzli
Copy link
Owner

dbuenzli commented Jan 7, 2022

I think the idea here is overkill. In the end I think manually specifying your synopses is "good enough".

One thing that I plan to improve though is to specify the synopses of commands in COMMANDS sections.

E.g. in dune --help instead of:

COMMANDS
       build
           Build the given targets, or all installable targets if none are
           given.

       cache
           Manage the shared artifacts cache
...

have:

COMMANDS
       build [OPTION]... [TARGET]...
           Build the given targets, or all installable targets if none are
           given.

       cache [OPTION]... [ACTION]
           Manage the shared artifacts cache
...

@dbuenzli dbuenzli closed this as completed Jan 7, 2022
@dbuenzli
Copy link
Owner

dbuenzli commented Feb 7, 2022

Btw @edwintorok cmdliner 1.1.0 will now show options in synopses according to the heuristic spelled out in this message dae9c17

@edwintorok
Copy link
Author

Thanks, it might also be useful to automatically list all the subcommands in the main auto-generated synopsis.
E.g. the manually specified SYNOPSIS for dune is:

       dune build [--watch]
       dune runtest [--watch]
       dune exec NAME
       dune utop [DIR]
       dune install
       dune init project NAME [PATH] [--libs=l1,l2 --ppx=p1,p2 --inline-tests]

But if I were to remove the manual SYNOPSIS and see how an autogenerated one would look like I get this instead:

SYNOPSIS
       dune COMMAND ...

Although all the subcommands are available further down, having a quick overview at the top might be useful (within reason, probably wouldn't like to put all subcommands there for something like git, for a command with a lot of subcommands a manually curated list of commonly used subcommands in the synopsis might be better).

@dbuenzli
Copy link
Owner

dbuenzli commented Feb 7, 2022

Thanks, it might also be useful to automatically list all the subcommands in the main auto-generated synopsis.

I have tried a few things along these lines but so far I was not really convinced, it made it very redundant with the COMMANDS section which is just a few lines below.

For now I'm happy with hand-crafted synopsis sections.

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

No branches or pull requests

3 participants