| 1 | """ |
|---|
| 2 | Cobra Command Line Program (compiler and more) |
|---|
| 3 | """ |
|---|
| 4 | |
|---|
| 5 | use System.Diagnostics |
|---|
| 6 | use System.Reflection |
|---|
| 7 | use System.Text.RegularExpressions |
|---|
| 8 | |
|---|
| 9 | |
|---|
| 10 | class OptionSpec inherits Dictionary<of String, dynamic> |
|---|
| 11 | """ |
|---|
| 12 | Represents a specific, named command line option specification. |
|---|
| 13 | |
|---|
| 14 | In addition to being a dictionary containing the original raw specification key+value pairs, |
|---|
| 15 | several convenient properties and methods are provided such as .name, .type and .hasDefault. |
|---|
| 16 | """ |
|---|
| 17 | |
|---|
| 18 | var _isUnpacked = false |
|---|
| 19 | var _name = '' |
|---|
| 20 | var _type = 'unknown' |
|---|
| 21 | var _synonyms = List<of String>() |
|---|
| 22 | var _description = 'No description.' |
|---|
| 23 | var _isAccumulator = false |
|---|
| 24 | var _hasDefault = false |
|---|
| 25 | var _default = '' |
|---|
| 26 | var _choices = List<of String>() |
|---|
| 27 | |
|---|
| 28 | get isUnpacked from var |
|---|
| 29 | |
|---|
| 30 | get name as String |
|---|
| 31 | require .isUnpacked |
|---|
| 32 | return _name |
|---|
| 33 | |
|---|
| 34 | get isMain from var as bool |
|---|
| 35 | |
|---|
| 36 | get type as String |
|---|
| 37 | require .isUnpacked |
|---|
| 38 | ensure result in ['accumulator', 'args-list', 'bool', 'int', 'menu', 'set', 'string'] |
|---|
| 39 | return _type |
|---|
| 40 | |
|---|
| 41 | get synonyms from var |
|---|
| 42 | |
|---|
| 43 | get description from var |
|---|
| 44 | |
|---|
| 45 | get isAccumulator from var |
|---|
| 46 | |
|---|
| 47 | get hasDefault from var |
|---|
| 48 | |
|---|
| 49 | get default from var |
|---|
| 50 | """ |
|---|
| 51 | Check .hasDefault before using this property for anything useful. |
|---|
| 52 | """ |
|---|
| 53 | |
|---|
| 54 | get choices from var |
|---|
| 55 | """ |
|---|
| 56 | The choices for options that are type 'menu'. |
|---|
| 57 | """ |
|---|
| 58 | |
|---|
| 59 | def isOptionSpecRestrictionViolated as bool |
|---|
| 60 | """ |
|---|
| 61 | Returns true if the option spec has a 'restriction' key and the check against that restriction is true. |
|---|
| 62 | """ |
|---|
| 63 | if .containsKey('restriction') |
|---|
| 64 | branch this['restriction'] to String |
|---|
| 65 | on 'mono-only' |
|---|
| 66 | return not CobraCore.isRunningOnMono |
|---|
| 67 | return false |
|---|
| 68 | |
|---|
| 69 | def unpack |
|---|
| 70 | _name = this['name'] to String |
|---|
| 71 | try |
|---|
| 72 | _type = this['type'] to String |
|---|
| 73 | catch KeyNotFoundException |
|---|
| 74 | if .containsKey('isAccumulator') and this['isAccumulator'] to bool |
|---|
| 75 | this['type'] = _type = 'accumulator' |
|---|
| 76 | else |
|---|
| 77 | this['type'] = 'string' |
|---|
| 78 | if _type == 'main' |
|---|
| 79 | _isMain, _type = true, 'bool' |
|---|
| 80 | if .containsKey('is-main'), _isMain = this['is-main'] to bool |
|---|
| 81 | assert _type in ['accumulator', 'args-list', 'bool', 'int', 'menu', 'set', 'string'] |
|---|
| 82 | if .containsKey('synonyms') |
|---|
| 83 | for syn in this['synonyms'] |
|---|
| 84 | _synonyms.add(syn) |
|---|
| 85 | if .containsKey('description') |
|---|
| 86 | _description = this['description'] |
|---|
| 87 | if .containsKey('isAccumulator') |
|---|
| 88 | _isAccumulator = this['isAccumulator'] to bool |
|---|
| 89 | if .containsKey('default') |
|---|
| 90 | _default = this['default'] |
|---|
| 91 | _hasDefault = true |
|---|
| 92 | if .containsKey('choices') |
|---|
| 93 | for choice in this['choices'] |
|---|
| 94 | _choices.add(choice) |
|---|
| 95 | _unpackPlatforms |
|---|
| 96 | _isUnpacked = true |
|---|
| 97 | assert .type=='menu' implies .choices.count > 0 |
|---|
| 98 | assert .choices.count <> 0 implies .type in ['menu', 'set'] |
|---|
| 99 | |
|---|
| 100 | def _unpackPlatforms |
|---|
| 101 | if not .containsKey('platforms') |
|---|
| 102 | this['platforms'] = ['jvm', '.net', 'objc'] |
|---|
| 103 | for platform in this['platforms'] |
|---|
| 104 | assert platform in ['jvm', '.net', 'objc'] |
|---|
| 105 | this['platforms'] = Set<of String>(this['platforms'] to List<of String>) |
|---|
| 106 | |
|---|
| 107 | |
|---|
| 108 | class CommandLineOptionSpecs |
|---|
| 109 | """ |
|---|
| 110 | Returns the command line option specifications, via .specsList, as a List of OptionSpec. |
|---|
| 111 | Also, the raw option specifications are contained and maintained in this class. |
|---|
| 112 | """ |
|---|
| 113 | |
|---|
| 114 | var _specs = List<of OptionSpec>() |
|---|
| 115 | |
|---|
| 116 | cue init |
|---|
| 117 | base.init |
|---|
| 118 | for rawSpec in _rawCommandLineOptionSpecs |
|---|
| 119 | spec = OptionSpec() |
|---|
| 120 | if rawSpec inherits Dictionary<of String, Object> |
|---|
| 121 | for key in rawSpec.keys, spec[key] = rawSpec[key] |
|---|
| 122 | else if rawSpec inherits Dictionary<of String, String> |
|---|
| 123 | for key in rawSpec.keys, spec[key] = rawSpec[key] |
|---|
| 124 | else |
|---|
| 125 | throw FallThroughException(rawSpec.getType) |
|---|
| 126 | spec.unpack |
|---|
| 127 | _specs.add(spec) |
|---|
| 128 | |
|---|
| 129 | def specsList as List<of OptionSpec> |
|---|
| 130 | """ |
|---|
| 131 | Returns the option specs in the order they were declared. |
|---|
| 132 | """ |
|---|
| 133 | return _specs |
|---|
| 134 | |
|---|
| 135 | var _rawCommandLineOptionSpecs = [ |
|---|
| 136 | { |
|---|
| 137 | 'name': 'about', |
|---|
| 138 | 'description': 'Print the name, copyright, etc. but no usage.', |
|---|
| 139 | 'type': 'main', |
|---|
| 140 | }, |
|---|
| 141 | { |
|---|
| 142 | 'name': 'back-end', |
|---|
| 143 | 'description': 'Specify the back-end of the compiler, possibly different than the build platform. Used for cross-compilation.', |
|---|
| 144 | 'type': 'menu', |
|---|
| 145 | 'choices': ['none', 'clr', 'jvm', 'objc'], |
|---|
| 146 | 'args': 'none|clr|jvm|objc', |
|---|
| 147 | 'default': 'none', |
|---|
| 148 | }, |
|---|
| 149 | { |
|---|
| 150 | 'name': 'build-standard-library', |
|---|
| 151 | 'synonyms': ['bsl'], |
|---|
| 152 | 'description': 'Builds the standard library.', |
|---|
| 153 | 'type': 'main', |
|---|
| 154 | 'developer-only': true, |
|---|
| 155 | }, |
|---|
| 156 | { |
|---|
| 157 | 'name': 'compile', |
|---|
| 158 | 'synonyms': ['c'], |
|---|
| 159 | 'description': 'Compile the library (to DLL) or the program (to EXE) without running the code.', |
|---|
| 160 | 'type': 'main', |
|---|
| 161 | }, |
|---|
| 162 | { |
|---|
| 163 | 'name': 'compile-if-needed', |
|---|
| 164 | 'synonyms': ['cin'], |
|---|
| 165 | 'type': 'bool', |
|---|
| 166 | 'description': 'Compile if any source file is newer than the existing output file (or there is no output file). Does not consider changes in compiler flags; only source code.', |
|---|
| 167 | }, |
|---|
| 168 | { |
|---|
| 169 | 'name': 'color', |
|---|
| 170 | 'type': 'bool', |
|---|
| 171 | 'description': 'Colorizes the output of error messages and the messages "Compilation failed" and "Compilation succeeded" (as red, red and blue).', |
|---|
| 172 | }, |
|---|
| 173 | { |
|---|
| 174 | 'name': 'contracts', |
|---|
| 175 | 'description': 'Control treatment of code generation for contracts.', |
|---|
| 176 | 'type': 'menu', |
|---|
| 177 | 'choices': ['none', 'inline', 'methods'], |
|---|
| 178 | 'args': 'none|inline|methods', |
|---|
| 179 | 'default': 'inline', |
|---|
| 180 | }, |
|---|
| 181 | { |
|---|
| 182 | 'name': 'correct-source', |
|---|
| 183 | 'synonyms': ['cs'], |
|---|
| 184 | 'description': 'Rewrite source files with corrections, only when unambiguous. For example, "string" --> "String". If possible, you should set your editor to automatically reload files when using this option.', |
|---|
| 185 | 'type': 'set', |
|---|
| 186 | 'choices': ['none', 'bang-equals', 'case', 'all'], |
|---|
| 187 | 'args': 'none|case|all', |
|---|
| 188 | 'default': 'none', |
|---|
| 189 | }, |
|---|
| 190 | { |
|---|
| 191 | 'name': 'debug', |
|---|
| 192 | 'synonyms': ['d'], |
|---|
| 193 | 'type': 'string', |
|---|
| 194 | 'description': 'Turn on system debugging information. The value 1 implies full, which enables attaching a debugger to a running program. Turning on implies -debugging-tips:no.', |
|---|
| 195 | 'args': '0|1|pdbonly|full' |
|---|
| 196 | }, |
|---|
| 197 | { |
|---|
| 198 | 'name': 'debugging-tips', |
|---|
| 199 | 'description': 'Display debugging tips when an unhandled exception occurs. Overridden by -exception-report.', |
|---|
| 200 | 'type': 'bool', |
|---|
| 201 | 'default': 'yes', |
|---|
| 202 | }, |
|---|
| 203 | { |
|---|
| 204 | 'name': 'delay-sign', |
|---|
| 205 | 'type': 'bool', |
|---|
| 206 | 'description': 'Delay-sign the assembly using only the public portion of the strong name key.', |
|---|
| 207 | 'platforms': ['.net'], |
|---|
| 208 | }, |
|---|
| 209 | { |
|---|
| 210 | 'name': 'detailed-stack-trace', |
|---|
| 211 | 'synonyms': ['dst'], |
|---|
| 212 | 'type': 'bool', |
|---|
| 213 | 'description': 'Enable a detailed stack trace which gives great postmortem information for uncaught exceptions, but slows execution. Also, detects stack overflow which is needed for Mono but not .NET which already does this. Works in combination with -exception-report, or see ObjectExplorer-WinForms.cobra and its doc string for ideas on how to present this information graphically.', |
|---|
| 214 | }, |
|---|
| 215 | { |
|---|
| 216 | 'name': 'document', |
|---|
| 217 | 'synonyms': ['doc'], |
|---|
| 218 | 'type': 'main', |
|---|
| 219 | 'description': 'Generate HTML docs for the Cobra source.', |
|---|
| 220 | }, |
|---|
| 221 | { |
|---|
| 222 | 'name': 'document-library', |
|---|
| 223 | 'synonyms': ['doc-lib'], |
|---|
| 224 | 'is-main': true, |
|---|
| 225 | 'type': 'string', |
|---|
| 226 | 'description': 'Generate HTML docs for a library.', |
|---|
| 227 | }, |
|---|
| 228 | { |
|---|
| 229 | 'name': 'embed-run-time', |
|---|
| 230 | 'synonyms': ['ert'], |
|---|
| 231 | 'type': 'bool', |
|---|
| 232 | 'default': 'no', |
|---|
| 233 | 'description': 'Embed the Cobra run-time support code in the assembly so that no reference to an external Cobra.Lang.dll is required. Approximately 85KB - 130KB overhead depending on other options such as -include-asserts or -turbo.', |
|---|
| 234 | }, |
|---|
| 235 | { |
|---|
| 236 | 'name': 'exception-report', |
|---|
| 237 | 'synonyms': ['exc-rpt', 'er'], |
|---|
| 238 | 'type': 'bool', |
|---|
| 239 | 'description': 'Turn on an informative HTML report that will be generated if the program throws an uncaught exception. Also, see ObjectExplorer-WinForms.cobra and its doc string for an alternative, GUI approach.', |
|---|
| 240 | }, |
|---|
| 241 | { |
|---|
| 242 | 'name': 'files', |
|---|
| 243 | 'isAccumulator': true, |
|---|
| 244 | 'description': 'Specify the files for Cobra to process in a separate text file. One file per line; # comments and blank lines are ignored.', |
|---|
| 245 | 'args': 'filename', |
|---|
| 246 | }, |
|---|
| 247 | { |
|---|
| 248 | 'name': 'editor', |
|---|
| 249 | 'type': 'string', |
|---|
| 250 | 'description': 'Specify an editor and command line options to invoke if there is a compile-time error. Use underscore (_) for space in the specification. Can also set via COBRA_EDITOR environment variable (in which case spaces work fine).', |
|---|
| 251 | 'example': ['uedit32_FILE/LINE', 'mate_FILE_-l_LINE'], |
|---|
| 252 | 'args': 'editor_spec_with_FILE_LINE', |
|---|
| 253 | }, |
|---|
| 254 | { |
|---|
| 255 | 'name': 'extra-source', |
|---|
| 256 | 'type': 'string', |
|---|
| 257 | 'args': 'SOURCE', |
|---|
| 258 | 'description': 'Add extra source code to be compiled with the rest. Generally used only internally by the compiler.', |
|---|
| 259 | 'developer-only': true, |
|---|
| 260 | }, |
|---|
| 261 | { |
|---|
| 262 | 'name': 'help', |
|---|
| 263 | 'synonyms': ['h'], |
|---|
| 264 | 'type': 'main', |
|---|
| 265 | 'description': 'Display this help message.', |
|---|
| 266 | }, |
|---|
| 267 | { |
|---|
| 268 | 'name': 'highlight', |
|---|
| 269 | 'description': 'Write HTML versions of the source files with syntax highlighting.', |
|---|
| 270 | 'type': 'main', |
|---|
| 271 | }, |
|---|
| 272 | { |
|---|
| 273 | 'name': 'include-asserts', |
|---|
| 274 | 'type': 'bool', |
|---|
| 275 | 'default': 'yes', |
|---|
| 276 | 'description': 'Include assert statements in the program.', |
|---|
| 277 | }, |
|---|
| 278 | { |
|---|
| 279 | 'name': 'include-nil-checks', |
|---|
| 280 | 'type': 'bool', |
|---|
| 281 | 'default': 'yes', |
|---|
| 282 | 'description': 'Include checks on non-nilable class variables, method arguments and "to !" casts.', |
|---|
| 283 | }, |
|---|
| 284 | { |
|---|
| 285 | 'name': 'include-tests', |
|---|
| 286 | 'type': 'bool', |
|---|
| 287 | 'default': 'yes', |
|---|
| 288 | 'description': 'Includes unit tests for classes and members in the output assembly.', |
|---|
| 289 | }, |
|---|
| 290 | { |
|---|
| 291 | 'name': 'include-traces', |
|---|
| 292 | 'type': 'bool', |
|---|
| 293 | 'default': 'yes', |
|---|
| 294 | 'description': 'Includes trace statements in the output assembly.', |
|---|
| 295 | }, |
|---|
| 296 | { |
|---|
| 297 | 'name': 'keep-intermediate-files', |
|---|
| 298 | 'synonyms': ['kif'], |
|---|
| 299 | 'type': 'bool', |
|---|
| 300 | 'description': 'Keeps any intermediate files that Cobra generates, which are normally deleted. Example intermediate files are *.cobra.cs.', |
|---|
| 301 | }, |
|---|
| 302 | { |
|---|
| 303 | 'name': 'key-container', |
|---|
| 304 | 'type': 'string', |
|---|
| 305 | 'args': 'FILE', |
|---|
| 306 | 'description': 'Specify a strong name key container used to strongname the output assembly.', |
|---|
| 307 | 'platforms': ['.net'], |
|---|
| 308 | }, |
|---|
| 309 | { |
|---|
| 310 | 'name': 'key-file', |
|---|
| 311 | 'type': 'string', |
|---|
| 312 | 'args': 'FILE', |
|---|
| 313 | 'description': 'Specify a strong name key file used to sign the output assembly.', |
|---|
| 314 | 'platforms': ['.net'], |
|---|
| 315 | }, |
|---|
| 316 | { |
|---|
| 317 | 'name': 'legacy-one-default-initializer', |
|---|
| 318 | 'type': 'bool', |
|---|
| 319 | 'description': 'When a class does not provide initializers, Cobra will mirror each initializer in the base class. The old behavior was to provide one parameterless initializer. Use this option to switch to the old behavior.', |
|---|
| 320 | }, |
|---|
| 321 | { |
|---|
| 322 | 'name': 'library-directory', |
|---|
| 323 | 'synonyms': ['lib'], |
|---|
| 324 | 'type': 'string', |
|---|
| 325 | 'isAccumulator': true, |
|---|
| 326 | 'description': 'Specify additional directories to search in for references. Maps to -lib: on .NET and -classpath on JVM.', |
|---|
| 327 | 'args': 'PATH', |
|---|
| 328 | }, |
|---|
| 329 | { |
|---|
| 330 | 'name': 'main', |
|---|
| 331 | 'type': 'string', |
|---|
| 332 | 'description': 'Specify the type containing the "main" method, particularly when more than one type declaration has a "main" method.', |
|---|
| 333 | 'args': 'TYPENAME', |
|---|
| 334 | }, |
|---|
| 335 | { |
|---|
| 336 | 'name': 'namespace', |
|---|
| 337 | 'synonyms': ['name-space', 'ns'], |
|---|
| 338 | 'type': 'string', |
|---|
| 339 | 'description': 'Set the namespace for all Cobra source files as if each one started with "namespace <name>". Can be a qualified name.', |
|---|
| 340 | 'example': 'Foo.Bar', |
|---|
| 341 | }, |
|---|
| 342 | { |
|---|
| 343 | 'name': 'native-compiler', |
|---|
| 344 | 'synonyms': ['sharp-compiler'], |
|---|
| 345 | 'description': 'Specify the path to the back-end native compiler. This can be an executable (such as csc.exe or javac), a library (such as Cobra.Sharp.dll), "auto" or "provider".', |
|---|
| 346 | 'type': 'string', |
|---|
| 347 | 'args': 'file-system-path', |
|---|
| 348 | 'default': 'auto', |
|---|
| 349 | }, |
|---|
| 350 | { |
|---|
| 351 | 'name': 'number', |
|---|
| 352 | 'type': 'menu', |
|---|
| 353 | 'choices': ['decimal', 'float', 'float32', 'float64'], |
|---|
| 354 | 'args': 'decimal|float|float32|float64', |
|---|
| 355 | 'default': 'decimal', |
|---|
| 356 | 'description': "Set the real numeric type for both the 'number' type and fractional literals such as '1.0'.", |
|---|
| 357 | }, |
|---|
| 358 | { |
|---|
| 359 | 'name': 'optimize', |
|---|
| 360 | 'synonyms': ['o'], |
|---|
| 361 | 'type': 'bool', |
|---|
| 362 | 'description': 'Enable optimizations.', |
|---|
| 363 | }, |
|---|
| 364 | { |
|---|
| 365 | 'name': 'out', |
|---|
| 366 | 'type': 'string', |
|---|
| 367 | 'description': 'Specify output file name (default: base name of first file). -test overrides this with a temporary program that runs only the unit tests and is then removed afterwards.', |
|---|
| 368 | 'args': 'FILENAME', |
|---|
| 369 | }, |
|---|
| 370 | { |
|---|
| 371 | 'name': 'output-html', |
|---|
| 372 | 'type': 'bool', |
|---|
| 373 | 'description': "The command line's output will be in HTML.", |
|---|
| 374 | }, |
|---|
| 375 | { |
|---|
| 376 | 'name': 'pkg', |
|---|
| 377 | 'type': 'string', |
|---|
| 378 | 'isAccumulator': true, |
|---|
| 379 | 'description': 'References package via "pkg-config --libs". (Mono only.)', |
|---|
| 380 | 'args': 'NAME', |
|---|
| 381 | 'restriction': 'mono', |
|---|
| 382 | 'platforms': ['.net'], |
|---|
| 383 | }, |
|---|
| 384 | { |
|---|
| 385 | 'name': 'reference', |
|---|
| 386 | 'synonyms': ['ref'], |
|---|
| 387 | 'isAccumulator': true, |
|---|
| 388 | 'description': 'Add a DLL reference.', |
|---|
| 389 | 'args': 'Some.dll', |
|---|
| 390 | }, |
|---|
| 391 | { |
|---|
| 392 | 'name': 'reveal-internal-exceptions', |
|---|
| 393 | 'description': 'When true, uncaught exceptions from the Cobra compiler itself are not caught and wrapped in error messages. This is useful when developing on the Cobra compiler itself. Set to true if the environment variable COBRA_IS_DEV_MACHINE.', |
|---|
| 394 | 'type': 'bool', |
|---|
| 395 | 'developer-only': true, |
|---|
| 396 | }, |
|---|
| 397 | { |
|---|
| 398 | 'name': 'run', # this is a switch only |
|---|
| 399 | 'synonyms': ['r'], |
|---|
| 400 | 'description': 'Runs the Cobra program. This is the default behavior if specify any Cobra source files.', |
|---|
| 401 | 'type': 'main', |
|---|
| 402 | }, |
|---|
| 403 | { |
|---|
| 404 | 'name': 'run-args', |
|---|
| 405 | 'synonyms': ['-'], # '--' |
|---|
| 406 | 'description': 'Remaining args are to be passed to executable.', |
|---|
| 407 | 'eg': '-- arg1 arg2 arg3', |
|---|
| 408 | 'type': 'args-list', |
|---|
| 409 | }, |
|---|
| 410 | { |
|---|
| 411 | 'name': 'native-compiler-args', |
|---|
| 412 | 'synonyms': ['sharp-args'], |
|---|
| 413 | 'description': 'Pass additional arguments to the native back-end compiler (such as C# or Java).', |
|---|
| 414 | 'isAccumulator': true, |
|---|
| 415 | 'type': 'string', |
|---|
| 416 | 'args': '"arg1 arg2"', |
|---|
| 417 | }, |
|---|
| 418 | { |
|---|
| 419 | 'name': 'target', |
|---|
| 420 | 'synonyms': ['t'], |
|---|
| 421 | 'description': 'Build a specific target.', |
|---|
| 422 | 'type': 'menu', |
|---|
| 423 | 'choices': ['exe', 'winexe', 'lib', 'module'], |
|---|
| 424 | 'args': 'exe|winexe|lib|module', |
|---|
| 425 | 'platforms': ['.net'], |
|---|
| 426 | }, |
|---|
| 427 | { |
|---|
| 428 | 'name': 'test', |
|---|
| 429 | 'description': 'Run the unit tests in the code without running .main. Works for libraries too.', |
|---|
| 430 | 'type': 'main', |
|---|
| 431 | }, |
|---|
| 432 | { |
|---|
| 433 | 'name': 'test-runner', |
|---|
| 434 | 'type': 'string', |
|---|
| 435 | 'description': 'Specify the method to invoke to run the unit tests. The method must be "shared". Typically the method will make use of classes in Cobra.Lang.Test to set up and initiate the test run.', |
|---|
| 436 | 'default': 'Cobra.Lang.CobraCore.runAllTests', |
|---|
| 437 | 'args': 'QUALIFIED-METHOD-NAME|nil', |
|---|
| 438 | }, |
|---|
| 439 | { |
|---|
| 440 | 'name': 'testify', |
|---|
| 441 | 'description': '...', |
|---|
| 442 | 'type': 'main', |
|---|
| 443 | 'developer-only': true, |
|---|
| 444 | }, |
|---|
| 445 | { |
|---|
| 446 | 'name': 'testify-results', |
|---|
| 447 | 'description': 'The filename to write the testify results to. Progress is still written to console.', |
|---|
| 448 | 'type': 'string', |
|---|
| 449 | 'default': 'r-testify', |
|---|
| 450 | 'developer-only': true, |
|---|
| 451 | }, |
|---|
| 452 | { |
|---|
| 453 | 'name': 'testify-threads', |
|---|
| 454 | 'description': '...', |
|---|
| 455 | 'type': 'int', |
|---|
| 456 | 'developer-only': true, |
|---|
| 457 | }, |
|---|
| 458 | { |
|---|
| 459 | 'name': 'timeit', |
|---|
| 460 | 'description': 'Gives the total duration of running cobra (including the target program, if it is to be run). This is "wall time", not "cpu time".', |
|---|
| 461 | # although this option is implied by 'testify', the description does not say so, since 'testify' is a hidden option |
|---|
| 462 | 'type': 'bool', |
|---|
| 463 | }, |
|---|
| 464 | { |
|---|
| 465 | 'name': 'turbo', |
|---|
| 466 | 'description': 'Maximum run-time performance. This is a convenience for -contracts:none -include-asserts:no -include-nil-checks:no -include-tests:no -include-traces:no -optimize', |
|---|
| 467 | 'type': 'bool', |
|---|
| 468 | }, |
|---|
| 469 | { |
|---|
| 470 | 'name': 'verbosity', |
|---|
| 471 | 'synonyms': ['verbose', 'v'], |
|---|
| 472 | 'type': 'int', |
|---|
| 473 | 'min': 0, |
|---|
| 474 | 'max': 5, |
|---|
| 475 | 'args': 'N', |
|---|
| 476 | 'description': 'Enable extra output from Cobra. Mostly useful for debugging Cobra and reporting problems. Values 0 - 5. Warning: Level 5 causes megabytes of output.', |
|---|
| 477 | }, |
|---|
| 478 | { |
|---|
| 479 | 'name': 'verbosity-ref', |
|---|
| 480 | 'type': 'int', |
|---|
| 481 | 'min': 0, |
|---|
| 482 | 'max': 5, |
|---|
| 483 | 'args': 'N', |
|---|
| 484 | 'description': 'Enable extra output from Cobra regarding the resolution of references to libraries. Mostly useful for debugging Cobra and reporting problems. Values 0 - 5.', |
|---|
| 485 | }, |
|---|
| 486 | { |
|---|
| 487 | 'name': 'version', |
|---|
| 488 | 'description': 'Print just the version number.', # ([.versionString]).', |
|---|
| 489 | 'type': 'main', |
|---|
| 490 | }, |
|---|
| 491 | ] |
|---|
| 492 | |
|---|
| 493 | |
|---|
| 494 | class CommandLine |
|---|
| 495 | """ |
|---|
| 496 | The main options that control the command line's behavior are: |
|---|
| 497 | run |
|---|
| 498 | test |
|---|
| 499 | compile |
|---|
| 500 | testify |
|---|
| 501 | help |
|---|
| 502 | |
|---|
| 503 | "testify" is private to the implementor of Cobra. |
|---|
| 504 | |
|---|
| 505 | "run" is the default if none are specified and at least one path is provided. |
|---|
| 506 | |
|---|
| 507 | If no arguments are passed at all, "help" becomes the default. |
|---|
| 508 | |
|---|
| 509 | You need to put at least one dash in front of an option. Also, you can leave |
|---|
| 510 | out the ".cobra" extension if you like. For example: |
|---|
| 511 | |
|---|
| 512 | cobra -compile foo bar |
|---|
| 513 | |
|---|
| 514 | """ |
|---|
| 515 | |
|---|
| 516 | get versionString as String is shared |
|---|
| 517 | ensure result.count(c'.') >= 2 or result.startsWith('svn:') |
|---|
| 518 | |
|---|
| 519 | # Can't just take CobraCore.versionDescription as is, because that will be the one from Snapshot, |
|---|
| 520 | # not the current Source directory. And Snapshot can be a final release such as '0.7.4' for a |
|---|
| 521 | # period of time where this Cobra source represents an svn-post-RELEASE. |
|---|
| 522 | # Keep three components to the version number: X.Y.Z |
|---|
| 523 | |
|---|
| 524 | ver = 'svn:[CompileTimeInfo.subversionRevision] (post 0.8) / [CompileTimeInfo.date]' |
|---|
| 525 | # ver += ', informal release 2009-03-01' |
|---|
| 526 | return ver |
|---|
| 527 | |
|---|
| 528 | get platformString as String is shared |
|---|
| 529 | return .clrVersion + ' on ' + .opSysString |
|---|
| 530 | |
|---|
| 531 | get clrVersion as String is shared |
|---|
| 532 | """ Get runtime version, like '.NET CLR v4.0.30319' or 'Mono 2.6.7 CLR v2.0.50727'. """ |
|---|
| 533 | if CobraCore.isRunningOnMono |
|---|
| 534 | ver = 'Mono' |
|---|
| 535 | try |
|---|
| 536 | mvs = CobraCore.monoVersionString |
|---|
| 537 | if mvs and mvs.length > 0 |
|---|
| 538 | ver += ' ' + mvs.split(@[c' '], 2)[0] |
|---|
| 539 | catch |
|---|
| 540 | pass # if the above fails for any reason, that's okay |
|---|
| 541 | else |
|---|
| 542 | ver = '.NET' |
|---|
| 543 | ver += ' CLR ' + Assembly.getAssembly(Object).imageRuntimeVersion |
|---|
| 544 | return ver |
|---|
| 545 | |
|---|
| 546 | get opSysString as String is shared |
|---|
| 547 | name = '' |
|---|
| 548 | if File.exists('/System/Library/CoreServices/SystemVersion.plist') |
|---|
| 549 | # Mac |
|---|
| 550 | content = File.readAllText('/System/Library/CoreServices/SystemVersion.plist') |
|---|
| 551 | match = Regex.match(content, r'<string>(Mac[^<]+)') |
|---|
| 552 | if match.success |
|---|
| 553 | name += match.groups[1].toString |
|---|
| 554 | match = Regex.match(content, r'<string>(\d+\.[\d\.]+)') |
|---|
| 555 | if match.success |
|---|
| 556 | name += ' ' + match.groups[1].toString |
|---|
| 557 | else if File.exists('/etc/lsb-release') |
|---|
| 558 | # Ubuntu, ... |
|---|
| 559 | content = File.readAllText('/etc/lsb-release') |
|---|
| 560 | d = Dictionary<of String, String>() |
|---|
| 561 | for line in content.splitLines |
|---|
| 562 | line = line.trim |
|---|
| 563 | if line == '' or line.startsWith('#') or '=' not in line, continue |
|---|
| 564 | pair = line.split('=', 2) |
|---|
| 565 | d[pair[0].trim] = pair[1].trim |
|---|
| 566 | if d.containsKey('DISTRIB_DESCRIPTION'), name = d['DISTRIB_DESCRIPTION'].trim |
|---|
| 567 | if name == '' |
|---|
| 568 | try |
|---|
| 569 | name = d['DISTRIB_ID'] + ' ' + d['DISTRIB_RELEASE'] |
|---|
| 570 | catch |
|---|
| 571 | pass |
|---|
| 572 | if name.startsWith('"') and name.endsWith('"'), name = name[1:-1] |
|---|
| 573 | else if File.exists('/etc/arch-release') |
|---|
| 574 | # Arch Linux |
|---|
| 575 | name = 'Arch Linux ' + _getCommandOutput('/bin/uname', '-r').trim |
|---|
| 576 | pacman = _getCommandOutput('pacman', '--version') |
|---|
| 577 | match = Regex.match(pacman, r'[Pp]acman v?(\d+\.[\d\.]+)') |
|---|
| 578 | if match.success |
|---|
| 579 | name += ' (pacman ' + match.groups[1].toString.trim + ')' |
|---|
| 580 | else if File.exists('/etc/system-release') |
|---|
| 581 | # CentOS, Fedora, ... |
|---|
| 582 | name = File.readAllText('/etc/system-release') |
|---|
| 583 | else if File.exists('/etc/redhat-release') |
|---|
| 584 | # RedHat, ... |
|---|
| 585 | name = File.readAllText('/etc/redhat-release') |
|---|
| 586 | name = name.trim |
|---|
| 587 | if name == '' |
|---|
| 588 | # everyone else |
|---|
| 589 | name = Environment.osVersion.toString |
|---|
| 590 | return name |
|---|
| 591 | |
|---|
| 592 | def _getCommandOutput(command as String, args as String) as String is shared |
|---|
| 593 | p = System.Diagnostics.Process() |
|---|
| 594 | p.startInfo.fileName = command |
|---|
| 595 | p.startInfo.arguments = args |
|---|
| 596 | p.startInfo.useShellExecute = false |
|---|
| 597 | try |
|---|
| 598 | return CobraCore.runAndCaptureAllOutput(p).trim |
|---|
| 599 | catch |
|---|
| 600 | # specific to users of this method: return '' rather than error |
|---|
| 601 | return '' |
|---|
| 602 | |
|---|
| 603 | var _startTime as DateTime |
|---|
| 604 | var _verbosity = 0 |
|---|
| 605 | |
|---|
| 606 | var _options = OptionValues() |
|---|
| 607 | var _pathList as List<of String>? |
|---|
| 608 | var _htmlWriter as HtmlWriter? |
|---|
| 609 | |
|---|
| 610 | var _compiler as Compiler? |
|---|
| 611 | var _argParser as ArgParser |
|---|
| 612 | |
|---|
| 613 | cue init |
|---|
| 614 | base.init |
|---|
| 615 | _startTime = DateTime.now |
|---|
| 616 | _argParser = ArgParser(.versionString, nil) |
|---|
| 617 | |
|---|
| 618 | get compiler from var |
|---|
| 619 | |
|---|
| 620 | get options from var |
|---|
| 621 | |
|---|
| 622 | get argParser from var |
|---|
| 623 | |
|---|
| 624 | get verboseLineSeparator as String |
|---|
| 625 | try |
|---|
| 626 | w = Console.bufferWidth |
|---|
| 627 | catch IOException |
|---|
| 628 | w = 0 |
|---|
| 629 | if w < 20, w = 80 |
|---|
| 630 | w -= 1 |
|---|
| 631 | return String(c'-', w) |
|---|
| 632 | |
|---|
| 633 | get verbosity as int |
|---|
| 634 | return _verbosity |
|---|
| 635 | |
|---|
| 636 | def parseArgs(args as List<of String>) |
|---|
| 637 | .parseArgs(args, out _options, out _pathList) |
|---|
| 638 | |
|---|
| 639 | def parseArgs(args as IList<of String>, options as out OptionValues?, paths as out List<of String>?) |
|---|
| 640 | try |
|---|
| 641 | _argParser.parseArgs(args, out options, out paths) |
|---|
| 642 | catch ape as ArgParseException |
|---|
| 643 | options = nil |
|---|
| 644 | paths = nil |
|---|
| 645 | .error(ape.message) |
|---|
| 646 | _verbosity = _argParser.verbosity |
|---|
| 647 | CobraMain.willTimeIt = _argParser.willTimeIt |
|---|
| 648 | |
|---|
| 649 | def run |
|---|
| 650 | """ |
|---|
| 651 | Run the command line using the command line arguments. |
|---|
| 652 | """ |
|---|
| 653 | .run(CobraCore.commandLineArgs[1:]) |
|---|
| 654 | |
|---|
| 655 | def run(args as List<of String>) |
|---|
| 656 | """ |
|---|
| 657 | Run the command line using the given arguments. |
|---|
| 658 | The `args` should include only the arguments and not the executable/program name. |
|---|
| 659 | """ |
|---|
| 660 | if args.count == 0 |
|---|
| 661 | .doAbout |
|---|
| 662 | return |
|---|
| 663 | .parseArgs(args) |
|---|
| 664 | |
|---|
| 665 | if _options.boolValue('output-html') |
|---|
| 666 | _htmlWriter = HtmlWriter(Console.out) |
|---|
| 667 | dest = _htmlWriter to TextWriter |
|---|
| 668 | else |
|---|
| 669 | dest = Console.out |
|---|
| 670 | if _htmlWriter |
|---|
| 671 | stylePath = Path.combine(Path.getDirectoryName(CobraCore.exePath), 'styles-output-html.css') |
|---|
| 672 | _htmlWriter.writeHtml('<html><head><link href="file://[stylePath]" rel=stylesheet type="text/css"></head><body>[_htmlWriter.newLine]') |
|---|
| 673 | print to dest |
|---|
| 674 | paths = _pathList to ! |
|---|
| 675 | options = _options |
|---|
| 676 | if .verbosity > 0 |
|---|
| 677 | print 'Cobra Command Line [.versionString]' |
|---|
| 678 | print 'Copyright (C) 2003-[DateTime.now.year] by Cobra Language LLC.' |
|---|
| 679 | print |
|---|
| 680 | print 'OS Version: ', Environment.osVersion |
|---|
| 681 | print 'CLR Platform:', if(CobraCore.isRunningOnMono, 'Mono', '.NET') |
|---|
| 682 | print 'CLR Version: ', Environment.version |
|---|
| 683 | print 'Current Directory: [Environment.currentDirectory]' |
|---|
| 684 | print 'Current Exe: [CobraCore.exePath]' |
|---|
| 685 | print 'Option Dictionary:' |
|---|
| 686 | options.print |
|---|
| 687 | print 'Paths:' |
|---|
| 688 | for path in paths |
|---|
| 689 | print ' [path]' |
|---|
| 690 | if options.boolValue('testify') |
|---|
| 691 | .doTestify(paths) |
|---|
| 692 | else if options.boolValue('run') |
|---|
| 693 | .doRun(paths) |
|---|
| 694 | else if options.boolValue('test') |
|---|
| 695 | .doTest(paths) |
|---|
| 696 | else if options.boolValue('compile') |
|---|
| 697 | .doCompile(paths) |
|---|
| 698 | else if options.boolValue('highlight') |
|---|
| 699 | .doHighlight(paths) |
|---|
| 700 | else if options.boolValue('document') |
|---|
| 701 | .doDocument(paths) |
|---|
| 702 | else if options.isSpecified('document-library') |
|---|
| 703 | .doDocumentLibrary |
|---|
| 704 | else if options.boolValue('help') |
|---|
| 705 | .doHelp |
|---|
| 706 | else if options.boolValue('version') |
|---|
| 707 | .doVersion |
|---|
| 708 | else if options.boolValue('about') |
|---|
| 709 | .doAbout |
|---|
| 710 | else if options.boolValue('build-standard-library') |
|---|
| 711 | .doBuildStandardLibrary |
|---|
| 712 | else if not paths.count |
|---|
| 713 | .doHelp |
|---|
| 714 | else |
|---|
| 715 | .doRun(paths) |
|---|
| 716 | if _htmlWriter |
|---|
| 717 | _htmlWriter.writeHtml('</body></html>[_htmlWriter.newLine]') |
|---|
| 718 | |
|---|
| 719 | def doHighlight(paths as List<of String>) as Compiler |
|---|
| 720 | """ |
|---|
| 721 | Syntax highlighting via HTML generation. |
|---|
| 722 | """ |
|---|
| 723 | comp = .doCompile(paths, false, false, do(c as Compiler)=c.lastPhase inherits BindInterfacePhase) |
|---|
| 724 | Node.setCompiler(comp) |
|---|
| 725 | try |
|---|
| 726 | comp.highlightFiles |
|---|
| 727 | finally |
|---|
| 728 | Node.setCompiler(nil) |
|---|
| 729 | return comp |
|---|
| 730 | |
|---|
| 731 | def doCompile(paths as List<of String>) as Compiler |
|---|
| 732 | return .doCompile(paths, true, false, nil) |
|---|
| 733 | |
|---|
| 734 | def doCompile(paths as List<of String>, willPrintSuccessMsg as bool, |
|---|
| 735 | writeTestInvocation as bool, stopCompilation as Predicate<of Compiler>?) as Compiler |
|---|
| 736 | sep = Path.directorySeparatorChar.toString |
|---|
| 737 | oldPaths = for path in paths get path.replace('\\', sep).replace('/', sep) |
|---|
| 738 | paths.clear |
|---|
| 739 | for path in oldPaths |
|---|
| 740 | if File.exists(path) |
|---|
| 741 | paths.add(path) |
|---|
| 742 | else if Directory.exists(path) |
|---|
| 743 | .error('Cannot process directories in general ("[path]").') |
|---|
| 744 | else |
|---|
| 745 | .error('Cannot find file "[path]".') |
|---|
| 746 | if paths.count == 0 and not .options.buildStandardLibrary |
|---|
| 747 | .error('No files to process.') |
|---|
| 748 | _compiler = c = Compiler(.verbosity) |
|---|
| 749 | c.commandLineArgParser = _argParser |
|---|
| 750 | c.options = _options |
|---|
| 751 | c.willPrintSuccessMsg = willPrintSuccessMsg |
|---|
| 752 | c.htmlWriter = _htmlWriter |
|---|
| 753 | try |
|---|
| 754 | c.compileFilesNamed(paths, writeTestInvocation, stopCompilation) |
|---|
| 755 | if _verbosity <> c.verbosity, _verbosity = c.verbosity |
|---|
| 756 | catch StopCompilation |
|---|
| 757 | # Each phase of the compiler may throw an exception to stop compilation. |
|---|
| 758 | # Before doing so, it prints its errors. |
|---|
| 759 | assert c.errors.count>0 |
|---|
| 760 | if _options.containsKey('editor') |
|---|
| 761 | spec = _options['editor'] to String? |
|---|
| 762 | else |
|---|
| 763 | spec = Environment.getEnvironmentVariable('COBRA_EDITOR') |
|---|
| 764 | if spec and spec <> '' |
|---|
| 765 | if spec.indexOf('FILE')==-1 |
|---|
| 766 | .error('Missing FILE from editor spec.') |
|---|
| 767 | if spec.indexOf('LINE')==-1 |
|---|
| 768 | .error('Missing LINE from editor spec.') |
|---|
| 769 | i = spec.indexOf('_') |
|---|
| 770 | if i == -1 |
|---|
| 771 | i = spec.indexOf(' ') |
|---|
| 772 | if i == -1 |
|---|
| 773 | .error('Missing underscore or space from editor spec.') |
|---|
| 774 | exeName = spec.substring(0, i) |
|---|
| 775 | args = spec.substring(i+1) |
|---|
| 776 | for error in c.errors |
|---|
| 777 | if error.isError and error.hasSourceSite |
|---|
| 778 | if error.fileName.trim <> '' |
|---|
| 779 | # trace error.fileName, error.lineNum |
|---|
| 780 | args = args.replace('FILE', error.fileName) |
|---|
| 781 | args = args.replace('LINE', error.lineNum.toString) |
|---|
| 782 | p = System.Diagnostics.Process() |
|---|
| 783 | p.startInfo.fileName = exeName |
|---|
| 784 | p.startInfo.arguments = args |
|---|
| 785 | p.startInfo.useShellExecute = false |
|---|
| 786 | if _verbosity >= 3 |
|---|
| 787 | print 'Running: [p.startInfo.fileName] [p.startInfo.arguments]' |
|---|
| 788 | try |
|---|
| 789 | p.start |
|---|
| 790 | p.waitForExit # TODO: is this really needed? |
|---|
| 791 | catch exc as Exception |
|---|
| 792 | print 'Cannot invoke editor:' |
|---|
| 793 | print ' Command: [p.startInfo.fileName] [p.startInfo.arguments]' |
|---|
| 794 | print ' Exception: [exc]' |
|---|
| 795 | break |
|---|
| 796 | CobraMain.compiler = c |
|---|
| 797 | return c |
|---|
| 798 | |
|---|
| 799 | def doDocument(paths as List<of String>) as Compiler |
|---|
| 800 | comp = .doCompile(paths, false, false, do(c as Compiler)=c.lastPhase inherits BindInterfacePhase) |
|---|
| 801 | GenerateHtmlDocVisitor(do(module)=module inherits CobraModule).gen(comp) |
|---|
| 802 | return comp |
|---|
| 803 | |
|---|
| 804 | def doDocumentLibrary |
|---|
| 805 | docModules = Set<of Module>() |
|---|
| 806 | comp = Compiler(.verbosity) |
|---|
| 807 | comp.commandLineArgParser = _argParser |
|---|
| 808 | comp.options = .options |
|---|
| 809 | Node.setCompiler(comp) |
|---|
| 810 | try |
|---|
| 811 | comp.initBackEnd |
|---|
| 812 | reference = .options['document-library'] to String # to-do: pick out type name. to-do: support multiple references |
|---|
| 813 | reference = comp.backEnd.fixLibExtension(reference) |
|---|
| 814 | .options['reference'] = [reference] |
|---|
| 815 | phaseClasses = [ |
|---|
| 816 | BindRunTimeLibraryPhase, |
|---|
| 817 | ReadLibrariesPhase, |
|---|
| 818 | BindUsePhase, |
|---|
| 819 | BindInheritancePhase, |
|---|
| 820 | BindInterfacePhase, |
|---|
| 821 | ComputeMatchingBaseMembersPhase, |
|---|
| 822 | ] |
|---|
| 823 | phases = for phaseClass in phaseClasses get phaseClass(comp) to Phase |
|---|
| 824 | hasErrors = false |
|---|
| 825 | for phase in phases |
|---|
| 826 | if not hasErrors or phase.willRunWithErrors |
|---|
| 827 | if comp.runPhase(phase), hasErrors = true |
|---|
| 828 | # running a phase unsets the compiler, so set it again: |
|---|
| 829 | Node.setCompiler(comp) |
|---|
| 830 | comp.printMessages |
|---|
| 831 | if hasErrors, Environment.exit(1) |
|---|
| 832 | for module in comp.modules.clone |
|---|
| 833 | if module.fileName.endsWith(reference) |
|---|
| 834 | _prepIfNeeded(module) |
|---|
| 835 | docModules.add(module) |
|---|
| 836 | GenerateHtmlDocVisitor(do(m)=docModules.contains(m to Module)).gen(comp) # CC: shouldn't need typecast |
|---|
| 837 | finally |
|---|
| 838 | Node.setCompiler(nil) |
|---|
| 839 | |
|---|
| 840 | def _prepIfNeeded(node) |
|---|
| 841 | if node inherits AssemblyModule |
|---|
| 842 | _prepIfNeeded(node.topNameSpace to passthrough) |
|---|
| 843 | else if node inherits NameSpace |
|---|
| 844 | for decl in node.declsInOrder |
|---|
| 845 | _prepIfNeeded(decl to passthrough) |
|---|
| 846 | else if node inherits Box |
|---|
| 847 | node.prepIfNeeded |
|---|
| 848 | else if node implements INameSpaceMember |
|---|
| 849 | # ex: Enum |
|---|
| 850 | pass |
|---|
| 851 | else |
|---|
| 852 | throw FallThroughException(node) |
|---|
| 853 | |
|---|
| 854 | def doTest(paths as List<of String>) |
|---|
| 855 | if paths.count == 0 |
|---|
| 856 | .error('You must specify one or more Cobra files to run unit tests for.') |
|---|
| 857 | c = .doCompile(paths, false, true, nil) |
|---|
| 858 | if c.errors.count |
|---|
| 859 | print 'Not running tests due to errors above.' |
|---|
| 860 | return |
|---|
| 861 | testInvoker = c.modules.last |
|---|
| 862 | assert testInvoker inherits NativeModule |
|---|
| 863 | assert testInvoker.fileName.startsWith('test-') |
|---|
| 864 | File.delete(testInvoker.fileName) |
|---|
| 865 | try |
|---|
| 866 | p = c.runProcess |
|---|
| 867 | if _verbosity >= 1 |
|---|
| 868 | print 'Running: [p.startInfo.fileName] [p.startInfo.arguments]' |
|---|
| 869 | print .verboseLineSeparator |
|---|
| 870 | p.startInfo.useShellExecute = false |
|---|
| 871 | p.start |
|---|
| 872 | p.waitForExit # TODO: is this necessary? |
|---|
| 873 | catch |
|---|
| 874 | try, File.delete(c.fullExeFileName) |
|---|
| 875 | catch IOException, pass |
|---|
| 876 | catch UnauthorizedAccessException, pass |
|---|
| 877 | throw |
|---|
| 878 | success |
|---|
| 879 | try |
|---|
| 880 | File.delete(c.fullExeFileName) |
|---|
| 881 | catch IOException |
|---|
| 882 | print 'warning: Cannot remove "[c.fullExeFileName]"' |
|---|
| 883 | catch UnauthorizedAccessException |
|---|
| 884 | print 'warning: Cannot remove "[c.fullExeFileName]"' |
|---|
| 885 | |
|---|
| 886 | def doRun(paths as List<of String>) |
|---|
| 887 | c = .doCompile(paths, false, false, nil) |
|---|
| 888 | if c.errors.count |
|---|
| 889 | print 'Not running due to errors above.' |
|---|
| 890 | return |
|---|
| 891 | if .options.boolValue('compile') # maybe changed by compiler directive |
|---|
| 892 | return |
|---|
| 893 | |
|---|
| 894 | sw = System.Diagnostics.Stopwatch() |
|---|
| 895 | sw.start |
|---|
| 896 | |
|---|
| 897 | exeFileName as String? = nil |
|---|
| 898 | runArgs = .options.getStringList('run-args') |
|---|
| 899 | # TODO: what's this? |
|---|
| 900 | # exeArgs = .options.getDefaultLOStr('exe-args') |
|---|
| 901 | # if exeArgs.count |
|---|
| 902 | # exeFileName = exeArgs[0] |
|---|
| 903 | # runArgs = exeArgs[1:] |
|---|
| 904 | p = c.runProcess(exeFileName, runArgs) |
|---|
| 905 | if _verbosity >= 1 |
|---|
| 906 | print 'Running: [p.startInfo.fileName] [p.startInfo.arguments]' |
|---|
| 907 | print .verboseLineSeparator |
|---|
| 908 | p.startInfo.useShellExecute = false |
|---|
| 909 | try |
|---|
| 910 | p.start |
|---|
| 911 | p.waitForExit # TODO: is this necessary? |
|---|
| 912 | catch exc as Exception |
|---|
| 913 | print 'ERROR: Compilation succeeded, but cannot run "[p.startInfo.fileName]" because: [exc.typeOf.name]: [exc.message]' |
|---|
| 914 | print 'You may be able to launch the executable program directly from the command line.' |
|---|
| 915 | if 'elevation' in exc.message |
|---|
| 916 | print 'If you are on Windows Vista, using an admin or power user account may avoid this problem.' |
|---|
| 917 | Environment.exit(1) |
|---|
| 918 | sw.stop |
|---|
| 919 | CobraMain.runTime = sw.elapsed |
|---|
| 920 | |
|---|
| 921 | def doHelp |
|---|
| 922 | .doAbout |
|---|
| 923 | print '' |
|---|
| 924 | print 'Usage:' |
|---|
| 925 | print '' |
|---|
| 926 | print ' cobra <options> <filename>' |
|---|
| 927 | print ' * run filename' |
|---|
| 928 | print ' * compile if needed' |
|---|
| 929 | print ' * .cobra extension is optional' |
|---|
| 930 | print '' |
|---|
| 931 | print ' cobra <options> <command> <path(s)>' |
|---|
| 932 | print ' * commands that operate on path(s) are:' |
|---|
| 933 | print ' -compile .... Compile only. Also, -c' |
|---|
| 934 | print ' -run ........ Run the program (compile if necessary). Also -r (Default)' |
|---|
| 935 | print ' -test ....... Run the unit tests of a library.' |
|---|
| 936 | print ' -document ... Document the program (partial compilation). Also, -doc' |
|---|
| 937 | print ' -highlight .. Syntax highlight the program in HTML.' |
|---|
| 938 | print '' |
|---|
| 939 | print ' cobra <options> <command>' |
|---|
| 940 | print ' * standalone commands are:' |
|---|
| 941 | print ' -help ...... Print full help information.' |
|---|
| 942 | print ' -about ..... Print name, copyright, etc. no usage.' |
|---|
| 943 | print ' -version ... Print just the version number ([.versionString]).' |
|---|
| 944 | print '' |
|---|
| 945 | print ' <options> can be:' |
|---|
| 946 | |
|---|
| 947 | # print options from their specs (but not the main ones which are covered above) |
|---|
| 948 | leftMarginStr = ' ' |
|---|
| 949 | width = _calcWidth() |
|---|
| 950 | for spec in CommandLineOptionSpecs().specsList |
|---|
| 951 | if spec.isMain, continue |
|---|
| 952 | if spec.containsKey('developer-only') and spec['developer-only'] to bool and not Utils.isDevMachine |
|---|
| 953 | continue |
|---|
| 954 | if spec.isOptionSpecRestrictionViolated |
|---|
| 955 | continue |
|---|
| 956 | print |
|---|
| 957 | print ' -[spec["name"]]' stop |
|---|
| 958 | if spec.containsKey('args') |
|---|
| 959 | if spec.hasDefault |
|---|
| 960 | lbracket = r'[' |
|---|
| 961 | print '[lbracket]:[spec["args"]]]' stop |
|---|
| 962 | else |
|---|
| 963 | print ':[spec["args"]]' stop |
|---|
| 964 | else if spec.type == 'bool' |
|---|
| 965 | print r'[:no|yes]' stop |
|---|
| 966 | if spec.hasDefault |
|---|
| 967 | print ' default is [spec.default]' stop |
|---|
| 968 | print |
|---|
| 969 | if spec.synonyms.count |
|---|
| 970 | print ' ' stop |
|---|
| 971 | sep = '' |
|---|
| 972 | for syn in spec.synonyms |
|---|
| 973 | print '[sep]-[syn]' stop |
|---|
| 974 | sep = ', ' |
|---|
| 975 | print |
|---|
| 976 | s = spec.description |
|---|
| 977 | while s.length |
|---|
| 978 | if s.length < width |
|---|
| 979 | print '[leftMarginStr][s]' |
|---|
| 980 | s = '' |
|---|
| 981 | else |
|---|
| 982 | # TODO: bug in here for narrow widths. try "width = 20" to reproduce |
|---|
| 983 | j = width + 1 |
|---|
| 984 | if j >= s.length, j = s.length - 1 |
|---|
| 985 | while j > 0 and s[j] <> ' ', j -= 1 |
|---|
| 986 | if j |
|---|
| 987 | sub = s.substring(0, j) |
|---|
| 988 | s = if(s.length, s.substring(j+1), '') |
|---|
| 989 | print '[leftMarginStr][sub]' |
|---|
| 990 | if spec.containsKey('example') |
|---|
| 991 | if spec['example'] inherits System.Collections.IList |
|---|
| 992 | first = true |
|---|
| 993 | for example in spec['example'] to System.Collections.IList |
|---|
| 994 | if first, print '[leftMarginStr]Examples: ' stop |
|---|
| 995 | else, print '[leftMarginStr] ' stop |
|---|
| 996 | print '-[spec["name"]]:[example]' |
|---|
| 997 | first = false |
|---|
| 998 | else |
|---|
| 999 | print '[leftMarginStr]Example: -[spec["name"]]:[spec["example"]]' |
|---|
| 1000 | if spec.containsKey('eg') # verbatim example line |
|---|
| 1001 | print '[leftMarginStr]e.g. [spec["eg"]]' |
|---|
| 1002 | |
|---|
| 1003 | def doAbout |
|---|
| 1004 | print |
|---|
| 1005 | print 'The Cobra Programming Language [.versionString]' |
|---|
| 1006 | print 'on [.platformString]' |
|---|
| 1007 | print 'at [Assembly.getEntryAssembly.location]' |
|---|
| 1008 | print |
|---|
| 1009 | print 'Copyright (C) 2003-[DateTime.now.year] by Cobra Language LLC. All Rights Reserved.' |
|---|
| 1010 | print '' |
|---|
| 1011 | print 'On the web: http://cobra-language.com/' |
|---|
| 1012 | print 'Source: http://cobra-language.com/source' |
|---|
| 1013 | print 'Support: http://cobra-language.com/support' |
|---|
| 1014 | print 'License: http://www.opensource.org/licenses/mit-license.php' |
|---|
| 1015 | print |
|---|
| 1016 | print 'Usage: cobra -h' |
|---|
| 1017 | |
|---|
| 1018 | def _calcWidth as int |
|---|
| 1019 | leftMargin = 8 |
|---|
| 1020 | try |
|---|
| 1021 | consoleWidth = Console.windowWidth |
|---|
| 1022 | catch IOException |
|---|
| 1023 | # 2008-04-11, When redirecting output, MS .NET 2.0 throws IOException while Novell Mono 1.9 returns 0 |
|---|
| 1024 | consoleWidth = 0 |
|---|
| 1025 | if consoleWidth < 1 |
|---|
| 1026 | try |
|---|
| 1027 | consoleWidth = Console.bufferWidth |
|---|
| 1028 | catch IOException |
|---|
| 1029 | consoleWidth = 0 |
|---|
| 1030 | totalWidth = consoleWidth - 2 |
|---|
| 1031 | if totalWidth < 0, totalWidth = 0 |
|---|
| 1032 | if totalWidth == 0, totalWidth = 78 |
|---|
| 1033 | else if totalWidth < 20, totalWidth = 20 |
|---|
| 1034 | assert totalWidth > 0 |
|---|
| 1035 | width = totalWidth - leftMargin |
|---|
| 1036 | assert width > 0 |
|---|
| 1037 | return width |
|---|
| 1038 | |
|---|
| 1039 | def doVersion |
|---|
| 1040 | print 'Cobra [.versionString] on [.platformString]' |
|---|
| 1041 | |
|---|
| 1042 | def error(msg as String) |
|---|
| 1043 | if msg.length |
|---|
| 1044 | print 'cobra: error: [msg]' |
|---|
| 1045 | print 'Run Cobra without options to get full usage information.' |
|---|
| 1046 | Environment.exit(1) |
|---|
| 1047 | |
|---|
| 1048 | |
|---|
| 1049 | ## Build Standard Library |
|---|
| 1050 | |
|---|
| 1051 | def doBuildStandardLibrary |
|---|
| 1052 | v = .verbosity |
|---|
| 1053 | if v, print 'Building standard library' |
|---|
| 1054 | dllInfo = FileInfo('Cobra.Lang.dll') |
|---|
| 1055 | if dllInfo.exists |
|---|
| 1056 | prevName = 'Cobra.Lang-previous.dll' |
|---|
| 1057 | if v, print 'Renaming Cobra.Lang.dll to [prevName]' |
|---|
| 1058 | prevInfo = FileInfo(prevName) |
|---|
| 1059 | try |
|---|
| 1060 | if prevInfo.exists, prevInfo.delete |
|---|
| 1061 | catch UnauthorizedAccessException |
|---|
| 1062 | print 'warning: Cannot delete [prevName]' |
|---|
| 1063 | success |
|---|
| 1064 | try |
|---|
| 1065 | FileInfo('Cobra.Lang.dll').moveTo(prevName) |
|---|
| 1066 | catch UnauthorizedAccessException |
|---|
| 1067 | print 'warning: Cannot move Cobra.Lang.dll to [prevName]' |
|---|
| 1068 | _options['target'] = 'lib' |
|---|
| 1069 | _options['include-tests'] = false |
|---|
| 1070 | _options['embed-run-time'] = true # because the runtime is what we're building! |
|---|
| 1071 | |
|---|
| 1072 | # embed the version |
|---|
| 1073 | reMatch = Regex(r'^\d+\.\d+\.\d+\b').match(.versionString) |
|---|
| 1074 | if reMatch.success |
|---|
| 1075 | version = reMatch.value to ! |
|---|
| 1076 | else |
|---|
| 1077 | reMatch = Regex(r'^svn:(\d+)\b').match(.versionString) |
|---|
| 1078 | if reMatch.success |
|---|
| 1079 | version = reMatch.groups[1].value to ! |
|---|
| 1080 | else |
|---|
| 1081 | print 'warning: Cannot extract version number from version string: [.versionString]' |
|---|
| 1082 | version = '999' |
|---|
| 1083 | version = '0.0.' + version |
|---|
| 1084 | assert version.count(c'.') == 2 # ex: '0.8.0' |
|---|
| 1085 | if ' ' in .versionString or 'post' in .versionString # ex: '0.8.0 post' |
|---|
| 1086 | # 'post' versions have a fourth version component of 1, as opposed to 0 |
|---|
| 1087 | version += '.1' |
|---|
| 1088 | assert version.count(c'.') == 3 |
|---|
| 1089 | _options.addExtraUse('use System.Reflection') |
|---|
| 1090 | _options.addExtraSource("assembly\n\thas AssemblyVersion('[version]')\n") |
|---|
| 1091 | .doCompile(List<of String>(), true, false, nil) |
|---|
| 1092 | |
|---|
| 1093 | |
|---|
| 1094 | ## Testify |
|---|
| 1095 | |
|---|
| 1096 | def doTestify(paths as List<of String>) |
|---|
| 1097 | """ |
|---|
| 1098 | Used internally for testing cobra during development. |
|---|
| 1099 | Why not just 'test'? because that is reserved for regular developers to run true unit tests. |
|---|
| 1100 | """ |
|---|
| 1101 | TestifyRunner(_startTime, this, paths).run |
|---|
| 1102 | |
|---|
| 1103 | |
|---|
| 1104 | |
|---|
| 1105 | class ArgParseException |
|---|
| 1106 | inherits Exception |
|---|
| 1107 | |
|---|
| 1108 | cue init(msg as String?) |
|---|
| 1109 | base.init(msg) |
|---|
| 1110 | |
|---|
| 1111 | |
|---|
| 1112 | class ArgParser |
|---|
| 1113 | """ |
|---|
| 1114 | Parse command line arguments into a dictionary of recognized OptionValues and a list of paths. |
|---|
| 1115 | """ |
|---|
| 1116 | |
|---|
| 1117 | var _versionString as String |
|---|
| 1118 | var _verbosity = 0 |
|---|
| 1119 | var _willTimeIt = false |
|---|
| 1120 | var _optsOnly = false |
|---|
| 1121 | |
|---|
| 1122 | get versionString from var |
|---|
| 1123 | get verbosity from var |
|---|
| 1124 | get willTimeIt from var |
|---|
| 1125 | |
|---|
| 1126 | var _optionSpecs as List<of OptionSpec> |
|---|
| 1127 | |
|---|
| 1128 | var _specDict as Dictionary<of String, OptionSpec> |
|---|
| 1129 | # will contain keys for all spec names and their synonyms |
|---|
| 1130 | |
|---|
| 1131 | var _synToName as Dictionary<of String, String> |
|---|
| 1132 | # maps synonyms to their full names |
|---|
| 1133 | |
|---|
| 1134 | var _synList as List<of String> |
|---|
| 1135 | |
|---|
| 1136 | cue init(version as String, optionSpecs as List<of OptionSpec>?) |
|---|
| 1137 | base.init |
|---|
| 1138 | _versionString = version |
|---|
| 1139 | # prep the option specs |
|---|
| 1140 | if optionSpecs |
|---|
| 1141 | _optionSpecs = List<of OptionSpec>(optionSpecs) |
|---|
| 1142 | else |
|---|
| 1143 | _optionSpecs = CommandLineOptionSpecs().specsList |
|---|
| 1144 | _specDict = Dictionary<of String, OptionSpec>() |
|---|
| 1145 | _synToName = Dictionary<of String, String>() |
|---|
| 1146 | _synList = List<of String>() |
|---|
| 1147 | _initSynonyms |
|---|
| 1148 | |
|---|
| 1149 | def parseToOptions(args as IList<of String>) as OptionValues |
|---|
| 1150 | """ |
|---|
| 1151 | Reuse ArgParser to parse some additional string option args. Files are not allowed. |
|---|
| 1152 | Return new set of Options from given args list |
|---|
| 1153 | """ |
|---|
| 1154 | opts = OptionValues() |
|---|
| 1155 | paths = List<of String>() |
|---|
| 1156 | # TODO: mark which opts as unusable in this context and filter out |
|---|
| 1157 | _optsOnly = true |
|---|
| 1158 | .parseArgs(args, out opts, out paths) |
|---|
| 1159 | if .verbosity or opts.getDefault('verbosity', 0) to int |
|---|
| 1160 | print 'parseToOptions Option Dictionary:' |
|---|
| 1161 | opts.print |
|---|
| 1162 | return opts |
|---|
| 1163 | |
|---|
| 1164 | def parseArgs(args as IList<of String>, options as out OptionValues?, paths as out List<of String>?) |
|---|
| 1165 | """ |
|---|
| 1166 | Parse command line arguments: options and files. |
|---|
| 1167 | The `args` should include only the arguments and not the executable/program name. |
|---|
| 1168 | """ |
|---|
| 1169 | _optsOnly = false |
|---|
| 1170 | _parseArgs(args, out options, out paths) |
|---|
| 1171 | |
|---|
| 1172 | def _parseArgs(args as IList<of String>, options as out OptionValues?, paths as out List<of String>?) |
|---|
| 1173 | ensure |
|---|
| 1174 | options |
|---|
| 1175 | paths |
|---|
| 1176 | body |
|---|
| 1177 | optionPrefix = '-' |
|---|
| 1178 | if not args.count |
|---|
| 1179 | options = OptionValues() |
|---|
| 1180 | options.add('about', true) |
|---|
| 1181 | paths = List<of String>() |
|---|
| 1182 | return |
|---|
| 1183 | |
|---|
| 1184 | # set up initial valueDict |
|---|
| 1185 | valueDict = Dictionary<of String, Object>() |
|---|
| 1186 | didSpecify = Dictionary<of String, bool>() # CC: could just be a Set |
|---|
| 1187 | if Utils.isDevMachine |
|---|
| 1188 | valueDict['reveal-internal-exceptions'] = true # this is a specially computed default, but can still be overridden on the command line |
|---|
| 1189 | |
|---|
| 1190 | valueStr = 'no-value' |
|---|
| 1191 | fileList = List<of String>() |
|---|
| 1192 | mainOptions = List<of String>() |
|---|
| 1193 | argn = 0 |
|---|
| 1194 | for arg in args |
|---|
| 1195 | argn += 1 # offset next arg after current |
|---|
| 1196 | if not arg.trim.length, continue |
|---|
| 1197 | |
|---|
| 1198 | isOption = arg.startsWith(optionPrefix) |
|---|
| 1199 | if isOption |
|---|
| 1200 | name = _getOptionParts(arg, optionPrefix, out valueStr) |
|---|
| 1201 | assert name.trim <> '' |
|---|
| 1202 | spec = _specDict[name] |
|---|
| 1203 | |
|---|
| 1204 | if spec.isAccumulator |
|---|
| 1205 | _accumulateOptValue(name, valueStr, spec, valueDict, didSpecify) |
|---|
| 1206 | continue |
|---|
| 1207 | |
|---|
| 1208 | value = _processToValue(name, valueStr, spec, mainOptions) |
|---|
| 1209 | if value == 'args-list' |
|---|
| 1210 | value = args[argn:] |
|---|
| 1211 | if value is nil |
|---|
| 1212 | errMsg = 'Cannot parse value "[valueStr]" for option "[name]".' |
|---|
| 1213 | branch spec.type |
|---|
| 1214 | on 'bool', errMsg += ' Possible values include yes, no, y, n, true, false, t, f, 1, 0, + and -.' |
|---|
| 1215 | on 'menu', errMsg += ' Possible values are [spec.choices.join(", ", " and ")].' |
|---|
| 1216 | _error(errMsg) |
|---|
| 1217 | valueDict[name] = value to ! |
|---|
| 1218 | didSpecify[name] = true |
|---|
| 1219 | if spec.type == 'args-list' |
|---|
| 1220 | break # absorbed remainder of args |
|---|
| 1221 | else # not isOption |
|---|
| 1222 | if _optsOnly |
|---|
| 1223 | _error('Filenames are not allowed here, All the args provided must be "-" prefixed options') |
|---|
| 1224 | if arg.startsWith('/') |
|---|
| 1225 | errHint = ' If you meant to specify an option, use dash (-) instead of slash (/).' |
|---|
| 1226 | _processAsFile(arg, fileList, errHint) |
|---|
| 1227 | |
|---|
| 1228 | _handleSynonyms(valueDict) |
|---|
| 1229 | _addInDefaults(valueDict) |
|---|
| 1230 | |
|---|
| 1231 | # TODO: make the option names case-insensitive |
|---|
| 1232 | |
|---|
| 1233 | if mainOptions.count > 1 |
|---|
| 1234 | _error('Cannot have these main options at the same time: [mainOptions.join(", ")]') |
|---|
| 1235 | |
|---|
| 1236 | _unpackOptions(valueDict, fileList) |
|---|
| 1237 | |
|---|
| 1238 | # set the out parameters |
|---|
| 1239 | options = OptionValues(valueDict) |
|---|
| 1240 | options.setSpecified(didSpecify) |
|---|
| 1241 | paths = fileList |
|---|
| 1242 | _computeArgImplications(options to !) |
|---|
| 1243 | |
|---|
| 1244 | def _getOptionParts(arg as String, optionPrefix as String, valueStr as out String) as String |
|---|
| 1245 | arg = .fixOptionArg(arg, optionPrefix) |
|---|
| 1246 | # CC: name, valueStr = .splitOpt(arg) |
|---|
| 1247 | parts = .splitOpt(arg) |
|---|
| 1248 | name = parts[0] |
|---|
| 1249 | valueStr = parts[1] |
|---|
| 1250 | name = .validateOptionName(name) |
|---|
| 1251 | return name |
|---|
| 1252 | |
|---|
| 1253 | def fixOptionArg(arg as String, optionPrefix as String) as String |
|---|
| 1254 | """ |
|---|
| 1255 | Strip any leading switch chars. |
|---|
| 1256 | """ |
|---|
| 1257 | while arg.startsWith(optionPrefix) |
|---|
| 1258 | arg = arg[1:] |
|---|
| 1259 | if not arg.length # '--' |
|---|
| 1260 | arg = 'run-args' |
|---|
| 1261 | return arg |
|---|
| 1262 | |
|---|
| 1263 | def splitOpt(arg as String) as IList<of String> |
|---|
| 1264 | """ |
|---|
| 1265 | Split option into name and valueStr |
|---|
| 1266 | """ |
|---|
| 1267 | valuePrefix = c':' |
|---|
| 1268 | parts = arg.split(@[valuePrefix], 2) |
|---|
| 1269 | if parts.length == 1 |
|---|
| 1270 | name = parts[0] |
|---|
| 1271 | if name.endsWith('+') |
|---|
| 1272 | name = name[:-1] |
|---|
| 1273 | valueStr = 'on' |
|---|
| 1274 | else if name.endsWith('-') |
|---|
| 1275 | name = name[:-1] |
|---|
| 1276 | valueStr = 'off' |
|---|
| 1277 | else |
|---|
| 1278 | valueStr = 'on' # this should probably be done at a later point, like _processToValue |
|---|
| 1279 | else |
|---|
| 1280 | assert parts.length == 2 |
|---|
| 1281 | name = parts[0] |
|---|
| 1282 | valueStr = parts[1] |
|---|
| 1283 | assert name.length, [arg, parts] |
|---|
| 1284 | # assert valueStr.length # not valid. an option could be cleared out like: -editor:"" |
|---|
| 1285 | return [name, valueStr] |
|---|
| 1286 | |
|---|
| 1287 | def _initSynonyms |
|---|
| 1288 | """ |
|---|
| 1289 | Init supporting data structures for handling option synonyms |
|---|
| 1290 | """ |
|---|
| 1291 | for spec in _optionSpecs |
|---|
| 1292 | if spec.isOptionSpecRestrictionViolated |
|---|
| 1293 | continue |
|---|
| 1294 | _specDict[spec.name] = spec |
|---|
| 1295 | if spec.synonyms.count |
|---|
| 1296 | for syn in spec.synonyms |
|---|
| 1297 | assert not _specDict.containsKey(syn) |
|---|
| 1298 | _specDict[syn] = spec |
|---|
| 1299 | _synToName[syn] = spec.name |
|---|
| 1300 | _synList.add(syn) |
|---|
| 1301 | |
|---|
| 1302 | def validateOptionName(name as String) as String |
|---|
| 1303 | """ |
|---|
| 1304 | Ensure the given name exists as an option name or synonym mappable |
|---|
| 1305 | to an option name; return the canonical name for the option/synonym |
|---|
| 1306 | """ |
|---|
| 1307 | require name.trim <> '' |
|---|
| 1308 | ensure result.trim <> '' |
|---|
| 1309 | name = _synToName.get(name, name) |
|---|
| 1310 | assert name.trim <> '' |
|---|
| 1311 | if not _specDict.containsKey(name) |
|---|
| 1312 | msg = 'No such option "[name]".' |
|---|
| 1313 | if name.contains('=') |
|---|
| 1314 | msg += ' If you meant to specify an option value, use colon (:) instead of equals (=).' |
|---|
| 1315 | _error(msg) |
|---|
| 1316 | return name |
|---|
| 1317 | |
|---|
| 1318 | def _accumulateOptValue(name as String, valueStr as String, |
|---|
| 1319 | spec as OptionSpec, |
|---|
| 1320 | valueDict as Dictionary<of String, Object>, |
|---|
| 1321 | didSpecify as Dictionary<of String, bool>) |
|---|
| 1322 | # accumulators are always treated as strings. TODO: assert that |
|---|
| 1323 | value = _interpretValue(valueStr, spec) |
|---|
| 1324 | if valueDict.containsKey(name) |
|---|
| 1325 | (valueDict[name] to System.Collections.IList).add(value) |
|---|
| 1326 | else |
|---|
| 1327 | valueDict[name] = [value to String] |
|---|
| 1328 | didSpecify[name] = true |
|---|
| 1329 | |
|---|
| 1330 | def _fixDebug(valueStr as String) as String? |
|---|
| 1331 | if valueStr == 'pdbonly' or valueStr == 'full' |
|---|
| 1332 | return valueStr |
|---|
| 1333 | |
|---|
| 1334 | value as String? = 'no-value' |
|---|
| 1335 | try |
|---|
| 1336 | b = _boolForString(valueStr) |
|---|
| 1337 | catch FormatException |
|---|
| 1338 | value = nil |
|---|
| 1339 | success |
|---|
| 1340 | value = if(b, '+', '-') |
|---|
| 1341 | return value |
|---|
| 1342 | |
|---|
| 1343 | def _processToValue(name as String, valueStr as String, spec as OptionSpec, mainOptions as List<of String>) as dynamic? |
|---|
| 1344 | value = 'no-value' to dynamic? |
|---|
| 1345 | |
|---|
| 1346 | if name == 'debug' # special case |
|---|
| 1347 | return _fixDebug(valueStr) |
|---|
| 1348 | |
|---|
| 1349 | branch spec.type |
|---|
| 1350 | on 'main' |
|---|
| 1351 | mainOptions.add(name) |
|---|
| 1352 | value = true |
|---|
| 1353 | on 'args-list' # remainder of args are for execution of exe file |
|---|
| 1354 | value = 'args-list' |
|---|
| 1355 | else |
|---|
| 1356 | value = _interpretValue(valueStr, spec) |
|---|
| 1357 | return value |
|---|
| 1358 | |
|---|
| 1359 | def _handleSynonyms(valueDict as Dictionary<of String, Object>) |
|---|
| 1360 | for syn in _synList |
|---|
| 1361 | if valueDict.containsKey(syn) |
|---|
| 1362 | valueDict[_synToName[syn]] = valueDict[syn] |
|---|
| 1363 | valueDict.remove(syn) |
|---|
| 1364 | |
|---|
| 1365 | def _addInDefaults(valueDict as Dictionary<of String, Object>) |
|---|
| 1366 | for spec in _optionSpecs |
|---|
| 1367 | if not valueDict.containsKey(spec.name) and spec.hasDefault |
|---|
| 1368 | defaultValue = _interpretValue(spec.default, spec) to ! |
|---|
| 1369 | if .verbosity |
|---|
| 1370 | print 'Setting option "[spec.name]" to default value [defaultValue].' |
|---|
| 1371 | valueDict[spec.name] = defaultValue |
|---|
| 1372 | |
|---|
| 1373 | def _unpackOptions(valueDict as Dictionary<of String, Object>, fileList as List<of String>) |
|---|
| 1374 | """ |
|---|
| 1375 | Unpack certain options (verbosity and timeit) into specific class fields, |
|---|
| 1376 | do files option processing |
|---|
| 1377 | """ |
|---|
| 1378 | if valueDict.containsKey('verbosity') |
|---|
| 1379 | _verbosity = valueDict['verbosity'] to int |
|---|
| 1380 | |
|---|
| 1381 | if not valueDict.containsKey('timeit') and valueDict.containsKey('testify') |
|---|
| 1382 | valueDict['timeit'] = true |
|---|
| 1383 | if valueDict.containsKey('timeit') |
|---|
| 1384 | _willTimeIt = valueDict['timeit'] to bool |
|---|
| 1385 | |
|---|
| 1386 | if valueDict.containsKey('files') |
|---|
| 1387 | fileNamesList = valueDict['files'] to System.Collections.IList |
|---|
| 1388 | _processFilesFile(fileNamesList, fileList) |
|---|
| 1389 | |
|---|
| 1390 | def readFilesFile(filesFilePath as String) as List<of String> |
|---|
| 1391 | """ |
|---|
| 1392 | Augments the list of files held by the arg parser with those passed in. |
|---|
| 1393 | """ |
|---|
| 1394 | paths = List<of String>() |
|---|
| 1395 | _processFilesFile([filesFilePath], paths) |
|---|
| 1396 | return paths |
|---|
| 1397 | |
|---|
| 1398 | def _processFilesFile(fileNamesList as System.Collections.IList, fileList as List<of String>) |
|---|
| 1399 | """ |
|---|
| 1400 | Treat entries in fileNamesList as names of files containing filenames to compile, |
|---|
| 1401 | validate names and add into fileList |
|---|
| 1402 | """ |
|---|
| 1403 | for fileName as String in fileNamesList |
|---|
| 1404 | try |
|---|
| 1405 | try |
|---|
| 1406 | baseDir = Path.getDirectoryName(fileName) |
|---|
| 1407 | catch ArgumentException |
|---|
| 1408 | _error('Cannot open file "[fileName]" which appears to be an invalid path.') |
|---|
| 1409 | for line in File.readAllLines(fileName) |
|---|
| 1410 | line = line.trim |
|---|
| 1411 | if line.length==0 or line.startsWith('#'), continue |
|---|
| 1412 | # note that source files are relative to the location of the "files file" |
|---|
| 1413 | try |
|---|
| 1414 | fileArg = Path.combine(baseDir, line) |
|---|
| 1415 | catch ArgumentException |
|---|
| 1416 | _error('Cannot properly read file "[fileName]" for file names.') |
|---|
| 1417 | _processAsFile(fileArg, fileList, nil) |
|---|
| 1418 | catch IOException |
|---|
| 1419 | _error('Cannot open file "[fileName]".') |
|---|
| 1420 | |
|---|
| 1421 | def _processAsFile(arg as String, fileList as List<of String>, errHint as String?) |
|---|
| 1422 | """ |
|---|
| 1423 | Validate arg as filename and on success add into fileList |
|---|
| 1424 | """ |
|---|
| 1425 | sep = Path.directorySeparatorChar.toString |
|---|
| 1426 | arg = arg.replace('\\', sep).replace('/', sep) |
|---|
| 1427 | if File.exists(arg) |
|---|
| 1428 | fileList.add(arg) |
|---|
| 1429 | else if File.exists(arg+'.cobra') |
|---|
| 1430 | fileList.add(arg+'.cobra') |
|---|
| 1431 | else if Directory.exists(arg) |
|---|
| 1432 | fileList.add(arg) |
|---|
| 1433 | else |
|---|
| 1434 | _error('Cannot find "[arg]" as a file.' + (errHint?'')) |
|---|
| 1435 | |
|---|
| 1436 | def _computeArgImplications(options as OptionValues) |
|---|
| 1437 | if options.getDefault('target', '') == 'lib' and not options.isSpecified('compile') |
|---|
| 1438 | options['compile'] = true |
|---|
| 1439 | if options.getDefault('debug', '') not in ['', '0', '-'] and not options.isSpecified('debugging-tips') |
|---|
| 1440 | options['debugging-tips'] = false |
|---|
| 1441 | if options.boolValue('turbo') |
|---|
| 1442 | options['contracts'] = 'none' |
|---|
| 1443 | options['include-asserts'] = false |
|---|
| 1444 | options['include-nil-checks'] = false |
|---|
| 1445 | options['include-tests'] = false |
|---|
| 1446 | options['include-traces'] = false |
|---|
| 1447 | options['optimize'] = true |
|---|
| 1448 | |
|---|
| 1449 | def _interpretValue(valueStr as String, spec as OptionSpec) as dynamic? |
|---|
| 1450 | value as dynamic? |
|---|
| 1451 | branch spec.type |
|---|
| 1452 | on 'main' |
|---|
| 1453 | throw InvalidOperationException('This method does not handle the main type.') |
|---|
| 1454 | on 'bool' |
|---|
| 1455 | try |
|---|
| 1456 | value = _boolForString(valueStr) |
|---|
| 1457 | catch FormatException |
|---|
| 1458 | value = nil # cannot process |
|---|
| 1459 | on 'int' |
|---|
| 1460 | if valueStr == 'on' # set internally when there is no value |
|---|
| 1461 | valueStr = '1' |
|---|
| 1462 | try |
|---|
| 1463 | value = int.parse(valueStr) |
|---|
| 1464 | catch FormatException |
|---|
| 1465 | value = nil |
|---|
| 1466 | catch OverflowException |
|---|
| 1467 | value = nil |
|---|
| 1468 | # TODO: check min and max |
|---|
| 1469 | on 'string' or 'accumulator' |
|---|
| 1470 | if valueStr.startsWith('"') and valueStr.endsWith('"'), valueStr = valueStr[1:-1] |
|---|
| 1471 | else if valueStr.startsWith("'") and valueStr.endsWith("'"), valueStr = valueStr[1:-1] |
|---|
| 1472 | value = valueStr |
|---|
| 1473 | on 'menu' |
|---|
| 1474 | if valueStr.length == 0 |
|---|
| 1475 | value = 'all' |
|---|
| 1476 | else if not valueStr in spec.choices |
|---|
| 1477 | value = nil |
|---|
| 1478 | else |
|---|
| 1479 | value = valueStr |
|---|
| 1480 | on 'set' |
|---|
| 1481 | if valueStr in ['', 'on'], valueStr = 'all' |
|---|
| 1482 | valueSet = Set<of String>() |
|---|
| 1483 | for choice in valueStr.split(c',') |
|---|
| 1484 | if choice == 'none' |
|---|
| 1485 | valueSet = {'none'} |
|---|
| 1486 | else if choice == 'all' |
|---|
| 1487 | valueSet = Set<of String>(for choice in spec.choices where choice not in ['none', 'all']) |
|---|
| 1488 | else |
|---|
| 1489 | valueSet.add(choice) |
|---|
| 1490 | value = valueSet |
|---|
| 1491 | else |
|---|
| 1492 | throw FallThroughException(spec.type) |
|---|
| 1493 | return value |
|---|
| 1494 | |
|---|
| 1495 | def _boolForString(s as String) as bool |
|---|
| 1496 | if s.toLower in ['', '+', 'on', 'true', 't', 'yes', 'y', '1'] |
|---|
| 1497 | return true |
|---|
| 1498 | else if s.toLower in ['-', 'off', 'false', 'f', 'no', 'n', '0'] |
|---|
| 1499 | return false |
|---|
| 1500 | else |
|---|
| 1501 | throw FormatException() |
|---|
| 1502 | |
|---|
| 1503 | def _error(msg as String) |
|---|
| 1504 | throw ArgParseException(msg) |
|---|
| 1505 | |
|---|
| 1506 | |
|---|
| 1507 | |
|---|
| 1508 | class HtmlWriter |
|---|
| 1509 | inherits TextWriter |
|---|
| 1510 | """ |
|---|
| 1511 | In support of the output-html option. |
|---|
| 1512 | """ |
|---|
| 1513 | |
|---|
| 1514 | var _otherWriter as TextWriter |
|---|
| 1515 | var _isWritingHtml as bool |
|---|
| 1516 | |
|---|
| 1517 | cue init(otherWriter as TextWriter) |
|---|
| 1518 | base.init |
|---|
| 1519 | _otherWriter = otherWriter |
|---|
| 1520 | |
|---|
| 1521 | get encoding as Encoding? is override |
|---|
| 1522 | return Encoding.default |
|---|
| 1523 | |
|---|
| 1524 | def write(c as char) is override |
|---|
| 1525 | _otherWriter.write(c) |
|---|
| 1526 | if not _isWritingHtml and c == c'\n' |
|---|
| 1527 | _otherWriter.write('<br>') |
|---|
| 1528 | |
|---|
| 1529 | def writeHtml(html as String) |
|---|
| 1530 | _isWritingHtml = true |
|---|
| 1531 | try |
|---|
| 1532 | .write(html) |
|---|
| 1533 | finally |
|---|
| 1534 | _isWritingHtml = false |
|---|
| 1535 | |
|---|
| 1536 | |
|---|
| 1537 | class OptionValues inherits Dictionary<of String, Object> |
|---|
| 1538 | |
|---|
| 1539 | var _isSpecified = Dictionary<of String, bool>() # CC: could just be a Set |
|---|
| 1540 | |
|---|
| 1541 | cue init |
|---|
| 1542 | base.init |
|---|
| 1543 | # .init(nil) - cannot call base.init(d) with nil |
|---|
| 1544 | |
|---|
| 1545 | cue init(d as IDictionary<of String, Object>?) |
|---|
| 1546 | base.init(d) |
|---|
| 1547 | |
|---|
| 1548 | def isSpecified(name as String) as bool |
|---|
| 1549 | """ |
|---|
| 1550 | Returns true if the given option name is explicitly specified (as opposed to being present |
|---|
| 1551 | in the options dictionary due to having a default value). |
|---|
| 1552 | """ |
|---|
| 1553 | return _isSpecified.containsKey(name) |
|---|
| 1554 | |
|---|
| 1555 | def didSpecify(name as String) |
|---|
| 1556 | _isSpecified[name] = true |
|---|
| 1557 | |
|---|
| 1558 | def setSpecified(specify as Dictionary<of String,bool>) |
|---|
| 1559 | for name in specify.keys |
|---|
| 1560 | .didSpecify(name) |
|---|
| 1561 | |
|---|
| 1562 | def boolValue(key as String) as bool |
|---|
| 1563 | if .containsKey(key) |
|---|
| 1564 | return this[key] to bool |
|---|
| 1565 | else |
|---|
| 1566 | return false |
|---|
| 1567 | |
|---|
| 1568 | def get(key as String) as dynamic? |
|---|
| 1569 | return this[key] |
|---|
| 1570 | |
|---|
| 1571 | def getDefault(key as String, default as dynamic?) as dynamic? |
|---|
| 1572 | if .containsKey(key) |
|---|
| 1573 | return this[key] |
|---|
| 1574 | else |
|---|
| 1575 | return default |
|---|
| 1576 | |
|---|
| 1577 | def getStringList(key as String) as List<of String> |
|---|
| 1578 | """ |
|---|
| 1579 | Returns a List<of String> for the given key. |
|---|
| 1580 | If the key is not present, returns an empty list. |
|---|
| 1581 | """ |
|---|
| 1582 | if .containsKey(key) |
|---|
| 1583 | return this[key] to List<of String> |
|---|
| 1584 | else |
|---|
| 1585 | return List<of String>() |
|---|
| 1586 | |
|---|
| 1587 | # CC: def getDefault<of T>(key as String, value as T) as T ... |
|---|
| 1588 | |
|---|
| 1589 | def setValue(key as String) as Set<of String> |
|---|
| 1590 | """ Returns an empty string if no such set exists. """ |
|---|
| 1591 | if .containsKey(key) |
|---|
| 1592 | try |
|---|
| 1593 | return this[key] to Set<of String> |
|---|
| 1594 | catch InvalidCastException |
|---|
| 1595 | throw InvalidCastException('Cannot cast [CobraCore.toTechString(this[key])] for key "[key]" to Set<of String>.') |
|---|
| 1596 | else |
|---|
| 1597 | return Set<of String>() |
|---|
| 1598 | |
|---|
| 1599 | def combine(options as OptionValues) |
|---|
| 1600 | if options is not this |
|---|
| 1601 | for key in options.keys |
|---|
| 1602 | this[key] = options[key] |
|---|
| 1603 | |
|---|
| 1604 | def combineNew(options as OptionValues) |
|---|
| 1605 | if options is not this |
|---|
| 1606 | for key in options.keys |
|---|
| 1607 | if not .isSpecified(key) |
|---|
| 1608 | this[key] = options[key] |
|---|
| 1609 | |
|---|
| 1610 | def print |
|---|
| 1611 | for key in .keys |
|---|
| 1612 | print ' [key]: [CobraCore.toTechString(this[key])]' |
|---|
| 1613 | |
|---|
| 1614 | get buildStandardLibrary as bool |
|---|
| 1615 | return .boolValue('build-standard-library') |
|---|
| 1616 | |
|---|
| 1617 | def addExtraUse(useSource as String) |
|---|
| 1618 | es = .getDefault('extra-source', '') to String |
|---|
| 1619 | useSource = useSource.trim |
|---|
| 1620 | if not useSource in es |
|---|
| 1621 | es = useSource + '\n' + es |
|---|
| 1622 | this['extra-source'] = es |
|---|
| 1623 | |
|---|
| 1624 | def addExtraSource(source as String) |
|---|
| 1625 | es = .getDefault('extra-source', '') to String |
|---|
| 1626 | es = es + source + '\n' |
|---|
| 1627 | this['extra-source'] = es |
|---|
| 1628 | |
|---|
| 1629 | |
|---|
| 1630 | var _uniqueIdentifier as String? |
|---|
| 1631 | |
|---|
| 1632 | def uniqueIdentifier as String |
|---|
| 1633 | """ |
|---|
| 1634 | Returns a unique id such as '95b141d670c19f2f20a820751897b9c6' which is guaranteed to be |
|---|
| 1635 | unique per Cobra process. Can be used in identifiers. Motivated by -embed-run-time |
|---|
| 1636 | option which suffixes the Cobra.Lang suffix to avoid collisions with other assemblies. |
|---|
| 1637 | """ |
|---|
| 1638 | if _uniqueIdentifier is nil |
|---|
| 1639 | components = [DateTime.now, this, Environment.currentDirectory, Process.getCurrentProcess.id] |
|---|
| 1640 | _uniqueIdentifier = components.toPrintString.md5HashInHex |
|---|
| 1641 | return _uniqueIdentifier to ! |
|---|
| 1642 | |
|---|
| 1643 | def embedRunTimeSuffix as String |
|---|
| 1644 | if .boolValue('embed-run-time') and not .boolValue('build-standard-library') |
|---|
| 1645 | return '_ert_' + .uniqueIdentifier |
|---|
| 1646 | else |
|---|
| 1647 | return '' |
|---|