craceplot 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. craceplot/__init__.py +60 -0
  2. craceplot/_containers/__init__.py +0 -0
  3. craceplot/_containers/_core.py +1602 -0
  4. craceplot/_containers/_draw.py +104 -0
  5. craceplot/_inst/examples/acotsp/crace-3240078.stdout +838 -0
  6. craceplot/_inst/examples/acotsp/crace.stderr +8 -0
  7. craceplot/_inst/examples/acotsp/crace.test +216 -0
  8. craceplot/_inst/examples/acotsp/crace.train +622 -0
  9. craceplot/_inst/examples/cats200/crace-2814581.stdout +1665 -0
  10. craceplot/_inst/examples/cats200/crace.stderr +10 -0
  11. craceplot/_inst/examples/cats200/crace.test +1010 -0
  12. craceplot/_inst/examples/cats200/crace.train +655 -0
  13. craceplot/_plots/__init__.py +8 -0
  14. craceplot/_plots/_core.py +80 -0
  15. craceplot/_plots/parameters.py +1805 -0
  16. craceplot/_plots/quality.py +1406 -0
  17. craceplot/_scripts/__init__.py +4 -0
  18. craceplot/_scripts/_main.py +132 -0
  19. craceplot/_scripts/_utils.py +66 -0
  20. craceplot/_scripts/main +3 -0
  21. craceplot/_scripts/open_guide +12 -0
  22. craceplot/_settings/_description.py +168 -0
  23. craceplot/_settings/_options.json +299 -0
  24. craceplot/_utils/__init__.py +0 -0
  25. craceplot/_utils/_base.py +196 -0
  26. craceplot/_utils/_const.py +8 -0
  27. craceplot/_utils/_crace.py +42 -0
  28. craceplot/_utils/_format.py +191 -0
  29. craceplot/_vergit.py +2 -0
  30. craceplot/_version.py +24 -0
  31. craceplot/_vignettes/guide.ipynb +18943 -0
  32. craceplot-0.1.0.dist-info/METADATA +369 -0
  33. craceplot-0.1.0.dist-info/RECORD +61 -0
  34. craceplot-0.1.0.dist-info/WHEEL +5 -0
  35. craceplot-0.1.0.dist-info/entry_points.txt +2 -0
  36. craceplot-0.1.0.dist-info/licenses/LICENSE.md +21 -0
  37. craceplot-0.1.0.dist-info/top_level.txt +2 -0
  38. docs/_config.yml +51 -0
  39. docs/_static/css/custom.css +168 -0
  40. docs/_static/js/navbar.js +40 -0
  41. docs/_toc.yml +5 -0
  42. docs/index.md +4 -0
  43. docs/references/404.md +14 -0
  44. docs/references/authors.md +46 -0
  45. docs/references/citiation.bib +17 -0
  46. docs/references/functions/index.md +5 -0
  47. docs/references/functions/param_boxplot.ipynb +19 -0
  48. docs/references/index.md +44 -0
  49. docs/references/license.md +21 -0
  50. docs/references/others/favicon.ico +0 -0
  51. docs/references/others/favicon_io/about.txt +6 -0
  52. docs/references/others/favicon_io/android-chrome-192x192.png +0 -0
  53. docs/references/others/favicon_io/android-chrome-512x512.png +0 -0
  54. docs/references/others/favicon_io/apple-touch-icon.png +0 -0
  55. docs/references/others/favicon_io/favicon-16x16.png +0 -0
  56. docs/references/others/favicon_io/favicon-32x32.png +0 -0
  57. docs/references/others/favicon_io/site.webmanifest +1 -0
  58. docs/references/others/logo.png +0 -0
  59. docs/references/others/logo1.png +0 -0
  60. docs/references/plots/acotsp-parallelcoord-all.png +0 -0
  61. docs/requirements.txt +5 -0
