polettix / App-Easer

Simplify writing (hierarchical) CLI applications in Perl
https://github.polettix.it/App-Easer
Other
9 stars 0 forks source link

RFE: improve documentation and user interface for options with transmit=>1 #13

Closed djerius closed 3 days ago

djerius commented 1 year ago

Hi,

The ability for a sub-command to access a parent's option via the transmit attribute is very useful, but it presents some challenges for the sub-command's documentation and user interface.

Documentation:

help on the sub-command does not list the parent options which are applicable. Depending upon the complexity of the program, the user may be so focused on the sub-command that they may not pay attention to the documentation for the additional options available from the parent.

User Interface:

It may be awkward for the user to recall that some options need to go before the sub-command, and some after, e.g. in the following, --verbose and --outdir are the parent option, --cachdir the sub-command option, and for a user, the illegal invocation for sub-command deroll-asol

  cxc-deroll deroll-asol --verbose 3 --outdir <dir> --cachedir  <cache>

may be more intuitive than the legal:

  cxc-deroll --verbose 3 --outdir <dir> deroll-asol --cachedir  <cache>

One approach to resolving these issues might be to explicitly add the parent options to the subcommands, although this might (would?) impact the options values collection +Parent specification in some fashion.

polettix commented 1 year ago

Hi! Thanks for providing lots of feedback.

I'm not sure about how you're using the transmit option to be honest. The transmission of an option to a child command is "opt in" by the sub command itself; declaring transmit => 1 in the parent only marks the option as transmittable, it's then up to each individual child to actually include it or not.

The main reason behind this design is that the top command might act as a sort of "catchall" for providing multiple services below.

Think about having many subcommands dealing with parts of a user profile in multiple subsystems, and having one single command "to rule them all", e.g. to query all sub-systems at once and provide a comprehensive view, e.g. a SQL RDBMS and an LDAP directory. For this reason, it makes sense to have the connectivity configuration for both the RDBMS and the LDAP at the top level, but specialized sub-commands might want to only inherit parent options that actually make sense (e.g. the RDBMS configuration for RDBMS-related sub-commands, and the same for LDAP).

Consider the following application example:

#!/usr/bin/env perl
use v5.24;
use warnings;
use English qw< -no_match_vars >;
use experimental 'signatures';

use App::Easer::V2 'run';

my $app = {
   aliases => [ 'main' ],
   options => [
      {
         getopt => 'transmittable',
         help => 'option available in parent, transmit => 1',
         transmit => 1,
      },
      {
         getopt => 'parent-only',
         help => 'option available in parent only',
         transmit => 0,
      },
   ],
   children => [
      {
         aliases => [ 'foo' ],
         options => [ '+parent',
            {
               getopt => 'child-only',
               help => 'option available in child only',
            },
         ],
         execute => sub ($self) {
            say "foo says: ",
               $self->config('transmittable') ? 'transmit' : 'no way';
         },
      },
   ],
};

exit run($app, $0, @ARGV);

Here, option transmittable is marked as available for propagation; in the child command foo we're providing the +parent moniker to inherit everything from the parent, so that we then have:

$ perl prova.pl help foo
no concise help yet

Can be called as: foo

Options:
  transmittable: option available in parent, transmit => 1
                 command-line: boolean
                               --transmittable

     child-only: option available in child only
                 command-line: boolean
                               --child-only

No sub-commands

As you see, the help for the option defined in the parent and inherited in the child is indeed shown in the child's help.

The rationale is also to support the other behaviour you were talking about: with this setup, it's possible to pass transmittable both in the parent and in the child, so that the end user can put it wherever it finds it better. This requires some attention (especially with short options and default values) but it's anyway under the programmer's control:

$ perl prova.pl foo
foo says: no way

$ perl prova.pl --transmittable foo
foo says: transmit

$ perl prova.pl foo --transmittable
foo says: transmit

Last, while +parent blindly gets everything from the parent, it's possible to just provide names of the single options that you want to be duplicated in the child; in this case, you might use transmittable instead of +parent, because transmittable is the name of the option you want to inherit. This helps with the "top catchall, bottom specialized" example I was writing about before.

In hindsight, I acknowledge that there might be some confusion between using +Parent in sources, which just inherits values from parent options, as opposed to +parent in options, which instead inherits the possibility to fiddle with those values directly from the child's command line. Alas... they both make sense, each in its own context :grin:

Let me know if I totally missed your point, of course.

djerius commented 1 year ago

Hi, thanks for the detailed reply, and my apologies for the long delay in responding.

You understood my point exactly. I reviewed the documentation to understand why I missed this.

Passing of '+parent' in the options array is mentioned in passing in the documentation for the resolve_options method,

Expand a $single_specification into one or more options. Basic optiosn specifications are hash references, but they might be strings like "+parent" (for inheriting all that is transmitted from a parent command) or regular expressions to bulk import options based on their names.

but isn't mentioned in the main OPTIONS section. (Also, outside of the above description, the ability to use regular expressions is only mentioned in passing in the description of the transmit_exactattribute in OPTIONS). As most of the methods in that section of the documentation aren't of interest to me as an end user, I find that I simply skip over that part to get to the OPTIONS section.

As for my understanding of transmit, yes that was faulty. I understood it to mean that the value of the option was transmitted to the child, not that its specification was essentially duplicated in the child.

My experience with other options modules biased my understanding of what "transmit" meant. Because the default value for sources includes Parent, when I examined the configuration seen by the child it included the values from the parent, so I concluded that transmit was consistent with the paradigm I'd become acculturated to. I didn't check if the child saw the parent's option values without transmit set, as that possibility didn't fit my mental model.

Based on your explanation above, the language in OPTIONS makes much more sense. However without that explanation, I think that section is ambiguous. For example, what does it mean to "inherit" an option, to "expose" an option?

Because App::Easer distinguishes between the inheritance of value versus inheritance of specification, and the parameters which control that behavior are in three separate places (via sources, the transmit attribute in the parent, and the passing of strings or regexps in options in the echild), it's hard to get a clear picture of the underlying model.

Your explanation above is quite clear; adding that to the documentation would go a long way to clearing things up.

Another thing that might help would be to relegate the list of supplied methods to a separate INTERNALS or ADVANCED USE section at the end of the documentation , and move the information of general use (e.g. +parent) out of it. I find myself simply paging through all of that to get to the OPTIONS section; there's too much detail about things that I will not use.

Thanks again!

polettix commented 2 weeks ago

I would definitely appreciate some help in restructuring the docs, possibly creating a separate one as a tutorial with most of the things of interest, while leaving other detailed things for the reference docs.

I've also introduced a different way of specifying sources for gathering options, which I'm planning to use. There is already documentation about it in the hsources branch.

polettix commented 2 weeks ago

Fixed the immediate problem in d5cab9f.

polettix commented 6 days ago

Version 2.008 (soon to be released) should address all concerns, hopefully.

There's also an attempt at a tutorial covering basics and most importantly new functionalities, see https://github.com/polettix/App-Easer/blob/hsources/lib/App/Easer/Tutorial/V2_008.pod (updated) and its metacpan rendition at https://metacpan.org/release/POLETTIX/App-Easer-2.007004/view/lib/App/Easer/Tutorial/V2_008.pod#Pass-6:-final-in-sources (slightly obsolete due to minor fixes in the meantime).

polettix commented 3 days ago

Release v2.008 is out