11.2. How to Format Help Strings

This section explains how to go about writing the help section for command-line programs in a manner that is consistent with the AFNI standard of trickery. The process is somewhat different for the different types of programs as detailed below. But first a quick recap of the trickeries.

The point of all this is to allow a fancier formatting of the help output without much effort. Existing help output can be automatically sphinxized with the command:

apsearch -asphinx_phelp PROGNAME

However, you can also add markup directives in the help string that alters the output depending on the desrired format. For examples on how to add markup into the help strings, run:

apsearch -doc_markup_sample

Creating strings with special markup for classic and sphinx display:

Function SUMA_Sphinx_String_Edit is used to take strings with
the following special markers and return them formatted in either
Sphinx or regular text. What follows is a list of special directives
that change the output string depending on the desired format and a bunch
examples to illustrate their use.

 :SPX: Hiding a SPHINX directive with minimal fanfare:
     Text between :SPX: markers does not appear in default output
     format.
        :SPX: Sphinx chunk :DEF: regular chunk :SPX:
     Use this to insert into a text string a section that is
     only displayed when Sphinx output is requested.
     It is also possible to provide an alternate section
     after the :DEF: marker between the opening and closing
     :SPX: markers. The alternate section is used when the
     requested output format is simple text.

     The example coming up next will show how we can have
     alternate output where a key press would be mentioned
     simply in the SUMA output but with a reference directive
     when SPHINX output is used:

 :LR: Replace this marker with a new line character for
      Sphinx output. Cut it out for regular output.
 :LIT: Replace this marker with '::
' to mark an upoming literal
       paragraph for sphinx. If the character before :LIT:
       is a non blank, a ':' will terminate the sentence preceding
       the literal paragraph.
       For regular output, :LIT: is cut out if it is preceded by
       a blank. Otherwise it is replaced by a ':'
       Note that the literal paragraph must be indented relative to
       the preceding one.

 :ref:`Some Label <reference_key>` Leave such a block untouched for
                              sphinx format. Replace whole thing
                              with just 'Some Label' for default format.

 :[blanks]: Cut this marker out of string for Sphinx output,
            but keep all blanks and pads with two more in regular
            output to compensate for the ':' characters.
            Also, for the Sphinx format, a newline directly preceding
            the opening ':' gets cut out.

 '\|' Escaped vertical bar are kept as such for Sphinx, but shown
       without the escape character in default output. This is
       needed to keep sphinx from considering words between vertical
       bars to be substitution references.

 :NOF: When found right after a new line, don't let function
      SUMA_Offset_Lines() insert any spaces. :NOF: is otherwise cut
      from all output

 :=ABIN: Replace with afni bin directory
 :=AFACE: Replace with afni face directory

See function SUMA_Sphinx_String_Edit_Help() for a code sample.

Strings as defined in the source code:

Example 1:
Below you will see a figure directive, but only for Sphinx format.
:SPX:

.. figure:: media/face_houstonbull.jpg
   :align: center

:SPX:And now the rest of text continues...

Example 2:
Press buton :SPX::ref:`a <LC_a>`:DEF:'a':SPX: to attenuate...

Example 2.1 (simpler version):
Press buton :ref:`a <LC_a>` to attenuate...

Example 3:
For 'Trn' choose one of::LR:
   0: No transparency.
:    :Surface is opaque.:LR:
   8: 50% transparency.
:    :Surface is in cheese cloth transparency.:LR:

Example 4:
... or if '\|T\|' is used then ...

Example 5:
A sample file would be: test.1D.col with content:LIT:
   0    0.1 0.2 1
   1    0   1   0.8
   4    1   1   1
   7    1   0   1
   14   0.7 0.3 0

 -------

Edited for display in AFNI or SUMA:

Example 1:
Below you will see a figure directive, but only for Sphinx format.
And now the rest of text continues...

Example 2:
Press buton 'a' to attenuate...

Example 2.1 (simpler version):
Press buton a  to attenuate...

Example 3:
For 'Trn' choose one of:
   0: No transparency.

   Surface is opaque.
   8: 50% transparency.

   Surface is in cheese cloth transparency.

Example 4:
... or if '|T|' is used then ...

Example 5:
A sample file would be: test.1D.col with content:
   0    0.1 0.2 1
   1    0   1   0.8
   4    1   1   1
   7    1   0   1
   14   0.7 0.3 0

 -------

Edited for SPHINX:

