The SamCommand Tools

Overview:

The SamCommand parsing tools make it easy develop self-documenting shell and python API interfaces in a uniform and consistent fashion. The user sees a consistent front-end to commands, with clear instructions on legal syntax when an illegal command is entered (or when help is requested). The product maintainer needs to define the legal syntax of the command, write an implementation method, and [optionally] an appropriate dispatch handler, but does not need to parse the command line elements on a case-by-case basis.

The SamCommand tools define four objects of interest:

Command:
Defines the elements of a legal command, including:

Contains methods which may be used or overridden for further control over commands, the most useful of which are:

CommandInterface:
A command interface is a special deriviative of a command, which can be called from the shell (via its cliDispatch method) or directly from python using kwargs (keyword-value pairs corresponding to the command line options, flags, and argument list). The __call__ method of the CommandInterface object parses the kwargs, builds the corresponding parsed argDict/argList, and passes control to the same implementation that would have been called from the shell. The advantage is that, when called from python, the same command line argument parsing and type-checking is performed, so that the actual implementation can be assured that no matter how it was called, the arguments are of the proper type and number.

CommandInterfaces have the following methods that may be overridden:

  • cliDispatch: parse the shell arguments, invoke the implementation, handle the results
  • cliPrepareOpts: perform any initial preparation of shell arguments before calling the implementation
  • cliProcessResults: perform any post-processing of the results for the shell interface
  • implementation: the actual implementation

CommandSuite:
A command suite "learns" a list of legal command definitions (and/or implementations). The commands are callable by the shell; the command suite parses the command line arguments according to the laws of this list. If the command is not legal, consistently formatted help text is available on the syntax and requirements of each command. If the command is legal, returns a legal command object to the caller.

Command Suites always include the verb form _generate _documentation, which can be used to write a consistent set of web pages (or text files) with the documentation from any command suite. Methods which are useful in this context are:

  • setBgColor: set the overall background color for all documentation generated
  • setSeeAlso: set additional seeAlso references for the entire command suite
  • setShellCommand: set the shellCommand for all commands in the suite
  • setSummaryFile: set the name of the summary index for all CLI usage docs
  • setIndexFile: set the name of the index file for all generated docs
  • setApiImport: set the apiImport string for all commands in the suite
  • setApiObjectName: set the apiObjectName for all commands in the suite
  • setWriteApi: flag to write API usage documents as well as CLI usage docs
  • setApiSummaryFile: set the name of the summary file for all API usage docs

CommandInterfaceSuite:
A command interface suite is similar to a command suite, but each of the commands is also callable directly from the python interface with the same argument parsing and type-checking. The sam_user_pyapi (aka sam), sam_admin_pyapi (aka samAdmin) and sam_mis_pyapi (aka samMis) (among other) utilities are all encoded as instances of a CommandInterfaceSuite.

CommandInterfaceSuites have the following methods of interest:

  • buildImplementationModuleList(): build a list of python modules containing the definitions of the CommandInterface objects which should be included in this suite
  • getCommandSuite(): return the corresponding commandSuite
  • dispatchCommand(args): command line dispatcher

BlessedCommandInterfaceSuite:
As of the v6_8 series of sam_common_pylib, it is now possible to create "blessed" command interface suites. The purpose is to improve performance by deferring instantiation of the command interface objects in a suite until/unless they are called.

To bless a command suite, the steps are:

  1. Create the command suite in the usual way (see sam_user_pyapi/src directories for examples).
  2. At build time, run the ${SAM_COMMON_PYLIB_DIR}/SamCommand/BlessedCommandInterfaceSuiteCodeGenerator.py script over the suite to generate the blessed suite. For example, the "sam" command suite is generated using:
               python $SAM_COMMON_PYLIB_DIR/SamCommand/BlessedCommandInterfaceSuiteCodeGenerator.py \
                --suiteName=sam \
                --initString='from SamUserApi import getSam; sam = getSam()' \
                --output=$SAM_USER_PYAPI_DIR/bin/sam.py
              
    The resulting sam.py script contains the "blessed" command definition for each of the interfaces in the suite (meaning, we no longer need to check the input as we build the list of interfaces, we accept this definition with no validation). Each of the blessed command definitions point to a "placeholder" class, which will instantiate the true interface class only when called.
The sam, sam_admin, and sam_mis command suites are all built using this technique.


What the User Sees:

