@voodocs/cli 0.3.2 → 0.4.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.
- package/CHANGELOG.md +441 -0
- package/cli.py +31 -2
- package/lib/darkarts/annotations/DARKARTS_SYMBOLS.md +529 -0
- package/lib/darkarts/annotations/TRANSFORMATION_EXAMPLES.md +478 -0
- package/lib/darkarts/annotations/__init__.py +42 -0
- package/lib/darkarts/annotations/darkarts_parser.py +238 -0
- package/lib/darkarts/annotations/parser.py +186 -5
- package/lib/darkarts/annotations/symbols.py +244 -0
- package/lib/darkarts/annotations/translator.py +386 -0
- package/lib/darkarts/context/ai_instructions.py +8 -1
- package/lib/darkarts/context/checker.py +290 -62
- package/lib/darkarts/context/commands.py +374 -290
- package/lib/darkarts/context/errors.py +164 -0
- package/lib/darkarts/context/models.py +23 -1
- package/lib/darkarts/context/module_utils.py +198 -0
- package/lib/darkarts/context/ui.py +337 -0
- package/lib/darkarts/context/validation.py +311 -0
- package/lib/darkarts/context/yaml_utils.py +117 -33
- package/lib/darkarts/exceptions.py +5 -0
- package/lib/darkarts/plugins/voodocs/instruction_generator.py +8 -1
- package/package.json +1 -1
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""@darkarts
|
|
2
|
+
⊢parser:darkarts.annotations
|
|
3
|
+
∂{re,typing,symbols}
|
|
4
|
+
⚠{@darkarts∈docstrings,unicode-support}
|
|
5
|
+
⊨{∀parse→structured-output,¬modify-src,handle-errors}
|
|
6
|
+
🔒{read-only}
|
|
7
|
+
⚡{O(n)|n=annotation-length}
|
|
8
|
+
|
|
9
|
+
DarkArts Annotation Parser
|
|
10
|
+
|
|
11
|
+
Parses @darkarts symbolic annotations from source code.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
from typing import Dict, List, Optional, Any
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
|
|
18
|
+
from .symbols import VOCABULARY, get_meta_symbols
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class DarkArtsAnnotation:
|
|
23
|
+
"""Parsed DarkArts annotation."""
|
|
24
|
+
module: Optional[str] = None # From ⊢
|
|
25
|
+
dependencies: List[str] = field(default_factory=list) # From ∂
|
|
26
|
+
assumptions: List[str] = field(default_factory=list) # From ⚠
|
|
27
|
+
invariants: List[str] = field(default_factory=list) # From ⊨
|
|
28
|
+
security: Optional[str] = None # From 🔒
|
|
29
|
+
performance: Optional[str] = None # From ⚡
|
|
30
|
+
complexity: Optional[str] = None # From 📊
|
|
31
|
+
objectives: Optional[str] = None # From 🎯
|
|
32
|
+
configuration: Optional[str] = None # From ⚙️
|
|
33
|
+
raw_text: str = "" # Original annotation text
|
|
34
|
+
|
|
35
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
36
|
+
"""Convert to dictionary."""
|
|
37
|
+
return {
|
|
38
|
+
'module': self.module,
|
|
39
|
+
'dependencies': self.dependencies,
|
|
40
|
+
'assumptions': self.assumptions,
|
|
41
|
+
'invariants': self.invariants,
|
|
42
|
+
'security': self.security,
|
|
43
|
+
'performance': self.performance,
|
|
44
|
+
'complexity': self.complexity,
|
|
45
|
+
'objectives': self.objectives,
|
|
46
|
+
'configuration': self.configuration,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DarkArtsParser:
|
|
51
|
+
"""
|
|
52
|
+
Parser for DarkArts symbolic annotations.
|
|
53
|
+
|
|
54
|
+
Extracts and parses @darkarts annotations from source code.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# Pattern to match @darkarts annotations
|
|
58
|
+
PATTERN = r'"""@darkarts\s*(.*?)\s*"""'
|
|
59
|
+
|
|
60
|
+
# Meta symbol mappings
|
|
61
|
+
META_SYMBOLS = {
|
|
62
|
+
'⊢': 'module',
|
|
63
|
+
'∂': 'dependencies',
|
|
64
|
+
'⚠': 'assumptions',
|
|
65
|
+
'⊨': 'invariants',
|
|
66
|
+
'🔒': 'security',
|
|
67
|
+
'⚡': 'performance',
|
|
68
|
+
'📊': 'complexity',
|
|
69
|
+
'🎯': 'objectives',
|
|
70
|
+
'⚙️': 'configuration',
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
def __init__(self):
|
|
74
|
+
"""Initialize the parser."""
|
|
75
|
+
self.meta_symbols = get_meta_symbols()
|
|
76
|
+
|
|
77
|
+
def parse(self, source_code: str) -> List[DarkArtsAnnotation]:
|
|
78
|
+
"""
|
|
79
|
+
Parse all @darkarts annotations from source code.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
source_code: Source code containing @darkarts annotations
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
List of parsed annotations
|
|
86
|
+
"""
|
|
87
|
+
annotations = []
|
|
88
|
+
|
|
89
|
+
# Find all @darkarts blocks
|
|
90
|
+
matches = re.finditer(self.PATTERN, source_code, re.DOTALL)
|
|
91
|
+
|
|
92
|
+
for match in matches:
|
|
93
|
+
annotation_text = match.group(1)
|
|
94
|
+
annotation = self.parse_annotation(annotation_text)
|
|
95
|
+
annotations.append(annotation)
|
|
96
|
+
|
|
97
|
+
return annotations
|
|
98
|
+
|
|
99
|
+
def parse_annotation(self, text: str) -> DarkArtsAnnotation:
|
|
100
|
+
"""
|
|
101
|
+
Parse a single @darkarts annotation.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
text: Annotation text (without @darkarts marker)
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Parsed annotation
|
|
108
|
+
"""
|
|
109
|
+
annotation = DarkArtsAnnotation(raw_text=text)
|
|
110
|
+
|
|
111
|
+
# Split into lines
|
|
112
|
+
lines = text.strip().split('\n')
|
|
113
|
+
|
|
114
|
+
for line in lines:
|
|
115
|
+
line = line.strip()
|
|
116
|
+
if not line:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
# Check for meta symbols
|
|
120
|
+
for symbol, field_name in self.META_SYMBOLS.items():
|
|
121
|
+
if line.startswith(symbol):
|
|
122
|
+
# Extract content after symbol
|
|
123
|
+
content = line[len(symbol):].strip()
|
|
124
|
+
self._parse_field(annotation, field_name, content)
|
|
125
|
+
break
|
|
126
|
+
|
|
127
|
+
return annotation
|
|
128
|
+
|
|
129
|
+
def _parse_field(self, annotation: DarkArtsAnnotation, field_name: str, content: str):
|
|
130
|
+
"""
|
|
131
|
+
Parse a field and update annotation.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
annotation: Annotation to update
|
|
135
|
+
field_name: Name of field (module, dependencies, etc.)
|
|
136
|
+
content: Content to parse
|
|
137
|
+
"""
|
|
138
|
+
if field_name == 'module':
|
|
139
|
+
annotation.module = content
|
|
140
|
+
|
|
141
|
+
elif field_name == 'dependencies':
|
|
142
|
+
annotation.dependencies = self._parse_set(content)
|
|
143
|
+
|
|
144
|
+
elif field_name == 'assumptions':
|
|
145
|
+
annotation.assumptions = self._parse_set(content)
|
|
146
|
+
|
|
147
|
+
elif field_name == 'invariants':
|
|
148
|
+
annotation.invariants = self._parse_set(content)
|
|
149
|
+
|
|
150
|
+
elif field_name == 'security':
|
|
151
|
+
annotation.security = content
|
|
152
|
+
|
|
153
|
+
elif field_name == 'performance':
|
|
154
|
+
annotation.performance = content
|
|
155
|
+
|
|
156
|
+
elif field_name == 'complexity':
|
|
157
|
+
annotation.complexity = content
|
|
158
|
+
|
|
159
|
+
elif field_name == 'objectives':
|
|
160
|
+
annotation.objectives = content
|
|
161
|
+
|
|
162
|
+
elif field_name == 'configuration':
|
|
163
|
+
annotation.configuration = content
|
|
164
|
+
|
|
165
|
+
def _parse_set(self, content: str) -> List[str]:
|
|
166
|
+
"""
|
|
167
|
+
Parse a set notation {a,b,c} into a list.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
content: Content like "{a,b,c}" or "a,b,c"
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
List of items
|
|
174
|
+
"""
|
|
175
|
+
# Remove braces if present
|
|
176
|
+
content = content.strip()
|
|
177
|
+
if content.startswith('{') and content.endswith('}'):
|
|
178
|
+
content = content[1:-1]
|
|
179
|
+
|
|
180
|
+
# Split by comma
|
|
181
|
+
items = []
|
|
182
|
+
current = []
|
|
183
|
+
depth = 0
|
|
184
|
+
in_string = False
|
|
185
|
+
|
|
186
|
+
for char in content:
|
|
187
|
+
if char == '{' and not in_string:
|
|
188
|
+
depth += 1
|
|
189
|
+
elif char == '}' and not in_string:
|
|
190
|
+
depth -= 1
|
|
191
|
+
elif char == '"':
|
|
192
|
+
in_string = not in_string
|
|
193
|
+
elif char == ',' and depth == 0 and not in_string:
|
|
194
|
+
item = ''.join(current).strip()
|
|
195
|
+
if item:
|
|
196
|
+
items.append(item)
|
|
197
|
+
current = []
|
|
198
|
+
continue
|
|
199
|
+
|
|
200
|
+
current.append(char)
|
|
201
|
+
|
|
202
|
+
# Add last item
|
|
203
|
+
if current:
|
|
204
|
+
item = ''.join(current).strip()
|
|
205
|
+
if item:
|
|
206
|
+
items.append(item)
|
|
207
|
+
|
|
208
|
+
return items
|
|
209
|
+
|
|
210
|
+
def parse_file(self, file_path: str) -> List[DarkArtsAnnotation]:
|
|
211
|
+
"""
|
|
212
|
+
Parse @darkarts annotations from a file.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
file_path: Path to source file
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
List of parsed annotations
|
|
219
|
+
"""
|
|
220
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
221
|
+
source_code = f.read()
|
|
222
|
+
|
|
223
|
+
return self.parse(source_code)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
# Convenience function
|
|
227
|
+
def parse_darkarts(source_code: str) -> List[DarkArtsAnnotation]:
|
|
228
|
+
"""
|
|
229
|
+
Parse @darkarts annotations from source code.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
source_code: Source code containing @darkarts annotations
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
List of parsed annotations
|
|
236
|
+
"""
|
|
237
|
+
parser = DarkArtsParser()
|
|
238
|
+
return parser.parse(source_code)
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""@darkarts
|
|
2
|
+
⊢parser:annotations.multi-lang
|
|
3
|
+
∂{re,ast,pathlib,types}
|
|
4
|
+
⚠{src:utf8,@voodocs∈docstrings,yaml-lists,fs:readable}
|
|
5
|
+
⊨{∀parse→¬modify-src,∀read→handle-encoding,parsed∈pyobj,dedup-invariants,lang-detect:accurate}
|
|
6
|
+
🔒{read-only,¬exec}
|
|
7
|
+
⚡{O(n'*m/c)|n'=files-with-annotations,m=avg-file-size,c=cache-constant,speedup=5-10x}
|
|
8
|
+
|
|
2
9
|
DarkArts Annotation Parser
|
|
3
10
|
|
|
4
11
|
Extracts DarkArts annotations from source code in multiple languages.
|
|
@@ -8,8 +15,10 @@ import re
|
|
|
8
15
|
import ast
|
|
9
16
|
import json
|
|
10
17
|
import subprocess
|
|
18
|
+
import os
|
|
11
19
|
from typing import List, Optional, Dict, Any
|
|
12
20
|
from pathlib import Path
|
|
21
|
+
from functools import lru_cache
|
|
13
22
|
|
|
14
23
|
from .types import (
|
|
15
24
|
ParsedAnnotations,
|
|
@@ -39,6 +48,11 @@ class AnnotationParser:
|
|
|
39
48
|
# Regex patterns for different languages
|
|
40
49
|
PATTERNS = {
|
|
41
50
|
Language.PYTHON: r'"""@voodocs\s*(.*?)\s*"""',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# DarkArts patterns (symbolic annotations)
|
|
54
|
+
DARKARTS_PATTERNS = {
|
|
55
|
+
Language.PYTHON: r'"""@darkarts\s*(.*?)\s*"""',
|
|
42
56
|
Language.TYPESCRIPT: r'/\*@voodocs\s*(.*?)\s*\*/',
|
|
43
57
|
Language.JAVASCRIPT: r'/\*@voodocs\s*(.*?)\s*\*/',
|
|
44
58
|
Language.JAVA: r'/\*@voodocs\s*(.*?)\s*\*/',
|
|
@@ -75,7 +89,35 @@ class AnnotationParser:
|
|
|
75
89
|
return mapping.get(ext, Language.PYTHON)
|
|
76
90
|
|
|
77
91
|
def parse_file(self, source_file: str) -> ParsedAnnotations:
|
|
78
|
-
"""
|
|
92
|
+
"""
|
|
93
|
+
Parse annotations from a source file.
|
|
94
|
+
|
|
95
|
+
Phase 1 Optimization: Cache results based on file modification time.
|
|
96
|
+
"""
|
|
97
|
+
# Get file modification time for cache key
|
|
98
|
+
try:
|
|
99
|
+
mtime = os.path.getmtime(source_file)
|
|
100
|
+
except OSError:
|
|
101
|
+
mtime = 0
|
|
102
|
+
|
|
103
|
+
# Use cached version if available
|
|
104
|
+
return self._parse_file_cached(source_file, mtime)
|
|
105
|
+
|
|
106
|
+
@lru_cache(maxsize=1000)
|
|
107
|
+
def _parse_file_cached(self, source_file: str, mtime: float) -> ParsedAnnotations:
|
|
108
|
+
"""
|
|
109
|
+
Parse annotations from a source file with caching.
|
|
110
|
+
|
|
111
|
+
Phase 1 Optimization: LRU cache with mtime as key.
|
|
112
|
+
Cache invalidates when file is modified (mtime changes).
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
source_file: Path to the source file
|
|
116
|
+
mtime: File modification time (used as cache key)
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
ParsedAnnotations object
|
|
120
|
+
"""
|
|
79
121
|
with open(source_file, 'r', encoding='utf-8') as f:
|
|
80
122
|
source_code = f.read()
|
|
81
123
|
|
|
@@ -92,9 +134,22 @@ class AnnotationParser:
|
|
|
92
134
|
if language is None:
|
|
93
135
|
language = self.detect_language(source_file)
|
|
94
136
|
|
|
95
|
-
# Extract all annotation blocks
|
|
96
|
-
pattern = self.PATTERNS[
|
|
97
|
-
|
|
137
|
+
# Extract all annotation blocks (VooDocs and DarkArts)
|
|
138
|
+
pattern = self.PATTERNS.get(language, self.PATTERNS[Language.PYTHON])
|
|
139
|
+
darkarts_pattern = self.DARKARTS_PATTERNS.get(language, self.DARKARTS_PATTERNS[Language.PYTHON])
|
|
140
|
+
|
|
141
|
+
# Try VooDocs first
|
|
142
|
+
voodocs_matches = list(re.finditer(pattern, source_code, re.DOTALL))
|
|
143
|
+
|
|
144
|
+
# Also check for DarkArts
|
|
145
|
+
darkarts_matches = list(re.finditer(darkarts_pattern, source_code, re.DOTALL))
|
|
146
|
+
|
|
147
|
+
# If we found DarkArts annotations, translate them to VooDocs format
|
|
148
|
+
if darkarts_matches and not voodocs_matches:
|
|
149
|
+
return self._parse_darkarts_annotations(source_code, source_file, language, darkarts_matches)
|
|
150
|
+
|
|
151
|
+
# Use VooDocs annotations
|
|
152
|
+
matches = voodocs_matches
|
|
98
153
|
|
|
99
154
|
annotations = []
|
|
100
155
|
for match in matches:
|
|
@@ -628,10 +683,35 @@ class AnnotationParser:
|
|
|
628
683
|
if 'side_effects' in annotations:
|
|
629
684
|
func.side_effects = annotations['side_effects'] if isinstance(annotations['side_effects'], list) else [annotations['side_effects']]
|
|
630
685
|
|
|
686
|
+
@staticmethod
|
|
687
|
+
def _has_annotations(file_path: Path) -> bool:
|
|
688
|
+
"""
|
|
689
|
+
Quick check if a file contains @voodocs annotations.
|
|
690
|
+
|
|
691
|
+
Phase 1 Optimization: Pre-filter files before full parsing.
|
|
692
|
+
Reads only first 10KB to check for @voodocs marker.
|
|
693
|
+
|
|
694
|
+
Args:
|
|
695
|
+
file_path: Path to the file to check
|
|
696
|
+
|
|
697
|
+
Returns:
|
|
698
|
+
True if file likely contains annotations, False otherwise
|
|
699
|
+
"""
|
|
700
|
+
try:
|
|
701
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
702
|
+
# Read first 10KB (enough for module-level annotations)
|
|
703
|
+
preview = f.read(10000)
|
|
704
|
+
return '@voodocs' in preview
|
|
705
|
+
except Exception:
|
|
706
|
+
# If we can't read it, skip it
|
|
707
|
+
return False
|
|
708
|
+
|
|
631
709
|
def parse_directory(self, directory: Path) -> List[ParsedAnnotations]:
|
|
632
710
|
"""
|
|
633
711
|
Parse all source files in a directory recursively.
|
|
634
712
|
|
|
713
|
+
Phase 1 Optimization: Pre-filter files before parsing.
|
|
714
|
+
|
|
635
715
|
Args:
|
|
636
716
|
directory: Path to the directory to scan
|
|
637
717
|
|
|
@@ -662,6 +742,10 @@ class AnnotationParser:
|
|
|
662
742
|
if path.suffix.lower() not in extensions:
|
|
663
743
|
continue
|
|
664
744
|
|
|
745
|
+
# Phase 1 Optimization: Pre-filter files without annotations
|
|
746
|
+
if not self._has_annotations(path):
|
|
747
|
+
continue
|
|
748
|
+
|
|
665
749
|
try:
|
|
666
750
|
# Parse the file
|
|
667
751
|
parsed = self.parse_file(str(path))
|
|
@@ -680,3 +764,100 @@ class AnnotationParser:
|
|
|
680
764
|
continue
|
|
681
765
|
|
|
682
766
|
return results
|
|
767
|
+
|
|
768
|
+
def _parse_darkarts_annotations(
|
|
769
|
+
self,
|
|
770
|
+
source_code: str,
|
|
771
|
+
source_file: str,
|
|
772
|
+
language: Language,
|
|
773
|
+
darkarts_matches: List
|
|
774
|
+
) -> ParsedAnnotations:
|
|
775
|
+
"""Parse @darkarts annotations and translate to VooDocs format."""
|
|
776
|
+
from .darkarts_parser import parse_darkarts
|
|
777
|
+
from .translator import DarkArtsTranslator
|
|
778
|
+
|
|
779
|
+
# Parse all @darkarts annotations
|
|
780
|
+
translator = DarkArtsTranslator()
|
|
781
|
+
translated_annotations = []
|
|
782
|
+
|
|
783
|
+
for match in darkarts_matches:
|
|
784
|
+
darkarts_text = match.group(1)
|
|
785
|
+
|
|
786
|
+
# Parse @darkarts annotation (parse_annotation expects just the content)
|
|
787
|
+
from .darkarts_parser import DarkArtsParser
|
|
788
|
+
parser = DarkArtsParser()
|
|
789
|
+
darkarts_annotation = parser.parse_annotation(darkarts_text)
|
|
790
|
+
|
|
791
|
+
# Process the annotation
|
|
792
|
+
if darkarts_annotation:
|
|
793
|
+
# Convert DarkArtsAnnotation to dict for YAML generation
|
|
794
|
+
annotation_dict = {
|
|
795
|
+
'module': darkarts_annotation.module,
|
|
796
|
+
'dependencies': darkarts_annotation.dependencies,
|
|
797
|
+
'assumptions': darkarts_annotation.assumptions,
|
|
798
|
+
'invariants': darkarts_annotation.invariants,
|
|
799
|
+
'security_model': darkarts_annotation.security,
|
|
800
|
+
'performance': darkarts_annotation.performance,
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
# Convert to @voodocs YAML format
|
|
804
|
+
voodocs_yaml = self._darkarts_to_voodocs_yaml(annotation_dict)
|
|
805
|
+
|
|
806
|
+
line_number = source_code[:match.start()].count('\n') + 1
|
|
807
|
+
translated_annotations.append({
|
|
808
|
+
'text': voodocs_yaml,
|
|
809
|
+
'line': line_number,
|
|
810
|
+
'start': match.start(),
|
|
811
|
+
'end': match.end(),
|
|
812
|
+
})
|
|
813
|
+
|
|
814
|
+
# Parse as if they were @voodocs
|
|
815
|
+
if language == Language.PYTHON:
|
|
816
|
+
return self._parse_python(source_code, source_file, translated_annotations)
|
|
817
|
+
else:
|
|
818
|
+
return self._parse_generic(source_code, source_file, language, translated_annotations)
|
|
819
|
+
|
|
820
|
+
def _darkarts_to_voodocs_yaml(self, annotation_dict: Dict[str, Any]) -> str:
|
|
821
|
+
"""Convert DarkArts annotation dict to @voodocs YAML format."""
|
|
822
|
+
lines = []
|
|
823
|
+
|
|
824
|
+
# Module name becomes module_purpose (just use the identifier)
|
|
825
|
+
if annotation_dict.get('module'):
|
|
826
|
+
lines.append(f'module_purpose: "Module {annotation_dict["module"]}"')
|
|
827
|
+
|
|
828
|
+
# Dependencies - keep as-is
|
|
829
|
+
if annotation_dict.get('dependencies'):
|
|
830
|
+
lines.append('dependencies: [')
|
|
831
|
+
for dep in annotation_dict['dependencies']:
|
|
832
|
+
lines.append(f' "{dep}",')
|
|
833
|
+
lines.append(']')
|
|
834
|
+
|
|
835
|
+
# Assumptions - keep as-is
|
|
836
|
+
if annotation_dict.get('assumptions'):
|
|
837
|
+
lines.append('assumptions: [')
|
|
838
|
+
for assumption in annotation_dict['assumptions']:
|
|
839
|
+
lines.append(f' "{assumption}",')
|
|
840
|
+
lines.append(']')
|
|
841
|
+
|
|
842
|
+
# Invariants - keep as-is
|
|
843
|
+
if annotation_dict.get('invariants'):
|
|
844
|
+
lines.append('invariants: [')
|
|
845
|
+
for invariant in annotation_dict['invariants']:
|
|
846
|
+
lines.append(f' "{invariant}",')
|
|
847
|
+
lines.append(']')
|
|
848
|
+
|
|
849
|
+
# Security model
|
|
850
|
+
if annotation_dict.get('security_model'):
|
|
851
|
+
sec = annotation_dict['security_model']
|
|
852
|
+
if isinstance(sec, list):
|
|
853
|
+
sec = ', '.join(sec)
|
|
854
|
+
lines.append(f'security_model: "{sec}"')
|
|
855
|
+
|
|
856
|
+
# Performance
|
|
857
|
+
if annotation_dict.get('performance'):
|
|
858
|
+
perf = annotation_dict['performance']
|
|
859
|
+
if isinstance(perf, dict):
|
|
860
|
+
perf = str(perf)
|
|
861
|
+
lines.append(f'performance_model: "{perf}"')
|
|
862
|
+
|
|
863
|
+
return '\n'.join(lines)
|