Example 1:
Below you will see a figure directive, but only for Sphinx format.


.. figure:: media/face_houstonbull.jpg
   :align: center

And now the rest of text continues...

Example 2:
Press buton :ref:`a <LC_a>` to attenuate...

Example 2.1 (simpler version):
Press buton :ref:`a <LC_a>` to attenuate...

Example 3:
For 'Trn' choose one of:

   0: No transparency. Surface is opaque.

   8: 50% transparency. Surface is in cheese cloth transparency.


Example 4:
... or if '\|T\|' is used then ...

Example 5:
A sample file would be: test.1D.col with content::

   0    0.1 0.2 1
   1    0   1   0.8
   4    1   1   1
   7    1   0   1
   14   0.7 0.3 0

 -------

As would be displayed by SPHINX once compiled:

Example 1: Below you will see a figure directive, but only for Sphinx format.

../../_images/face_houstonbull.jpg

And now the rest of text continues...

Example 2: Press buton a to attenuate...

Example 2.1 (simpler version): Press buton a to attenuate...

Example 3: For ‘Trn’ choose one of:

0: No transparency. Surface is opaque.

8: 50% transparency. Surface is in cheese cloth transparency.

Example 4: ... or if ‘|T|’ is used then ...

Example 5: A sample file would be: test.1D.col with content:

  0    0.1 0.2 1
  1    0   1   0.8
  4    1   1   1
  7    1   0   1
  14   0.7 0.3 0

-------