The following examples are taken from the sam suite of utilities.

#
# getting help for all available commands:
#
$ sam help


Usage: sam <command> <requiredOptions> <otherOptions> <args>

Where <command> is one of the following, sorted by category:

  Category Miscellaneous:
       _generate _documentation
  Category samConsumer:
       decrement job count
       end process OR end consumer process OR stop process
       establish consumer OR create consumer
       establish persistent process OR establish persistent consumer process OR create persistent process
       establish process OR establish consumer process OR create process
       get next file
       increment job count
       release file
  Category samDataset:
       create dataset OR take snapshot
       create definition OR create dataset definition
       translate constraints OR list files
       translate dimensions
  Category samDbInfo:
       get dbserver connection info
       get dbserver info
       get dbserver logfile data
  Category samDumpster:
       dump consumer
       dump dbserver
       dump file
       dump fss OR dump fsserver OR dump file storage server
       dump project
       dump station
  Category samLocate:
       add location
       erase file location OR erase location
       locate
  Category samMetadata:
       declare file
       get metadata
       undeclare file OR undeclare metadata
       update file crc
       update file size
       verify metadata OR verify description file
  Category samProject:
       declare project to station
       reserve project OR create project
       restart project
       start project
       start reserved project
       start unreserved project
       stop project
  Category samSubmit:
       run job
       run project
       submit OR submit job
  Category samUtility:
       describe metadata attributes
       describe metadata requirements
       get dimension info
       get metadata attribute descriptor
       get registered dimension categories
       get registered filetypes OR get registered file types
       get registered parameter keywords
       get registered station admins
       get registered station group admins
       get registered station groups
       get registered values

#
# getting help for limited number of matching commands:
#
$ sam help get

Usage: sam <command> <requiredOptions> <otherOptions> <args>
 
Where <command> is one of the following, sorted by category:
 
  Category samConsumer:
       get next file
  Category samDbInfo:
       get dbserver connection info
       get dbserver info
       get dbserver logfile data
  Category samMetadata:
       get metadata
  Category samUtility:
       get dimension info
       get metadata attribute descriptor
       get registered dimension categories
       get registered filetypes OR get registered file types
       get registered parameter keywords
       get registered station admins
       get registered station group admins
       get registered station groups
       get registered values

#
# getting help for a specific command:
#
$ sam help get next file

Command Description:
 
Receive the next file to be analyzed.
 
 
Usage:
       sam get next file <--requiredOptions> [--options] [-flags]
 
Where:
 
  --requiredOptions:
                   --processId=<value> # Consumer process ID for the process receiving the file
                     --project=<value> # name of the project under which this process is running
                     --station=<value> # name of the station where the project is running
  --options:
                --erralertflag=<value> # set to "verbose" for more information in output message
                      --rtfile=<value> # routing file into which results will be written (default: stdout)
                     --timeout=<value> # number of seconds to wait before timing out
  --flags:
                                    -d
                                    -v
Synonymous Options:
         --consumer-process-id=<value> # synonym for --processId=<value>
           --consumerProcessId=<value> # synonym for --processId=<value>
                        --cpid=<value> # synonym for --processId=<value>
                  --process-id=<value> # synonym for --processId=<value>
 
Restricted Types:
        --erralertflag=( must be string )
        --processId=( must be long )
        --project=( must be string )
        --rtfile=( must be string )
        --station=( must be string )
        --timeout=( must be long )
 
You may omit "--project=<value>" if the $SAM_PROJECT environmental variable is set.
You may omit "--station=<value>" if the $SAM_STATION environmental variable is set.

Command Suite:

A CommandSuite is a suite of related commands which have been defined, and which knows how to parse user-entered command lines and dispatch appropriately.

Usage: In it's bare form, the usage of the CommandSuite is:

    from SamCommand.CommandSuite import CommandSuite
    suite = CommandSuite( [<list of commands>] )
    suite.learn( <list of commands> )

    theCommand = suite.parseArgs(sys.argv)
    theCommand.dispatch()

However, there are more "precautions" that your code should take when actually using the CommandSuite, and there are more features available to you when using the suite. These are illustrated in the example below.

