modelable 1.0.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.
Potentially problematic release.
This version of modelable might be problematic. Click here for more details.
- modelable/__init__.py +1 -0
- modelable/__main__.py +3 -0
- modelable/_pydantic_py314_compat.py +31 -0
- modelable/cli.py +41 -0
- modelable/commands/__init__.py +1 -0
- modelable/commands/apicurio.py +84 -0
- modelable/commands/codegen.py +241 -0
- modelable/commands/common.py +43 -0
- modelable/commands/compile.py +237 -0
- modelable/commands/create.py +164 -0
- modelable/commands/diff.py +82 -0
- modelable/commands/graph.py +53 -0
- modelable/commands/llm.py +564 -0
- modelable/commands/lsp.py +15 -0
- modelable/commands/runtime.py +37 -0
- modelable/commands/scenario.py +104 -0
- modelable/commands/spec.py +197 -0
- modelable/commands/workspace.py +240 -0
- modelable/compat/__init__.py +11 -0
- modelable/compat/checker.py +179 -0
- modelable/compat/diff.py +169 -0
- modelable/compiler/__init__.py +3 -0
- modelable/compiler/compiler.py +19 -0
- modelable/compiler/workspace.py +346 -0
- modelable/diagnostics/__init__.py +3 -0
- modelable/diagnostics/model.py +27 -0
- modelable/emitters/__init__.py +0 -0
- modelable/emitters/base.py +22 -0
- modelable/emitters/csharp.py +245 -0
- modelable/emitters/dbt_yaml.py +290 -0
- modelable/emitters/diagnostics.py +25 -0
- modelable/emitters/fhir.py +694 -0
- modelable/emitters/fhir_validator.py +36 -0
- modelable/emitters/go.py +334 -0
- modelable/emitters/java.py +264 -0
- modelable/emitters/json_schema.py +458 -0
- modelable/emitters/markdown.py +252 -0
- modelable/emitters/odcs.py +355 -0
- modelable/emitters/openlineage.py +315 -0
- modelable/emitters/openmetadata.py +258 -0
- modelable/emitters/python.py +282 -0
- modelable/emitters/rust.py +643 -0
- modelable/emitters/shapes.py +261 -0
- modelable/emitters/sql.py +266 -0
- modelable/emitters/targets.py +141 -0
- modelable/emitters/typescript.py +352 -0
- modelable/expressions/__init__.py +0 -0
- modelable/expressions/cel.py +547 -0
- modelable/governance/__init__.py +3 -0
- modelable/governance/checker.py +271 -0
- modelable/governance/por.py +46 -0
- modelable/grammar/__init__.py +1 -0
- modelable/grammar/modelable.lark +257 -0
- modelable/graph/__init__.py +5 -0
- modelable/graph/export.py +442 -0
- modelable/llm/__init__.py +43 -0
- modelable/llm/chat.py +255 -0
- modelable/llm/config.py +87 -0
- modelable/llm/context.py +194 -0
- modelable/llm/engine.py +976 -0
- modelable/llm/importers.py +1077 -0
- modelable/llm/provenance.py +84 -0
- modelable/llm/providers.py +182 -0
- modelable/llm/qa.py +126 -0
- modelable/llm/recommendations.py +33 -0
- modelable/llm/redaction.py +19 -0
- modelable/llm/render.py +279 -0
- modelable/llm/update_plan.py +101 -0
- modelable/llm/validation_help.py +10 -0
- modelable/lsp/__init__.py +3 -0
- modelable/lsp/__main__.py +4 -0
- modelable/lsp/code_actions.py +210 -0
- modelable/lsp/completion.py +480 -0
- modelable/lsp/definition.py +343 -0
- modelable/lsp/diagnostics.py +31 -0
- modelable/lsp/document_symbols.py +197 -0
- modelable/lsp/federation.py +261 -0
- modelable/lsp/folding.py +33 -0
- modelable/lsp/formatting.py +64 -0
- modelable/lsp/highlight.py +30 -0
- modelable/lsp/hover.py +370 -0
- modelable/lsp/inlay_hints.py +158 -0
- modelable/lsp/references.py +511 -0
- modelable/lsp/rename.py +564 -0
- modelable/lsp/semantic_tokens.py +412 -0
- modelable/lsp/server.py +370 -0
- modelable/lsp/workspace.py +83 -0
- modelable/lsp/workspace_symbols.py +104 -0
- modelable/parser/__init__.py +94 -0
- modelable/parser/ir.py +451 -0
- modelable/parser/parse.py +47 -0
- modelable/parser/transformer.py +798 -0
- modelable/parser/wire.py +68 -0
- modelable/planner/__init__.py +0 -0
- modelable/planner/lineage.py +91 -0
- modelable/planner/planner.py +134 -0
- modelable/planner/plans.py +122 -0
- modelable/py.typed +0 -0
- modelable/registry/__init__.py +9 -0
- modelable/registry/apicurio.py +166 -0
- modelable/registry/base.py +18 -0
- modelable/registry/factory.py +18 -0
- modelable/registry/index.py +419 -0
- modelable/registry/local.py +26 -0
- modelable/registry/oci.py +22 -0
- modelable/registry/resolver.py +213 -0
- modelable/registry/schema.sql +119 -0
- modelable/registry/signature.py +26 -0
- modelable/release.py +125 -0
- modelable/runtime/__init__.py +5 -0
- modelable/runtime/adapter/__init__.py +17 -0
- modelable/runtime/adapter/base.py +18 -0
- modelable/runtime/adapter/postgres.py +82 -0
- modelable/specs/__init__.py +23 -0
- modelable/specs/tracking.py +220 -0
- modelable/validation/__init__.py +3 -0
- modelable/validation/semantic.py +659 -0
- modelable-1.0.0.dist-info/METADATA +61 -0
- modelable-1.0.0.dist-info/RECORD +122 -0
- modelable-1.0.0.dist-info/WHEEL +4 -0
- modelable-1.0.0.dist-info/entry_points.txt +2 -0
- modelable-1.0.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from lsprotocol import types
|
|
7
|
+
|
|
8
|
+
from modelable.lsp.federation import (
|
|
9
|
+
mirror_domain_names,
|
|
10
|
+
mirror_field_names,
|
|
11
|
+
mirror_model_versions,
|
|
12
|
+
mirror_reference_names,
|
|
13
|
+
)
|
|
14
|
+
from modelable.lsp.workspace import LspWorkspaceIndex
|
|
15
|
+
|
|
16
|
+
_KEYWORDS = [
|
|
17
|
+
"domain",
|
|
18
|
+
"entity",
|
|
19
|
+
"aggregate",
|
|
20
|
+
"event",
|
|
21
|
+
"value",
|
|
22
|
+
"projection",
|
|
23
|
+
"from",
|
|
24
|
+
"join",
|
|
25
|
+
"as",
|
|
26
|
+
"group by",
|
|
27
|
+
]
|
|
28
|
+
_ANNOTATIONS = [
|
|
29
|
+
"@key",
|
|
30
|
+
"@pii",
|
|
31
|
+
"@classification",
|
|
32
|
+
"@deprecated",
|
|
33
|
+
"@owner",
|
|
34
|
+
"@server",
|
|
35
|
+
"@wire",
|
|
36
|
+
]
|
|
37
|
+
_DECL_PATTERN = re.compile(
|
|
38
|
+
r"^\s*(?P<kind>entity|aggregate|event|value|projection)\s+"
|
|
39
|
+
r"(?P<name>[A-Za-z_][A-Za-z0-9_]*)\s*@\s*(?P<version>\d+)"
|
|
40
|
+
)
|
|
41
|
+
_DOMAIN_PATTERN = re.compile(r'^\s*domain\s+(?:"(?P<quoted>[^"]+)"|(?P<name>[A-Za-z_][A-Za-z0-9_]*))')
|
|
42
|
+
_WORD_PREFIX_PATTERN = re.compile(r"[A-Za-z_][A-Za-z0-9_-]*$")
|
|
43
|
+
_ANNOTATION_PATTERN = re.compile(r"(?:^|\s)@[A-Za-z_][A-Za-z0-9_-]*$")
|
|
44
|
+
_REFERENCE_PATTERN = re.compile(r"\b(from|join)\s+[A-Za-z_][A-Za-z0-9_.-]*$")
|
|
45
|
+
_REFERENCE_VERSION_PATTERN = re.compile(
|
|
46
|
+
r"\b(from|join)\s+(?P<domain>[A-Za-z_][A-Za-z0-9_.-]*)\."
|
|
47
|
+
r"(?P<model>[A-Za-z_][A-Za-z0-9_.-]*)\s*@\s*(?P<prefix>\d*)$"
|
|
48
|
+
)
|
|
49
|
+
_IMPORT_VERSION_PATTERN = re.compile(
|
|
50
|
+
r"\bimport\s+domain\s+[A-Za-z_][A-Za-z0-9_.-]*\s+from\s+registry\s+\"[^\"]+\""
|
|
51
|
+
r"\s+at\s+(?P<domain>[A-Za-z_][A-Za-z0-9_.-]*)\."
|
|
52
|
+
r"(?P<model>[A-Za-z_][A-Za-z0-9_.-]*)\s*@\s*(?P<prefix>\d*)$"
|
|
53
|
+
)
|
|
54
|
+
_IMPORT_PIN_MODEL_PATTERN = re.compile(
|
|
55
|
+
r"\bimport\s+domain\s+[A-Za-z_][A-Za-z0-9_.-]*\s+from\s+registry\s+\"[^\"]+\""
|
|
56
|
+
r"\s+at\s+(?P<domain>[A-Za-z_][A-Za-z0-9_.-]*)\.(?P<prefix>[A-Za-z_][A-Za-z0-9_]*)?$"
|
|
57
|
+
)
|
|
58
|
+
_ALIAS_FIELD_PATTERN = re.compile(r"(?P<alias>[A-Za-z_][A-Za-z0-9_]*)\.(?P<prefix>[A-Za-z_][A-Za-z0-9_]*)?$")
|
|
59
|
+
_SOURCE_ALIAS_PATTERN = re.compile(
|
|
60
|
+
r"^\s*(from|join)\s+(?P<domain>[A-Za-z_][A-Za-z0-9_.-]*)\."
|
|
61
|
+
r"(?P<model>[A-Za-z_][A-Za-z0-9_.-]*)\s*@\s*(?P<version>\d+)\s+as\s+"
|
|
62
|
+
r"(?P<alias>[A-Za-z_][A-Za-z0-9_]*)"
|
|
63
|
+
r"(?:\s+on\s+.*)?$"
|
|
64
|
+
)
|
|
65
|
+
_IMPORT_DOMAIN_PATTERN = re.compile(r"\bimport\s+domain\s+[A-Za-z_][A-Za-z0-9_.-]*$")
|
|
66
|
+
_DOMAIN_DECL_PATTERN = re.compile(r"^\s*domain\s+[A-Za-z_][A-Za-z0-9_-]*$")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass(frozen=True)
|
|
70
|
+
class _Scope:
|
|
71
|
+
domain: str
|
|
72
|
+
kind: str
|
|
73
|
+
name: str
|
|
74
|
+
version: int
|
|
75
|
+
line: int
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True)
|
|
79
|
+
class _Candidate:
|
|
80
|
+
label: str
|
|
81
|
+
kind: types.CompletionItemKind | None
|
|
82
|
+
sort_rank: int
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def build_completion(
|
|
86
|
+
index: LspWorkspaceIndex,
|
|
87
|
+
uri: str,
|
|
88
|
+
line: int,
|
|
89
|
+
character: int,
|
|
90
|
+
) -> types.CompletionList:
|
|
91
|
+
source = index.documents.get(uri)
|
|
92
|
+
workspace = index.workspace
|
|
93
|
+
if source is None or workspace is None:
|
|
94
|
+
return _empty_completion()
|
|
95
|
+
|
|
96
|
+
lines = source.text.splitlines()
|
|
97
|
+
if line < 0 or line >= len(lines):
|
|
98
|
+
return _empty_completion()
|
|
99
|
+
|
|
100
|
+
text_line = lines[line]
|
|
101
|
+
cursor = max(character, 0)
|
|
102
|
+
before_cursor = text_line[:cursor]
|
|
103
|
+
prefix = _completion_prefix(before_cursor)
|
|
104
|
+
scope = _current_scope(source.text, line)
|
|
105
|
+
|
|
106
|
+
if _version_context(before_cursor):
|
|
107
|
+
candidates = _version_candidates(index, before_cursor, prefix)
|
|
108
|
+
elif _annotation_context(before_cursor):
|
|
109
|
+
candidates = _annotation_candidates(prefix)
|
|
110
|
+
elif _import_pin_model_context(before_cursor):
|
|
111
|
+
candidates = _import_pin_model_candidates(index, before_cursor, prefix)
|
|
112
|
+
elif _domain_context(before_cursor):
|
|
113
|
+
candidates = _domain_candidates(workspace, prefix)
|
|
114
|
+
candidates.extend(_mirror_domain_candidates(index, prefix))
|
|
115
|
+
elif _reference_context(before_cursor):
|
|
116
|
+
candidates = _workspace_reference_candidates(workspace, prefix)
|
|
117
|
+
candidates.extend(_mirror_reference_candidates(index, prefix))
|
|
118
|
+
elif _alias_context(before_cursor):
|
|
119
|
+
candidates = _alias_field_candidates(index, source.text, scope, line, before_cursor, prefix)
|
|
120
|
+
elif scope is not None and line > scope.line:
|
|
121
|
+
candidates = _field_candidates(workspace, scope, prefix)
|
|
122
|
+
else:
|
|
123
|
+
candidates = _keyword_candidates(prefix)
|
|
124
|
+
candidates.extend(_mirror_domain_candidates(index, prefix))
|
|
125
|
+
|
|
126
|
+
return types.CompletionList(
|
|
127
|
+
is_incomplete=False,
|
|
128
|
+
items=[_to_completion_item(candidate, index) for index, candidate in enumerate(_dedupe(candidates))],
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _empty_completion() -> types.CompletionList:
|
|
133
|
+
return types.CompletionList(is_incomplete=False, items=[])
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _annotation_context(before_cursor: str) -> bool:
|
|
137
|
+
stripped = before_cursor.rstrip()
|
|
138
|
+
return stripped.endswith("@") or bool(_ANNOTATION_PATTERN.search(stripped))
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _domain_context(before_cursor: str) -> bool:
|
|
142
|
+
stripped = before_cursor.rstrip()
|
|
143
|
+
return (
|
|
144
|
+
before_cursor.endswith("domain ")
|
|
145
|
+
or before_cursor.endswith("import domain ")
|
|
146
|
+
or bool(_IMPORT_DOMAIN_PATTERN.search(stripped))
|
|
147
|
+
or bool(_DOMAIN_DECL_PATTERN.match(stripped))
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _reference_context(before_cursor: str) -> bool:
|
|
152
|
+
stripped = before_cursor.rstrip()
|
|
153
|
+
return (
|
|
154
|
+
before_cursor.endswith("from ") or before_cursor.endswith("join ") or bool(_REFERENCE_PATTERN.search(stripped))
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _keyword_candidates(prefix: str) -> list[_Candidate]:
|
|
159
|
+
return _filtered_candidates(
|
|
160
|
+
[
|
|
161
|
+
_Candidate(label=keyword, kind=types.CompletionItemKind.Keyword, sort_rank=index + 10)
|
|
162
|
+
for index, keyword in enumerate(_KEYWORDS)
|
|
163
|
+
],
|
|
164
|
+
prefix,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _annotation_candidates(prefix: str) -> list[_Candidate]:
|
|
169
|
+
return _filtered_candidates(
|
|
170
|
+
[
|
|
171
|
+
_Candidate(label=annotation, kind=types.CompletionItemKind.Keyword, sort_rank=index + 10)
|
|
172
|
+
for index, annotation in enumerate(_ANNOTATIONS)
|
|
173
|
+
],
|
|
174
|
+
prefix,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _domain_candidates(workspace, prefix: str) -> list[_Candidate]:
|
|
179
|
+
names = sorted({domain.name for domain in workspace.mdl.domains})
|
|
180
|
+
return _filtered_candidates(
|
|
181
|
+
[
|
|
182
|
+
_Candidate(label=name, kind=types.CompletionItemKind.Module, sort_rank=index + 20)
|
|
183
|
+
for index, name in enumerate(names)
|
|
184
|
+
],
|
|
185
|
+
prefix,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _workspace_reference_candidates(workspace, prefix: str) -> list[_Candidate]:
|
|
190
|
+
names: list[_Candidate] = []
|
|
191
|
+
for domain in workspace.mdl.domains:
|
|
192
|
+
for model_name in sorted(domain.models):
|
|
193
|
+
names.append(
|
|
194
|
+
_Candidate(
|
|
195
|
+
label=f"{domain.name}.{model_name}",
|
|
196
|
+
kind=types.CompletionItemKind.Class,
|
|
197
|
+
sort_rank=100,
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
for projection_name in sorted(domain.projections):
|
|
201
|
+
names.append(
|
|
202
|
+
_Candidate(
|
|
203
|
+
label=f"{domain.name}.{projection_name}",
|
|
204
|
+
kind=types.CompletionItemKind.Class,
|
|
205
|
+
sort_rank=120,
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
names.sort(key=lambda candidate: (candidate.sort_rank, candidate.label.lower(), candidate.label))
|
|
209
|
+
return _filtered_candidates(names, prefix)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _mirror_domain_candidates(index: LspWorkspaceIndex, prefix: str) -> list[_Candidate]:
|
|
213
|
+
candidates: list[_Candidate] = []
|
|
214
|
+
for domain_name in mirror_domain_names(index):
|
|
215
|
+
candidates.append(_Candidate(label=domain_name, kind=types.CompletionItemKind.Module, sort_rank=40))
|
|
216
|
+
return _filtered_candidates(candidates, prefix)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def _mirror_reference_candidates(index: LspWorkspaceIndex, prefix: str) -> list[_Candidate]:
|
|
220
|
+
candidates: list[_Candidate] = []
|
|
221
|
+
for domain_name, model_name in mirror_reference_names(index):
|
|
222
|
+
candidates.append(
|
|
223
|
+
_Candidate(
|
|
224
|
+
label=f"{domain_name}.{model_name}",
|
|
225
|
+
kind=types.CompletionItemKind.Class,
|
|
226
|
+
sort_rank=150,
|
|
227
|
+
)
|
|
228
|
+
)
|
|
229
|
+
return _filtered_candidates(candidates, prefix)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _alias_field_candidates(
|
|
233
|
+
index: LspWorkspaceIndex,
|
|
234
|
+
text: str,
|
|
235
|
+
scope: _Scope | None,
|
|
236
|
+
line: int,
|
|
237
|
+
before_cursor: str,
|
|
238
|
+
prefix: str,
|
|
239
|
+
) -> list[_Candidate]:
|
|
240
|
+
if scope is None:
|
|
241
|
+
return []
|
|
242
|
+
|
|
243
|
+
alias_match = _alias_name(before_cursor)
|
|
244
|
+
if alias_match is None:
|
|
245
|
+
return []
|
|
246
|
+
|
|
247
|
+
alias, alias_prefix = alias_match
|
|
248
|
+
reference = _projection_reference_for_alias(text, scope, line, alias)
|
|
249
|
+
if reference is None:
|
|
250
|
+
return []
|
|
251
|
+
|
|
252
|
+
domain_name, model_name, version = reference
|
|
253
|
+
fields = _mirror_or_workspace_fields(index, domain_name, model_name, version)
|
|
254
|
+
candidates = [
|
|
255
|
+
_Candidate(label=field_name, kind=types.CompletionItemKind.Field, sort_rank=index + 30)
|
|
256
|
+
for index, field_name in enumerate(fields)
|
|
257
|
+
]
|
|
258
|
+
return _filtered_candidates(candidates, alias_prefix or prefix)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _version_candidates(index: LspWorkspaceIndex, before_cursor: str, prefix: str) -> list[_Candidate]:
|
|
262
|
+
context = _version_context(before_cursor)
|
|
263
|
+
if context is None:
|
|
264
|
+
return []
|
|
265
|
+
|
|
266
|
+
domain_name, model_name = context
|
|
267
|
+
candidates: list[_Candidate] = []
|
|
268
|
+
version_prefix = _version_prefix(before_cursor)
|
|
269
|
+
for mirror_domain, mirror_model, version in mirror_model_versions(index):
|
|
270
|
+
if mirror_domain == domain_name and mirror_model == model_name:
|
|
271
|
+
if version_prefix and not str(version).startswith(version_prefix):
|
|
272
|
+
continue
|
|
273
|
+
candidates.append(
|
|
274
|
+
_Candidate(
|
|
275
|
+
label=str(version),
|
|
276
|
+
kind=types.CompletionItemKind.Value,
|
|
277
|
+
sort_rank=160,
|
|
278
|
+
)
|
|
279
|
+
)
|
|
280
|
+
return _filtered_candidates(candidates, prefix)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _field_candidates(workspace, scope: _Scope, prefix: str) -> list[_Candidate]:
|
|
284
|
+
domain = next((item for item in workspace.mdl.domains if item.name == scope.domain), None)
|
|
285
|
+
if domain is None:
|
|
286
|
+
return []
|
|
287
|
+
|
|
288
|
+
if scope.kind == "projection":
|
|
289
|
+
versions = domain.projections.get(scope.name, [])
|
|
290
|
+
else:
|
|
291
|
+
versions = domain.models.get(scope.name, [])
|
|
292
|
+
|
|
293
|
+
version = next((item for item in versions if item.version == scope.version), None)
|
|
294
|
+
if version is None:
|
|
295
|
+
return []
|
|
296
|
+
|
|
297
|
+
fields = getattr(version, "fields", [])
|
|
298
|
+
candidates = [
|
|
299
|
+
_Candidate(label=field.name, kind=types.CompletionItemKind.Field, sort_rank=index + 30)
|
|
300
|
+
for index, field in enumerate(sorted(fields, key=lambda item: item.name))
|
|
301
|
+
]
|
|
302
|
+
return _filtered_candidates(candidates, prefix)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _filtered_candidates(candidates: list[_Candidate], prefix: str) -> list[_Candidate]:
|
|
306
|
+
if not prefix:
|
|
307
|
+
return candidates
|
|
308
|
+
lowered = prefix.lower()
|
|
309
|
+
return [candidate for candidate in candidates if candidate.label.lower().startswith(lowered)]
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _dedupe(candidates: list[_Candidate]) -> list[_Candidate]:
|
|
313
|
+
seen: set[str] = set()
|
|
314
|
+
deduped: list[_Candidate] = []
|
|
315
|
+
for candidate in candidates:
|
|
316
|
+
if candidate.label in seen:
|
|
317
|
+
continue
|
|
318
|
+
seen.add(candidate.label)
|
|
319
|
+
deduped.append(candidate)
|
|
320
|
+
return deduped
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _to_completion_item(candidate: _Candidate, index: int) -> types.CompletionItem:
|
|
324
|
+
return types.CompletionItem(
|
|
325
|
+
label=candidate.label,
|
|
326
|
+
kind=candidate.kind,
|
|
327
|
+
sort_text=f"{candidate.sort_rank:04d}-{index:04d}",
|
|
328
|
+
insert_text=candidate.label,
|
|
329
|
+
filter_text=candidate.label,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def _completion_prefix(before_cursor: str) -> str:
|
|
334
|
+
if not before_cursor:
|
|
335
|
+
return ""
|
|
336
|
+
|
|
337
|
+
version_match = re.search(r"@\s*([0-9]*)$", before_cursor)
|
|
338
|
+
if version_match is not None:
|
|
339
|
+
return version_match.group(1)
|
|
340
|
+
|
|
341
|
+
if before_cursor.endswith("@"):
|
|
342
|
+
return "@"
|
|
343
|
+
|
|
344
|
+
annotation_match = re.search(r"@[A-Za-z_][A-Za-z0-9_-]*$", before_cursor)
|
|
345
|
+
if annotation_match is not None:
|
|
346
|
+
return annotation_match.group(0)
|
|
347
|
+
|
|
348
|
+
stripped = before_cursor.rstrip()
|
|
349
|
+
if stripped.endswith("group"):
|
|
350
|
+
return "group"
|
|
351
|
+
|
|
352
|
+
match = _WORD_PREFIX_PATTERN.search(before_cursor)
|
|
353
|
+
if match is None:
|
|
354
|
+
return ""
|
|
355
|
+
return match.group(0)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _alias_context(before_cursor: str) -> bool:
|
|
359
|
+
stripped = before_cursor.rstrip()
|
|
360
|
+
return bool(_ALIAS_FIELD_PATTERN.search(stripped))
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def _version_context(before_cursor: str) -> tuple[str, str] | None:
|
|
364
|
+
stripped = before_cursor.rstrip()
|
|
365
|
+
import_match = _IMPORT_VERSION_PATTERN.search(stripped)
|
|
366
|
+
if import_match is not None:
|
|
367
|
+
return import_match.group("domain"), import_match.group("model")
|
|
368
|
+
reference_match = _REFERENCE_VERSION_PATTERN.search(stripped)
|
|
369
|
+
if reference_match is not None:
|
|
370
|
+
return reference_match.group("domain"), reference_match.group("model")
|
|
371
|
+
return None
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _version_prefix(before_cursor: str) -> str:
|
|
375
|
+
stripped = before_cursor.rstrip()
|
|
376
|
+
import_match = _IMPORT_VERSION_PATTERN.search(stripped)
|
|
377
|
+
if import_match is not None:
|
|
378
|
+
return import_match.group("prefix") or ""
|
|
379
|
+
reference_match = _REFERENCE_VERSION_PATTERN.search(stripped)
|
|
380
|
+
if reference_match is not None:
|
|
381
|
+
return reference_match.group("prefix") or ""
|
|
382
|
+
return ""
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _import_pin_model_context(before_cursor: str) -> bool:
|
|
386
|
+
stripped = before_cursor.rstrip()
|
|
387
|
+
return bool(_IMPORT_PIN_MODEL_PATTERN.search(stripped))
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _import_pin_model_candidates(index: LspWorkspaceIndex, before_cursor: str, prefix: str) -> list[_Candidate]:
|
|
391
|
+
stripped = before_cursor.rstrip()
|
|
392
|
+
match = _IMPORT_PIN_MODEL_PATTERN.search(stripped)
|
|
393
|
+
if match is None:
|
|
394
|
+
return []
|
|
395
|
+
|
|
396
|
+
domain_name = match.group("domain")
|
|
397
|
+
model_prefix = match.group("prefix") or ""
|
|
398
|
+
candidates: list[_Candidate] = []
|
|
399
|
+
for mirror_domain, model_name in mirror_reference_names(index):
|
|
400
|
+
if mirror_domain != domain_name:
|
|
401
|
+
continue
|
|
402
|
+
if model_prefix and not model_name.lower().startswith(model_prefix.lower()):
|
|
403
|
+
continue
|
|
404
|
+
candidates.append(
|
|
405
|
+
_Candidate(
|
|
406
|
+
label=model_name,
|
|
407
|
+
kind=types.CompletionItemKind.Class,
|
|
408
|
+
sort_rank=150,
|
|
409
|
+
)
|
|
410
|
+
)
|
|
411
|
+
return _filtered_candidates(candidates, prefix)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _alias_name(before_cursor: str) -> tuple[str, str] | None:
|
|
415
|
+
match = _ALIAS_FIELD_PATTERN.search(before_cursor)
|
|
416
|
+
if match is None:
|
|
417
|
+
return None
|
|
418
|
+
return match.group("alias"), match.group("prefix") or ""
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _projection_reference_for_alias(
|
|
422
|
+
text: str,
|
|
423
|
+
scope: _Scope,
|
|
424
|
+
line: int,
|
|
425
|
+
alias: str,
|
|
426
|
+
) -> tuple[str, str, int] | None:
|
|
427
|
+
lines = text.splitlines()
|
|
428
|
+
end_line = min(line, len(lines) - 1)
|
|
429
|
+
for item in lines[scope.line + 1 : end_line + 1]:
|
|
430
|
+
match = _SOURCE_ALIAS_PATTERN.match(item)
|
|
431
|
+
if match is None:
|
|
432
|
+
continue
|
|
433
|
+
if match.group("alias") != alias:
|
|
434
|
+
continue
|
|
435
|
+
return match.group("domain"), match.group("model"), int(match.group("version"))
|
|
436
|
+
return None
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _mirror_or_workspace_fields(
|
|
440
|
+
index: LspWorkspaceIndex,
|
|
441
|
+
domain_name: str,
|
|
442
|
+
model_name: str,
|
|
443
|
+
version: int,
|
|
444
|
+
) -> list[str]:
|
|
445
|
+
workspace = index.workspace
|
|
446
|
+
if workspace is not None:
|
|
447
|
+
domain = next((item for item in workspace.mdl.domains if item.name == domain_name), None)
|
|
448
|
+
if domain is not None:
|
|
449
|
+
versions = domain.models.get(model_name, [])
|
|
450
|
+
current = next((item for item in versions if item.version == version), None)
|
|
451
|
+
if current is None:
|
|
452
|
+
versions = domain.projections.get(model_name, [])
|
|
453
|
+
current = next((item for item in versions if item.version == version), None)
|
|
454
|
+
if current is not None:
|
|
455
|
+
return sorted(field.name for field in current.fields)
|
|
456
|
+
return mirror_field_names(index, domain_name, model_name, version)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _current_scope(text: str, line: int) -> _Scope | None:
|
|
460
|
+
lines = text.splitlines()
|
|
461
|
+
current_domain: str | None = None
|
|
462
|
+
current_scope: _Scope | None = None
|
|
463
|
+
for index, item in enumerate(lines[: line + 1]):
|
|
464
|
+
domain_match = _DOMAIN_PATTERN.match(item)
|
|
465
|
+
if domain_match:
|
|
466
|
+
current_domain = domain_match.group("quoted") or domain_match.group("name")
|
|
467
|
+
current_scope = None
|
|
468
|
+
continue
|
|
469
|
+
|
|
470
|
+
decl_match = _DECL_PATTERN.match(item)
|
|
471
|
+
if decl_match and current_domain is not None:
|
|
472
|
+
current_scope = _Scope(
|
|
473
|
+
domain=current_domain,
|
|
474
|
+
kind="projection" if decl_match.group("kind") == "projection" else "model",
|
|
475
|
+
name=decl_match.group("name"),
|
|
476
|
+
version=int(decl_match.group("version")),
|
|
477
|
+
line=index,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
return current_scope
|