| 392 | | def isOptionSpecRestrictionViolated(optionSpec as Dictionary<of String, Object>) as bool |
| 393 | | """ |
| 394 | | Returns true if the option spec has a 'restriction' key and the check against that restriction is true. |
| 395 | | """ |
| 396 | | if optionSpec.containsKey('restriction') |
| 397 | | branch optionSpec['restriction'] to String |
| 398 | | on 'mono-only' |
| 399 | | return not CobraCore.isRunningOnMono |
| 400 | | return false |
| 401 | | |
| 402 | | def parseArgs(args as IList<of String>, options as out Options?, paths as out List<of String>?) |
| 403 | | """ |
| 404 | | Parse command line arguments. |
| 405 | | The `args` should include only the arguments and not the executable/program name. |
| 406 | | """ |
| 407 | | ensure |
| 408 | | options |
| 409 | | paths |
| 410 | | body |
| 411 | | optionPrefix = '-' |
| 412 | | valuePrefix = c':' |
| 413 | | if not args.count |
| 414 | | options = Options() |
| 415 | | options.add('help', true) |
| 416 | | paths = List<of String>() |
| 417 | | return |
| 418 | | |
| 419 | | specDict = Dictionary<of String, Dictionary<of String, Object>>() |
| 420 | | # ^ will contain keys for all spec names and their synonyms |
| 421 | | synToName = Dictionary<of String, String>() |
| 422 | | # ^ maps synonyms to their full names |
| 423 | | synList = List<of String>() |
| 424 | | for d in _optionSpecs |
| 425 | | if .isOptionSpecRestrictionViolated(d) |
| 426 | | continue |
| 427 | | specDict[d['name'] to String] = d |
| 428 | | if d.containsKey('synonyms') |
| 429 | | syns = d['synonyms'] to System.Collections.IList |
| 430 | | for syn as String in syns |
| 431 | | assert not specDict.containsKey(syn) |
| 432 | | specDict[syn] = d |
| 433 | | synToName[syn] = d['name'] to String |
| 434 | | synList.add(syn) |
| 435 | | if not d.containsKey('type') |
| 436 | | d.add('type', 'string') |
| 437 | | |
| 438 | | # set up initial valueDict |
| 439 | | valueDict = Dictionary<of String, Object>() |
| 440 | | if Utils.isDevMachine |
| 441 | | valueDict['reveal-internal-exceptions'] = true # this is a specially computed default, but can still be overridden on the command line |
| 442 | | |
| 443 | | fileList = List<of String>() |
| 444 | | value = 'no-value' to dynamic |
| 445 | | mainOptions = List<of String>() |
| 446 | | didSpecify = Dictionary<of String, bool>() # CC: could just be a Set |
| 447 | | for arg in args |
| 448 | | if arg.trim.length == 0 |
| 449 | | continue |
| 450 | | if arg.startsWith(optionPrefix) |
| 451 | | isOption = true |
| 452 | | while arg.startsWith(optionPrefix) |
| 453 | | arg = arg[1:] |
| 454 | | else |
| 455 | | isOption = false |
| 456 | | if isOption |
| 457 | | parts = arg.split(@[valuePrefix], 2) |
| 458 | | if parts.length == 1 |
| 459 | | name = parts[0] |
| 460 | | if name.endsWith('+') |
| 461 | | name = name[:-1] |
| 462 | | valueStr = 'on' |
| 463 | | else if name.endsWith('-') |
| 464 | | name = name[:-1] |
| 465 | | valueStr = 'off' |
| 466 | | else |
| 467 | | valueStr = 'on' |
| 468 | | else |
| 469 | | assert parts.length == 2 |
| 470 | | name = parts[0] |
| 471 | | valueStr = parts[1] |
| 472 | | assert name.length, parts |
| 473 | | name = Utils.getSS(synToName to passthrough, name, name) to ! |
| 474 | | if not specDict.containsKey(name) |
| 475 | | msg = 'No such option "[name]".' |
| 476 | | if name.contains('=') |
| 477 | | msg += ' If you meant to specify an option value, use colon (:) instead of equals (=).' |
| 478 | | .error(msg) |
| 479 | | spec = specDict[name] |
| 480 | | if Utils.getSB(spec to passthrough, 'isAccumulator', false) |
| 481 | | # accumulators are always treated as strings. TODO: assert that |
| 482 | | if valueDict.containsKey(name) |
| 483 | | (valueDict[name] to System.Collections.IList).add(valueStr to passthrough) |
| 484 | | else |
| 485 | | valueDict[name] = [valueStr] |
| 486 | | didSpecify[name] = true |
| 487 | | else |
| 488 | | cannotProcess = false |
| 489 | | if name=='debug' |
| 490 | | # special case |
| 491 | | if valueStr=='pdbonly' or valueStr=='full' |
| 492 | | value = valueStr |
| 493 | | else |
| 494 | | try |
| 495 | | value = .boolForString(valueStr) |
| 496 | | catch FormatException |
| 497 | | cannotProcess = true |
| 498 | | success |
| 499 | | value = if(value, '+', '-') |
| 500 | | else |
| 501 | | if spec['type'] == 'main' |
| 502 | | mainOptions.add(name) |
| 503 | | value = true |
| 504 | | else |
| 505 | | possible = .interpretValue(valueStr, spec) |
| 506 | | if possible is not nil |
| 507 | | value = possible |
| 508 | | else |
| 509 | | cannotProcess = true |
| 510 | | if cannotProcess |
| 511 | | .error('Cannot process value "[valueStr]" for option "[name]".') |
| 512 | | valueDict[name] = value |
| 513 | | didSpecify[name] = true |
| 514 | | else # not isOption |
| 515 | | if File.exists(arg) |
| 516 | | fileList.add(arg) |
| 517 | | else if File.exists(arg+'.cobra') |
| 518 | | fileList.add(arg+'.cobra') |
| 519 | | else if Directory.exists(arg) |
| 520 | | fileList.add(arg) |
| 521 | | else |
| 522 | | msg = 'Cannot find "[arg]" as a file.' |
| 523 | | if arg.startsWith('/') |
| 524 | | msg += ' If you meant to specify an option, use dash (-) instead of slash (/).' |
| 525 | | .error(msg) |
| 526 | | |
| 527 | | # handle synonyms |
| 528 | | for syn in synList |
| 529 | | if valueDict.containsKey(syn) |
| 530 | | valueDict[synToName[syn]] = valueDict[syn] |
| 531 | | valueDict.remove(syn) |
| 532 | | |
| 533 | | # add in defaults |
| 534 | | for d in _optionSpecs |
| 535 | | defaultName = d['name'] to String |
| 536 | | if not valueDict.containsKey(defaultName) and d.containsKey('default') |
| 537 | | defaultValue = .interpretValue(d['default'] to String, d) to ! |
| 538 | | if .verbosity |
| 539 | | print 'Setting option "[defaultName]" to default value [defaultValue].' |
| 540 | | valueDict[defaultName] = defaultValue |
| 541 | | |
| 542 | | # TODO: make the option names case-insensitive |
| 543 | | |
| 544 | | # check for more than one main option |
| 545 | | if mainOptions.count > 1 |
| 546 | | .error('Cannot have these main options at the same time: [Utils.join(", ", mainOptions)]') |
| 547 | | |
| 548 | | # unpack certain options into specific class fields |
| 549 | | if valueDict.containsKey('verbosity') |
| 550 | | _verbosity = valueDict['verbosity'] to int |
| 551 | | if not valueDict.containsKey('timeit') and valueDict.containsKey('testify') |
| 552 | | valueDict['timeit'] = true |
| 553 | | if valueDict.containsKey('timeit') |
| 554 | | CobraMain.willTimeIt = valueDict['timeit'] to bool |
| 555 | | if valueDict.containsKey('files') |
| 556 | | for fileName as String in valueDict['files'] to System.Collections.IList |
| 557 | | try |
| 558 | | for line in File.readAllLines(fileName) |
| 559 | | line = line.trim |
| 560 | | if line.length==0 or line.startsWith('#') |
| 561 | | continue |
| 562 | | # TODO: dup'ed above |
| 563 | | arg = line |
| 564 | | if File.exists(arg) |
| 565 | | fileList.add(arg) |
| 566 | | else if File.exists(arg+'.cobra') |
| 567 | | fileList.add(arg+'.cobra') |
| 568 | | else if Directory.exists(arg) |
| 569 | | fileList.add(arg) |
| 570 | | else |
| 571 | | msg = 'Cannot find "[arg]" as a file.' |
| 572 | | #if arg.startsWith('/') |
| 573 | | # msg += ' If you meant to specify an option, use dash (-) instead of slash (/).' |
| 574 | | .error(msg) |
| 575 | | # end dup |
| 576 | | catch IOException |
| 577 | | .error('Cannot open file "[fileName]".') |
| 578 | | |
| 579 | | # set the out parameters |
| 580 | | options = Options(valueDict) |
| 581 | | for name in didSpecify.keys |
| 582 | | options.didSpecify(name) |
| 583 | | paths = fileList |
| 584 | | |
| 585 | | .computeArgImplications(options to !) |
| 586 | | |
| 587 | | |
| 588 | | def computeArgImplications(options as Options) |
| 589 | | if options.getDefault('target', '') == 'lib' and not options.isSpecified('compile') |
| 590 | | options['compile'] = true |
| 591 | | if options.getDefault('debug', '') not in ['', '0', '-'] and not options.isSpecified('debugging-tips') |
| 592 | | options['debugging-tips'] = false |
| 593 | | if options.boolValue('turbo') |
| 594 | | options['contracts'] = 'none' |
| 595 | | options['include-asserts'] = false |
| 596 | | options['include-nil-checks'] = false |
| 597 | | options['include-tests'] = false |
| 598 | | options['optimize'] = true |
| 599 | | |
| 600 | | def interpretValue(valueStr as String, spec as Dictionary<of String, Object>) as dynamic? |
| 601 | | value as dynamic? |
| 602 | | branch spec['type'] to String |
| 603 | | on 'main' |
| 604 | | throw InvalidOperationException('This method does not handle the main type.') |
| 605 | | on 'bool' |
| 606 | | try |
| 607 | | value = .boolForString(valueStr) |
| 608 | | catch FormatException |
| 609 | | cannotProcess = true |
| 610 | | on 'int' |
| 611 | | if valueStr == 'on' # set internally when there is no value |
| 612 | | valueStr = '1' |
| 613 | | try |
| 614 | | value = int.parse(valueStr) |
| 615 | | catch FormatException |
| 616 | | cannotProcess = true |
| 617 | | catch OverflowException |
| 618 | | cannotProcess = true |
| 619 | | # TODO: check min and max |
| 620 | | on 'string' |
| 621 | | value = valueStr |
| 622 | | on 'menu' |
| 623 | | if valueStr.length==0 |
| 624 | | cannotProcess = true |
| 625 | | if not (spec['choices'] to System.Collections.IList).contains(valueStr) |
| 626 | | cannotProcess = true |
| 627 | | else |
| 628 | | value = valueStr |
| 629 | | r = if(cannotProcess, nil, value) |
| 630 | | return r |
| 631 | | |
| | 484 | return |
| | 485 | if .options.boolValue('compile') # maybe changed by compiler directive |
| | 486 | return |
| | 487 | |
| | 488 | p = c.runProcess |
| | 489 | if _verbosity >= 1 |
| | 490 | print 'Running: [p.startInfo.fileName] [p.startInfo.arguments]' |
| | 491 | print .verboseLineSeparator |
| | 492 | p.startInfo.useShellExecute = false |
| | 493 | p.start |
| | 494 | p.waitForExit # TODO: is this necessary? |
| | 495 | |
| | 496 | def doHelp |
| | 497 | _argParser.doHelp # CC: multiline string |
| | 498 | |
| | 499 | def doAbout |
| | 500 | _argParser.doAbout |
| | 501 | |
| | 502 | def doVersion |
| | 503 | print .versionString |
| | 504 | |
| | 505 | def error(msg as String) |
| | 506 | if msg.length |
| | 507 | print 'cobra: error: [msg]' |
| | 508 | print 'Run Cobra without options to get full usage information.' |
| | 509 | Environment.exit(1) |
| | 510 | |
| | 511 | |
| | 512 | ## Build Standard Library |
| | 513 | |
| | 514 | def doBuildStandardLibrary |
| | 515 | v = .verbosity |
| | 516 | if v, print 'Building standard library' |
| | 517 | dllInfo = FileInfo('Cobra.Lang.dll') |
| | 518 | if dllInfo.exists |
| | 519 | if v, print 'Renaming Cobra.Lang.dll to Cobra.Lang-previous.dll' |
| | 520 | prevInfo = FileInfo('Cobra.Lang-previous.dll') |
| | 521 | if prevInfo.exists, prevInfo.delete |
| | 522 | FileInfo('Cobra.Lang.dll').moveTo('Cobra.Lang-previous.dll') |
| | 523 | _options['target'] = 'lib' |
| | 524 | _options['include-tests'] = false # TODO: including tests in a DLL tends to cause problems. it might be because tests are triggered by type initializers. this needs investigation |
| | 525 | _options['embed-run-time'] = true # because the runtime is what we're building! |
| | 526 | .doCompile(List<of String>(), true, false, false) |
| | 527 | |
| | 528 | |
| | 529 | ## Testify |
| | 530 | |
| | 531 | def doTestify(paths as List<of String>) |
| | 532 | """ |
| | 533 | Used internally for testing cobra during development. |
| | 534 | Why not just 'test'? because that is reserved for regular developers to run true unit tests. |
| | 535 | """ |
| | 536 | TestifyRunner(_startTime, this, paths).run |
| | 537 | |
| | 538 | |
| | 539 | |
| | 540 | class ArgParser |
| | 541 | """ |
| | 542 | Class for handling parsing of cmdline string arguments into a set of recognised |
| | 543 | Options and a list of paths. |
| | 544 | Provides documentation display (help, about) printed output as well |
| | 545 | """ |
| | 546 | |
| | 547 | var _self as ArgParser? is shared |
| | 548 | var _versionString as String |
| | 549 | var _verbosity = 0 |
| | 550 | var _willTimeIt = false |
| | 551 | var _optsOnly = false |
| | 552 | |
| | 553 | get versionString from var |
| | 554 | get verbosity from var |
| | 555 | get willTimeIt from var |
| | 556 | |
| | 557 | var _optionSpecs as List<of Dictionary<of String, Object>> |
| | 558 | |
| | 559 | var _specDict as Dictionary<of String, Dictionary<of String, Object>> |
| | 560 | # will contain keys for all spec names and their synonyms |
| | 561 | |
| | 562 | var _synToName as Dictionary<of String, String> |
| | 563 | # maps synonyms to their full names |
| | 564 | |
| | 565 | var _synList as List<of String> |
| | 566 | |
| | 567 | def init(version as String, rawOptionSpecs) |
| | 568 | _versionString = version |
| | 569 | # prep the option specs |
| | 570 | _optionSpecs = List<of Dictionary<of String, Object>>() |
| | 571 | for specObj in rawOptionSpecs |
| | 572 | # since some _optionSpecs are Dictionary<of String, Object> and others are |
| | 573 | # Dictionary<of String, String> then _optionSpecs ends up being |
| | 574 | # Dictionary<of String, Object> |
| | 575 | |
| | 576 | if specObj inherits Dictionary<of String, Object> |
| | 577 | d = specObj |
| | 578 | else if specObj inherits Dictionary<of String, String> |
| | 579 | d = Dictionary<of String, Object>() |
| | 580 | for key in specObj.keys |
| | 581 | d[key] = specObj[key] |
| | 582 | else |
| | 583 | throw FallThroughException(specObj.getType) |
| | 584 | _optionSpecs.add(d) |
| | 585 | |
| | 586 | _specDict = Dictionary<of String, Dictionary<of String, Object>>() |
| | 587 | _synToName = Dictionary<of String, String>() |
| | 588 | _synList = List<of String>() |
| | 589 | _initSynonyms |
| | 590 | _self = this |
| | 591 | |