Example:

    #!/usr/bin/env python
    import sys
    from SamCommand.CommandSuite import CommandSuite
    from SamCommand.CommandDefinition import Command
    from SamCommand.CommandInterface import mainDispatch

    c1 = Command( <command definition 1> )
    c2 = Command( <command definition 2> )
    ..

    knownCommands = ( c1, c2 )
    aSuite = CommandSuite( knownCommands )

    # web documentation can be automatically generated; tailor
    # its appearance for our needs:
    aSuite.setBgColor("#FFFF00")                     # standard SAM yellow
    aSuite.setSeeAlso(["http://d0db.fnal.gov/",      # list of other useful pages
                       "http://d0db.fnal.gov/sam"])

    ..
    c3 = Command( <command definition 3> )
    c4 = Command( <command definition 4> )
    aSuite.learn( (c3, c4) )

    exitStatus = mainDispatch(aSuite, sys.argv)
    sys.exit(exitStatus)

Command Interface:

A command interface is a special type of derived Command which, through the magic of python, will make sure that when the native python implementation is called, the arguments will be parsed as if they had been passed through the shell.

In its simplest form, a command interface looks like:

#!/usr/bin/env python

from SamCommand.CommandInterface import CommandInterface

class anInterface(CommandInterface):
    def __init__(self):
        CommandInterface.__init__(
            self,
            commandParameters = {
            'verb' : ['do something here'],
            'requiredOptions' : [ 'opt1=', 'opt2=' ],
            'allowedOptions'  : [ 'opt3=' ]
            }
            )

    def cliProcessResults(self, results):
        # for shell interface: stringify and print results
        print( str(results) )

    def implementation(self, argDict, argList):
        # actual implementation is here;
        # argDict is a case-insensitive, dash-insensitive,
        # underscore-insensitive dictionary containing
        # the keyword/value pairs from either sys.argv,
        # or the user's call to anInterface(a1=v1, a2=v2, ...).

        opt1 = argDict.get('opt1')
        opt2 = argDict.get('opt2')
        opt3 = argDict.get('opt3', 'no argument entered for opt3')

        # do something here with the opts
        result = f(opt1, opt2, opt3)
        
        # return the final object result
        return result


# call from python:
myInterface = anInterface
results = myInterface(opt1='this is opt1', opt2='this is opt2')

Command Interface Suite:

A command interface suite is a collection of python-callable command interfaces. In its simplest form, a command interface suite looks like:


# define the command interfaces that will be part of this suite:
from SamCommand.CommandInterface import CommandInterface

class doThingOne(CommandInterface):
    def __init__(self):
        CommandInterface.__init__(
            self,
            commandParameters = {
            'verb' : ['do thing one'],
            'requiredOptions' : ['doWhat=']
            })

    def implementation(self, argDict, argList):
        return "doing thing one: argDict = %s, argList = %s" % (str(argDict), str(argList))

class doThingTwo(CommandInterface):
    def __init__(self):
        CommandInterface.__init__(
            self,
            commandParameters = {
            'verb' : ['do thing two'],
            'requiredOptions' : ['doWhat=']
            })

    def implementation(self, argDict, argList):
        return "doing thing two: argDict = %s, argList = %s" % (str(argDict), str(argList))

# define the suite itself:
from SamCommand.CommandInterfaceSuite import CommandInterfaceSuite
class myInterfaceSuite(CommandInterfaceSuite):
    def buildImplementationModuleList(self):
        return [ self.__module__ ]

def main(args):
    return myInterfaceSuite().dispatchCommand(args)

if __name__ == "__main__":
    import sys
    sys.exit( main(sys.argv) )
The command interfaces are now available through an instance of the CommandInterfaceSuite object:
    # get a commandInterfaceSuite:
    suite = myInterfaceSuite()

    # do something with it
    suite.doThingOne(doWhat='do this thing here')
    suite.doThingTwo(doWhat='do something else')


Command Definition Syntax:

The syntax of the code to define a command is shown below. Note that only the verb is required; everything else is optional, with suitable defaults. When defining a command element that is a "list", you may use either "lists" or "tuples". The example here shows tuples; examples elsewhere in this document generally use "lists".

  #!/usr/bin/env python
  from SamCommand.CommandDefinition import Command

  aCommandDefinition = { 'verb' : ( 'main verb form', 'legal synonyms', ...),
                         'requiredOptions' : ( 'requiredOpt1', 'requiredOpt2', ...), 
                         'allowedOptions' : ( 'allowedOpt1', 'allowedOpt2', ...), 
                         'allowedFlags' : ( 'allowedFlag1', 'allowedFlags2', ...), 
                         'envFlags' : {'d' : 'DEBUG',
                                       'v' : 'VERBOSE',
                                       's' : 'SILENT'},
                         'allowedArgCount' : Nargs, 
                         'envOptions' : { 'envOpt1=' : 'ENV_VAR_1',
                                          'envOpt2=' : 'ENV_VAR_2', ...},
                         'requiredEnvOptions' : { 'envOptRequired' : 'ENV_VAR_REQUIRED', ...},
                         'synonymousOptions' : { 'opt1' : ( 'synOpt1', 'synOpt2', ...), ...}, 
                         'mutuallyExclusiveOptions' : (  ('opt1', 'opt2',...), ( 'opta', 'optb',...) ...), 
                         'mustHaveOneOfOptions' : ( ('opt1', 'opt2', ...), ('opta', 'optb', ...) ...), 
                         'restrictedValueOptions' : { 'opt' : ( 'valOptA', 'valOptB', ...), ...}, 
                         'restrictedTypeOptions' : { 'opt' : WhatType,... }, 
                         'incompatibleOptionCombinations' : [ { 'oldOptions' : [ 'oldOpt1', 'oldOpt2' ],
                                                                'newOptions' : [ 'newOpt1', 'newOpt2' ] },
                                                              { 'funOptions' : [ 'funThing1', 'funThing2', 'funThing3' ],
                                                                'dullOptions' : [ 'dullStuff' ] }
                                                            ],
                         'importModule' : 'nameOfModuleContainingCode', 
                         'dispatchHandler' : 'nameOfRoutineToHandleDispatch', 
                         'description' : { 'opt' : 'description of opt', ...},
                         'isDeprecated' : 0,
                         'apiImport' : "from sam import sam",
                         'apiObjectName' : "sam",
                         'apiDispatchMethod' : "mainVerbForm",
                         'apiReturns' : "list of strings",
                         'apiHelpText' : None,
                         'apiObjectsThatReplaceOptions' : { 'keyword1' : ['option1', 'option2'],
                                                            'keyword2' : ['envOpt1',], },
                         'additionalAttr1' : additionalValue1,
                         'additionalAttr2' : additionalValue2,...
                        }
  aCommand = Command(aCommandDefinition)

Command Definition Elements:

verb A list of strings defining the fundamental command form and any legal synonyms. Do not include the shell command (e.g., 'sam' or 'sam_admin').

Examples:

   createDef  = Command( {'verb' : 
                          [ 'create project definition', 
                            'define project', 
                            'create dataset definition' ] ...})
   createDset = Command( {'verb' : 
                          [ 'create dataset', 
                            'create project snapshot', 
                            'create snapshot' ]...})
requiredOptions A list of (long) options which must appear on the command line (unless it is listed also as a requiredEnvOption -- in which case, either the option must appear on the commandline OR the associated environmental variable must be set). Do not include the initial '--'. A trailing '=' implies that the long option requires a value.

Examples:

    'requiredOptions' : ( 'group=' )
    'requiredOptions' : ( 'interactive' )
    'requiredOptions' : ( 'family=', 'appname=', 'version=' )
In the first case, the user would need to include --group=someGroupName on the command line or the command would not be considered legal. In the second case, the user would need to include --interactive. In the third case, all three options would need to be entered on the command line.
allowedOptions A list of all (long) options which may appear on the command line. (You do not have to include which have already been listed as 'requiredOptions'). Do not include the initial '--'. A trailing '=' implies that the long option requires a value.

Examples:

  'allowedOptions' : ( 'groups', 
                       'disks', 
                       'projects', 
                       'fsman', 
                       'all', 
                       'files=' )
  'allowedOptions' : ( 'job=', 'project=', 'all-jobs' )
allowedFlags A list of all (short) options (i.e., flags) which may appear on the command line. Do not include the initial '-'. A trailing ':' implies that the short option takes a value.

Examples:

  'allowedFlags' : ( 'd', 'i', 'v', 'o:' )
envFlags A dictionary mapping an allowedFlag to an environmental variable name. If the flag is not specified on the command line, the environmental variable will be used to determine the boolean value of the flag.

Examples:

 'envFlags' : {'s' : 'SAM_PYAPI_RETRY_SILENTLY'}