@@ -0,0 +1,1602 @@
1
+ import os
2
+ import re
3
+ import ast
4
+ import sys
5
+ import json
6
+ import traceback
7
+ from datetime import datetime
8
+ from abc import ABC, abstractmethod
9
+ from typing import Literal, Union, Optional, get_args, get_origin
10
+
11
+ import craceplot._settings._description as info
12
+ from craceplot._utils._const import WIDTH
13
+ from craceplot._utils._format import format_string
14
+ from craceplot._utils._base import check_type, parse_range_list
15
+
16
+ CPLOT_HOME=os.path.dirname(os.path.dirname(__file__))
17
+
18
+ bold = '\033[1m'
19
+ underline = '\033[4m'
20
+ reset = '\033[0m'
21
+
22
+
23
+ ############################# Error information #############################
24
+ # CplotError #
25
+ # OptionError #
26
+ # ParameterDefinitionError #
27
+ # ParameterValueError #
28
+ # ModelError #
29
+ # FileError #
30
+ # CplotExecutionError #
31
+ # ExitError #
32
+ #############################################################################
33
+ class CplotError(Exception):
34
+ """Exceptions for crace-plot"""
35
+
36
+ def __init__(self, message="No message provided."):
37
+ super().__init__(message)
38
+ pass
39
+
40
+ class OptionError(CplotError):
41
+ """Exception for option type """
42
+ def __init__(self, message="No message provided."):
43
+ super().__init__(message)
44
+ pass
45
+
46
+ class ParameterDefinitionError(CplotError):
47
+ """Exception for option type """
48
+ def __init__(self, message="No message provided."):
49
+ super().__init__(message)
50
+ pass
51
+
52
+ class ParameterValueError(CplotError):
53
+ """Exception for option type """
54
+ def __init__(self, message="No message provided."):
55
+ super().__init__(message)
56
+ pass
57
+
58
+ class ModelError(CplotError):
59
+ """Exception for option type """
60
+ def __init__(self, message="No message provided."):
61
+ super().__init__(message)
62
+ pass
63
+
64
+ class FileError(CplotError):
65
+ """ Errors related to files (not found or invalid"""
66
+ def __init__(self, message="No message provided."):
67
+ super().__init__(message)
68
+ pass
69
+
70
+ class CplotExecutionError(CplotError):
71
+ """
72
+ Errors related to the execution of jobs
73
+ """
74
+ def __init__(self, message="No message provided."):
75
+ super().__init__(message)
76
+ pass
77
+
78
+ class ExitError(CplotError):
79
+ """Exit without error"""
80
+ def __init__(self, message="No message provided."):
81
+ super().__init__(message)
82
+ pass
83
+
84
+ class Errors:
85
+ """
86
+ Namespace wrapper so users can import all errors via:
87
+ from file import Errors
88
+ raise Errors.Option("...")
89
+ """
90
+
91
+ CplotError = CplotError
92
+ OptionError = OptionError
93
+ ParameterDefinitionError = ParameterDefinitionError
94
+ ParameterValueError = ParameterValueError
95
+ ModelError = ModelError
96
+ FileError = FileError
97
+ CplotExecutionError = CplotExecutionError
98
+ ExitError = ExitError
99
+
100
+
101
+
102
+ ################################ Option rules ###############################
103
+ # _option_decoder #
104
+ # IntegerOption #
105
+ # RealOption #
106
+ # ParameterValueError #
107
+ # StringOption #
108
+ # BooleanOption #
109
+ # FileOption #
110
+ # ExeOption #
111
+ # EnablerOption #
112
+ # ListOption #
113
+ # UnionOption #
114
+ #############################################################################
115
+ def _option_decoder(obj):
116
+ """
117
+ instantiates the correct class for an option
118
+ :param obj: option (dictionary) obtained from the json settings definition
119
+ :return: an instance of the Option class
120
+ :raises OptionError: if type attribute is not found in obj or if type was not recognized
121
+ """
122
+ o = None
123
+ if not ('type' in obj):
124
+ raise OptionError("Option has no type.")
125
+ if obj['type'] == 'i':
126
+ o = IntegerOption(obj)
127
+ if obj['type'] == 'r':
128
+ o = RealOption(obj)
129
+ if obj['type'] == 'b':
130
+ o = BooleanOption(obj)
131
+ if obj['type'] == 's':
132
+ o = StringOption(obj)
133
+ if obj['type'] == 'p':
134
+ o = FileOption(obj)
135
+ if obj['type'] == 'x':
136
+ o = ExeOption(obj)
137
+ if obj['type'] == 'e':
138
+ o = EnablerOption(obj)
139
+ if obj['type'] == 'l':
140
+ o = ListOption(obj)
141
+ if obj['type'] == 'u':
142
+ o = UnionOption(obj)
143
+
144
+ if o is None:
145
+ raise OptionError(f"Option cannot be read: {underline}{obj['name']}{reset}")
146
+
147
+ return o
148
+
149
+ class Option(ABC):
150
+ """
151
+ Class Option handles options of the configurator
152
+ :var general_options: List of variables associated with an Option
153
+ name: option name
154
+ type: option type (see option_types)
155
+ short: short flag
156
+ long: long flag
157
+ section: section (group) to which the option belongs
158
+ vignettes: text for the documentation of the option
159
+ description: general description of the option
160
+ :var option_types: List of option types available
161
+ e: enabler option (activates behaviour)
162
+ i: integer option
163
+ r: real option
164
+ s: string option
165
+ b: boolean option
166
+ p: file path option
167
+ x: script file or function to be parsed. Both should be executable
168
+ u: union option
169
+
170
+ :ivar value: option value
171
+ :ivar default: option default value
172
+ """
173
+
174
+ general_options = ["name", "type", "short", "long", "section", "vignettes", "description", "critical"]
175
+
176
+ option_types = ["e", "i", "r", "s", "b", "p", "x", "l", "u"]
177
+
178
+ def __init__(self):
179
+ """
180
+ Creates an Option object
181
+ """
182
+ self.value = None
183
+ self.default = None
184
+
185
+ @staticmethod
186
+ def _check_general_options(obj):
187
+ """
188
+ checks that all options in Options.general_options are defined in obj
189
+ :param obj: Dictionary of options read from the json settings file
190
+ :return: none
191
+ :raises OptionError: if an option is not included in obj
192
+ """
193
+ for key in Option.general_options:
194
+ if not (key in obj.keys()):
195
+ raise OptionError(f"All craceplot options must define attribute {underline}{key}{reset}")
196
+
197
+ def _set_general_options(self, obj):
198
+ """
199
+ Creates variables in Option.general_options list and assigns
200
+ the values for these variables in obj
201
+ :param obj: Dictionary of variable values, keys are the names
202
+ in Option.general_options
203
+ """
204
+ for key in Option.general_options:
205
+ if key == "critical":
206
+ setattr(self, key, int(obj[key]))
207
+ elif key in ["vignettes", "description"]:
208
+ setattr(self, key, obj[key])
209
+ else:
210
+ setattr(self, key, obj[key].replace(".", ""))
211
+
212
+ def is_set(self):
213
+ return not (self.value is None)
214
+
215
+ def set_value(self, value):
216
+ """
217
+ sets the value of the option and validates it
218
+ :param value: the value to be set
219
+ :return: none
220
+ """
221
+ self.value = self.parse_value(value, True)
222
+
223
+ def is_default(self):
224
+ """
225
+ Returns True if value assigned is the default value
226
+ """
227
+ return self.value == self.default
228
+
229
+ @abstractmethod
230
+ def parse_value(self, value: str, check: bool = False):
231
+ """
232
+ parses the option value from a string
233
+ :param value: string that has the value of the option
234
+ :param check: boolean that indicates if the value must be validated
235
+ :return: parsed value. If the value is None or empty, the default value is returned
236
+ :raises OptionError: if value is not integer
237
+ """
238
+
239
+ def parse_default(self, value: str):
240
+ """
241
+ parse the default value of the option, no validation is performed
242
+ :param value: string with the default value
243
+ :return: the parsed value (integer) or None if default is None or empty
244
+ """
245
+
246
+ def check_value(self, value):
247
+ """
248
+ checks a value has the correct type (integer) and is in the option domain
249
+ :param value: value to be checked
250
+ :return: none
251
+ :raises OptionError: if value is no integer o value is not within domain
252
+ """
253
+
254
+ def parse_domain(self, domain: str):
255
+ """
256
+ parses the domain of an option
257
+ :param domain: string that defines the domain in the format (lower, upper)
258
+ :return: returns a list of size two with first element the lower bound and second element the upper bound [lower, upper]
259
+ :raises OptionError: if the provided domain cannot be parsed, or if the domain is not correct lower > upper
260
+ """
261
+
262
+ class IntegerOption(Option):
263
+ """
264
+ class the handles integer craceplot options
265
+ :ivar domain: domain of the option as a two element list [lower, upper]
266
+ """
267
+ def __init__(self, obj):
268
+ """
269
+ Creates an Integer Option
270
+ :param obj: dictionary of the option read from the json setting file
271
+ """
272
+ # add general variables
273
+ super()._check_general_options(obj)
274
+ super()._set_general_options(obj)
275
+
276
+ # other variables
277
+ self.domain = self.parse_domain(obj['domain'])
278
+ self.default = self.parse_default(obj['default'])
279
+ self.value = self.default
280
+
281
+ def parse_value(self, value: str, check: bool = False):
282
+ """
283
+ parses the option value from a string
284
+ :param value: string that has the value of the option
285
+ :param check: boolean that indicates if the value must be validated
286
+ :return: parsed value. If the value is None or empty, the default value is returned
287
+ :raises OptionError: if value is not integer
288
+ """
289
+ # if value is empty return default
290
+ if value is None or value == "":
291
+ v = self.default
292
+ elif value.lower() == 'none':
293
+ v = self.default
294
+ elif isinstance(value, str):
295
+ try:
296
+ v = eval(value)
297
+ except Exception as e:
298
+ raise ValueError(f"Option {underline}{self.name}{reset} "
299
+ f"must be integer but has value {bold}{value}{bold}")
300
+ if not isinstance(v, int):
301
+ raise OptionError(f"Option {underline}{self.name}{reset} "
302
+ f"must be integer but has value/default: {bold}{value}{reset}")
303
+ else:
304
+ v = value
305
+
306
+ if check:
307
+ self.check_value(v)
308
+ return v
309
+
310
+ def parse_default(self, value: str):
311
+ """
312
+ parse the default value of the option, no validation is performed
313
+ :param value: string with the default value
314
+ :return: the parsed value (integer) or None if default is None or empty
315
+ """
316
+ if value is None or value == "":
317
+ return None
318
+ elif isinstance(value, str):
319
+ return eval(value)
320
+ else:
321
+ return value
322
+
323
+ def check_value(self, value):
324
+ """
325
+ checks a value has the correct type (integer) and is in the option domain
326
+ :param value: value to be checked
327
+ :return: none
328
+ :raises OptionError: if value is no integer o value is not within domain
329
+ """
330
+ if value == self.default:
331
+ return
332
+ if not isinstance(value, int):
333
+ raise OptionError(f"Option {underline}{self.name}{reset} type error value "
334
+ f"{bold}{str(value)}{reset} is not int")
335
+ elif (value < self.domain[0]) or (value > self.domain[1]):
336
+ raise OptionError(f"Option {underline}{self.name}{reset} value/default: "
337
+ f"{bold}{str(value)}{reset} is not within its integer domain "
338
+ f"({str(self.domain[0])}, {str(self.domain[1])})")
339
+
340
+ def parse_domain(self, domain: str):
341
+ """
342
+ parses the domain of an option
343
+ :param domain: string that defines the domain in the format (lower, upper)
344
+ :return: returns a list of size two with first element the lower bound and second element the upper bound [lower, upper]
345
+ :raises OptionError: if the provided domain cannot be parsed, or if the domain is not correct lower > upper
346
+ """
347
+ if not domain: return None
348
+ p = re.compile("(?P<d1>\d+),(?P<d2>\d+)")
349
+ m = p.match(domain.strip("()"))
350
+ if m is None:
351
+ raise OptionError(f"Domain for integer option "
352
+ f"{underline}{self.name}{reset} is not correct : {domain}")
353
+ d = [eval(m.group("d1")), eval(m.group("d2"))]
354
+ if d[0] > d[1]:
355
+ raise OptionError(f"Domain for integer option "
356
+ f"{underline}{self.name}{reset} is not correct : {domain}")
357
+ return d
358
+
359
+ class RealOption(Option):
360
+ """
361
+ class the handles real valued craceplot options
362
+
363
+ :ivar domain: domain of the option as a two element list [lower, upper]
364
+ """
365
+ def __init__(self, obj):
366
+ """
367
+ Creates a Real Option object
368
+ :param obj: dictionary of the option read from the json setting file
369
+ """
370
+ # add general variables
371
+ super()._check_general_options(obj)
372
+ super()._set_general_options(obj)
373
+
374
+ # other variables
375
+ self.domain = self.parse_domain(obj['domain'])
376
+ self.default = self.parse_default(obj['default'])
377
+ self.value = self.default
378
+
379
+ def parse_value(self, value: str, check: bool = False):
380
+ """
381
+ parses the option value from a string
382
+ :param value: string that has the value of the option
383
+ :param check: boolean that indicates if the value must be validated
384
+ :return: parsed value. If the value is None or empty, the default value is returned
385
+ :raises OptionError: if value is not float
386
+ """
387
+ if value is None or value == "":
388
+ return self.default
389
+ if isinstance(value, str):
390
+ try:
391
+ v = float(eval(value))
392
+ except Exception as e:
393
+ raise ValueError(f"Option {underline}{self.name}{reset} "
394
+ f"must be real but has value {bold}{value}{reset}")
395
+ if not isinstance(v, float):
396
+ raise OptionError(f"Option {underline}{self.name}{reset} "
397
+ f"must be real but has value/default: {value}")
398
+
399
+ else:
400
+ v = value
401
+
402
+ if check:
403
+ self.check_value(v)
404
+ return v
405
+
406
+ def parse_default(self, value: str):
407
+ """
408
+ parse the default value of the option, no validation is performed
409
+ :param value: string with the default value
410
+ :return: the parsed value (float) or None if default is None or empty
411
+ """
412
+ if value is None or value == "":
413
+ return None
414
+ elif isinstance(value, str):
415
+ return eval(value)
416
+ else:
417
+ return value
418
+
419
+ def check_value(self, value):
420
+ """
421
+ checks a value has the correct type (float) and is in the option domain
422
+ :param value: value to be checked
423
+ :return: none
424
+ :raises OptionError: if value is not float or value is not within domain
425
+ """
426
+ if not isinstance(value, float):
427
+ raise OptionError(f"Option {underline}{self.name}{reset} type error value "
428
+ f"{bold}{str(value)}{reset} is not float")
429
+ elif (value < self.domain[0]) or (value > self.domain[1]):
430
+ raise OptionError(f"Option {underline}{self.name}{reset} value/default: "
431
+ f"{bold}{str(value)}{reset} is not within its real domain "
432
+ f"({str(self.domain[0])}, {str(self.domain[1])})")
433
+
434
+ def parse_domain(self, domain: str):
435
+ """
436
+ parses the domain of an option
437
+ :param domain: string that defines the domain in the format (lower, upper)
438
+ :return: returns a list of size two with first element the lower bound and second element the upper bound [lower, upper]
439
+ :raises OptionError: if the provided domain cannot be parsed, or if the domain is not correct lower > upper
440
+ """
441
+ if not domain: return None
442
+ p = re.compile("(?P<d1>[0-9]+\.*[0-9]*),(?P<d2>[0-9]+\.*[0-9]*)")
443
+ m = p.match(domain.strip("()"))
444
+ if m is None:
445
+ raise OptionError(f"Domain for real option "
446
+ f"{underline}{self.name}{reset} is not correct :{domain}")
447
+ d = [eval(m.group("d1")), eval(m.group("d2"))]
448
+ if d[0] > d[1]:
449
+ raise OptionError(f"Domain for real option "
450
+ f"{underline}{self.name}{reset} is not correct :{domain}")
451
+ return d
452
+
453
+ class StringOption(Option):
454
+ """
455
+ class the handles string craceplot options
456
+
457
+ :ivar domain: domain of the option as list [value1, value2, value3,...]
458
+ """
459
+ def __init__(self, obj):
460
+ """
461
+ Creates a String Option object
462
+ :param obj: dictionary of the option read from the json setting file
463
+ """
464
+ # add general variables
465
+ super()._check_general_options(obj)
466
+ super()._set_general_options(obj)
467
+
468
+ # other variables
469
+ # if obj['name'] in ('title', 'fileName', 'keyParameter', 'catx', 'caty'):
470
+ if 'domain' not in obj.keys():
471
+ self.domain = None
472
+ else:
473
+ self.domain = self.parse_domain(obj['domain'])
474
+ self.default = self.parse_default(obj['default'])
475
+ self.value = self.default
476
+
477
+ def parse_value(self, value: str, check: bool = False):
478
+ """
479
+ parses the option value from a string
480
+ :param value: string that has the value of the option
481
+ :param check: boolean that indicates if the value must be validated
482
+ :return: parsed value. If the value is None or empty, the default value is returned
483
+ """
484
+ if value is None or value == "":
485
+ return self.default
486
+ v = str(value)
487
+ if check:
488
+ self.check_value(v)
489
+ return v.lower()
490
+
491
+ def parse_default(self, value: str):
492
+ """
493
+ parse the default value of the option, no validation is performed
494
+ :param value: string with the default value
495
+ :return: the parses value or None if default is None or empty
496
+ """
497
+ if not value:
498
+ return None
499
+ elif not isinstance(value, str):
500
+ return str(value).lower()
501
+ else:
502
+ return value.lower()
503
+
504
+ def check_value(self, value):
505
+ """
506
+ checks a value is in the option domain
507
+ :param value: value to be checked
508
+ :return: none
509
+ :raises OptionError: if value is not string or value is not within domain
510
+ """
511
+ if not isinstance(value, str):
512
+ raise OptionError(f"Option {underline}{self.name}{reset} type error value "
513
+ f"{bold}{value}{reset} is not string")
514
+ elif self.domain is None:
515
+ return
516
+ elif not (value.lower() in self.domain):
517
+ raise OptionError(f"Option {underline}{self.name}{reset} value/default: "
518
+ f"{bold}{str(value)}{reset} is not within the domain ({', '.join(self.domain)})")
519
+
520
+ def parse_domain(self, domain: str):
521
+ """
522
+ parses the domain of an option
523
+ :param domain: string that defines the domain in the format (value1, value2, value3, ...)
524
+ :return: returns a list of size N with the different values [value1, value2, value3, ..., valueN]
525
+ :raises OptionError: if the provided domain has repeated values or is empty
526
+ """
527
+ if not domain: return None
528
+ d = domain.strip("()").split(",")
529
+ # TODO: how to validate string craceplot option domains when some options do not have domain
530
+ if d[0] == "":
531
+ return None
532
+ # if d[0]=="":
533
+ # raise OptionError(f"Domain for string option "
534
+ # f"{underline}{self.name}{reset} is empty or cannot be parsed")
535
+ if len(d) > len(set(d)):
536
+ raise OptionError(f"Domain for string option {underline}{self.name}{reset} "
537
+ f"has repeated values in domain: {domain}")
538
+ d = [x.strip().lower() for x in d]
539
+ return d
540
+
541
+ class BooleanOption(Option):
542
+ """
543
+ class the handles boolean craceplot options
544
+
545
+ """
546
+
547
+ def __init__(self, obj):
548
+ """
549
+ Creates a Boolean Option object
550
+ :param obj: dictionary of the option read from the json setting file
551
+ """
552
+ # add general variables
553
+ super()._check_general_options(obj)
554
+ super()._set_general_options(obj)
555
+
556
+ # other variables
557
+ self.default = self.parse_default(obj['default'])
558
+ self.value = self.default
559
+
560
+ def set_value(self, value: str):
561
+ """
562
+ sets the value of the option
563
+ :param value: the value to be set
564
+ :return: none
565
+ """
566
+ self.value = self.parse_value(value)
567
+
568
+ def parse_value(self, value: str):
569
+ """
570
+ parses the option value from a string
571
+ :param value: string that has the value of the option
572
+ :return: parsed value. If the value is None or empty, the default value is returned
573
+ """
574
+ if value is None or value == "":
575
+ return self.default
576
+ elif not isinstance(value, bool):
577
+ if isinstance(value, int):
578
+ return bool(value)
579
+ elif value.lower() == "true":
580
+ return bool(True)
581
+ elif value.lower() == "false":
582
+ return bool(False)
583
+ else:
584
+ return bool(eval(value))
585
+ else:
586
+ return value
587
+
588
+ def parse_default(self, value: str):
589
+ """
590
+ parse the default value of the option
591
+ :param value: string with the default value
592
+ :return: the parse value or None if default is None or empty
593
+ """
594
+ if value is None or value == "":
595
+ return None
596
+ elif not isinstance(value, bool):
597
+ if isinstance(value, int):
598
+ return bool(value)
599
+ else:
600
+ return bool(eval(value))
601
+ else:
602
+ return value
603
+
604
+ class FileOption(Option):
605
+ """
606
+ class the handles file and directory path craceplot options
607
+
608
+ """
609
+ def __init__(self, obj):
610
+ """
611
+ Creates a File Option object
612
+ :param obj: dictionary of the option read from the json setting file
613
+ """
614
+ # add general variables
615
+ super()._check_general_options(obj)
616
+ super()._set_general_options(obj)
617
+
618
+ # other variables
619
+ self.input_value = obj['default']
620
+ self.default = self.parse_default(obj['default'])
621
+ self.value = self.default
622
+
623
+ def set_value(self, value: str, check_file: bool = False, check_parent_dir: bool=True):
624
+ """
625
+ sets the value of the option and validates it
626
+ :param value: the value to be set
627
+ :param check_file: boolean that indicated if the file should be validated
628
+ :return: none
629
+ """
630
+ self.input_value = value.strip()
631
+ self.value = self.parse_value(value, check_file, check_parent_dir)
632
+
633
+ def parse_value(self, value: str, check_file: bool = False, check_parent_dir: bool = True):
634
+ """
635
+ parses the option file or directory path from a string.
636
+ :param value: string that has the path
637
+ :param check_file: boolean that indicates the type of validation to be performed:
638
+ - False: only the directory where the file/directory is located should be validated as existing
639
+ - True: the file or directory should be validated as existing
640
+ :return: parsed absolute path. If the value is None or empty, the default value is returned
641
+ """
642
+ if value is None or value == "":
643
+ return None
644
+ value = os.path.expanduser(value)
645
+ fvalue = os.path.abspath(value)
646
+ self.check_value(fvalue, check_file, check_parent_dir)
647
+ return fvalue
648
+
649
+ def parse_default(self, value: str):
650
+ """
651
+ parse the default path of the option, no validation is performed
652
+ :param value: string with the default path
653
+ :return: the parsed absolute path or None if default is None or empty
654
+ """
655
+ if value is None or value == "":
656
+ return None
657
+ fvalue = os.path.abspath(value)
658
+ return fvalue
659
+
660
+ def check_value(self, value, check_file: bool = False, check_parent_dir:bool =True):
661
+ """
662
+ checks if the file or directory provided exists
663
+ :param value: path to be checked
664
+ :param check_file: boolean that indicates the type of validation to be performed:
665
+ - False: only the directory where the file/directory is located should be validated as existing
666
+ - True: the file or directory should be validated as existing
667
+ :return: none
668
+ :raises OptionError: if the directory/file does not exists
669
+ """
670
+ if value is None:
671
+ return
672
+ if value == "":
673
+ raise OptionError(f"Option {underline}{self.name}{reset}: "
674
+ f"provided file {value} is not valid.")
675
+ if check_file:
676
+ if not os.path.exists(value):
677
+ raise OptionError(f"Option {underline}{self.name}{reset}: "
678
+ f"provided file {value} does not exist.")
679
+ elif check_parent_dir:
680
+ dir_path = os.path.dirname(value)
681
+ if not os.path.exists(dir_path):
682
+ raise OptionError(f"Option {underline}{self.name}{reset}: "
683
+ f"provided directory {dir_path} does not exist (option value {value})")
684
+
685
+ def exists_file(self):
686
+ """
687
+ checks if the file or directory set as option value exists
688
+ :return: True if the value file or directory exists, else False
689
+ """
690
+ if os.path.exists(self.value):
691
+ return True
692
+ return False
693
+
694
+ def check_value_dir(self, writable: bool = False):
695
+ """
696
+ checks if the parent directory of the file/directory set as option value exists and its writable
697
+ :param writable: boolean that indicates if the directory should be checked for writing permissions
698
+ :return: none
699
+ :raises OptionError: if the directory does not exists or does not have writing permissions
700
+ """
701
+ dir_path = os.path.dirname(self.value)
702
+ if not os.path.exists(dir_path):
703
+ raise OptionError(f"Option {underline}{self.name}{reset}: "
704
+ f"directory {dir_path} does not exist (option value {self.value})")
705
+ if writable and (not os.access(dir_path, os.W_OK)):
706
+ raise OptionError(f"Option {underline}{self.name}{reset}: "
707
+ f"directory {dir_path} is not writable (option value {self.value})")
708
+
709
+ def check_value_file(self, writable: bool = False):
710
+ """
711
+ checks if the file set as option value exists and its writable
712
+ :param writable: boolean that indicates if the file should be checked for writing permissions
713
+ :return: none
714
+ :raises OptionError: if the file does not exists or does not have writing permissions
715
+ """
716
+ if not os.path.exists(self.value):
717
+ raise OptionError(f"Option {underline}{self.name}{reset}: "
718
+ f"provided file {bold}{self.value}{reset} does not exist.")
719
+ if writable and (not os.access(self.value, os.W_OK)):
720
+ raise OptionError(f"Option {underline}{self.name}{reset}: "
721
+ f"provided directory {bold}{self.value}{reset} is not writable")
722
+
723
+ def check_executable(self, executable: bool = False):
724
+ """
725
+ checks if the file set as option value exists and its executable
726
+ :param executable: boolean that indicates if the file should be checked for executing permissions
727
+ :return: none
728
+ :raises OptionError: if the file does not exists or does not have executing permissions
729
+ """
730
+ if not os.path.exists(self.value):
731
+ raise OptionError(f"Option {underline}{self.name}{reset}: "
732
+ f"provided file {bold}{self.value}{reset} does not exist.")
733
+ if executable and (not os.access(self.value, os.X_OK)):
734
+ raise OptionError(f"Option {underline}{self.name}{reset}: "
735
+ f"provided directory {bold}{self.value}{reset} is not executable")
736
+
737
+ class ExeOption(Option):
738
+ """
739
+ class the handles executable file path craceplot options
740
+
741
+ """
742
+ def __init__(self, obj):
743
+ """
744
+ Creates and Executable Option
745
+ :param obj: dictionary of the option read from the json setting file
746
+ """
747
+ # add general variables
748
+ super()._check_general_options(obj)
749
+ super()._set_general_options(obj)
750
+
751
+ # other variables
752
+ self.default = self.parse_default(obj['default'])
753
+ self.value = self.default
754
+
755
+
756
+ def parse_value(self, value: str, check: bool = False):
757
+ """
758
+ parses the option file path from a string.
759
+ :param value: string that has the path
760
+ :param check: boolean that indicates if validation should be performed:
761
+ :return: parsed absolute path. If the value is None or empty, the default value is returned
762
+ """
763
+ if value is None or value == "":
764
+ return self.default
765
+ if check:
766
+ check_return,value_ = self.check_value(value)
767
+ if check_return:
768
+ return value_
769
+ value = os.path.expanduser(value)
770
+ fvalue = os.path.abspath(value)
771
+ return fvalue
772
+
773
+ def parse_default(self, value: str):
774
+ """
775
+ parse the default path of the option, no validation is performed
776
+ :param value: string with the default path
777
+ :return: the parsed absolute path or None if default is None or empty
778
+ """
779
+ if value is None or value == "":
780
+ return None
781
+ return str(value)
782
+
783
+ def check_value(self, value):
784
+ """
785
+ checks if the file provided exists and it has execution permissions
786
+ :param value: path to be checked
787
+ :return: none
788
+ :raises OptionError: if the file does not exists or does not have execution permissions
789
+ """
790
+ if re.search('def', value) == None:
791
+ if not os.path.isfile(value):
792
+ raise OptionError(f"Option {underline}{self.name}{reset} "
793
+ f"provided file {value} does not exist.")
794
+ if not os.access(value, os.X_OK):
795
+ raise OptionError(f"Option {underline}{self.name}{reset} "
796
+ f"provided file {value} does not have correct permissions.")
797
+ else:
798
+ try:
799
+ exec(value)
800
+ return True,value
801
+ except SyntaxError:
802
+ raise OptionError(f"Option {underline}{self.name}{reset} "
803
+ f"provided function {value} has syntax errors.")
804
+
805
+ class EnablerOption(Option):
806
+ """
807
+ class the handles craceplot options that signal execution modes (e.g. --help, --onlytest, --check, --version)
808
+
809
+ """
810
+ def __init__(self, obj):
811
+ """
812
+ Creates an Enabler Option object
813
+ :param obj: dictionary of the option read from the json setting file
814
+ """
815
+ # add general variables
816
+ super()._check_general_options(obj)
817
+ super()._set_general_options(obj)
818
+
819
+ # other variables
820
+ self.value = False
821
+ self.default = False
822
+
823
+ def parse_value(self, value, check = False):
824
+ self.value = value
825
+
826
+ class ListOption(Option):
827
+ """
828
+ class the handles string crace options in a list
829
+
830
+ :ivar domain: domain of the option as list [value1, value2, value3,...]
831
+ :ivar value: the selection/combination of choices from the list
832
+ """
833
+ def __init__(self, obj):
834
+ """
835
+ Creates a String Option object
836
+ :param obj: dictionary of the option read from the json setting file
837
+ """
838
+ # add general variables
839
+ super()._check_general_options(obj)
840
+ super()._set_general_options(obj)
841
+
842
+ # other variables
843
+ self.domain = self.parse_domain(obj['domain'])
844
+ self.default = self.parse_default(obj['default'])
845
+ self.value = self.default
846
+
847
+ def parse_value(self, value, check: bool = False):
848
+ """
849
+ parses the option value from a string
850
+ :param value: string that has the value of the option
851
+ :param check: boolean that indicates if the value must be validated
852
+ :return: parsed value. If the value is None or empty, the default value is returned
853
+ """
854
+ if check:
855
+ self.check_value(value)
856
+
857
+ if isinstance(value, list):
858
+ out = []
859
+ for x in value:
860
+ if isinstance(x, str):
861
+ parts = re.split(r"[, ]+", x.strip())
862
+ else:
863
+ parts = [x]
864
+ for p in parts:
865
+ if isinstance(p, str):
866
+ r = parse_range_list(p.strip())
867
+ if r is not None:
868
+ out.extend(r)
869
+ else:
870
+ out.append(p)
871
+ else:
872
+ out.append(p)
873
+ return out
874
+
875
+ elif isinstance(value, str):
876
+ r = parse_range_list(value.strip())
877
+ if r is not None:
878
+ return r
879
+ return value
880
+
881
+ else:
882
+ raise OptionError(
883
+ f"Option {underline}{self.name}{reset} got wrong value {value}"
884
+ )
885
+
886
+ def parse_default(self, value):
887
+ """
888
+ parse the default value of the option, no validation is performed
889
+ :param value: string with the default value
890
+ :return: the parses value or None if default is None or empty
891
+ """
892
+ if value is None or value == "":
893
+ return None
894
+ if isinstance(value, list):
895
+ return value
896
+ if isinstance(value, str):
897
+ r = parse_range_list(value.strip())
898
+ if r is not None:
899
+ return r
900
+ return value
901
+ return value
902
+
903
+
904
+ def check_value(self, value):
905
+ """
906
+ checks a value is in the option domain
907
+ :param value: value to be checked
908
+ :return: none
909
+ :raises OptionError: if value is not string or value is not within domain
910
+ """
911
+ if len(value) > 0:
912
+ for v in value:
913
+ if self.domain is None:
914
+ return
915
+ elif not (v in self.domain):
916
+ raise OptionError(f"Option {underline}{self.name}{reset} value/default:"
917
+ f"{str(v)} is not within the domain ({','.join(self.domain)})")
918
+
919
+ def parse_domain(self, domain: str):
920
+ """
921
+ parses the domain of an option
922
+ :param domain: string that defines the domain in the format (value1, value2, value3, ...)
923
+ :return: returns a list of size N with the different values [value1, value2, value3, ..., valueN]
924
+ :raises OptionError: if the provided domain has repeated values or is empty
925
+ """
926
+ if not domain is None:
927
+ d = domain.strip("()").split(",")
928
+ # TODO: how to validate string crace option domains when some options do not have domain
929
+ if d[0] == "":
930
+ return None
931
+ if len(d) > len(set(d)):
932
+ raise OptionError(f"Domain for string option {underline}{self.name}{reset} "
933
+ f"has repeated values in domain: {domain}")
934
+ d = [x.strip()for x in d]
935
+ return d
936
+ else:
937
+ return None
938
+
939
+ class UnionOption(Option):
940
+ """
941
+ class the handles string crace options in a union
942
+
943
+ :ivar domain: domain of the option as list [value1, value2, value3,...]
944
+ :ivar value: the selection/combination of choices from the list
945
+ """
946
+ def __init__(self, obj):
947
+ """
948
+ Creates a String Option object
949
+ :param obj: dictionary of the option read from the json setting file
950
+ """
951
+ # add general variables
952
+ super()._check_general_options(obj)
953
+ super()._set_general_options(obj)
954
+
955
+ # other variables
956
+ self.domain = self.parse_domain(obj['domain'])
957
+ self.default = self.parse_default(obj['default'])
958
+ self.value = self.default
959
+
960
+ def parse_domain(self, domain: str):
961
+ """
962
+ parses the domain of an option
963
+ :param domain: string that defines the domain in the format (value1, value2, value3, ...)
964
+ :return: returns a list of size N with the different values [value1, value2, value3, ..., valueN]
965
+ :raises OptionError: if the provided domain has repeated values or is empty
966
+ """
967
+ if domain is None:
968
+ raise OptionError(f"Union option {underline}{self.name}{reset} must include {bold}domain{reset}")
969
+
970
+ try:
971
+ new = eval(domain, {"Union": Union,
972
+ "Literal": Literal,
973
+ "Optional": Optional,
974
+ "bool": bool,
975
+ "List": list,
976
+ "list": list,
977
+ "str": str,
978
+ "int": int,
979
+ "float": float,
980
+ "None": None,})
981
+ except:
982
+ raise OptionError(f"Union option {underline}{self.name}{reset} including wrong {bold}domain{reset}")
983
+
984
+ return new
985
+
986
+ def parse_value(self, value, check: bool = False, check_default: bool=False):
987
+ """
988
+ parses the option value from a string
989
+ :param value: string that has the value of the option
990
+ :param check: boolean that indicates if the value must be validated
991
+ :return: parsed value. If the value is None or empty, the default value is returned
992
+ """
993
+ if value is None or value == "":
994
+ parsed = None
995
+ else:
996
+ parsed = self._parse_by_typing_domain(value, self.domain)
997
+
998
+ if check:
999
+ self.check_value(parsed, check_default)
1000
+
1001
+ return parsed
1002
+
1003
+ def parse_default(self, value):
1004
+ """
1005
+ parse the default value of the option, no validation is performed
1006
+ :param value: string with the default value
1007
+ :return: the parses value or None if default is None or empty
1008
+ """
1009
+ if value is None or value == "":
1010
+ return None
1011
+ return self.parse_value(value, True, True)
1012
+
1013
+ def set_value(self, value: str):
1014
+ """
1015
+ sets the value of the option
1016
+ :param value: the value to be set
1017
+ :return: none
1018
+ """
1019
+ self.value = self.parse_value(value, check=True)
1020
+
1021
+ def check_value(self, value, check_default: bool=False):
1022
+ """
1023
+ checks a value is in the option domain Union[Literal[..], ]
1024
+ :param value: value to be checked
1025
+ :return: none
1026
+ :raises OptionError: if value is not string or value is not within domain
1027
+ """
1028
+ if value is None or value == "":
1029
+ return None
1030
+
1031
+ if not check_default and value == self.default: return
1032
+
1033
+ check_type(value=value, expected=self.domain, name=self.name)
1034
+
1035
+ def _parse_by_typing_domain(self, value, expected):
1036
+ """
1037
+ parse value as in the provided Union domain
1038
+
1039
+ :param value: value to be checked
1040
+ :param expected: expected type for the value
1041
+ :return: none
1042
+ """
1043
+ origin = get_origin(expected)
1044
+ args = get_args(expected)
1045
+
1046
+
1047
+ # --------------------------------
1048
+ # Union / Optional
1049
+ # --------------------------------
1050
+ if origin is Union:
1051
+ last_error = None
1052
+ for t in args:
1053
+ try:
1054
+ return self._parse_by_typing_domain(value, t)
1055
+ except Exception as e:
1056
+ last_error = e
1057
+ raise last_error
1058
+
1059
+ # --------------------------------
1060
+ # Literal
1061
+ # --------------------------------
1062
+ if origin is Literal:
1063
+ # case-insensitive for string literals
1064
+ if isinstance(value, str) and all(isinstance(a, str) for a in args):
1065
+ v = value.strip().lower()
1066
+ for a in args:
1067
+ if v == a.lower():
1068
+ # return canonical literal from domain
1069
+ return a
1070
+
1071
+ # fallback: non-str literals (int, bool, etc.)
1072
+ try:
1073
+ v = value
1074
+ if isinstance(value, str):
1075
+ v = ast.literal_eval(value)
1076
+ except Exception:
1077
+ pass
1078
+
1079
+ if v in args:
1080
+ return v
1081
+
1082
+ raise OptionError(
1083
+ f"Option {underline}{self.name}{reset} value {bold}{value}{reset} "
1084
+ f"is not within domain {self.domain}"
1085
+ )
1086
+
1087
+ # --------------------------------
1088
+ # list / list[T]
1089
+ # --------------------------------
1090
+ if origin is list or expected is list:
1091
+ print(f"in parse: {value}, {type(value)}, {origin}, {expected}")
1092
+ if isinstance(value, list):
1093
+ v = value
1094
+ elif isinstance(value, str):
1095
+ r = parse_range_list(value.strip())
1096
+ print("parsed list: ", r)
1097
+ if r is not None:
1098
+ v = r
1099
+ else:
1100
+ try:
1101
+ v = ast.literal_eval(value)
1102
+ if not isinstance(v, list):
1103
+ v = value
1104
+ except Exception:
1105
+ v = value
1106
+ else:
1107
+ v = value
1108
+
1109
+ # not a list -> single element
1110
+ if not isinstance(v, list):
1111
+ if len(args) == 1:
1112
+ return self._parse_by_typing_domain(v, args[0])
1113
+
1114
+ raise OptionError(
1115
+ f"Option {underline}{self.name}{reset} value {bold}{value}{reset} "
1116
+ f"is not within domain {self.domain}"
1117
+ )
1118
+
1119
+ # real list
1120
+ if len(args) == 0:
1121
+ return v
1122
+
1123
+ elem_type = args[0]
1124
+ return [self._parse_by_typing_domain(x, elem_type) for x in v]
1125
+
1126
+ # --------------------------------
1127
+ # None
1128
+ # --------------------------------
1129
+ if expected is type(None):
1130
+ if value is None:
1131
+ return None
1132
+
1133
+ if isinstance(value, str) and value.lower() == "none":
1134
+ return None
1135
+
1136
+ raise OptionError(
1137
+ f"Option {underline}{self.name}{reset} value {bold}{value}{reset} "
1138
+ f"is not within domain {self.domain}"
1139
+ )
1140
+
1141
+ # --------------------------------
1142
+ # basic types
1143
+ # --------------------------------
1144
+ if expected in (str, int, float, bool):
1145
+
1146
+ # already correct type
1147
+ if isinstance(value, expected):
1148
+ return value
1149
+
1150
+ # only parse from string
1151
+ if not isinstance(value, str):
1152
+ return value
1153
+
1154
+ if expected is str:
1155
+ return value
1156
+
1157
+ if expected is int:
1158
+ return int(value)
1159
+
1160
+ if expected is float:
1161
+ return float(value)
1162
+
1163
+ # bool
1164
+ v = value.lower()
1165
+ if v in ("true", "1", "yes", "y"):
1166
+ return True
1167
+ if v in ("false", "0", "no", "n"):
1168
+ return False
1169
+
1170
+ raise OptionError(
1171
+ f"Option {underline}{self.name}{reset} expects bool, got {bold}{value}{reset}"
1172
+ )
1173
+
1174
+ # --------------------------------
1175
+ # other object types (e.g. Colormap)
1176
+ # --------------------------------
1177
+ # Try python literal first
1178
+ if isinstance(value, str):
1179
+ try:
1180
+ v = ast.literal_eval(value)
1181
+ if isinstance(v, expected):
1182
+ return v
1183
+ except Exception:
1184
+ pass
1185
+
1186
+ # If already correct object
1187
+ if isinstance(value, expected):
1188
+ return value
1189
+
1190
+ # last fallback: return as is (will be rejected by check later)
1191
+ return value
1192
+
1193
+
1194
+ exclude_options = ['help', 'man', 'version', 'dpi', 'showfliers', 'showmeans', 'showscale', 'statisticalTest']
1195
+ class ParseOptions:
1196
+ """
1197
+ Parse arguments from the input
1198
+ """
1199
+ def from_input(arguments=None, console=False, add=False):
1200
+ options = CplotOptions(arguments=arguments, console=console, add=add)
1201
+
1202
+ return options
1203
+
1204
+ class CplotOptions:
1205
+ """
1206
+ Class contains the set of options that crace plot defined for the results analysis.
1207
+ The options are defined in a setting file provided by the package.
1208
+ The results are record from an exsiting Crace execution.
1209
+ """
1210
+ def __init__(self, arguments, console=False, add=False):
1211
+ """
1212
+ Initialization of an LoadOptions object. Arguments must be provided.
1213
+
1214
+ :param arguments: command line arguments provided by the user (it is not None)
1215
+ """
1216
+
1217
+ # TODO: option definition file should be set in the module/package.
1218
+ # load options definition from json file
1219
+ package_dir, _ = os.path.split(__file__)
1220
+ filename = os.path.dirname(package_dir) + "/_settings/_options.json"
1221
+ with open(filename, "r") as read_file:
1222
+ all_options = json.loads(read_file.read(), object_hook=_option_decoder)
1223
+
1224
+ # create option variables (default values are put in place)
1225
+ self.options = []
1226
+ for o in all_options:
1227
+ self.options.append(o.name)
1228
+ setattr(self, o.name, o)
1229
+
1230
+ # arguments is the whole line of input
1231
+ self.arguments = arguments
1232
+
1233
+ # load option values
1234
+ try:
1235
+ self._load_options(arguments, console, add)
1236
+ except OptionError as err:
1237
+ print("#\n! There was an error while reading crace options :")
1238
+ print(f"! {err}")
1239
+ print_info()
1240
+ sys.exit(1)
1241
+ except FileError as err:
1242
+ print("#\n! There was an error while reading crace options file :")
1243
+ print(f"! {err}")
1244
+ sys.exit(1)
1245
+ except ExitError as err:
1246
+ if console:
1247
+ pass
1248
+ sys.exit(0)
1249
+ except Exception as err:
1250
+ print("#\n! There was an error: ")
1251
+ print(f"! {err}")
1252
+ err = traceback.format_exc()
1253
+ print(err)
1254
+ sys.exit(1)
1255
+
1256
+ def _load_options(self, arguments, console=False, add=False):
1257
+ """
1258
+ Load option values from arguments
1259
+
1260
+ :param arguments: command line arguments provided by the user (it is not None here)
1261
+ :return: none
1262
+ """
1263
+ # check arguments
1264
+ any_arg = False
1265
+ any_other_arg = False
1266
+ for o in self.options:
1267
+ if self._get_option(o).long in arguments or self._get_option(o).short in arguments:
1268
+ any_arg = True
1269
+ if self._get_option(o).name != 'logDir':
1270
+ any_other_arg = True
1271
+ break
1272
+ # if not any_arg:
1273
+ # raise OptionError(f"Missing valid option(s)")
1274
+
1275
+ # check for help command
1276
+ if self.help.long in arguments:
1277
+ print_help_long()
1278
+ raise ExitError("")
1279
+
1280
+ # check for help command
1281
+ if self.help.short in arguments:
1282
+ print_help_short()
1283
+ raise ExitError("")
1284
+
1285
+ # check for version command
1286
+ if self.version.short in arguments:
1287
+ print_version_short()
1288
+ raise ExitError("")
1289
+
1290
+ if self.version.long in arguments:
1291
+ print_version_long()
1292
+ raise ExitError("")
1293
+
1294
+ if self.man.long in arguments or self.man.short in arguments:
1295
+ index = arguments.index(self.man.long) if self.man.long in arguments else arguments.index(self.man.short)
1296
+ if len(arguments) > index+1:
1297
+ if arguments[index+1] not in self.options:
1298
+ raise OptionError(f"{arguments[index+1]} is an incorrect crace option name.")
1299
+ else:
1300
+ print_man(self._get_option(arguments[index+1]))
1301
+ else:
1302
+ raise OptionError(f"One option shoud be provided for man.")
1303
+ raise ExitError("")
1304
+
1305
+ print(f"#{f'':-^{WIDTH-1}}")
1306
+ print("# Reading crace plot options...")
1307
+ print("# Arguments: {}".format(arguments))
1308
+ print("#\n# Loading crace plot options...")
1309
+
1310
+ # check logDir
1311
+ try:
1312
+ if self.logDir.long in arguments:
1313
+ i = arguments.index(self.logDir.long)
1314
+ self.logDir.set_value(arguments[i+1])
1315
+ elif self.logDir.short in arguments:
1316
+ i = arguments.index(self.logDir.short)
1317
+ self.logDir.set_value(arguments[i+1])
1318
+ elif self.logDir.exists_file():
1319
+ print("# Default race_log folder was found in current directory.")
1320
+ else:
1321
+ # in this case, we remove the scenario file and assume parameters
1322
+ # will be handled manually
1323
+ raise OptionError(f"Option {underline}logDir(-l){reset} must be provided.")
1324
+ except Exception as e:
1325
+ if isinstance(e, IndexError):
1326
+ raise OptionError(f"Option {underline}logDir(-l){reset} has no value.")
1327
+ else:
1328
+ raise OptionError(e)
1329
+
1330
+ if self.logDir.value and not self.logDir.exists_file():
1331
+ raise OptionError(f"The provided race_log folder {self.logDir.value} is not readable or does not exist.")
1332
+
1333
+ if self.logDir.is_default():
1334
+ self.logDir.set_value(os.path.dirname(self.logDir.value))
1335
+
1336
+ if console and not any_other_arg:
1337
+ return
1338
+
1339
+ options_long = [self._get_option(x).long for x in self.options]
1340
+ options_short = [self._get_option(x).short for x in self.options]
1341
+
1342
+ for o in self.options:
1343
+ if len(arguments) < 1:
1344
+ break
1345
+ if self._get_option(o).short in arguments:
1346
+ index = arguments.index(self._get_option(o).short)
1347
+ elif self._get_option(o).long in arguments:
1348
+ index = arguments.index(self._get_option(o).long)
1349
+ else:
1350
+ continue
1351
+
1352
+ if isinstance(self._get_option(o), EnablerOption):
1353
+ self._set_option(o, True)
1354
+ del arguments[index]
1355
+ elif isinstance(self._get_option(o), ListOption):
1356
+ s = []
1357
+ start = index
1358
+ end = start + 1
1359
+ while (end < len(arguments) and
1360
+ arguments[end] not in options_long and
1361
+ arguments[end] not in options_short):
1362
+ s.append(arguments[end])
1363
+ end += 1
1364
+ for i in reversed(range(start, end)):
1365
+ del arguments[i]
1366
+ self._set_option(o, s)
1367
+ else:
1368
+ try:
1369
+ self._set_option(o, arguments[index + 1])
1370
+ del arguments[index:(index + 2)]
1371
+ except Exception as e:
1372
+ if isinstance(e, OptionError) or isinstance(e, ValueError):
1373
+ raise OptionError(e)
1374
+ else:
1375
+ raise OptionError(f"{self._get_option(o).name} has no value.")
1376
+
1377
+ if len(arguments) > 0:
1378
+ raise OptionError(f"Argument not recognized: {arguments[0]}")
1379
+
1380
+ if self.fileName.value is None:
1381
+ current_time = datetime.now()
1382
+ formatted_time = current_time.strftime("Plot_%Y%m%d_%H%M%S")
1383
+ self.fileName.value = formatted_time
1384
+
1385
+ def _get_option(self, option_name):
1386
+ """
1387
+ get a option in the LoadOption object
1388
+ :param option_name: name of the option to be returned
1389
+ :return: value of the option
1390
+ :raises OptionError: if the option name is unknown
1391
+ """
1392
+ if option_name not in self.options:
1393
+ raise OptionError(f"Attempt to get unknown option {underline}option_name{reset}")
1394
+ return getattr(self, option_name)
1395
+
1396
+ def _set_option(self, option_name, option_value):
1397
+ """
1398
+ set the value of an option in the LoadOptions object
1399
+ :param option_name: name of the option
1400
+ :param option_value: value to be set
1401
+ :return: none
1402
+ :raises OptionError: if the option name is unknown
1403
+ """
1404
+ if option_name not in self.options:
1405
+ raise OptionError(f"Attempt to set unknown option {underline}option_name{reset} with value {bold}option_value{reset}")
1406
+ getattr(self, option_name).set_value(option_value)
1407
+
1408
+ def _print_options(self):
1409
+ """
1410
+ Print all options
1411
+ """
1412
+ print('# Crace plot options: ')
1413
+ for o in self.options:
1414
+ v = self._get_option(o).value
1415
+ if o not in exclude_options:
1416
+ print("# {}: {}".format(o, v))
1417
+ print(f"#{f'':-^{WIDTH-1}}")
1418
+
1419
+
1420
+
1421
+ ################################ Print details ##############################
1422
+ # print_cplot_header #
1423
+ # print_help #
1424
+ # print_help_long #
1425
+ # print_help_short #
1426
+ # print_man #
1427
+ # print_info #
1428
+ # print_version_long #
1429
+ # print_version_short #
1430
+ # print_args #
1431
+ #############################################################################
1432
+ def print_cplot_header():
1433
+ # TODO: make version automatic
1434
+ print(f"#{f'':-^{WIDTH-1}}")
1435
+ print(f'# {info.description}')
1436
+ print(f'# Version: {info.version}')
1437
+ print(f'# {info.copyright}')
1438
+ print('#')
1439
+ print('# Authors: ')
1440
+ for x in info.authors: print(f'# {x}')
1441
+ print('#')
1442
+ print(f'# Contact:\n# {info.contact} ({info.contact_email})')
1443
+ print('#')
1444
+ print(f'# Check more details at {info.url} ')
1445
+ line = format_string(info.license, hanging=0, space=True)
1446
+ print("\n".join(f"# {line}" for line in line.splitlines()))
1447
+ print('#')
1448
+ print(f'# installed at: {CPLOT_HOME}')
1449
+
1450
+ print(f"#{f'':-^{WIDTH-1}}")
1451
+
1452
+
1453
+ def print_help():
1454
+ """
1455
+ Print help information of crace-plot
1456
+ """
1457
+ print_cplot_header()
1458
+ print("# called with: --help (-h)")
1459
+ package_dir, _ = os.path.split(__file__)
1460
+ filename = os.path.dirname(package_dir) + "/_settings/_options.json"
1461
+ with open(filename, "r") as read_file:
1462
+ all_options = json.loads(read_file.read(), object_hook=_option_decoder)
1463
+
1464
+ options = []
1465
+ for o in all_options:
1466
+ options.append(o.name)
1467
+ if o.short is not None and o.short != "":
1468
+ print("#\n# {:<4}, {:<29}{}".format(o.short, o.long, o.description))
1469
+ else:
1470
+ print("#\n# {:<6}{:<29}{}".format(o.short,o.long, o.description))
1471
+ if o.default is not None or o.default != "":
1472
+ print("# {:<35}Default: {}".format('', o.default))
1473
+ if o.type in ['i', 's']:
1474
+ print("# {:<35}Domain: {}".format('', o.domain))
1475
+ print(f"#\n#{f'':-^{WIDTH-1}}")
1476
+
1477
+
1478
+ def print_help_long():
1479
+ print_cplot_header()
1480
+ package_dir, _ = os.path.split(__file__)
1481
+ filename = os.path.dirname(package_dir) + "/_settings/_options.json"
1482
+ with open(filename, "r") as read_file:
1483
+ all_options = json.loads(read_file.read(), object_hook=_option_decoder)
1484
+
1485
+ from collections import defaultdict
1486
+ groups = defaultdict(list)
1487
+ for o in all_options:
1488
+ if o.critical > 1: continue
1489
+ groups[o.section].append(o)
1490
+
1491
+ options = []
1492
+ for section, options_list in groups.items():
1493
+ print(f"\n {f' {section.upper()} ':=^{WIDTH-4}}\n")
1494
+
1495
+ for o in options_list:
1496
+ if o.critical > 1:
1497
+ continue
1498
+ options.append(o.name)
1499
+
1500
+ print(f"{bold}# Name:{reset} {o.name}")
1501
+ print(f" {bold}Long:{reset} {o.long if o.long else 'None'}")
1502
+ print(f" {bold}Short:{reset} {o.short if o.short else 'None'}")
1503
+ if o.type in ['i', 's']:
1504
+ print(f""" {format_string(f"{bold}Domain:{reset} {o.domain if o.domain else 'None'}", hanging=2)}""")
1505
+ if o.default is not None and o.default != "" and o.default != []:
1506
+ print(f" {bold}Default:{reset} {o.default}")
1507
+ else:
1508
+ print(f" {bold}Default:{reset} None")
1509
+ if o.description:
1510
+ print(f""" {format_string(f"{bold}Description:{reset} {o.description}", hanging=2)}""")
1511
+ if o.vignettes:
1512
+ print(f""" {format_string(f"{bold}Vignettes:{reset} {o.vignettes}", hanging=2, space=True)}""")
1513
+ print()
1514
+
1515
+ print(f'\n# call with {bold}--man [option_name]{reset} to check the details of specified option')
1516
+ print(f"#{f'':-^{WIDTH-1}}")
1517
+
1518
+
1519
+ def print_help_short():
1520
+ print_cplot_header()
1521
+ package_dir, _ = os.path.split(__file__)
1522
+ filename = os.path.dirname(package_dir) + "/_settings/_options.json"
1523
+ with open(filename, "r") as read_file:
1524
+ all_options = json.loads(read_file.read(), object_hook=_option_decoder)
1525
+
1526
+ from collections import defaultdict
1527
+ groups = defaultdict(list)
1528
+ for o in all_options:
1529
+ if o.critical > 1: continue
1530
+ groups[o.section].append(o)
1531
+
1532
+ max_name_l = max([len(o.name) for o in all_options if o.critical <= 1])
1533
+ max_short_l = max([len(o.short) for o in all_options if o.critical <= 1])
1534
+ max_long_l = max([len(o.long) for o in all_options if o.critical <= 1])
1535
+ sum_max_l = sum([max_name_l, max_short_l, max_long_l])
1536
+
1537
+ options = []
1538
+ for section, options_list in groups.items():
1539
+ print(f"# {bold}{section}{reset}")
1540
+
1541
+ for o in options_list:
1542
+ if o.critical > 1:
1543
+ continue
1544
+ options.append(o.name)
1545
+ format_de = format_string(o.description, width=WIDTH*1.2, hanging=sum_max_l+5) if o.description else ""
1546
+ print(f" {o.name:<{max_name_l+1}}{o.short:<{max_short_l+1}}{o.long:<{max_long_l+1}}{format_de}")
1547
+ print()
1548
+ print(f'\n# call with {bold}--man [option_name]{reset} to check the details of specified option')
1549
+ print(f"#{f'':-^{WIDTH-1}}")
1550
+
1551
+
1552
+ def print_man(o):
1553
+ """
1554
+ print selected options: critical <= 1
1555
+ """
1556
+ if o.critical > 1 and o.critical <= 4:
1557
+ print(f"Option {bold}{o.name}{reset} is not available in crace currently.")
1558
+ raise ExitError("")
1559
+ elif o.critical > 4 and o.critical <7:
1560
+ print(f"{bold}{o.name}{reset} is not an available crace option.")
1561
+ raise ExitError("")
1562
+
1563
+ print(f"{bold}Name:{reset} {o.name}")
1564
+ print(f"{bold}Long:{reset} {o.long if o.long else 'None'}")
1565
+ print(f"{bold}Short:{reset} {o.short if o.short else 'None'}")
1566
+ if o.type in ['i', 's']:
1567
+ print(f"""\n{format_string(f"{bold}Domain:{reset} {o.domain if o.domain else 'None'}", hanging=0)}\n""")
1568
+ if o.default is not None and o.default != "" and o.default != []:
1569
+ print(f"{bold}Default:{reset} {o.default}")
1570
+ else:
1571
+ print(f"{bold}Default:{reset} None")
1572
+ print(f"""\n{format_string(f"{bold}Description:{reset} {o.description}", hanging=0)}\n""")
1573
+ print(f"""{format_string(f"{bold}Vignettes:{reset} {o.vignettes}", hanging=0, space=True)}""")
1574
+
1575
+
1576
+ def print_info():
1577
+ print(f"! You can check the information of HELP ({bold}-h, --help{reset}) "
1578
+ f"or man page ({bold}--man / -m{reset}).\n")
1579
+
1580
+
1581
+ def print_version_long():
1582
+ print_cplot_header()
1583
+ print_cite_info()
1584
+
1585
+
1586
+ def print_version_short():
1587
+ print(f"crace {info.version}")
1588
+
1589
+
1590
+ def print_args(args):
1591
+ print(f"#{f'':-^{WIDTH-1}}")
1592
+ print('# Crace plot arguments: ')
1593
+ for name, value in vars(args).items():
1594
+ if name in ['data', 'options']: continue
1595
+ print(f"# {name}: {value}")
1596
+ print(f"#{f'':-^{WIDTH-1}}")
1597
+
1598
+
1599
+ def print_cite_info():
1600
+ cit = format_string(info.citiation, width=WIDTH, hanging=0, space=True)
1601
+ print("\n".join(f"# {line}" for line in cit.splitlines()))
1602
+ print(f"#{f'':-^{WIDTH-1}}")