Ticket #12: parseArgRefactor.patch
File parseArgRefactor.patch, 17.0 KB (added by hopscc, 17 years ago) |
---|
commandline parseArgs refactor |
-
CommandLine.cobra
195 195 }, 196 196 { 197 197 'name': 'reference', 198 'synonyms': ['r'], 198 'synonyms': ['r'], # TODO: lose 'r' ->'Ref', 'R', something else 199 199 'isAccumulator': true, 200 200 'description': 'Add a DLL reference.', 201 201 'args': 'Some.dll', … … 214 214 # 'args': ':Qualified.Type.Name', 215 215 # }, 216 216 { 217 'name': 'run', 217 'name': 'run', # TODO: Synonym 'r' after change 'reference' switch 218 218 'description': 'Runs the Cobra program. This is the default behavior if specify any Cobra source files.', 219 219 'type': 'main', 220 220 }, 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 # 221 239 { 222 240 'name': 'sharp-compiler', 223 241 'description': 'Specify the path to the backend C# compiler.', … … 281 299 282 300 var _options = Options() 283 301 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 284 304 var _htmlWriter as HtmlWriter? 285 305 286 306 var _compiler as Compiler? … … 381 401 if _htmlWriter 382 402 _htmlWriter.writeHtml('</body></html>[_htmlWriter.newLine]') 383 403 384 def isOptionSpecRestrictionViolated(optionSpec as Dictionary<of String, Object>) as bool385 """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 String390 on 'mono-only'391 return not CobraCore.isRunningOnMono392 return false393 394 404 def parseArgs(args as IList<of String>, options as out Options?, paths as out List<of String>?) 395 405 """ 396 406 Parse command line arguments. … … 399 409 ensure 400 410 options 401 411 paths 402 body 412 body # TODO: refactor to something more modular (i.e much < 200 lines long) 403 413 optionPrefix = '-' 404 valuePrefix = c':'405 414 if not args.count 406 415 options = Options() 407 416 options.add('help', true) … … 413 422 synToName = Dictionary<of String, String>() 414 423 # ^ maps synonyms to their full names 415 424 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) 429 426 430 427 # set up initial valueDict 431 428 valueDict = Dictionary<of String, Object>() 432 429 if Utils.isDevMachine 433 430 valueDict['reveal-internal-exceptions'] = true # this is a specially computed default, but can still be overridden on the command line 434 431 432 value = 'no-value' to dynamic 433 valueStr = 'no-value' 435 434 fileList = List<of String>() 436 value = 'no-value' to dynamic437 435 mainOptions = List<of String>() 438 436 didSpecify = Dictionary<of String, bool>() # CC: could just be a Set 437 argn=0 439 438 for arg in args 439 argn += 1 # next arg after current 440 440 if arg.trim.length == 0 441 441 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) 448 444 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) 471 446 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 506 461 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) 518 465 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) 524 468 525 # add in defaults526 for d in _optionSpecs527 defaultName = d['name'] to String528 if not valueDict.containsKey(defaultName) and d.containsKey('default')529 defaultValue = .interpretValue(d['default'] to String, d) to !530 if .verbosity531 print 'Setting option "[defaultName]" to default value [defaultValue].'532 valueDict[defaultName] = defaultValue533 534 469 # TODO: make the option names case-insensitive 535 470 536 471 # check for more than one main option 537 472 if mainOptions.count > 1 538 473 .error('Cannot have these main options at the same time: [Utils.join(", ", mainOptions)]') 539 474 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) 570 476 571 477 # set the out parameters 572 478 options = Options(valueDict) … … 576 482 577 483 .computeArgImplications(options to !) 578 484 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 579 639 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 580 702 def computeArgImplications(options as Options) 581 703 if options.getDefault('target', '') == 'lib' and not options.isSpecified('compile') 582 704 options['compile'] = true … … 588 710 options['include-nil-checks'] = false 589 711 options['include-tests'] = false 590 712 options['optimize'] = true 591 713 592 714 def interpretValue(valueStr as String, spec as Dictionary<of String, Object>) as dynamic? 593 715 value as dynamic? 594 716 branch spec['type'] to String … … 714 836 if c.errors.count 715 837 print 'Not running due to errors above.' 716 838 else 717 p = c.runProcess 839 p = c.runProcess(_exeFileName, _runArgs) 718 840 if _verbosity >= 1 719 841 print 'Running: [p.startInfo.fileName] [p.startInfo.arguments]' 720 842 print .verboseLineSeparator … … 825 947 first = false 826 948 else 827 949 print ' Example: -[spec["name"]]:[spec["example"]]' 950 if spec.containsKey('eg') #Verbatim example line 951 print ' e.g. [spec["eg"]]' 828 952 829 953 def doAbout 830 954 # CC: multiline string