linked-data-python 0.5.1__tar.gz → 0.5.2__tar.gz
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.
- {linked_data_python-0.5.1/linked_data_python.egg-info → linked_data_python-0.5.2}/PKG-INFO +1 -1
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/console.py +13 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/debug.py +24 -2
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/runtime.py +1 -3
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/transpiler/core.py +12 -8
- {linked_data_python-0.5.1 → linked_data_python-0.5.2/linked_data_python.egg-info}/PKG-INFO +1 -1
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/pyproject.toml +1 -1
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_console.py +58 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_debug.py +41 -0
- linked_data_python-0.5.2/tests/test_docs.py +103 -0
- linked_data_python-0.5.1/tests/test_docs.py +0 -46
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/LICENSE.md +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/README.md +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/__init__.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/__main__.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/build.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/formatter.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/importer.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/lsp/__init__.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/lsp/__main__.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/lsp/backend.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/lsp/hover.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/lsp/islanddoc.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/lsp/rpc.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/lsp/server.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/lsp/translate.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/pygments_lexer.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/sparql.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/transpiler/__init__.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/transpiler/errors.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/ldpy/transpiler/linemap.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/linked_data_python.egg-info/SOURCES.txt +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/linked_data_python.egg-info/dependency_links.txt +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/linked_data_python.egg-info/entry_points.txt +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/linked_data_python.egg-info/requires.txt +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/linked_data_python.egg-info/top_level.txt +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/setup.cfg +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_bench.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_bindings.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_build.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_call_suffix.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_charsets.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_coercion.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_current_graph.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_debug_stepping.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_disambiguation.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_docstrings.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_examples.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_formatter.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_generator.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_graphs.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_hover_parts.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_identity.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_import_hook.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_islanddoc.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_islands_terms.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_linemap.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_lsp_rpc.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_lsp_server.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_lsp_translate.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_match_island.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_micropython_subset.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_prefix_import.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_prefix_scoping.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_pygments_lexer.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_scope_modifiers.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_semantics.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_sourcemap_v3.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_sparql_expr.py +0 -0
- {linked_data_python-0.5.1 → linked_data_python-0.5.2}/tests/test_sparql_island.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linked-data-python
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
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
|
|
@@ -70,6 +70,12 @@ class LdpyConsole(code.InteractiveConsole):
|
|
|
70
70
|
self._prefixes = dict(prefixes or {})
|
|
71
71
|
self._prefix_cols = {k: 0 for k in self._prefixes}
|
|
72
72
|
self._base = base
|
|
73
|
+
# The current graph and the current bindings are declarations too:
|
|
74
|
+
# like @prefix, a top-level @graph must survive to the next entry
|
|
75
|
+
# (otherwise `+{ ... }` after `@graph as g` sees no graph at all).
|
|
76
|
+
self._graph_var = None
|
|
77
|
+
self._bindings_var = None
|
|
78
|
+
self._counter = 0
|
|
73
79
|
|
|
74
80
|
def runsource(self, source, filename=None, symbol="single"):
|
|
75
81
|
"""Transpile then compile one entry; True = incomplete entry
|
|
@@ -79,6 +85,10 @@ class LdpyConsole(code.InteractiveConsole):
|
|
|
79
85
|
t.prefixes = dict(self._prefixes)
|
|
80
86
|
t._prefix_col = dict(self._prefix_cols)
|
|
81
87
|
t.base = self._base
|
|
88
|
+
t._graph_var = self._graph_var
|
|
89
|
+
t._bindings_var = self._bindings_var
|
|
90
|
+
# fresh names must not collide with those of the previous entries
|
|
91
|
+
t._ns_counter = self._counter
|
|
82
92
|
try:
|
|
83
93
|
result = t.run()
|
|
84
94
|
except LdpySyntaxError as e:
|
|
@@ -98,6 +108,9 @@ class LdpyConsole(code.InteractiveConsole):
|
|
|
98
108
|
self._prefixes = dict(t.prefixes)
|
|
99
109
|
self._prefix_cols = dict(t._prefix_col)
|
|
100
110
|
self._base = t.base
|
|
111
|
+
self._graph_var = t._graph_var
|
|
112
|
+
self._bindings_var = t._bindings_var
|
|
113
|
+
self._counter = t._ns_counter
|
|
101
114
|
for w in result.warnings:
|
|
102
115
|
self.write(str(w) + "\n")
|
|
103
116
|
self.runcode(code_obj)
|
|
@@ -92,11 +92,30 @@ def probe():
|
|
|
92
92
|
#: re-exported: debug tooling looks for them here (pure functions, defined
|
|
93
93
|
#: alongside the map in ldpy/transpiler/linemap.py)
|
|
94
94
|
__all__ = ["stepping_rules", "probe", "package_dir", "load_map",
|
|
95
|
+
"shadow_rel",
|
|
95
96
|
"translate_breakpoints", "translate_frames",
|
|
96
97
|
"snap_breakpoint_line", "snap_breakpoint_lines",
|
|
97
98
|
"run_direct", "main"]
|
|
98
99
|
|
|
99
100
|
|
|
101
|
+
def shadow_rel(source, root=None):
|
|
102
|
+
"""Path of the shadow inside the output directory.
|
|
103
|
+
|
|
104
|
+
Without a root, the basename: the shadow sits beside the source, and two
|
|
105
|
+
files never meet. With one — a workspace whose whole build is gathered in
|
|
106
|
+
a single directory — the tree is mirrored, which is what keeps `a/m.ldpy`
|
|
107
|
+
and `b/m.ldpy` from writing the same `m.py`. A source outside the root
|
|
108
|
+
falls back to its basename rather than climbing out of the output
|
|
109
|
+
directory with `../`."""
|
|
110
|
+
name = os.path.basename(source)
|
|
111
|
+
if not root:
|
|
112
|
+
return name
|
|
113
|
+
rel = os.path.relpath(os.path.abspath(source), os.path.abspath(root))
|
|
114
|
+
if rel.startswith(os.pardir + os.sep) or rel == os.pardir:
|
|
115
|
+
return name
|
|
116
|
+
return rel
|
|
117
|
+
|
|
118
|
+
|
|
100
119
|
def load_map(map_path):
|
|
101
120
|
"""Load a LanguageMap from a .ldpy.map file (JSON)."""
|
|
102
121
|
with open(map_path, "r", encoding="utf-8") as f:
|
|
@@ -167,6 +186,10 @@ def main(argv=None):
|
|
|
167
186
|
"straight in the .ldpy)")
|
|
168
187
|
parser.add_argument("-o", "--out", default=DEFAULT_OUT,
|
|
169
188
|
help="shadow directory (default: %(default)s)")
|
|
189
|
+
parser.add_argument("--root", metavar="DIR",
|
|
190
|
+
help="tree root the shadow mirrors: the .py is "
|
|
191
|
+
"written at OUT/<path of the source relative "
|
|
192
|
+
"to DIR> (default: OUT/<basename>)")
|
|
170
193
|
parser.add_argument("--listen", metavar="HOTE:PORT",
|
|
171
194
|
help="start debugpy listening (e.g. 127.0.0.1:5678)")
|
|
172
195
|
parser.add_argument("--wait-for-client", action="store_true",
|
|
@@ -201,8 +224,7 @@ def main(argv=None):
|
|
|
201
224
|
|
|
202
225
|
try:
|
|
203
226
|
py_path, map_path, result = build_file(
|
|
204
|
-
args.source, args.out,
|
|
205
|
-
rel=os.path.basename(args.source))
|
|
227
|
+
args.source, args.out, rel=shadow_rel(args.source, args.root))
|
|
206
228
|
except LdpySyntaxError as e:
|
|
207
229
|
print(str(e), file=sys.stderr)
|
|
208
230
|
return 1
|
|
@@ -641,7 +641,6 @@ def add_to(graph, *triples, bindings=None):
|
|
|
641
641
|
s2, p2, o2 = term(s), term(p), term(o)
|
|
642
642
|
if s2 is not None and p2 is not None and o2 is not None:
|
|
643
643
|
graph.add((s2, p2, o2))
|
|
644
|
-
return graph
|
|
645
644
|
|
|
646
645
|
|
|
647
646
|
def remove_from(graph, *patterns, bindings=None):
|
|
@@ -665,13 +664,12 @@ def remove_from(graph, *patterns, bindings=None):
|
|
|
665
664
|
to_remove.add(inst)
|
|
666
665
|
for tr in to_remove:
|
|
667
666
|
graph.remove(tr)
|
|
668
|
-
return
|
|
667
|
+
return
|
|
669
668
|
term = _materializer(bindings, keep_vars=True)
|
|
670
669
|
for s, p, o in patterns:
|
|
671
670
|
tr = tuple(None if isinstance(x, (Variable, bn)) else x
|
|
672
671
|
for x in (term(s), term(p), term(o)))
|
|
673
672
|
graph.remove(tr)
|
|
674
|
-
return graph
|
|
675
673
|
|
|
676
674
|
|
|
677
675
|
def graph(namespaces, base, *triples, bindings=None):
|
|
@@ -345,13 +345,16 @@ class Transpiler:
|
|
|
345
345
|
self.operand = False
|
|
346
346
|
self.stmt_start = False
|
|
347
347
|
|
|
348
|
-
def _error(self, msg, line=None, col=None):
|
|
348
|
+
def _error(self, msg, line=None, col=None, incomplete=True):
|
|
349
349
|
exc = LdpySyntaxError(msg, self.filename,
|
|
350
350
|
self.src_line if line is None else line,
|
|
351
351
|
self.src_col if col is None else col)
|
|
352
352
|
# la console s'en sert pour distinguer « entrée incomplète » (îlot
|
|
353
|
-
# non fermé en fin de tampon) d'une vraie erreur de syntaxe
|
|
354
|
-
|
|
353
|
+
# non fermé en fin de tampon) d'une vraie erreur de syntaxe.
|
|
354
|
+
# `incomplete=False` : erreur qu'une suite d'entrée ne répare pas
|
|
355
|
+
# (elle porte sur ce qui a déjà été lu), même si le curseur est en
|
|
356
|
+
# fin de tampon — sinon la console attendrait indéfiniment.
|
|
357
|
+
exc.at_eof = incomplete and self.i >= self.n
|
|
355
358
|
raise exc
|
|
356
359
|
|
|
357
360
|
def _warn(self, msg):
|
|
@@ -675,7 +678,7 @@ class Transpiler:
|
|
|
675
678
|
if re.match(r"@(prefix|base|graph|bindings)\b", t[j:j + 10]):
|
|
676
679
|
self._close_copy()
|
|
677
680
|
self._take(m.end() - self.i) # le mot-clé
|
|
678
|
-
while self._peek() in " \t":
|
|
681
|
+
while self._peek() in (" ", "\t"):
|
|
679
682
|
self._take(1)
|
|
680
683
|
if not self._try_prefix_or_base(scope_mode=name):
|
|
681
684
|
self._error("déclaration d'îlot attendue après "
|
|
@@ -1388,7 +1391,8 @@ class Transpiler:
|
|
|
1388
1391
|
if not encl:
|
|
1389
1392
|
what = "prefix %s:" % name if kind == "prefix" else kind
|
|
1390
1393
|
self._error("nonlocal @%s : aucune déclaration englobante "
|
|
1391
|
-
"(comme le nonlocal de Python)" % what
|
|
1394
|
+
"(comme le nonlocal de Python)" % what,
|
|
1395
|
+
incomplete=False)
|
|
1392
1396
|
keep = encl[-1]
|
|
1393
1397
|
target_col = self._scope_stack[keep][0]
|
|
1394
1398
|
touched = [i for i in matches if i > keep]
|
|
@@ -1562,7 +1566,7 @@ class Transpiler:
|
|
|
1562
1566
|
return self._take(m.end() - self.i)
|
|
1563
1567
|
|
|
1564
1568
|
def _graph_ws_inline(self):
|
|
1565
|
-
while self._peek() in " \t":
|
|
1569
|
+
while self._peek() in (" ", "\t"):
|
|
1566
1570
|
self._take(1)
|
|
1567
1571
|
|
|
1568
1572
|
def _addremove_island(self, sign):
|
|
@@ -1581,7 +1585,7 @@ class Transpiler:
|
|
|
1581
1585
|
self._error("'%s{ ... }' sans graphe courant : déclarez-le avec "
|
|
1582
1586
|
"'@graph <expression>' ou '@graph as g' (fiche 014), "
|
|
1583
1587
|
"ou donnez-le en suffixe — %s{ ... }(g)"
|
|
1584
|
-
% (sign, sign))
|
|
1588
|
+
% (sign, sign), incomplete=False)
|
|
1585
1589
|
bvar = sfx_bindings if sfx_bindings else self._bindings_var
|
|
1586
1590
|
fn = "add_to" if sign == "+" else "remove_from"
|
|
1587
1591
|
args = [gvar]
|
|
@@ -2482,7 +2486,7 @@ class Transpiler:
|
|
|
2482
2486
|
if word.upper() == "BOUND" and after == "(":
|
|
2483
2487
|
self._take(len(word) + 1)
|
|
2484
2488
|
self._e_ws()
|
|
2485
|
-
if self._peek() not in "
|
|
2489
|
+
if self._peek() not in ("?", "$"):
|
|
2486
2490
|
self._error("BOUND attend une variable ?v")
|
|
2487
2491
|
self._take(1)
|
|
2488
2492
|
vm = _NAME_RE.match(t, self.i)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linked-data-python
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.2
|
|
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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "linked-data-python"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.2"
|
|
8
8
|
description = "Python extended with Semantic Web primitives: IRIs, RDF literals and Turtle-notation graphs as expressions, transpiled by island parsing."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -94,3 +94,61 @@ def test_interactive_after_script(tmp_path):
|
|
|
94
94
|
p = console("X\nex:b\n", args=("-i", str(script)))
|
|
95
95
|
assert "URIRef('http://e/a')" in p.stdout # les globals du script
|
|
96
96
|
assert "URIRef('http://e/b')" in p.stdout # et ses préfixes
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_current_graph_persists_across_entries():
|
|
100
|
+
"""`@graph as g` then `+{ }` on the next line: the current graph is a
|
|
101
|
+
declaration like @prefix, it must survive the entry that declares it."""
|
|
102
|
+
p = console("@prefix ex: <http://e/> .\n"
|
|
103
|
+
"@graph as g\n"
|
|
104
|
+
"+{ ex:alice a ex:Person }\n"
|
|
105
|
+
"len(g)\n")
|
|
106
|
+
assert p.returncode == 0
|
|
107
|
+
assert "1" in results(p)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_add_island_prints_nothing():
|
|
111
|
+
"""`+{ }` is a statement: the console must not echo the graph."""
|
|
112
|
+
p = console("@prefix ex: <http://e/> .\n"
|
|
113
|
+
"@graph as g\n"
|
|
114
|
+
"+{ ex:alice a ex:Person }\n")
|
|
115
|
+
assert "Graph" not in p.stdout
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_current_bindings_persist_across_entries():
|
|
119
|
+
"""`for @bindings as b in ...` names the bindings; the entries that
|
|
120
|
+
follow must still see the current graph declared before them."""
|
|
121
|
+
p = console("@prefix ex: <http://e/> .\n"
|
|
122
|
+
"@graph as g\n"
|
|
123
|
+
"for @bindings as b in [{\"n\": \"ana\"}]:\n"
|
|
124
|
+
" +{ ex:p ex:name ?n }\n"
|
|
125
|
+
"\n"
|
|
126
|
+
"len(g)\n")
|
|
127
|
+
assert "1" in results(p)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_add_without_current_graph_reports_at_once():
|
|
131
|
+
"""An island that is complete but has no current graph is an error, not
|
|
132
|
+
an entry waiting to be finished: the console used to hang on it."""
|
|
133
|
+
p = console("@prefix ex: <http://e/> .\n"
|
|
134
|
+
"+{ ex:a ex:b ex:c }\n"
|
|
135
|
+
"'survivant'\n")
|
|
136
|
+
assert p.returncode == 0
|
|
137
|
+
assert "graphe courant" in p.stderr
|
|
138
|
+
assert "survivant" in p.stdout
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_graph_declaration_at_end_of_buffer():
|
|
142
|
+
"""`@graph as g` with no trailing newline: the inline-whitespace scan
|
|
143
|
+
used to loop for ever on the empty string returned at end of text."""
|
|
144
|
+
from ldpy.transpiler.core import Transpiler
|
|
145
|
+
t = Transpiler("@graph as g", "<c>", emit_prelude=False)
|
|
146
|
+
assert t.run().code.startswith("g = ")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_bound_at_end_of_buffer_is_an_error_not_a_crash():
|
|
150
|
+
from ldpy.transpiler import LdpySyntaxError
|
|
151
|
+
from ldpy.transpiler.core import Transpiler
|
|
152
|
+
import pytest
|
|
153
|
+
with pytest.raises(LdpySyntaxError):
|
|
154
|
+
Transpiler("e{ BOUND(", "<c>", emit_prelude=False).run()
|
|
@@ -170,3 +170,44 @@ def test_cli_under_real_debugpy(tmp_path):
|
|
|
170
170
|
p = run_debug(tmp_path, ["--listen", "127.0.0.1:0"])
|
|
171
171
|
assert p.returncode == 0, p.stderr
|
|
172
172
|
assert p.stdout.strip().endswith("2")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# ------------------------------------------------- où le .py est matérialisé
|
|
176
|
+
|
|
177
|
+
def test_shadow_rel_without_root_is_the_basename():
|
|
178
|
+
from ldpy.debug import shadow_rel
|
|
179
|
+
assert shadow_rel("/w/a/m.ldpy") == "m.ldpy"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def test_shadow_rel_mirrors_the_tree_under_the_root():
|
|
183
|
+
from ldpy.debug import shadow_rel
|
|
184
|
+
assert shadow_rel(os.path.join("/w", "a", "m.ldpy"), "/w") == \
|
|
185
|
+
os.path.join("a", "m.ldpy")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_shadow_rel_never_climbs_out_of_the_output_directory():
|
|
189
|
+
"""A source outside the root would give '../…': the shadow would land
|
|
190
|
+
beside the build directory instead of inside it."""
|
|
191
|
+
from ldpy.debug import shadow_rel
|
|
192
|
+
assert shadow_rel("/elsewhere/m.ldpy", "/w") == "m.ldpy"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_cli_root_keeps_same_named_files_apart(tmp_path):
|
|
196
|
+
"""One build directory for the whole workspace: `a/m.ldpy` and
|
|
197
|
+
`b/m.ldpy` must not both write `m.py`."""
|
|
198
|
+
out = tmp_path / "build"
|
|
199
|
+
shadows = []
|
|
200
|
+
for folder in ("a", "b"):
|
|
201
|
+
d = tmp_path / folder
|
|
202
|
+
d.mkdir()
|
|
203
|
+
(d / "m.ldpy").write_text(SRC)
|
|
204
|
+
env = dict(os.environ, PYTHONPATH=REPO)
|
|
205
|
+
p = subprocess.run(
|
|
206
|
+
[sys.executable, "-m", "ldpy.debug", str(d / "m.ldpy"),
|
|
207
|
+
"-o", str(out), "--root", str(tmp_path), "--breakpoints", "2"],
|
|
208
|
+
capture_output=True, text=True, cwd=REPO, env=env, timeout=120)
|
|
209
|
+
assert p.returncode == 0, p.stderr
|
|
210
|
+
shadows.append(json.loads(p.stdout)["shadow"])
|
|
211
|
+
assert len(set(shadows)) == 2
|
|
212
|
+
assert all(os.path.isfile(s) for s in shadows)
|
|
213
|
+
assert os.path.basename(os.path.dirname(shadows[0])) == "a"
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""La documentation est exécutable : chaque bloc ``ldpy`` des pages de docs/
|
|
2
|
+
est transpilé PUIS EXÉCUTÉ ; chaque bloc ``python`` est exécuté. Un snippet
|
|
3
|
+
qui ne tourne pas ne peut pas rester dans la doc."""
|
|
4
|
+
|
|
5
|
+
import glob
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
12
|
+
DOCS = sorted(glob.glob(os.path.join(REPO, "docs", "**", "*.md"),
|
|
13
|
+
recursive=True))
|
|
14
|
+
|
|
15
|
+
FENCE = re.compile(r"^```(\w+)\n(.*?)^```", re.M | re.S)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def blocks(lang):
|
|
19
|
+
out = []
|
|
20
|
+
for path in DOCS:
|
|
21
|
+
text = open(path, encoding="utf-8").read()
|
|
22
|
+
for i, m in enumerate(FENCE.finditer(text)):
|
|
23
|
+
if m.group(1) == lang:
|
|
24
|
+
out.append(pytest.param(
|
|
25
|
+
m.group(2),
|
|
26
|
+
id="%s#%d" % (os.path.relpath(path, REPO), i)))
|
|
27
|
+
return out
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_docs_exist_and_cover_diataxis():
|
|
31
|
+
rels = {os.path.relpath(p, os.path.join(REPO, "docs")) for p in DOCS}
|
|
32
|
+
assert "README.md" in rels
|
|
33
|
+
for quadrant in ("tutorials", "how-to", "reference", "explanation"):
|
|
34
|
+
assert any(r.startswith(quadrant + os.sep) for r in rels), quadrant
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.parametrize("code", blocks("ldpy"))
|
|
38
|
+
def test_ldpy_snippets_run(code):
|
|
39
|
+
from ldpy.transpiler import transpile
|
|
40
|
+
result = transpile(code, "<docs>")
|
|
41
|
+
exec(compile(result.code, "<docs>", "exec"), {"__name__": "docs"})
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.mark.parametrize("code", blocks("python"))
|
|
45
|
+
def test_python_snippets_run(code):
|
|
46
|
+
exec(compile(code, "<docs>", "exec"), {"__name__": "docs"})
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# --------------------------------------------------------------------------
|
|
50
|
+
# The same snippets, typed into the interactive console.
|
|
51
|
+
#
|
|
52
|
+
# The console transpiles ONE ENTRY AT A TIME: what a file keeps in a single
|
|
53
|
+
# pass (the current @prefix, @graph, @bindings) has to survive from one entry
|
|
54
|
+
# to the next. A doc snippet that runs as a file but not in the console is a
|
|
55
|
+
# console bug, not a doc bug — hence this second pass over the same blocks.
|
|
56
|
+
# --------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
def _pasteable(code):
|
|
59
|
+
"""A basic Python console (`code.InteractiveConsole`, what we build on)
|
|
60
|
+
ends a block on a blank line and has no bracketed paste. Blocks that
|
|
61
|
+
depend on either are out of scope here — CPython's own REPL is no
|
|
62
|
+
better."""
|
|
63
|
+
if '"""' in code or "'''" in code:
|
|
64
|
+
return False
|
|
65
|
+
lines = code.splitlines()
|
|
66
|
+
for i, line in enumerate(lines):
|
|
67
|
+
if line.strip():
|
|
68
|
+
continue
|
|
69
|
+
following = next((x for x in lines[i + 1:] if x.strip()), "")
|
|
70
|
+
if following.startswith((" ", "\t")):
|
|
71
|
+
return False # blank line inside a block
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _console_output(code):
|
|
76
|
+
"""Feed a snippet line by line, as one would type it; return everything
|
|
77
|
+
the console reported (errors and warnings)."""
|
|
78
|
+
import contextlib
|
|
79
|
+
import io
|
|
80
|
+
from ldpy.console import LdpyConsole
|
|
81
|
+
|
|
82
|
+
console = LdpyConsole()
|
|
83
|
+
reported = io.StringIO()
|
|
84
|
+
console.write = reported.write
|
|
85
|
+
lines = code.splitlines() + [""]
|
|
86
|
+
for line in lines:
|
|
87
|
+
# dedenting to column 0 closes the block above, as typing does
|
|
88
|
+
if console.buffer and line[:1] not in (" ", "\t", ""):
|
|
89
|
+
console.push("")
|
|
90
|
+
with contextlib.redirect_stdout(io.StringIO()), \
|
|
91
|
+
contextlib.redirect_stderr(reported):
|
|
92
|
+
console.push(line)
|
|
93
|
+
return reported.getvalue()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@pytest.mark.parametrize("code", blocks("ldpy"))
|
|
97
|
+
def test_ldpy_snippets_run_in_the_console(code):
|
|
98
|
+
if not _pasteable(code):
|
|
99
|
+
pytest.skip("block relies on paste, not on typing")
|
|
100
|
+
reported = _console_output(code)
|
|
101
|
+
errors = [ligne for ligne in reported.splitlines()
|
|
102
|
+
if ligne.strip() and "LdpyWarning" not in ligne]
|
|
103
|
+
assert not errors, "\n".join(errors)
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"""La documentation est exécutable : chaque bloc ``ldpy`` des pages de docs/
|
|
2
|
-
est transpilé PUIS EXÉCUTÉ ; chaque bloc ``python`` est exécuté. Un snippet
|
|
3
|
-
qui ne tourne pas ne peut pas rester dans la doc."""
|
|
4
|
-
|
|
5
|
-
import glob
|
|
6
|
-
import os
|
|
7
|
-
import re
|
|
8
|
-
|
|
9
|
-
import pytest
|
|
10
|
-
|
|
11
|
-
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
12
|
-
DOCS = sorted(glob.glob(os.path.join(REPO, "docs", "**", "*.md"),
|
|
13
|
-
recursive=True))
|
|
14
|
-
|
|
15
|
-
FENCE = re.compile(r"^```(\w+)\n(.*?)^```", re.M | re.S)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def blocks(lang):
|
|
19
|
-
out = []
|
|
20
|
-
for path in DOCS:
|
|
21
|
-
text = open(path, encoding="utf-8").read()
|
|
22
|
-
for i, m in enumerate(FENCE.finditer(text)):
|
|
23
|
-
if m.group(1) == lang:
|
|
24
|
-
out.append(pytest.param(
|
|
25
|
-
m.group(2),
|
|
26
|
-
id="%s#%d" % (os.path.relpath(path, REPO), i)))
|
|
27
|
-
return out
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def test_docs_exist_and_cover_diataxis():
|
|
31
|
-
rels = {os.path.relpath(p, os.path.join(REPO, "docs")) for p in DOCS}
|
|
32
|
-
assert "README.md" in rels
|
|
33
|
-
for quadrant in ("tutorials", "how-to", "reference", "explanation"):
|
|
34
|
-
assert any(r.startswith(quadrant + os.sep) for r in rels), quadrant
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
@pytest.mark.parametrize("code", blocks("ldpy"))
|
|
38
|
-
def test_ldpy_snippets_run(code):
|
|
39
|
-
from ldpy.transpiler import transpile
|
|
40
|
-
result = transpile(code, "<docs>")
|
|
41
|
-
exec(compile(result.code, "<docs>", "exec"), {"__name__": "docs"})
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
@pytest.mark.parametrize("code", blocks("python"))
|
|
45
|
-
def test_python_snippets_run(code):
|
|
46
|
-
exec(compile(code, "<docs>", "exec"), {"__name__": "docs"})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{linked_data_python-0.5.1 → linked_data_python-0.5.2}/linked_data_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{linked_data_python-0.5.1 → linked_data_python-0.5.2}/linked_data_python.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{linked_data_python-0.5.1 → linked_data_python-0.5.2}/linked_data_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{linked_data_python-0.5.1 → linked_data_python-0.5.2}/linked_data_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|