allowedArgCount An integer representing the number of arguments (non-options) allowed on the command line. If allowedArgCount is not specified, the default is 0 (no arguments allowed). The integer -1 may be used to specify that "one or more" arguments are required.

Examples:

   Command( { ...
     'allowedArgCount' : 2,  
     'description' : { 'args' : "<input> <output>" }
    ...})
   Command( { ...
     'allowedArgCount' : -1, 
     'description' : { 'args' : "list of tape volumes" }
    ...})
envOptions A dictionary whose keys option names, and whose values are environmental variable names associated with this option. The commandline will always override the environment.

Examples:

   Command( { ...
        'envOptions' : { 'home=' : HOME,
                         'path=' : PATH,
                       },
   ... })
In this case, if "--home=<value>" is specified it will take precedence; otherwise, use the value from $HOME. (Ditto for path). If the option is not on the commandline, and the environmental variable is not set, the argument dictionary will not contain an entry for this option.
requiredEnvOptions A dictionary (just like envOptions) -- but the value must be set or the command will be deemed illegal. That is, either the option must be specified on the commandline, or the environmental variable must be set, in order for the command to parse as valid.

synonymousOptions A dictionary containing a table of synonyms. The keys to the dictionary are the primary option names -- that is, the name by which the option is known within the code, and the name by which the value will be passed back in the argDict. The values are lists of synonyms -- which will be translated to the primary value before return. You may omit the "=" on options which take a value, as long as they have been previously listed in the list of 'allowedOptions' or 'requiredOptions' with the appropriate "=". Examples:
   Command({ ...
   'allowedOptions'    : [ 'data_file_name=', 'run_number=' ],
   'synonymousOptions' : { 'data_file_name' : [ 'filename', 'file', ],
                           'run_number'     : [ 'run', 'number' ] },
   'restrictedTypeOptions' : { 'run_number' : IntType }
    ... })
In this example, the following commands are equivalent:
   $  --data_file_name=myFile.py --run_number=10
   $  --file=myFile.py --run=10
   $  --filename=myFile.py --number=10
In all cases, the argDict passed back will include the entries: { 'data_file_name' : 'myFile.py', 'run_number' : 10 }
mutuallyExclusiveOptions A list of sub-lists; each sub-list defines a set of options where only one from the list may appear on the command line. The options must be listed as allowedOptions, allowedFlags, or requiredOptions. You do not have to include the trailing '=' or ':' for options which require values when defining mutually exclusive sets.

Examples:

   Command( { ...
      'mutuallyExclusiveOptions' : ( 
                    ('apples', 'oranges', 'cherries'), 
                    ('dog', 'cat', 'hamster') )
      ...})
   Command( { ...
      'mutuallyExclusiveOptions' : ( 
                    ('job', 'project', 'all-jobs') )
      ...})
In the first case, the user may select only one fruit, and/or only one animal. In the second case, the user may specify '--job' OR '--project' OR '--all-jobs'.
mustHaveOneOfOptions A list of sub-lists; each sub-list defines a set of options where the user must specify (at least) one from the list. The options must be listed as allowedOptions, allowedFlags, or requiredOptions. You do not have to include the trailing '=' or ':' for options which require values when defining "must have one-of" sets.

Examples:

   Command( { ...
        'mustHaveOneOfOptions' : ( 
                ('apples', 'oranges', 'cherries'), 
                ('dog', 'cat', 'hamster') )
      ...})
   Command( { ...
         'mustHaveOneOfOptions' : ( ('job', 'project', 'all-jobs') )
      ...})
In the first case, the user must select at least one fruit and at least one one animal. In the second case, the user must specify at least one of '--job', '--project', and '--all-jobs'.
restrictedValueOptions A dictionary of options which are restricted to a certain set of known values. The keys of the dictionary are the names of the options. The values are the list of known restricted values. The options must be listed as allowedOptions, allowedFlags, or requiredOptions. You do not have to include the trailing '=' or ':' for options which require values when defining restrictedValues.

Examples:

   Command( { ...
            'restrictedValueOptions' : { 
                  'policy' : ( 'DEFAULT', 'LRU', 'FIFO', 'RANDOM' ),
                  'type' : ( 'datafile', 'run' ) }
             ...})
restrictedTypeOptions A dictionary of options which are restricted to a certain type. The keys of the dictionary are the names of the options. The values are the required data type of this keyword, either a native python data type (string, int, long, float), or an instance of a class. The parser will convert the input arguments to the specified data type. The default data type if none is specified is StringType.

