; docformat = 'rst' ;+ ; An object to facilitate parsing of command line options. ; ; :Examples: ; Try:: ; ; ; create options object ; opts = obj_new('mg_options', app_name='mg_options_example', version='1.0') ; ; ; setup options ; opts->addOption, 'verbose', 'v', $ ; /boolean, $ ; help='set to print a verbose greeting' ; opts->addOption, 'name', 'n', help='name of user to greet', default='Mike', $ ; metavar='user''s name' ; ; ; parse the options ; opts->parseArgs, error_message=errorMsg ; ; if (errorMsg ne '') then begin ; oldQuiet = !quiet ; !quiet = 0 ; message, errorMsg, /informational, /noname ; !quiet = oldQuiet ; end ; ; if (errorMsg eq '' && ~opts->get('help') && ~opts->get('version')) then begin ; print, (opts->get('verbose') ? 'Greetings and salutations' : 'Hello'), $ ; opts->get('name'), $ ; format='(%"%s, %s!")' ; endif ; ; ; destroy the options when done ; obj_destroy, opts ; ; :Uses: ; `MGcoHashTable` ; ; :Requires: ; IDL 6.2 ;- ; Class definition for a helper object representing individual option ; definitions. ;+ ; Set properties. ; ; :Private: ;- pro mg_opt::setProperty, short_name=shortName compile_opt strictarr if (n_elements(shortName) gt 0L) then begin self.shortName = shortName endif end ;+ ; Get properties. ; ; :Private: ;- pro mg_opt::getProperty, long_name=longName, short_name=shortName, $ boolean=boolean, metavar=metavar, $ key_column_width=keyColumnWidth, $ help_header=helpHeader compile_opt strictarr if (arg_present(longName)) then longName = self.longName if (arg_present(shortName)) then shortName = self.shortName if (arg_present(boolean)) then boolean = self.boolean if (arg_present(metavar)) then metavar = self.metavar if (arg_present(keyColumnWidth) || arg_present(helpHeader)) then begin helpHeader = '--' + self.longName helpText = self.metavar eq '' ? strupcase(self.longName) : self.metavar if (~self.boolean) then begin helpHeader += '=' + helpText endif if (self.shortName ne '') then begin helpHeader += ', -' + self.shortName if (~self.boolean) then helpHeader += ' ' + helpText endif keyColumnWidth = strlen(helpHeader) endif end ;+ ; Returns whether the option has had a value set i.e. it is present on the ; current command line. ; ; :Private: ; ; :Returns: ; byte ;- function mg_opt::isPresent compile_opt strictarr return, self.present end ;+ ; Returns the help text for the option. ; ; :Private: ; ; :Returns: ; string ;- function mg_opt::getHelp compile_opt strictarr return, self.help end ;+ ; Get value of the option. ; ; :Private: ; ; :Returns: ; string (normally) or byte (if boolean) ; ; :Keywords: ; present : out, optional, type=boolean ; set to a named variable to determine if the option is present ;- function mg_opt::getValue, present=present compile_opt strictarr present = self.present if (self.boolean) then begin return, self.present ? 1B : 0B endif else begin return, self.present ? self.value : self.default endelse end ;+ ; Set the value of the option. ; ; :Private: ; ; :Params: ; value : in, optional, type=string ; value of the option ;- pro mg_opt::setValue, value compile_opt strictarr on_error, 2 self.present = 1B if (n_elements(value) gt 0L) then begin self.value = value endif else begin if (~self.boolean) then begin message, 'non-boolean options like --' + self.longName + ' must have a value if present' endif endelse end ;+ ; Create an option. ; ; :Private: ; ; :Returns: ; 1 for success, 0 for failure ; ; :Keywords: ; long_name : in, required, type=string ; long name of the option ; boolean : in, optional, type=boolean ; set to indicate the option is boolean i.e. it does not take a value, ; being present "sets" it ; help : in, optional, type=string, default='' ; help text to display for the option ; default : in, optional, type=string, default='' ; default value of the option ; metavar : in, optional, type=string ; text to display in the help for non-boolean option values ;- function mg_opt::init, long_name=longName, boolean=boolean, $ help=help, default=default, metavar=metavar compile_opt strictarr self.longName = longName self.boolean = keyword_set(boolean) self.help = n_elements(help) gt 0L ? help : '' self.default = n_elements(default) gt 0L ? default : '' self.metavar = n_elements(metavar) gt 0L ? metavar : '' return, 1 end ;+ ; Define instance variables. ; ; :Private: ; ; :Fields: ; longName ; long name of the option ; shortName ; single character abbreviation for the option ; value ; value of the option ; present ; true if value has been set ; boolean ; true if option is a boolean type option ; metavar ; text to display in the help for non-boolean option values ; help ; text to print for the option if help is asked for ; default ; default value if value is not present ;- pro mg_opt__define compile_opt strictarr define = { mg_opt, $ longName: '', $ shortName: '', $ value: '', $ present: 0B, $ boolean: 0B, $ metavar: '', $ help: '', $ default: '' } end ; Definition of mg_options class ;+ ; Get value of option. ; ; :Returns: ; string ; ; :Params: ; optname : in, required, type=string ; long name of option ; ; :Keywords: ; params : in, optional, type=boolean ; set to return parameters ; n_params : out, optional, type=long ; number of parameters returned, only used if PARAMS is set ; present : out, optional, type=boolean ; set to a named variable to determine if the option was present ;- function mg_options::get, optname, params=params, n_params=nparams, $ present=present compile_opt strictarr on_error, 2 if (keyword_set(params)) then return, self.params->get(/all, count=nparams) opt = self.longOptions->get(optname, found=found) if (~found) then message, string(optname, format='(%"option %s not found")') return, opt->getValue(present=present) end ;+ ; Display the help for the defined options. ; ; :Private: ;- pro mg_options::_displayHelp compile_opt strictarr keys = self.longOptions->keys(count=nkeys) shortNames = self.shortOptions->keys(count=nshortNames) args = '' if (self.nparamsAccepted[0] gt 0L) then begin args += strjoin('arg' + strtrim(indgen(self.nparamsAccepted[0]) + 1, 2), ' ') endif case 1 of self.nParamsAccepted[1] lt 0L: args += '...' self.nParamsAccepted[1] eq 0L: self.nParamsAccepted[1] gt 0L: begin if (self.nparamsAccepted[1] gt self.nparamsAccepted[0]) then begin optionalArgIndices $ = indgen(self.nparamsAccepted[1] - self.nparamsAccepted[0]) $ + self.nparamsAccepted[0] if (args ne '') then args += ' ' args += strjoin('[arg' + strtrim(optionalArgIndices, 2) + ']', ' ') endif end endcase print, self.appname, args, format='(%"usage: %s [options] %s")' print keyColumnWidth = 2L maxKeyColumnWidth = 24L for k = 0L, nkeys - 1L do begin opt = self.longOptions->get(keys[k]) opt->getProperty, key_column_width=keyWidth keyColumnWidth >= keyWidth endfor keyColumnWidth = keyColumnWidth <; maxKeyColumnWidth combinedFormat = '(%" %-' + strtrim(keyColumnWidth, 2L) + 's %s")' splitFormat1 = '(%" %-0s")' splitFormat2 = '(%"' + strjoin((strarr(maxKeyColumnWidth + 4L) + ' ')) + '%s")' print, 'options:' keyind = sort(keys) for k = 0L, nkeys - 1L do begin opt = self.longOptions->get(keys[keyind[k]]) opt->getProperty, help_header=helpHeader if (strlen(helpHeader) gt maxKeyColumnWidth) then begin print, helpHeader, format=splitFormat1 print, opt->getHelp(), format=splitFormat2 endif else begin print, helpHeader, opt->getHelp(), format=combinedFormat endelse endfor end ;+ ; Print version information. ; ; :Private: ;- pro mg_options::_displayVersion compile_opt strictarr print, self.appname, self.version, format='(%"%s %s")' end ;+ ; Parse arguments. ; ; :Params: ; args : in, optional, type=strarr, default=command_line_args() ; string array of arguments ; ; :Keywords: ; error_message : out, optional, type=string ; set to a named variable to receive any error message generated from ; parsing the parameters ;- pro mg_options::parseArgs, args, error_message=errorMsg compile_opt strictarr errorMsg = '' _args = n_elements(args) eq 0L ? command_line_args(count=nargs) : args _nargs = n_elements(nargs) eq 0L ? n_elements(args) : nargs argumentExpected = 0B for a = 0L, _nargs - 1L do begin ; set an option value from the last token in the argument list if (argumentExpected) then begin opt->setValue, _args[a] argumentExpected = 0B continue endif ; long form if (strpos(_args[a], '--') eq 0L) then begin equalpos = strpos(_args[a], '=') if (equalpos eq -1L) then begin optname = strmid(_args[a], 2L) endif else begin optname = strmid(_args[a], 2L, equalpos - 2L) endelse opt = self.longOptions->get(optname, found=found) opt->getProperty, boolean=boolean if (~found) then begin errorMsg = string(optname, format='(%"unknown option: --%s")') return endif if (boolean) then begin opt->setValue endif else begin if (equalpos eq -1L) then begin argumentExpected = 1B endif else begin opt->setValue, strmid(_args[a], equalpos + 1L) endelse endelse continue endif ; short form if (strpos(_args[a], '-') eq 0L) then begin for sf = 1L, strlen(_args[a]) - 1L do begin shortName = strmid(_args[a], sf, 1) opt = self.shortOptions->get(shortName, found=found) if (~found) then begin errorMsg = string(shortName, format='(%"unknown option: -%s")') return endif opt->getProperty, boolean=boolean if (boolean) then begin opt->setValue endif else begin ; short name option with value must be the last one if (sf ne strlen(_args[a]) - 1L) then begin msg = '(%"non-boolean option -%s must be specified last to accept a value")' errorMsg = string(shortName, format=msg) return endif argumentExpected = 1B endelse endfor continue endif ; argument self.params->add, _args[a] endfor if (argumentExpected) then begin errorMsg = string(_args[a - 1L], format='(%"argument expected for %s")') return endif helpOpt = self.longOptions->get('help') if (helpOpt->isPresent()) then self->_displayHelp versionOpt = self.longOptions->get('version', found=versionFound) if (versionFound && versionOpt->isPresent()) then self->_displayVersion if (helpOpt->isPresent() || (versionFound && versionOpt->isPresent())) then return nparams = self.params->count() if (nparams lt self.nParamsAccepted[0]) then begin errorMsg = string(self.nParamsAccepted[0], nparams, $ format='(%"%d parameters required, %d given")') return endif if (self.nParamsAccepted[1] ge 0L && nparams gt self.nParamsAccepted[1]) then begin errorMsg = string(self.nParamsAccepted[1], nparams, $ format='(%"%d parameters allowed, %d given")') return endif end ;+ ; Add the definition of an option to the parser. ; ; :Params: ; longForm : in, required, type=string ; long name of the option, used with two dashes i.e. --help ; shortForm : in, optional, type=string ; single character name of an option, used with a single dash i.e. -h ; ; :Keywords: ; boolean : in, optional, type=boolean ; set to indicate the option is a boolean switch ; help : in, optional, type=string ; help text for the option ; default : in, optional, type=string ; default value ; metavar : in, optional, type=string ; text to display in the help for non-boolean option values ;- pro mg_options::addOption, longForm, shortForm, help=help, default=default, $ boolean=boolean, metavar=metavar compile_opt strictarr opt = obj_new('mg_opt', $ long_name=longForm, $ boolean=boolean, help=help, default=default, metavar=metavar) self.longOptions->put, longForm, opt if (n_elements(shortForm) gt 0L) then begin self.shortOptions->put, shortForm, opt opt->setProperty, short_name=shortForm endif end ;+ ; Add a range of positional parameters. ; ; :Params: ; nparamsRange : in, required, type=lonarr(2) ; valid range for number of positional parameters, use -1 for the max ; value to allow an unlimited number of parameters ;- pro mg_options::addParams, nparamsRange compile_opt strictarr on_error, 2 if (nparamsRange[0] lt 0L) then begin message, 'minimum number of params must be positive' endif if (nparamsRange[1] gt 0L && (nparamsRange[1] lt nparamsRange[0])) then begin message, 'maximum number of params must be greater than minimum number' endif self.nParamsAccepted = nparamsRange end ;+ ; Free resources. ;- pro mg_options::cleanup compile_opt strictarr ; free individual options objects opts = self.longOptions->values(count=count) if (count gt 0L) then obj_destroy, opts ; free hash tables of options obj_destroy, [self.longOptions, self.shortOptions, self.params] end ;+ ; Create option parsing object. ; ; :Returns: ; 1 for success, 0 for failure ; ; :Keywords: ; app_name : in, optional, type=string, default='' ; application name ; version : in, optional, type=string ; version of the application ;- function mg_options::init, app_name=appname, version=version compile_opt strictarr self.appname = n_elements(appname) eq 0L ? 'app' : appname self.version = n_elements(version) eq 0L ? '' : version self.longOptions = obj_new('MGcoHashTable', key_type=7, value_type=11) self.shortOptions = obj_new('MGcoHashTable', key_type=7, value_type=11) self.params = obj_new('MGcoArrayList', type=7) self->addOption, 'help', 'h', /boolean, help='display this help' if (self.version ne '') then begin self->addOption, 'version', /boolean, help='display version information' endif return, 1 end ;+ ; Define instance variables. ; ; :Fields: ; longOptions ; hash table of options keyed on long name of option ; shortOptions ; hash table of options keyed on short name of option, if present ;- pro mg_options__define compile_opt strictarr define = { mg_options, $ appname: '', $ version: '', $ longOptions: obj_new(), $ shortOptions: obj_new(), $ params: obj_new(), $ nParamsAccepted: lonarr(2) $ } end ; main-level example program ;+ ; Execute this program with something like:: ; ; idl -IDL_QUIET 1 -quiet -e ".run mg_options__define" -args --verbose --name=Mike ; ; or:: ; ; idl -IDL_QUIET 1 -quiet -e ".run mg_options__define" -args --help ;- ; create options object opts = obj_new('mg_options', app_name='mg_options_example', version='1.0') ; setup options opts->addOption, 'verbose', 'v', $ /boolean, $ help='set to print a verbose greeting' opts->addOption, 'name', 'n', help='name of user to greet', default='Mike', $ metavar='user''s name' ; parse the options opts->parseArgs, error_message=errorMsg if (errorMsg ne '') then begin oldQuiet = !quiet !quiet = 0 message, errorMsg, /informational, /noname !quiet = oldQuiet end if (errorMsg eq '' && ~opts->get('help') && ~opts->get('version')) then begin print, (opts->get('verbose') ? 'Greetings and salutations' : 'Hello'), $ opts->get('name'), $ format='(%"%s, %s!")' endif ; destroy the options when done obj_destroy, opts end