pz-scripts-data 1.1.1__tar.gz → 1.1.2__tar.gz

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 (20) hide show
  1. {pz_scripts_data-1.1.1/src/pz_scripts_data.egg-info → pz_scripts_data-1.1.2}/PKG-INFO +2 -6
  2. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/pyproject.toml +2 -3
  3. pz_scripts_data-1.1.2/src/pz_scripts_data/blocks.py +6 -0
  4. pz_scripts_data-1.1.2/src/pz_scripts_data/formatBlocks.py +287 -0
  5. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/src/pz_scripts_data/schema.py +14 -11
  6. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2/src/pz_scripts_data.egg-info}/PKG-INFO +2 -6
  7. pz_scripts_data-1.1.1/src/pz_scripts_data/blocks.py +0 -65
  8. pz_scripts_data-1.1.1/src/pz_scripts_data/formatBlocks.py +0 -279
  9. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/LICENSE +0 -0
  10. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/README.md +0 -0
  11. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/setup.cfg +0 -0
  12. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/src/pz_scripts_data/py.typed +0 -0
  13. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/src/pz_scripts_data/release.py +0 -0
  14. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/src/pz_scripts_data/sortItemParameters.py +0 -0
  15. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/src/pz_scripts_data/validateBlocks.py +0 -0
  16. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/src/pz_scripts_data.egg-info/SOURCES.txt +0 -0
  17. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/src/pz_scripts_data.egg-info/dependency_links.txt +0 -0
  18. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/src/pz_scripts_data.egg-info/entry_points.txt +0 -0
  19. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/src/pz_scripts_data.egg-info/requires.txt +0 -0
  20. {pz_scripts_data-1.1.1 → pz_scripts_data-1.1.2}/src/pz_scripts_data.egg-info/top_level.txt +0 -0
@@ -1,14 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pz-scripts-data
3
- Version: 1.1.1
3
+ Version: 1.1.2
4
4
  Summary: Dataset for the Scripts of Project Zomboid.
5
5
  Author: SimKDT
6
- License: PZ Scripts Data License
7
- This dataset is provided as fully open-source. You are free to use, modify, repost this dataset and any tools in this projects. Any contribution to it is welcomed.
8
-
9
- We are not responsible for what is made with this dataset, whenever it gets updated and breaks anything.
6
+ License-Expression: MIT
10
7
  Classifier: Programming Language :: Python :: 3.12
11
- Classifier: License :: OSI Approved :: MIT License
12
8
  Classifier: Topic :: Database
13
9
  Classifier: Operating System :: OS Independent
14
10
  Classifier: Development Status :: 5 - Production/Stable
@@ -3,8 +3,8 @@ name = "pz-scripts-data"
3
3
  description = "Dataset for the Scripts of Project Zomboid."
4
4
  readme = { file = "README.md", content-type = "text/markdown" }
5
5
  requires-python = ">=3.12.3"
6
- version = "1.1.1"
7
- license = {file = "LICENSE"}
6
+ version = "1.1.2"
7
+ license = "MIT"
8
8
  authors = [
9
9
  { name = "SimKDT" },
10
10
  ]
@@ -15,7 +15,6 @@ dependencies = [
15
15
  ]
