siliconcompiler 0.32.3__py3-none-any.whl → 0.33.1__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 (280) hide show
  1. siliconcompiler/__init__.py +19 -2
  2. siliconcompiler/_common.py +5 -0
  3. siliconcompiler/_metadata.py +1 -1
  4. siliconcompiler/apps/sc.py +2 -2
  5. siliconcompiler/apps/sc_install.py +10 -3
  6. siliconcompiler/apps/sc_issue.py +1 -1
  7. siliconcompiler/apps/sc_remote.py +10 -5
  8. siliconcompiler/apps/sc_show.py +2 -2
  9. siliconcompiler/apps/utils/replay.py +5 -3
  10. siliconcompiler/asic.py +120 -0
  11. siliconcompiler/checklist.py +150 -0
  12. siliconcompiler/core.py +299 -299
  13. siliconcompiler/flowgraph.py +803 -515
  14. siliconcompiler/fpga.py +84 -0
  15. siliconcompiler/metric.py +479 -0
  16. siliconcompiler/optimizer/vizier.py +2 -3
  17. siliconcompiler/package/__init__.py +29 -6
  18. siliconcompiler/pdk.py +415 -0
  19. siliconcompiler/record.py +453 -0
  20. siliconcompiler/remote/client.py +15 -5
  21. siliconcompiler/remote/schema.py +116 -112
  22. siliconcompiler/remote/server.py +9 -6
  23. siliconcompiler/report/dashboard/cli/__init__.py +14 -721
  24. siliconcompiler/report/dashboard/cli/board.py +899 -0
  25. siliconcompiler/report/dashboard/web/__init__.py +10 -10
  26. siliconcompiler/report/dashboard/web/components/__init__.py +5 -4
  27. siliconcompiler/report/dashboard/web/components/flowgraph.py +3 -3
  28. siliconcompiler/report/dashboard/web/components/graph.py +6 -3
  29. siliconcompiler/report/dashboard/web/state.py +1 -1
  30. siliconcompiler/report/dashboard/web/utils/__init__.py +4 -3
  31. siliconcompiler/report/html_report.py +2 -3
  32. siliconcompiler/report/report.py +22 -11
  33. siliconcompiler/report/summary_image.py +1 -1
  34. siliconcompiler/report/summary_table.py +3 -3
  35. siliconcompiler/report/utils.py +21 -14
  36. siliconcompiler/scheduler/__init__.py +234 -1206
  37. siliconcompiler/scheduler/run_node.py +2 -1
  38. siliconcompiler/scheduler/send_messages.py +11 -5
  39. siliconcompiler/scheduler/slurm.py +11 -44
  40. siliconcompiler/scheduler/taskscheduler.py +320 -0
  41. siliconcompiler/schema/__init__.py +19 -2
  42. siliconcompiler/schema/baseschema.py +493 -0
  43. siliconcompiler/schema/cmdlineschema.py +250 -0
  44. siliconcompiler/{sphinx_ext → schema/docs}/__init__.py +3 -1
  45. siliconcompiler/{sphinx_ext → schema/docs}/dynamicgen.py +63 -81
  46. siliconcompiler/{sphinx_ext → schema/docs}/schemagen.py +73 -85
  47. siliconcompiler/{sphinx_ext → schema/docs}/utils.py +12 -13
  48. siliconcompiler/schema/editableschema.py +136 -0
  49. siliconcompiler/schema/journalingschema.py +238 -0
  50. siliconcompiler/schema/namedschema.py +41 -0
  51. siliconcompiler/schema/packageschema.py +101 -0
  52. siliconcompiler/schema/parameter.py +791 -0
  53. siliconcompiler/schema/parametertype.py +323 -0
  54. siliconcompiler/schema/parametervalue.py +736 -0
  55. siliconcompiler/schema/safeschema.py +37 -0
  56. siliconcompiler/schema/schema_cfg.py +109 -1789
  57. siliconcompiler/schema/utils.py +5 -68
  58. siliconcompiler/schema_obj.py +119 -0
  59. siliconcompiler/tool.py +1416 -0
  60. siliconcompiler/tools/_common/__init__.py +6 -10
  61. siliconcompiler/tools/_common/asic.py +5 -5
  62. siliconcompiler/tools/_common/sdc/sc_constraints.sdc +1 -1
  63. siliconcompiler/tools/bluespec/convert.py +9 -8
  64. siliconcompiler/tools/builtin/_common.py +9 -2
  65. siliconcompiler/tools/builtin/concatenate.py +7 -3
  66. siliconcompiler/tools/builtin/minimum.py +7 -2
  67. siliconcompiler/tools/builtin/mux.py +8 -2
  68. siliconcompiler/tools/builtin/nop.py +7 -2
  69. siliconcompiler/tools/builtin/verify.py +11 -5
  70. siliconcompiler/tools/chisel/convert.py +10 -10
  71. siliconcompiler/tools/genfasm/bitstream.py +3 -3
  72. siliconcompiler/tools/ghdl/convert.py +1 -1
  73. siliconcompiler/tools/icarus/compile.py +4 -4
  74. siliconcompiler/tools/icepack/bitstream.py +6 -1
  75. siliconcompiler/tools/klayout/convert_drc_db.py +5 -0
  76. siliconcompiler/tools/klayout/drc.py +2 -2
  77. siliconcompiler/tools/klayout/klayout_export.py +0 -1
  78. siliconcompiler/tools/klayout/klayout_show.py +6 -6
  79. siliconcompiler/tools/klayout/klayout_utils.py +15 -22
  80. siliconcompiler/tools/netgen/count_lvs.py +2 -2
  81. siliconcompiler/tools/netgen/lvs.py +1 -1
  82. siliconcompiler/tools/nextpnr/apr.py +6 -1
  83. siliconcompiler/tools/nextpnr/nextpnr.py +4 -4
  84. siliconcompiler/tools/openroad/_apr.py +15 -2
  85. siliconcompiler/tools/openroad/rdlroute.py +3 -3
  86. siliconcompiler/tools/openroad/scripts/apr/postamble.tcl +1 -1
  87. siliconcompiler/tools/openroad/scripts/apr/preamble.tcl +5 -5
  88. siliconcompiler/tools/openroad/scripts/apr/sc_antenna_repair.tcl +2 -2
  89. siliconcompiler/tools/openroad/scripts/apr/sc_clock_tree_synthesis.tcl +2 -2
  90. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_placement.tcl +2 -2
  91. siliconcompiler/tools/openroad/scripts/apr/sc_detailed_route.tcl +2 -2
  92. siliconcompiler/tools/openroad/scripts/apr/sc_endcap_tapcell_insertion.tcl +2 -2
  93. siliconcompiler/tools/openroad/scripts/apr/sc_fillercell_insertion.tcl +2 -2
  94. siliconcompiler/tools/openroad/scripts/apr/sc_fillmetal_insertion.tcl +2 -2
  95. siliconcompiler/tools/openroad/scripts/apr/sc_global_placement.tcl +2 -2
  96. siliconcompiler/tools/openroad/scripts/apr/sc_global_route.tcl +2 -2
  97. siliconcompiler/tools/openroad/scripts/apr/sc_init_floorplan.tcl +3 -9
  98. siliconcompiler/tools/openroad/scripts/apr/sc_macro_placement.tcl +3 -3
  99. siliconcompiler/tools/openroad/scripts/apr/sc_metrics.tcl +2 -2
  100. siliconcompiler/tools/openroad/scripts/apr/sc_pin_placement.tcl +2 -2
  101. siliconcompiler/tools/openroad/scripts/apr/sc_power_grid.tcl +2 -2
  102. siliconcompiler/tools/openroad/scripts/apr/sc_repair_design.tcl +2 -2
  103. siliconcompiler/tools/openroad/scripts/apr/sc_repair_timing.tcl +2 -2
  104. siliconcompiler/tools/openroad/scripts/apr/sc_write_data.tcl +2 -2
  105. siliconcompiler/tools/openroad/scripts/common/procs.tcl +75 -1
  106. siliconcompiler/tools/openroad/scripts/common/read_input_files.tcl +1 -7
  107. siliconcompiler/tools/openroad/scripts/common/screenshot.tcl +2 -2
  108. siliconcompiler/tools/openroad/scripts/common/write_images.tcl +28 -3
  109. siliconcompiler/tools/openroad/scripts/sc_rcx.tcl +1 -1
  110. siliconcompiler/tools/openroad/scripts/sc_rdlroute.tcl +3 -3
  111. siliconcompiler/tools/openroad/scripts/sc_show.tcl +6 -6
  112. siliconcompiler/tools/opensta/scripts/sc_timing.tcl +10 -0
  113. siliconcompiler/tools/opensta/timing.py +11 -0
  114. siliconcompiler/tools/slang/__init__.py +13 -13
  115. siliconcompiler/tools/slang/elaborate.py +6 -6
  116. siliconcompiler/tools/slang/lint.py +1 -3
  117. siliconcompiler/tools/surelog/parse.py +4 -4
  118. siliconcompiler/tools/sv2v/convert.py +20 -3
  119. siliconcompiler/tools/verilator/compile.py +2 -2
  120. siliconcompiler/tools/verilator/verilator.py +3 -3
  121. siliconcompiler/tools/vpr/_xml_constraint.py +8 -8
  122. siliconcompiler/tools/vpr/place.py +1 -1
  123. siliconcompiler/tools/vpr/route.py +4 -4
  124. siliconcompiler/tools/vpr/screenshot.py +1 -1
  125. siliconcompiler/tools/vpr/show.py +5 -5
  126. siliconcompiler/tools/vpr/vpr.py +24 -24
  127. siliconcompiler/tools/xdm/convert.py +2 -2
  128. siliconcompiler/tools/xyce/simulate.py +1 -1
  129. siliconcompiler/tools/yosys/prepareLib.py +2 -2
  130. siliconcompiler/tools/yosys/sc_synth_asic.tcl +111 -63
  131. siliconcompiler/tools/yosys/screenshot.py +1 -1
  132. siliconcompiler/tools/yosys/syn_asic.py +7 -7
  133. siliconcompiler/toolscripts/_tools.json +12 -10
  134. siliconcompiler/toolscripts/rhel8/install-chisel.sh +9 -2
  135. siliconcompiler/toolscripts/rhel8/install-icarus.sh +10 -3
  136. siliconcompiler/toolscripts/rhel8/install-klayout.sh +8 -1
  137. siliconcompiler/toolscripts/rhel8/install-magic.sh +9 -2
  138. siliconcompiler/toolscripts/rhel8/install-montage.sh +1 -1
  139. siliconcompiler/toolscripts/rhel8/install-netgen.sh +9 -2
  140. siliconcompiler/toolscripts/rhel8/install-slang.sh +11 -4
  141. siliconcompiler/toolscripts/rhel8/install-surelog.sh +9 -2
  142. siliconcompiler/toolscripts/rhel8/install-sv2v.sh +11 -4
  143. siliconcompiler/toolscripts/rhel8/install-verible.sh +11 -3
  144. siliconcompiler/toolscripts/rhel8/install-verilator.sh +10 -3
  145. siliconcompiler/toolscripts/rhel8/install-xyce.sh +15 -10
  146. siliconcompiler/toolscripts/rhel9/install-chisel.sh +9 -2
  147. siliconcompiler/toolscripts/rhel9/install-ghdl.sh +9 -2
  148. siliconcompiler/toolscripts/rhel9/install-gtkwave.sh +10 -3
  149. siliconcompiler/toolscripts/rhel9/install-icarus.sh +10 -3
  150. siliconcompiler/toolscripts/rhel9/install-klayout.sh +8 -1
  151. siliconcompiler/toolscripts/rhel9/install-magic.sh +9 -2
  152. siliconcompiler/toolscripts/rhel9/install-montage.sh +1 -1
  153. siliconcompiler/toolscripts/rhel9/install-netgen.sh +9 -2
  154. siliconcompiler/toolscripts/rhel9/install-openroad.sh +16 -3
  155. siliconcompiler/toolscripts/rhel9/install-opensta.sh +17 -5
  156. siliconcompiler/toolscripts/rhel9/install-slang.sh +11 -4
  157. siliconcompiler/toolscripts/rhel9/install-surelog.sh +9 -2
  158. siliconcompiler/toolscripts/rhel9/install-sv2v.sh +11 -4
  159. siliconcompiler/toolscripts/rhel9/install-verible.sh +11 -3
  160. siliconcompiler/toolscripts/rhel9/install-verilator.sh +10 -3
  161. siliconcompiler/toolscripts/rhel9/install-vpr.sh +9 -2
  162. siliconcompiler/toolscripts/rhel9/install-xdm.sh +10 -2
  163. siliconcompiler/toolscripts/rhel9/install-xyce.sh +15 -10
  164. siliconcompiler/toolscripts/rhel9/install-yosys-moosic.sh +9 -2
  165. siliconcompiler/toolscripts/rhel9/install-yosys-parmys.sh +10 -3
  166. siliconcompiler/toolscripts/rhel9/install-yosys-slang.sh +10 -2
  167. siliconcompiler/toolscripts/rhel9/install-yosys.sh +9 -2
  168. siliconcompiler/toolscripts/ubuntu20/install-bambu.sh +10 -2
  169. siliconcompiler/toolscripts/ubuntu20/install-bluespec.sh +10 -3
  170. siliconcompiler/toolscripts/ubuntu20/install-chisel.sh +9 -2
  171. siliconcompiler/toolscripts/ubuntu20/install-ghdl.sh +9 -2
  172. siliconcompiler/toolscripts/ubuntu20/install-gtkwave.sh +9 -2
  173. siliconcompiler/toolscripts/ubuntu20/install-icarus.sh +9 -2
  174. siliconcompiler/toolscripts/ubuntu20/install-icepack.sh +9 -2
  175. siliconcompiler/toolscripts/ubuntu20/install-klayout.sh +8 -1
  176. siliconcompiler/toolscripts/ubuntu20/install-magic.sh +9 -2
  177. siliconcompiler/toolscripts/ubuntu20/install-montage.sh +1 -1
  178. siliconcompiler/toolscripts/ubuntu20/install-netgen.sh +9 -2
  179. siliconcompiler/toolscripts/ubuntu20/install-nextpnr.sh +9 -2
  180. siliconcompiler/toolscripts/ubuntu20/install-openroad.sh +16 -3
  181. siliconcompiler/toolscripts/ubuntu20/install-opensta.sh +16 -5
  182. siliconcompiler/toolscripts/ubuntu20/install-slang.sh +11 -4
  183. siliconcompiler/toolscripts/ubuntu20/install-slurm.sh +9 -2
  184. siliconcompiler/toolscripts/ubuntu20/install-surelog.sh +10 -2
  185. siliconcompiler/toolscripts/ubuntu20/install-sv2v.sh +11 -4
  186. siliconcompiler/toolscripts/ubuntu20/install-verible.sh +11 -3
  187. siliconcompiler/toolscripts/ubuntu20/install-verilator.sh +9 -2
  188. siliconcompiler/toolscripts/ubuntu20/install-xdm.sh +10 -2
  189. siliconcompiler/toolscripts/ubuntu20/install-xyce.sh +13 -8
  190. siliconcompiler/toolscripts/ubuntu20/install-yosys-moosic.sh +9 -2
  191. siliconcompiler/toolscripts/ubuntu20/install-yosys.sh +9 -2
  192. siliconcompiler/toolscripts/ubuntu22/install-bambu.sh +10 -2
  193. siliconcompiler/toolscripts/ubuntu22/install-bluespec.sh +10 -3
  194. siliconcompiler/toolscripts/ubuntu22/install-chisel.sh +9 -2
  195. siliconcompiler/toolscripts/ubuntu22/install-ghdl.sh +9 -2
  196. siliconcompiler/toolscripts/ubuntu22/install-gtkwave.sh +9 -2
  197. siliconcompiler/toolscripts/ubuntu22/install-icarus.sh +9 -2
  198. siliconcompiler/toolscripts/ubuntu22/install-icepack.sh +9 -2
  199. siliconcompiler/toolscripts/ubuntu22/install-klayout.sh +8 -1
  200. siliconcompiler/toolscripts/ubuntu22/install-magic.sh +9 -2
  201. siliconcompiler/toolscripts/ubuntu22/install-montage.sh +1 -1
  202. siliconcompiler/toolscripts/ubuntu22/install-netgen.sh +9 -2
  203. siliconcompiler/toolscripts/ubuntu22/install-nextpnr.sh +9 -2
  204. siliconcompiler/toolscripts/ubuntu22/install-openroad.sh +16 -3
  205. siliconcompiler/toolscripts/ubuntu22/install-opensta.sh +17 -5
  206. siliconcompiler/toolscripts/ubuntu22/install-slang.sh +11 -4
  207. siliconcompiler/toolscripts/ubuntu22/install-slurm.sh +9 -2
  208. siliconcompiler/toolscripts/ubuntu22/install-surelog.sh +10 -2
  209. siliconcompiler/toolscripts/ubuntu22/install-sv2v.sh +11 -4
  210. siliconcompiler/toolscripts/ubuntu22/install-verible.sh +11 -3
  211. siliconcompiler/toolscripts/ubuntu22/install-verilator.sh +9 -2
  212. siliconcompiler/toolscripts/ubuntu22/install-vpr.sh +9 -4
  213. siliconcompiler/toolscripts/ubuntu22/install-xdm.sh +10 -2
  214. siliconcompiler/toolscripts/ubuntu22/install-xyce.sh +13 -8
  215. siliconcompiler/toolscripts/ubuntu22/install-yosys-moosic.sh +9 -2
  216. siliconcompiler/toolscripts/ubuntu22/install-yosys-parmys.sh +10 -3
  217. siliconcompiler/toolscripts/ubuntu22/install-yosys-slang.sh +10 -2
  218. siliconcompiler/toolscripts/ubuntu22/install-yosys.sh +9 -2
  219. siliconcompiler/toolscripts/ubuntu24/install-bambu.sh +12 -4
  220. siliconcompiler/toolscripts/ubuntu24/install-bluespec.sh +10 -3
  221. siliconcompiler/toolscripts/ubuntu24/install-chisel.sh +9 -2
  222. siliconcompiler/toolscripts/ubuntu24/install-ghdl.sh +9 -2
  223. siliconcompiler/toolscripts/ubuntu24/install-gtkwave.sh +9 -2
  224. siliconcompiler/toolscripts/ubuntu24/install-icarus.sh +9 -2
  225. siliconcompiler/toolscripts/ubuntu24/install-icepack.sh +9 -2
  226. siliconcompiler/toolscripts/ubuntu24/install-klayout.sh +8 -1
  227. siliconcompiler/toolscripts/ubuntu24/install-magic.sh +9 -2
  228. siliconcompiler/toolscripts/ubuntu24/install-montage.sh +1 -1
  229. siliconcompiler/toolscripts/ubuntu24/install-netgen.sh +9 -2
  230. siliconcompiler/toolscripts/ubuntu24/install-nextpnr.sh +9 -2
  231. siliconcompiler/toolscripts/ubuntu24/install-openroad.sh +16 -3
  232. siliconcompiler/toolscripts/ubuntu24/install-opensta.sh +17 -5
  233. siliconcompiler/toolscripts/ubuntu24/install-slang.sh +11 -4
  234. siliconcompiler/toolscripts/ubuntu24/install-slurm.sh +9 -2
  235. siliconcompiler/toolscripts/ubuntu24/install-surelog.sh +10 -2
  236. siliconcompiler/toolscripts/ubuntu24/install-sv2v.sh +11 -4
  237. siliconcompiler/toolscripts/ubuntu24/install-verible.sh +11 -3
  238. siliconcompiler/toolscripts/ubuntu24/install-verilator.sh +9 -2
  239. siliconcompiler/toolscripts/ubuntu24/install-vpr.sh +9 -4
  240. siliconcompiler/toolscripts/ubuntu24/install-xdm.sh +10 -2
  241. siliconcompiler/toolscripts/ubuntu24/install-xyce.sh +13 -8
  242. siliconcompiler/toolscripts/ubuntu24/install-yosys-moosic.sh +9 -2
  243. siliconcompiler/toolscripts/ubuntu24/install-yosys-parmys.sh +10 -3
  244. siliconcompiler/toolscripts/ubuntu24/install-yosys-slang.sh +10 -2
  245. siliconcompiler/toolscripts/ubuntu24/install-yosys.sh +9 -2
  246. siliconcompiler/utils/__init__.py +19 -112
  247. siliconcompiler/utils/flowgraph.py +244 -0
  248. siliconcompiler/{issue.py → utils/issue.py} +18 -25
  249. siliconcompiler/utils/logging.py +3 -4
  250. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/METADATA +9 -8
  251. siliconcompiler-0.33.1.dist-info/RECORD +488 -0
  252. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/WHEEL +1 -1
  253. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/entry_points.txt +8 -8
  254. siliconcompiler/schema/schema_obj.py +0 -1936
  255. siliconcompiler/toolscripts/ubuntu20/install-vpr.sh +0 -29
  256. siliconcompiler/toolscripts/ubuntu20/install-yosys-parmys.sh +0 -61
  257. siliconcompiler-0.32.3.dist-info/RECORD +0 -470
  258. /siliconcompiler/{templates → data/templates}/__init__.py +0 -0
  259. /siliconcompiler/{templates → data/templates}/email/__init__.py +0 -0
  260. /siliconcompiler/{templates → data/templates}/email/general.j2 +0 -0
  261. /siliconcompiler/{templates → data/templates}/email/summary.j2 +0 -0
  262. /siliconcompiler/{templates → data/templates}/issue/README.txt +0 -0
  263. /siliconcompiler/{templates → data/templates}/issue/__init__.py +0 -0
  264. /siliconcompiler/{templates → data/templates}/issue/run.sh +0 -0
  265. /siliconcompiler/{templates → data/templates}/replay/replay.py.j2 +0 -0
  266. /siliconcompiler/{templates → data/templates}/replay/replay.sh.j2 +0 -0
  267. /siliconcompiler/{templates → data/templates}/replay/requirements.txt +0 -0
  268. /siliconcompiler/{templates → data/templates}/replay/setup.sh +0 -0
  269. /siliconcompiler/{templates → data/templates}/report/__init__.py +0 -0
  270. /siliconcompiler/{templates → data/templates}/report/bootstrap.min.css +0 -0
  271. /siliconcompiler/{templates → data/templates}/report/bootstrap.min.js +0 -0
  272. /siliconcompiler/{templates → data/templates}/report/bootstrap_LICENSE.md +0 -0
  273. /siliconcompiler/{templates → data/templates}/report/sc_report.j2 +0 -0
  274. /siliconcompiler/{templates → data/templates}/slurm/__init__.py +0 -0
  275. /siliconcompiler/{templates → data/templates}/slurm/run.sh +0 -0
  276. /siliconcompiler/{templates → data/templates}/tcl/__init__.py +0 -0
  277. /siliconcompiler/{templates → data/templates}/tcl/manifest.tcl.j2 +0 -0
  278. /siliconcompiler/{units.py → utils/units.py} +0 -0
  279. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/licenses/LICENSE +0 -0
  280. {siliconcompiler-0.32.3.dist-info → siliconcompiler-0.33.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,791 @@
1
+ # Copyright 2025 Silicon Compiler Authors. All Rights Reserved.
2
+
3
+ # NOTE: this file cannot rely on any third-party dependencies, including other
4
+ # SC dependencies outside of its directory, since it may be used by tool drivers
5
+ # that have isolated Python environments.
6
+
7
+ import argparse
8
+ import copy
9
+ import re
10
+ import shlex
11
+
12
+ from enum import Enum
13
+
14
+ from .parametervalue import NodeValue, DirectoryNodeValue, FileNodeValue, NodeListValue
15
+ from .parametertype import NodeType, NodeEnumType
16
+
17
+
18
+ class Scope(Enum):
19
+ '''
20
+ Enum for scope Schema parameters
21
+ '''
22
+ GLOBAL = 'global'
23
+ JOB = 'job'
24
+ SCRATCH = 'scratch'
25
+
26
+
27
+ class PerNode(Enum):
28
+ '''
29
+ Enum for pernode Schema parameters
30
+ '''
31
+ NEVER = 'never'
32
+ OPTIONAL = 'optional'
33
+ REQUIRED = 'required'
34
+
35
+ def is_never(self):
36
+ '''
37
+ Returns true is this is 'never'
38
+ '''
39
+ return self == PerNode.NEVER
40
+
41
+
42
+ class Parameter:
43
+ '''
44
+ Leaf nodes in the schema. This holds all the information for a given keypath.
45
+
46
+ Args:
47
+ type (str): type for the parameter, see
48
+ :class:`.parametertype.NodeType` for supported types.
49
+ require (bool): require field
50
+ defvalue (any): defvalue field
51
+ scope (:class:`.Scope`): scope field
52
+ copy (bool): copy field
53
+ lock (bool): bool field
54
+ hashalgo (str): hashalgo field
55
+ notes (str): notes field
56
+ unit (str): unit field
57
+ shorthelp (str): shorthelp field
58
+ switch (list of str): switch field
59
+ example (list of str): example field
60
+ help (str): help field
61
+ pernode (:class:`.PerNode`): pernode field
62
+ '''
63
+
64
+ GLOBAL_KEY = 'global'
65
+
66
+ def __init__(self,
67
+ type,
68
+ require=False,
69
+ defvalue=None,
70
+ scope=Scope.JOB,
71
+ copy=False,
72
+ lock=False,
73
+ hashalgo='sha256',
74
+ notes=None,
75
+ unit=None,
76
+ shorthelp=None,
77
+ switch=None,
78
+ example=None,
79
+ help=None,
80
+ pernode=PerNode.NEVER):
81
+
82
+ self.__type = NodeType.parse(type)
83
+ self.__scope = Scope(scope)
84
+ self.__lock = lock
85
+ self.__require = require
86
+
87
+ if switch is None:
88
+ switch = []
89
+ elif isinstance(switch, str):
90
+ switch = [switch]
91
+ self.__switch = switch
92
+
93
+ self.__shorthelp = shorthelp
94
+
95
+ if example is None:
96
+ example = []
97
+ elif isinstance(example, str):
98
+ example = [example]
99
+ self.__example = example
100
+
101
+ self.__help = help
102
+
103
+ self.__notes = notes
104
+
105
+ if self.__type == 'bool':
106
+ if defvalue is None:
107
+ defvalue = False
108
+
109
+ self.__pernode = PerNode(pernode)
110
+
111
+ self.__setdefvalue(defvalue)
112
+
113
+ self.__node = {}
114
+
115
+ self.__unit = None
116
+ if unit is not None and \
117
+ (NodeType.contains(self.__type, 'int') or NodeType.contains(self.__type, 'float')):
118
+ self.__unit = str(unit)
119
+
120
+ self.__hashalgo = None
121
+ self.__copy = None
122
+ if NodeType.contains(self.__type, 'dir') or NodeType.contains(self.__type, 'file'):
123
+ self.__hashalgo = str(hashalgo)
124
+ self.__copy = bool(copy)
125
+
126
+ def __setdefvalue(self, defvalue):
127
+ if NodeType.contains(self.__type, 'file'):
128
+ base = FileNodeValue(defvalue)
129
+ if isinstance(self.__type, list):
130
+ self.__defvalue = NodeListValue(base)
131
+ else:
132
+ self.__defvalue = base
133
+ elif NodeType.contains(self.__type, 'dir'):
134
+ base = DirectoryNodeValue(defvalue)
135
+ if isinstance(self.__type, list):
136
+ self.__defvalue = NodeListValue(base)
137
+ else:
138
+ self.__defvalue = base
139
+ else:
140
+ if isinstance(self.__type, list):
141
+ self.__defvalue = NodeListValue(NodeValue(self.__type[0]))
142
+ if defvalue:
143
+ self.__defvalue.set(defvalue)
144
+ else:
145
+ self.__defvalue = NodeValue(self.__type, value=defvalue)
146
+
147
+ def __str__(self):
148
+ return str(self.getvalues())
149
+
150
+ def get(self, field='value', step=None, index=None):
151
+ """
152
+ Returns the value in a parameter field.
153
+
154
+ Args:
155
+ field (str): Parameter field to fetch.
156
+ step (str): Step name to access for parameters that may be specified
157
+ on a per-node basis.
158
+ index (str): Index name to access for parameters that may be specified
159
+ on a per-node basis.
160
+
161
+ Returns:
162
+ Field value for the parameter.
163
+
164
+ Examples:
165
+ >>> value = param.get()
166
+ Returns the value stored in the parameter.
167
+ """
168
+
169
+ self.__assert_step_index(field, step, index)
170
+
171
+ if field in self.__defvalue.fields:
172
+ if isinstance(index, int):
173
+ index = str(index)
174
+
175
+ try:
176
+ return self.__node[step][index].get(field=field)
177
+ except KeyError:
178
+ if self.__pernode == PerNode.REQUIRED:
179
+ return self.__defvalue.get(field=field)
180
+
181
+ try:
182
+ return self.__node[step][Parameter.GLOBAL_KEY].get(field=field)
183
+ except KeyError:
184
+ pass
185
+
186
+ try:
187
+ return self.__node[Parameter.GLOBAL_KEY][Parameter.GLOBAL_KEY].get(field=field)
188
+ except KeyError:
189
+ return self.__defvalue.get(field=field)
190
+ elif field == "type":
191
+ return NodeType.encode(self.__type)
192
+ elif field == "scope":
193
+ return self.__scope
194
+ elif field == "lock":
195
+ return self.__lock
196
+ elif field == "switch":
197
+ return copy.deepcopy(self.__switch)
198
+ elif field == "shorthelp":
199
+ return self.__shorthelp
200
+ elif field == "example":
201
+ return copy.deepcopy(self.__example)
202
+ elif field == "help":
203
+ return self.__help
204
+ elif field == "notes":
205
+ return self.__notes
206
+ elif field == "pernode":
207
+ return self.__pernode
208
+ elif field == "unit":
209
+ return self.__unit
210
+ elif field == "hashalgo":
211
+ return self.__hashalgo
212
+ elif field == "copy":
213
+ return self.__copy
214
+ elif field == "require":
215
+ return self.__require
216
+
217
+ raise ValueError(f'"{field}" is not a valid field')
218
+
219
+ def __assert_step_index(self, field, step, index):
220
+ if field not in self.__defvalue.fields:
221
+ if step is not None or index is not None:
222
+ raise KeyError(
223
+ 'step and index are only valid for'
224
+ f': {", ".join([field for field in self.__defvalue.fields if field])}')
225
+ return
226
+
227
+ if self.__pernode == PerNode.NEVER and (step is not None or index is not None):
228
+ raise KeyError('use of step and index are not valid')
229
+
230
+ if self.__pernode == PerNode.REQUIRED and (step is None or index is None):
231
+ raise KeyError('step and index are required')
232
+
233
+ if step is None and index is not None:
234
+ raise KeyError('step is required if index is provided')
235
+
236
+ # Step and index for default should be accessed set_/get_default
237
+ if step == 'default':
238
+ raise KeyError('illegal step name: default is reserved')
239
+
240
+ if index == 'default':
241
+ raise KeyError('illegal index name: default is reserved')
242
+
243
+ def set(self, value, field='value', step=None, index=None, clobber=True):
244
+ '''
245
+ Sets a parameter field.
246
+
247
+ Args:
248
+ value (any): Value to set.
249
+ field (str): Parameter field to set.
250
+ clobber (bool): Existing value is overwritten if True.
251
+ step (str): Step name to set for parameters that may be specified
252
+ on a per-node basis.
253
+ index (str): Index name to set for parameters that may be specified
254
+ on a per-node basis.
255
+
256
+ Examples:
257
+ >>> param.set('top')
258
+ Sets the value to 'top'
259
+ '''
260
+
261
+ if field != "lock":
262
+ if self.__lock:
263
+ return False
264
+
265
+ if self.is_set(step, index) and not clobber:
266
+ return False
267
+
268
+ self.__assert_step_index(field, step, index)
269
+
270
+ if field in self.__defvalue.fields:
271
+ if isinstance(index, int):
272
+ index = str(index)
273
+
274
+ step = step if step is not None else Parameter.GLOBAL_KEY
275
+ index = index if index is not None else Parameter.GLOBAL_KEY
276
+
277
+ if step not in self.__node:
278
+ self.__node[step] = {}
279
+ if index not in self.__node[step]:
280
+ self.__node[step][index] = self.__defvalue.copy()
281
+
282
+ return self.__node[step][index].set(value, field=field)
283
+ elif field == "type":
284
+ self.__type = NodeType.normalize(value, "str")
285
+ elif field == "scope":
286
+ if isinstance(value, Scope):
287
+ self.__scope = value
288
+ else:
289
+ self.__scope = Scope(NodeType.normalize(value,
290
+ NodeEnumType(*[v.value for v in Scope])))
291
+ elif field == "lock":
292
+ self.__lock = NodeType.normalize(value, "bool")
293
+ elif field == "switch":
294
+ self.__switch = NodeType.normalize(value, ["str"])
295
+ elif field == "shorthelp":
296
+ self.__shorthelp = NodeType.normalize(value, "str")
297
+ elif field == "example":
298
+ self.__example = NodeType.normalize(value, ["str"])
299
+ elif field == "help":
300
+ self.__help = NodeType.normalize(value, "str")
301
+ elif field == "notes":
302
+ self.__notes = NodeType.normalize(value, "str")
303
+ elif field == "pernode":
304
+ if isinstance(value, PerNode):
305
+ self.__pernode = value
306
+ else:
307
+ self.__pernode = PerNode(NodeType.normalize(value,
308
+ NodeEnumType(
309
+ *[v.value for v in PerNode])))
310
+ elif field == "unit":
311
+ self.__unit = NodeType.normalize(value, "str")
312
+ elif field == "hashalgo":
313
+ self.__hashalgo = NodeType.normalize(value, "str")
314
+ elif field == "copy":
315
+ self.__copy = NodeType.normalize(value, "bool")
316
+ elif field == "require":
317
+ self.__require = NodeType.normalize(value, "bool")
318
+ else:
319
+ raise ValueError(f'"{field}" is not a valid field')
320
+
321
+ return True
322
+
323
+ def add(self, value, field='value', step=None, index=None):
324
+ '''
325
+ Adds item(s) to a list.
326
+
327
+ Args:
328
+ value (any): Value to add.
329
+ field (str): Parameter field to modify.
330
+ step (str): Step name to modify for parameters that may be specified
331
+ on a per-node basis.
332
+ index (str): Index name to modify for parameters that may be specified
333
+ on a per-node basis.
334
+
335
+ Examples:
336
+ >>> param.add('hello.v')
337
+ Adds the file 'hello.v' the parameter.
338
+ '''
339
+
340
+ if self.__lock:
341
+ return False
342
+
343
+ self.__assert_step_index(field, step, index)
344
+
345
+ if field in self.__defvalue.fields:
346
+ if not self.is_list() and field == 'value':
347
+ raise ValueError("add can only be used on lists")
348
+
349
+ if isinstance(index, int):
350
+ index = str(index)
351
+
352
+ step = step if step is not None else Parameter.GLOBAL_KEY
353
+ index = index if index is not None else Parameter.GLOBAL_KEY
354
+
355
+ if step not in self.__node:
356
+ self.__node[step] = {}
357
+ if index not in self.__node[step]:
358
+ self.__node[step][index] = self.__defvalue.copy()
359
+
360
+ return self.__node[step][index].add(value, field=field)
361
+ elif field == "switch":
362
+ self.__switch.extend(NodeType.normalize(value, ["str"]))
363
+ elif field == "example":
364
+ self.__example.extend(NodeType.normalize(value, ["str"]))
365
+ else:
366
+ raise ValueError(f'"{field}" is not a valid field')
367
+
368
+ return True
369
+
370
+ def unset(self, step=None, index=None):
371
+ '''
372
+ Unsets a schema parameter.
373
+
374
+ This method effectively undoes any previous calls to :meth:`set()` made to
375
+ the given keypath and step/index. For parameters with required or no
376
+ per-node values, unsetting a parameter always causes it to revert to its
377
+ default value, and future calls to :meth:`set()` with ``clobber=False`` will
378
+ once again be able to modify the value.
379
+
380
+ If you unset a particular step/index for a parameter with optional
381
+ per-node values, note that the newly returned value will be the global
382
+ value if it has been set. To completely return the parameter to its
383
+ default state, the global value has to be unset as well.
384
+
385
+ ``unset()`` has no effect if called on a parameter that has not been
386
+ previously set.
387
+
388
+ Args:
389
+ step (str): Step name to unset for parameters that may be specified
390
+ on a per-node basis.
391
+ index (str): Index name to unset for parameters that may be specified
392
+ on a per-node basis.
393
+ '''
394
+
395
+ if self.__lock:
396
+ return False
397
+
398
+ if isinstance(index, int):
399
+ index = str(index)
400
+
401
+ step = step if step is not None else Parameter.GLOBAL_KEY
402
+ index = index if index is not None else Parameter.GLOBAL_KEY
403
+
404
+ try:
405
+ del self.__node[step][index]
406
+ except KeyError:
407
+ # If this key doesn't exist, silently continue - it was never set
408
+ pass
409
+
410
+ return True
411
+
412
+ def getdict(self, include_default=True):
413
+ """
414
+ Returns a schema dictionary.
415
+
416
+ Args:
417
+ include_default (boolean): If true will include default values
418
+
419
+ Returns:
420
+ A schema dictionary
421
+
422
+ Examples:
423
+ >>> param.getdict()
424
+ Returns the complete dictionary for the parameter
425
+ """
426
+
427
+ dictvals = {
428
+ "type": NodeType.encode(self.__type),
429
+ "require": self.__require,
430
+ "scope": self.__scope.value,
431
+ "lock": self.__lock,
432
+ "switch": self.__switch.copy(),
433
+ "shorthelp": self.__shorthelp,
434
+ "example": self.__example.copy(),
435
+ "help": self.__help,
436
+ "notes": self.__notes,
437
+ "pernode": self.__pernode.value,
438
+ "node": {}
439
+ }
440
+
441
+ for step in self.__node:
442
+ dictvals["node"][step] = {}
443
+ for index, val in self.__node[step].items():
444
+ dictvals["node"][step][index] = val.getdict()
445
+
446
+ if include_default:
447
+ dictvals["node"].setdefault("default", {})["default"] = self.__defvalue.getdict()
448
+
449
+ if self.__unit:
450
+ dictvals["unit"] = self.__unit
451
+ if self.__hashalgo:
452
+ dictvals["hashalgo"] = self.__hashalgo
453
+ if self.__copy is not None:
454
+ dictvals["copy"] = self.__copy
455
+ return dictvals
456
+
457
+ @classmethod
458
+ def from_dict(cls, manifest, keypath, version):
459
+ '''
460
+ Create a new parameter based on the provided dictionary.
461
+
462
+ Args:
463
+ manifest (dict): Manifest to decide.
464
+ keypath (list of str): Path to the current keypath.
465
+ version (packaging.Version): Version of the dictionary schema
466
+ '''
467
+
468
+ # create a dummy param
469
+ param = cls("str")
470
+ param._from_dict(manifest, keypath, version)
471
+ return param
472
+
473
+ def _from_dict(self, manifest, keypath, version):
474
+ '''
475
+ Copies the information from the manifest into this parameter.
476
+
477
+ Args:
478
+ manifest (dict): Manifest to decide.
479
+ keypath (list of str): Path to the current keypath.
480
+ version (packaging.Version): Version of the dictionary schema
481
+ '''
482
+
483
+ if self.__lock:
484
+ return
485
+
486
+ if version and version > (0, 50, 0):
487
+ self.__type = NodeType.parse(manifest["type"])
488
+ else:
489
+ if "enum" in manifest:
490
+ self.__type = NodeType.parse(
491
+ re.sub("enum", f"<{','.join(manifest['enum'])}>", manifest['type']))
492
+ else:
493
+ self.__type = NodeType.parse(manifest["type"])
494
+
495
+ self.__require = manifest.get("require", self.__require)
496
+ self.__scope = Scope(manifest.get("scope", self.__scope))
497
+ self.__lock = manifest.get("lock", self.__lock)
498
+ self.__switch = manifest.get("switch", self.__switch)
499
+ self.__shorthelp = manifest.get("shorthelp", self.__shorthelp)
500
+ self.__example = manifest.get("example", self.__example)
501
+ self.__help = manifest.get("help", self.__help)
502
+ self.__notes = manifest.get("notes", self.__notes)
503
+ self.__pernode = PerNode(manifest.get("pernode", self.__pernode))
504
+ self.__node = {}
505
+
506
+ self.__unit = manifest.get("unit", self.__unit)
507
+ self.__hashalgo = manifest.get("hashalgo", self.__hashalgo)
508
+ self.__copy = manifest.get("copy", self.__copy)
509
+
510
+ requires_set = NodeType.contains(self.__type, tuple) or NodeType.contains(self.__type, set)
511
+
512
+ try:
513
+ defvalue = manifest["node"]["default"]["default"]["value"]
514
+ del manifest["node"]["default"]
515
+ except KeyError:
516
+ defvalue = None
517
+
518
+ if requires_set:
519
+ self.__setdefvalue(NodeType.normalize(defvalue, self.__type))
520
+ else:
521
+ self.__setdefvalue(defvalue)
522
+
523
+ for step, indexdata in manifest["node"].items():
524
+ self.__node[step] = {}
525
+ for index, nodedata in indexdata.items():
526
+ value = self.__defvalue.copy()
527
+ value._from_dict(nodedata, keypath, version)
528
+ self.__node[step][index] = value
529
+
530
+ if requires_set:
531
+ for step, indexdata in self.__node.items():
532
+ for param in indexdata.values():
533
+ value = param.get()
534
+ param.set(value)
535
+
536
+ def gettcl(self, step=None, index=None):
537
+ """
538
+ Returns a tcl string for this parameter.
539
+
540
+ Args:
541
+ step (str): Step name to unset for parameters that may be specified
542
+ on a per-node basis.
543
+ index (str): Index name to unset for parameters that may be specified
544
+ on a per-node basis.
545
+ """
546
+
547
+ if self.__pernode == PerNode.REQUIRED and (step is None or index is None):
548
+ return None
549
+ if not self.__pernode.is_never():
550
+ value = self.get(step=step, index=index)
551
+ else:
552
+ value = self.get()
553
+
554
+ return NodeType.to_tcl(value, self.__type)
555
+
556
+ def getvalues(self, return_defvalue=True):
557
+ """
558
+ Returns all values (global and pernode) associated with a particular parameter.
559
+
560
+ Returns a list of tuples of the form (value, step, index). The list is
561
+ in no particular order. For the global value, step and index are None.
562
+ If return_defvalue is True, the default parameter value is added to the
563
+ list in place of a global value if a global value is not set.
564
+ """
565
+
566
+ vals = []
567
+ has_global = False
568
+ for step in self.__node:
569
+ for index in self.__node[step]:
570
+ step_arg = None if step == Parameter.GLOBAL_KEY else step
571
+ index_arg = None if index == Parameter.GLOBAL_KEY else index
572
+ if step_arg is None and index_arg is None:
573
+ has_global = True
574
+ vals.append((self.__node[step][index].get(), step_arg, index_arg))
575
+
576
+ if (self.__pernode != PerNode.REQUIRED) and not has_global and return_defvalue:
577
+ vals.append((self.__defvalue.get(), None, None))
578
+
579
+ return vals
580
+
581
+ def copy(self, key=None):
582
+ """
583
+ Returns a copy of this parameter.
584
+
585
+ Args:
586
+ key (list of str): keypath to this schema
587
+ """
588
+
589
+ return copy.deepcopy(self)
590
+
591
+ # Utility functions
592
+ def is_list(self):
593
+ """
594
+ Returns true is this parameter is a list type
595
+ """
596
+
597
+ return isinstance(self.__type, list)
598
+
599
+ def is_empty(self):
600
+ '''
601
+ Utility function to check key for an empty value.
602
+ '''
603
+
604
+ empty = (None, [])
605
+
606
+ values = self.getvalues()
607
+ return all([value in empty for value, _, _ in values])
608
+
609
+ def is_set(self, step=None, index=None):
610
+ '''
611
+ Returns whether a user has set a value for this parameter.
612
+
613
+ A value counts as set if a user has set a global value OR a value for
614
+ the provided step/index.
615
+ '''
616
+ if Parameter.GLOBAL_KEY in self.__node and \
617
+ Parameter.GLOBAL_KEY in self.__node[Parameter.GLOBAL_KEY] and \
618
+ self.__node[Parameter.GLOBAL_KEY][Parameter.GLOBAL_KEY]:
619
+ # global value is set
620
+ return True
621
+
622
+ if step is None:
623
+ return False
624
+ if index is None:
625
+ index = Parameter.GLOBAL_KEY
626
+
627
+ return step in self.__node and \
628
+ index in self.__node[step] and \
629
+ self.__node[step][index]
630
+
631
+ @property
632
+ def default(self):
633
+ """
634
+ Gets a copy of the default value.
635
+ """
636
+ return self.__defvalue.copy()
637
+
638
+ def add_commandline_arguments(self, argparser, *keypath, switchlist=None):
639
+ '''
640
+ Adds commandline arguments for this parameter.
641
+
642
+ Args:
643
+ argparser (argparse.ArgumentParser): argument parser to add switches to
644
+ keypath (list of str): keypath where this parameter is located.
645
+ switchlist (list of str): if provided will limited the switched added to
646
+ those in this list
647
+
648
+ Returns:
649
+ dest (str): key for argument parsing to lookup values in.
650
+ switches (list of str): list of switches added.
651
+ '''
652
+ if not self.__switch:
653
+ # no switches available to this parameter
654
+ return None, None
655
+
656
+ if not switchlist:
657
+ switchlist = []
658
+ elif not isinstance(switchlist, (list, set, tuple)):
659
+ switchlist = [switchlist]
660
+
661
+ switches = []
662
+ metavar = None
663
+ for switch in self.__switch:
664
+ switchmatch = re.match(r'(-[\w_]+)\s+(\'([\w]+\s)*<.*>\'|<.*>)', switch)
665
+ gccmatch = re.match(r'(-[\w_]+)(<.*>)', switch)
666
+ plusmatch = re.match(r'(\+[\w_\+]+)(<.*>)', switch)
667
+
668
+ if switchmatch:
669
+ switchstr = switchmatch.group(1)
670
+ metavar = switchmatch.group(2)
671
+ elif gccmatch:
672
+ switchstr = gccmatch.group(1)
673
+ metavar = gccmatch.group(2)
674
+ elif plusmatch:
675
+ switchstr = plusmatch.group(1)
676
+ metavar = plusmatch.group(2)
677
+ else:
678
+ raise ValueError(f"unable to process switch information: {switch}")
679
+
680
+ if switchlist and switchstr not in switchlist:
681
+ continue
682
+
683
+ switches.append(switchstr)
684
+
685
+ if not switches:
686
+ return None, None
687
+
688
+ # argparse 'dest' must be a string, so join keypath with commas
689
+ dest = '_'.join(keypath)
690
+
691
+ if self.__type == "bool":
692
+ # Boolean type arguments
693
+ if self.__pernode.is_never():
694
+ argparser.add_argument(
695
+ *switches,
696
+ nargs='?',
697
+ metavar=metavar,
698
+ dest=dest,
699
+ const='true',
700
+ help=self.__shorthelp,
701
+ default=argparse.SUPPRESS)
702
+ else:
703
+ argparser.add_argument(
704
+ *switches,
705
+ metavar=metavar,
706
+ nargs='?',
707
+ dest=dest,
708
+ action='append',
709
+ const='true',
710
+ help=self.__shorthelp,
711
+ default=argparse.SUPPRESS)
712
+ elif isinstance(self.__type, list) or self.__pernode != PerNode.NEVER:
713
+ # list type arguments
714
+ argparser.add_argument(
715
+ *switches,
716
+ metavar=metavar,
717
+ dest=dest,
718
+ action='append',
719
+ help=self.__shorthelp,
720
+ default=argparse.SUPPRESS)
721
+ else:
722
+ # all the rest
723
+ argparser.add_argument(
724
+ *switches,
725
+ metavar=metavar,
726
+ dest=dest,
727
+ help=self.__shorthelp,
728
+ default=argparse.SUPPRESS)
729
+
730
+ return dest, switches
731
+
732
+ def parse_commandline_arguments(self, value, *keypath):
733
+ """
734
+ Parse and set the values provided form the commandline parser.
735
+
736
+ Args:
737
+ value (str): string from commandline
738
+ keypath (list of str): leypath to this parameter
739
+ """
740
+ num_free_keys = keypath.count('default')
741
+
742
+ if num_free_keys > 0:
743
+ valueitem = shlex.split(value)
744
+ if len(valueitem) != num_free_keys + 1:
745
+ raise ValueError(f'Invalid value "{value}" for switch {"/".join(self.__switch)}')
746
+
747
+ free_keys = valueitem[0:num_free_keys]
748
+ remainder = valueitem[-1]
749
+ keypath = tuple([free_keys.pop(0) if key == 'default' else key for key in keypath])
750
+ else:
751
+ remainder = value
752
+
753
+ step, index = None, None
754
+ if self.__pernode == PerNode.REQUIRED:
755
+ try:
756
+ step, index, val = shlex.split(remainder)
757
+ except ValueError:
758
+ raise ValueError(f'Invalid value "{value}" for switch {"/".join(self.__switch)}: '
759
+ 'Requires step and index before final value')
760
+ elif self.__pernode == PerNode.OPTIONAL:
761
+ # Split on spaces, preserving items that are grouped in quotes
762
+ items = shlex.split(remainder)
763
+ if len(items) > 3:
764
+ raise ValueError(f'Invalid value "{value}" for switch {"/".join(self.__switch)}: '
765
+ 'Too many arguments')
766
+ if self.__type == 'bool':
767
+ if len(items) == 3:
768
+ step, index, val = items
769
+ elif len(items) == 2:
770
+ step, val = items
771
+ if val != 'true' and val != 'false':
772
+ index = val
773
+ val = 'true'
774
+ elif len(items) == 1:
775
+ val, = items
776
+ if val != 'true' and val != 'false':
777
+ step = val
778
+ val = 'true'
779
+ else:
780
+ val = 'true'
781
+ else:
782
+ if len(items) == 3:
783
+ step, index, val = items
784
+ elif len(items) == 2:
785
+ step, val = items
786
+ else:
787
+ val, = items
788
+ else:
789
+ val = remainder
790
+
791
+ return keypath, step, index, val