$include (..\lib\compStch.ext) /* *============================================================================ * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE * * Permission to use for any purpose, modify, copy, and make enhancements * and derivative works of the software is granted if attribution is given to * R.M. Gillmore, dba the ACME Software Deli, as the author * * While the ACME Software Deli does not work for money, there is nonetheless * a significant amount of work involved. The ACME Software Deli maintains the * rights to all code written, though it may be used and distributed as long as * the following conditions are maintained. * * 1. The copyright statement at the top of each code block is maintained in * your distribution. * 2. You do not identify yourself as the ACME Software Deli * 3. Any changes made to the software are sent to the ACME Software Deli *============================================================================ */ cmdArgsModule: do; $if not noID declare IDString (*) byte data ( '@(#)cmdArgs.p86 $Author: rmgillmore $ $Date:: 2025-05-04 19:35:39#$:', 0 ); $endif $include (..\lib\comnDefs.ext) $set (CMDARGS_IMPLEMENTATION) $include (..\lib\cmdArgs.ext) $include (..\lib\string.ext) $include (..\lib\sysCalls.ext) $include (..\lib\fileIO.ext) $include (..\lib\mem.ext) $include (..\lib\ptrMath.ext) $include (..\lib\errCode.ext) $include (..\lib\videoIO.ext) $include (..\lib\logger.ext) declare optionsReported boolean; /* * ============================================================================ * Function: reportCmdOptions() * * Description: * This function uses the available information to present the command * name and all of its possible arguments, using the description * provided for each argument * * Input: * firstCommandArgument - * typically argv[0]. the simple name of the application is extracted * from this string * optionsListPtr - * this is the definition given to assist command line parsing. as used * in this function, the command execution example is given, along with * the abbreviated and complete switch name and the provided description * for each switch (see getCmdOptions()) * * Output: * (to stderr) * ============================================================================ */ reportCmdOptions: procedure ( firstCommandArgument, optionsListPtr ) public; declare firstCommandArgument pointer, optionsListPtr pointer, optionsList based optionsListPtr commandOptionDefinition_t; declare NUMBER_INDENT_SPACES literally '2'; declare numberToIndent integer, maxLineLength integer, rowsOnTheScreen integer, delimiterChar char, longestShort integer, longestFull integer, commandLine (128) char, progNameLength word, cmdOptionsPtr pointer, cmdOptions based cmdOptionsPtr (*) commandOption_t; if ( not optionsReported ) then do; numberToIndent = NUMBER_INDENT_SPACES; /* * how wide is the current window? */ call screenSize( @maxLineLength, @rowsOnTheScreen ); delimiterChar = optionsList.delimiterChar; longestShort, longestFull = 0; /* * create the command line representation. start with the simple name * of the application */ call strcpy( @commandLine, simpleExecName( firstCommandArgument ) ); progNameLength = strlen( @commandLine ); /* * find the length of the longest switch string (we will use it when * reporting the command line, and when we report the help for each * switch to align the switch columns) */ findLongestSwitchString: do; declare index word, tempShortLen integer, tempLongLen integer; cmdOptionsPtr = optionsList.commandOptions; do index = 0 to optionsList.numberOptions - 1; if ( cmdOptions( index ).optionType <> NO_SWITCH ) then do; /* * we are going to look for the longest switch specifiers so we * will know how to indent the columns so that like information * is aligned */ tempShortLen = signed( strlen( cmdOptions( index ).shortSwitchChars ) ); if ( tempShortLen > longestShort ) then longestShort = tempShortLen; /* * a long switch specification is optional, so handle the NULL case */ if ( NULL <> cmdOptions( index ).longSwitchChars ) then do; tempLongLen = signed( strlen( cmdOptions( index ).longSwitchChars ) ); if ( tempLongLen > longestFull ) then longestFull = tempLongLen; end; end; /* if there is a switch */ end; /* end foreach option in list */ end findLongestSwitchString; /* * loop through all of the command parameters, appending like this: * [- {long switch in uppercase if NEXT_ARG}] */ buildingSwitchString: do; declare noLongSwitchName (*) byte data ( '(none)', 0 ), tempVarName (64) byte, index integer; do index = 0 to optionsList.numberOptions - 1; if ( cmdOptions( index ).optionType <> NO_SWITCH ) then do; call strcat( @commandLine, @( ' [-', 0 ) ); call strcat( @commandLine, cmdOptions( index ).shortSwitchChars ); if ( cmdOptions( index ).optionType = NEXT_ARG ) then do; call strcat( @commandLine, @( ' <', 0 ) ); /* * convert the string to upper case (to distinguish from * the switch string) */ if ( NULL = cmdOptions( index ).longSwitchChars ) then do; call strcpy( @tempVarName, @noLongSwitchName ); end; else do; call strcpy( @tempVarName, cmdOptions( index ).longSwitchChars ); call strupr( @tempVarName ); end; /* * add the switch indicator to the command string */ call strcat( @commandLine, @tempVarName ); call strcat( @commandLine, @( '>', 0 ) ); end; /* * close the bracket for this switch */ call strcat( @commandLine, @( ']', 0 ) ); end; /* if not the "NO_SWITCH" option */ end; /* building the command line */ end buildingSwitchString; /* * ensure that the command line is not too long */ reportResults: do; declare resultingStringsPtr pointer, divideResults based resultingStringsPtr stringArray_t, stringSegmentsPtr pointer, stringSegs based stringSegmentsPtr (*) pointer, tempCmdLine (128) char, shortIndentColumn integer, longIndentColumn integer, descriptionIndexColumn integer, index integer, optionIndex integer, counter integer, numberSpaces integer; call strcpy( @tempCmdLine, @commandLine ); resultingStringsPtr = strSplit( NUMBER_INDENT_SPACES, maxLineLength - 1, @tempCmdLine ); stringSegmentsPtr = divideResults.stringSegments; /* * We know what is necessary, let's report the program invocation */ call printf( @( '\nExecute as:\n', 0 ) ); do index = 0 to divideResults.numberSegments - 1; do counter = 0 to numberToIndent; call printStringAsciiZ( @( ' ', 0 ) ); end; call printf( @( '%s\n', 0 ), stringSegs( index ) ); if ( index = 0 ) then numberToIndent = numberToIndent + signed( progNameLength ) + 1; index = index + 1; end; call printStringAsciiZ( @( CR, LF, 0 ) ); /* * Now that we know the longest switch string in each category, account for * the switch indicator */ longestShort = longestShort + signed( size( delimiterChar ) ); longestFull = longestFull + signed( size( delimiterChar ) ); shortIndentColumn = NUMBER_INDENT_SPACES; longIndentColumn = shortIndentColumn + longestShort; descriptionIndexColumn = longIndentColumn + NUMBER_INDENT_SPACES; /* * We know what is necessary, let's report the switches */ call printf( @( 'Command Options:\n', 0 ) ); do optionIndex = 0 to optionsList.numberOptions - 1; if ( cmdOptions( optionIndex ).optionType <> NO_SWITCH ) then do; /* * short switch string report */ do counter = 0 to shortIndentColumn; call printStringAsciiZ( @( ' ', 0 ) ); end; call printf( @( '%c%s', 0 ), delimiterChar, cmdOptions( optionIndex ).shortSwitchChars ); if ( NULL <> cmdOptions( optionIndex ).longSwitchChars ) then do; /* * long switch string report */ numberSpaces = longIndentColumn - NUMBER_INDENT_SPACES - signed( strlen( cmdOptions( optionIndex ).shortSwitchChars ) ) + 1; do counter = 0 to numberSpaces; call printStringAsciiZ( @( ' ', 0 ) ); end; call printf( @( '%c%c%s', 0 ), delimiterChar, delimiterChar, cmdOptions( optionIndex ).longSwitchChars ); end; call printStringAsciiZ( @( CR, LF, 0 ) ); if ( NULL <> cmdOptions( optionIndex ).descriptionString ) then do; /* * description report. this one is a little harder because * the description may be longer than a single line can hold, * so it will be broken between words as necessary */ declare divideStringLength word, stringToDivide pointer, index integer; divideStringLength = strlen( cmdOptions( optionIndex ).descriptionString ); stringToDivide = malloc( divideStringLength + 1 ); call strcpy( stringToDivide, cmdOptions( optionIndex ).descriptionString ); resultingStringsPtr = strSplit( descriptionIndexColumn, maxLineLength - 1, stringToDivide ); stringSegmentsPtr = divideResults.stringSegments; do index = 0 to divideResults.numberSegments - 1; do counter = 0 to descriptionIndexColumn - 1; call printStringAsciiZ( @( ' ', 0 ) ); end; call printf( @( '%s\n', 0 ), stringSegs( index ) ); end; /* * we are going to ignore the value returned from free */ divideStringLength = word( free( stringToDivide ) ); end; /* if there was a description */ end; /* if ! NO_SWITCH */ end; /* for each option specified */ end reportResults; optionsReported = TRUE; end; /* not options reported */ end reportCmdOptions; /*============================================================================ * Function: availableSlot() * * Description: * using the command option definition, we create a list of places where * arguments not associated with a defined switch can be stored * * Input: * optionList - * a list of the command options provided by this module's user * * Output: * an int which is the index of the option where there is an empty * space (a NO_SWITCH slot that has not been used) *============================================================================ */ /* * searchedForSlots will be FALSE following the data segment initialization */ declare NO_SLOT_AVAILABLE literally '-1'; declare searchedForSlots boolean, startLookingAt integer; availableSlot: procedure ( optionDefinitionPtr ) integer; declare optionDefinitionPtr pointer, optionDefinition based optionDefinitionPtr commandOptionDefinition_t; declare slotToUse integer, cmdOptionsPtr pointer, cmdOptions based cmdOptionsPtr (*) commandOption_t; if ( not searchedForSlots ) then do; /* * search for the next available slot */ startLookingAt = 0; end; cmdOptionsPtr = optionDefinition.commandOptions; /* * a start looking slot of -1 means none are available */ if ( startLookingAt <> NO_SLOT_AVAILABLE ) then do; declare slotIndex integer; slotIndex = startLookingAt; /* * find the next NO_SWITCH slot */ do while ( slotIndex <= optionDefinition.numberOptions ) and ( cmdOptions( slotIndex ).optionType <> NO_SWITCH ); slotIndex = slotIndex + 1; end; if ( slotIndex < optionDefinition.numberOptions ) then do; slotToUse = slotIndex; startLookingAt = slotToUse + 1; end; else do; startLookingAt, slotToUse = -1; end; end; /* * store the "we have searched" indicator so that * we don't start at the top of the list again */ searchedForSlots = TRUE; /* * give the user the index of the available slot. if there are * none available, the default index (-1) is returned */ return ( slotToUse ); end; /* end availableSlot() */ /*============================================================================ * Function: getCmdOptions() * * Description: * using the command option definition, we parse and interpret the command * arguments, populating the variables associated with each switch * * Input: * numberArguments - * the number of argument strings in the array that was created by parsing * the command line. The number includes the name of the application. * argumentList - * an array of pointers to the argument strings * optionDefinitionPtr - * this is the definition given to assist command line parsing. as used * in this function, the command execution example is given, along with * the abbreviated and complete switch name and the provided description * for each switch (see reportCmdOptions()) * * Output: * - (indirect) interpreted commands stored as specified * - success/fail flag *============================================================================ */ getCmdOptions: procedure ( numberArguments, argumentListPtr, optionsListPtr ) integer public; declare numberArguments integer, argumentListPtr pointer, argumentArray based argumentListPtr (*) pointer, optionsListPtr pointer, optionsList based optionsListPtr commandOptionDefinition_t; declare switchString_t literally 'integer', NOT_SPECIFIED literally '0', USE_SHORT literally '1', USE_LONG literally '2'; declare done boolean, argIndex integer, switchType switchString_t; declare returnCode integer, numberOptions integer; returnCode = EXIT_SUCCESS; numberOptions = optionsList.numberOptions; /* convenience variable */ /* * the first pointer in the argPtrs array is the name of the program. We won't * consider that one in this function */ done = FALSE; argIndex = 1; switchType = NOT_SPECIFIED; innerBlock: do; declare numberSwitchChars integer, thisArgument pointer, argumentString based thisArgument (*) char; declare found boolean, index integer, commandOptionsPtr pointer, commandOptions based commandOptionsPtr (*) commandOption_t, switchPtr pointer, switchChars based switchPtr (*) char, resultsPtr pointer; commandOptionsPtr = optionsList.commandOptions; foreachArgument: do while ( ( argIndex < numberArguments ) and ( not done ) ); numberSwitchChars = 0; thisArgument = argumentArray( argIndex ); if ( argumentString( 0 ) = optionsList.delimiterChar ) then do; /* * a switch string is specified. is it a short switch, or a long one? */ numberSwitchChars = numberSwitchChars + 1; if ( argumentString( 1 ) <> optionsList.delimiterChar ) then do; switchType = USE_SHORT; end; /* short switch specified */ else do; numberSwitchChars = numberSwitchChars + 1; switchType = USE_LONG; end; /* long switch specified */ found = FALSE; index = 0; commandOptionsPtr = optionsList.commandOptions; do while ( not found ) and ( index < numberOptions ); do case ( switchType ); do; /* NOT_SPECIFIED */ switchPtr = NULL; found = FALSE; end; do; /* USE_SHORT */ switchPtr = commandOptions( index ).shortSwitchChars; end; do; /* USE_LONG */ switchPtr = commandOptions( index ).longSwitchChars; end; end; /* case ( switchType ) */ if ( switchPtr <> NULL ) then do; found = ( strcmp( @argumentString( numberSwitchChars ), switchPtr ) = 0 ); end; /* switchPtr is not NULL */ if ( found ) then do; /* * now figure out what we need to do. * * if EXISTS is the option type, interpret the * resultsStorePtr as a boolean_t *, and set that * variable to true. * * if NEXT_ARG is the option type, interpret the * resultsStorePtr as a char *, and copy the contents * of the next argument to the string space */ resultsPtr = commandOptions( index ).resultsStorePtr; do case ( commandOptions( index ).optionType ); do; /* EXISTS */ declare resultBoolean based resultsPtr boolean; resultBoolean = TRUE; end; do; /* NEXT_ARG */ call strcpy( resultsPtr, argumentArray( argIndex + 1 ) ); argIndex = argIndex + 1; end; do; /* NOT_SPECIFIED */ /* * there is nothing to do, since we should not get here * when there is a switch string found */ end; end; /* case switchType */ end; /* if found */ else do; index = index + 1; end; end; /* while an argument does not match */ if ( not found ) and ( not done ) then do; /* * the command argument switch could not be found, report the * error, and return to the caller with an error noted */ call fprintf( stderr, @( '\ninvalid command option (%s)\n', 0 ), argumentArray( argIndex ) ); call reportCmdOptions( argumentArray( 0 ), optionsListPtr ); returnCode = ERROR_DETECTED; done = TRUE; end; end; /* if the argument is a switch specification */ else do; /* * this argument is not in the form of a switch string, so store * the option in the next available NO_SWITCH location */ declare slotIndex integer; slotIndex = availableSlot( optionsListPtr ); if ( slotIndex <> NO_SLOT_AVAILABLE ) then do; resultsPtr = commandOptions( slotIndex ).resultsStorePtr; call strcpy( resultsPtr, argumentArray( argIndex ) ); end; else do; call printf( @( '\a\a\nno slot available to store command argument "%s"\n', 0 ), thisArgument ); returnCode = INSUFFICIENT_MEMORY; end; end; /* not a switch string */ argIndex = argIndex + 1; end foreachArgument; end innerBlock; return ( returnCode ); end getCmdOptions; end cmdArgsModule;