Examples:

   Command( { ...
      'restrictedTypeOptions' : { 
                      'sizeK'     : types.LongType, 
                      'sizeG'     : types.IntType, 
                      'pi'        : types.FloatType,
                      'metadata'  : SamDataFile.SamMetaData,
                      'AppFamily' : SamStruct.AppFamily,
                      }
    ... })
incompatibleOptionCombinations A list of incompatible option combinations. Each element in the list is a dictionary. Each key of the dictionary is a "column", where the user may select "some" from columnA or "some" from columnB, but not both. (The precise number is determined by other command definition entities, such as "mutuallyExclusiveOptions" or "requiredOptions", etc.).

Example:

   Command( { ...
      'incompatibleOptionCombinations' : [ 
               { 'oldFormatOptions' : [ 'dim', 'rpn', 'infix' ],
                 'newFormatOptions' : [ 'filename', 'size', 
                                        'defname', 'datatier', 
                                        'eventnum', 'runnum' ] },
               { 'byMonthOptions' : [ 'byMonth', 'month=', ],
                 'byYearOptions'  : [ 'byYear',  'year=', ],
                 'byDayOptions'   : [ 'byDay',   'day=', ] }
             ]
    ...} )
In this example, a user may enter options from the list of "oldFormatOptions" or from the list of "newFormatOptions", but may not mix the two formats. A user may enter from the "byMonthOptions", "byYearOptions", or "byDayOptions", but may not mix options from these groups.
importModule The importModule can be one of the following:
  1. the name (as a python string) of the module to import in order to execute the appropriate dispatch handler,
  2. the module itself (as a python object of type 'ModuleType')
The module should be in the user's $PYTHONPATH. Do not include the ".py" at the end of the module name if using the string form. Examples:

   Command( { ...
      'importModule' : "SamAdminDispatchHandler"
    ...})
   Command( { ...
      'importModule' : "sam_file_client"
    ...})
   Command( { ...
      'importModule' : self.__class__.__module__
    ...})
dispatchHandler You do not need to specify a dispatchHandler when writing a CommandInterface; the implementation acts as the dispatch method. The dispatch handler can be one of the following:
  1. the name (as a python string) of a function in the importModule which accepts two arguments and dispatches the command appropriately.
  2. a function (as a python object of type 'FunctionType') which accepts two arguments and dispatches the command appropriately,
  3. a class method (as a python object of type 'MethodType') which accepts three arguments and dispatches the command appropriately.
The arguments passed are argDict, a dictionary containing the values of all options and flags from the commandline; and argList, a list of the additional args from the command line. (In the third case, where the dispatch handler is a class method, the third argument is of course, 'self').

The dispatch handler, in its crudest form, looks like:

  def aDispatchHandler( argDict, argList ):
      try:
          # do something based on argDict and argList here
          return 0
      except:
          # raise an exception if desired
          return 1
where argDict and argList are the options, flags, and arguments after successfully parsing the user's command.

The argDict contains a few "special" entries that do not come from the commandline, but rather are inserted by the CommandParser for your convenience. They are:

      argDict['command'] = command       # the command object itself

The dispatch handler should return a status value so that the main wrapper can call sys.exit() with the appropriate status!

description A dictionary of descriptions (to be used in the on-line help) for the legal options and arguments. The keys are the name of the option (or primary option, in the case of synonyms), or the special value 'args' for the description of arguments. The values are a text string describing the option or the arguments. You do not have to include the trailing '=' or ':' for options which require values when defining the description.

Examples:

   aCommand = Command( {
        verb' : ('do this'),
        'allowedArgCount' : 2,
        'allowedOptions' : ( 'when=', 'how-many-times=', 'carefully' ),
        'synonymousOptions' : { 
                'carefully' : ['with-great-care', 'slowly'] },
        'restrictedValueOptions' : { 
                'when' : ("today", "tomorrow", "yesterday") },
        'restrictedTypeOptions ' : { 
                'how-many-times' : IntType },
        'description' : { 
                'args'      : "inFile outFile",
                'carefully' : "take special care in doing it"
                'when'      : "when does it need to be done",
                'how'       : "how to do it" }
                } )
isDeprecated A boolean value which, when set, modifies the command documentation to indicate that this command is deprecated (no longer actively maintained).

