minecraft-datapack-language 15.4.28__py3-none-any.whl → 15.4.30__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 (27) hide show
  1. minecraft_datapack_language/__init__.py +23 -2
  2. minecraft_datapack_language/_version.py +2 -2
  3. minecraft_datapack_language/ast_nodes.py +87 -59
  4. minecraft_datapack_language/cli.py +276 -139
  5. minecraft_datapack_language/mdl_compiler.py +470 -0
  6. minecraft_datapack_language/mdl_errors.py +14 -0
  7. minecraft_datapack_language/mdl_lexer.py +624 -0
  8. minecraft_datapack_language/mdl_parser.py +573 -0
  9. minecraft_datapack_language-15.4.30.dist-info/METADATA +266 -0
  10. minecraft_datapack_language-15.4.30.dist-info/RECORD +17 -0
  11. minecraft_datapack_language/cli_build.py +0 -1292
  12. minecraft_datapack_language/cli_check.py +0 -155
  13. minecraft_datapack_language/cli_colors.py +0 -264
  14. minecraft_datapack_language/cli_help.py +0 -508
  15. minecraft_datapack_language/cli_new.py +0 -300
  16. minecraft_datapack_language/cli_utils.py +0 -276
  17. minecraft_datapack_language/expression_processor.py +0 -352
  18. minecraft_datapack_language/linter.py +0 -409
  19. minecraft_datapack_language/mdl_lexer_js.py +0 -754
  20. minecraft_datapack_language/mdl_parser_js.py +0 -1049
  21. minecraft_datapack_language/pack.py +0 -758
  22. minecraft_datapack_language-15.4.28.dist-info/METADATA +0 -1274
  23. minecraft_datapack_language-15.4.28.dist-info/RECORD +0 -25
  24. {minecraft_datapack_language-15.4.28.dist-info → minecraft_datapack_language-15.4.30.dist-info}/WHEEL +0 -0
  25. {minecraft_datapack_language-15.4.28.dist-info → minecraft_datapack_language-15.4.30.dist-info}/entry_points.txt +0 -0
  26. {minecraft_datapack_language-15.4.28.dist-info → minecraft_datapack_language-15.4.30.dist-info}/licenses/LICENSE +0 -0
  27. {minecraft_datapack_language-15.4.28.dist-info → minecraft_datapack_language-15.4.30.dist-info}/top_level.txt +0 -0
