statable 3.1.0__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.
- codegen/__init__.py +38 -0
- codegen/c_code_generator.py +1752 -0
- codegen/code_merger.py +371 -0
- codegen/code_templates.py +509 -0
- codegen/config.py +167 -0
- codegen/enum_generator.py +377 -0
- codegen/event_queue_generator.py +303 -0
- codegen/interrupt_generator.py +345 -0
- codegen/naming_convention.py +171 -0
- codegen/osal_generator.py +282 -0
- codegen/role_function_generator.py +1244 -0
- codegen/sample_data.py +269 -0
- codegen/state_actions_generator.py +395 -0
- codegen/struct_generator.py +721 -0
- codegen/timer_generator.py +299 -0
- codegen/transition_generator.py +907 -0
- codegen/type_mapper.py +69 -0
- codegen/validate/__init__.py +38 -0
- codegen/validate/change_actions.py +90 -0
- codegen/validate/change_applier.py +416 -0
- codegen/validate/clipboard_manager.py +47 -0
- codegen/validate/data/__init__.py +30 -0
- codegen/validate/data/action_definitions.py +200 -0
- codegen/validate/data/keywords.py +46 -0
- codegen/validate/data/prompt_templates.py +176 -0
- codegen/validate/data/validation_rules.py +205 -0
- codegen/validate/items/__init__.py +30 -0
- codegen/validate/items/base_validator.py +25 -0
- codegen/validate/items/cell_validator.py +275 -0
- codegen/validate/items/custom_type_validator.py +75 -0
- codegen/validate/items/event_validator.py +78 -0
- codegen/validate/items/flag_validator.py +74 -0
- codegen/validate/items/interrupt_validator.py +75 -0
- codegen/validate/items/queue_validator.py +73 -0
- codegen/validate/items/role_function_validator.py +175 -0
- codegen/validate/items/state_validator.py +88 -0
- codegen/validate/items/timer_validator.py +120 -0
- codegen/validate/items/transition_validator.py +113 -0
- codegen/validate/items/variable_validator.py +132 -0
- codegen/validate/logger.py +52 -0
- codegen/validate/models.py +177 -0
- codegen/validate/prompt_generator.py +724 -0
- codegen/validate/response_parser.py +154 -0
- codegen/validate/response_validator.py +275 -0
- codegen/validate/validation_dialog.py +322 -0
- codegen/validate/validator.py +94 -0
- codegen/variable_generator.py +419 -0
- statable/__init__.py +71 -0
- statable/__main__.py +49 -0
- statable/cli.py +251 -0
- statable/global_defs.py +256 -0
- statable/mermaid_gen.py +144 -0
- statable/model.py +426 -0
- statable/parser.py +64 -0
- statable/sample_data.py +435 -0
- statable/shared/__init__.py +24 -0
- statable/shared/condition_library.py +63 -0
- statable/shared/literal_library.py +66 -0
- statable/shared/role_function_library.py +150 -0
- statable/state_machine.py +121 -0
- statable/xml_io.py +1164 -0
- statable-3.1.0.dist-info/METADATA +151 -0
- statable-3.1.0.dist-info/RECORD +117 -0
- statable-3.1.0.dist-info/WHEEL +5 -0
- statable-3.1.0.dist-info/entry_points.txt +2 -0
- statable-3.1.0.dist-info/top_level.txt +3 -0
- statable_gui/__init__.py +23 -0
- statable_gui/action_edit_dialog.py +214 -0
- statable_gui/code_generation_dialog.py +486 -0
- statable_gui/code_generation_settings_dialog.py +522 -0
- statable_gui/common_widgets.py +537 -0
- statable_gui/condition_builder_dialog.py +500 -0
- statable_gui/condition_edit_dialog.py +123 -0
- statable_gui/condition_template_dialog.py +58 -0
- statable_gui/config.py +21 -0
- statable_gui/dialogs.py +439 -0
- statable_gui/event_definition_dialog.py +605 -0
- statable_gui/event_delivery_settings_dialog.py +191 -0
- statable_gui/event_queue_dialog.py +245 -0
- statable_gui/global_defs.py +167 -0
- statable_gui/global_defs_dialog.py +796 -0
- statable_gui/i18n/__init__.py +118 -0
- statable_gui/interrupt_handler_edit_dialog.py +810 -0
- statable_gui/layer_settings_dialog.py +180 -0
- statable_gui/libcntrl/__init__.py +23 -0
- statable_gui/libcntrl/condition_library.py +13 -0
- statable_gui/libcntrl/literal_library.py +13 -0
- statable_gui/libcntrl/literal_management_dialog.py +185 -0
- statable_gui/libcntrl/role_function_edit_dialog.py +145 -0
- statable_gui/libcntrl/role_function_library.py +13 -0
- statable_gui/literal_definition_dialog.py +62 -0
- statable_gui/logger.py +83 -0
- statable_gui/main_window.py +1380 -0
- statable_gui/matrix_table.py +413 -0
- statable_gui/preference_keys.py +18 -0
- statable_gui/preferences.py +74 -0
- statable_gui/role_function_dialog.py +244 -0
- statable_gui/sample_data.py +11 -0
- statable_gui/state_actions_dialog.py +398 -0
- statable_gui/symbol_picker.py +170 -0
- statable_gui/traceball.py +37 -0
- statable_gui/transition_editor_direct/__init__.py +15 -0
- statable_gui/transition_editor_direct/actions_tab.py +856 -0
- statable_gui/transition_editor_direct/code_widget.py +344 -0
- statable_gui/transition_editor_direct/coverage_analyzer.py +163 -0
- statable_gui/transition_editor_direct/dialog.py +335 -0
- statable_gui/transition_editor_direct/draft.py +270 -0
- statable_gui/transition_editor_direct/edit_dialogs.py +160 -0
- statable_gui/transition_editor_direct/overview_tab.py +173 -0
- statable_gui/transition_editor_direct/palette_widget.py +233 -0
- statable_gui/transition_editor_direct/relations_edit_dialog.py +156 -0
- statable_gui/transition_editor_direct/relations_tab.py +230 -0
- statable_gui/transition_editor_direct/system_global_dialog.py +52 -0
- statable_gui/transition_editor_direct/transition_actions_dialog.py +109 -0
- statable_gui/transition_editor_direct/transitions_tab.py +620 -0
- statable_gui/validation_dialog.py +285 -0
- statable_gui/widgets.py +1315 -0
codegen/code_merger.py
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# codegen/code_merger.py
|
|
2
|
+
"""
|
|
3
|
+
Merging generated code with existing code.
|
|
4
|
+
Updates auto-generated code while preserving user-edited sections.
|
|
5
|
+
|
|
6
|
+
Supported markers:
|
|
7
|
+
- File-level user area: [[STABLE_USER_CODE_START/END]]
|
|
8
|
+
- Function-level user area: [[STABLE_USER_CODE_START:<name>/END:<name>]]
|
|
9
|
+
- RoleFunc_XXX uses <name> in "Driver_Init" form (includes layer)
|
|
10
|
+
- ISR_XXX uses <name> in "TIMER0" form (no layer)
|
|
11
|
+
- File-tail user area: [[STABLE_USER_CODE_TAIL_START/END]]
|
|
12
|
+
|
|
13
|
+
Version History
|
|
14
|
+
---------------
|
|
15
|
+
v2.0 - inject_file_user_code: if an existing START/END block is present,
|
|
16
|
+
replace its contents. Old implementation always inserted a new
|
|
17
|
+
block after the includes, leaving the original block as dead code
|
|
18
|
+
and the placeholder behind.
|
|
19
|
+
|
|
20
|
+
v2.1 - Idempotency fix. The replacement pattern previously captured
|
|
21
|
+
the trailing newline into group(2), so each merge added one
|
|
22
|
+
extra newline per marker (4 markers -> +4 chars per merge).
|
|
23
|
+
The new pattern replaces the ENTIRE block (START..END) with a
|
|
24
|
+
freshly constructed block (START\\n user_code \\n END), and
|
|
25
|
+
uses a callable replacement to avoid re.sub escape handling.
|
|
26
|
+
Verified by tests/test_v2_4_p1_merge.py §3.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import re
|
|
30
|
+
import os
|
|
31
|
+
import logging
|
|
32
|
+
from typing import Dict, List, Optional, Tuple, Callable
|
|
33
|
+
|
|
34
|
+
logger = logging.getLogger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CodeMerger:
|
|
38
|
+
"""Merge class for generated code and existing code"""
|
|
39
|
+
|
|
40
|
+
# Marker definitions
|
|
41
|
+
MARKERS = {
|
|
42
|
+
'file_user_start': '/* [[STABLE_USER_CODE_START]] */',
|
|
43
|
+
'file_user_end': '/* [[STABLE_USER_CODE_END]] */',
|
|
44
|
+
'func_user_start': '/* [[STABLE_USER_CODE_START:{func_name}]] */',
|
|
45
|
+
'func_user_end': '/* [[STABLE_USER_CODE_END:{func_name}]] */',
|
|
46
|
+
'auto_start': '/* [[STABLE_AUTO_GENERATED_START]] */',
|
|
47
|
+
'auto_end': '/* [[STABLE_AUTO_GENERATED_END]] */',
|
|
48
|
+
# File-tail user area
|
|
49
|
+
'file_tail_user_start': '/* [[STABLE_USER_CODE_TAIL_START]] */',
|
|
50
|
+
'file_tail_user_end': '/* [[STABLE_USER_CODE_TAIL_END]] */',
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# Function name extraction patterns (RoleFunc_ / ISR_)
|
|
54
|
+
FUNC_NAME_PATTERNS = [
|
|
55
|
+
r'RoleFunc_(\w+)\s*\(', # RoleFunc_Driver_Init(
|
|
56
|
+
r'ISR_(\w+)\s*\(', # ISR_TIMER0(
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
def __init__(self):
|
|
60
|
+
self.markers = self.MARKERS
|
|
61
|
+
|
|
62
|
+
def _log_debug(self, message, level='debug'):
|
|
63
|
+
log_func = getattr(logger, level, logger.debug)
|
|
64
|
+
log_func(message)
|
|
65
|
+
|
|
66
|
+
# ===== Extraction =====
|
|
67
|
+
def extract_file_user_code(self, existing_content: str) -> str:
|
|
68
|
+
"""Extract file-level user code (contents between START and END)."""
|
|
69
|
+
start = self.markers['file_user_start']
|
|
70
|
+
end = self.markers['file_user_end']
|
|
71
|
+
|
|
72
|
+
pattern = rf'{re.escape(start)}\s*\n(.*?)\n\s*{re.escape(end)}'
|
|
73
|
+
match = re.search(pattern, existing_content, re.DOTALL)
|
|
74
|
+
|
|
75
|
+
if match:
|
|
76
|
+
self._log_debug("Extracted file user code")
|
|
77
|
+
return match.group(1)
|
|
78
|
+
return ""
|
|
79
|
+
|
|
80
|
+
def extract_func_user_code(self, existing_content: str,
|
|
81
|
+
func_name: str) -> str:
|
|
82
|
+
"""Extract per-function user code."""
|
|
83
|
+
start = self.markers['func_user_start'].format(func_name=func_name)
|
|
84
|
+
end = self.markers['func_user_end'].format(func_name=func_name)
|
|
85
|
+
|
|
86
|
+
pattern = rf'{re.escape(start)}\s*\n(.*?)\n\s*{re.escape(end)}'
|
|
87
|
+
match = re.search(pattern, existing_content, re.DOTALL)
|
|
88
|
+
|
|
89
|
+
if match:
|
|
90
|
+
self._log_debug(f"Extracted user code for function {func_name}")
|
|
91
|
+
return match.group(1)
|
|
92
|
+
return ""
|
|
93
|
+
|
|
94
|
+
def extract_all_func_user_codes(self, existing_content: str
|
|
95
|
+
) -> Dict[str, str]:
|
|
96
|
+
"""
|
|
97
|
+
Extract all per-function user codes.
|
|
98
|
+
|
|
99
|
+
Supports both RoleFunc_XXX and ISR_XXX.
|
|
100
|
+
- RoleFunc_Driver_Init -> key "Driver_Init"
|
|
101
|
+
- ISR_TIMER0 -> key "TIMER0"
|
|
102
|
+
"""
|
|
103
|
+
func_user_codes = {}
|
|
104
|
+
seen = set()
|
|
105
|
+
|
|
106
|
+
for pattern in self.FUNC_NAME_PATTERNS:
|
|
107
|
+
for match in re.finditer(pattern, existing_content):
|
|
108
|
+
func_name = match.group(1)
|
|
109
|
+
if func_name in seen:
|
|
110
|
+
continue
|
|
111
|
+
seen.add(func_name)
|
|
112
|
+
user_code = self.extract_func_user_code(
|
|
113
|
+
existing_content, func_name
|
|
114
|
+
)
|
|
115
|
+
if user_code:
|
|
116
|
+
func_user_codes[func_name] = user_code
|
|
117
|
+
|
|
118
|
+
self._log_debug(
|
|
119
|
+
f"extract_all_func_user_codes: {len(func_user_codes)} funcs"
|
|
120
|
+
)
|
|
121
|
+
return func_user_codes
|
|
122
|
+
|
|
123
|
+
def extract_file_tail_user_code(self, existing_content: str) -> str:
|
|
124
|
+
"""Extract file-tail user code."""
|
|
125
|
+
start = self.markers['file_tail_user_start']
|
|
126
|
+
end = self.markers['file_tail_user_end']
|
|
127
|
+
|
|
128
|
+
pattern = rf'{re.escape(start)}\s*\n(.*?)\n\s*{re.escape(end)}'
|
|
129
|
+
match = re.search(pattern, existing_content, re.DOTALL)
|
|
130
|
+
|
|
131
|
+
if match:
|
|
132
|
+
self._log_debug("Extracted tail user code")
|
|
133
|
+
return match.group(1)
|
|
134
|
+
return ""
|
|
135
|
+
|
|
136
|
+
# ===== Helper: block replacement (idempotent) =====
|
|
137
|
+
@staticmethod
|
|
138
|
+
def _replace_block(text: str, start_marker: str, end_marker: str,
|
|
139
|
+
user_code: str) -> Tuple[str, int]:
|
|
140
|
+
"""
|
|
141
|
+
Replace the contents of an existing START..END block with a
|
|
142
|
+
canonical block:
|
|
143
|
+
|
|
144
|
+
START
|
|
145
|
+
<user_code>
|
|
146
|
+
END
|
|
147
|
+
|
|
148
|
+
Returns (new_text, replaced_count).
|
|
149
|
+
The replacement uses a callable to bypass re.sub escape handling.
|
|
150
|
+
"""
|
|
151
|
+
pattern = re.compile(
|
|
152
|
+
rf'{re.escape(start_marker)}.*?{re.escape(end_marker)}',
|
|
153
|
+
re.DOTALL,
|
|
154
|
+
)
|
|
155
|
+
canonical = f"{start_marker}\n{user_code}\n{end_marker}"
|
|
156
|
+
new_text, count = pattern.subn(
|
|
157
|
+
lambda m: canonical, text, count=1,
|
|
158
|
+
)
|
|
159
|
+
return new_text, count
|
|
160
|
+
|
|
161
|
+
# ===== Injection =====
|
|
162
|
+
def inject_file_user_code(self, generated_content: str,
|
|
163
|
+
user_code: str) -> str:
|
|
164
|
+
"""
|
|
165
|
+
Inject file-level user code into the generated code.
|
|
166
|
+
|
|
167
|
+
[v2.0]
|
|
168
|
+
If an existing START/END block is present, replace its contents.
|
|
169
|
+
Old: always inserted a new block after includes -> the original
|
|
170
|
+
block became dead code.
|
|
171
|
+
New: replace existing block -> always exactly one block.
|
|
172
|
+
|
|
173
|
+
[v2.1]
|
|
174
|
+
Idempotency fix. The whole block (START..END) is now replaced
|
|
175
|
+
with a freshly constructed one, so repeated merges do not
|
|
176
|
+
accumulate extra newlines.
|
|
177
|
+
"""
|
|
178
|
+
if not user_code:
|
|
179
|
+
return generated_content
|
|
180
|
+
|
|
181
|
+
start = self.markers['file_user_start']
|
|
182
|
+
end = self.markers['file_user_end']
|
|
183
|
+
|
|
184
|
+
# ---- 1. Replace if existing block found ----
|
|
185
|
+
result, count = self._replace_block(
|
|
186
|
+
generated_content, start, end, user_code,
|
|
187
|
+
)
|
|
188
|
+
if count:
|
|
189
|
+
self._log_debug("Replaced file user code")
|
|
190
|
+
return result
|
|
191
|
+
|
|
192
|
+
# ---- 2. No block: insert new one after includes ----
|
|
193
|
+
canonical = f"{start}\n{user_code}\n{end}"
|
|
194
|
+
lines = generated_content.split('\n')
|
|
195
|
+
result_lines = []
|
|
196
|
+
injected = False
|
|
197
|
+
last_include_idx = -1
|
|
198
|
+
|
|
199
|
+
for i, line in enumerate(lines):
|
|
200
|
+
if line.startswith('#include'):
|
|
201
|
+
last_include_idx = i
|
|
202
|
+
|
|
203
|
+
if last_include_idx >= 0:
|
|
204
|
+
for i, line in enumerate(lines):
|
|
205
|
+
result_lines.append(line)
|
|
206
|
+
if i == last_include_idx:
|
|
207
|
+
result_lines.append("")
|
|
208
|
+
result_lines.append(canonical)
|
|
209
|
+
injected = True
|
|
210
|
+
|
|
211
|
+
if not injected:
|
|
212
|
+
result_lines.insert(0, canonical)
|
|
213
|
+
|
|
214
|
+
self._log_debug("Inserted new file user code block")
|
|
215
|
+
return '\n'.join(result_lines)
|
|
216
|
+
|
|
217
|
+
def inject_func_user_code(self, generated_content: str,
|
|
218
|
+
func_name: str, user_code: str) -> str:
|
|
219
|
+
"""
|
|
220
|
+
Replace existing per-function marker block with user_code.
|
|
221
|
+
|
|
222
|
+
- Assumes the generated code already has the marker; replaces
|
|
223
|
+
the content between them.
|
|
224
|
+
- If the marker is missing, does nothing (no new insertion).
|
|
225
|
+
"""
|
|
226
|
+
if not user_code:
|
|
227
|
+
return generated_content
|
|
228
|
+
|
|
229
|
+
start = self.markers['func_user_start'].format(func_name=func_name)
|
|
230
|
+
end = self.markers['func_user_end'].format(func_name=func_name)
|
|
231
|
+
|
|
232
|
+
result, count = self._replace_block(
|
|
233
|
+
generated_content, start, end, user_code,
|
|
234
|
+
)
|
|
235
|
+
if count:
|
|
236
|
+
self._log_debug(f"Injected user code for function {func_name}")
|
|
237
|
+
else:
|
|
238
|
+
self._log_debug(
|
|
239
|
+
f"Marker not found for function {func_name}", 'warning'
|
|
240
|
+
)
|
|
241
|
+
return result
|
|
242
|
+
|
|
243
|
+
def inject_file_tail_user_code(self, generated_content: str,
|
|
244
|
+
user_code: str) -> str:
|
|
245
|
+
"""Inject user code into the file-tail marker (replace)."""
|
|
246
|
+
if not user_code:
|
|
247
|
+
return generated_content
|
|
248
|
+
|
|
249
|
+
start = self.markers['file_tail_user_start']
|
|
250
|
+
end = self.markers['file_tail_user_end']
|
|
251
|
+
|
|
252
|
+
result, count = self._replace_block(
|
|
253
|
+
generated_content, start, end, user_code,
|
|
254
|
+
)
|
|
255
|
+
if count:
|
|
256
|
+
self._log_debug("Injected tail user code")
|
|
257
|
+
else:
|
|
258
|
+
self._log_debug("Tail marker not found", 'warning')
|
|
259
|
+
return result
|
|
260
|
+
|
|
261
|
+
# ===== Merge =====
|
|
262
|
+
def merge_file(self, generated_content: str,
|
|
263
|
+
existing_content: Optional[str]) -> str:
|
|
264
|
+
"""Merge generated code with existing code"""
|
|
265
|
+
if existing_content is None or not existing_content.strip():
|
|
266
|
+
self._log_debug("No existing code, using generated code as-is")
|
|
267
|
+
return generated_content
|
|
268
|
+
|
|
269
|
+
self._log_debug("Starting merge")
|
|
270
|
+
|
|
271
|
+
# Extract user codes
|
|
272
|
+
file_user_code = self.extract_file_user_code(existing_content)
|
|
273
|
+
func_user_codes = self.extract_all_func_user_codes(existing_content)
|
|
274
|
+
tail_user_code = self.extract_file_tail_user_code(existing_content)
|
|
275
|
+
|
|
276
|
+
self._log_debug(f"File user code: {len(file_user_code)} chars")
|
|
277
|
+
self._log_debug(f"Function user codes: {len(func_user_codes)} funcs")
|
|
278
|
+
self._log_debug(f"Tail user code: {len(tail_user_code)} chars")
|
|
279
|
+
|
|
280
|
+
# Inject user codes into generated code
|
|
281
|
+
result = generated_content
|
|
282
|
+
|
|
283
|
+
result = self.inject_file_user_code(result, file_user_code)
|
|
284
|
+
|
|
285
|
+
for func_name, user_code in func_user_codes.items():
|
|
286
|
+
result = self.inject_func_user_code(
|
|
287
|
+
result, func_name, user_code
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
result = self.inject_file_tail_user_code(result, tail_user_code)
|
|
291
|
+
|
|
292
|
+
return result
|
|
293
|
+
|
|
294
|
+
# Folder structure aware version
|
|
295
|
+
def merge_all_files(self, generated_files: Dict[str, str],
|
|
296
|
+
existing_dir: str,
|
|
297
|
+
path_resolver: Optional[Callable] = None,
|
|
298
|
+
layer_name: str = '') -> Dict[str, str]:
|
|
299
|
+
"""
|
|
300
|
+
Merge all files (folder structure aware).
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
generated_files: generated file dictionary
|
|
304
|
+
existing_dir: base directory for existing file lookup
|
|
305
|
+
path_resolver: function returning relative path
|
|
306
|
+
(filename, layer_name) -> rel_path
|
|
307
|
+
If None, use filename as-is
|
|
308
|
+
layer_name: layer name (for by_layer)
|
|
309
|
+
"""
|
|
310
|
+
self._log_debug(f"Starting merge all files: {existing_dir}")
|
|
311
|
+
merged_files = {}
|
|
312
|
+
|
|
313
|
+
for filename, generated_content in generated_files.items():
|
|
314
|
+
# Path resolution
|
|
315
|
+
if path_resolver is not None:
|
|
316
|
+
try:
|
|
317
|
+
rel_path = path_resolver(filename, layer_name)
|
|
318
|
+
except Exception as e:
|
|
319
|
+
self._log_debug(
|
|
320
|
+
f"path_resolver failed for {filename}: {e}",
|
|
321
|
+
'warning'
|
|
322
|
+
)
|
|
323
|
+
rel_path = filename
|
|
324
|
+
else:
|
|
325
|
+
rel_path = filename
|
|
326
|
+
|
|
327
|
+
existing_path = os.path.join(existing_dir, rel_path)
|
|
328
|
+
|
|
329
|
+
if os.path.exists(existing_path):
|
|
330
|
+
self._log_debug(f"Existing file found: {existing_path}")
|
|
331
|
+
with open(existing_path, 'r', encoding='utf-8') as f:
|
|
332
|
+
existing_content = f.read()
|
|
333
|
+
else:
|
|
334
|
+
self._log_debug(f"No existing file: {existing_path}")
|
|
335
|
+
existing_content = None
|
|
336
|
+
|
|
337
|
+
merged_files[filename] = self.merge_file(
|
|
338
|
+
generated_content, existing_content,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
return merged_files
|
|
342
|
+
|
|
343
|
+
# ===== Marker existence =====
|
|
344
|
+
def has_user_code(self, content: str) -> bool:
|
|
345
|
+
"""Whether the content contains user code"""
|
|
346
|
+
return self.markers['file_user_start'] in content
|
|
347
|
+
|
|
348
|
+
def has_func_user_code(self, content: str, func_name: str) -> bool:
|
|
349
|
+
"""Whether a specific function contains user code"""
|
|
350
|
+
start = self.markers['func_user_start'].format(func_name=func_name)
|
|
351
|
+
return start in content
|
|
352
|
+
|
|
353
|
+
def get_user_code_summary(self, content: str) -> Dict[str, int]:
|
|
354
|
+
"""Get user code summary"""
|
|
355
|
+
summary = {
|
|
356
|
+
'file_user_code': 0,
|
|
357
|
+
'func_user_codes': 0,
|
|
358
|
+
'file_tail_user_code': 0,
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if self.has_user_code(content):
|
|
362
|
+
file_code = self.extract_file_user_code(content)
|
|
363
|
+
summary['file_user_code'] = len(file_code)
|
|
364
|
+
|
|
365
|
+
func_codes = self.extract_all_func_user_codes(content)
|
|
366
|
+
summary['func_user_codes'] = len(func_codes)
|
|
367
|
+
|
|
368
|
+
tail_code = self.extract_file_tail_user_code(content)
|
|
369
|
+
summary['file_tail_user_code'] = len(tail_code)
|
|
370
|
+
|
|
371
|
+
return summary
|