16
16
  classifiers = [
17
17
  "Programming Language :: Python :: 3.12",
18
- "License :: OSI Approved :: MIT License",
19
18
  "Topic :: Database",
20
19
  "Operating System :: OS Independent",
21
20
  "Development Status :: 5 - Production/Stable",
@@ -0,0 +1,6 @@
1
+ from enum import StrEnum
2
+
3
+ class PropertyData(StrEnum):
4
+ NAME = 'name'
5
+ DESCRIPTION = 'description'
6
+ PROPERTIES = 'properties'
@@ -0,0 +1,287 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Any, Literal, Optional, TypedDict
4
+
5
+ import yaml
6
+ from pydantic import BaseModel, Field, ValidationError, model_validator
7
+
8
+ from pz_scripts_data.blocks import PropertyData
9
+ from pz_scripts_data.schema import BlockDefinition, ParameterDefinition
10
+
11
+
12
+ class ScriptsBlock(BlockDefinition):
13
+ """Block used by the pipeline: parameters are keyed by lowercase name and relations are filled in during preparation."""
14
+
15
+ parameters: dict[str, ParameterDefinition] = Field(default_factory=dict) # type: ignore
16
+ children: list[str] | None = None
17
+ variants: list[str] | None = None
18
+
19
+ @model_validator(mode="before")
20
+ @classmethod
21
+ def _key_parameters_by_name(cls, data: Any) -> Any:
22
+ if not isinstance(data, dict):
23
+ return data
24
+ parameters = data.get("parameters", [])
25
+ if not isinstance(parameters, list):
26
+ return data
27
+
28
+ keyed: dict[str, Any] = {}
29
+ for param in parameters:
30
+ name = param.get("name") if isinstance(param, dict) else None
31
+ if not isinstance(name, str):
32
+ raise ValueError("every parameter must have a 'name' to be referenced")
33
+ keyed[name.lower()] = param
34
+ return {**data, "parameters": keyed}
35
+
36
+
37
+ class Item(TypedDict):
38
+ input: Path
39
+ output: Path
40
+ data: Optional[dict[str, ScriptsBlock]]
41
+
42
+
43
+ items: list[Item] = [
44
+ Item(
45
+ input=Path("data/blocks"),
46
+ output=Path("out/scriptsBlocks.json"),
47
+ data=None,
48
+ ),
49
+ Item(
50
+ input=Path("data/roots"),
51
+ output=Path("out/roots.json"),
52
+ data=None,
53
+ ),
54
+ ]
55
+
56
+
57
+
58
+
59
+
60
+ ## PROCESS ITEM
61
+
62
+ def fetchBlocks(item: Item):
63
+ # combine all block yaml files into one, validating each against the schema
64
+ blocks: dict[str, ScriptsBlock] = {}
65
+ for file_path in item['input'].glob("*.yaml"):
66
+ with open(file_path, 'r', encoding='utf-8') as f:
67
+ data = yaml.safe_load(f)
68
+ try:
69
+ block = ScriptsBlock.model_validate(data)
70
+ except ValidationError as exc:
71
+ raise ValueError(f"Invalid block definition in '{file_path}':\n{exc}") from exc
72
+ if block.name.lower() in blocks:
73
+ raise ValueError(f"Duplicate block name '{block.name}' found in '{file_path}'.")
74
+ blocks[block.name.lower()] = block
75
+ return blocks
76
+
77
+ def process_properties(block: ScriptsBlock) -> ScriptsBlock:
78
+ # transform each property group's list into a dictionary keyed by property name
79
+ if block.properties is None:
80
+ return block
81
+ for property_data in block.properties.values():
82
+ properties: list = property_data[PropertyData.PROPERTIES]
83
+ property_data[PropertyData.PROPERTIES] = {prop[PropertyData.NAME]: prop for prop in properties}
84
+ return block
85
+
86
+ # main
87
+
88
+ def process(item: Item):
89
+ blocks = fetchBlocks(item)
90
+ for block in blocks.values():
91
+ process_properties(block)
92
+
93
+ return blocks
94
+
95
+
96
+
97
+ ## PREPARE
98
+
99
+ # relationship and hierarchy
100
+
101
+ def add_source_ref_to_targets(
102
+ source_name: str,
103
+ targets_names: list[str],
104
+ items: list[Item],
105
+ array_type: Literal["children", "variants"]):
106
+ """
107
+ This is used to add source_name to the targets's array_type.
108
+
109
+ For example:
110
+ - source_name -> child_name
111
+ - targets_names -> child_parents
112
+ - array_type -> children (of parent)
113
+ will add source_name in the array stored at key array_type of the targets of targets_names.
114
+ (child_name --append--> array (key: children) of each parents)
115
+ """
116
+ _processed: list[str] = []
117
+ for item in items:
118
+ blocks = item['data']
119
+ assert blocks is not None, "Something went wrong."
120
+
121
+ # add children_name ref to every parents
122
+ for parent_name in targets_names:
123
+ parent_block = blocks.get(parent_name.lower(), None)
124
+ if parent_block is None:
125
+ continue
126
+
127
+ # init if needed
128
+ if getattr(parent_block, array_type) is None:
129
+ setattr(parent_block, array_type, [])
130
+
131
+ # add name
132
+ children: list[str] = getattr(parent_block, array_type)
133
+ if source_name not in children:
134
+ children.append(source_name)
135
+ _processed.append(parent_name)
136
+
137
+ # we make sure they were all properly processed
138
+ for parent_name in targets_names:
139
+ if parent_name not in _processed:
140
+ raise ValueError(f"Parent block '{parent_name}' not found for block '{source_name}'.")
141
+
142
+
143
+ def prepare_relationship(block: ScriptsBlock, items: list[Item]):
144
+ """
145
+ This will retrieve the parents of this block and add a reference to itself inside the data,
146
+ of this parent block
147
+ """
148
+ # handle parents
149
+ parents = block.parents
150
+ if parents:
151
+ add_source_ref_to_targets(block.name, parents, items, "children")
152
+
153
+ # handle variants
154
+ variant = block.variant_of
155
+ if variant:
156
+ add_source_ref_to_targets(block.name, [variant], items, "variants")
157
+
158
+
159
+ # description and reference
160
+
161
+ def find_block_in_items(block_name: str, items: list[Item]) -> ScriptsBlock | None:
162
+ for item in items:
163
+ blocks = item['data']
164
+ assert blocks is not None, "Something is wrong."
165
+
166
+ if block_name.lower() in blocks:
167
+ return blocks[block_name.lower()]
168
+ return None
169
+
170
+
171
+ def copy_missing_fields(target: BaseModel, source: BaseModel) -> None:
172
+ """Copies every field explicitly set on source that isn't already set on target."""
173
+ for field_name in source.model_fields_set:
174
+ if field_name not in target.model_fields_set:
175
+ setattr(target, field_name, getattr(source, field_name))
176
+
177
+ def copy_block_ref(block: ScriptsBlock, items: list[Item]) -> ScriptsBlock:
178
+ """
179
+ #desc copies the description of the source block
180
+ #ref copies every field of that source block into the block (if not already present)
181
+ """
182
+ # copy #desc
183
+ if block.ref_desc is not None:
184
+ desc_source = find_block_in_items(block.ref_desc, items)
185
+
186
+ if desc_source is not None:
187
+ if desc_source.description is None:
188
+ raise ValueError(f"Block '{block.name}' has #desc but source has no description.")
189
+ block.description = desc_source.description
190
+
191
+ # copy the short description if available too
192
+ if desc_source.short_description is not None:
193
+ block.short_description = desc_source.short_description
194
+
195
+ # copy #ref
196
+ if block.ref is not None:
197
+ ref_source = find_block_in_items(block.ref, items)
198
+ if ref_source is not None:
199
+ copy_missing_fields(block, ref_source)
200
+
201
+ return block
202
+
203
+
204
+ # parameters
205
+
206
+ def get_source_parameter(ref_key: str, items: list[Item]) -> ParameterDefinition:
207
+ """
208
+ This will return the source parameter from the ref_key.
209
+ The ref_key is in the format "BlockName/ParameterName"
210
+ """
211
+ # find the source block and parameter
212
+ path = ref_key.split('/')
213
+ assert len(path) == 2, f"Invalid #ref format for '{ref_key}'. Needs to be in format 'BlockName/ParameterName'."
214
+ origin_block = path[0]
215
+ origin_param = path[1].lower()
216
+
217
+ origin_block_data = find_block_in_items(origin_block, items)
218
+
219
+ # make sure the source block and parameter exist
220
+ if origin_block_data is None:
221
+ raise ValueError(f"Source block '{origin_block}' not found for parameter '{origin_param}'.")
222
+ if origin_param not in origin_block_data.parameters:
223
+ raise ValueError(f"Source block '{origin_block}' has no parameter '{origin_param}'.")
224
+
225
+ return origin_block_data.parameters[origin_param]
226
+
227
+ def copy_parameters_ref(parameters: dict[str, ParameterDefinition], items: list[Item]):
228
+ for param_key, param in parameters.items():
229
+ if param.ref is not None:
230
+ source_param = get_source_parameter(param.ref, items)
231
+
232
+ # copy every field from the source parameter
233
+ # that isn't already set on the parameter
234
+ copy_missing_fields(param, source_param)
235
+
236
+ if param.ref_desc is not None:
237
+ source_param = get_source_parameter(param.ref_desc, items)
238
+
239
+ # make sure the source parameter has a description
240
+ if source_param.description is None:
241
+ raise ValueError(f"Parameter '{param_key}' has #desc but source has no description.")
242
+
243
+ # only copy the description
244
+ param.description = source_param.description
245
+
246
+
247
+ # main
248
+
249
+ def prepare(item: Item, items: list[Item]):
250
+ blocks = item['data']
251
+ assert blocks is not None, "Something went wrong."
252
+
253
+ for block in blocks.values():
254
+ prepare_relationship(block, items)
255
+ copy_block_ref(block, items)
256
+ copy_parameters_ref(block.parameters, items)
257
+
258
+
259
+ return blocks
260
+
261
+
262
+ # serialization
263
+
264
+ def serialize(blocks: dict[str, ScriptsBlock]) -> dict[str, dict[str, Any]]:
265
+ # only write the fields that were set, using their schema (alias) names
266
+ # the version is only used for validation, so it is not written
267
+ return {
268
+ name: block.model_dump(by_alias=True, exclude_unset=True, exclude={"version"})
269
+ for name, block in blocks.items()
270
+ }
271
+
272
+
273
+ def main():
274
+ for item in items:
275
+ item['data'] = process(item)
276
+ for item in items:
277
+ item['data'] = prepare(item, items)
278
+ for item in items:
279
+ output_file = item['output']
280
+ output_file.parent.mkdir(parents=True, exist_ok=True)
281
+ with open(output_file, 'w', encoding='utf-8') as f:
282
+ assert item['data'] is not None
283
+ json.dump(serialize(item['data']), f, indent=2,
284
+ ensure_ascii=False, sort_keys=True)
285
+
286
+ if __name__ == "__main__":
287
+ main()
@@ -7,9 +7,9 @@
7
7
  import json
8
8
  import sys
9
9
  from pathlib import Path
10
- from typing import Any, Literal, Self
10
+ from typing import Annotated, Any, Literal, Self
11
11
 
12
- from pydantic import BaseModel, ConfigDict, Field, model_validator
12
+ from pydantic import BaseModel, ConfigDict, Field, WithJsonSchema, model_validator
13
13
  from pydantic.json_schema import GenerateJsonSchema, JsonSchemaValue
14
14
  from pydantic_core import core_schema
15
15
 
@@ -17,6 +17,9 @@ DEFAULT_OUTPUT = Path(__file__).resolve().parents[2] / "schemas" / "blocks.json"
17
17
 
18
18
  ScalarType = Literal["string", "float", "boolean", "integer"]
19
19
 
20
+ # Accepts whole numbers as int so they are not widened to float on output. The JSON Schema stays "number".
21
+ Number = Annotated[int | float, WithJsonSchema({"type": "number"})]
22
+
20
23
 
21
24
  class SchemaModel(BaseModel):
22
25
  """Base model. Unknown keys are rejected and no value may be null."""
@@ -109,12 +112,12 @@ class BlockDeprecation(SchemaModel):
109
112
 
110
113
  class _ArrayBase(SchemaModel):
111
114
  separator: str = Field(description="Specifies the separator used to split the string into array items.")
112
- min_items: float | None = Field(
115
+ min_items: Number | None = Field(
113
116
  None,
114
117
  alias="min",
115
118
  description="The minimum number of items required in the array.",
116
119
  )
117
- max_items: float | None = Field(
120
+ max_items: Number | None = Field(
118
121
  None,
119
122
  alias="max",
120
123
  description="The maximum number of items allowed in the array.",
@@ -270,7 +273,7 @@ class ParameterNeed(SchemaModel):
270
273
  """A condition on another parameter that must be met for this parameter to be valid."""
271
274
 
272
275
  name: str | None = Field(None, description="The name of the parameter that this condition depends on.")
273
- values: list[str | float | bool] | None = Field(
276
+ values: list[str | Number | bool] | None = Field(
274
277
  None,
275
278
  description="The values of the dependent parameter that must be met for this parameter to be valid.",
276
279
  )
@@ -280,11 +283,11 @@ class ParameterNeed(SchemaModel):
280
283
  description="A mapping of values of the dependent parameter to the expected type of this parameter. For example, if the dependent parameter is 'type' and it has a value 'integer', then this parameter should be of type 'float'. The key is the value of the dependent parameter, and the value is the expected type of this parameter when the dependent parameter has that value.",
281
284
  examples=[{"integer": "float", "double": "float", "boolean": "boolean", "string": "string"}],
282
285
  )
283
- minimum: float | None = Field(
286
+ minimum: Number | None = Field(
284
287
  None,
285
288
  description="The minimum value for this parameter when the dependent parameter has a specific value. Requires the target parameter to be of type 'float' or 'integer'.",
286
289
  )
287
- maximum: float | None = Field(
290
+ maximum: Number | None = Field(
288
291
  None,
289
292
  description="The maximum value for this parameter when the dependent parameter has a specific value. Requires the target parameter to be of type 'float' or 'integer'.",
290
293
  )
@@ -335,16 +338,16 @@ class ParameterDefinition(SchemaModel):
335
338
  description="DEPRECATED: A list of parent block types for which this parameter is applicable.",
336
339
  )
337
340
  required: bool | None = Field(None, description="Indicates whether the parameter is required.")
338
- default_value: str | float | bool | list[Any] | dict[str, Any] | None = Field(
341
+ default_value: str | Number | bool | list[Any] | dict[str, Any] | None = Field(
339
342
  None,
340
343
  alias="default",
341
344
  description="The default value for the parameter if not specified.",
342
345
  )
343
- minimum: float | None = Field(
346
+ minimum: Number | None = Field(
344
347
  None,
345
348
  description="The minimum value for the parameter if it is a number. Needs type.main to be 'float' or 'integer' to be applicable.",
346
349
  )
347
- maximum: float | None = Field(
350
+ maximum: Number | None = Field(
348
351
  None,
349
352
  description="The maximum value for the parameter if it is a number. Needs type.main to be 'float' or 'integer' to be applicable.",
350
353
  )
@@ -353,7 +356,7 @@ class ParameterDefinition(SchemaModel):
353
356
  alias="type",
354
357
  description="The data type of the parameter.",
355
358
  )
356
- values: list[str | float] | None = Field(None, description="A list of allowed values for the parameter.")
359
+ values: list[str | Number] | None = Field(None, description="A list of allowed values for the parameter.")
357
360
  deprecated: ParameterDeprecation | None = None
358
361
  needs: list[ParameterNeed] | None = Field(
359
362
  None,
@@ -1,14 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pz-scripts-data
3
- Version: 1.1.1
3
+ Version: 1.1.2
4
4
  Summary: Dataset for the Scripts of Project Zomboid.
5
5
  Author: SimKDT
6
- License: PZ Scripts Data License
7
- This dataset is provided as fully open-source. You are free to use, modify, repost this dataset and any tools in this projects. Any contribution to it is welcomed.
8
-
9
- We are not responsible for what is made with this dataset, whenever it gets updated and breaks anything.
6
+ License-Expression: MIT
10
7
  Classifier: Programming Language :: Python :: 3.12
11
- Classifier: License :: OSI Approved :: MIT License
12
8
  Classifier: Topic :: Database
13
9
  Classifier: Operating System :: OS Independent
14
10
  Classifier: Development Status :: 5 - Production/Stable
@@ -1,65 +0,0 @@
1
- from enum import StrEnum
2
-
3
- class BlockProperties(StrEnum):
4
- VERSION = 'version'
5
- REF_DESC = '#desc'
6
- REF_FULL = '#ref'
7
- DEPRECATED = 'deprecated'
8
-
9
- NAME = 'name'
10
- ID = 'ID'
11
- SOFT_OVERRIDE = 'softOverride'
12
- NO_COMMA = 'noComma'
13
- PATTERN = 'pattern'
14
-
15
- DESCRIPTION = 'description'
16
- SHORT_DESCRIPTION = 'short_description'
17
-
18
- VARIANT = 'variantOf'
19
- VARIANTS = 'variants'
20
-
21
- PARENTS = 'parents'
22
- CHILDREN = 'children'
23
- NEEDS_CHILDREN = 'needsChildren'
24
- PARAMETERS = 'parameters'
25
- PROPERTIES = 'properties'
26
-
27
- IS_ROOT = 'isRoot'
28
-
29
- class IDProperties(StrEnum):
30
- OPTIONAL = 'optional'
31
- CAN_HAVE_SPACES = 'canHaveSpace'
32
- FORBIDDEN = 'forbidden'
33
-
34
- VALUES = 'values'
35
- PARENTS_WITHOUT = 'parentsWithout'
36
- AS_TYPE = 'asType'
37
-
38
- TRANSLATION = 'translation'
39
-
40
- class ParameterProperties(StrEnum):
41
- REF_DESC = '#desc'
42
- REF_FULL = '#ref'
43
-
44
- NAME = 'name'
45
- DESCRIPTION = 'description'
46
-
47
- TYPE = 'type'
48
- DEFAULT = 'default'
49
- REQUIRED = 'required'
50
- CAN_BE_EMPTY = 'canBeEmpty'
51
- MINIMUM = 'minimum'
52
- MAXIMUM = 'maximum'
53
- VALUES = 'values'
54
-
55
- DEPRECATED = 'deprecated'
56
- IS_USELESS = 'isUseless'
57
- ALLOW_DUPLICATES = 'allowDuplicates'
58
- INCOMPATIBLE_WITH = 'incompatibleWith'
59
-
60
- SEE_ALSO = 'seeAlso'
61
-
62
- class PropertyData(StrEnum):
63
- NAME = 'name'
64
- DESCRIPTION = 'description'
65
- PROPERTIES = 'properties'
@@ -1,279 +0,0 @@
1
- import os, json, yaml
2
- from pathlib import Path
3
- from typing import TypedDict, Optional, Any
4
-
5
- from pz_scripts_data.blocks import BlockProperties, ParameterProperties, IDProperties, PropertyData
6
-
7
-
8
- type ScriptsBlock = dict[str, Any]
9
- class Item(TypedDict):
10
- input: Path
11
- output: Path
12
- data: Optional[dict[str, ScriptsBlock]]
13
-
14
-
15
- items: list[Item] = [
16
- Item(
17
- input=Path("data/blocks"),
18
- output=Path("out/scriptsBlocks.json"),
19
- data=None,
20
- ),
21
- Item(
22
- input=Path("data/roots"),
23
- output=Path("out/roots.json"),
24
- data=None,
25
- ),
26
- ]
27
-
28
-
29
-
30
-
31
-
32
- ## PROCESS ITEM
33
-
34
- def fetchBlocks(item: Item):
35
- # combine all block json files into one
36
- blocks: dict[str, ScriptsBlock] = {}
37
- for file_path in item['input'].glob("*.yaml"):
38
- with open(file_path, 'r', encoding='utf-8') as f:
39
- data = yaml.safe_load(f)
40
- key = data[ParameterProperties.NAME]
41
- if key in blocks:
42
- raise ValueError(f"Duplicate block name '{key}' found in '{file_path}'.")
43
- blocks[key] = data
44
- return blocks
45
-
46
- def process_parameters(data: ScriptsBlock) -> ScriptsBlock:
47
- # remove unnecessary fields
48
- data.pop(BlockProperties.VERSION)
49
-
50
- # replace parameters array into dictionary
51
- # where keys are lowercased parameter names
52
- parameters_data = data.get(BlockProperties.PARAMETERS, [])
53
- parameters_new = {}
54
- for param_data in parameters_data:
55
- name = param_data[ParameterProperties.NAME]
56
- parameters_new[name.lower()] = param_data
57
- data[BlockProperties.PARAMETERS] = parameters_new
58
-
59
- # handle properties
60
- properties_data = data.get(BlockProperties.PROPERTIES, None)
61
- if properties_data is not None:
62
- for property_data in properties_data.values():
63
- # transform into dictionary too
64
- new_properties = {}
65
- properties: list = property_data[PropertyData.PROPERTIES]
66
- for prop_data in properties:
67
- prop_name = prop_data[PropertyData.NAME]
68
- new_properties[prop_name] = prop_data
69
- property_data[PropertyData.PROPERTIES] = new_properties
70
-
71
- return data
72
-
73
- # main
74
-
75
- def process(item: Item):
76
- blocks = fetchBlocks(item)
77
- for key, block in blocks.items():
78
- blocks[key] = process_parameters(block)
79
-
80
- return blocks
81
-
82
-
83
-
84
- ## PREPARE
85
-
86
- # relationship and hierarchy
87
-
88
- def add_source_ref_to_targets(
89
- source_name: str,
90
- targets_names: list[str],
91
- items: list[Item],
92
- array_type: BlockProperties):
93
- """
94
- This is used to add source_name to the targets's array_type.
95
-
96
- For example:
97
- - source_name -> child_name
98
- - targets_names -> child_parents
99
- - array_type -> children (of parent)
100
- will add source_name in the array stored at key array_type of the targets of targets_names.
101
- (child_name --append--> array (key: children) of each parents)
102
- """
103
- _processed: list[str] = []
104
- for item in items:
105
- blocks = item['data']
106
- assert blocks is not None, "Something went wrong."
107
-
108
- # add children_name ref to every parents
109
- for parent_name in targets_names:
110
- parent_block = blocks.get(parent_name, None)
111
- if parent_block is None:
112
- continue
113
-
114
- # init if needed
115
- if array_type not in parent_block:
116
- parent_block[array_type] = []
117
-
118
- # add name
119
- children: list[str] = parent_block[array_type]
120
- if source_name not in children:
121
- children.append(source_name)
122
- _processed.append(parent_name)
123
-
124
- # we make sure they were all properly processed
125
- for parent_name in targets_names:
126
- if parent_name not in _processed:
127
- raise ValueError(f"Parent block '{parent_name}' not found for block '{source_name}'.")
128
-
129
-
130
- def prepare_relationship(block: ScriptsBlock, items: list[Item]):
131
- """
132
- This will retrieve the parents of this block and add a reference to itself inside the data,
133
- of this parent block
134
- """
135
- # handle parents
136
- parents = block.get(BlockProperties.PARENTS, [])
137
- if parents:
138
- add_source_ref_to_targets(
139
- block[BlockProperties.NAME], parents, items,
140
- BlockProperties.CHILDREN)
141
-
142
- # handle variants
143
- variant = block.get(BlockProperties.VARIANT, None)
144
- if variant:
145
- add_source_ref_to_targets(
146
- block[BlockProperties.NAME], [variant], items,
147
- BlockProperties.VARIANTS)
148
-
149
-
150
- # description and reference
151
-
152
- def find_block_in_items(block_name: str, items: list[Item]) -> ScriptsBlock | None:
153
- for item in items:
154
- blocks = item['data']
155
- assert blocks is not None, "Something is wrong."
156
-
157
- if block_name in blocks:
158
- return blocks[block_name]
159
- return None
160
-
161
-
162
- def copy_block_ref(block: ScriptsBlock, items: list[Item]) -> ScriptsBlock:
163
- """
164
- #desc copies the description of the source block
165
- #ref copies every key values of that source block into the block (if not already present)
166
- """
167
- # copy #desc
168
- if BlockProperties.REF_DESC in block:
169
- desc_source_key: str = block[BlockProperties.REF_DESC]
170
- desc_source = find_block_in_items(desc_source_key, items)
171
-
172
- if desc_source is not None:
173
- block[BlockProperties.DESCRIPTION] = desc_source[BlockProperties.DESCRIPTION]
174
-
175
- # copy the short description if available too
176
- if BlockProperties.SHORT_DESCRIPTION in desc_source:
177
- block[BlockProperties.SHORT_DESCRIPTION] = desc_source[BlockProperties.SHORT_DESCRIPTION]
178
-
179
- # copy #ref
180
- if BlockProperties.REF_FULL in block:
181
- ref_source_key: str = block[BlockProperties.REF_FULL]
182
- ref_source = find_block_in_items(ref_source_key, items)
183
- if ref_source is not None:
184
- # we copy every key-value from the ref_block
185
- # that doesn't already exist in the block
186
- for key, value in ref_source.items():
187
- if key not in block:
188
- block[key] = value
189
-
190
- return block
191
-
192
-
193
- # parameters
194
-
195
- def get_source_parameter(ref_key: str, items: list[Item]) -> dict:
196
- """
197
- This will return the source parameter data from the ref_key.
198
- The ref_key is in the format "BlockName/ParameterName"
199
- """
200
- # find the source block and parameter
201
- path = ref_key.split('/')
202
- assert len(path) == 2, f"Invalid #ref format for '{ref_key}'. Needs to be in format 'BlockName/ParameterName'."
203
- origin_block = path[0]
204
- origin_param = path[1].lower()
205
-
206
- origin_block_data = find_block_in_items(origin_block, items)
207
-
208
- # make sure the source block and parameter exist
209
- if origin_block_data is None:
210
- raise ValueError(f"Source block '{origin_block}' not found for parameter '{origin_param}'.")
211
- if BlockProperties.PARAMETERS not in origin_block_data:
212
- raise ValueError(f"Source block '{origin_block}' has no parameters for parameter '{origin_param}'.")
213
- if origin_param not in origin_block_data[BlockProperties.PARAMETERS]:
214
- raise ValueError(f"Source block '{origin_block}' has no parameter '{origin_param}'.")
215
-
216
- return origin_block_data[BlockProperties.PARAMETERS][origin_param]
217
-
218
- def copy_parameters_ref(parameters: dict[str, dict], items: list[Item]):
219
- for param_key, param_data in parameters.items():
220
- if ParameterProperties.REF_FULL in param_data:
221
- ref_key = param_data[ParameterProperties.REF_FULL]
222
-
223
- source_param_data = get_source_parameter(ref_key, items)
224
- assert source_param_data is not None, f"Source parameter '{ref_key}' not found for parameter '{param_key}'."
225
-
226
- # copy every key-value from the source parameter data
227
- # that doesn't already exist in the parameter data
228
- for key, value in source_param_data.items():
229
- if key not in param_data:
230
- param_data[key] = value
231
-
232
- if ParameterProperties.REF_DESC in param_data:
233
- desc_key = param_data[ParameterProperties.REF_DESC]
234
-
235
- source_param_data = get_source_parameter(desc_key, items)
236
- assert source_param_data is not None, f"Source parameter '{desc_key}' not found for parameter '{param_key}'."
237
-
238
- # make sure the source parameter has a description
239
- if ParameterProperties.DESCRIPTION not in source_param_data:
240
- raise ValueError(f"Parameter '{param_key}' has #desc but source has no description.")
241
-
242
- # only copy the description
243
- param_data[ParameterProperties.DESCRIPTION] = source_param_data[ParameterProperties.DESCRIPTION]
244
-
245
-
246
- # main
247
-
248
- def prepare(item: Item, items: list[Item]):
249
- blocks = item['data']
250
- assert blocks is not None, "Something went wrong."
251
-
252
- for block in blocks.values():
253
- prepare_relationship(block, items)
254
- copy_block_ref(block, items)
255
-
256
- parameters = block.get(BlockProperties.PARAMETERS, {})
257
- copy_parameters_ref(parameters, items)
258
-
259
-
260
- return blocks
261
-
262
-
263
-
264
-
265
-
266
-
267
- def main():
268
- for item in items:
269
- item['data'] = process(item)
270
- for item in items:
271
- item['data'] = prepare(item, items)
272
- for item in items:
273
- output_file = item['output']
274
- output_file.parent.mkdir(parents=True, exist_ok=True)
275
- with open(output_file, 'w', encoding='utf-8') as f:
276
- json.dump(item['data'], f, indent=2, ensure_ascii=False, sort_keys=True)
277
-
278
- if __name__ == "__main__":
279
- main()
File without changes