dfpyre 0.4.2__py3-none-any.whl → 0.10.5__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.

Potentially problematic release.


This version of dfpyre might be problematic. Click here for more details.

@@ -0,0 +1,222 @@
1
+ """
2
+ Generates Codeblock classes with static methods for each action.
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+ from dfpyre.core.actiondump import ACTIONDUMP, ActionArgument, ActionTag, TagOption
7
+ from dfpyre.util.util import flatten
8
+ from dfpyre.gen.action_class_data import (
9
+ INDENT, CODEBLOCK_LOOKUP, PARAM_NAME_REPLACEMENTS, PARAM_TYPE_LOOKUP, TEMPLATE_OVERRIDES, OUTPUT_PATH, IMPORTS, CLASS_ALIASES,
10
+ get_method_name_and_aliases, to_valid_identifier
11
+ )
12
+
13
+
14
+ @dataclass
15
+ class ParameterData:
16
+ name: str
17
+ types: str
18
+ description: str
19
+ notes: str
20
+ is_optional: bool
21
+ has_none: bool
22
+
23
+ def get_param_string(self) -> str:
24
+ param_str = f'{self.name}: {self.types}'
25
+ if self.has_none or self.is_optional:
26
+ param_str += '=None'
27
+ return param_str
28
+
29
+ def get_docstring(self) -> str:
30
+ docstring = f':param {self.types} {self.name}: {self.description}'
31
+ if self.is_optional:
32
+ docstring += ' (optional)'
33
+ if self.notes:
34
+ docstring += f' ({self.notes})'
35
+ return docstring
36
+
37
+ def __eq__(self, value):
38
+ return self is value
39
+
40
+
41
+ def parse_parameters(action_arguments: list[tuple[ActionArgument, ...]]) -> list[ParameterData]:
42
+ parameter_list: list[ParameterData] = []
43
+ prev_optional_exists = False
44
+
45
+ for arg_union in action_arguments:
46
+ if len(arg_union) == 1:
47
+ param_name = arg_union[0].type.lower()
48
+ if param_name in PARAM_NAME_REPLACEMENTS:
49
+ param_name = PARAM_NAME_REPLACEMENTS[param_name]
50
+ else:
51
+ param_name = f'arg'
52
+
53
+ is_optional = arg_union[0].optional
54
+ has_none = 'NONE' in (arg.type for arg in arg_union)
55
+
56
+ param_types_list = list(flatten(PARAM_TYPE_LOOKUP[arg.type] for arg in arg_union))
57
+ if is_optional:
58
+ param_types_list.append('None')
59
+ prev_optional_exists = True
60
+ elif prev_optional_exists:
61
+ # If a previous optional param exists, all subsequent params must be
62
+ # optional as well, so we set `has_none` to True to add `=None` to future params.
63
+ has_none = True
64
+
65
+ param_types_dedup = list(dict.fromkeys(param_types_list))
66
+ param_types = ' | '.join(param_types_dedup)
67
+
68
+ if arg_union[0].plural:
69
+ param_name += 's'
70
+ plural_param_types = ' | '.join(t for t in param_types_dedup if t != 'None') # Doesn't make sense to have a list of None
71
+ param_types = f'list[{plural_param_types}] | {param_types}'
72
+
73
+ param_description = ' OR '.join(arg.description for arg in arg_union if arg.description)
74
+ param_notes = ' OR '.join(arg.notes for arg in arg_union if arg.notes)
75
+
76
+ param = ParameterData(param_name, param_types, param_description, param_notes, is_optional, has_none)
77
+ parameter_list.append(param)
78
+
79
+ # Add numbers to params with duplicate names
80
+ param_names = [p.name for p in parameter_list]
81
+ param_name_lookup = {p.name: [q for q in parameter_list if p.name == q.name] for p in parameter_list}
82
+ for param in parameter_list:
83
+ if param_names.count(param.name) > 1:
84
+ params_with_name = param_name_lookup[param.name]
85
+ param_index = params_with_name.index(param)
86
+ param.name += str(param_index + 1)
87
+
88
+ return parameter_list
89
+
90
+
91
+ @dataclass
92
+ class TagData:
93
+ name: str
94
+ options: list[TagOption]
95
+ default: str
96
+
97
+ def get_varname(self) -> str:
98
+ return to_valid_identifier(self.name.lower())
99
+
100
+ def get_param_string(self) -> str:
101
+ options_list = ', '.join(f'"{o.name}"' for o in self.options)
102
+ tag_type = f'Literal[{options_list}]'
103
+ return f'{self.get_varname()}: {tag_type}="{self.default}"'
104
+
105
+ def get_docstring(self) -> str:
106
+ docstring_lines = [f':param str {self.get_varname()}: {self.name}']
107
+ docstring_lines += [f'{INDENT*2}- {o.name}: {o.description}' for o in self.options if o.description]
108
+ if len(docstring_lines) > 1:
109
+ docstring_lines.insert(1, '')
110
+ return '\n'.join(docstring_lines)
111
+
112
+
113
+ def parse_tags(action_tags: list[ActionTag]) -> list[TagData]:
114
+ return [TagData(t.name, t.options, t.default) for t in action_tags]
115
+
116
+
117
+ def generate_actions():
118
+ generated_lines: list[str] = IMPORTS.copy()
119
+ generated_lines += ['']
120
+
121
+ for codeblock_type, actions in ACTIONDUMP.action_data.items():
122
+ codeblock_data = CODEBLOCK_LOOKUP.get(codeblock_type)
123
+ if codeblock_data is None:
124
+ continue
125
+
126
+ class_name, method_template = codeblock_data
127
+
128
+ class_docstring = ACTIONDUMP.codeblock_data[codeblock_type].description
129
+ class_def_lines = [
130
+ f'class {class_name}:',
131
+ f'{INDENT}"""',
132
+ f'{INDENT}{class_docstring}',
133
+ f'{INDENT}"""',
134
+ ''
135
+ ]
136
+ generated_lines += class_def_lines
137
+
138
+ for action_name, action_data in actions.items():
139
+ if action_data.is_deprecated:
140
+ # Skip deprecated actions
141
+ continue
142
+
143
+ method_data = get_method_name_and_aliases(codeblock_type, action_name)
144
+ if method_data is None:
145
+ continue
146
+ method_name, method_aliases = method_data
147
+
148
+ template_overrides = TEMPLATE_OVERRIDES.get(codeblock_type)
149
+ if template_overrides:
150
+ override_template = template_overrides.get(action_name)
151
+ if override_template is not None:
152
+ method_template = override_template
153
+
154
+ action_description = action_data.description or ''
155
+ if action_description:
156
+ action_description = f'{INDENT}{action_description}\n\n'
157
+
158
+ # Get tag data
159
+ tags = parse_tags(action_data.tags)
160
+
161
+ tag_parameter_list = ', '.join(t.get_param_string() for t in tags)
162
+ if tag_parameter_list:
163
+ tag_parameter_list = f'{tag_parameter_list}, '
164
+
165
+ tag_values = ', '.join(f"'{t.name}': {t.get_varname()}" for t in tags)
166
+
167
+ # Get parameter data
168
+ parameters = parse_parameters(action_data.arguments)
169
+
170
+ parameter_list = ', '.join(p.get_param_string() for p in parameters)
171
+ if parameter_list:
172
+ parameter_list += ', '
173
+
174
+ parameter_names = ', '.join(p.name for p in parameters)
175
+ if parameter_names:
176
+ parameter_names += ','
177
+
178
+ docstring_list = [p.get_docstring() for p in parameters] + [t.get_docstring() for t in tags]
179
+ parameter_docstrings = '\n'.join(INDENT + s for s in docstring_list)
180
+
181
+ if parameter_docstrings:
182
+ parameter_docstrings += '\n'
183
+
184
+ if action_description or parameter_docstrings:
185
+ # Fix indentation
186
+ parameter_docstrings += INDENT
187
+
188
+ method_code = method_template.format(
189
+ method_name = method_name,
190
+ parameter_list = parameter_list,
191
+ parameter_names = parameter_names,
192
+ parameter_docstrings = parameter_docstrings,
193
+ tag_parameter_list = tag_parameter_list,
194
+ tag_values = tag_values,
195
+ codeblock_type = codeblock_type,
196
+ action_name = action_name,
197
+ action_description = action_description
198
+ )
199
+
200
+ method_lines = [f'@staticmethod']
201
+ method_lines += method_code.split('\n')
202
+
203
+ for alias in method_aliases:
204
+ method_lines += [f'{alias} = {method_name}']
205
+
206
+ method_lines = [INDENT + l for l in method_lines]
207
+ method_lines += ['']
208
+ generated_lines += method_lines
209
+
210
+ class_aliases = CLASS_ALIASES.get(codeblock_type) or []
211
+ for alias in class_aliases:
212
+ alias_def = f'{alias} = {class_name}'
213
+ generated_lines.append(alias_def)
214
+
215
+
216
+ with open(OUTPUT_PATH, 'w') as f:
217
+ f.write('\n'.join(generated_lines) + '\n')
218
+
219
+
220
+ if __name__ == '__main__':
221
+ generate_actions()
222
+ print(f'Wrote Codeblock action classes to {OUTPUT_PATH}.')
@@ -0,0 +1,43 @@
1
+ """
2
+ Generates `Literal` objects containing action names for each codeblock type.
3
+ This allows action names to be autocompleted if the user's IDE supports it.
4
+ """
5
+
6
+ from datetime import datetime, timezone
7
+ from dfpyre.core.actiondump import ACTIONDUMP
8
+
9
+
10
+ OUTPUT_PATH = 'dfpyre/gen/action_literals.py'
11
+
12
+
13
+ def generate_action_literals():
14
+ generated_lines: list[str] = [
15
+ f'# Auto generated by pyre {datetime.now(timezone.utc).isoformat()}',
16
+ 'from typing import Literal\n'
17
+ ]
18
+ for codeblock_type, actions in ACTIONDUMP.action_data.items():
19
+ if len(actions) == 1:
20
+ continue
21
+
22
+ filtered_actions = [a for a, d in actions.items() if not d.is_deprecated] # Omit deprecated actions
23
+ if not filtered_actions:
24
+ continue
25
+ action_list = str(filtered_actions)
26
+ literal_line = f'{codeblock_type.upper()}_ACTION = Literal{action_list}'
27
+ generated_lines.append(literal_line)
28
+
29
+ game_value_names = list(ACTIONDUMP.game_values.keys())
30
+ generated_lines += [
31
+ f'GAME_VALUE_NAME = Literal{str(game_value_names)}',
32
+ f'SOUND_NAME = Literal{str(ACTIONDUMP.sound_names)}',
33
+ f'POTION_NAME = Literal{str(ACTIONDUMP.potion_names)}',
34
+ 'SUBACTION = IF_PLAYER_ACTION | IF_ENTITY_ACTION | IF_GAME_ACTION | IF_VAR_ACTION'
35
+ ]
36
+
37
+ with open(OUTPUT_PATH, 'w') as f:
38
+ f.write('\n'.join(generated_lines) + '\n')
39
+
40
+
41
+ if __name__ == '__main__':
42
+ generate_action_literals()
43
+ print(f'Wrote codeblock action Literals to {OUTPUT_PATH}.')
@@ -0,0 +1,274 @@
1
+ import dataclasses
2
+ from dfpyre.util.util import is_number
3
+ from dfpyre.core.items import *
4
+ from dfpyre.core.actiondump import get_default_tags
5
+ from dfpyre.core.codeblock import CodeBlock, CONDITIONAL_CODEBLOCKS, TARGET_CODEBLOCKS, EVENT_CODEBLOCKS
6
+ from dfpyre.gen.action_class_data import get_method_name_and_aliases, to_valid_identifier
7
+
8
+
9
+ IMPORT_STATEMENT = 'from dfpyre import *'
10
+
11
+ CODEBLOCK_FUNCTION_LOOKUP = {
12
+ 'event': 'PlayerEvent',
13
+ 'entity_event': 'EntityEvent',
14
+ 'func': 'Function',
15
+ 'process': 'Process',
16
+ 'call_func': 'CallFunction',
17
+ 'start_process': 'StartProcess',
18
+ 'player_action': 'PlayerAction',
19
+ 'game_action': 'GameAction',
20
+ 'entity_action': 'EntityAction',
21
+ 'if_player': 'IfPlayer',
22
+ 'if_var': 'IfVariable',
23
+ 'if_game': 'IfGame',
24
+ 'if_entity': 'IfEntity',
25
+ 'else': 'Else',
26
+ 'repeat': 'Repeat',
27
+ 'control': 'Control',
28
+ 'select_obj': 'SelectObject',
29
+ 'set_var': 'SetVariable'
30
+ }
31
+
32
+ NO_ACTION_BLOCKS = {'func', 'process', 'call_func', 'start_process', 'else'}
33
+ CONTAINER_CODEBLOCKS = {'event', 'entity_event', 'func', 'process', 'if_player', 'if_entity', 'if_game', 'if_var', 'else', 'repeat'}
34
+ VAR_SCOPE_LOOKUP = {'unsaved': 'g', 'saved': 's', 'local': 'l', 'line': 'i'}
35
+
36
+
37
+ @dataclasses.dataclass
38
+ class GeneratorFlags:
39
+ indent_size: int
40
+ literal_shorthand: bool
41
+ var_shorthand: bool
42
+ preserve_slots: bool
43
+ assign_variable: bool
44
+ include_import: bool
45
+ build_and_send: bool
46
+
47
+
48
+ def item_to_string(class_name: str, i: Item, slot_argument: str):
49
+ i.nbt.pop('DF_NBT')
50
+ stripped_id = i.get_id().replace('minecraft:', '')
51
+ if set(i.nbt.keys()) == {'id', 'count'}:
52
+ if i.get_count() == 1:
53
+ return f"{class_name}('{stripped_id}'{slot_argument})"
54
+ return f"{class_name}('{stripped_id}', {i.get_count()}{slot_argument})"
55
+
56
+ snbt_string = i.get_snbt().replace('\\"', '\\\\"')
57
+ return f'{class_name}.from_snbt("""{snbt_string}""")'
58
+
59
+
60
+ def escape(s: str) -> str:
61
+ return s.replace('\n', '\\n').replace("\'", "\\'")
62
+
63
+
64
+ def str_literal(s: str) -> str:
65
+ return "'" + escape(s) + "'"
66
+
67
+
68
+ def argument_item_to_string(flags: GeneratorFlags, arg_item: CodeItem) -> str:
69
+ class_name = arg_item.__class__.__name__
70
+ has_slot = arg_item.slot is not None and flags.preserve_slots
71
+ slot_argument = f', slot={arg_item.slot}' if has_slot else ''
72
+
73
+ if isinstance(arg_item, Item):
74
+ return item_to_string(class_name, arg_item, slot_argument)
75
+
76
+ if isinstance(arg_item, String):
77
+ literal = str_literal(arg_item.value)
78
+ if not has_slot and flags.literal_shorthand:
79
+ return literal
80
+ return f"{class_name}({literal}{slot_argument})"
81
+
82
+ if isinstance(arg_item, Text):
83
+ literal = str_literal(arg_item.value)
84
+ return f"{class_name}({literal}{slot_argument})"
85
+
86
+ if isinstance(arg_item, Number):
87
+ if not is_number(str(arg_item.value)): # Probably a math expression
88
+ return f"{class_name}({str_literal(arg_item.value)}{slot_argument})"
89
+ if not has_slot and flags.literal_shorthand:
90
+ return str(arg_item.value)
91
+ return f'{class_name}({arg_item.value}{slot_argument})'
92
+
93
+ if isinstance(arg_item, Location):
94
+ loc_components = [arg_item.x, arg_item.y, arg_item.z]
95
+ if arg_item.pitch != 0:
96
+ loc_components.append(arg_item.pitch)
97
+ if arg_item.yaw != 0:
98
+ loc_components.append(arg_item.yaw)
99
+ return f'{class_name}({", ".join(str(c) for c in loc_components)}{slot_argument})'
100
+
101
+ if isinstance(arg_item, Variable):
102
+ name = escape(arg_item.name)
103
+ if not has_slot and flags.var_shorthand:
104
+ return f"'${VAR_SCOPE_LOOKUP[arg_item.scope]} {name}'"
105
+ if arg_item.scope == 'unsaved':
106
+ return f"{class_name}('{name}'{slot_argument})"
107
+ return f"{class_name}('{name}', '{arg_item.scope}'{slot_argument})"
108
+
109
+ if isinstance(arg_item, Sound):
110
+ return f"{class_name}({str_literal(arg_item.name)}, {arg_item.pitch}, {arg_item.vol}{slot_argument})"
111
+
112
+ if isinstance(arg_item, Particle):
113
+ return f'{class_name}({arg_item.particle_data})'
114
+
115
+ if isinstance(arg_item, Potion):
116
+ return f"{class_name}({str_literal(arg_item.name)}, {arg_item.dur}, {arg_item.amp}{slot_argument})"
117
+
118
+ if isinstance(arg_item, GameValue):
119
+ name = str_literal(arg_item.name)
120
+ if arg_item.target == 'Default':
121
+ return f"{class_name}({name}{slot_argument})"
122
+ return f"{class_name}({name}, '{arg_item.target}'{slot_argument})"
123
+
124
+ if isinstance(arg_item, Parameter):
125
+ param_type_class_name = arg_item.param_type.__class__.__name__
126
+ param_args = [str_literal(arg_item.name), f'{param_type_class_name}.{arg_item.param_type.name}']
127
+ if arg_item.plural:
128
+ param_args.append('plural=True')
129
+ if arg_item.optional:
130
+ param_args.append('optional=True')
131
+ if arg_item.default_value is not None:
132
+ param_args.append(f'default_value={argument_item_to_string(flags, arg_item.default_value)}')
133
+ if arg_item.description:
134
+ param_args.append(f"description={str_literal(arg_item.description)}")
135
+ if arg_item.note:
136
+ param_args.append(f"note={str_literal(arg_item.note)}")
137
+ return f'{class_name}({", ".join(param_args)}{slot_argument})'
138
+
139
+ if isinstance(arg_item, Vector):
140
+ return f'{class_name}({arg_item.x}, {arg_item.y}, {arg_item.z}{slot_argument})'
141
+
142
+
143
+ def string_to_python_name(string: str) -> str:
144
+ """Converts `string` into a valid python identifier."""
145
+ string = string.strip()
146
+ if string[0].isnumeric():
147
+ string = '_' + string
148
+ return ''.join(c if c.isalnum() else '_' for c in string)
149
+
150
+
151
+ def add_script_line(flags: GeneratorFlags, script_lines: list[str], indent_level: int, line: str, add_comma: bool=True):
152
+ added_line = ' '*flags.indent_size*indent_level + line
153
+ if add_comma and indent_level > 0:
154
+ added_line += ','
155
+ script_lines.append(added_line)
156
+
157
+
158
+ def generate_script(codeblocks: list[CodeBlock], flags: GeneratorFlags) -> str:
159
+ indent_level = 0
160
+ script_lines = []
161
+ variable_assigned = False
162
+
163
+ if flags.include_import:
164
+ script_lines.append(IMPORT_STATEMENT + '\n')
165
+
166
+ def remove_comma_from_last_line():
167
+ if script_lines[-1].endswith(','):
168
+ script_lines[-1] = script_lines[-1][:-1]
169
+
170
+ def get_var_assignment_snippet() -> str:
171
+ first_block_data = codeblocks[0].data
172
+ if 'data' in first_block_data:
173
+ name = first_block_data['data']
174
+ var_name = name if name else 'unnamed_template'
175
+ else:
176
+ var_name = first_block_data['block'] + '_' + first_block_data['action']
177
+ return f'{string_to_python_name(var_name)} = '
178
+
179
+
180
+ for codeblock in codeblocks:
181
+ # Handle closing brackets
182
+ if codeblock.type == 'bracket':
183
+ if codeblock.data['direct'] == 'close':
184
+ remove_comma_from_last_line()
185
+ indent_level -= 1
186
+ add_script_line(flags, script_lines, indent_level, '])')
187
+ continue
188
+
189
+ # Get codeblock function and set the action method
190
+ function_name = CODEBLOCK_FUNCTION_LOOKUP[codeblock.type]
191
+ if codeblock.type in NO_ACTION_BLOCKS:
192
+ function_args = [str_literal(codeblock.action_name)]
193
+ else:
194
+ method_data = get_method_name_and_aliases(codeblock.type, codeblock.action_name)
195
+ if method_data is None:
196
+ raise PyreException(f'scriptgen: Failed to get method data of {codeblock.action_name}')
197
+
198
+ function_name += f'.{method_data[0]}'
199
+ function_args = []
200
+
201
+ # Add variable assignment if necessary
202
+ var_assignment_snippet = ''
203
+ if flags.assign_variable and not variable_assigned:
204
+ var_assignment_snippet = get_var_assignment_snippet()
205
+ variable_assigned = True
206
+
207
+ # Set function or process name if necessary
208
+ if codeblock.action_name == 'dynamic':
209
+ function_args[0] = str_literal(codeblock.data["data"])
210
+
211
+ # Convert argument objects to valid Python strings
212
+ codeblock_args = [argument_item_to_string(flags, i) for i in codeblock.args]
213
+ if codeblock_args:
214
+ function_args.extend(codeblock_args)
215
+
216
+ sub_action = codeblock.data.get('subAction')
217
+
218
+ # Add tags
219
+ if codeblock.tags:
220
+ default_tags = codeblock.tags
221
+ if sub_action is not None:
222
+ for conditional_block_type in CONDITIONAL_CODEBLOCKS:
223
+ default_tags = get_default_tags(conditional_block_type, sub_action)
224
+ if default_tags:
225
+ break
226
+ else:
227
+ default_tags = get_default_tags(codeblock.data.get('block'), codeblock.action_name)
228
+
229
+ for tag, option in codeblock.tags.items():
230
+ if default_tags[tag] != option:
231
+ tag_param_name = to_valid_identifier(tag.lower())
232
+ function_args.append(f"{tag_param_name}='{option}'")
233
+
234
+ # Add sub-action for repeat and select object
235
+ if sub_action is not None:
236
+ function_args.append(f"sub_action='{sub_action}'")
237
+
238
+ # Add target if necessary
239
+ if codeblock.type in TARGET_CODEBLOCKS and codeblock.target.name != 'SELECTION':
240
+ function_args.append(f'target=Target.{codeblock.target.name}')
241
+
242
+ codeblock_attribute = codeblock.data.get('attribute')
243
+ # Add inversion for NOT
244
+ if codeblock_attribute == 'NOT':
245
+ function_args.append('inverted=True')
246
+
247
+ # Add LS Cancel
248
+ elif codeblock_attribute == 'LS-CANCEL':
249
+ function_args.append('ls_cancel=True')
250
+
251
+ # Create and add the line
252
+ if codeblock.type in CONTAINER_CODEBLOCKS:
253
+ if codeblock.type == 'else':
254
+ line = f'{function_name}(['
255
+ elif codeblock.type in EVENT_CODEBLOCKS and codeblock_attribute is None:
256
+ line = f'{var_assignment_snippet}{function_name}([' # omit `codeblocks=` when we don't need it
257
+ else:
258
+ joined_args = ', '.join(function_args) + ', ' if function_args else ''
259
+ line = f'{var_assignment_snippet}{function_name}({joined_args}codeblocks=['
260
+ add_script_line(flags, script_lines, indent_level, line, False)
261
+ indent_level += 1
262
+ else:
263
+ line = f'{function_name}({", ".join(function_args)})'
264
+ add_script_line(flags, script_lines, indent_level, line)
265
+
266
+ remove_comma_from_last_line()
267
+ indent_level -= 1
268
+ add_script_line(flags, script_lines, indent_level, '])') # add final closing brackets
269
+
270
+ # Add `.build_and_send()` if necessary
271
+ if flags.build_and_send:
272
+ script_lines[-1] += '.build_and_send()'
273
+
274
+ return '\n'.join(script_lines)