Wiki

Ticket #12: parseArgRefactor.patch

File parseArgRefactor.patch, 17.0 KB (added by hopscc, 16 years ago)

commandline parseArgs refactor

  • CommandLine.cobra

     
    195195        }, 
    196196        { 
    197197            'name': 'reference', 
    198             'synonyms': ['r'], 
     198            'synonyms': ['r'], # TODO: lose 'r' ->'Ref', 'R', something else 
    199199            'isAccumulator': true, 
    200200            'description': 'Add a DLL reference.', 
    201201            'args': 'Some.dll', 
     
    214214#           'args': ':Qualified.Type.Name', 
    215215#       }, 
    216216        { 
    217             'name': 'run', 
     217            'name': 'run',  # TODO: Synonym 'r' after change 'reference' switch 
    218218            'description': 'Runs the Cobra program. This is the default behavior if specify any Cobra source files.', 
    219219            'type': 'main', 
    220220        }, 
     221# 
     222        {        
     223            'name': 'exeArgs',  # TODO: -> 'run' after change 'run' to 'r' 
     224            'synonyms': ['X'], 
     225            'description': 'Remaining args are name of the file to run and args to pass to it.', 
     226            'eg':          '-X echo arg1 arg2 argThree', 
     227            'type': 'exeArgs', 
     228        }, 
     229# 
     230# 
     231        {        
     232            'name': 'runArgs', 
     233            'synonyms': ['-'],  #'--' 
     234            'description': 'Remaining args are to be passed to executable.', 
     235            'eg':          '-- arg1 arg2 argThree', 
     236            'type': 'runArgs', 
     237        }, 
     238#            
    221239        { 
    222240            'name': 'sharp-compiler', 
    223241            'description': 'Specify the path to the backend C# compiler.', 
     
    281299 
    282300    var _options = Options() 
    283301    var _pathList as List<of String>? 
     302    var _runArgs  as IList<of String>?  # nil or args set for running exe 
     303    var _exeFileName as String?         # Name specified for exe to run 
    284304    var _htmlWriter as HtmlWriter? 
    285305 
    286306    var _compiler as Compiler? 
     
    381401        if _htmlWriter 
    382402            _htmlWriter.writeHtml('</body></html>[_htmlWriter.newLine]') 
    383403 
    384     def isOptionSpecRestrictionViolated(optionSpec as Dictionary<of String, Object>) as bool 
    385         """ 
    386         Returns true if the option spec has a 'restriction' key and the check against that restriction is true. 
    387         """ 
    388         if optionSpec.containsKey('restriction') 
    389             branch optionSpec['restriction'] to String 
    390                 on 'mono-only' 
    391                     return not CobraCore.isRunningOnMono 
    392         return false 
    393  
    394404    def parseArgs(args as IList<of String>, options as out Options?, paths as out List<of String>?) 
    395405        """ 
    396406        Parse command line arguments. 
     
    399409        ensure 
    400410            options 
    401411            paths 
    402         body 
     412        body    # TODO: refactor to something more modular (i.e much < 200 lines long) 
    403413            optionPrefix = '-' 
    404             valuePrefix = c':' 
    405414            if not args.count 
    406415                options = Options() 
    407416                options.add('help', true) 
     
    413422            synToName = Dictionary<of String, String>() 
    414423                # ^ maps synonyms to their full names 
    415424            synList = List<of String>() 
    416             for d in _optionSpecs 
    417                 if .isOptionSpecRestrictionViolated(d) 
    418                     continue 
    419                 specDict[d['name'] to String] = d 
    420                 if d.containsKey('synonyms') 
    421                     syns = d['synonyms'] to System.Collections.IList 
    422                     for syn as String in syns 
    423                         assert not specDict.containsKey(syn) 
    424                         specDict[syn] = d 
    425                         synToName[syn] = d['name'] to String 
    426                         synList.add(syn) 
    427                 if not d.containsKey('type') 
    428                     d.add('type', 'string') 
     425            .initSyns( specDict, synToName, synList) 
    429426 
    430427            # set up initial valueDict 
    431428            valueDict = Dictionary<of String, Object>() 
    432429            if Utils.isDevMachine 
    433430                valueDict['reveal-internal-exceptions'] = true  # this is a specially computed default, but can still be overridden on the command line 
    434431 
     432            value = 'no-value' to dynamic 
     433            valueStr = 'no-value' 
    435434            fileList = List<of String>() 
    436             value = 'no-value' to dynamic 
    437435            mainOptions = List<of String>() 
    438436            didSpecify = Dictionary<of String, bool>()  # CC: could just be a Set 
     437            argn=0 
    439438            for arg in args 
     439                argn += 1   # next arg after current 
    440440                if arg.trim.length == 0 
    441441                    continue 
    442                 if arg.startsWith(optionPrefix) 
    443                     isOption = true 
    444                     while arg.startsWith(optionPrefix) 
    445                         arg = arg[1:] 
    446                 else 
    447                     isOption = false 
     442                     
     443                isOption = arg.startsWith(optionPrefix) 
    448444                if isOption 
    449                     parts = arg.split(@[valuePrefix], 2) 
    450                     if parts.length == 1 
    451                         name = parts[0] 
    452                         if name.endsWith('+') 
    453                             name = name[:-1] 
    454                             valueStr = 'on' 
    455                         else if name.endsWith('-') 
    456                             name = name[:-1] 
    457                             valueStr = 'off' 
    458                         else 
    459                             valueStr = 'on' 
    460                     else 
    461                         assert parts.length == 2 
    462                         name = parts[0] 
    463                         valueStr = parts[1] 
    464                     assert name.length, parts 
    465                     name = Utils.getSS(synToName to passthrough, name, name) to ! 
    466                     if not specDict.containsKey(name) 
    467                         msg = 'No such option "[name]".' 
    468                         if name.contains('=') 
    469                             msg += ' If you meant to specify an option value, use colon (:) instead of equals (=).' 
    470                         .error(msg) 
     445                    name = .getOptionParts(arg, optionPrefix, synToName, specDict, out valueStr) 
    471446                    spec = specDict[name] 
    472                     if Utils.getSB(spec to passthrough, 'isAccumulator', false) 
    473                         # accumulators are always treated as strings. TODO: assert that 
    474                         if valueDict.containsKey(name) 
    475                             (valueDict[name] to System.Collections.IList).add(valueStr to passthrough) 
    476                         else 
    477                             valueDict[name] = [valueStr] 
    478                             didSpecify[name] = true 
    479                     else 
    480                         cannotProcess = false 
    481                         if name=='debug' 
    482                             # special case 
    483                             if valueStr=='pdbonly' or valueStr=='full' 
    484                                 value = valueStr 
    485                             else 
    486                                 try 
    487                                     value = .boolForString(valueStr) 
    488                                 catch FormatException 
    489                                     cannotProcess = true 
    490                                 success 
    491                                     value = if(value, '+', '-') 
    492                         else 
    493                             if spec['type'] == 'main' 
    494                                 mainOptions.add(name) 
    495                                 value = true 
    496                             else 
    497                                 possible = .interpretValue(valueStr, spec) 
    498                                 if possible is not nil 
    499                                     value = possible 
    500                                 else 
    501                                     cannotProcess = true 
    502                         if cannotProcess 
    503                             .error('Cannot process value "[valueStr]" for option "[name]".') 
    504                         valueDict[name] = value 
    505                         didSpecify[name] = true 
     447 
     448                    if .isAccumulatorOpt(spec) 
     449                        .accumulateOptValue(name, valueStr, valueDict, didSpecify) 
     450                        continue 
     451                         
     452                    cannotProcess = false 
     453                    argsDone = false 
     454                    value = .processToValue(name, valueStr, spec, mainOptions, args, argn, out cannotProcess, out argsDone ) 
     455                    if argsDone 
     456                        break   # slurpt up rest of args 
     457                    if cannotProcess 
     458                        .error('Cannot process value "[valueStr]" for option "[name]".') 
     459                    valueDict[name] = value 
     460                    didSpecify[name] = true 
    506461                else # not isOption 
    507                     if File.exists(arg) 
    508                         fileList.add(arg) 
    509                     else if File.exists(arg+'.cobra') 
    510                         fileList.add(arg+'.cobra') 
    511                     else if Directory.exists(arg) 
    512                         fileList.add(arg) 
    513                     else 
    514                         msg = 'Cannot find "[arg]" as a file.' 
    515                         if arg.startsWith('/') 
    516                             msg += ' If you meant to specify an option, use dash (-) instead of slash (/).' 
    517                         .error(msg) 
     462                    if arg.startsWith('/') 
     463                        errHint = ' If you meant to specify an option, use dash (-) instead of slash (/).' 
     464                    .processAsFile(arg, fileList, errHint) 
    518465 
    519             # handle synonyms 
    520             for syn in synList 
    521                 if valueDict.containsKey(syn) 
    522                     valueDict[synToName[syn]] = valueDict[syn] 
    523                     valueDict.remove(syn) 
     466            .handleSynonyms(synList, synToName, valueDict)           
     467            .addInDefaults(valueDict)        
    524468 
    525             # add in defaults 
    526             for d in _optionSpecs 
    527                 defaultName = d['name'] to String 
    528                 if not valueDict.containsKey(defaultName) and d.containsKey('default') 
    529                     defaultValue = .interpretValue(d['default'] to String, d) to ! 
    530                     if .verbosity 
    531                         print 'Setting option "[defaultName]" to default value [defaultValue].' 
    532                     valueDict[defaultName] = defaultValue 
    533  
    534469            # TODO: make the option names case-insensitive 
    535470 
    536471            # check for more than one main option 
    537472            if mainOptions.count > 1 
    538473                .error('Cannot have these main options at the same time: [Utils.join(", ", mainOptions)]') 
    539474 
    540             # unpack certain options into specific class fields 
    541             if valueDict.containsKey('verbosity') 
    542                 _verbosity = valueDict['verbosity'] to int 
    543             if not valueDict.containsKey('timeit') and valueDict.containsKey('testify') 
    544                 valueDict['timeit'] = true 
    545             if valueDict.containsKey('timeit') 
    546                 CobraMain.willTimeIt = valueDict['timeit'] to bool 
    547             if valueDict.containsKey('files') 
    548                 for fileName as String in valueDict['files'] to System.Collections.IList 
    549                     try 
    550                         for line in File.readAllLines(fileName) 
    551                             line = line.trim 
    552                             if line.length==0 or line.startsWith('#') 
    553                                 continue 
    554                             # TODO: dup'ed above 
    555                             arg = line 
    556                             if File.exists(arg) 
    557                                 fileList.add(arg) 
    558                             else if File.exists(arg+'.cobra') 
    559                                 fileList.add(arg+'.cobra') 
    560                             else if Directory.exists(arg) 
    561                                 fileList.add(arg) 
    562                             else 
    563                                 msg = 'Cannot find "[arg]" as a file.' 
    564                                 #if arg.startsWith('/') 
    565                                 #   msg += ' If you meant to specify an option, use dash (-) instead of slash (/).' 
    566                                 .error(msg) 
    567                             # end dup 
    568                     catch IOException 
    569                         .error('Cannot open file "[fileName]".') 
     475            .unpackOptions(valueDict, fileList)  
    570476 
    571477            # set the out parameters 
    572478            options = Options(valueDict) 
     
    576482 
    577483            .computeArgImplications(options to !) 
    578484 
     485         
     486    def getOptionParts(arg as String, _ 
     487                optionPrefix as String, _ 
     488                synToName as Dictionary<of String, String>, _ 
     489                specDict as Dictionary<of String, Dictionary<of String, Object>>, _ 
     490                valueStr as out String) as String 
     491        arg = .fixOptionArg(arg, optionPrefix)   
     492        # [name, valueStr] = .splitOpt(arg) 
     493        l   = .splitOpt(arg)  
     494        name     = l[0] 
     495        valueStr = l[1] 
     496        name = .validateOptionName(name, synToName, specDict) 
     497        return name          
     498         
     499             
     500    # Strip any lead                                                                                                ing optionPrefix and adjust remaining option 
     501    def fixOptionArg(arg as String, optionPrefix as String) as String    
     502        while arg.startsWith(optionPrefix) 
     503            arg = arg[1:] 
     504            if not arg.length  # '--' 
     505                arg='runArgs' 
     506        return arg 
     507                 
     508    # Split option into name and valueStr 
     509    def  splitOpt(arg as String) as IList<of String> 
     510        valuePrefix = c':' 
     511        parts = arg.split(@[valuePrefix], 2) 
     512        if parts.length == 1 
     513            name = parts[0] 
     514            if name.endsWith('+') 
     515                name = name[:-1] 
     516                valueStr = 'on' 
     517            else if name.endsWith('-') 
     518                name = name[:-1] 
     519                valueStr = 'off' 
     520            else 
     521                valueStr = 'on' 
     522        else 
     523            assert parts.length == 2 
     524            name = parts[0] 
     525            valueStr = parts[1] 
     526        assert parts, name.length  
     527        assert valueStr.length 
     528        return [name, valueStr]  
     529         
     530    def isOptionSpecRestrictionViolated(optionSpec as Dictionary<of String, Object>) as bool 
     531        """ 
     532        Returns true if the option spec has a 'restriction' key and the check against that restriction is true. 
     533        """ 
     534        if optionSpec.containsKey('restriction') 
     535            branch optionSpec['restriction'] to String 
     536                on 'mono-only' 
     537                    return not CobraCore.isRunningOnMono 
     538        return false 
     539             
     540    # init supporting data structures for handling option synonyms       
     541    def initSyns(specDict as Dictionary<of String, Dictionary<of String, Object>>, _ 
     542                    synToName as Dictionary<of String, String>, _ 
     543                    synList as List<of String>) 
     544        for d in _optionSpecs 
     545            if .isOptionSpecRestrictionViolated(d) 
     546                continue 
     547            specDict[d['name'] to String] = d 
     548            if d.containsKey('synonyms') 
     549                syns = d['synonyms'] to System.Collections.IList 
     550                for syn as String in syns 
     551                    assert not specDict.containsKey(syn) 
     552                    specDict[syn] = d 
     553                    synToName[syn] = d['name'] to String 
     554                    synList.add(syn) 
     555            if not d.containsKey('type') 
     556                d.add('type', 'string') 
     557             
     558    # ensure the given name exists as an option name or synonym mappable 
     559    # to an option name; return the canonical name for the option/synonym 
     560    def validateOptionName( name as String, _ 
     561            synToName as Dictionary<of String, String>, _ 
     562            specDict as Dictionary<of String, Dictionary<of String, Object>> _ 
     563            ) as String 
     564        name = Utils.getSS(synToName to passthrough, name, name) to ! 
     565        if not specDict.containsKey(name) 
     566            msg = 'No such option "[name]".' 
     567            if name.contains('=') 
     568                msg += ' If you meant to specify an option value, use colon (:) instead of equals (=).' 
     569            .error(msg) 
     570        return name      
     571         
     572    def isAccumulatorOpt(spec as Dictionary<of String,Object>) as bool 
     573        return  Utils.getSB(spec to passthrough, 'isAccumulator', false) 
     574             
     575    def accumulateOptValue(name as String, valueStr as String, _ 
     576            valueDict as Dictionary<of String, Object>, _ 
     577            didSpecify as Dictionary<of String, bool>) 
     578        # accumulators are always treated as strings. TODO: assert that 
     579        if valueDict.containsKey(name) 
     580            (valueDict[name] to System.Collections.IList).add(valueStr to passthrough) 
     581        else 
     582            valueDict[name] = [valueStr] 
     583            didSpecify[name] = true 
     584         
     585             
     586    def fixupDebug(valueStr as String, cannotProcess as out bool) as String 
     587        cannotProcess = false 
     588        if valueStr == 'pdbonly' or valueStr == 'full' 
     589            return valueStr 
     590             
     591        value = 'no-value'   
     592        try 
     593            b = .boolForString(valueStr) 
     594        catch FormatException 
     595            cannotProcess = true 
     596        success 
     597            value = if(b, '+', '-') 
     598        return value 
     599     
     600    def processToValue(name as String,  _ 
     601                        valueStr as String, _ 
     602                        spec as Dictionary<of String, Object>,_ 
     603                        mainOptions as List<of String>, _ 
     604                        argList as IList<of String>, _ 
     605                        argn as int, _ 
     606                        cannotProcess as out bool, _ 
     607                        argsDone as out bool ) as dynamic 
     608        value as dynamic = 'no-value' 
     609        argsDone = false 
     610        cannotProcess = false 
     611     
     612        if name == 'debug'      # special case 
     613            return .fixupDebug(valueStr, out cannotProcess ) 
     614         
     615        t  = spec['type'] to String 
     616        branch t 
     617            on 'main' 
     618                mainOptions.add(name) 
     619                value = true 
     620            on 'runArgs' 
     621                # remainder of args are for execution of exe file 
     622                _runArgs =  argList[argn:] 
     623                argsDone = true 
     624            on 'exeArgs' 
     625                # remainder of args are exe file and then args for it 
     626                if argList.count <= argn 
     627                    .error("exeArgs option must have at least one arg following") 
     628                _exeFileName = argList[argn] 
     629                _runArgs     = argList[argn+1:] 
     630                argsDone = true 
     631            else 
     632                possible = .interpretValue(valueStr, spec) 
     633                if possible is not nil 
     634                    value = possible 
     635                else 
     636                    cannotProcess = true 
     637        return value 
     638                 
    579639 
     640    def handleSynonyms(synList as List<of String>, _ 
     641                        synToName as Dictionary<of String, String>, _ 
     642                        valueDict as Dictionary<of String, Object>) 
     643        for syn in synList 
     644            if valueDict.containsKey(syn) 
     645                valueDict[synToName[syn]] = valueDict[syn] 
     646                valueDict.remove(syn) 
     647             
     648    def addInDefaults(valueDict as Dictionary<of String, Object>) 
     649        for d in _optionSpecs 
     650            defaultName = d['name'] to String 
     651            if not valueDict.containsKey(defaultName) and d.containsKey('default') 
     652                defaultValue = .interpretValue(d['default'] to String, d) to ! 
     653                if .verbosity 
     654                    print 'Setting option "[defaultName]" to default value [defaultValue].' 
     655                valueDict[defaultName] = defaultValue 
     656         
     657    # unpack certain options into specific class fields 
     658    def unpackOptions(valueDict as Dictionary<of String, Object>, _ 
     659                      fileList as List<of String>)   
     660        if valueDict.containsKey('verbosity') 
     661            _verbosity = valueDict['verbosity'] to int 
     662 
     663        if not valueDict.containsKey('timeit') and valueDict.containsKey('testify') 
     664            valueDict['timeit'] = true 
     665        if valueDict.containsKey('timeit') 
     666            CobraMain.willTimeIt = valueDict['timeit'] to bool 
     667             
     668        if valueDict.containsKey('files') 
     669            fileNamesList = valueDict['files'] to System.Collections.IList 
     670            .processFilesFile( fileNamesList, fileList) 
     671 
     672                 
     673    # Treat entries in fileNamesList as names of files containing file 
     674    # names to compile, validate names and add into fileList 
     675    def processFilesFile(fileNamesList as IList, fileList as List<of String>) 
     676        for fileName as String in fileNamesList 
     677            try 
     678                for line in File.readAllLines(fileName) 
     679                    line = line.trim 
     680                    if line.length==0 or line.startsWith('#') 
     681                        continue 
     682                    .processAsFile(line, fileList, nil) 
     683            catch IOException 
     684                .error('Cannot open file "[fileName]".') 
     685             
     686    # validate arg as filename and on success add into fileList 
     687    def  processAsFile(arg as String, fileList as List<of String>, _ 
     688                errHint as String?) 
     689        if File.exists(arg) 
     690            fileList.add(arg) 
     691        else if File.exists(arg+'.cobra') 
     692            fileList.add(arg+'.cobra') 
     693        else if Directory.exists(arg) 
     694            fileList.add(arg) 
     695        else  
     696            msg = 'Cannot find "[arg]" as a file.' 
     697            if errHint  
     698                msg += errHint 
     699            .error(msg) 
     700             
     701 
    580702    def computeArgImplications(options as Options) 
    581703        if options.getDefault('target', '') == 'lib' and not options.isSpecified('compile') 
    582704            options['compile'] = true 
     
    588710            options['include-nil-checks'] = false 
    589711            options['include-tests'] = false 
    590712            options['optimize'] = true 
    591  
     713             
    592714    def interpretValue(valueStr as String, spec as Dictionary<of String, Object>) as dynamic? 
    593715        value as dynamic? 
    594716        branch spec['type'] to String 
     
    714836        if c.errors.count 
    715837            print 'Not running due to errors above.' 
    716838        else 
    717             p = c.runProcess 
     839            p = c.runProcess(_exeFileName, _runArgs) 
    718840            if _verbosity >= 1 
    719841                print 'Running: [p.startInfo.fileName] [p.startInfo.arguments]' 
    720842                print .verboseLineSeparator 
     
    825947                        first = false 
    826948                else 
    827949                    print '        Example: -[spec["name"]]:[spec["example"]]' 
     950            if spec.containsKey('eg') #Verbatim example line 
     951                print '        e.g. [spec["eg"]]' 
    828952 
    829953    def doAbout 
    830954        # CC: multiline string