pycforge 0.15.2__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.
Files changed (221) hide show
  1. pycforge/__init__.py +8 -0
  2. pycforge/__main__.py +2 -0
  3. pycforge/_version.py +1 -0
  4. pycforge/converter/__init__.py +0 -0
  5. pycforge/converter/analysis/__init__.py +5 -0
  6. pycforge/converter/analysis/functions.py +723 -0
  7. pycforge/converter/analysis/model.py +158 -0
  8. pycforge/converter/analysis/planning.py +464 -0
  9. pycforge/converter/analysis/stage.py +1976 -0
  10. pycforge/converter/analysis/symbols.py +237 -0
  11. pycforge/converter/analysis/validation.py +2166 -0
  12. pycforge/converter/c_output/__init__.py +2 -0
  13. pycforge/converter/c_output/conformance.py +297 -0
  14. pycforge/converter/c_output/renderer.py +220 -0
  15. pycforge/converter/conditional_regions/__init__.py +71 -0
  16. pycforge/converter/conditional_regions/analysis.py +481 -0
  17. pycforge/converter/conditional_regions/lowering.py +579 -0
  18. pycforge/converter/conditional_regions/model.py +243 -0
  19. pycforge/converter/conditional_regions/validation.py +690 -0
  20. pycforge/converter/containers/__init__.py +19 -0
  21. pycforge/converter/containers/analysis.py +542 -0
  22. pycforge/converter/containers/lowering.py +290 -0
  23. pycforge/converter/containers/model.py +146 -0
  24. pycforge/converter/contracts/__init__.py +0 -0
  25. pycforge/converter/contracts/configuration.py +178 -0
  26. pycforge/converter/contracts/identifiers.py +68 -0
  27. pycforge/converter/contracts/versions.py +71 -0
  28. pycforge/converter/core/__init__.py +0 -0
  29. pycforge/converter/core/artifact_io.py +87 -0
  30. pycforge/converter/core/cancellation.py +7 -0
  31. pycforge/converter/core/canonicalization.py +254 -0
  32. pycforge/converter/core/context.py +13 -0
  33. pycforge/converter/core/diagnostics.py +95 -0
  34. pycforge/converter/core/enums.py +23 -0
  35. pycforge/converter/core/fingerprint.py +22 -0
  36. pycforge/converter/core/pipeline.py +8 -0
  37. pycforge/converter/core/progress.py +25 -0
  38. pycforge/converter/core/request.py +50 -0
  39. pycforge/converter/core/resource_policy.py +31 -0
  40. pycforge/converter/core/result.py +23 -0
  41. pycforge/converter/core/serialization.py +36 -0
  42. pycforge/converter/core/stage.py +10 -0
  43. pycforge/converter/core/stage_artifact.py +80 -0
  44. pycforge/converter/core/stage_outcome.py +15 -0
  45. pycforge/converter/decision_trace/__init__.py +0 -0
  46. pycforge/converter/decision_trace/recorder.py +52 -0
  47. pycforge/converter/facade.py +459 -0
  48. pycforge/converter/frontend/__init__.py +4 -0
  49. pycforge/converter/frontend/normalizer.py +59 -0
  50. pycforge/converter/frontend/parser.py +20 -0
  51. pycforge/converter/frontend/source_document.py +93 -0
  52. pycforge/converter/frontend/stages.py +518 -0
  53. pycforge/converter/frontend/validation.py +76 -0
  54. pycforge/converter/io/__init__.py +0 -0
  55. pycforge/converter/io/atomic_writer.py +33 -0
  56. pycforge/converter/ir/__init__.py +1 -0
  57. pycforge/converter/ir/c_ir/__init__.py +4 -0
  58. pycforge/converter/ir/c_ir/builder.py +26 -0
  59. pycforge/converter/ir/c_ir/model.py +318 -0
  60. pycforge/converter/ir/c_ir/serialization.py +88 -0
  61. pycforge/converter/ir/c_ir/validation.py +834 -0
  62. pycforge/converter/ir/python_ir/__init__.py +17 -0
  63. pycforge/converter/ir/python_ir/nodes.py +86 -0
  64. pycforge/converter/keyword_calls/__init__.py +68 -0
  65. pycforge/converter/keyword_calls/analysis.py +458 -0
  66. pycforge/converter/keyword_calls/lowering.py +271 -0
  67. pycforge/converter/keyword_calls/model.py +304 -0
  68. pycforge/converter/keyword_calls/validation.py +802 -0
  69. pycforge/converter/keyword_only_calls/__init__.py +68 -0
  70. pycforge/converter/keyword_only_calls/analysis.py +640 -0
  71. pycforge/converter/keyword_only_calls/lowering.py +376 -0
  72. pycforge/converter/keyword_only_calls/model.py +328 -0
  73. pycforge/converter/keyword_only_calls/validation.py +1357 -0
  74. pycforge/converter/lowering.py +1000 -0
  75. pycforge/converter/modules/__init__.py +13 -0
  76. pycforge/converter/modules/analysis.py +1250 -0
  77. pycforge/converter/modules/lowering.py +200 -0
  78. pycforge/converter/modules/model.py +73 -0
  79. pycforge/converter/modules/stage.py +192 -0
  80. pycforge/converter/numeric_semantics/__init__.py +23 -0
  81. pycforge/converter/numeric_semantics/analysis.py +420 -0
  82. pycforge/converter/numeric_semantics/lowering.py +153 -0
  83. pycforge/converter/numeric_semantics/model.py +123 -0
  84. pycforge/converter/records/__init__.py +34 -0
  85. pycforge/converter/records/analysis.py +1394 -0
  86. pycforge/converter/records/lowering.py +393 -0
  87. pycforge/converter/records/model.py +319 -0
  88. pycforge/converter/rules/__init__.py +3 -0
  89. pycforge/converter/support_templates/__init__.py +49 -0
  90. pycforge/converter/support_templates/assembly.py +106 -0
  91. pycforge/converter/support_templates/factories.py +348 -0
  92. pycforge/converter/support_templates/model.py +159 -0
  93. pycforge/converter/support_templates/registry.py +388 -0
  94. pycforge/converter/telemetry/__init__.py +0 -0
  95. pycforge/converter/telemetry/sink.py +39 -0
  96. pycforge/ide/__init__.py +19 -0
  97. pycforge/ide/__main__.py +5 -0
  98. pycforge/ide/_worker_event_codec.py +276 -0
  99. pycforge/ide/_worker_protocol_json.py +197 -0
  100. pycforge/ide/_worker_protocol_types.py +90 -0
  101. pycforge/ide/_worker_request_codec.py +200 -0
  102. pycforge/ide/_worker_request_data.py +260 -0
  103. pycforge/ide/_worker_result_data.py +343 -0
  104. pycforge/ide/action_contract.py +469 -0
  105. pycforge/ide/action_validation.py +229 -0
  106. pycforge/ide/command_palette.py +223 -0
  107. pycforge/ide/controller.py +904 -0
  108. pycforge/ide/controller_conversion.py +546 -0
  109. pycforge/ide/controller_io.py +205 -0
  110. pycforge/ide/editor.py +951 -0
  111. pycforge/ide/editor_commands_qt.py +274 -0
  112. pycforge/ide/editor_lexical.py +43 -0
  113. pycforge/ide/editor_sidebars.py +236 -0
  114. pycforge/ide/editor_syntax.py +243 -0
  115. pycforge/ide/find_replace.py +535 -0
  116. pycforge/ide/icons.py +91 -0
  117. pycforge/ide/io_service.py +196 -0
  118. pycforge/ide/model.py +164 -0
  119. pycforge/ide/panels.py +903 -0
  120. pycforge/ide/positions.py +220 -0
  121. pycforge/ide/process_worker.py +130 -0
  122. pycforge/ide/qt.py +230 -0
  123. pycforge/ide/qt_actions.py +418 -0
  124. pycforge/ide/qt_close.py +169 -0
  125. pycforge/ide/qt_command_palette.py +297 -0
  126. pycforge/ide/qt_contract.py +220 -0
  127. pycforge/ide/qt_documents.py +590 -0
  128. pycforge/ide/qt_editor_buffers.py +113 -0
  129. pycforge/ide/qt_editor_surfaces.py +112 -0
  130. pycforge/ide/qt_menus.py +324 -0
  131. pycforge/ide/qt_projection.py +500 -0
  132. pycforge/ide/qt_shell.py +348 -0
  133. pycforge/ide/qt_shell_interactions.py +320 -0
  134. pycforge/ide/qt_state.py +393 -0
  135. pycforge/ide/qt_workspace_features.py +398 -0
  136. pycforge/ide/qt_workspace_navigation.py +208 -0
  137. pycforge/ide/qt_workspace_observers.py +173 -0
  138. pycforge/ide/qt_workspace_panels.py +545 -0
  139. pycforge/ide/qt_workspace_widgets.py +228 -0
  140. pycforge/ide/resources/icons/about.svg +1 -0
  141. pycforge/ide/resources/icons/add-document.svg +1 -0
  142. pycforge/ide/resources/icons/brand-mark.svg +1 -0
  143. pycforge/ide/resources/icons/bundle-search.svg +1 -0
  144. pycforge/ide/resources/icons/cancel.svg +1 -0
  145. pycforge/ide/resources/icons/check.svg +1 -0
  146. pycforge/ide/resources/icons/chevron-right.svg +1 -0
  147. pycforge/ide/resources/icons/close.svg +1 -0
  148. pycforge/ide/resources/icons/collapse-all.svg +1 -0
  149. pycforge/ide/resources/icons/command-palette.svg +1 -0
  150. pycforge/ide/resources/icons/convert.svg +1 -0
  151. pycforge/ide/resources/icons/copy.svg +1 -0
  152. pycforge/ide/resources/icons/cut.svg +1 -0
  153. pycforge/ide/resources/icons/decision-trace.svg +1 -0
  154. pycforge/ide/resources/icons/details.svg +1 -0
  155. pycforge/ide/resources/icons/diagnostics.svg +1 -0
  156. pycforge/ide/resources/icons/duplicate-line.svg +1 -0
  157. pycforge/ide/resources/icons/exit.svg +1 -0
  158. pycforge/ide/resources/icons/expand-all.svg +1 -0
  159. pycforge/ide/resources/icons/export.svg +1 -0
  160. pycforge/ide/resources/icons/find.svg +1 -0
  161. pycforge/ide/resources/icons/go-to-line.svg +1 -0
  162. pycforge/ide/resources/icons/go-to-output.svg +1 -0
  163. pycforge/ide/resources/icons/go-to-source.svg +1 -0
  164. pycforge/ide/resources/icons/history.svg +1 -0
  165. pycforge/ide/resources/icons/indent.svg +1 -0
  166. pycforge/ide/resources/icons/link-c.svg +1 -0
  167. pycforge/ide/resources/icons/mappings.svg +1 -0
  168. pycforge/ide/resources/icons/module.svg +1 -0
  169. pycforge/ide/resources/icons/move-down.svg +1 -0
  170. pycforge/ide/resources/icons/move-line-down.svg +1 -0
  171. pycforge/ide/resources/icons/move-line-up.svg +1 -0
  172. pycforge/ide/resources/icons/move-up.svg +1 -0
  173. pycforge/ide/resources/icons/next.svg +1 -0
  174. pycforge/ide/resources/icons/open.svg +1 -0
  175. pycforge/ide/resources/icons/outdent.svg +1 -0
  176. pycforge/ide/resources/icons/outline.svg +1 -0
  177. pycforge/ide/resources/icons/paste.svg +1 -0
  178. pycforge/ide/resources/icons/previous.svg +1 -0
  179. pycforge/ide/resources/icons/primary-module.svg +1 -0
  180. pycforge/ide/resources/icons/redo.svg +1 -0
  181. pycforge/ide/resources/icons/remove-document.svg +1 -0
  182. pycforge/ide/resources/icons/replace.svg +1 -0
  183. pycforge/ide/resources/icons/save-as.svg +1 -0
  184. pycforge/ide/resources/icons/save.svg +1 -0
  185. pycforge/ide/resources/icons/select-all.svg +1 -0
  186. pycforge/ide/resources/icons/settings.svg +1 -0
  187. pycforge/ide/resources/icons/show-c.svg +1 -0
  188. pycforge/ide/resources/icons/split-view.svg +1 -0
  189. pycforge/ide/resources/icons/summary.svg +1 -0
  190. pycforge/ide/resources/icons/telemetry.svg +1 -0
  191. pycforge/ide/resources/icons/toggle-comment.svg +1 -0
  192. pycforge/ide/resources/icons/toggle-fold.svg +1 -0
  193. pycforge/ide/resources/icons/undo.svg +1 -0
  194. pycforge/ide/resources/icons/whitespace.svg +1 -0
  195. pycforge/ide/revisions.py +400 -0
  196. pycforge/ide/search_service.py +280 -0
  197. pycforge/ide/session_history.py +150 -0
  198. pycforge/ide/source_editing.py +417 -0
  199. pycforge/ide/source_structure.py +558 -0
  200. pycforge/ide/source_structure_async.py +191 -0
  201. pycforge/ide/supervisor.py +503 -0
  202. pycforge/ide/theme.py +119 -0
  203. pycforge/ide/theme_stylesheet.py +598 -0
  204. pycforge/ide/theme_workspace_stylesheet.py +155 -0
  205. pycforge/ide/visual_tokens.py +142 -0
  206. pycforge/ide/worker_protocol.py +80 -0
  207. pycforge/ide/workspace_search.py +462 -0
  208. pycforge/ide/workspace_session.py +256 -0
  209. pycforge/laboratory/__init__.py +6 -0
  210. pycforge/laboratory/audits.py +2244 -0
  211. pycforge/laboratory/checkpoint_e.py +2685 -0
  212. pycforge/laboratory/cli.py +119 -0
  213. pycforge/laboratory/exit_codes.py +11 -0
  214. pycforge/laboratory/keyword_audit.py +661 -0
  215. pycforge/laboratory/keyword_only_audit.py +481 -0
  216. pycforge-0.15.2.dist-info/METADATA +209 -0
  217. pycforge-0.15.2.dist-info/RECORD +221 -0
  218. pycforge-0.15.2.dist-info/WHEEL +5 -0
  219. pycforge-0.15.2.dist-info/entry_points.txt +5 -0
  220. pycforge-0.15.2.dist-info/licenses/LICENSE +674 -0
  221. pycforge-0.15.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,256 @@
