This repository was archived by the owner on Apr 9, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
215 lines (172 loc) · 8.07 KB
/
Copy pathcommand-validator.yml
File metadata and controls
215 lines (172 loc) · 8.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
name: "Command Syntax Validator"
on:
push:
branches: [ main, dev, develop ]
paths:
- '**.mcfunction'
- 'data/**'
pull_request:
branches: [ main ]
paths:
- '**.mcfunction'
- 'data/**'
workflow_dispatch:
jobs:
validate-commands:
name: Validate Minecraft Command Syntax
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Create command validation script
run: |
cat > validate_commands.py << 'PYTHON_SCRIPT'
#!/usr/bin/env python3
import os
import re
import sys
from pathlib import Path
from collections import defaultdict
# Common Minecraft commands (1.21.x)
VALID_COMMANDS = {
'advancement', 'attribute', 'ban', 'ban-ip', 'banlist', 'bossbar',
'clear', 'clone', 'damage', 'data', 'datapack', 'debug', 'defaultgamemode',
'deop', 'difficulty', 'effect', 'enchant', 'execute', 'experience', 'xp',
'fill', 'fillbiome', 'forceload', 'function', 'gamemode', 'gamerule',
'give', 'help', 'item', 'jfr', 'kick', 'kill', 'list', 'locate', 'loot',
'me', 'msg', 'tell', 'w', 'op', 'pardon', 'pardon-ip', 'particle',
'perf', 'place', 'playsound', 'publish', 'random', 'recipe', 'reload',
'return', 'ride', 'save-all', 'save-off', 'save-on', 'say', 'schedule',
'scoreboard', 'seed', 'setblock', 'setidletimeout', 'setworldspawn',
'spawnpoint', 'spectate', 'spreadplayers', 'stop', 'stopsound',
'summon', 'tag', 'team', 'teleport', 'tp', 'tellraw', 'tick',
'time', 'title', 'transfer', 'trigger', 'weather', 'whitelist',
'worldborder'
}
# Commands that support execute subcommands
EXECUTE_SUBCOMMANDS = {
'align', 'anchored', 'as', 'at', 'facing', 'in', 'on',
'positioned', 'rotated', 'store', 'if', 'unless', 'run'
}
def parse_mcfunction_file(file_path):
"""Parse mcfunction file and extract commands."""
commands = []
with open(file_path, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
# Remove comments
line = line.split('#')[0].strip()
# Skip empty lines
if not line:
continue
commands.append((line_num, line))
return commands
def validate_command(line_num, command):
"""Validate a single command."""
errors = []
warnings = []
# Check for leading slash (should not be present in mcfunction)
if command.startswith('/'):
warnings.append(f"Line {line_num}: Leading slash detected (unnecessary in mcfunction)")
# Extract command name
cmd_parts = command.lstrip('/').split()
if not cmd_parts:
return errors, warnings
cmd_name = cmd_parts[0]
# Special handling for execute command
if cmd_name == 'execute':
return validate_execute_command(line_num, command)
# Check if command is valid
if cmd_name not in VALID_COMMANDS:
errors.append(f"Line {line_num}: Unknown command '{cmd_name}'")
# Check for common mistakes
if re.search(r'\$\{[^}]+\}', command):
warnings.append(f"Line {line_num}: Bash-style variable detected, use $(var) for macros")
# Check for unbalanced brackets
if command.count('{') != command.count('}'):
errors.append(f"Line {line_num}: Unbalanced curly braces")
if command.count('[') != command.count(']'):
errors.append(f"Line {line_num}: Unbalanced square brackets")
# Check for double spaces (common typo)
if ' ' in command:
warnings.append(f"Line {line_num}: Multiple consecutive spaces detected")
return errors, warnings
def validate_execute_command(line_num, command):
"""Validate execute command specifically."""
errors = []
warnings = []
# Check if execute has 'run' at the end
if not re.search(r'\s+run\s+\w+', command):
warnings.append(f"Line {line_num}: Execute command missing 'run' clause")
# Check for common execute mistakes
parts = command.split()
for i, part in enumerate(parts[1:], 1): # Skip 'execute'
if part in EXECUTE_SUBCOMMANDS:
continue
if part in VALID_COMMANDS or part == 'run':
break
# Check if it looks like a selector or coordinate
if part.startswith('@') or part.startswith('~') or part.replace('.', '').replace('-', '').isdigit():
continue
# Unknown token
if not any(c in part for c in ['=', ':', '{', '[', ']', '}']):
warnings.append(f"Line {line_num}: Possible unknown execute subcommand or typo: '{part}'")
return errors, warnings
def main():
errors_count = 0
warnings_count = 0
data_dir = Path('data')
if not data_dir.exists():
print("⚠️ No 'data' directory found, skipping validation")
return 0
mcfunction_files = list(data_dir.rglob('*.mcfunction'))
print(f"🔍 Validating commands in {len(mcfunction_files)} mcfunction files...\n")
file_stats = defaultdict(lambda: {'errors': 0, 'warnings': 0})
for file_path in mcfunction_files:
commands = parse_mcfunction_file(file_path)
for line_num, command in commands:
errors, warnings = validate_command(line_num, command)
if errors or warnings:
rel_path = file_path.relative_to(Path.cwd())
for error in errors:
print(f"❌ {rel_path}: {error}")
errors_count += 1
file_stats[rel_path]['errors'] += 1
for warning in warnings:
print(f"⚠️ {rel_path}: {warning}")
warnings_count += 1
file_stats[rel_path]['warnings'] += 1
# Print summary
print(f"\n{'='*60}")
print(f"Summary:")
print(f" Files checked: {len(mcfunction_files)}")
print(f" Errors: {errors_count}")
print(f" Warnings: {warnings_count}")
if errors_count > 0:
print(f"\n❌ Validation failed with {errors_count} error(s)")
return 1
elif warnings_count > 0:
print(f"\n⚠️ Validation passed with {warnings_count} warning(s)")
return 0
else:
print(f"\n✅ All command syntax checks passed!")
return 0
if __name__ == '__main__':
sys.exit(main())
PYTHON_SCRIPT
chmod +x validate_commands.py
- name: Run command validation
run: python validate_commands.py
- name: Upload validation report
if: always()
uses: actions/upload-artifact@v4
with:
name: command-validation-report
path: |
*.log
validation-*.txt
retention-days: 7
if-no-files-found: ignore