@@ -1,300 +0,0 @@
1
- """
2
- CLI New Project - Create new MDL projects with templates
3
- """
4
-
5
- import os
6
- import shutil
7
- from pathlib import Path
8
- from typing import Optional
9
-
10
- from .cli_utils import _slugify
11
-
12
-
13
- def create_new_project(project_name: str, pack_name: str = None, pack_format: int = 82) -> None:
14
- """Create a new MDL project with template files."""
15
- # Validate project name
16
- if not project_name or not project_name.strip():
17
- raise ValueError("Project name cannot be empty")
18
-
19
- # Clean and validate project name
20
- clean_name = _slugify(project_name.strip())
21
- if not clean_name:
22
- raise ValueError("Project name must contain valid characters")
23
-
24
- # Use pack_name if provided, otherwise use project name
25
- if pack_name is None:
26
- pack_name = project_name
27
-
28
- # Validate pack format
29
- if not isinstance(pack_format, int) or pack_format < 1:
30
- raise ValueError("Pack format must be a positive integer")
31
-
32
- # Create project directory
33
- project_dir = Path(clean_name)
34
- if project_dir.exists():
35
- raise ValueError(f"Project directory '{clean_name}' already exists")
36
-
37
- try:
38
- # Create the project directory
39
- project_dir.mkdir(parents=True, exist_ok=False)
40
-
41
- # Create the main MDL file
42
- mdl_file = project_dir / "main.mdl"
43
-
44
- # Generate template content
45
- template_content = _generate_mdl_template(clean_name, pack_name, pack_format)
46
-
47
- with open(mdl_file, 'w', encoding='utf-8') as f:
48
- f.write(template_content)
49
-
50
- # Create README.md
51
- readme_content = _generate_readme_template(clean_name, pack_name)
52
- readme_file = project_dir / "README.md"
53
-
54
- with open(readme_file, 'w', encoding='utf-8') as f:
55
- f.write(readme_content)
56
-
57
- try:
58
- from .cli_colors import color
59
- print(f"{color.success('[OK]')} Successfully created new MDL project: {color.highlight(clean_name)}")
60
- print(f"{color.info('[DIR]')} Project directory: {color.file_path(str(project_dir.absolute()))}")
61
- print(f"{color.info('[FILE]')} Main file: {color.file_path(str(mdl_file))}")
62
- print(f"{color.info('[DOC]')} Documentation: {color.file_path(str(readme_file))}")
63
- print()
64
- print(f"{color.section('[NEXT]')} Next steps:")
65
- print(f" 1. cd {color.highlight(clean_name)}")
66
- print(f" 2. Edit main.mdl with your code")
67
- print(f" 3. mdl build --mdl main.mdl -o dist")
68
- print(f" 4. mdl check main.mdl")
69
- print()
70
- print(f"{color.section('[INFO]')} Learn more:")
71
- print(" • Language Reference: https://www.mcmdl.com/docs/language-reference")
72
- print(" • Examples: https://www.mcmdl.com/docs/examples")
73
- print(" • CLI Reference: https://www.mcmdl.com/docs/cli-reference")
74
- except ImportError:
75
- # Fallback if colors aren't available
76
- print(f"[OK] Successfully created new MDL project: {clean_name}")
77
- print(f"[DIR] Project directory: {project_dir.absolute()}")
78
- print(f"[FILE] Main file: {mdl_file}")
79
- print(f"[DOC] Documentation: {readme_file}")
80
- print()
81
- print("[NEXT] Next steps:")
82
- print(f" 1. cd {clean_name}")
83
- print(f" 2. Edit main.mdl with your code")
84
- print(f" 3. mdl build --mdl main.mdl -o dist")
85
- print(f" 4. mdl check main.mdl")
86
- print()
87
- print("[INFO] Learn more:")
88
- print(" • Language Reference: https://www.mcmdl.com/docs/language-reference")
89
- print(" • Examples: https://www.mcmdl.com/docs/examples")
90
- print(" • CLI Reference: https://www.mcmdl.com/docs/cli-reference")
91
-
92
- except Exception as e:
93
- # Clean up on error
94
- if project_dir.exists():
95
- shutil.rmtree(project_dir)
96
- raise ValueError(f"Failed to create project: {str(e)}")
97
-
98
-
99
- def _generate_mdl_template(project_name: str, pack_name: str, pack_format: int) -> str:
100
- """Generate the template MDL content."""
101
- return f'''pack "{pack_name}" "Generated by MDL CLI" {pack_format};
102
- namespace "{pack_name}";
103
-
104
- // Example variables
105
- var num score = 0;
106
- var num lives = 3;
107
-
108
- // Main function - this runs when called
109
- function "main" {{
110
- say Hello from {project_name}!;
111
-
112
- // Set initial values
113
- score = 10;
114
- lives = 3;
115
-
116
- // Display current values
117
- say Score: $score$;
118
- say Lives: $lives$;
119
-
120
- // Example conditional statement
121
- if "$score$ > 5" {{
122
- say Great score!;
123
- }} else {{
124
- say Keep trying!;
125
- }};
126
-
127
- // Example while loop
128
- while "$lives$ > 0" {{
129
- say You have $lives$ lives remaining;
130
- lives = lives - 1;
131
- }};
132
-
133
- say Game over!;
134
- }}
135
-
136
- // Init function - this runs when the datapack loads
137
- function "init" {{
138
- say [GAME] {pack_name} loaded successfully!;
139
- say Type: /function {project_name}:main;
140
- }}
141
-
142
- // Tick function - this runs every tick (20 times per second)
143
- function "tick" {{
144
- // Add your tick logic here
145
- // Be careful with performance!
146
- }}
147
-
148
- // Example function with parameters
149
- function "greet_player" {{
150
- say Welcome to {pack_name}!;
151
- say Have fun playing!;
152
- }}
153
-
154
- // Raw Minecraft commands example
155
- $!raw
156
- # You can use raw Minecraft commands here
157
- # These are passed through directly to the output
158
- # Useful for complex commands or commands not yet supported by MDL
159
- tellraw @a {{"text":"Raw command example","color":"gold"}}
160
- raw!$
161
-
162
- // Example recipe (optional)
163
- // recipe "diamond_sword" "diamond_sword.json";
164
-
165
- // Example loot table (optional)
166
- // loot_table "chest_loot" "chest_loot.json";
167
-
168
- // Example advancement (optional)
169
- // advancement "first_diamond" "first_diamond.json";
170
-
171
- // Hook the init function to run when the datapack loads
172
- on_load "{project_name}:init";
173
- '''
174
-
175
-
176
- def _generate_readme_template(project_name: str, pack_name: str) -> str:
177
- """Generate the README template content."""
178
- return f'''# {pack_name}
179
-
180
- A Minecraft datapack created with MDL (Minecraft Datapack Language).
181
-
182
- ## [GAME] About
183
-
184
- This datapack was generated using the MDL CLI tool. MDL is a simplified language for creating Minecraft datapacks with variables, control structures, and easy syntax.
185
-
186
- ## [DIR] Project Structure
187
-
188
- ```
189
- {project_name}/
190
- ├── README.md # This file
191
- └── main.mdl # Main MDL source file
192
- ```
193
-
194
- ## [NEXT] Getting Started
195
-
196
- ### Prerequisites
197
-
198
- - Minecraft Java Edition (1.20+ recommended)
199
- - MDL CLI tool installed (`pipx install minecraft-datapack-language`)
200
-
201
- ### Building the Datapack
202
-
203
- 1. **Build the project:**
204
- ```bash
205
- mdl build --mdl {project_name}.mdl -o dist
206
- ```
207
-
208
- 2. **Check for errors:**
209
- ```bash
210
- mdl check {project_name}.mdl
211
- ```
212
-
213
- 3. **Install in Minecraft:**
214
- - Copy the `dist` folder to your world's `datapacks` directory
215
- - Or use the `--wrapper` option to create a zip file:
216
- ```bash
217
- mdl build --mdl {project_name}.mdl -o dist --wrapper {project_name}
218
- ```
219
-
220
- ### Using the Datapack
221
-
222
- 1. Load your world in Minecraft
223
- 2. The datapack will automatically load
224
- 3. Run the main function: `/function {project_name}:main`
225
-
226
- ## [OPT] Development
227
-
228
- ### Editing the Code
229
-
230
- Open `{project_name}.mdl` in your favorite text editor. The file contains:
231
-
232
- - **Pack declaration** - Datapack metadata
233
- - **Variables** - Scoreboard variables for storing data
234
- - **Functions** - The main logic of your datapack
235
- - **Control structures** - If/else statements and loops
236
- - **Raw commands** - Direct Minecraft commands when needed
237
-
238
- ### Key Features
239
-
240
- - **Variables**: Use `variable name = value` to create scoreboard objectives
241
- - **Functions**: Define reusable code blocks with `function name { ... }`
242
- - **Conditionals**: Use `if (condition) { ... } else { ... }`
243
- - **Loops**: Use `while (condition) { ... }` for repeating actions
244
- - **Variable substitution**: Use `$variable$` in commands to insert values
245
-
246
- ### Example Code
247
-
248
- ```mdl
249
- // Set a variable
250
- score = 10
251
-
252
- // Use it in a command
253
- say "Your score is: $score$"
254
-
255
- // Conditional logic
256
- if (score > 5) {{
257
- say "Great job!"
258
- }} else {{
259
- say "Keep trying!"
260
- }}
261
- ```
262
-
263
- ## [INFO] Resources
264
-
265
- - **Language Reference**: https://www.mcmdl.com/docs/language-reference
266
- - **CLI Reference**: https://www.mcmdl.com/docs/cli-reference
267
- - **Examples**: https://www.mcmdl.com/docs/examples
268
- - **Website**: https://www.mcmdl.com
269
- - **GitHub**: https://github.com/aaron777collins/MinecraftDatapackLanguage
270
-
271
- ## 🐛 Troubleshooting
272
-
273
- ### Common Issues
274
-
275
- 1. **"No .mdl files found"**
276
- - Make sure you're in the correct directory
277
- - Check that the file has a `.mdl` extension
278
-
279
- 2. **Syntax errors**
280
- - Use `mdl check {project_name}.mdl` to find and fix errors
281
- - Check the error messages for line numbers and suggestions
282
-
283
- 3. **Datapack not loading**
284
- - Verify the pack format is correct for your Minecraft version
285
- - Check that the `dist` folder is in the right location
286
-
287
- ### Getting Help
288
-
289
- - Check the error messages - they include helpful suggestions
290
- - Visit the documentation: https://www.mcmdl.com/docs
291
- - Report bugs: https://github.com/aaron777collins/MinecraftDatapackLanguage/issues
292
-
293
- ## [FILE] License
294
-
295
- This project is licensed under the MIT License - see the LICENSE file for details.
296
-
297
- ---
298
-
299
- Happy coding! [GAME]
300
- '''
@@ -1,276 +0,0 @@
1
- """
2
- CLI Utilities - Helper functions for the MDL CLI
3
- """
4
-
5
- import os
6
- import json
7
- import re
8
- from pathlib import Path
9
- from typing import Any, List
10
-
11
-
12
- def ensure_dir(path: str) -> None:
13
- """Ensure a directory exists, creating it if necessary."""
14
- os.makedirs(path, exist_ok=True)
15
-
16
-
17
- def write_json(path: str, data: Any) -> None:
18
- """Write JSON data to a file."""
19
- with open(path, 'w', encoding='utf-8') as f:
20
- json.dump(data, f, indent=2)
21
-
22
-
23
- def _process_variable_substitutions(command: str, selector: str = "@s") -> str:
24
- """Process $variable$ and $variable<selector>$ substitutions in commands."""
25
-
26
- # Skip say commands - they have their own variable substitution logic
27
- if command.strip().startswith('say '):
28
- return command
29
-
30
- # Check if this is a tellraw command with JSON
31
- if command.strip().startswith('tellraw'):
32
- # Special handling for tellraw commands with variable substitutions
33
- # Find the JSON part of the tellraw command
34
- json_start = command.find('[')
35
- if json_start == -1:
36
- json_start = command.find('{')
37
- json_end = command.rfind(']') + 1
38
- if json_end == 0:
39
- json_end = command.rfind('}') + 1
40
-
41
- if json_start != -1 and json_end != -1:
42
- prefix = command[:json_start]
43
- json_part = command[json_start:json_end]
44
- suffix = command[json_end:]
45
-
46
- # Parse the JSON to handle variable substitutions properly
47
- try:
48
- data = json.loads(json_part)
49
-
50
- # Handle both single object and array formats
51
- if isinstance(data, list):
52
- # JSON array format - process each element
53
- for item in data:
54
- if isinstance(item, dict) and 'score' in item and 'name' in item['score']:
55
- # Resolve scope selector in score components
56
- name = item['score']['name']
57
- if name == "global":
58
- item['score']['name'] = _resolve_selector("global")
59
-
60
- # Return the processed JSON
61
- new_json = json.dumps(data)
62
- return f"{prefix}{new_json}{suffix}"
63
-
64
- elif isinstance(data, dict) and 'text' in data and '$' in data['text']:
65
- # Single object format with variable substitutions
66
- # Split the text into parts before and after variables
67
- text = data['text']
68
- parts = []
69
- current_pos = 0
70
-
71
- # Find all variable substitutions (including scoped ones)
72
- var_pattern = r'\$([^$]+)\$'
73
- for match in re.finditer(var_pattern, text):
74
- start, end = match.span()
75
- var_name = match.group(1)
76
-
77
- # Add text before the variable
78
- if start > current_pos:
79
- parts.append(text[current_pos:start])
80
-
81
- # Process the variable
82
- if '<' in var_name and '>' in var_name:
83
- # Scoped variable: $var<selector>$
84
- base_name, scope = var_name.split('<', 1)
85
- scope = scope.rstrip('>')
86
- resolved_scope = _resolve_selector(scope)
87
- parts.append(f"${{{base_name}}}")
88
- # Replace the scope in the command
89
- command = command.replace(f"${{{base_name}}}", f"${{{base_name}}}")
90
- else:
91
- # Simple variable: $var$
92
- parts.append(f"${{{var_name}}}")
93
-
94
- current_pos = end
95
-
96
- # Add remaining text
97
- if current_pos < len(text):
98
- parts.append(text[current_pos:])
99
-
100
- # Update the text in the JSON
101
- data['text'] = ''.join(parts)
102
- new_json = json.dumps(data)
103
- return f"{prefix}{new_json}{suffix}"
104
-
105
- except json.JSONDecodeError:
106
- # If JSON parsing fails, fall back to simple substitution
107
- pass
108
-
109
- # Simple variable substitution for non-JSON commands
110
- # Handle scoped variables: $variable<selector>$
111
- scoped_pattern = r'\$([^$<]+)<([^>]+)>\$'
112
- command = re.sub(scoped_pattern, lambda m: f"${{{m.group(1)}}}", command)
113
-
114
- # Handle simple variables: $variable$
115
- simple_pattern = r'\$([^$]+)\$'
116
- command = re.sub(simple_pattern, lambda m: f"${{{m.group(1)}}}", command)
117
-
118
- return command
119
-
120
-
121
- def _convert_condition_to_minecraft_syntax(condition: str, selector: str = "@s", variable_scopes: dict = None) -> str:
122
- """Convert MDL condition syntax to Minecraft scoreboard syntax."""
123
- # Remove extra whitespace and normalize
124
- condition = condition.strip()
125
-
126
- # Handle variable substitution in conditions (e.g., "$value$ > 3")
127
- import re
128
- var_pattern = r'\$([^$]+)\$'
129
-
130
- # Replace variable references with proper scoreboard syntax
131
- def replace_var(match):
132
- var_name = match.group(1)
133
- # Check if variable has explicit scope selector
134
- if '<' in var_name and var_name.endswith('>'):
135
- var_parts = var_name.split('<', 1)
136
- base_var = var_parts[0]
137
- scope_selector = var_parts[1][:-1] # Remove trailing >
138
- # Resolve special selectors
139
- if scope_selector == "global":
140
- scope_selector = "@e[type=armor_stand,tag=mdl_server,limit=1]"
141
- return f"{scope_selector} {base_var}"
142
- else:
143
- # Use declared scope if available, otherwise default to current selector
144
- if variable_scopes and var_name in variable_scopes:
145
- declared_scope = variable_scopes[var_name]
146
- if declared_scope == 'global':
147
- return f"@e[type=armor_stand,tag=mdl_server,limit=1] {var_name}"
148
- else:
149
- return f"{declared_scope} {var_name}"
150
- else:
151
- # Default to current selector
152
- return f"{selector} {var_name}"
153
-
154
- # Apply variable substitution
155
- condition = re.sub(var_pattern, replace_var, condition)
156
-
157
- # Handle common patterns
158
- if '==' in condition:
159
- var1, var2 = condition.split('==', 1)
160
- var1 = var1.strip()
161
- var2 = var2.strip()
162
-
163
- # Check if var2 is a number
164
- try:
165
- value = int(var2)
166
- return f"score {var1} matches {value}"
167
- except ValueError:
168
- # Both are variables, compare them
169
- return f"score {var1} = {var2}"
170
-
171
- elif '!=' in condition:
172
- var1, var2 = condition.split('!=', 1)
173
- var1 = var1.strip()
174
- var2 = var2.strip()
175
-
176
- try:
177
- value = int(var2)
178
- return f"score {var1} matches ..{value-1} {value+1}.."
179
- except ValueError:
180
- return f"score {var1} != {var2}"
181
-
182
- elif '>' in condition:
183
- var1, var2 = condition.split('>', 1)
184
- var1 = var1.strip()
185
- var2 = var2.strip()
186
-
187
- try:
188
- value = int(var2)
189
- return f"score {var1} matches {value+1}.."
190
- except ValueError:
191
- return f"score {var1} > {var2}"
192
-
193
- elif '<' in condition:
194
- var1, var2 = condition.split('<', 1)
195
- var1 = var1.strip()
196
- var2 = var2.strip()
197
-
198
- try:
199
- value = int(var2)
200
- return f"score {var1} matches ..{value-1}"
201
- except ValueError:
202
- return f"score {var1} < {var2}"
203
-
204
- elif '>=' in condition:
205
- var1, var2 = condition.split('>=', 1)
206
- var1 = var1.strip()
207
- var2 = var2.strip()
208
-
209
- try:
210
- value = int(var2)
211
- return f"score {var1} matches {value}.."
212
- except ValueError:
213
- return f"score {var1} >= {var2}"
214
-
215
- elif '<=' in condition:
216
- var1, var2 = condition.split('<=', 1)
217
- var1 = var1.strip()
218
- var2 = var2.strip()
219
-
220
- try:
221
- value = int(var2)
222
- return f"score {var1} matches ..{value}"
223
- except ValueError:
224
- return f"score {var1} <= {var2}"
225
-
226
- # Default: treat as a variable that should be non-zero
227
- return f"score {condition} matches 1.."
228
-
229
-
230
- def _find_mdl_files(directory: Path) -> List[Path]:
231
- """Find all .mdl files in a directory recursively."""
232
- mdl_files = []
233
- if directory.is_file():
234
- if directory.suffix == '.mdl':
235
- mdl_files.append(directory)
236
- else:
237
- for file_path in directory.rglob('*.mdl'):
238
- mdl_files.append(file_path)
239
-
240
- return sorted(mdl_files)
241
-
242
-
243
- def _validate_selector(selector: str, variable_name: str) -> None:
244
- """Validate a selector string."""
245
- valid_selectors = ['@s', '@p', '@a', '@r', '@e', 'global']
246
- if selector not in valid_selectors:
247
- raise ValueError(f"Invalid selector '{selector}' for variable '{variable_name}'. Valid selectors: {', '.join(valid_selectors)}")
248
-
249
-
250
- def _resolve_selector(selector: str) -> str:
251
- """Resolve a selector to its Minecraft equivalent."""
252
- if selector == "global":
253
- return "global"
254
- return selector
255
-
256
-
257
- def _extract_base_variable_name(var_name: str) -> str:
258
- """Extract the base variable name from a scoped variable."""
259
- if '<' in var_name and '>' in var_name:
260
- return var_name.split('<')[0]
261
- return var_name
262
-
263
-
264
- def _slugify(name: str) -> str:
265
- """Convert a string to a valid namespace/identifier."""
266
- # Remove special characters and replace spaces with underscores
267
- import re
268
- slug = re.sub(r'[^a-zA-Z0-9_]', '_', name)
269
- # Remove multiple consecutive underscores
270
- slug = re.sub(r'_+', '_', slug)
271
- # Remove leading/trailing underscores
272
- slug = slug.strip('_')
273
- # Ensure it starts with a letter or underscore
274
- if slug and not slug[0].isalpha() and slug[0] != '_':
275
- slug = '_' + slug
276
- return slug.lower()