1
+ """Immutable, presentation-only document tab and split-pane state.
2
+
3
+ This module deliberately stores document identifiers only. It cannot retain
4
+ source text, paths, conversion results, observer data, or other semantic
5
+ artifacts. The controller remains the authority for the exact current set and
6
+ order of SourceBundle documents.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass, replace
12
+ from enum import Enum
13
+ from typing import Iterable
14
+
15
+
16
+ MAX_EDITOR_PANES = 2
17
+ MAX_SESSION_DOCUMENTS = 64
18
+ MAX_DOCUMENT_ID_CHARS = 256
19
+
20
+
21
+ class SplitOrientation(str, Enum):
22
+ HORIZONTAL = "horizontal"
23
+ VERTICAL = "vertical"
24
+
25
+
26
+ def _document_ids(values: Iterable[str]) -> tuple[str, ...]:
27
+ if isinstance(values, (str, bytes)):
28
+ raise TypeError("document IDs must be an iterable of complete IDs")
29
+ records = tuple(values)
30
+ if not records:
31
+ raise ValueError("workspace session requires a document")
32
+ if len(records) > MAX_SESSION_DOCUMENTS:
33
+ raise ValueError(
34
+ f"workspace session exceeds {MAX_SESSION_DOCUMENTS} documents"
35
+ )
36
+ for value in records:
37
+ if (
38
+ not isinstance(value, str)
39
+ or not value
40
+ or len(value) > MAX_DOCUMENT_ID_CHARS
41
+ or any(ord(character) < 32 for character in value)
42
+ ):
43
+ raise ValueError("document IDs must be bounded printable text")
44
+ if len(records) != len(set(records)):
45
+ raise ValueError("workspace session document IDs must be unique")
46
+ return records
47
+
48
+
49
+ def _orientation(value: SplitOrientation | str) -> SplitOrientation:
50
+ try:
51
+ return SplitOrientation(value)
52
+ except (TypeError, ValueError) as exc:
53
+ raise ValueError("split orientation is invalid") from exc
54
+
55
+
56
+ @dataclass(frozen=True, slots=True)
57
+ class WorkspaceSession:
58
+ """Exact tab order and at most two document-ID-only editor panes."""
59
+
60
+ document_ids: tuple[str, ...]
61
+ pane_document_ids: tuple[str, ...]
62
+ active_pane: int = 0
63
+ split_orientation: SplitOrientation = SplitOrientation.HORIZONTAL
64
+
65
+ def __post_init__(self) -> None:
66
+ documents = _document_ids(self.document_ids)
67
+ if documents != self.document_ids:
68
+ raise TypeError("document IDs must be a tuple")
69
+ panes = self.pane_document_ids
70
+ if not isinstance(panes, tuple) or not 1 <= len(panes) <= MAX_EDITOR_PANES:
71
+ raise ValueError("workspace session must contain one or two panes")
72
+ if any(document_id not in documents for document_id in panes):
73
+ raise ValueError("every pane must show a current document")
74
+ if (
75
+ isinstance(self.active_pane, bool)
76
+ or not isinstance(self.active_pane, int)
77
+ or not 0 <= self.active_pane < len(panes)
78
+ ):
79
+ raise ValueError("active pane is outside the session")
80
+ if not isinstance(self.split_orientation, SplitOrientation):
81
+ raise TypeError("split orientation must be SplitOrientation")
82
+
83
+ @property
84
+ def active_document_id(self) -> str:
85
+ return self.pane_document_ids[self.active_pane]
86
+
87
+ @property
88
+ def is_split(self) -> bool:
89
+ return len(self.pane_document_ids) == MAX_EDITOR_PANES
90
+
91
+
92
+ def create_workspace_session(
93
+ document_ids: Iterable[str],
94
+ active_document_id: str | None = None,
95
+ ) -> WorkspaceSession:
96
+ """Create a one-pane session over the exact supplied tab order."""
97
+
98
+ records = _document_ids(document_ids)
99
+ active = records[0] if active_document_id is None else active_document_id
100
+ if active not in records:
101
+ raise ValueError("active document is not a current document")
102
+ return WorkspaceSession(records, (active,))
103
+
104
+
105
+ def activate_document(
106
+ session: WorkspaceSession,
107
+ document_id: str,
108
+ *,
109
+ pane: int | None = None,
110
+ ) -> WorkspaceSession:
111
+ """Show a current document in one pane and make that pane active."""
112
+
113
+ if not isinstance(session, WorkspaceSession):
114
+ raise TypeError("session must be WorkspaceSession")
115
+ if document_id not in session.document_ids:
116
+ raise ValueError("cannot activate a document outside the current tabs")
117
+ target = session.active_pane if pane is None else pane
118
+ if (
119
+ isinstance(target, bool)
120
+ or not isinstance(target, int)
121
+ or not 0 <= target < len(session.pane_document_ids)
122
+ ):
123
+ raise ValueError("target pane is outside the session")
124
+ panes = list(session.pane_document_ids)
125
+ panes[target] = document_id
126
+ return replace(
127
+ session,
128
+ pane_document_ids=tuple(panes),
129
+ active_pane=target,
130
+ )
131
+
132
+
133
+ def activate_pane(
134
+ session: WorkspaceSession,
135
+ pane: int,
136
+ ) -> WorkspaceSession:
137
+ """Make an existing pane active without changing either pane document."""
138
+
139
+ if not isinstance(session, WorkspaceSession):
140
+ raise TypeError("session must be WorkspaceSession")
141
+ if (
142
+ isinstance(pane, bool)
143
+ or not isinstance(pane, int)
144
+ or not 0 <= pane < len(session.pane_document_ids)
145
+ ):
146
+ raise ValueError("target pane is outside the session")
147
+ return replace(session, active_pane=pane)
148
+
149
+
150
+ def split_session(
151
+ session: WorkspaceSession,
152
+ orientation: SplitOrientation | str,
153
+ *,
154
+ document_id: str | None = None,
155
+ ) -> WorkspaceSession:
156
+ """Open or reorient the second pane, never creating a third pane."""
157
+
158
+ if not isinstance(session, WorkspaceSession):
159
+ raise TypeError("session must be WorkspaceSession")
160
+ resolved = _orientation(orientation)
161
+ target = session.active_document_id if document_id is None else document_id
162
+ if target not in session.document_ids:
163
+ raise ValueError("split document is not a current document")
164
+ if session.is_split:
165
+ panes = list(session.pane_document_ids)
166
+ panes[session.active_pane] = target
167
+ return replace(
168
+ session,
169
+ pane_document_ids=tuple(panes),
170
+ split_orientation=resolved,
171
+ )
172
+ return WorkspaceSession(
173
+ session.document_ids,
174
+ (session.active_document_id, target),
175
+ active_pane=1,
176
+ split_orientation=resolved,
177
+ )
178
+
179
+
180
+ def close_split(
181
+ session: WorkspaceSession,
182
+ *,
183
+ keep_pane: int | None = None,
184
+ ) -> WorkspaceSession:
185
+ """Collapse to one pane, keeping the active pane unless specified."""
186
+
187
+ if not isinstance(session, WorkspaceSession):
188
+ raise TypeError("session must be WorkspaceSession")
189
+ target = session.active_pane if keep_pane is None else keep_pane
190
+ if (
191
+ isinstance(target, bool)
192
+ or not isinstance(target, int)
193
+ or not 0 <= target < len(session.pane_document_ids)
194
+ ):
195
+ raise ValueError("pane to keep is outside the session")
196
+ return WorkspaceSession(
197
+ session.document_ids,
198
+ (session.pane_document_ids[target],),
199
+ active_pane=0,
200
+ split_orientation=session.split_orientation,
201
+ )
202
+
203
+
204
+ def reconcile_session(
205
+ session: WorkspaceSession,
206
+ current_document_ids: Iterable[str],
207
+ *,
208
+ active_document_id: str | None = None,
209
+ ) -> WorkspaceSession:
210
+ """Reconcile tabs and panes to the controller's exact current IDs.
211
+
212
+ Existing pane choices survive when their document still exists. A removed
213
+ pane document is replaced by the requested active document. The active
214
+ pane always shows the controller-selected active document when one is
215
+ supplied.
216
+ """
217
+
218
+ if not isinstance(session, WorkspaceSession):
219
+ raise TypeError("session must be WorkspaceSession")
220
+ current = _document_ids(current_document_ids)
221
+ active = (
222
+ session.active_document_id
223
+ if active_document_id is None
224
+ and session.active_document_id in current
225
+ else current[0]
226
+ if active_document_id is None
227
+ else active_document_id
228
+ )
229
+ if active not in current:
230
+ raise ValueError("active document is not a current document")
231
+ panes = [
232
+ document_id if document_id in current else active
233
+ for document_id in session.pane_document_ids
234
+ ]
235
+ panes[session.active_pane] = active
236
+ return WorkspaceSession(
237
+ current,
238
+ tuple(panes),
239
+ active_pane=session.active_pane,
240
+ split_orientation=session.split_orientation,
241
+ )
242
+
243
+
244
+ __all__ = [
245
+ "MAX_DOCUMENT_ID_CHARS",
246
+ "MAX_EDITOR_PANES",
247
+ "MAX_SESSION_DOCUMENTS",
248
+ "SplitOrientation",
249
+ "WorkspaceSession",
250
+ "activate_document",
251
+ "activate_pane",
252
+ "close_split",
253
+ "create_workspace_session",
254
+ "reconcile_session",
255
+ "split_session",
256
+ ]
@@ -0,0 +1,6 @@
1
+ """Headless PyCForge laboratory audits."""
2
+
3
+ from .keyword_audit import audit_keyword
4
+ from .keyword_only_audit import audit_keyword_only
5
+
6
+ __all__ = ["audit_keyword", "audit_keyword_only"]