Skip to content

Commit c5eed6b

Browse files
committed
feat: add dsl generator skill
1 parent 8ee2d7a commit c5eed6b

9 files changed

Lines changed: 3832 additions & 0 deletions

File tree

.claude/skills/tinyengine-dsl-generator/SKILL.md

Lines changed: 671 additions & 0 deletions
Large diffs are not rendered by default.

.claude/skills/tinyengine-dsl-generator/references/components.md

Lines changed: 614 additions & 0 deletions
Large diffs are not rendered by default.

.claude/skills/tinyengine-dsl-generator/references/patterns.md

Lines changed: 1141 additions & 0 deletions
Large diffs are not rendered by default.

.claude/skills/tinyengine-dsl-generator/references/protocol.md

Lines changed: 595 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#!/usr/bin/env python3
2+
"""
3+
TinyEngine CSS Syntax Checker
4+
5+
检查DSL中的CSS字段是否有语法错误。
6+
7+
支持三种模式:
8+
1. basic: 基础语法检查(默认,无需额外依赖)
9+
2. tinycss2: 使用 tinycss2 库(需要安装:pip install tinycss2)
10+
3. postcss: 使用 postcss 命令行工具(需要安装:npm install -g postcss)
11+
12+
用法:
13+
python3 check_css.py <dsl-file> [mode]
14+
python3 check_css.py <dsl-file> postcss # 使用postcss检查
15+
python3 check_css.py <dsl-file> tinycss2 # 使用tinycss2检查
16+
"""
17+
18+
import json
19+
import re
20+
import subprocess
21+
import sys
22+
from typing import Dict, List, Any
23+
24+
25+
class CSSChecker:
26+
"""CSS语法检查器基类"""
27+
28+
def __init__(self, dsl_data: Dict[str, Any]):
29+
self.dsl = dsl_data
30+
self.errors = []
31+
self.warnings = []
32+
33+
def check(self) -> bool:
34+
"""检查CSS语法"""
35+
page_content = self.dsl.get('page_content', self.dsl)
36+
css_string = page_content.get('css', '')
37+
38+
if not css_string:
39+
self.warnings.append("No CSS field found")
40+
return True
41+
42+
return self._check_css(css_string)
43+
44+
def _check_css(self, css: str) -> bool:
45+
"""子类实现具体的检查逻辑"""
46+
raise NotImplementedError
47+
48+
def report(self) -> str:
49+
"""生成报告"""
50+
lines = []
51+
if self.errors:
52+
lines.append("❌ CSS Errors:")
53+
for error in self.errors:
54+
lines.append(f" - {error}")
55+
if self.warnings:
56+
lines.append("⚠️ CSS Warnings:")
57+
for warning in self.warnings:
58+
lines.append(f" - {warning}")
59+
if not self.errors and not self.warnings:
60+
lines.append("✅ CSS check passed!")
61+
return "\n".join(lines)
62+
63+
64+
class BasicCSSChecker(CSSChecker):
65+
"""基础CSS语法检查器(无需额外依赖)"""
66+
67+
def _check_css(self, css: str) -> bool:
68+
"""基础检查:括号匹配、基本语法"""
69+
# 检查括号匹配
70+
stack = []
71+
i = 0
72+
while i < len(css):
73+
char = css[i]
74+
if char in '{':
75+
stack.append((char, i))
76+
elif char in '}':
77+
if not stack or stack[-1][0] != '{':
78+
self.errors.append(f"Unmatched '}}' at position {i}")
79+
return False
80+
stack.pop()
81+
elif char in '(':
82+
stack.append((char, i))
83+
elif char in ')':
84+
if not stack or stack[-1][0] != '(':
85+
self.errors.append(f"Unmatched ')' at position {i}")
86+
return False
87+
stack.pop()
88+
i += 1
89+
90+
if stack:
91+
for char, pos in stack:
92+
self.errors.append(f"Unclosed '{char}' at position {pos}")
93+
return False
94+
95+
# 移除注释进行检查
96+
css_no_comments = re.sub(r'/\*.*?\*/', '', css, flags=re.DOTALL)
97+
98+
# 检查是否有CSS规则
99+
if '{' not in css_no_comments:
100+
self.warnings.append("CSS may not contain any rules")
101+
102+
# 检查分号使用
103+
rules = re.findall(r'\{([^}]*)\}', css_no_comments)
104+
for rule in rules:
105+
properties = rule.split(';')
106+
for prop in properties[:-1]: # 最后一个可能为空
107+
prop = prop.strip()
108+
if prop and ':' not in prop:
109+
self.warnings.append(f"Property without colon: {prop[:50]}")
110+
111+
return len(self.errors) == 0
112+
113+
114+
class TinyCSS2Checker(CSSChecker):
115+
"""使用tinycss2库的CSS检查器"""
116+
117+
def _check_css(self, css: str) -> bool:
118+
try:
119+
import tinycss2
120+
except ImportError:
121+
self.errors.append(
122+
"tinycss2 not installed. Install with: pip install tinycss2"
123+
)
124+
return False
125+
126+
# 使用tinycss2解析CSS
127+
try:
128+
# 解析CSS规则列表
129+
rules = tinycss2.parse_stylesheet(css, skip_comments=False)
130+
131+
# tinycss2会抛出解析错误
132+
for rule in rules:
133+
if rule.type == 'error':
134+
self.errors.append(f"Parse error: {rule.message}")
135+
136+
return len(self.errors) == 0
137+
138+
except Exception as e:
139+
self.errors.append(f"Failed to parse CSS: {e}")
140+
return False
141+
142+
143+
class PostCSSChecker(CSSChecker):
144+
"""使用postcss命令行工具的CSS检查器"""
145+
146+
def _check_css(self, css: str) -> bool:
147+
# 检查postcss是否可用
148+
try:
149+
result = subprocess.run(
150+
['postcss', '--version'],
151+
capture_output=True,
152+
text=True,
153+
timeout=5
154+
)
155+
if result.returncode != 0:
156+
self.errors.append("postcss command failed. Install with: npm install -g postcss")
157+
return False
158+
except FileNotFoundError:
159+
self.errors.append("postcss not found. Install with: npm install -g postcss")
160+
return False
161+
except subprocess.TimeoutExpired:
162+
self.errors.append("postcss command timed out")
163+
return False
164+
165+
# 将CSS写入临时文件
166+
import tempfile
167+
with tempfile.NamedTemporaryFile(mode='w', suffix='.css', delete=False) as f:
168+
f.write(css)
169+
temp_file = f.name
170+
171+
try:
172+
# 使用postcss解析CSS
173+
result = subprocess.run(
174+
['postcss', temp_file],
175+
capture_output=True,
176+
text=True,
177+
timeout=10
178+
)
179+
180+
# postcss的错误输出
181+
if result.stderr:
182+
# 过滤掉警告(如<css input>:3:3: Yellow color)
183+
errors = []
184+
warnings = []
185+
for line in result.stderr.strip().split('\n'):
186+
if line:
187+
if any(w in line.lower() for w in ['warning', 'yellow']):
188+
warnings.append(line)
189+
else:
190+
errors.append(line)
191+
192+
self.errors.extend(errors)
193+
self.warnings.extend(warnings)
194+
195+
return len(self.errors) == 0
196+
197+
except subprocess.TimeoutExpired:
198+
self.errors.append("postcss command timed out")
199+
return False
200+
finally:
201+
import os
202+
try:
203+
os.unlink(temp_file)
204+
except:
205+
pass
206+
207+
208+
def main():
209+
"""主函数"""
210+
if len(sys.argv) < 2:
211+
print("Usage: check_css.py <dsl-file> [mode]")
212+
print(" mode: basic (default), tinycss2, postcss")
213+
sys.exit(1)
214+
215+
file_path = sys.argv[1]
216+
mode = sys.argv[2] if len(sys.argv) > 2 else 'basic'
217+
218+
# 读取DSL文件
219+
try:
220+
with open(file_path, 'r', encoding='utf-8') as f:
221+
dsl_data = json.load(f)
222+
except json.JSONDecodeError as e:
223+
print(f"❌ Invalid JSON: {e}")
224+
sys.exit(1)
225+
except FileNotFoundError:
226+
print(f"❌ File not found: {file_path}")
227+
sys.exit(1)
228+
229+
# 选择检查器
230+
checkers = {
231+
'basic': BasicCSSChecker,
232+
'tinycss2': TinyCSS2Checker,
233+
'postcss': PostCSSChecker
234+
}
235+
236+
checker_class = checkers.get(mode)
237+
if not checker_class:
238+
print(f"❌ Unknown mode: {mode}")
239+
print(f"Available modes: {', '.join(checkers.keys())}")
240+
sys.exit(1)
241+
242+
checker = checker_class(dsl_data)
243+
is_valid = checker.check()
244+
print(checker.report())
245+
sys.exit(0 if is_valid else 1)
246+
247+
248+
if __name__ == '__main__':
249+
main()
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#!/usr/bin/env python3
2+
"""
3+
TinyEngine Event Binding Checker
4+
5+
检查DSL文件中的事件绑定是否正确使用JSExpression引用方法,
6+
而不是在value中直接写函数定义。
7+
"""
8+
9+
import json
10+
import sys
11+
from typing import Dict, List, Any
12+
13+
14+
class EventBindingChecker:
15+
"""事件绑定检查器"""
16+
17+
def __init__(self, dsl_data: Dict[str, Any]):
18+
self.dsl = dsl_data
19+
self.errors = []
20+
self.warnings = []
21+
22+
def check(self) -> bool:
23+
"""检查所有事件绑定"""
24+
# 从page_content或直接检查
25+
page_content = self.dsl.get('page_content', self.dsl)
26+
27+
self._check_node(page_content)
28+
return len(self.errors) == 0
29+
30+
def _check_node(self, node: Any) -> None:
31+
"""递归检查节点"""
32+
if isinstance(node, dict):
33+
# 检查当前节点的事件绑定
34+
self._check_event_bindings(node)
35+
36+
# 递归检查子节点
37+
if 'children' in node:
38+
for child in node['children']:
39+
self._check_node(child)
40+
41+
elif isinstance(node, list):
42+
for item in node:
43+
self._check_node(item)
44+
45+
def _check_event_bindings(self, node: Dict[str, Any]) -> None:
46+
"""检查单个节点的事件绑定"""
47+
component = node.get('componentName', 'unknown')
48+
49+
# 检查所有可能的事件属性
50+
event_keys = [
51+
'onClick', 'onChange', 'onKeyup', 'onKeyDown', 'onKeyPress',
52+
'onFocus', 'onBlur', 'onSubmit', 'onInput', 'onTabClick',
53+
'onCurrentChange', 'onSizeChange', 'onCheckChange',
54+
'onNodeClick', 'onRowClick', 'onCellClick'
55+
]
56+
57+
for key in event_keys:
58+
if key in node:
59+
value = node[key]
60+
if isinstance(value, dict):
61+
self._check_event_value(component, key, value)
62+
63+
# 也检查以'on'开头的属性
64+
for key, value in node.items():
65+
if key.startswith('on') and key not in event_keys:
66+
if isinstance(value, dict):
67+
self._check_event_value(component, key, value)
68+
69+
def _check_event_value(self, component: str, event_key: str, value: Dict[str, Any]) -> None:
70+
"""检查事件值"""
71+
value_type = value.get('type')
72+
value_content = value.get('value', '')
73+
74+
# 错误1: 使用JSFunction类型进行事件绑定
75+
if value_type == 'JSFunction':
76+
self.errors.append(
77+
f"{component}.{event_key}: 使用了JSFunction类型,应该使用JSExpression引用methods中的方法"
78+
)
79+
80+
# 错误2: JSExpression的value中包含函数定义
81+
if value_type == 'JSExpression' and value_content.startswith('function'):
82+
self.errors.append(
83+
f"{component}.{event_key}: JSExpression的value中包含函数定义 '{value_content[:30]}...',"
84+
f"应该引用方法如 'this.methodName'"
85+
)
86+
87+
def report(self) -> str:
88+
"""生成报告"""
89+
lines = []
90+
if self.errors:
91+
lines.append("❌ 发现事件绑定错误:")
92+
for error in self.errors:
93+
lines.append(f" - {error}")
94+
if self.warnings:
95+
lines.append("⚠️ 警告:")
96+
for warning in self.warnings:
97+
lines.append(f" - {warning}")
98+
if not self.errors and not self.warnings:
99+
lines.append("✅ 所有事件绑定检查通过!")
100+
return "\n".join(lines)
101+
102+
103+
def main():
104+
"""主函数"""
105+
if len(sys.argv) < 2:
106+
print("Usage: check_event_bindings.py <dsl-file>")
107+
sys.exit(1)
108+
109+
file_path = sys.argv[1]
110+
111+
try:
112+
with open(file_path, 'r', encoding='utf-8') as f:
113+
dsl_data = json.load(f)
114+
except json.JSONDecodeError as e:
115+
print(f"❌ Invalid JSON: {e}")
116+
sys.exit(1)
117+
except FileNotFoundError:
118+
print(f"❌ File not found: {file_path}")
119+
sys.exit(1)
120+
121+
checker = EventBindingChecker(dsl_data)
122+
is_valid = checker.check()
123+
print(checker.report())
124+
sys.exit(0 if is_valid else 1)
125+
126+
127+
if __name__ == '__main__':
128+
main()

0 commit comments

Comments
 (0)