@voodocs/cli 2.5.2 → 3.0.0

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.
@@ -0,0 +1,249 @@
1
+ """@darkarts
2
+ ⊢{darkarts:pattern-shortcuts}
3
+ ∂{typing,re}
4
+ ⚠{patterns≥15-types}
5
+ ⊨{bidirectional:expand↔compress}
6
+ ⚡{O(1):lookup}
7
+ 🔒{read-only:patterns}
8
+ """
9
+
10
+ """
11
+ DarkArts v3.0.0 - Pattern Shortcuts
12
+
13
+ This module provides pattern shortcuts for common validations and constraints.
14
+ Designed for maximum token efficiency in preconditions and postconditions.
15
+
16
+ Pattern types:
17
+ - Type patterns (:uuid, :email, :url, :json, :jwt, :hash, :enc)
18
+ - Comparison operators (≥, ≤, >, <)
19
+ - Range notation ([N,M])
20
+ - Set membership (∈{...})
21
+ """
22
+
23
+ import re
24
+ from typing import Dict, Optional, Tuple
25
+
26
+ # Pattern shortcuts
27
+ PATTERN_SHORTCUTS: Dict[str, str] = {
28
+ ':uuid': 'valid UUID',
29
+ ':email': 'valid email format',
30
+ ':url': 'valid URL',
31
+ ':uri': 'valid URI',
32
+ ':json': 'valid JSON',
33
+ ':jwt': 'valid JWT token',
34
+ ':hash': 'hashed value',
35
+ ':enc': 'encrypted value',
36
+ ':hex': 'hexadecimal value',
37
+ ':b64': 'base64 encoded',
38
+ ':iso': 'ISO format',
39
+ ':rfc': 'RFC compliant',
40
+ }
41
+
42
+ # Reverse mapping
43
+ PATTERN_EXPANSIONS: Dict[str, str] = {v: k for k, v in PATTERN_SHORTCUTS.items()}
44
+
45
+ # Comparison operators (Unicode mathematical symbols)
46
+ COMPARISON_OPERATORS: Dict[str, str] = {
47
+ '≥': 'greater than or equal to',
48
+ '≤': 'less than or equal to',
49
+ '>': 'greater than',
50
+ '<': 'less than',
51
+ '=': 'equal to',
52
+ '≠': 'not equal to',
53
+ '≈': 'approximately equal to',
54
+ }
55
+
56
+ # Reverse mapping
57
+ COMPARISON_EXPANSIONS: Dict[str, str] = {v: k for k, v in COMPARISON_OPERATORS.items()}
58
+
59
+
60
+ def compress_pattern(text: str) -> str:
61
+ """
62
+ Compress text using pattern shortcuts.
63
+
64
+ Examples:
65
+ "valid UUID" → ":uuid"
66
+ "valid email format" → ":email"
67
+ "greater than or equal to 8" → "≥8"
68
+ """
69
+ result = text
70
+
71
+ # Replace pattern expansions with shortcuts
72
+ for expansion, shortcut in PATTERN_EXPANSIONS.items():
73
+ result = result.replace(expansion, shortcut)
74
+
75
+ # Replace comparison operators
76
+ for expansion, operator in COMPARISON_EXPANSIONS.items():
77
+ # Handle "X greater than or equal to N" → "X≥N"
78
+ pattern = rf'(\w+)\s+{re.escape(expansion)}\s+(\d+)'
79
+ result = re.sub(pattern, rf'\1{operator}\2', result)
80
+
81
+ # Handle "greater than or equal to N" → "≥N"
82
+ pattern = rf'{re.escape(expansion)}\s+(\d+)'
83
+ result = re.sub(pattern, rf'{operator}\1', result)
84
+
85
+ return result
86
+
87
+
88
+ def expand_pattern(text: str) -> str:
89
+ """
90
+ Expand pattern shortcuts to full text.
91
+
92
+ Examples:
93
+ ":uuid" → "valid UUID"
94
+ ":email" → "valid email format"
95
+ "≥8" → "greater than or equal to 8"
96
+ """
97
+ result = text
98
+
99
+ # Replace pattern shortcuts with expansions
100
+ for shortcut, expansion in PATTERN_SHORTCUTS.items():
101
+ result = result.replace(shortcut, expansion)
102
+
103
+ # Replace comparison operators
104
+ for operator, expansion in COMPARISON_OPERATORS.items():
105
+ # Handle "X≥N" → "X greater than or equal to N"
106
+ pattern = rf'(\w+){re.escape(operator)}(\d+)'
107
+ result = re.sub(pattern, rf'\1 {expansion} \2', result)
108
+
109
+ # Handle "≥N" → "greater than or equal to N"
110
+ pattern = rf'{re.escape(operator)}(\d+)'
111
+ result = re.sub(pattern, rf'{expansion} \1', result)
112
+
113
+ return result
114
+
115
+
116
+ def parse_range(text: str) -> Optional[Tuple[int, int]]:
117
+ """
118
+ Parse range notation [N,M].
119
+
120
+ Example:
121
+ "[18,65]" → (18, 65)
122
+ """
123
+ match = re.match(r'\[(\d+),(\d+)\]', text)
124
+ if match:
125
+ return (int(match.group(1)), int(match.group(2)))
126
+ return None
127
+
128
+
129
+ def parse_set_membership(text: str) -> Optional[list]:
130
+ """
131
+ Parse set membership ∈{...}.
132
+
133
+ Example:
134
+ "∈{active,pending,completed}" → ["active", "pending", "completed"]
135
+ """
136
+ match = re.match(r'∈\{([^}]+)\}', text)
137
+ if match:
138
+ return [item.strip() for item in match.group(1).split(',')]
139
+ return None
140
+
141
+
142
+ def compress_range(min_val: int, max_val: int) -> str:
143
+ """
144
+ Compress range to notation.
145
+
146
+ Example:
147
+ (18, 65) → "[18,65]"
148
+ """
149
+ return f'[{min_val},{max_val}]'
150
+
151
+
152
+ def compress_set_membership(items: list) -> str:
153
+ """
154
+ Compress set membership to notation.
155
+
156
+ Example:
157
+ ["active", "pending"] → "∈{active,pending}"
158
+ """
159
+ return f'∈{{{",".join(items)}}}'
160
+
161
+
162
+ def validate_pattern(pattern: str) -> Tuple[bool, Optional[str]]:
163
+ """
164
+ Validate a pattern shortcut.
165
+
166
+ Returns:
167
+ (is_valid, error_message)
168
+ """
169
+ # Check if it contains a type pattern (var:type format)
170
+ if ':' in pattern and not pattern.startswith(':'):
171
+ # Extract the pattern part after the colon
172
+ parts = pattern.split(':')
173
+ if len(parts) == 2:
174
+ pattern_type = ':' + parts[1]
175
+ if pattern_type not in PATTERN_SHORTCUTS:
176
+ similar = _find_similar_pattern(pattern_type)
177
+ if similar:
178
+ return (False, f"Unknown pattern '{pattern_type}'. Did you mean '{similar}'?")
179
+ return (False, f"Unknown pattern '{pattern_type}'. Available patterns: {', '.join(PATTERN_SHORTCUTS.keys())}")
180
+
181
+ # Check if it's a standalone pattern shortcut
182
+ if pattern.startswith(':'):
183
+ if pattern not in PATTERN_SHORTCUTS:
184
+ similar = _find_similar_pattern(pattern)
185
+ if similar:
186
+ return (False, f"Unknown pattern '{pattern}'. Did you mean '{similar}'?")
187
+ return (False, f"Unknown pattern '{pattern}'. Available patterns: {', '.join(PATTERN_SHORTCUTS.keys())}")
188
+
189
+ # Check if it's a comparison with number
190
+ for operator in COMPARISON_OPERATORS.keys():
191
+ if operator in pattern:
192
+ # Extract number after operator
193
+ match = re.search(rf'{re.escape(operator)}(\d+)', pattern)
194
+ if not match:
195
+ return (False, f"Invalid comparison '{pattern}'. Expected format: 'var{operator}N' where N is a number")
196
+
197
+ # Check if it's a range
198
+ if '[' in pattern:
199
+ range_val = parse_range(pattern)
200
+ if range_val is None:
201
+ return (False, f"Invalid range '{pattern}'. Expected format: '[N,M]' where N and M are numbers")
202
+ if range_val[0] >= range_val[1]:
203
+ return (False, f"Invalid range '{pattern}'. Minimum must be less than maximum")
204
+
205
+ # Check if it's set membership
206
+ if '∈' in pattern:
207
+ set_val = parse_set_membership(pattern)
208
+ if set_val is None:
209
+ return (False, f"Invalid set membership '{pattern}'. Expected format: '∈{{item1,item2,...}}'")
210
+ if len(set_val) == 0:
211
+ return (False, f"Invalid set membership '{pattern}'. Set must contain at least one item")
212
+
213
+ return (True, None)
214
+
215
+
216
+ def _find_similar_pattern(pattern: str) -> Optional[str]:
217
+ """Find similar pattern using simple edit distance."""
218
+ pattern_lower = pattern.lower()
219
+
220
+ for known_pattern in PATTERN_SHORTCUTS.keys():
221
+ # Simple similarity check
222
+ if len(pattern_lower) >= 3 and known_pattern.startswith(pattern_lower[:3]):
223
+ return known_pattern
224
+
225
+ return None
226
+
227
+
228
+ # Common pattern combinations
229
+ COMMON_PATTERNS = {
230
+ 'id': ':uuid',
231
+ 'identifier': ':uuid',
232
+ 'email': ':email',
233
+ 'url': ':url',
234
+ 'link': ':url',
235
+ 'token': ':jwt',
236
+ 'password': ':hash',
237
+ 'data': ':json',
238
+ }
239
+
240
+
241
+ def suggest_pattern(field_name: str) -> Optional[str]:
242
+ """
243
+ Suggest a pattern based on field name.
244
+
245
+ Example:
246
+ "id" → ":uuid"
247
+ "email" → ":email"
248
+ """
249
+ return COMMON_PATTERNS.get(field_name.lower())
@@ -0,0 +1,276 @@
1
+ """@darkarts
2
+ ⊢{darkarts:symbol-definitions}
3
+ ∂{typing}
4
+ ⚠{symbols≥15-types}
5
+ ⊨{∀symbol→unique-meaning}
6
+ ⚡{O(1):lookup}
7
+ 🔒{read-only:symbols}
8
+ """
9
+
10
+ """
11
+ DarkArts v3.0.0 - Mathematical Symbol Definitions
12
+
13
+ This module defines all mathematical symbols used in DarkArts notation.
14
+
15
+ Symbol categories:
16
+ - Core symbols (v2.x): ⊢, ∂, ⚠, ⊳, ⊲, ⊨, ⚡, 🔒
17
+ - New symbols (v3.0.0): ⇄, ⊕, ⊗, ≈, ∴, ∀, ∃
18
+ """
19
+
20
+ from typing import Dict, List
21
+
22
+ # Core Symbols (v2.x)
23
+ CORE_SYMBOLS: Dict[str, str] = {
24
+ '⊢': 'purpose',
25
+ '∂': 'dependencies',
26
+ '⚠': 'assumptions',
27
+ '⊳': 'preconditions',
28
+ '⊲': 'postconditions',
29
+ '⊨': 'invariants',
30
+ '⚡': 'complexity',
31
+ '🔒': 'security',
32
+ }
33
+
34
+ # New Symbols (v3.0.0)
35
+ NEW_SYMBOLS: Dict[str, str] = {
36
+ '⇄': 'bidirectional', # Bidirectional relationships
37
+ '⊕': 'side_effects', # Side effects
38
+ '⊗': 'forbidden', # Forbidden operations
39
+ '≈': 'approximation', # Approximation/tolerance
40
+ '∴': 'consequence', # Logical consequence
41
+ '∀': 'universal', # Universal quantifier
42
+ '∃': 'existential', # Existential quantifier
43
+ }
44
+
45
+ # All Symbols
46
+ ALL_SYMBOLS: Dict[str, str] = {**CORE_SYMBOLS, **NEW_SYMBOLS}
47
+
48
+ # Reverse mapping (name to symbol)
49
+ SYMBOL_NAMES: Dict[str, str] = {v: k for k, v in ALL_SYMBOLS.items()}
50
+
51
+ # Symbol descriptions
52
+ SYMBOL_DESCRIPTIONS: Dict[str, str] = {
53
+ '⊢': 'Purpose - What the function/module does',
54
+ '∂': 'Dependencies - External packages/modules required',
55
+ '⚠': 'Assumptions - Conditions assumed to be true',
56
+ '⊳': 'Preconditions - Conditions that must be true before execution',
57
+ '⊲': 'Postconditions - Conditions guaranteed after execution',
58
+ '⊨': 'Invariants - Properties that remain unchanged',
59
+ '⚡': 'Complexity - Time/space complexity (Big O notation)',
60
+ '🔒': 'Security - Security considerations and protections',
61
+ '⇄': 'Bidirectional - Bidirectional relationships or inverse operations',
62
+ '⊕': 'Side Effects - Operations that modify external state',
63
+ '⊗': 'Forbidden - Operations that must NOT happen',
64
+ '≈': 'Approximation - Acceptable error margins or tolerances',
65
+ '∴': 'Consequence - Logical implications and consequences',
66
+ '∀': 'Universal - Properties that apply to all elements',
67
+ '∃': 'Existential - Properties that must exist',
68
+ }
69
+
70
+ # Symbol examples
71
+ SYMBOL_EXAMPLES: Dict[str, List[str]] = {
72
+ '⊢': [
73
+ '⊢{u auth svc}',
74
+ '⊢{calc total price}',
75
+ '⊢{fetch u data from db}',
76
+ ],
77
+ '∂': [
78
+ '∂{bcrypt,jsonwebtoken,db}',
79
+ '∂{express,cors,helmet}',
80
+ '∂{react,redux,axios}',
81
+ ],
82
+ '⚠': [
83
+ '⚠{u exists in db}',
84
+ '⚠{db conn available}',
85
+ '⚠{valid sess}',
86
+ ],
87
+ '⊳': [
88
+ '⊳{id:uuid,pw≥8}',
89
+ '⊳{email:email,age≥18}',
90
+ '⊳{amt>0,currency∈{USD,EUR}}',
91
+ ],
92
+ '⊲': [
93
+ '⊲{ret JWT tok|null}',
94
+ '⊲{ret u obj|err}',
95
+ '⊲{ret bool}',
96
+ ],
97
+ '⊨': [
98
+ '⊨{no db mod}',
99
+ '⊨{no state change}',
100
+ '⊨{idempotent}',
101
+ ],
102
+ '⚡': [
103
+ '⚡{O(1)}',
104
+ '⚡{O(n)}',
105
+ '⚡{O(log n)}',
106
+ ],
107
+ '🔒': [
108
+ '🔒{pw:hash,tok:enc}',
109
+ '🔒{rate limit:100/min}',
110
+ '🔒{auth required}',
111
+ ],
112
+ '⇄': [
113
+ '⇄{enc↔dec}',
114
+ '⇄{ser↔deser}',
115
+ '⇄{compress↔decompress}',
116
+ ],
117
+ '⊕': [
118
+ '⊕{writes db}',
119
+ '⊕{sends email}',
120
+ '⊕{logs to file}',
121
+ ],
122
+ '⊗': [
123
+ '⊗{no pw log}',
124
+ '⊗{no network calls}',
125
+ '⊗{no db writes}',
126
+ ],
127
+ '≈': [
128
+ '≈{±0.001}',
129
+ '≈{~99.9% accuracy}',
130
+ '≈{<1ms latency}',
131
+ ],
132
+ '∴': [
133
+ '∴{if auth fails→ret null}',
134
+ '∴{if cache miss→fetch db}',
135
+ '∴{if invalid→throw err}',
136
+ ],
137
+ '∀': [
138
+ '∀{users have email}',
139
+ '∀{tokens expire 1h}',
140
+ '∀{pw≥8}',
141
+ ],
142
+ '∃': [
143
+ '∃{admin required}',
144
+ '∃{valid sess}',
145
+ '∃{active conn}',
146
+ ],
147
+ }
148
+
149
+ # Symbol categories
150
+ SYMBOL_CATEGORIES: Dict[str, List[str]] = {
151
+ 'core': list(CORE_SYMBOLS.keys()),
152
+ 'new': list(NEW_SYMBOLS.keys()),
153
+ 'required': ['⊢'], # Only purpose is truly required
154
+ 'optional': [s for s in ALL_SYMBOLS.keys() if s != '⊢'],
155
+ }
156
+
157
+
158
+ def get_symbol_name(symbol: str) -> str:
159
+ """Get the name of a symbol."""
160
+ return ALL_SYMBOLS.get(symbol, 'unknown')
161
+
162
+
163
+ def get_symbol_description(symbol: str) -> str:
164
+ """Get the description of a symbol."""
165
+ return SYMBOL_DESCRIPTIONS.get(symbol, 'Unknown symbol')
166
+
167
+
168
+ def get_symbol_examples(symbol: str) -> List[str]:
169
+ """Get examples for a symbol."""
170
+ return SYMBOL_EXAMPLES.get(symbol, [])
171
+
172
+
173
+ def is_valid_symbol(symbol: str) -> bool:
174
+ """Check if a symbol is valid."""
175
+ return symbol in ALL_SYMBOLS
176
+
177
+
178
+ def get_symbol_by_name(name: str) -> str:
179
+ """Get symbol by its name."""
180
+ return SYMBOL_NAMES.get(name, '')
181
+
182
+
183
+ def list_symbols(category: str = 'all') -> List[str]:
184
+ """
185
+ List symbols by category.
186
+
187
+ Categories: 'all', 'core', 'new', 'required', 'optional'
188
+ """
189
+ if category == 'all':
190
+ return list(ALL_SYMBOLS.keys())
191
+ return SYMBOL_CATEGORIES.get(category, [])
192
+
193
+
194
+ # Symbol validation rules
195
+ SYMBOL_RULES: Dict[str, Dict] = {
196
+ '⊢': {
197
+ 'required': True,
198
+ 'multiple': False,
199
+ 'format': 'Single sentence describing purpose',
200
+ },
201
+ '∂': {
202
+ 'required': False,
203
+ 'multiple': False,
204
+ 'format': 'Comma-separated list of dependencies',
205
+ },
206
+ '⚠': {
207
+ 'required': False,
208
+ 'multiple': True,
209
+ 'format': 'Comma-separated list of assumptions',
210
+ },
211
+ '⊳': {
212
+ 'required': False,
213
+ 'multiple': True,
214
+ 'format': 'Comma-separated list of preconditions',
215
+ },
216
+ '⊲': {
217
+ 'required': False,
218
+ 'multiple': True,
219
+ 'format': 'Comma-separated list of postconditions',
220
+ },
221
+ '⊨': {
222
+ 'required': False,
223
+ 'multiple': True,
224
+ 'format': 'Comma-separated list of invariants',
225
+ },
226
+ '⚡': {
227
+ 'required': False,
228
+ 'multiple': False,
229
+ 'format': 'Big O notation (e.g., O(1), O(n))',
230
+ },
231
+ '🔒': {
232
+ 'required': False,
233
+ 'multiple': True,
234
+ 'format': 'Comma-separated list of security measures',
235
+ },
236
+ '⇄': {
237
+ 'required': False,
238
+ 'multiple': True,
239
+ 'format': 'Bidirectional relationships (e.g., enc↔dec)',
240
+ },
241
+ '⊕': {
242
+ 'required': False,
243
+ 'multiple': True,
244
+ 'format': 'Comma-separated list of side effects',
245
+ },
246
+ '⊗': {
247
+ 'required': False,
248
+ 'multiple': True,
249
+ 'format': 'Comma-separated list of forbidden operations',
250
+ },
251
+ '≈': {
252
+ 'required': False,
253
+ 'multiple': True,
254
+ 'format': 'Approximation or tolerance (e.g., ±0.001)',
255
+ },
256
+ '∴': {
257
+ 'required': False,
258
+ 'multiple': True,
259
+ 'format': 'Logical implications (e.g., if X→Y)',
260
+ },
261
+ '∀': {
262
+ 'required': False,
263
+ 'multiple': True,
264
+ 'format': 'Universal properties (e.g., all users have email)',
265
+ },
266
+ '∃': {
267
+ 'required': False,
268
+ 'multiple': True,
269
+ 'format': 'Existential properties (e.g., admin required)',
270
+ },
271
+ }
272
+
273
+
274
+ def get_symbol_rules(symbol: str) -> Dict:
275
+ """Get validation rules for a symbol."""
276
+ return SYMBOL_RULES.get(symbol, {})
@@ -20,6 +20,7 @@ Translates @voodocs annotations (DarkArts language) into human-readable document
20
20
  from typing import List, Optional, Dict