Example:

    theCommand = Command( {'verb' : ['do something old'],
                           'isDeprecated' : 1,
                            ...} )
helpText A string containing additional help for this command; it is appended to the end of the auto-generated syntactical help.

Example:

    helpText = "\nHere is additional information about how to use this command." + \
               "\nIt can get very in-depth."
    theCommand = Command( {'verb' : ['do something'],
                           'helpText' : helpText
                           ...})
seeAlso A list of strings containing URLs for additional information pertaining to this particular command. It is appended automatically to the end of the auto-generated syntactical help for this particular command (not the entire suite).

Example:

    seeAlso = ["http://d0db.fnal.gov:/sam_common_pylib/CommandInterface.html"]
    theCommand = Command( {'verb' : ['do_something'],
                            ...
                           'seeAlso' : seeAlso, } )
apiImport This is a documentation string informing the api user what needs to be done in order to access the API interface (if any has been defined). At the present time, it is not very smart; it is just text.

Example:

   apiImport = "from sam import sam"
   theCommand = Command( { 'verb' : ['do_somethning'],
                           ...
                           'apiImport' : apiImport, })
apiObjectName This is a documentation string which is used to tell the user how to call the implementation. Some implementations are in terms of objects (as in, "object.doSomething") and others are in terms of functions (as in, "doSomething"). If the apiObjectName is set to 'None', then we assume the latter.

Example:

   theCommand = Command( { 'verb' : ['do something with an object method'],
                           ...
                           'apiImport' : "from sam import sam",
                           'apiObjectName' : 'sam', } )

   anotherCommand = Command( { 'verb' : ['do something with a function'],
                               ...
                               'apiImport' : "import moduleDefiningFunction",
                               'apiObjectName' : None } )
(Note that 'None' is the default, it does not need to be listed).
apiDispatchMethod This is a documentation string which tells the user the name of the method or function that they would use when writing a native API call to this routine (rather than using the shell call). It should be the function or method (i.e., the OBJECT, not its name).

Example:

   theCommand = Command( {'verb' : ['do something'],
                           ...
                          'apiDispatchMethod' : moduleDefiningFunction.doSomething } )
apiReturns This is a documentation string that is used instead of the command helpText (if set) to document the interfaced. If this is not set, the command helpText will be used.

Example:

   theCommand = Command( {'verb' : ['do something'],
                           ...
                          'apiHelpText' : """
     This is the python interface to the doSomething command.
     NOTE that in the python interface, the object is returned.
     Any other words would go here.  Otherwise, we could (and by
     default would) use the helpText that was set for the main
     command.
     """ } )
apiObjectsThatReplaceOptions On the command line, for which the CommandParser was originally written, you cannot pass python objects; you can only pass strings. However, api users can create objects and pass them instead of string representations of these objects. For example, at the command line you might say "---inputFile=nameOfFile", and the commandline implementation is "smart" enough to open the named file and read whatever information it is expecting from that file (e.g., nameOfFile.metadataDictionary). In the native API, one may pass that metadataDictionary as a python object. Hence the apiObjectsThatReplaceOptions.

Example:

   theCommand = Command( { 'verb' : ['process metadata'],
                           'requiredOptions' : ['inputFile='],
                           'apiOptionsThatReplaceObjects' : { 'metadata' : ['inputFile'] },
                           } )
Additional Attributes You may pass any other attributes within the dictionary that defines your command. This provides a shorthand method for passing additional information to the dispatchHandler. For example, the following two code segments provide idential results:
  aCommand = Command( {'verb' : ('run'),
                       'doingWhat' : 'running' } )
