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 |
| 517 | print 'Building standard library' |
| 518 | dllInfo = FileInfo('Cobra.Lang.dll') |
| 519 | if dllInfo.exists |
| 520 | if v |
| 521 | print 'Renaming Cobra.Lang.dll to Cobra.Lang-previous.dll' |
| 522 | prevInfo = FileInfo('Cobra.Lang-previous.dll') |
| 523 | if prevInfo.exists |
| 524 | prevInfo.delete |
| 525 | FileInfo('Cobra.Lang.dll').moveTo('Cobra.Lang-previous.dll') |
| 526 | _options['target'] = 'lib' |
| 527 | _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 |
| 528 | _options['embed-run-time'] = true # because the runtime is what we're building! |
| 529 | .doCompile(List<of String>(), true, false, false) |
| 530 | |
| 531 | |
| 532 | ## Testify |
| 533 | |
| 534 | def doTestify(paths as List<of String>) |
| 535 | """ |
| 536 | Used internally for testing cobra during development. |
| 537 | Why not just 'test'? because that is reserved for regular developers to run true unit tests. |
| 538 | """ |
| 539 | TestifyRunner(_startTime, this, paths).run |
| 540 | |
| 541 | |
| 542 | |
| 543 | class ArgParser |
| 544 | """ |
| 545 | Class for handling parsing of cmdline string arguments into a set of recognised |
| 546 | Options and a list of paths. |
| 547 | Provides documentation display (help, about) printed output as well |
| 548 | """ |
| 549 | var _self as ArgParser? is shared |
| 550 | var _versionString as String |
| 551 | var _verbosity = 0 |
| 552 | var _willTimeIt = false |
| 553 | var _optsOnly = false |
| 554 | |
| 555 | get versionString from var |
| 556 | get verbosity from var |
| 557 | get willTimeIt from var |
| 558 | |
| 559 | var _optionSpecs as List<of Dictionary<of String, Object>> |
| 560 | |
| 561 | var _specDict as Dictionary<of String, Dictionary<of String, Object>> |
| 562 | # ^ will contain keys for all spec names and their synonyms |
| 563 | var _synToName as Dictionary<of String, String> |
| 564 | # ^ maps synonyms to their full names |
| 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 | |
| 592 | def parseToOptions(args as IList<of String>) as Options is shared |
| 593 | """ |
| 594 | Reuse ArgParser to parse some additional string option args. Files are not allowed |
| 595 | Can be used only after ArgParser has already been constructed providing OptionSpecs |
| 596 | Return new set of Options from given args list |
| 597 | """ |
| 598 | assert _self , 'ArgParser.init must have been called sometime before using ArgParse.parseToOptions()' |
| 599 | opts = Options() |
| 600 | paths = List<of String>() |
| 601 | #TODO: mark which opts as unusable in this context and filter out |
| 602 | _self._optsOnly = true |
| 603 | _self.parseArgs(args, out opts, out paths) |
| 604 | if _self.verbosity or opts.getDefault('verbosity', 0) to int |
| 605 | print 'parseToOptions Option Dictionary:' |
| 606 | opts.print |
| 607 | return opts |
| 608 | |
| 609 | def parseArgs(args as IList<of String>, options as out Options?, paths as out List<of String>?) |
| 610 | """ |
| 611 | Parse command line arguments: options and files. |
| 612 | The `args` should include only the arguments and not the executable/program name. |
| 613 | """ |
| 614 | _optsOnly = false |
| 615 | _parseArgs(args, out options, out paths) |
| 616 | |
| 617 | def _parseArgs(args as IList<of String>, options as out Options?, paths as out List<of String>?) |
| 618 | ensure |
| 619 | options |
| 620 | paths |
| 621 | body |
| 622 | optionPrefix = '-' |
| 623 | if not args.count |
| 624 | options = Options() |
| 625 | options.add('help', true) |
| 626 | paths = List<of String>() |
| 627 | return |
| 628 | |
| 629 | # set up initial valueDict |
| 630 | valueDict = Dictionary<of String, Object>() |
| 631 | didSpecify = Dictionary<of String, bool>() # CC: could just be a Set |
| 632 | if Utils.isDevMachine |
| 633 | valueDict['reveal-internal-exceptions'] = true # this is a specially computed default, but can still be overridden on the command line |
| 634 | |
| 635 | valueStr = 'no-value' |
| 636 | fileList = List<of String>() |
| 637 | mainOptions = List<of String>() |
| 638 | for arg in args |
| 639 | if not arg.trim.length |
| 640 | continue |
| 641 | |
| 642 | isOption = arg.startsWith(optionPrefix) |
| 643 | if isOption |
| 644 | name = _getOptionParts(arg, optionPrefix, out valueStr) |
| 645 | spec = _specDict[name] |
| 646 | |
| 647 | if _isAccumulatorOpt(spec) |
| 648 | _accumulateOptValue(name, valueStr, valueDict, didSpecify) |
| 649 | continue |
| 650 | |
| 651 | value = _processToValue(name, valueStr, spec, mainOptions) |
| 652 | if value is nil |
| 653 | _error('Cannot process value "[valueStr]" for option "[name]".') |
| 654 | valueDict[name] = value to ! |
| 655 | didSpecify[name] = true |
| 656 | else # not isOption |
| 657 | if _optsOnly |
| 658 | _error("Filenames are not allowed here, All the args provided must be '-' prefixed options") |
| 659 | if arg.startsWith('/') |
| 660 | errHint = ' If you meant to specify an option, use dash (-) instead of slash (/).' |
| 661 | _processAsFile(arg, fileList, errHint) |
| 662 | |
| 663 | _handleSynonyms(valueDict) |
| 664 | _addInDefaults(valueDict) |
| 665 | |
| 666 | # TODO: make the option names case-insensitive |
| 667 | |
| 668 | if mainOptions.count > 1 |
| 669 | _error('Cannot have these main options at the same time: [Utils.join(", ", mainOptions)]') |
| 670 | |
| 671 | _unpackOptions(valueDict, fileList) |
| 672 | |
| 673 | # set the out parameters |
| 674 | options = Options(valueDict) |
| 675 | options.setSpecified(didSpecify) |
| 676 | paths = fileList |
| 677 | _computeArgImplications(options to !) |
| 678 | |
| 679 | def _getOptionParts(arg as String, |
| 680 | optionPrefix as String, |
| 681 | valueStr as out String) as String |
| 682 | arg = .fixOptionArg(arg, optionPrefix) |
| 683 | # name, valueStr = .splitOpt(arg) |
| 684 | l = .splitOpt(arg) |
| 685 | name = l[0] |
| 686 | valueStr = l[1] |
| 687 | name = .validateOptionName(name) |
| 688 | return name |
| 689 | |
| 690 | |
| 691 | def fixOptionArg(arg as String, optionPrefix as String) as String |
| 692 | """ |
| 693 | Strip any leading switch chars ing optionPrefix and adjust remaining option |
| 694 | """ |
| 695 | while arg.startsWith(optionPrefix) |
| 696 | arg = arg[1:] |
| 697 | return arg |
| 698 | |
| 699 | def splitOpt(arg as String) as IList<of String> |
| 700 | """ |
| 701 | Split option into name and valueStr |
| 702 | """ |
| 703 | valuePrefix = c':' |
| 704 | parts = arg.split(@[valuePrefix], 2) |
| 705 | if parts.length == 1 |
| 706 | name = parts[0] |
| 707 | if name.endsWith('+') |
| 708 | name = name[:-1] |
| 709 | valueStr = 'on' |
| 710 | else if name.endsWith('-') |
| 711 | name = name[:-1] |
| 712 | valueStr = 'off' |
| 713 | else |
| 714 | valueStr = 'on' |
732 | | p = c.runProcess |
733 | | if _verbosity >= 1 |
734 | | print 'Running: [p.startInfo.fileName] [p.startInfo.arguments]' |
735 | | print .verboseLineSeparator |
736 | | p.startInfo.useShellExecute = false |
737 | | p.start |
738 | | p.waitForExit # TODO: is this necessary? |
| 716 | assert parts.length == 2 |
| 717 | name = parts[0] |
| 718 | valueStr = parts[1] |
| 719 | assert parts, name.length |
| 720 | assert valueStr.length |
| 721 | return [name, valueStr] |
| 722 | |
| 723 | def isOptionSpecRestrictionViolated(optionSpec as Dictionary<of String, Object>) as bool |
| 724 | """ |
| 725 | Returns true if the option spec has a 'restriction' key and the check against that restriction is true. |
| 726 | """ |
| 727 | if optionSpec.containsKey('restriction') |
| 728 | branch optionSpec['restriction'] to String |
| 729 | on 'mono-only' |
| 730 | return not CobraCore.isRunningOnMono |
| 731 | return false |
| 732 | |
| 733 | def _initSynonyms |
| 734 | """ |
| 735 | Init supporting data structures for handling option synonyms |
| 736 | """ |
| 737 | for d in _optionSpecs |
| 738 | if .isOptionSpecRestrictionViolated(d) |
| 739 | continue |
| 740 | _specDict[d['name'] to String] = d |
| 741 | if d.containsKey('synonyms') |
| 742 | syns = d['synonyms'] to System.Collections.IList |
| 743 | for syn as String in syns |
| 744 | assert not _specDict.containsKey(syn) |
| 745 | _specDict[syn] = d |
| 746 | _synToName[syn] = d['name'] to String |
| 747 | _synList.add(syn) |
| 748 | if not d.containsKey('type') |
| 749 | d.add('type', 'string') |
| 750 | |
| 751 | def validateOptionName(name as String)as String |
| 752 | """ |
| 753 | Ensure the given name exists as an option name or synonym mappable |
| 754 | to an option name; return the canonical name for the option/synonym |
| 755 | """ |
| 756 | name = Utils.getSS(_synToName to passthrough, name, name) to ! |
| 757 | if not _specDict.containsKey(name) |
| 758 | msg = 'No such option "[name]".' |
| 759 | if name.contains('=') |
| 760 | msg += ' If you meant to specify an option value, use colon (:) instead of equals (=).' |
| 761 | _error(msg) |
| 762 | return name |
| 763 | |
| 764 | def _isAccumulatorOpt(spec as Dictionary<of String,Object>) as bool |
| 765 | return Utils.getSB(spec to passthrough, 'isAccumulator', false) |
| 766 | |
| 767 | def _accumulateOptValue(name as String, valueStr as String, _ |
| 768 | valueDict as Dictionary<of String, Object>, _ |
| 769 | didSpecify as Dictionary<of String, bool>) |
| 770 | # accumulators are always treated as strings. TODO: assert that |
| 771 | if valueDict.containsKey(name) |
| 772 | (valueDict[name] to System.Collections.IList).add(valueStr to passthrough) |
| 773 | else |
| 774 | valueDict[name] = [valueStr] |
| 775 | didSpecify[name] = true |
| 776 | |
| 777 | |
| 778 | def _fixupDebug(valueStr as String) as String? |
| 779 | if valueStr == 'pdbonly' or valueStr == 'full' |
| 780 | return valueStr |
| 781 | |
| 782 | value as String? = 'no-value' |
| 783 | try |
| 784 | b = _boolForString(valueStr) |
| 785 | catch FormatException |
| 786 | value = nil |
| 787 | success |
| 788 | value = if(b, '+', '-') |
| 789 | return value |
| 790 | |
| 791 | def _processToValue(name as String, |
| 792 | valueStr as String, |
| 793 | spec as Dictionary<of String, Object>, |
| 794 | mainOptions as List<of String>) as dynamic? |
| 795 | value as dynamic? = 'no-value' |
| 796 | |
| 797 | if name == 'debug' # special case |
| 798 | return _fixupDebug(valueStr) |
| 799 | |
| 800 | t = spec['type'] to String |
| 801 | branch t |
| 802 | on 'main' |
| 803 | mainOptions.add(name) |
| 804 | value = true |
| 805 | else |
| 806 | value = _interpretValue(valueStr, spec) |
| 807 | return value |
| 808 | |
| 810 | def _handleSynonyms(valueDict as Dictionary<of String, Object>) |
| 811 | for syn in _synList |
| 812 | if valueDict.containsKey(syn) |
| 813 | valueDict[_synToName[syn]] = valueDict[syn] |
| 814 | valueDict.remove(syn) |
| 815 | |
| 816 | def _addInDefaults(valueDict as Dictionary<of String, Object>) |
| 817 | for d in _optionSpecs |
| 818 | defaultName = d['name'] to String |
| 819 | if not valueDict.containsKey(defaultName) and d.containsKey('default') |
| 820 | defaultValue = _interpretValue(d['default'] to String, d) to ! |
| 821 | if .verbosity |
| 822 | print 'Setting option "[defaultName]" to default value [defaultValue].' |
| 823 | valueDict[defaultName] = defaultValue |
| 824 | |
| 825 | def _unpackOptions(valueDict as Dictionary<of String, Object>, fileList as List<of String>) |
| 826 | """ |
| 827 | Unpack certain options (verbosity and timeit) into specific class fields, |
| 828 | do files option processing |
| 829 | """ |
| 830 | if valueDict.containsKey('verbosity') |
| 831 | _verbosity = valueDict['verbosity'] to int |
| 832 | |
| 833 | if not valueDict.containsKey('timeit') and valueDict.containsKey('testify') |
| 834 | valueDict['timeit'] = true |
| 835 | if valueDict.containsKey('timeit') |
| 836 | _willTimeIt = valueDict['timeit'] to bool |
| 837 | |
| 838 | if valueDict.containsKey('files') |
| 839 | fileNamesList = valueDict['files'] to System.Collections.IList |
| 840 | _processFilesFile( fileNamesList, fileList) |
| 841 | |
| 842 | def _processFilesFile(fileNamesList as IList, fileList as List<of String>) |
| 843 | """ |
| 844 | Treat entries in fileNamesList as names of files containing filenames to compile, |
| 845 | validate names and add into fileList |
| 846 | """ |
| 847 | for fileName as String in fileNamesList |
| 848 | try |
| 849 | for line in File.readAllLines(fileName) |
| 850 | line = line.trim |
| 851 | if line.length==0 or line.startsWith('#') |
| 852 | continue |
| 853 | _processAsFile(line, fileList, nil) |
| 854 | catch IOException |
| 855 | _error('Cannot open file "[fileName]".') |
| 856 | |
| 857 | def _processAsFile(arg as String, fileList as List<of String>, errHint as String?) |
| 858 | """ |
| 859 | Validate arg as filename and on success add into fileList |
| 860 | """ |
| 861 | if File.exists(arg) |
| 862 | fileList.add(arg) |
| 863 | else if File.exists(arg+'.cobra') |
| 864 | fileList.add(arg+'.cobra') |
| 865 | else if Directory.exists(arg) |
| 866 | fileList.add(arg) |
| 867 | else |
| 868 | msg = 'Cannot find "[arg]" as a file.' |
| 869 | if errHint |
| 870 | msg += errHint |
| 871 | _error(msg) |
| 872 | |
| 873 | |
| 874 | def _computeArgImplications(options as Options) |
| 875 | if options.getDefault('target', '') == 'lib' and not options.isSpecified('compile') |
| 876 | options['compile'] = true |
| 877 | if options.getDefault('debug', '') not in ['', '0', '-'] and not options.isSpecified('debugging-tips') |
| 878 | options['debugging-tips'] = false |
| 879 | if options.boolValue('turbo') |
| 880 | options['contracts'] = 'none' |
| 881 | options['include-asserts'] = false |
| 882 | options['include-nil-checks'] = false |
| 883 | options['include-tests'] = false |
| 884 | options['optimize'] = true |
| 885 | |
| 886 | def _interpretValue(valueStr as String, spec as Dictionary<of String, Object>) as dynamic? |
| 887 | value as dynamic? |
| 888 | branch spec['type'] to String |
| 889 | on 'main' |
| 890 | throw InvalidOperationException('This method does not handle the main type.') |
| 891 | on 'bool' |
| 892 | try |
| 893 | value = _boolForString(valueStr) |
| 894 | catch FormatException |
| 895 | value = nil # cannot process |
| 896 | on 'int' |
| 897 | if valueStr == 'on' # set internally when there is no value |
| 898 | valueStr = '1' |
| 899 | try |
| 900 | value = int.parse(valueStr) |
| 901 | catch FormatException |
| 902 | value = nil |
| 903 | catch OverflowException |
| 904 | value = nil |
| 905 | # TODO: check min and max |
| 906 | on 'string' |
| 907 | value = valueStr |
| 908 | on 'menu' |
| 909 | if valueStr.length==0 |
| 910 | value = nil |
| 911 | if not (spec['choices'] to System.Collections.IList).contains(valueStr) |
| 912 | value = nil |
| 913 | else |
| 914 | value = valueStr |
| 915 | return value |
| 916 | |
| 917 | def _boolForString(s as String) as bool |
| 918 | if s.toLower in ['', '+', 'on', 'true', 't', 'yes', 'y', '1'] |
| 919 | return true |
| 920 | else if s.toLower in ['-', 'off', 'false', 'f', 'no', 'n', '0'] |
| 921 | return false |
| 922 | else |
| 923 | throw FormatException() |
| 924 | |
| 925 | def _calcWidth as int |
| 926 | leftMargin = 8 |
| 927 | try |
| 928 | consoleWidth = Console.windowWidth |
| 929 | catch IOException |
| 930 | # 2008-04-11, When redirecting output, MS .NET 2.0 throws IOException while Novell Mono 1.9 returns 0 |
| 931 | consoleWidth = 0 |
| 932 | if consoleWidth < 1 |
| 933 | try |
| 934 | consoleWidth = Console.bufferWidth |
| 935 | catch IOException |
| 936 | consoleWidth = 0 |
| 937 | totalWidth = consoleWidth - 2 |
| 938 | if totalWidth < 0, totalWidth = 0 |
| 939 | if totalWidth == 0, totalWidth = 78 |
| 940 | else if totalWidth < 20, totalWidth = 20 |
| 941 | assert totalWidth > 0 |
| 942 | width = totalWidth - leftMargin |
| 943 | assert width > 0 |
| 944 | return width |
| 945 | |