linked-data-python 0.2.0__py3-none-any.whl → 0.5.1__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.
- ldpy/__init__.py +13 -1
- ldpy/debug.py +6 -2
- ldpy/lsp/backend.py +14 -0
- ldpy/lsp/hover.py +62 -0
- ldpy/lsp/islanddoc.py +215 -0
- ldpy/lsp/server.py +79 -30
- ldpy/lsp/translate.py +31 -0
- ldpy/runtime.py +72 -2
- ldpy/transpiler/core.py +148 -5
- ldpy/transpiler/linemap.py +21 -6
- {linked_data_python-0.2.0.dist-info → linked_data_python-0.5.1.dist-info}/METADATA +23 -23
- linked_data_python-0.5.1.dist-info/RECORD +28 -0
- linked_data_python-0.2.0.dist-info/RECORD +0 -26
- {linked_data_python-0.2.0.dist-info → linked_data_python-0.5.1.dist-info}/WHEEL +0 -0
- {linked_data_python-0.2.0.dist-info → linked_data_python-0.5.1.dist-info}/entry_points.txt +0 -0
- {linked_data_python-0.2.0.dist-info → linked_data_python-0.5.1.dist-info}/licenses/LICENSE.md +0 -0
- {linked_data_python-0.2.0.dist-info → linked_data_python-0.5.1.dist-info}/top_level.txt +0 -0
ldpy/__init__.py
CHANGED
|
@@ -11,7 +11,19 @@ package but is no longer imported by default.
|
|
|
11
11
|
from ldpy.transpiler import transpile, LdpySyntaxError, LdpyWarning
|
|
12
12
|
from ldpy.importer import install, uninstall, install_excepthook
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
#: Read from the installed distribution rather than written here: two copies
|
|
15
|
+
#: of a version number drift, and this one had — a 0.2.0 install announced
|
|
16
|
+
#: itself as 0.1.0.dev0 in the editor status bar. The fallback covers a source
|
|
17
|
+
#: tree that was never installed.
|
|
18
|
+
try:
|
|
19
|
+
from importlib.metadata import PackageNotFoundError, version as _version
|
|
20
|
+
try:
|
|
21
|
+
__version__ = _version("linked-data-python")
|
|
22
|
+
except PackageNotFoundError: # pragma: no cover
|
|
23
|
+
__version__ = "0.0.0+unknown"
|
|
24
|
+
del _version, PackageNotFoundError
|
|
25
|
+
except ImportError: # pragma: no cover
|
|
26
|
+
__version__ = "0.0.0+unknown"
|
|
15
27
|
__date__ = "2026-08-26"
|
|
16
28
|
|
|
17
29
|
|
ldpy/debug.py
CHANGED
|
@@ -211,9 +211,13 @@ def main(argv=None):
|
|
|
211
211
|
|
|
212
212
|
if args.breakpoints:
|
|
213
213
|
lines = [int(x) for x in args.breakpoints.split(",") if x.strip()]
|
|
214
|
+
# ABSOLUTE paths: this output crosses a process boundary, and a
|
|
215
|
+
# relative path means nothing to a reader that does not share our
|
|
216
|
+
# working directory. An editor turning ".ldpy-build/x.py" into a URI
|
|
217
|
+
# gets "/.ldpy-build/x.py", rooted at the filesystem root.
|
|
214
218
|
print(json.dumps({
|
|
215
|
-
"shadow": py_path,
|
|
216
|
-
"map": map_path,
|
|
219
|
+
"shadow": os.path.abspath(py_path),
|
|
220
|
+
"map": os.path.abspath(map_path),
|
|
217
221
|
"breakpoints": dict(zip(
|
|
218
222
|
lines, translate_breakpoints(result.map, lines)))}))
|
|
219
223
|
return 0
|
ldpy/lsp/backend.py
CHANGED
|
@@ -12,6 +12,18 @@ import threading
|
|
|
12
12
|
|
|
13
13
|
from ldpy.lsp.rpc import Endpoint, read_message, RpcClosed
|
|
14
14
|
|
|
15
|
+
#: The backend judges GENERATED code. Style findings (line length, blank
|
|
16
|
+
#: lines, continuation indents, semicolons…) describe the transpiler's
|
|
17
|
+
#: output, not the user's source; re-projected onto the .ldpy they underline
|
|
18
|
+
#: whole islands (record vscode/107). The shadow therefore opts out of every
|
|
19
|
+
#: style plugin; semantic analyses (pyflakes' undefined names, unused
|
|
20
|
+
#: imports) stay on.
|
|
21
|
+
STYLE_PLUGINS = ("autopep8", "flake8", "mccabe", "pycodestyle",
|
|
22
|
+
"pydocstyle", "pylint", "yapf")
|
|
23
|
+
|
|
24
|
+
SHADOW_SETTINGS = {"pylsp": {"plugins": {
|
|
25
|
+
name: {"enabled": False} for name in STYLE_PLUGINS}}}
|
|
26
|
+
|
|
15
27
|
|
|
16
28
|
class PythonBackend:
|
|
17
29
|
"""Lifecycle and delegation to `python -m pylsp` (or another argv)."""
|
|
@@ -45,6 +57,8 @@ class PythonBackend:
|
|
|
45
57
|
}},
|
|
46
58
|
}, timeout=30.0)
|
|
47
59
|
self.endpoint.notify("initialized", {})
|
|
60
|
+
self.endpoint.notify("workspace/didChangeConfiguration",
|
|
61
|
+
{"settings": SHADOW_SETTINGS})
|
|
48
62
|
return self
|
|
49
63
|
|
|
50
64
|
def _pump(self):
|
ldpy/lsp/hover.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Rendering the hover panel — record vscode/108.
|
|
2
|
+
|
|
3
|
+
Three blocks separated by rules, in the shape Python tooling has taught
|
|
4
|
+
people to read: a signature line, the description with a link to the
|
|
5
|
+
documentation, and the Python the transpiler produced. The translation can
|
|
6
|
+
be turned off (`ldpy.hover.showTranslation`); the first two blocks are what
|
|
7
|
+
tells you *what you are looking at*, so they always show.
|
|
8
|
+
|
|
9
|
+
Kept apart from `server.py`: rendering is a function of strings, and it is
|
|
10
|
+
tested as one.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from ldpy.lsp import islanddoc
|
|
14
|
+
|
|
15
|
+
RULE = "\n\n---\n\n"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def format_python(code, line_length=88):
|
|
19
|
+
"""`code` through black, or unchanged when black cannot take it.
|
|
20
|
+
|
|
21
|
+
What a hover shows is a FRAGMENT: the expression one term became, or the
|
|
22
|
+
head of a statement the source splits in two (`for @bindings in`). Those
|
|
23
|
+
are not modules, and black rightly refuses them. Showing the generated
|
|
24
|
+
text as it stands beats showing nothing, so a refusal is silent.
|
|
25
|
+
|
|
26
|
+
black normalises what it accepts — quotes above all — so the block is the
|
|
27
|
+
translation *formatted*, not a byte-for-byte extract of the generated
|
|
28
|
+
file. `ldpy -t` remains the place to read that.
|
|
29
|
+
"""
|
|
30
|
+
code = code.strip()
|
|
31
|
+
if not code:
|
|
32
|
+
return code
|
|
33
|
+
try:
|
|
34
|
+
import black
|
|
35
|
+
except ImportError: # pragma: no cover
|
|
36
|
+
return code
|
|
37
|
+
try:
|
|
38
|
+
return black.format_str(
|
|
39
|
+
code, mode=black.Mode(line_length=line_length)).rstrip("\n")
|
|
40
|
+
except Exception:
|
|
41
|
+
return code
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def render(kind, code, line_length=88, show_translation=True):
|
|
45
|
+
"""The markdown of a hover over an island element.
|
|
46
|
+
|
|
47
|
+
`kind` is an island kind (with or without the `island:` prefix), `code`
|
|
48
|
+
the Python it became. An unknown kind still renders: the table can lag
|
|
49
|
+
the transpiler by a commit, and a hover must degrade rather than vanish.
|
|
50
|
+
"""
|
|
51
|
+
doc = islanddoc.get(kind)
|
|
52
|
+
blocks = []
|
|
53
|
+
if doc is None:
|
|
54
|
+
short = kind[len("island:"):] if kind.startswith("island:") else kind
|
|
55
|
+
blocks.append("```ldpy\n(%s island)\n```" % short)
|
|
56
|
+
else:
|
|
57
|
+
blocks.append("```ldpy\n%s\n```" % doc.signature)
|
|
58
|
+
blocks.append("%s [Documentation](%s)" % (doc.summary, doc.url))
|
|
59
|
+
if show_translation and code and code.strip():
|
|
60
|
+
blocks.append("```python\n%s\n```"
|
|
61
|
+
% format_python(code, line_length))
|
|
62
|
+
return RULE.join(blocks)
|
ldpy/lsp/islanddoc.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""What each island IS, in the three lines a hover shows — record vscode/108.
|
|
2
|
+
|
|
3
|
+
Per island kind: a signature line in the shape Python tooling uses, a short
|
|
4
|
+
prose description, and the documentation page (with anchor) that explains it.
|
|
5
|
+
Nothing here knows about LSP; `server.py` renders it, `pygments_lexer.py` and
|
|
6
|
+
the semantic tokens name the same kinds.
|
|
7
|
+
|
|
8
|
+
The table is CLOSED: `tests/test_islanddoc.py` fails when the transpiler grows
|
|
9
|
+
a kind with no entry here, and when an anchor no longer exists under `docs/`.
|
|
10
|
+
That second test is what keeps the links from rotting silently — a dead link
|
|
11
|
+
in a hover is worse than no link, because nothing ever reports it.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
DOCS = "https://linked-data-python.readthedocs.io/en/latest/"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class IslandDoc:
|
|
18
|
+
"""Signature, description and documentation target of one island kind."""
|
|
19
|
+
|
|
20
|
+
__slots__ = ("signature", "summary", "page", "anchor")
|
|
21
|
+
|
|
22
|
+
def __init__(self, signature, summary, page, anchor):
|
|
23
|
+
self.signature = signature
|
|
24
|
+
self.summary = summary
|
|
25
|
+
self.page = page # path under docs/, with the .md suffix
|
|
26
|
+
self.anchor = anchor
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def url(self):
|
|
30
|
+
"""The published URL — mkdocs serves `x/y.md` as `x/y/`."""
|
|
31
|
+
return "%s%s/#%s" % (DOCS, self.page[:-len(".md")], self.anchor)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_TERMS = "reference/language/terms.md"
|
|
35
|
+
_DECLS = "reference/language/declarations.md"
|
|
36
|
+
_GRAPHS = "reference/language/graphs.md"
|
|
37
|
+
_CURRENT = "reference/language/current-graph.md"
|
|
38
|
+
_QUERY = "reference/language/querying.md"
|
|
39
|
+
_BINDINGS = "reference/language/bindings.md"
|
|
40
|
+
_SPARQL_EXPR = "reference/sparql-expressions.md"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
ISLANDS = {
|
|
44
|
+
|
|
45
|
+
# -- declarations -------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
"prefix": IslandDoc(
|
|
48
|
+
"(declaration) @prefix ex: <IRI> .",
|
|
49
|
+
"Binds `ex:` to a namespace IRI for the rest of the enclosing block. "
|
|
50
|
+
"A prefix is lexical: it has no run-time object, and `ex:` on its own "
|
|
51
|
+
"is never a value. Declaring it again in a deeper block shadows it, "
|
|
52
|
+
"the way a Python name would.",
|
|
53
|
+
_DECLS, "prefix-bind-a-prefix"),
|
|
54
|
+
|
|
55
|
+
"base": IslandDoc(
|
|
56
|
+
"(declaration) @base <IRI> .",
|
|
57
|
+
"Sets the base against which relative IRIs are resolved for the rest "
|
|
58
|
+
"of the block, so that `<sensor/1>` means `<IRI>sensor/1`.",
|
|
59
|
+
_DECLS, "base-set-the-base-iri"),
|
|
60
|
+
|
|
61
|
+
"import": IslandDoc(
|
|
62
|
+
"(declaration) from MODULE import ex:, unit: as u:",
|
|
63
|
+
"Imports prefixes declared by another module, optionally renaming "
|
|
64
|
+
"them. The module is imported as usual; what travels is the prefix "
|
|
65
|
+
"bindings, which have no value to import by ordinary means.",
|
|
66
|
+
_DECLS, "prefixes-across-modules"),
|
|
67
|
+
|
|
68
|
+
"graph-decl": IslandDoc(
|
|
69
|
+
"(declaration) @graph EXPR | @graph as NAME -> Graph",
|
|
70
|
+
"Designates the current graph for the block — the one `+{ }`, `-{ }` "
|
|
71
|
+
"and a receiver-less `m{ }` act on. `as NAME` creates a fresh graph "
|
|
72
|
+
"and binds it to NAME; `global` and `nonlocal` widen the scope.",
|
|
73
|
+
_CURRENT, "graph-designate-or-create"),
|
|
74
|
+
|
|
75
|
+
"bindings-decl": IslandDoc(
|
|
76
|
+
"(declaration) @bindings EXPR | @bindings as NAME -> Bindings",
|
|
77
|
+
"Designates the current bindings: the mapping that gives `?name` its "
|
|
78
|
+
"value in the enclosing block. Any mapping will do, and `as NAME` "
|
|
79
|
+
"creates an empty one.",
|
|
80
|
+
_BINDINGS, "bindings-the-current-bindings"),
|
|
81
|
+
|
|
82
|
+
"for-bindings": IslandDoc(
|
|
83
|
+
"(statement) for @bindings [as NAME] in ITER:",
|
|
84
|
+
"Loops over an iterable of mappings, making each row the current "
|
|
85
|
+
"bindings for the body. A `csv.DictReader` and the solutions of "
|
|
86
|
+
"`m{ }` are both iterables of mappings, so both drive this loop.",
|
|
87
|
+
_BINDINGS, "for-bindings-in-iter-the-loop-that-carries-them"),
|
|
88
|
+
|
|
89
|
+
"for-bindings-close": IslandDoc(
|
|
90
|
+
"(statement) the ':' closing a for @bindings header",
|
|
91
|
+
"The end of a `for @bindings` header. It is mapped on its own "
|
|
92
|
+
"because the header is rewritten in two pieces, around the iterable "
|
|
93
|
+
"that stays verbatim Python.",
|
|
94
|
+
_BINDINGS, "for-bindings-in-iter-the-loop-that-carries-them"),
|
|
95
|
+
|
|
96
|
+
# -- graphs and the current graph ---------------------------------------
|
|
97
|
+
|
|
98
|
+
"graph": IslandDoc(
|
|
99
|
+
"(expression) g{ ... } -> Graph",
|
|
100
|
+
"Builds an RDF graph from Turtle written in place. `{expr}` "
|
|
101
|
+
"interpolates a Python value in term position, and each occurrence "
|
|
102
|
+
"is evaluated once. The braces are an expression, so a `g{ }` goes "
|
|
103
|
+
"anywhere a value goes — a default argument, a comprehension, a "
|
|
104
|
+
"return.",
|
|
105
|
+
_GRAPHS, "turtle-inside-the-braces"),
|
|
106
|
+
|
|
107
|
+
"addto": IslandDoc(
|
|
108
|
+
"(statement) +{ ... } [ (GRAPH) ]",
|
|
109
|
+
"Adds the triples to the current graph. `?name` takes its value from "
|
|
110
|
+
"the current bindings, and a triple with an unbound variable is "
|
|
111
|
+
"dropped rather than written half-way. A trailing `(g)` names "
|
|
112
|
+
"another receiver.",
|
|
113
|
+
_CURRENT, "and-write-to-it"),
|
|
114
|
+
|
|
115
|
+
"removefrom": IslandDoc(
|
|
116
|
+
"(statement) -{ ... } [ (GRAPH) ]",
|
|
117
|
+
"Removes from the current graph every triple matching the pattern. "
|
|
118
|
+
"An unbound variable is a wildcard here, not a hole: this is a "
|
|
119
|
+
"SPARQL `DELETE WHERE`, not a list of triples to subtract.",
|
|
120
|
+
_CURRENT, "and-write-to-it"),
|
|
121
|
+
|
|
122
|
+
# -- reading ------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
"match": IslandDoc(
|
|
125
|
+
"(expression) m{ ... } -> Solutions",
|
|
126
|
+
"Matches a basic graph pattern against the current graph, lazily. "
|
|
127
|
+
"Iterating yields a bare term when one variable is projected and a "
|
|
128
|
+
"tuple otherwise; `.one()`, `.first()`, `.count()` and `bool()` are "
|
|
129
|
+
"the usual reductions. A `(graph)` suffix names another source.",
|
|
130
|
+
_QUERY, "m-match-a-basic-graph-pattern"),
|
|
131
|
+
|
|
132
|
+
"sparql": IslandDoc(
|
|
133
|
+
"(expression) s{ ... } -> Query",
|
|
134
|
+
"A SPARQL query or update, parsed when the file is transpiled rather "
|
|
135
|
+
"than at run time. `{expr}` in term position becomes an initial "
|
|
136
|
+
"binding — never string pasting, so nothing here can be injected. "
|
|
137
|
+
"Call it on a graph to run it; `.execute()` runs an update.",
|
|
138
|
+
_QUERY, "s-a-sparql-query"),
|
|
139
|
+
|
|
140
|
+
# -- terms --------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
"iri": IslandDoc(
|
|
143
|
+
"(term) <IRI> -> URIRef",
|
|
144
|
+
"An absolute IRI, or a relative one resolved against the `@base` in "
|
|
145
|
+
"scope.",
|
|
146
|
+
_TERMS, "iris"),
|
|
147
|
+
|
|
148
|
+
"pname": IslandDoc(
|
|
149
|
+
"(term) ex:local -> URIRef",
|
|
150
|
+
"A prefixed name: the local part is appended to the IRI bound to "
|
|
151
|
+
"`ex:`. Turtle's character set applies inside an island, so "
|
|
152
|
+
"`o-pizza:topping` and `ex:café` are names here although neither is "
|
|
153
|
+
"a legal Python expression. `ex:{expr}` computes the local part.",
|
|
154
|
+
_TERMS, "prefixed-names-exlocal"),
|
|
155
|
+
|
|
156
|
+
"literal": IslandDoc(
|
|
157
|
+
"(term) \"...\"@lang | \"...\"^^dt -> Literal",
|
|
158
|
+
"An RDF literal carrying a language tag or a datatype. The quoted "
|
|
159
|
+
"part may be an f-string, and `{expr}` may supply the datatype "
|
|
160
|
+
"itself.",
|
|
161
|
+
_TERMS, "rdf-literals-lang-dt"),
|
|
162
|
+
|
|
163
|
+
"var": IslandDoc(
|
|
164
|
+
"(term) ?name | $name -> Variable",
|
|
165
|
+
"A SPARQL variable. In a pattern it is what gets matched and "
|
|
166
|
+
"projected; in `g{ }` or `+{ }` it takes its value from the current "
|
|
167
|
+
"bindings, and leaves the triple out when it has none.",
|
|
168
|
+
_TERMS, "variables-name-name"),
|
|
169
|
+
|
|
170
|
+
"firi": IslandDoc(
|
|
171
|
+
"(term) f<...{expr}...> -> URIRef",
|
|
172
|
+
"A formatted IRI: the braces interpolate as in an f-string, the "
|
|
173
|
+
"result is percent-encoded and then resolved against `@base`. "
|
|
174
|
+
"Encoding first is what keeps a space or a slash in a value from "
|
|
175
|
+
"changing the IRI's structure.",
|
|
176
|
+
_TERMS, "formatted-iris-f"),
|
|
177
|
+
|
|
178
|
+
"fnode": IslandDoc(
|
|
179
|
+
"(term) f{expr} | ?{expr} -> Node",
|
|
180
|
+
"Coerces any Python value into an RDF term, by the coercion policy "
|
|
181
|
+
"in scope. Two spellings of one operation: `?{ }` reads better in "
|
|
182
|
+
"term position, `f{ }` beside `f<...>`.",
|
|
183
|
+
_TERMS, "formatted-nodes-fexpr-expr"),
|
|
184
|
+
|
|
185
|
+
"bnode": IslandDoc(
|
|
186
|
+
"(term) _:{expr} -> BNode",
|
|
187
|
+
"A blank node whose identity is its data: the same value gives the "
|
|
188
|
+
"same node anywhere in the program, so two rows that share a key "
|
|
189
|
+
"join without inventing an IRI for them.",
|
|
190
|
+
_GRAPHS, "data-keyed-blank-nodes-_expr"),
|
|
191
|
+
|
|
192
|
+
# -- deferred evaluation ------------------------------------------------
|
|
193
|
+
|
|
194
|
+
"enode": IslandDoc(
|
|
195
|
+
"(expression) e{ ... } -> Expr",
|
|
196
|
+
"A deferred SPARQL expression. It is not evaluated where it is "
|
|
197
|
+
"written, but against the bindings in force when the island holding "
|
|
198
|
+
"it is instantiated — once per row of a `for @bindings` loop. "
|
|
199
|
+
"`{python}` holes are evaluated where they are written.",
|
|
200
|
+
_BINDINGS, "e-in-term-position"),
|
|
201
|
+
|
|
202
|
+
"eiri": IslandDoc(
|
|
203
|
+
"(expression) e<...{?var}...> -> Expr",
|
|
204
|
+
"A deferred IRI: `f<...>`'s interpolation, except that the holes are "
|
|
205
|
+
"SPARQL expressions re-evaluated for each set of bindings.",
|
|
206
|
+
_SPARQL_EXPR, "deferred-iris-e"),
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def get(kind):
|
|
211
|
+
"""The entry for an island kind, `island:` prefix optional; None if the
|
|
212
|
+
kind is unknown — a hover must degrade, never raise."""
|
|
213
|
+
if kind.startswith("island:"):
|
|
214
|
+
kind = kind[len("island:"):]
|
|
215
|
+
return ISLANDS.get(kind)
|
ldpy/lsp/server.py
CHANGED
|
@@ -21,6 +21,7 @@ import sys
|
|
|
21
21
|
from ldpy.transpiler import transpile, LdpySyntaxError
|
|
22
22
|
from ldpy.lsp.rpc import Endpoint, read_message, RpcClosed
|
|
23
23
|
from ldpy.lsp import translate as tr
|
|
24
|
+
from ldpy.lsp import hover as hv
|
|
24
25
|
from ldpy.transpiler.linemap import snap_breakpoint_lines
|
|
25
26
|
|
|
26
27
|
|
|
@@ -29,6 +30,28 @@ def fmt_default():
|
|
|
29
30
|
from ldpy.formatter import DEFAULT_LINE_LENGTH
|
|
30
31
|
return DEFAULT_LINE_LENGTH
|
|
31
32
|
|
|
33
|
+
|
|
34
|
+
def _ldpy_version():
|
|
35
|
+
"""The installed package version — never duplicated here."""
|
|
36
|
+
from ldpy import __version__
|
|
37
|
+
return __version__
|
|
38
|
+
|
|
39
|
+
def _setting(settings, *path, default=None):
|
|
40
|
+
"""Read a nested setting, whether the client sends the whole tree
|
|
41
|
+
(`{"ldpy": {"hover": ...}}`) or just our section (`{"hover": ...}`).
|
|
42
|
+
Clients differ on this and both spellings are legitimate."""
|
|
43
|
+
for root in (settings, (settings or {}).get("ldpy")):
|
|
44
|
+
node = root
|
|
45
|
+
for key in path:
|
|
46
|
+
if not isinstance(node, dict) or key not in node:
|
|
47
|
+
node = None
|
|
48
|
+
break
|
|
49
|
+
node = node[key]
|
|
50
|
+
if node is not None:
|
|
51
|
+
return node
|
|
52
|
+
return default
|
|
53
|
+
|
|
54
|
+
|
|
32
55
|
FORWARDED = {
|
|
33
56
|
"textDocument/completion",
|
|
34
57
|
"textDocument/definition",
|
|
@@ -64,6 +87,9 @@ class LdpyServer:
|
|
|
64
87
|
self.backend_argv = backend_argv
|
|
65
88
|
self.backend = None
|
|
66
89
|
self.line_length = line_length
|
|
90
|
+
# `ldpy.hover.showTranslation` — the generated Python in the hover.
|
|
91
|
+
# On by default: it is the point of the feature (record vscode/108).
|
|
92
|
+
self.hover_translation = True
|
|
67
93
|
self._shutdown = False
|
|
68
94
|
|
|
69
95
|
# ------------------------------------------------------------ backend
|
|
@@ -123,13 +149,20 @@ class LdpyServer:
|
|
|
123
149
|
|
|
124
150
|
def _on_backend_diags(self, shadow, diags):
|
|
125
151
|
"""The backend's Python diagnostics, re-projected onto the .ldpy.
|
|
126
|
-
Those landing on synthetic text (the prelude) are dropped
|
|
152
|
+
Those landing on synthetic text (the prelude) are dropped, and so is
|
|
153
|
+
everything a style linter says about the shadow: it judges GENERATED
|
|
154
|
+
code (record vscode/107). The backend is asked not to compute style
|
|
155
|
+
at all (SHADOW_SETTINGS); this filter covers a backend that does not
|
|
156
|
+
honour the configuration."""
|
|
157
|
+
from ldpy.lsp.backend import STYLE_PLUGINS
|
|
127
158
|
doc = self.docs.get(tr.unshadow_uri(shadow))
|
|
128
159
|
if doc is None or doc.result is None:
|
|
129
160
|
return
|
|
130
161
|
lmap = doc.result.map
|
|
131
162
|
kept = []
|
|
132
163
|
for d in diags:
|
|
164
|
+
if d.get("source") in STYLE_PLUGINS:
|
|
165
|
+
continue
|
|
133
166
|
rng = d.get("range", {})
|
|
134
167
|
start = tr.pos_to_ldpy(lmap, rng.get("start", {}))
|
|
135
168
|
if start is None:
|
|
@@ -173,6 +206,7 @@ class LdpyServer:
|
|
|
173
206
|
|
|
174
207
|
def _dispatch(self, method, params, rid):
|
|
175
208
|
if method == "initialize":
|
|
209
|
+
self._apply_settings(params.get("initializationOptions"))
|
|
176
210
|
self._ensure_backend(params.get("rootUri"))
|
|
177
211
|
return {
|
|
178
212
|
"capabilities": {
|
|
@@ -185,18 +219,18 @@ class LdpyServer:
|
|
|
185
219
|
"referencesProvider": True,
|
|
186
220
|
"signatureHelpProvider": {
|
|
187
221
|
"triggerCharacters": ["(", ","]},
|
|
188
|
-
"semanticTokensProvider": {
|
|
189
|
-
"legend": {"tokenTypes": tr.TOKEN_TYPES,
|
|
190
|
-
"tokenModifiers": []},
|
|
191
|
-
"full": True},
|
|
192
222
|
# our own extension, announced so the client can detect
|
|
193
223
|
# it instead of assuming it (record vscode/103)
|
|
194
224
|
"experimental": {"ldpyBreakpointLines": True},
|
|
195
225
|
},
|
|
196
|
-
"serverInfo": {"name": "ldpy-lsp", "version":
|
|
226
|
+
"serverInfo": {"name": "ldpy-lsp", "version": _ldpy_version()},
|
|
197
227
|
}
|
|
198
|
-
if method
|
|
199
|
-
|
|
228
|
+
if method == "workspace/didChangeConfiguration":
|
|
229
|
+
# a live toggle: no restart, and no re-transpilation either —
|
|
230
|
+
# only the rendering of the next hover changes
|
|
231
|
+
self._apply_settings(params.get("settings"))
|
|
232
|
+
return None
|
|
233
|
+
if method in ("initialized", "$/setTrace", "$/cancelRequest"):
|
|
200
234
|
return None
|
|
201
235
|
if method == "shutdown":
|
|
202
236
|
self._shutdown = True
|
|
@@ -240,12 +274,6 @@ class LdpyServer:
|
|
|
240
274
|
return {"lines": lines}
|
|
241
275
|
return {"lines": snap_breakpoint_lines(doc.result.map, lines)}
|
|
242
276
|
|
|
243
|
-
if method == "textDocument/semanticTokens/full":
|
|
244
|
-
doc = self.docs.get(params["textDocument"]["uri"])
|
|
245
|
-
if doc is None or doc.result is None:
|
|
246
|
-
return {"data": []}
|
|
247
|
-
return {"data": tr.semantic_tokens(doc.result.map)}
|
|
248
|
-
|
|
249
277
|
if method == "textDocument/hover":
|
|
250
278
|
return self._hover(params)
|
|
251
279
|
|
|
@@ -292,28 +320,49 @@ class LdpyServer:
|
|
|
292
320
|
return [{"range": {"start": {"line": 0, "character": 0}, "end": end},
|
|
293
321
|
"newText": new}]
|
|
294
322
|
|
|
323
|
+
def _apply_settings(self, settings):
|
|
324
|
+
"""Client configuration, from `initialize` or a later change."""
|
|
325
|
+
value = _setting(settings, "hover", "showTranslation")
|
|
326
|
+
if isinstance(value, bool):
|
|
327
|
+
self.hover_translation = value
|
|
328
|
+
|
|
329
|
+
def _gen_text(self, doc, gen):
|
|
330
|
+
"""The generated Python covered by a map range."""
|
|
331
|
+
gl0, gc0, gl1, gc1 = gen
|
|
332
|
+
lines = doc.result.code.split("\n")[gl0:gl1 + 1]
|
|
333
|
+
if not lines:
|
|
334
|
+
return ""
|
|
335
|
+
if len(lines) == 1:
|
|
336
|
+
return lines[0][gc0:gc1]
|
|
337
|
+
lines[0] = lines[0][gc0:]
|
|
338
|
+
lines[-1] = lines[-1][:gc1]
|
|
339
|
+
return "\n".join(lines)
|
|
340
|
+
|
|
295
341
|
def _hover(self, params):
|
|
342
|
+
"""Native on an island, delegated on the Python around it.
|
|
343
|
+
|
|
344
|
+
Inside an island we answer on the SMALLEST described element under
|
|
345
|
+
the cursor (record vscode/108): hovering a prefixed name in a
|
|
346
|
+
forty-line `g{ }` should explain that name, not dump the whole
|
|
347
|
+
translated block."""
|
|
296
348
|
doc = self.docs.get(params["textDocument"]["uri"])
|
|
297
349
|
if doc is None or doc.result is None:
|
|
298
350
|
return None
|
|
299
351
|
pos = params["position"]
|
|
300
352
|
seg = tr.island_at(doc.result.map, pos["line"], pos["character"])
|
|
301
|
-
if seg is
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
"range": {"start": {"line": sl0, "character": sc0},
|
|
315
|
-
"end": {"line": sl1, "character": sc1}}}
|
|
316
|
-
return self._forward("textDocument/hover", params)
|
|
353
|
+
if seg is None:
|
|
354
|
+
return self._forward("textDocument/hover", params)
|
|
355
|
+
kind, src, code = tr.island_target(seg, pos["line"],
|
|
356
|
+
pos["character"])
|
|
357
|
+
if code is None:
|
|
358
|
+
code = self._gen_text(doc, seg.gen)
|
|
359
|
+
width = self.line_length or fmt_default()
|
|
360
|
+
sl0, sc0, sl1, sc1 = src
|
|
361
|
+
return {"contents": {"kind": "markdown",
|
|
362
|
+
"value": hv.render(kind, code, width,
|
|
363
|
+
self.hover_translation)},
|
|
364
|
+
"range": {"start": {"line": sl0, "character": sc0},
|
|
365
|
+
"end": {"line": sl1, "character": sc1}}}
|
|
317
366
|
|
|
318
367
|
# --------------------------------------------------------- delegation
|
|
319
368
|
|
ldpy/lsp/translate.py
CHANGED
|
@@ -109,6 +109,7 @@ _KIND_TO_TYPE = {
|
|
|
109
109
|
"island:pname": 2, # type
|
|
110
110
|
"island:var": 3, # variable
|
|
111
111
|
"island:firi": 4, "island:fnode": 4, # macro
|
|
112
|
+
"island:bnode": 4, # macro (_:{expr})
|
|
112
113
|
"island:graph": 4, # macro (région entière)
|
|
113
114
|
# fiches 013-019
|
|
114
115
|
"island:import": 5, # keyword (prefix import)
|
|
@@ -138,3 +139,33 @@ def semantic_tokens(lmap):
|
|
|
138
139
|
data.extend([dline, dcol, length, ttype, 0])
|
|
139
140
|
prev_line, prev_col = line, col
|
|
140
141
|
return data
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def island_target(seg, line, character):
|
|
145
|
+
"""The smallest DESCRIBED element of an island covering a position.
|
|
146
|
+
|
|
147
|
+
A composite island (`g{ }`, `m{ }`, `+{ }`, ...) records the terms it
|
|
148
|
+
contains as `parts` (record vscode/108). A hover wants the innermost one
|
|
149
|
+
— that is what the eye is on — and falls back to the island itself when
|
|
150
|
+
the position is on the notation rather than on a term, or when the island
|
|
151
|
+
has no parts at all.
|
|
152
|
+
|
|
153
|
+
Returns `(kind, src, gen_text)`; `gen_text` is None for the island
|
|
154
|
+
itself, whose generated text is read off the map's positions instead.
|
|
155
|
+
"""
|
|
156
|
+
best = None
|
|
157
|
+
for kind, src, gen in getattr(seg, "parts", None) or ():
|
|
158
|
+
sl0, sc0, sl1, sc1 = src
|
|
159
|
+
if not (sl0, sc0) <= (line, character) < (sl1, sc1):
|
|
160
|
+
continue
|
|
161
|
+
if best is None or _span(src) < _span(best[1]):
|
|
162
|
+
best = (kind, src, gen)
|
|
163
|
+
if best is not None:
|
|
164
|
+
return best
|
|
165
|
+
return (seg.kind, seg.src, None)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _span(src):
|
|
169
|
+
"""A rough size, to pick the innermost of two nested spans."""
|
|
170
|
+
sl0, sc0, sl1, sc1 = src
|
|
171
|
+
return (sl1 - sl0, sc1 - sc0)
|
ldpy/runtime.py
CHANGED
|
@@ -38,6 +38,12 @@ except ImportError: # MicroPython
|
|
|
38
38
|
def _urljoin(base, rel):
|
|
39
39
|
return base + rel
|
|
40
40
|
|
|
41
|
+
try:
|
|
42
|
+
from types import MappingProxyType as _MappingProxy
|
|
43
|
+
except ImportError: # MicroPython
|
|
44
|
+
def _MappingProxy(d):
|
|
45
|
+
return dict(d)
|
|
46
|
+
|
|
41
47
|
import re
|
|
42
48
|
_SCHEME_RE = re.compile(r"^[A-Za-z][A-Za-z0-9+.\-]*:")
|
|
43
49
|
|
|
@@ -248,11 +254,52 @@ def dtype(value):
|
|
|
248
254
|
|
|
249
255
|
def pname(ns, *parts):
|
|
250
256
|
"""A dynamic prefixed name (record ldpy/013): concatenates the namespace
|
|
251
|
-
IRI (imported prefix, or computed IRI) and the local part.
|
|
257
|
+
IRI (imported prefix, or computed IRI) and the local part.
|
|
258
|
+
|
|
259
|
+
A part that is a Variable or a deferred expression makes the WHOLE name
|
|
260
|
+
deferred (record ldpy/017). `ex:{?id}` used to yield `ex:id` — the same
|
|
261
|
+
IRI on every row, with no error, which is the trap of forging an IRI
|
|
262
|
+
from a column. It now resolves against the current bindings like any
|
|
263
|
+
other deferred term: unbound, the triple is dropped rather than written
|
|
264
|
+
wrong. `ex:{expr}` on an ordinary value stays immediate and needs no
|
|
265
|
+
bindings — a Variable is a str subclass, hence the exact type test.
|
|
266
|
+
"""
|
|
267
|
+
cls = _expr_class()
|
|
268
|
+
for p in parts:
|
|
269
|
+
tp = type(p)
|
|
270
|
+
if tp is Variable or (cls and tp is cls):
|
|
271
|
+
return _deferred_pname(ns, parts)
|
|
252
272
|
return rdflib.URIRef(
|
|
253
273
|
str(ns) + "".join(p if isinstance(p, str) else str(p) for p in parts))
|
|
254
274
|
|
|
255
275
|
|
|
276
|
+
def _deferred_pname(ns, parts):
|
|
277
|
+
"""`ex:{?v}` — the prefixed name as a deferred expression.
|
|
278
|
+
|
|
279
|
+
Concatenation, exactly as in the immediate case: no percent-encoding,
|
|
280
|
+
because the namespace and the shape of the local part are yours. To mint
|
|
281
|
+
an IRI from data that may hold spaces or slashes, use `e<...{?v}>`,
|
|
282
|
+
which encodes."""
|
|
283
|
+
from ldpy.sparql import Expression, var
|
|
284
|
+
|
|
285
|
+
def build(sm):
|
|
286
|
+
out = [str(ns)]
|
|
287
|
+
for p in parts:
|
|
288
|
+
tp = type(p)
|
|
289
|
+
if tp is Variable:
|
|
290
|
+
out.append(str(var(sm, str(p))))
|
|
291
|
+
elif isinstance(p, Expression):
|
|
292
|
+
out.append(str(p(sm)))
|
|
293
|
+
else:
|
|
294
|
+
out.append(p if isinstance(p, str) else str(p))
|
|
295
|
+
return rdflib.URIRef("".join(out))
|
|
296
|
+
|
|
297
|
+
src = str(ns) + "".join(
|
|
298
|
+
"?%s" % p if type(p) is Variable
|
|
299
|
+
else (p if type(p) is str else repr(p)) for p in parts)
|
|
300
|
+
return Expression(build, src)
|
|
301
|
+
|
|
302
|
+
|
|
256
303
|
def firi(*parts, base=None):
|
|
257
304
|
"""A formatted IRI: concatenate str(part), then resolve against base if relative."""
|
|
258
305
|
iri = "".join(p if isinstance(p, str) else str(p) for p in parts)
|
|
@@ -421,8 +468,12 @@ class Bindings(dict):
|
|
|
421
468
|
Everything Python can do to a dict works: b[?x] = v, update, del, **b,
|
|
422
469
|
iteration over the keys."""
|
|
423
470
|
|
|
471
|
+
#: fallback when __init__ was bypassed (dict.copy, unpickling…)
|
|
472
|
+
_raw = {}
|
|
473
|
+
|
|
424
474
|
def __init__(self, mapping=None):
|
|
425
475
|
dict.__init__(self)
|
|
476
|
+
self._raw = {}
|
|
426
477
|
if mapping is not None:
|
|
427
478
|
if not hasattr(mapping, "items"):
|
|
428
479
|
raise TypeError(
|
|
@@ -431,14 +482,32 @@ class Bindings(dict):
|
|
|
431
482
|
for k, v in mapping.items():
|
|
432
483
|
self[k] = v
|
|
433
484
|
|
|
485
|
+
@property
|
|
486
|
+
def raw(self):
|
|
487
|
+
"""The values as they ARRIVED, before coercion (record ldpy/012,
|
|
488
|
+
point 22).
|
|
489
|
+
|
|
490
|
+
`b["v"]` is an RDF term, and a term does not compare equal to the
|
|
491
|
+
Python value it came from — `Literal("") != ""` is True, and
|
|
492
|
+
`if row["col"] != "":` is the commonest guard of a CSV-to-RDF
|
|
493
|
+
script. `b.raw["col"]` gives that guard its Python value back,
|
|
494
|
+
while `b["col"]` stays the term the islands instantiate against.
|
|
495
|
+
|
|
496
|
+
Read-only: write through `b[key]`, which coerces and records both.
|
|
497
|
+
"""
|
|
498
|
+
return _MappingProxy(self._raw)
|
|
499
|
+
|
|
434
500
|
def __setitem__(self, key, value):
|
|
435
|
-
|
|
501
|
+
k = str(key)
|
|
502
|
+
self._raw[k] = value
|
|
503
|
+
dict.__setitem__(self, k, node(value, field=k))
|
|
436
504
|
|
|
437
505
|
def __getitem__(self, key):
|
|
438
506
|
return dict.__getitem__(self, str(key))
|
|
439
507
|
|
|
440
508
|
def __delitem__(self, key):
|
|
441
509
|
dict.__delitem__(self, str(key))
|
|
510
|
+
self._raw.pop(str(key), None)
|
|
442
511
|
|
|
443
512
|
def __contains__(self, key):
|
|
444
513
|
return dict.__contains__(self, str(key))
|
|
@@ -466,6 +535,7 @@ def as_bindings_iter(iterable):
|
|
|
466
535
|
for k, v in sm.items():
|
|
467
536
|
if not str(k).startswith("__bn"):
|
|
468
537
|
dict.__setitem__(b, str(k), v) # already terms
|
|
538
|
+
b._raw[str(k)] = v # raw == the term here
|
|
469
539
|
yield b
|
|
470
540
|
return
|
|
471
541
|
for item in iterable:
|
ldpy/transpiler/core.py
CHANGED
|
@@ -35,6 +35,14 @@ KEYWORDS = frozenset((
|
|
|
35
35
|
))
|
|
36
36
|
# mots-clés « valeurs » : terminent un opérande
|
|
37
37
|
VALUE_KEYWORDS = frozenset(("False", "None", "True"))
|
|
38
|
+
# mots-clés qui ouvrent une instruction composée : leur ':' de profondeur 0
|
|
39
|
+
# ouvre une SUITE, position d'instruction (fiche 012, point 12). `match` et
|
|
40
|
+
# `case` sont des mots-clés SOUPLES — un `match: int = 0` reste une annotation,
|
|
41
|
+
# mais rien n'y suit un ':' qu'un îlot pourrait revendiquer.
|
|
42
|
+
COMPOUND_KEYWORDS = frozenset((
|
|
43
|
+
"async", "case", "class", "def", "elif", "else", "except", "finally",
|
|
44
|
+
"for", "if", "match", "try", "while", "with",
|
|
45
|
+
))
|
|
38
46
|
|
|
39
47
|
STRING_PREFIXES = frozenset((
|
|
40
48
|
"", "r", "b", "u", "f", "rb", "br", "rf", "fr",
|
|
@@ -165,6 +173,47 @@ class TranspileResult:
|
|
|
165
173
|
self.warnings = warnings # list[LdpyWarning]
|
|
166
174
|
|
|
167
175
|
|
|
176
|
+
_BOOL_RE = re.compile(r"(?:true|false|True|False)\s*$")
|
|
177
|
+
_STRING_START_RE = re.compile(r"[rbfuRBFU]{0,2}[\"']")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _term_kind(text):
|
|
181
|
+
"""The island kind of a term, from the source it was written as.
|
|
182
|
+
|
|
183
|
+
`_g_node` dispatches on exactly these leading characters, so the two stay
|
|
184
|
+
in step, and `tests/test_hover_parts.py` pins each form against the kind
|
|
185
|
+
the SAME term gets outside an island, where the transpiler labels it
|
|
186
|
+
itself. None means "no hover of its own": a `[ ]` or `( )` whose contents
|
|
187
|
+
are recorded one by one anyway, a `{python}` hole, a labelled blank node
|
|
188
|
+
— for those the island's own hover is the honest answer.
|
|
189
|
+
|
|
190
|
+
Order matters twice over: `f"x"@en` is a literal and not a formatted
|
|
191
|
+
node, and `foo:bar` is a prefixed name and not anything beginning with f.
|
|
192
|
+
"""
|
|
193
|
+
t = text.lstrip()
|
|
194
|
+
if t.startswith("_:{"):
|
|
195
|
+
return "bnode"
|
|
196
|
+
if t.startswith("_:"):
|
|
197
|
+
return None # labelled: no island kind of its own
|
|
198
|
+
if t[:1] == "<":
|
|
199
|
+
return "iri"
|
|
200
|
+
if t[:1] in "[({":
|
|
201
|
+
return None
|
|
202
|
+
if _STRING_START_RE.match(t):
|
|
203
|
+
return "literal" # incl. f"..."@en, r"...", b"..."
|
|
204
|
+
if t[:1] in "?$":
|
|
205
|
+
return "fnode" if t[1:2] == "{" else "var"
|
|
206
|
+
if t[:1] == "f" and t[1:2] in "<{":
|
|
207
|
+
return "firi" if t[1:2] == "<" else "fnode"
|
|
208
|
+
if t[:1] == "e" and t[1:2] in "{<":
|
|
209
|
+
return "enode" if t[1:2] == "{" else "eiri"
|
|
210
|
+
if t[:1].isdigit() or (t[:1] in "+-." and t[1:2].isdigit()):
|
|
211
|
+
return "literal"
|
|
212
|
+
if _BOOL_RE.match(t):
|
|
213
|
+
return "literal" # Turtle's true/false: a Literal
|
|
214
|
+
return "pname"
|
|
215
|
+
|
|
216
|
+
|
|
168
217
|
class Transpiler:
|
|
169
218
|
"""Une instance par fichier. Usage : Transpiler(src, filename).run()."""
|
|
170
219
|
|
|
@@ -199,6 +248,10 @@ class Transpiler:
|
|
|
199
248
|
# que Python possède, et où aucun terme RDF ne peut vivre.
|
|
200
249
|
self._lambda_depth = None
|
|
201
250
|
self._after_def = False # on vient de lire 'def' : le '(' suivant
|
|
251
|
+
# la ligne logique a commencé par un mot-clé d'instruction composée :
|
|
252
|
+
# son ':' de profondeur 0 ouvre une SUITE, donc une position
|
|
253
|
+
# d'instruction (fiche 012, point 12).
|
|
254
|
+
self._compound_header = False
|
|
202
255
|
self.operand = True # un opérande peut commencer ici
|
|
203
256
|
self.stmt_start = True # début de ligne logique (hors espaces)
|
|
204
257
|
self.after_dot = False
|
|
@@ -207,6 +260,7 @@ class Transpiler:
|
|
|
207
260
|
# une instruction moins indentée dépile et restaure.
|
|
208
261
|
self._scope_stack = []
|
|
209
262
|
self._retired = {} # préfixe sorti de portée -> ligne de décl.
|
|
263
|
+
self._shadowed = set() # préfixe aussi nom Python : averti une fois
|
|
210
264
|
self._prefix_col = {} # préfixe -> indentation de sa déclaration
|
|
211
265
|
# préfixes dynamiques (fiche 013)
|
|
212
266
|
self._ns_counter = 0 # variables fraîches _ldpy_ns*
|
|
@@ -215,6 +269,10 @@ class Transpiler:
|
|
|
215
269
|
self._graph_var = None # variable Python du graphe courant, ou None
|
|
216
270
|
# îlot de motif (fiche 016) : collecte des variables projetées
|
|
217
271
|
self._match_vars = None
|
|
272
|
+
# terms of the island being parsed, and the finished list waiting
|
|
273
|
+
# for the `_end_island` that closes it (record vscode/108)
|
|
274
|
+
self._parts = None
|
|
275
|
+
self._pending_parts = None
|
|
218
276
|
# binding courant (fiche 017)
|
|
219
277
|
self._bindings_var = None
|
|
220
278
|
|
|
@@ -280,7 +338,9 @@ class Transpiler:
|
|
|
280
338
|
sl, sc, gl, gc = mark
|
|
281
339
|
self.map.add("island:" + kind,
|
|
282
340
|
(sl, sc, self.src_line, self.src_col),
|
|
283
|
-
(gl, gc, self.gen_line, self.gen_col)
|
|
341
|
+
(gl, gc, self.gen_line, self.gen_col),
|
|
342
|
+
self._pending_parts)
|
|
343
|
+
self._pending_parts = None
|
|
284
344
|
self.uses_runtime = True
|
|
285
345
|
self.operand = False
|
|
286
346
|
self.stmt_start = False
|
|
@@ -357,6 +417,7 @@ class Transpiler:
|
|
|
357
417
|
if self.depth == 0:
|
|
358
418
|
self.stmt_start = True
|
|
359
419
|
self.operand = True
|
|
420
|
+
self._compound_header = False
|
|
360
421
|
self.after_dot = False
|
|
361
422
|
elif c in " \t\r":
|
|
362
423
|
self._copy(1)
|
|
@@ -427,17 +488,25 @@ class Transpiler:
|
|
|
427
488
|
self._copy(1)
|
|
428
489
|
self.stmt_start = False
|
|
429
490
|
elif c == ":":
|
|
430
|
-
|
|
491
|
+
lam = self._lambda_depth == self.depth
|
|
492
|
+
if lam:
|
|
431
493
|
self._lambda_depth = None # fin des paramètres
|
|
432
494
|
self._copy(1)
|
|
433
495
|
self.operand = True
|
|
434
|
-
|
|
496
|
+
# `if c: +{ ... }` — le ':' d'une instruction composée ouvre
|
|
497
|
+
# une suite, qui est une position d'instruction au même titre
|
|
498
|
+
# qu'un début de ligne (fiche 012, point 12). Pas celui d'une
|
|
499
|
+
# annotation (`x: int`) ni d'un lambda, qui n'ouvrent rien.
|
|
500
|
+
self.stmt_start = (self._compound_header and self.depth == 0
|
|
501
|
+
and not lam)
|
|
502
|
+
self._compound_header = False
|
|
435
503
|
self.after_dot = False
|
|
436
504
|
elif c == ";":
|
|
437
505
|
self._copy(1)
|
|
438
506
|
self.operand = True
|
|
439
507
|
if self.depth == 0:
|
|
440
508
|
self.stmt_start = True
|
|
509
|
+
self._compound_header = False
|
|
441
510
|
else:
|
|
442
511
|
# opérateurs, ponctuation, identifiants unicode
|
|
443
512
|
if c.isidentifier():
|
|
@@ -713,11 +782,30 @@ class Transpiler:
|
|
|
713
782
|
self._warn("le préfixe '%s:' est hors de portée ici (sa "
|
|
714
783
|
"déclaration est dans un bloc terminé) ; le texte "
|
|
715
784
|
"est laissé tel quel" % name)
|
|
785
|
+
# un préfixe déclaré qui sert AUSSI de nom Python : `{ex:b}` devient
|
|
786
|
+
# alors un ensemble d'IRI là où Python lisait un dict (fiche 002).
|
|
787
|
+
# Savoir où Python affecte un nom demanderait de le parser ; on s'en
|
|
788
|
+
# tient à l'approximation retenue le 2026-08-29 — le nom en tête
|
|
789
|
+
# d'instruction, suivi de '=' (hors '==') ou de ','.
|
|
790
|
+
if self.stmt_start and self.depth == 0 and not self._sub \
|
|
791
|
+
and name in self.prefixes and name not in self._shadowed:
|
|
792
|
+
j = self._skip_ws_ahead(m.end())
|
|
793
|
+
nx, nx2 = (t[j:j + 1], t[j + 1:j + 2])
|
|
794
|
+
if (nx == "=" and nx2 != "=") or nx == ",":
|
|
795
|
+
self._shadowed.add(name)
|
|
796
|
+
self._warn("'%s' est à la fois un préfixe déclaré et un nom "
|
|
797
|
+
"Python : dans `{%s:x}` et `a[%s:x]`, le nom "
|
|
798
|
+
"préfixé l'emporte sur la lecture Python. Écrire "
|
|
799
|
+
"`{%s: x}` avec une espace force le dict."
|
|
800
|
+
% (name, name, name, name))
|
|
716
801
|
|
|
717
802
|
if name == "lambda":
|
|
718
803
|
self._lambda_depth = self.depth
|
|
719
804
|
elif name == "def":
|
|
720
805
|
self._after_def = True
|
|
806
|
+
if self.stmt_start and self.depth == 0 \
|
|
807
|
+
and name in COMPOUND_KEYWORDS:
|
|
808
|
+
self._compound_header = True
|
|
721
809
|
self._copy(len(name))
|
|
722
810
|
if name in KEYWORDS:
|
|
723
811
|
self.operand = name not in VALUE_KEYWORDS
|
|
@@ -937,10 +1025,13 @@ class Transpiler:
|
|
|
937
1025
|
if all(p[0] for p in parts):
|
|
938
1026
|
local = "".join(p[1] for p in parts)
|
|
939
1027
|
return "%s.URIRef(%r)" % (RUNTIME_ALIAS, ns + local)
|
|
1028
|
+
# `ex:{expr}` EST un nom préfixé, pas une IRI formatée : même
|
|
1029
|
+
# concaténation, mais `pname` diffère l'évaluation quand la partie
|
|
1030
|
+
# locale porte une variable (fiche 017).
|
|
940
1031
|
args = [repr(ns)]
|
|
941
1032
|
for is_static, val in parts:
|
|
942
1033
|
args.append(repr(val) if is_static else "(%s)" % val)
|
|
943
|
-
return "%s.
|
|
1034
|
+
return "%s.pname(%s)" % (RUNTIME_ALIAS, ", ".join(args))
|
|
944
1035
|
|
|
945
1036
|
# ------------------------------------------------------------------
|
|
946
1037
|
# import de préfixes (fiche 013)
|
|
@@ -1624,6 +1715,24 @@ class Transpiler:
|
|
|
1624
1715
|
# ------------------------------------------------------------------
|
|
1625
1716
|
|
|
1626
1717
|
def _g_parse_triples(self):
|
|
1718
|
+
"""Collects the island's terms as `parts` while parsing it.
|
|
1719
|
+
|
|
1720
|
+
Saved and restored around the call, because an interpolation can
|
|
1721
|
+
hold another island: `g{ ex:p f{ g{ ... } } }` runs a whole inner
|
|
1722
|
+
island — `_end_island` included — in the middle of the outer one.
|
|
1723
|
+
The finished list waits in `_pending_parts` for the `_end_island`
|
|
1724
|
+
that closes THIS island; an inner island finds it None and attaches
|
|
1725
|
+
nothing, which is what keeps the two from stealing each other's
|
|
1726
|
+
terms.
|
|
1727
|
+
"""
|
|
1728
|
+
saved = self._parts
|
|
1729
|
+
self._parts = []
|
|
1730
|
+
try:
|
|
1731
|
+
return self._g_parse_triples_inner()
|
|
1732
|
+
finally:
|
|
1733
|
+
self._pending_parts, self._parts = self._parts, saved
|
|
1734
|
+
|
|
1735
|
+
def _g_parse_triples_inner(self):
|
|
1627
1736
|
"""Corps d'îlot de graphe : self.i juste après le '{' ouvrant.
|
|
1628
1737
|
Consomme jusqu'au '}' fermant inclus ; retourne (triples, gctx)."""
|
|
1629
1738
|
gctx = _GraphCtx()
|
|
@@ -1704,6 +1813,22 @@ class Transpiler:
|
|
|
1704
1813
|
return
|
|
1705
1814
|
|
|
1706
1815
|
def _g_verb(self, triples, gctx):
|
|
1816
|
+
"""Records the predicate as a `part`, then parses it — the sibling
|
|
1817
|
+
of the wrapper on `_g_node`, for the position `_g_node` never sees."""
|
|
1818
|
+
if self._parts is None or self._sub:
|
|
1819
|
+
return self._g_verb_inner(triples, gctx)
|
|
1820
|
+
sl, sc, i0 = self.src_line, self.src_col, self.i
|
|
1821
|
+
gen = self._g_verb_inner(triples, gctx)
|
|
1822
|
+
# `a` is Turtle's rdf:type shorthand, not a prefixed name: no kind
|
|
1823
|
+
# of its own, so the island's hover answers for it
|
|
1824
|
+
kind = (None if self.text[i0:self.i].strip() == "a"
|
|
1825
|
+
else _term_kind(self.text[i0:self.i]))
|
|
1826
|
+
if kind is not None:
|
|
1827
|
+
self._parts.append(
|
|
1828
|
+
(kind, (sl, sc, self.src_line, self.src_col), gen))
|
|
1829
|
+
return gen
|
|
1830
|
+
|
|
1831
|
+
def _g_verb_inner(self, triples, gctx):
|
|
1707
1832
|
t = self.text
|
|
1708
1833
|
c = self._peek()
|
|
1709
1834
|
if c == "a" and not (_name_char(self._peek(1)) or self._peek(1) == ":"):
|
|
@@ -1755,6 +1880,23 @@ class Transpiler:
|
|
|
1755
1880
|
return "%s.Variable(%r)" % (RUNTIME_ALIAS, name)
|
|
1756
1881
|
|
|
1757
1882
|
def _g_node(self, triples, gctx):
|
|
1883
|
+
"""Records the term as a `part` of the island, then parses it.
|
|
1884
|
+
|
|
1885
|
+
`_g_parse_triples` opens the list; everything a composite island
|
|
1886
|
+
contains comes through here, so this one wrapper gives `g{ }`,
|
|
1887
|
+
`m{ }`, `+{ }` and `-{ }` a hover per term (record vscode/108)
|
|
1888
|
+
without touching the flat language map."""
|
|
1889
|
+
if self._parts is None or self._sub:
|
|
1890
|
+
return self._g_node_inner(triples, gctx)
|
|
1891
|
+
sl, sc, i0 = self.src_line, self.src_col, self.i
|
|
1892
|
+
gen, composite = self._g_node_inner(triples, gctx)
|
|
1893
|
+
kind = _term_kind(self.text[i0:self.i])
|
|
1894
|
+
if kind is not None:
|
|
1895
|
+
self._parts.append(
|
|
1896
|
+
(kind, (sl, sc, self.src_line, self.src_col), gen))
|
|
1897
|
+
return gen, composite
|
|
1898
|
+
|
|
1899
|
+
def _g_node_inner(self, triples, gctx):
|
|
1758
1900
|
"""Parse un nœud de graphe. Retourne (expr, is_composite) ;
|
|
1759
1901
|
is_composite vrai pour [..], (..) qui peuvent être sujets sans props."""
|
|
1760
1902
|
t = self.text
|
|
@@ -2485,7 +2627,8 @@ class _Term(str):
|
|
|
2485
2627
|
def _maybe_impure(expr, gctx):
|
|
2486
2628
|
"""f-IRI et nom prefixe : impurs seulement s'ils portent une interpolation
|
|
2487
2629
|
(sinon le transpileur a deja produit une URIRef constante)."""
|
|
2488
|
-
if expr.startswith(RUNTIME_ALIAS + ".firi(")
|
|
2630
|
+
if expr.startswith(RUNTIME_ALIAS + ".firi(") \
|
|
2631
|
+
or expr.startswith(RUNTIME_ALIAS + ".pname("):
|
|
2489
2632
|
return _impure(expr, gctx)
|
|
2490
2633
|
return expr
|
|
2491
2634
|
|
ldpy/transpiler/linemap.py
CHANGED
|
@@ -12,14 +12,23 @@ import json
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class Segment:
|
|
15
|
-
"""One source/generated correspondence segment (copy, island:*, synthetic).
|
|
15
|
+
"""One source/generated correspondence segment (copy, island:*, synthetic).
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
A composite island (`g{ }`, `m{ }`, `+{ }`, `-{ }`) also carries `parts`:
|
|
18
|
+
the terms written inside it, as `(kind, src, gen_text)` (record
|
|
19
|
+
vscode/108). They are deliberately NOT segments of their own — the map is
|
|
20
|
+
a flat, ordered list that breakpoint snapping, the source map and request
|
|
21
|
+
forwarding all walk, and nesting spans inside it would change what those
|
|
22
|
+
three answer. Parts are read by the hover and by nothing else.
|
|
23
|
+
"""
|
|
18
24
|
|
|
19
|
-
|
|
25
|
+
__slots__ = ("kind", "src", "gen", "parts")
|
|
26
|
+
|
|
27
|
+
def __init__(self, kind, src, gen, parts=None):
|
|
20
28
|
self.kind = kind
|
|
21
29
|
self.src = src # (line0, col0, line1, col1), or None for synthetic
|
|
22
30
|
self.gen = gen # (line0, col0, line1, col1)
|
|
31
|
+
self.parts = parts or []
|
|
23
32
|
|
|
24
33
|
def __repr__(self):
|
|
25
34
|
return "Segment(%r, src=%r, gen=%r)" % (self.kind, self.src, self.gen)
|
|
@@ -29,6 +38,9 @@ class Segment:
|
|
|
29
38
|
d = {"kind": self.kind, "gen": list(self.gen)}
|
|
30
39
|
if self.src is not None:
|
|
31
40
|
d["src"] = list(self.src)
|
|
41
|
+
if self.parts:
|
|
42
|
+
d["parts"] = [{"kind": k, "src": list(s), "gen": g}
|
|
43
|
+
for k, s, g in self.parts]
|
|
32
44
|
return d
|
|
33
45
|
|
|
34
46
|
|
|
@@ -60,12 +72,12 @@ class LanguageMap:
|
|
|
60
72
|
self.generated_name = generated_name
|
|
61
73
|
self.segments = [] # ordered by increasing gen AND src positions
|
|
62
74
|
|
|
63
|
-
def add(self, kind, src, gen):
|
|
75
|
+
def add(self, kind, src, gen, parts=None):
|
|
64
76
|
"""Add a segment (segments empty on both sides are ignored)."""
|
|
65
77
|
# ignore segments that are empty on both sides
|
|
66
78
|
if src is not None and src[:2] == src[2:] and gen[:2] == gen[2:]:
|
|
67
79
|
return
|
|
68
|
-
self.segments.append(Segment(kind, src, gen))
|
|
80
|
+
self.segments.append(Segment(kind, src, gen, parts))
|
|
69
81
|
|
|
70
82
|
# -- traduction ---------------------------------------------------------
|
|
71
83
|
|
|
@@ -123,7 +135,10 @@ class LanguageMap:
|
|
|
123
135
|
m = cls(d.get("source", "<ldpy>"), d.get("generated"))
|
|
124
136
|
for sd in d.get("segments", []):
|
|
125
137
|
src = tuple(sd["src"]) if "src" in sd else None
|
|
126
|
-
|
|
138
|
+
parts = [(pd["kind"], tuple(pd["src"]), pd["gen"])
|
|
139
|
+
for pd in sd.get("parts", ())]
|
|
140
|
+
m.segments.append(
|
|
141
|
+
Segment(sd["kind"], src, tuple(sd["gen"]), parts))
|
|
127
142
|
return m
|
|
128
143
|
|
|
129
144
|
@classmethod
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linked-data-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: Python extended with Semantic Web primitives: IRIs, RDF literals and Turtle-notation graphs as expressions, transpiled by island parsing.
|
|
5
5
|
Author-email: Maxime Lefrançois <maxime.lefrancois@emse.fr>
|
|
6
6
|
License: MIT
|
|
@@ -96,31 +96,31 @@ From Python: `import ldpy; ldpy.install()` then `import yourmodule` finds
|
|
|
96
96
|
|
|
97
97
|
## Documentation
|
|
98
98
|
|
|
99
|
-
Read it at
|
|
100
|
-
|
|
99
|
+
Read it at **<https://linked-data-python.readthedocs.io/>** — start with the
|
|
100
|
+
home page for an overview, then:
|
|
101
101
|
|
|
102
|
-
- **Tutorials** — [first steps](
|
|
103
|
-
[build a knowledge graph](
|
|
102
|
+
- **Tutorials** — [first steps](https://linked-data-python.readthedocs.io/en/latest/tutorials/getting-started/), then
|
|
103
|
+
[build a knowledge graph](https://linked-data-python.readthedocs.io/en/latest/tutorials/build-a-knowledge-graph/) from
|
|
104
104
|
tabular data.
|
|
105
|
-
- **How-to guides** — [run & import](
|
|
106
|
-
[build graphs from tables](
|
|
107
|
-
[read and query](
|
|
108
|
-
[migrate from rdflib](
|
|
109
|
-
[VS Code](
|
|
110
|
-
[language server](
|
|
111
|
-
[highlighting](
|
|
112
|
-
- **Reference** — [the language](
|
|
113
|
-
per island family; [SPARQL expressions](
|
|
114
|
-
[CLI](
|
|
115
|
-
[language map formats](
|
|
116
|
-
- **Explanation** — [why](
|
|
117
|
-
[what real RDF code does](
|
|
105
|
+
- **How-to guides** — [run & import](https://linked-data-python.readthedocs.io/en/latest/how-to/run-and-import/),
|
|
106
|
+
[build graphs from tables](https://linked-data-python.readthedocs.io/en/latest/how-to/build-graphs-from-tables/),
|
|
107
|
+
[read and query](https://linked-data-python.readthedocs.io/en/latest/how-to/query-a-graph/),
|
|
108
|
+
[migrate from rdflib](https://linked-data-python.readthedocs.io/en/latest/how-to/migrate-from-rdflib/),
|
|
109
|
+
[VS Code](https://linked-data-python.readthedocs.io/en/latest/how-to/use-vscode/), [debugging](https://linked-data-python.readthedocs.io/en/latest/how-to/debug/),
|
|
110
|
+
[language server](https://linked-data-python.readthedocs.io/en/latest/how-to/language-server/),
|
|
111
|
+
[highlighting](https://linked-data-python.readthedocs.io/en/latest/how-to/highlight-ldpy/).
|
|
112
|
+
- **Reference** — [the language](https://linked-data-python.readthedocs.io/en/latest/reference/language/), one page
|
|
113
|
+
per island family; [SPARQL expressions](https://linked-data-python.readthedocs.io/en/latest/reference/sparql-expressions/);
|
|
114
|
+
[CLI](https://linked-data-python.readthedocs.io/en/latest/reference/cli/); [Python API](https://linked-data-python.readthedocs.io/en/latest/reference/api/);
|
|
115
|
+
[language map formats](https://linked-data-python.readthedocs.io/en/latest/reference/language-map/).
|
|
116
|
+
- **Explanation** — [why](https://linked-data-python.readthedocs.io/en/latest/explanation/why/),
|
|
117
|
+
[what real RDF code does](https://linked-data-python.readthedocs.io/en/latest/explanation/what-real-code-does/) (the
|
|
118
118
|
corpus study that drove the language's second wave),
|
|
119
|
-
[designing the syntax](
|
|
120
|
-
[island parsing](
|
|
121
|
-
[emission & semantics](
|
|
122
|
-
[tooling](
|
|
123
|
-
[how this is tested](
|
|
119
|
+
[designing the syntax](https://linked-data-python.readthedocs.io/en/latest/explanation/designing-the-syntax/),
|
|
120
|
+
[island parsing](https://linked-data-python.readthedocs.io/en/latest/explanation/island-parsing/),
|
|
121
|
+
[emission & semantics](https://linked-data-python.readthedocs.io/en/latest/explanation/emission-and-semantics/),
|
|
122
|
+
[tooling](https://linked-data-python.readthedocs.io/en/latest/explanation/tooling/),
|
|
123
|
+
[how this is tested](https://linked-data-python.readthedocs.io/en/latest/explanation/how-it-is-tested/).
|
|
124
124
|
|
|
125
125
|
Every `ldpy` and `python` block in the documentation is executed by the test
|
|
126
126
|
suite, and its assertions are the test.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
ldpy/__init__.py,sha256=IyuzHrp5OtuGO_NrrdmMUQW6bTYcyS5uob7CeoXg8mY,1943
|
|
2
|
+
ldpy/__main__.py,sha256=YKTEjmNQuh_0p-KiEus9HngFs_ZDmS56RXzHTspiWVI,3077
|
|
3
|
+
ldpy/build.py,sha256=495F9KyFMar-hTCuaQkF3FvzWQX4mE9H0U0V-JuRDmA,3394
|
|
4
|
+
ldpy/console.py,sha256=1FrroxoTTWG7fIBzaDQvOxPGj6NkyXJDJhhl5RXyE_0,4204
|
|
5
|
+
ldpy/debug.py,sha256=5o8oVYIbFk6rB9-ShTIfym_bhVG3imTw_l0taJZ9qH0,9462
|
|
6
|
+
ldpy/formatter.py,sha256=VaDPECxhivsMe8jnX_fOL7BUyLEu5r4xREdr4Hav-0c,12695
|
|
7
|
+
ldpy/importer.py,sha256=QXAhSujPN6rFMJSQuj0UsWyI4CajirKt1SOrykJ_fUw,3670
|
|
8
|
+
ldpy/pygments_lexer.py,sha256=mcQ0u9nZjLnSWpqzcxd6bZjk69LfKSNTsf9A3rqfhhk,25320
|
|
9
|
+
ldpy/runtime.py,sha256=2xugLVjgtbay9GAsC_QGVFBeaXSRbja7xEkl2ZbYDQ4,36040
|
|
10
|
+
ldpy/sparql.py,sha256=CHSB4v5lEuVQKRPuwcM8xI3jN2FVHkmPVb675QkAD9Y,14770
|
|
11
|
+
ldpy/lsp/__init__.py,sha256=rK5GYOITi9PN09-WVlJ2aPKodW8dWTpgI_X6dFbP1zA,542
|
|
12
|
+
ldpy/lsp/__main__.py,sha256=MIp4ktqaoWZCNIMfv14bSC9pqK86JBS5XVjd1Z3c24o,72
|
|
13
|
+
ldpy/lsp/backend.py,sha256=8DnSGaBk1dsIMOBo7D1WQxxF3MZjJbYd78jp4ucWORY,5375
|
|
14
|
+
ldpy/lsp/hover.py,sha256=zSJ0PpU3kTJDXcwI2zSDCbsKrT-eNTt2RlvmEPRoV-M,2407
|
|
15
|
+
ldpy/lsp/islanddoc.py,sha256=vxxRKEcywBH31c8XgykEoC8fZfvbHok1NH-rPc-0Sy0,9540
|
|
16
|
+
ldpy/lsp/rpc.py,sha256=-TF_9U8dum23Pi4O1FZLogwoZgrGrt14J5BFB1OYVZw,3554
|
|
17
|
+
ldpy/lsp/server.py,sha256=PAMMookGUn00vAW_-a5BGy3qkVe9PoUdfzAzpdlvoF8,16359
|
|
18
|
+
ldpy/lsp/translate.py,sha256=HU9fBP_xWyYLih6QwQfSoyfF-MH-oInFcPX3o8Vc2GI,6116
|
|
19
|
+
ldpy/transpiler/__init__.py,sha256=9mQx0e47ixz0iMqSbCBbtGiwJ7PLxTcsVSIp6KNTMG4,504
|
|
20
|
+
ldpy/transpiler/core.py,sha256=4_YXbLLdtZfRTLb3ySprsnS_LXqiGfxZXqZvnXWT5T8,113482
|
|
21
|
+
ldpy/transpiler/errors.py,sha256=zEEwsNvC2Tawq8jIYls7hrbcWSvQtd7eo89k_SKp0k0,1142
|
|
22
|
+
ldpy/transpiler/linemap.py,sha256=ecLlCUkD4-6QQtUjGfAL28ql7LPITTS4fjLcDIbAwpQ,11386
|
|
23
|
+
linked_data_python-0.5.1.dist-info/licenses/LICENSE.md,sha256=R6KjQ098eAFd2k5mKaSRJqMH1LhY4o7xkHrKT5vjRfs,1074
|
|
24
|
+
linked_data_python-0.5.1.dist-info/METADATA,sha256=zC_1FxXgKCcqGYOcqNzUQkVVJ40m8HMKH-GzbMPsfWU,7974
|
|
25
|
+
linked_data_python-0.5.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
26
|
+
linked_data_python-0.5.1.dist-info/entry_points.txt,sha256=n8Vs52oH81BrKOe1DNUvdFLSOlEufRMpK2waPFa6aYk,224
|
|
27
|
+
linked_data_python-0.5.1.dist-info/top_level.txt,sha256=dkG_F4jYkh6CS6gdpQeV_gKW3yaJBvl1ukUu0zo2KA8,5
|
|
28
|
+
linked_data_python-0.5.1.dist-info/RECORD,,
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
ldpy/__init__.py,sha256=ieLv4_enuK-Z6O-In0kp1zol08BsQwj2Ky1-wqb8Blo,1302
|
|
2
|
-
ldpy/__main__.py,sha256=YKTEjmNQuh_0p-KiEus9HngFs_ZDmS56RXzHTspiWVI,3077
|
|
3
|
-
ldpy/build.py,sha256=495F9KyFMar-hTCuaQkF3FvzWQX4mE9H0U0V-JuRDmA,3394
|
|
4
|
-
ldpy/console.py,sha256=1FrroxoTTWG7fIBzaDQvOxPGj6NkyXJDJhhl5RXyE_0,4204
|
|
5
|
-
ldpy/debug.py,sha256=mG8Dku-0erfJoBB9yYxI4eEuUipIpMsirLp6DfGx34k,9138
|
|
6
|
-
ldpy/formatter.py,sha256=VaDPECxhivsMe8jnX_fOL7BUyLEu5r4xREdr4Hav-0c,12695
|
|
7
|
-
ldpy/importer.py,sha256=QXAhSujPN6rFMJSQuj0UsWyI4CajirKt1SOrykJ_fUw,3670
|
|
8
|
-
ldpy/pygments_lexer.py,sha256=mcQ0u9nZjLnSWpqzcxd6bZjk69LfKSNTsf9A3rqfhhk,25320
|
|
9
|
-
ldpy/runtime.py,sha256=k1k_DfblW4Dxacu4BkZpfuEPV1mMN83PkLtML9SAoms,33390
|
|
10
|
-
ldpy/sparql.py,sha256=CHSB4v5lEuVQKRPuwcM8xI3jN2FVHkmPVb675QkAD9Y,14770
|
|
11
|
-
ldpy/lsp/__init__.py,sha256=rK5GYOITi9PN09-WVlJ2aPKodW8dWTpgI_X6dFbP1zA,542
|
|
12
|
-
ldpy/lsp/__main__.py,sha256=MIp4ktqaoWZCNIMfv14bSC9pqK86JBS5XVjd1Z3c24o,72
|
|
13
|
-
ldpy/lsp/backend.py,sha256=c5Jx1fZ1ZhoYet9__RB1mUqFUtcCl63tO8vgnv8iMII,4644
|
|
14
|
-
ldpy/lsp/rpc.py,sha256=-TF_9U8dum23Pi4O1FZLogwoZgrGrt14J5BFB1OYVZw,3554
|
|
15
|
-
ldpy/lsp/server.py,sha256=Xpl1ApcNlauLjEiabITOTJ6i8aVGDdPB7SgPxK2ePJs,14329
|
|
16
|
-
ldpy/lsp/translate.py,sha256=_xDnKrJdx02puRkcuzhGwCLdEByuvhTfSIc9dAFR6a8,4931
|
|
17
|
-
ldpy/transpiler/__init__.py,sha256=9mQx0e47ixz0iMqSbCBbtGiwJ7PLxTcsVSIp6KNTMG4,504
|
|
18
|
-
ldpy/transpiler/core.py,sha256=s0jzTHSsazF7Xm-32q_Km6dKCxyjWEucmyVqd0eTP1s,106350
|
|
19
|
-
ldpy/transpiler/errors.py,sha256=zEEwsNvC2Tawq8jIYls7hrbcWSvQtd7eo89k_SKp0k0,1142
|
|
20
|
-
ldpy/transpiler/linemap.py,sha256=6b7y0nNmgnAMr_bqomgty7V0RgWPXFAp_8BZE4VYqzc,10580
|
|
21
|
-
linked_data_python-0.2.0.dist-info/licenses/LICENSE.md,sha256=R6KjQ098eAFd2k5mKaSRJqMH1LhY4o7xkHrKT5vjRfs,1074
|
|
22
|
-
linked_data_python-0.2.0.dist-info/METADATA,sha256=iHtvFy2zY921K5YLqF2vk13DGasgPwV2bSQPMdRGcgg,6985
|
|
23
|
-
linked_data_python-0.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
24
|
-
linked_data_python-0.2.0.dist-info/entry_points.txt,sha256=n8Vs52oH81BrKOe1DNUvdFLSOlEufRMpK2waPFa6aYk,224
|
|
25
|
-
linked_data_python-0.2.0.dist-info/top_level.txt,sha256=dkG_F4jYkh6CS6gdpQeV_gKW3yaJBvl1ukUu0zo2KA8,5
|
|
26
|
-
linked_data_python-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
{linked_data_python-0.2.0.dist-info → linked_data_python-0.5.1.dist-info}/licenses/LICENSE.md
RENAMED
|
File without changes
|
|
File without changes
|