11.2.1. C-programs

  • When writing a new program

    • See program 3dToyProg.c for an example on how to format your help, and some examples for how to put special markup in the help strings.
  • To retrofit existing C programs

    • Put all help in a function of this prototype:

      int Help_Func(TFORM targ, int detail);
      
    • Replace printing functions as follows:

      fprintf( --> sphinx_fprintf(targ,
       printf( --> sphinx_printf(targ,
      
    • Replace the explanation for the -help option in Help_Func() with the string from:

      SUMA_Offset_SLines(get_help_help(),OFF);
      
      where OFF is the number of blank characters by which to offset the help string
      
    • Add CHECK_HELP(arg,Help_Func) macro in the block where you parse the command line arguments.

      Note

      All parsing decisions should be made after macro mainENTRY(). Otherwise your program won’t make proper use of things like -h_view, and -h_web, etc.

      Do not parse for -h, -help, or -HELP. That is now done by CHECK_HELP()

    • Add the following after you detect an illegal option in arg:

      suggest_best_prog_option(argv[0], *arg*);
      
  • Once you add a new program/script, or upgrade the -help output for an old one, you need to update prog_opts.c and rebuild libmri.a by running this command in src/ directory, to replace existing prog_opts.c:

    apsearch -C_prog_opt_array 3dToyProg > prog_opts.c
    touch thd_getpathprogs.c
    make libmri.a gitignore
    

11.2.2. C-GUI

The documentation for the GUI can be largely automated using existing BHelp and Hint strings. The idea is to put all documentation in one place and display it in multiple ways. The logic behind the process is as follows:

  • Keep track of each GUI element’s name and its help strings. This is done by replacing all calls to MCW_register_help() and MCW_register_hint() with a single call to: SUMA_Register_Widget_Help(). SUMA_Register_Widget_Children_Help() also replaces MCW_reghint_children() and MCW_reghelp_children().

    The prototypes of the old and new functions are:

    void MCW_register_help(Widget w , char *help);
    void MCW_register_hint(Widget w , char *hint);
    SUMA_Boolean SUMA_Register_Widget_Help(Widget w, int type, char *name, char *hint, char *help);
    

    The extra variables are type and name and are used as follows:

    type is 0 for a container widget, like frames around buttons. An example frame is the Surface Properties in SUMA. type is 1 for clickable widgets such as Trn.

    name is a string identifying the widget. The naming has a hierarchical scheme to it. For instance the name for Trn in the C-code was “SurfCont->Surface_Properties->Trn”. Because this widget is in the Surface Controller within the frame called Surface Properties. Names should be unique to get an entry into the help database (a bunch of strings really), but it is expected that there will be indentically named widgets such as when you open two surface controllers.

    When building the documentation for the whole GUI, you are expected to have an entry for each naming level. To get to “SurfCont->Surface_Properties->Trn”, you should have at the very least three calls to SUMA_Register_Widget_Help(), one for name “SurfCont” where you say a few kind words about the overall use of the Surface Controller GUI, then for “SurfCont->Surface_Properties”, and then for “SurfCont->Surface_Properties->Trn” and whatever else is in it.

    In AFNI, frames do not have labels that show up in the GUI, no “Surface Properties” for instance, but you should still name the frame, hopefully with something memorable so you can easily cross reference it when writing help.

    Now let’s consider what was needed to document a tiny part of the AFNI controller, the done button. To do so, we also need to include something for its containers.

    1. Add an entry for “AfniCont”:

      /* replace */
      MCW_register_help( vwid->top_form , AFNI_tophelp ) ;
      /* with */
      SUMA_Register_Widget_Help( vwid->top_form, 0, "AfniCont", NULL, AFNI_tophelp);
      
    2. Add an entry for “AfniCont->ProgCont”, “ProgCont” stands for “program controls”, a phrase I took from the comment in afni_widg.c:

      /* replace */
      MCW_register_hint( prog->frame , "rowcol to hold all program controls stuff" ) ;
      MCW_register_help( prog->frame , "Wish to write something here?" ) ;
      /* with */
      SUMA_Register_Widget_Help( prog->frame, 0, "AfniCont->ProgCont", "rowcol to hold all program controls stuff", "Wish to write something here?");
      
    3. Now for the button “AfniCont->ProgCont->done”:

      /* replace */
      MCW_register_hint( prog->quit_pb, "Click twice to close window");
      MCW_register_help( prog->quit_pb, AFNI_quit_help);
      /* with */
      SUMA_Register_Widget_Help(prog->quit_pb, 1, "AfniCont->ProgCont->done", "Click twice to close window", AFNI_quit_help);
      

    Note

    You can enhance the help strings with links, images, references, etc. that would get shown only for the Sphinx output. For some help on how to do this take a look at the output of apsearch -doc_markup_sample

  • Write a function to assemble all the help information for the controller in question, here “AfniCont”. You can use the function below as your template and add your widgets to the decalaration of worder[]. It goes without saying that each name in worder should have a corresponding call to SUMA_Register_Widget_Help() with an identical name.:

    char * AFNI_Help_AllMainCont (TFORM targ)
    {
       static char FuncName[]={"AFNI_Help_AllMainCont"};
       char *s = NULL, *shh=NULL, *sii=NULL;
       int k=0;
       SUMA_STRING *SS = NULL;
       char *worder[] = {
                         "AfniCont",
                         "AfniCont->ProgCont",
                         "AfniCont->ProgCont->done",
                         NULL };
       SUMA_ENTRY;
    
       SS = SUMA_StringAppend (NULL, NULL);
    
       k = 0;
       while (worder[k]) {
             s = AFNI_gsf(worder[k], targ, &sii, &shh);
             if (!shh || strstr(sii, shh)) {/* help same as hint */
                SS = SUMA_StringAppend_va(SS, "%s\n", s);
             } else {
                SS = SUMA_StringAppend_va(SS, "%s\n%s\n",
                                       s, shh?shh:"");
             }
             SUMA_ifree(sii); SUMA_ifree(shh);
          ++k;
       }
    
       SUMA_SS2S(SS, s);
    
       SUMA_RETURN(SUMA_Sphinx_String_Edit(&s, targ, 0));
    }
    
  • Automate the process of help generation, by using @gen_all script under doc/SphinxDocs/ with the -afni option. The script would launch afni and make it open whatever controllers are to be documented and issue a driver command that calls the function to generate all the help text. For the AFNI controller example above, the driver command “WRITE_CONT_SPX_HELP” will call AFNI_Help_AllMainCont() with TFORM set to SPX.

  • It is also convenient to have a function to automatically snap pictures of controllers or controller groups. This way you don’t have to manually recreate a bunch of pictures after you modify the GUI. Since snapping is to be done via plugout_drive (SNAP_CONT for this example) it is best to have the snapping in afni_driver.c. You must make sure that the controller is open and setup as you want it to be before snapping the picture or writing the help string. No help is availble until the GUI in question is created and displayed. The function that snaps a picture of the Afni controller is:

    static int AFNI_drive_snap_cont( char *cmd );
    
  • Automatically generated help files are included from manually created .rst files such as SphinxDocs/AFNI/Controllers.rst

  • Finally, to build the docs:

    cd SphinxDocs
    @gen_all -afni -html
    afni_open _build/html/index.html
    

11.2.3. C-Shell Scripts

  • Add the following line right after the #!/bin/tcsh -f line:

    @global_parse `basename $0` "$*" ; if ($status) exit 0
    
  • Next you will need to add blocks, typically at the end of your scripts that will contain:

    • The parsing of the special help options in HELP

    • The actual help string, conveniently tucked in HRAW

    • The use of the END block is to keep the script from getting to these sections without an explicit goto.

    • Here is an example for what you would add at the end of the script:

      goto END
      
      HELP:
           if ("$HelpOpt" == "-h_raw") then
         goto HRAW
      else if ("$HelpOpt" == "-h") then
         `basename $0` -h_raw | apsearch -hdoc_2_txt `basename $0` -
      else if ("$HelpOpt" == "-help") then
         `basename $0` -h_raw | apsearch -hdoc_2_txt `basename $0` -
      else if ("$HelpOpt" == "-h_txt") then
         `basename $0` -h_raw | apsearch -hdoc_2_txt `basename $0` -
      else if ("$HelpOpt" == "-h_spx") then
         `basename $0` -h_raw | apsearch -hdoc_2_spx `basename $0` -
      else if ("$HelpOpt" == "-h_aspx") then
         `basename $0` -h_raw | apsearch -hdoc_2_aspx `basename $0` -
      endif
      goto END
      
      HRAW:
      cat << EOF
      A multi line string that contains all your help, something like:
      Usage: @DriveSuma
      
      A script to demonstrate how to drive suma from the command line.
      The script pops messages explaining what will happen in the next command
      
      You can also read the script, focusing on the DriveSuma commands
      to understand what is going on.
      
      See also DriveSuma -help and @DO.examples
      
      Questions or comments are welcome on AFNI's message board:
      echo ' https://afni.nimh.nih.gov/afni/community/board/list.php?f=1 '
      :SPX:
      
         .. note::
      
            This is an example for how you can put special sphinx directives to improve your help.
      
      :SPX:
      
      `@global_parse -gopts_help_formats`
      
      EOF
      
      
      goto END
      
      END:
      
  • To get to these sections from where you are parsing the command line arguments, you can add:

    set HelpOpt = ''
    echo "$YourArg" | \grep -w -E  '\-h_txt|\-h_spx|\-h_aspx|\-h_raw|\-help|\-h' >& /dev/null
    if ($status == 0) then
       set HelpOpt = "$YourArg"
       goto HELP
    endif
    
  • And finally, update the list of program options in prog_opts.c

11.2.4. R programs

  • Add formatting argument targ to your help function as in:

    help.RprogDemo.opts <- function (params, alpha = TRUE,
                                     itspace='   ', adieu=FALSE, targ ='TXT')
    
  • Add a file argument to the command that cats the help:

    cat(intro, ex1, ss, sep='\n',
        file=help.cat.file.AFNI(ExecName,targ));
    
  • Augment the help options to something like:

    '-help' = apl(n=0, h = '-help: this help message, in simple text.\n'),
    '-h_raw' = apl(n=0, h = '-h_raw: this help message, as is in the code.\n'),
    '-h_txt' = apl(n=0, h = '-h_txt: this help message, in simple text\n'),
    '-h_spx' = apl(n=0, h = '-h_spx: this help message, in sphinx format\n'),
    '-h_aspx' = apl(n=0, h = '-h_aspx: like -h_spx, with autolabeling\n'),
    
  • And reflect that in the parsing section:

    help = help.RprogDemo.opts(params, adieu=TRUE),
    h_raw = help.RprogDemo.opts(params, adieu=TRUE, targ='RAW'),
    h_spx = help.RprogDemo.opts(params, adieu=TRUE, targ='SPX'),
    h_aspx = help.RprogDemo.opts(params, adieu=TRUE, targ='ASPX'),
    h_txt = help.RprogDemo.opts(params, adieu=TRUE, targ='TXT'),
    

Note

For an example, take a look at program 3dRprogDemo

11.2.5. Python programs

TBW

11.2.6. Building one help file

To compile the rst file into html, you can use the convenience script @test_html_build which is in the doc/SphinxDocs/ directory. Here is a sample command to test the build and view the help output of 3dToyProg:

3dToyProg -h_aspx | @test_html_build -

or if your program does not yet support the new help options:

apsearch -asphinx_phelp 3dToyProg | @test_html_build -