21
21
  from pathlib import Path
22
22
  import re
23
+ import sys
23
24
 
24
25
  from darkarts.annotations.types import (
25
26
  ParsedAnnotations,
@@ -29,6 +30,10 @@ from darkarts.annotations.types import (
29
30
  ComplexityAnnotation,
30
31
  )
31
32
 
33
+ # Import abbreviation expansion for v3.0.0
34
+ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
35
+ from darkarts.companion_files_expansion import expand_abbreviations, expand_pattern_shortcuts
36
+
32
37
 
33
38
  class DocumentationGenerator:
34
39
  """Generates human-readable documentation from @voodocs annotations."""
@@ -41,8 +46,24 @@ class DocumentationGenerator:
41
46
  """Initialize mathematical symbol translation tables."""
42
47
  # Mathematical symbols to natural language
43
48
  self.math_symbols = {
49
+ # Core DarkArts symbols
50
+ '⊢': 'purpose',
51
+ '∂': 'dependencies',
52
+ '⊳': 'preconditions',
53
+ '⊲': 'postconditions',
54
+ '⊨': 'invariants',
55
+ '⚠': 'assumptions',
56
+ '⚡': 'complexity',
57
+ '🔒': 'security',
58
+ # v3.0.0 new symbols
59
+ '⇄': 'bidirectional relationship',
60
+ '⊕': 'side effects',
61
+ '⊗': 'forbidden operations',
62
+ '≈': 'approximately',
63
+ '∴': 'therefore',
44
64
  '∀': 'for all',
45
65
  '∃': 'there exists',
66
+ # Standard mathematical symbols
46
67
  '∈': 'in',
47
68
  '∉': 'not in',
48
69
  '⊆': 'subset of',
@@ -53,12 +74,13 @@ class DocumentationGenerator:
53
74
  '≥': 'greater than or equal to',
54
75
  '≤': 'less than or equal to',
55
76
  '≠': 'not equal to',
56
- '≈': 'approximately equal to',
57
77
  '⟹': 'implies',
58
78
  '⟺': 'if and only if',
59
79
  '⇒': 'implies',
60
80
  '⇔': 'if and only if',
61
81
  '→': 'leads to',
82
+ '←': 'comes from',
83
+ '↔': 'corresponds to',
62
84
  '∧': 'and',
63
85
  '∨': 'or',
64
86
  '¬': 'not',
@@ -303,10 +325,17 @@ class DocumentationGenerator:
303
325
  """
304
326
  Translate mathematical notation to natural language.
305
327
 
306
- This method converts DarkArts mathematical expressions into readable English.
328
+ This method converts DarkArts mathematical expressions into readable English,
329
+ including v3.0.0 abbreviation expansion.
307
330
  """
308
331
  text = expression.strip()
309
332
 
333
+ # Expand v3.0.0 abbreviations first (e.g., "u auth svc" -> "user authentication service")
334
+ text = expand_abbreviations(text)
335
+
336
+ # Expand pattern shortcuts (e.g., ":uuid" -> "must be valid UUID")
337
+ text = expand_pattern_shortcuts(text)
338
+
310
339
  # Handle quantifiers (∀, ∃) specially
311
340
  text = self._translate_quantifiers(text)
312
341