and
  aCommand = Command( {'verb' : ('run') )
  aCommand.doingWhat = 'running'
NOTE: if the the command you are defining will be used in the context of a blessed command interface suite, you need to be careful with the data type of any additionalAttributes that you set. In general, it is only supported to set additionalAttributes to string values (as opposed to classes or instances). For example, the following will not work in the context of a blessed command interface suite:
   # this will NOT work if the suite is blessed:
   from somePlace import someThing
   aCommandInterface = CommandInterface(commandParameters = 
    {'verb' : ['do something'],
     'doingWhat' : someThing
     ...})
Instead, use the following:
    # this will work if the suite is blessed:
    from somePlace import someThing
    aCommandInterface = CommandInterface(commandParameters = 
     {'verb' : ['do something'],
      'doingWhatModuleName' : 'somePlace',
      'doingWhatImportName' : 'someThing'
      ...})

    ...
    # inside the implementation of aCommandInterface:
    code = "from %s import %s as DOING_WHAT" % (self.doingWhatModuleName, self.doingWhatImportName)
    ...



Command Methods:

verifyArgs(argDict,argList) Designed to be overridden by derived classes that need additional checking beyond syntactic correctnes of the parsed command object. Should return 0 if the command is not legal, 1 if the command is legal. This routine is called after all of the syntactic checking has been done.

Parsing of complications beyond those handled by the base Command class may be handled here.

Examples:

    from SamCommand.CommandDefinition import Command, TRUE, FALSE
    class MyCommand(Command):
      def verifyArgs(self, argDict=None, argList=None):
        if argDict is None:
            argDict = self.getArgDict()
        if argList is None:
            argList = self.getArgList()

        # parent class verification:
        returnStatus = Command.verifyArgs(self, argDict, argList)

        # now our additional verification

        OK = TRUE
        BAD = FALSE

        #
        # special processing for complications:
        #

        if ( argDict.has_key('ABORT') ):
           returnStatus = returnStatus and BAD

        if ( argDict.has_key('forbiddenOption') ):
           self.storeProblemExplanation('The "forbidden" option is FORBIDDEN.')
           returnStatus = returnStatus and BAD

        if ( argDict.has_key('obsoleteOption') ):
           sys.stderr.write("The --obsoleteOption is no longer recommended.\n")
           sys.stderr.write("Please use --newOption instead.")

           argDict['newOption'] = argDict['obsoleteOption']
           del argDict['obsoleteOption']

        if ( argDict.has_key('sings') and argDict.has_key('dances') ):
           argDict['multiTalented'] = 'yes'

        self.setArgDict(argDict)
        return returnStatus
getArgDict() Return the command's argDict as a dictionary.

Example:

         theCommand = commandSuite.parseArgs(sys.argv)
         argDict = theCommand.getArgDict()
         for option in argDict.keys():
              setSomethingBasedOn(argDict[option])
getArgList() Return the command's argList as a list.

Example:

          theCommand = commandSuite.parseArgs(sys.argv)
          argList = theCommand.getArgList()
          for argument in argList:
               doSomethingTo(argument)
appendHelpText(aString) Append additional help information to the syntactical help generated by the base class.

Examples:

   class MyCommand(Command):
       def __init__(self, *args):
           Command.__init__(self, args)

           additionalHelp = """
              Here is more help on the command.

              Why would you use this command?  When?

              What is it good for?  What information
              beyond syntax do you need for this
              command to be useful?
           """

          self.appendHelpText(additionalHelp)
printHelp() Print the help text associated with this command. Example:
   theCommand.printHelp()
getHelpText() Retrieve the help text associated with this command as a string object. Example:
   aMessage = theCommand.getHelpText()
   print aMessage
dispatch() A routine which imports the specified dispatchHandler from the specified importModule, and executes the dispatch handler code. The appropriate exit status should be returned to the caller.

The base class code is shown below, since I can't think of any realistic examples of how it might be modified (but I'm sure somebody else will).

Command.dispatch(self):

    #==================================================
    # Dispatch the command appropriately
    #==================================================
    def dispatch(self):
        returnStatus = 0
        if ( type(self.importModule) == StringType ):
                importCode = 'import %s' % self.importModule
                exec importCode

        if ( type(self.dispatchHandler) == StringType ):
            if ( type(self.importModule) == StringType ):
                dispatchCode = \
                 'returnStatus = %s.%s(self.getArgDict(), self.getArgList())' % \
                 (self.importModule, self.dispatchHandler)
            else:
                dispatchCode = \
                 'returnStatus = %s(self.getArgDict(), self.getArgList())' % \
                 self.dispatchHandler
            exec dispatchCode

        elif ( type(self.dispatchHandler) == FunctionType ):
            returnStatus = self.dispatchHandler(self.getArgDict(), self.getArgList())
        elif ( type(self.dispatchHandler) == MethodType ):
            returnStatus = self.dispatchHandler(self.getArgDict(), self.getArgList())

        return returnStatus


For further information, contact sam-design@fnal.gov.
This document last updated:
$Date: 2005/05/20 18:36:00 $
$Author: lauri $