linked-data-python 0.0.3__py3-none-any.whl → 0.2.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.
- ldpy/__init__.py +35 -11
- ldpy/__main__.py +75 -250
- ldpy/build.py +91 -0
- ldpy/console.py +116 -0
- ldpy/debug.py +235 -0
- ldpy/formatter.py +329 -0
- ldpy/importer.py +110 -0
- ldpy/lsp/__init__.py +11 -0
- ldpy/lsp/__main__.py +4 -0
- ldpy/lsp/backend.py +118 -0
- ldpy/lsp/rpc.py +104 -0
- ldpy/lsp/server.py +353 -0
- ldpy/lsp/translate.py +140 -0
- ldpy/pygments_lexer.py +613 -0
- ldpy/runtime.py +931 -0
- ldpy/sparql.py +551 -0
- ldpy/transpiler/__init__.py +14 -0
- ldpy/transpiler/core.py +2558 -0
- ldpy/transpiler/errors.py +38 -0
- ldpy/transpiler/linemap.py +292 -0
- linked_data_python-0.2.0.dist-info/METADATA +158 -0
- linked_data_python-0.2.0.dist-info/RECORD +26 -0
- {linked_data_python-0.0.3.dist-info → linked_data_python-0.2.0.dist-info}/WHEEL +1 -1
- linked_data_python-0.2.0.dist-info/entry_points.txt +9 -0
- {linked_data_python-0.0.3.dist-info → linked_data_python-0.2.0.dist-info/licenses}/LICENSE.md +0 -0
- {linked_data_python-0.0.3.dist-info → linked_data_python-0.2.0.dist-info}/top_level.txt +0 -0
- ldpy/grun/lib.py +0 -63
- ldpy/grun/util.py +0 -11
- ldpy/ldpy.py +0 -183
- ldpy/rewriter/IndentedStringWriter.py +0 -54
- ldpy/rewriter/LDPythonRewriter.py +0 -677
- ldpy/rewriter/MultiChannelTokenStream.py +0 -127
- ldpy/rewriter/Result.py +0 -49
- ldpy/rewriter/__init__.py +0 -7
- ldpy/rewriter/antlr/LDPythonLexer.py +0 -870
- ldpy/rewriter/antlr/LDPythonParser.py +0 -9336
- ldpy/rewriter/antlr/LDPythonVisitor.py +0 -573
- ldpy/sparql/builtin.py +0 -326
- linked_data_python-0.0.3.dist-info/METADATA +0 -139
- linked_data_python-0.0.3.dist-info/RECORD +0 -20
- linked_data_python-0.0.3.dist-info/entry_points.txt +0 -3
ldpy/__init__.py
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
"""Linked-Data Python (ldpy) v2.
|
|
2
|
+
|
|
3
|
+
An "island parsing" transpiler: Python is copied through untouched, the RDF
|
|
4
|
+
islands (@prefix, @base, IRIs, prefixed names, RDF literals, variables,
|
|
5
|
+
graphs g{...}) are rewritten into Python expressions built on ldpy.runtime.
|
|
6
|
+
|
|
7
|
+
The historical ANTLR chain (v1) is still reachable through the ldpy.rewriter
|
|
8
|
+
package but is no longer imported by default.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from ldpy.transpiler import transpile, LdpySyntaxError, LdpyWarning
|
|
12
|
+
from ldpy.importer import install, uninstall, install_excepthook
|
|
13
|
+
|
|
14
|
+
__version__ = "0.1.0.dev0"
|
|
15
|
+
__date__ = "2026-08-26"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def transform_source(source, filename="<ldpy>"):
|
|
19
|
+
"""Simplified v1 compatibility: returns (python_code, prefixes, map)."""
|
|
20
|
+
result = transpile(source, filename)
|
|
21
|
+
return result.code, result.prefixes, result.map
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def Coercion(rules):
|
|
25
|
+
"""Politique de conversion Python -> RDF (fiche 020) — voir
|
|
26
|
+
ldpy.runtime.Coercion; exposed here as public API."""
|
|
27
|
+
from ldpy.runtime import Coercion as _C
|
|
28
|
+
return _C(rules)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def instantiateBGP(input, solutionMappings, initialGraph=None):
|
|
32
|
+
"""Instantiate a graph template with solution mappings
|
|
33
|
+
(re-export of ldpy.runtime.instantiateBGP, v1 compatibility)."""
|
|
34
|
+
from ldpy.runtime import instantiateBGP as _f
|
|
35
|
+
return _f(input, solutionMappings, initialGraph)
|
ldpy/__main__.py
CHANGED
|
@@ -1,257 +1,82 @@
|
|
|
1
|
-
|
|
2
|
-
# The MIT License (MIT)
|
|
3
|
-
#
|
|
4
|
-
# Copyright (c) 2022 by Maxime Lefrançois
|
|
5
|
-
#
|
|
6
|
-
# Permission is hereby granted, free of charge, to any person
|
|
7
|
-
# obtaining a copy of this software and associated documentation
|
|
8
|
-
# files (the "Software"), to deal in the Software without
|
|
9
|
-
# restriction, including without limitation the rights to use,
|
|
10
|
-
# copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
-
# copies of the Software, and to permit persons to whom the
|
|
12
|
-
# Software is furnished to do so, subject to the following
|
|
13
|
-
# conditions:
|
|
14
|
-
#
|
|
15
|
-
# The above copyright notice and this permission notice shall be
|
|
16
|
-
# included in all copies or substantial portions of the Software.
|
|
17
|
-
#
|
|
18
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
19
|
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
20
|
-
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
21
|
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
22
|
-
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
23
|
-
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
24
|
-
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
25
|
-
# OTHER DEALINGS IN THE SOFTWARE.
|
|
26
|
-
#
|
|
27
|
-
# Project : ldpython-parser; Linked-Data Python to Python/Micropython Rewriter
|
|
28
|
-
# https://gitlab.com/coswot/linked-data-python/ldpy
|
|
29
|
-
# Developed by : Maxime Lefrançois, maxime.lefrancois@emse.fr
|
|
30
|
-
#
|
|
31
|
-
"""ldpy extends the Python syntax with primitives from the Semantic Web such as namespaces, RDF terms, and RDF graphs.
|
|
1
|
+
"""ldpy — command-line execution.
|
|
32
2
|
|
|
33
|
-
|
|
3
|
+
Usage :
|
|
4
|
+
python -m ldpy source.ldpy # transpile and run
|
|
5
|
+
python -m ldpy -s source.ldpy # also print the transformed code
|
|
6
|
+
python -m ldpy -t source.ldpy # transpile seulement (stdout)
|
|
34
7
|
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
35
10
|
import os
|
|
36
11
|
import sys
|
|
37
|
-
import platform
|
|
38
|
-
import argparse
|
|
39
|
-
import ldpy
|
|
40
|
-
from importlib import import_module
|
|
41
|
-
import types
|
|
42
|
-
from code import InteractiveConsole
|
|
43
|
-
import readline
|
|
44
|
-
|
|
45
|
-
# include a subset of the arguments from https://github.com/aroberge/ideas/blob/master/ideas/__main__.py
|
|
46
|
-
parser = argparse.ArgumentParser(
|
|
47
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
48
|
-
description=__doc__,
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
parser.add_argument(
|
|
52
|
-
"-v",
|
|
53
|
-
"--version",
|
|
54
|
-
help="only displays the current version.",
|
|
55
|
-
action="store_true",
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
parser.add_argument(
|
|
59
|
-
"-l",
|
|
60
|
-
'--debug-lexer',
|
|
61
|
-
dest='lexer',
|
|
62
|
-
action='store_true',
|
|
63
|
-
default=False,
|
|
64
|
-
help='print the lexer output'
|
|
65
|
-
)
|
|
66
|
-
|
|
67
|
-
parser.add_argument(
|
|
68
|
-
'-p',
|
|
69
|
-
'--debug-parser',
|
|
70
|
-
dest='parser',
|
|
71
|
-
action='store_true',
|
|
72
|
-
default=False,
|
|
73
|
-
help='print the parser output'
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
parser.add_argument(
|
|
77
|
-
"-s",
|
|
78
|
-
"--show_changes",
|
|
79
|
-
action="store_true",
|
|
80
|
-
help="""shows the transformed code before it is executed.""",
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
parser.add_argument(
|
|
84
|
-
"-i",
|
|
85
|
-
action="store_true",
|
|
86
|
-
help="""starts the interactive console after executing a source"""
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
group = parser.add_mutually_exclusive_group(required=False)
|
|
90
|
-
|
|
91
|
-
group.add_argument(
|
|
92
|
-
"-m",
|
|
93
|
-
"--module",
|
|
94
|
-
dest="module",
|
|
95
|
-
type=str,
|
|
96
|
-
help="""Run library module as a script. The module may be a .ldpy or a .py file.""",
|
|
97
|
-
)
|
|
98
12
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def push(self, line):
|
|
124
|
-
"""Push a ldpy line to the interpreter.
|
|
125
|
-
|
|
126
|
-
The line should not have a trailing newline; it may have
|
|
127
|
-
internal newlines. The line is appended to a buffer and the
|
|
128
|
-
interpreter's runsource() method is called with the
|
|
129
|
-
concatenated contents of the buffer as source. If this
|
|
130
|
-
indicates that the command was executed or invalid, the buffer
|
|
131
|
-
is reset; otherwise, the command is incomplete, and the buffer
|
|
132
|
-
is left as it was after the line was appended. The return
|
|
133
|
-
value is 1 if more input is required, 0 if the line was dealt
|
|
134
|
-
with in some way (this is the same as runsource()).
|
|
135
|
-
|
|
136
|
-
"""
|
|
137
|
-
self.ldpybuffer.append(line)
|
|
138
|
-
ldpysource = "\n".join(self.ldpybuffer)
|
|
139
|
-
|
|
140
|
-
try:
|
|
141
|
-
namespaces = self.locals.get("__namespaces__", dict())
|
|
142
|
-
transformed_source, namespaces, linemap = ldpy.transform_source(ldpysource, self.filename, namespaces)
|
|
143
|
-
self.locals["__namespaces__"] = namespaces
|
|
144
|
-
except SyntaxError as ex:
|
|
145
|
-
if "mismatched input '<EOF>' expecting" in ex.msg:
|
|
146
|
-
return True
|
|
147
|
-
self.write(ex.msg + "\n")
|
|
148
|
-
self.resetldpybuffer()
|
|
149
|
-
return False
|
|
150
|
-
self.resetldpybuffer()
|
|
151
|
-
|
|
152
|
-
if self.locals["__debug_ldpy__"]:
|
|
153
|
-
print("", file=sys.stderr)
|
|
154
|
-
for transformed_line in transformed_source.split("\n"):
|
|
155
|
-
print("ldpy>>> " + transformed_line, file=sys.stderr)
|
|
156
|
-
|
|
157
|
-
for line in transformed_source.split("\n"):
|
|
158
|
-
self.buffer.append(line)
|
|
159
|
-
source = "\n".join(self.buffer)
|
|
160
|
-
more = self.runsource(source, self.filename)
|
|
161
|
-
if not more:
|
|
162
|
-
self.resetbuffer()
|
|
163
|
-
return more
|
|
164
|
-
|
|
165
|
-
def interact(self):
|
|
166
|
-
from rdflib import __version__ as rdflib_version
|
|
167
|
-
BANNER = (
|
|
168
|
-
f">>> ldpy interactive console version {ldpy.__version__}. "
|
|
169
|
-
+ f"[rdflib version {rdflib_version}, "
|
|
170
|
-
+ f"Python version: {platform.python_version()}]"
|
|
171
|
-
)
|
|
172
|
-
InteractiveConsole.interact(self, banner=BANNER)
|
|
173
|
-
|
|
174
|
-
def rewrite_traceback(tb, linemap):
|
|
175
|
-
if not tb:
|
|
176
|
-
return None
|
|
177
|
-
tb_frame = tb.tb_frame
|
|
178
|
-
if "__name__" in tb_frame.f_globals:
|
|
179
|
-
name = tb_frame.f_globals["__name__"]
|
|
180
|
-
if name == 'importlib' or name == 'importlib._bootstrap' or name == 'ideas.import_hook':
|
|
181
|
-
return rewrite_traceback(tb.tb_next, linemap)
|
|
182
|
-
tb_lineno = tb.tb_lineno
|
|
183
|
-
tb_lasti = tb.tb_lasti
|
|
184
|
-
if "__file__" in tb_frame.f_locals and tb_frame.f_locals["__file__"].endswith(".ldpy"):
|
|
185
|
-
file = tb_frame.f_locals["__file__"]
|
|
186
|
-
if file in linemap:
|
|
187
|
-
linemap_for_file = linemap[file]
|
|
188
|
-
tb_lineno = linemap_for_file[tb_lineno-4] # -4 because hook source_init contains three lines, and index starts at 0
|
|
189
|
-
## it would be nice to have a second tb with the transformed .py source, but it's not possible to instantiate the builtin Frame type
|
|
190
|
-
# one could update the __file__ local attribute as follows
|
|
191
|
-
# tb_frame.f_locals["__file__"] = tb_frame.f_locals["__file__"][:-5] + ".py"
|
|
192
|
-
# and add the the traceback without changing the lineno
|
|
193
|
-
return types.TracebackType(rewrite_traceback(tb.tb_next, linemap), tb_frame, tb_lasti, tb_lineno)
|
|
13
|
+
import ldpy
|
|
14
|
+
from ldpy.transpiler import transpile, LdpySyntaxError
|
|
15
|
+
from ldpy.transpiler.linemap import compile_mapped
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main(argv=None):
|
|
19
|
+
parser = argparse.ArgumentParser(
|
|
20
|
+
prog="ldpy",
|
|
21
|
+
description="ldpy extends Python syntax with Semantic Web "
|
|
22
|
+
"primitives (IRIs, RDF literals, graphs).")
|
|
23
|
+
parser.add_argument("-v", "--version", action="store_true",
|
|
24
|
+
help="print the version and exit.")
|
|
25
|
+
parser.add_argument("-s", "--show-changes", action="store_true",
|
|
26
|
+
help="print the transformed code before running it.")
|
|
27
|
+
parser.add_argument("-t", "--transpile-only", action="store_true",
|
|
28
|
+
help="write the transformed code to stdout, do not run.")
|
|
29
|
+
parser.add_argument("-i", "--interactive", action="store_true",
|
|
30
|
+
help="open the interactive console after the script.")
|
|
31
|
+
parser.add_argument("-m", "--map", action="store_true",
|
|
32
|
+
help="also write the language map (<source>.map).")
|
|
33
|
+
parser.add_argument("source", nargs="?",
|
|
34
|
+
help=".ldpy (or .py) file to run.")
|
|
35
|
+
args = parser.parse_args(argv)
|
|
194
36
|
|
|
195
|
-
def main() -> None:
|
|
196
|
-
args = parser.parse_args()
|
|
197
37
|
if args.version:
|
|
198
|
-
print(
|
|
199
|
-
return
|
|
200
|
-
if args.source
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
except ModuleNotFoundError as exc:
|
|
244
|
-
print(f"{exc.__class__.__name__}: {exc.msg}", file=sys.stderr)
|
|
245
|
-
_locals = dict()
|
|
246
|
-
except Exception as exc:
|
|
247
|
-
linemap = ldpy.config.linemap
|
|
248
|
-
tb = exc.__traceback__
|
|
249
|
-
tb = rewrite_traceback(tb, linemap)
|
|
250
|
-
raise exc.with_traceback(tb)
|
|
251
|
-
|
|
252
|
-
if sys.flags.interactive or args.i:
|
|
253
|
-
"""Starts a special console that works with ldpy."""
|
|
254
|
-
c = LdpyConsole(show_changes=args.show_changes, locals=_locals)
|
|
255
|
-
c.interact()
|
|
256
|
-
|
|
257
|
-
main()
|
|
38
|
+
print("ldpy " + ldpy.__version__)
|
|
39
|
+
return 0
|
|
40
|
+
if not args.source:
|
|
41
|
+
from ldpy.console import interact
|
|
42
|
+
interact()
|
|
43
|
+
return 0
|
|
44
|
+
|
|
45
|
+
with open(args.source, "r", encoding="utf-8") as f:
|
|
46
|
+
source = f.read()
|
|
47
|
+
try:
|
|
48
|
+
result = transpile(source, args.source)
|
|
49
|
+
except LdpySyntaxError as e:
|
|
50
|
+
print(str(e), file=sys.stderr)
|
|
51
|
+
return 1
|
|
52
|
+
for w in result.warnings:
|
|
53
|
+
print(str(w), file=sys.stderr)
|
|
54
|
+
if args.map:
|
|
55
|
+
with open(args.source + ".map", "w", encoding="utf-8") as f:
|
|
56
|
+
f.write(result.map.to_json(indent=1))
|
|
57
|
+
if args.transpile_only:
|
|
58
|
+
sys.stdout.write(result.code)
|
|
59
|
+
return 0
|
|
60
|
+
if args.show_changes:
|
|
61
|
+
print("ldpy>>> ======== transformed code ========", file=sys.stderr)
|
|
62
|
+
for lineno, line in enumerate(result.code.split("\n"), 1):
|
|
63
|
+
print("ldpy>>> %3d: %s" % (lineno, line), file=sys.stderr)
|
|
64
|
+
print("ldpy>>> =================================", file=sys.stderr)
|
|
65
|
+
|
|
66
|
+
ldpy.install()
|
|
67
|
+
from ldpy.importer import MAPS
|
|
68
|
+
src_path = os.path.abspath(args.source)
|
|
69
|
+
MAPS[args.source] = MAPS[src_path] = result.map
|
|
70
|
+
# remapped compilation: tracebacks, pdb and debugpy all speak in
|
|
71
|
+
# .ldpy coordinates (record ldpy/011)
|
|
72
|
+
code = compile_mapped(result.code, result.map, src_path)
|
|
73
|
+
g = {"__name__": "__main__", "__file__": src_path}
|
|
74
|
+
exec(code, g)
|
|
75
|
+
if args.interactive:
|
|
76
|
+
from ldpy.console import interact
|
|
77
|
+
interact(locals=g, prefixes=result.prefixes, base=result.base)
|
|
78
|
+
return 0
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if __name__ == "__main__":
|
|
82
|
+
sys.exit(main())
|
ldpy/build.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Materialisation: transpile .ldpy files into a shadow directory.
|
|
2
|
+
|
|
3
|
+
`python -m ldpy.build src/ -o .ldpy-build` mirrors the tree and writes:
|
|
4
|
+
- <module>.py (generated code)
|
|
5
|
+
- <module>.ldpy.map (language map JSON)
|
|
6
|
+
|
|
7
|
+
This is the base of debugging (debugpy runs on the shadow .py files) and of
|
|
8
|
+
language server (voir docs/explanation/tooling.md)."""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
from ldpy.transpiler import transpile, LdpySyntaxError
|
|
15
|
+
|
|
16
|
+
DEFAULT_OUT = ".ldpy-build"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def build_file(src_path, out_dir, rel=None):
|
|
20
|
+
"""Transpile one file; returns (py_path, map_path, result)."""
|
|
21
|
+
with open(src_path, "r", encoding="utf-8") as f:
|
|
22
|
+
source = f.read()
|
|
23
|
+
rel = rel or os.path.basename(src_path)
|
|
24
|
+
stem = rel[:-5] if rel.endswith(".ldpy") else rel
|
|
25
|
+
py_path = os.path.join(out_dir, stem + ".py")
|
|
26
|
+
map_path = os.path.join(out_dir, stem + ".ldpy.map")
|
|
27
|
+
result = transpile(source, src_path)
|
|
28
|
+
result.map.generated_name = py_path
|
|
29
|
+
os.makedirs(os.path.dirname(py_path) or ".", exist_ok=True)
|
|
30
|
+
with open(py_path, "w", encoding="utf-8") as f:
|
|
31
|
+
f.write(result.code)
|
|
32
|
+
with open(map_path, "w", encoding="utf-8") as f:
|
|
33
|
+
f.write(result.map.to_json(indent=1))
|
|
34
|
+
# Source Map v3: for standard tooling
|
|
35
|
+
with open(py_path + ".map", "w", encoding="utf-8") as f:
|
|
36
|
+
f.write(result.map.to_sourcemap_v3_json())
|
|
37
|
+
return py_path, map_path, result
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def build_tree(root, out_dir):
|
|
41
|
+
"""Recursively transpile every .ldpy under root. Plain .py files are
|
|
42
|
+
copied as they are (a mixed package must stay importable)."""
|
|
43
|
+
built, errors = [], []
|
|
44
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
45
|
+
dirnames[:] = [d for d in dirnames
|
|
46
|
+
if d not in (DEFAULT_OUT, "__pycache__", ".git")]
|
|
47
|
+
for name in filenames:
|
|
48
|
+
src = os.path.join(dirpath, name)
|
|
49
|
+
rel = os.path.relpath(src, root)
|
|
50
|
+
if name.endswith(".ldpy"):
|
|
51
|
+
try:
|
|
52
|
+
built.append(build_file(src, out_dir, rel))
|
|
53
|
+
except LdpySyntaxError as e:
|
|
54
|
+
errors.append(e)
|
|
55
|
+
elif name.endswith(".py"):
|
|
56
|
+
dst = os.path.join(out_dir, rel)
|
|
57
|
+
os.makedirs(os.path.dirname(dst) or ".", exist_ok=True)
|
|
58
|
+
with open(src, "rb") as fi, open(dst, "wb") as fo:
|
|
59
|
+
fo.write(fi.read())
|
|
60
|
+
return built, errors
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def main(argv=None):
|
|
64
|
+
parser = argparse.ArgumentParser(
|
|
65
|
+
prog="ldpy.build",
|
|
66
|
+
description="Transpile .ldpy files into a shadow directory "
|
|
67
|
+
"(.py + .ldpy.map).")
|
|
68
|
+
parser.add_argument("source", help=".ldpy file or directory")
|
|
69
|
+
parser.add_argument("-o", "--out", default=DEFAULT_OUT,
|
|
70
|
+
help="output directory (default: %(default)s)")
|
|
71
|
+
args = parser.parse_args(argv)
|
|
72
|
+
|
|
73
|
+
if os.path.isdir(args.source):
|
|
74
|
+
built, errors = build_tree(args.source, args.out)
|
|
75
|
+
for e in errors:
|
|
76
|
+
print(str(e), file=sys.stderr)
|
|
77
|
+
print("%d file(s) transpiled into %s" % (len(built), args.out))
|
|
78
|
+
return 1 if errors else 0
|
|
79
|
+
try:
|
|
80
|
+
py_path, _, result = build_file(args.source, args.out)
|
|
81
|
+
except LdpySyntaxError as e:
|
|
82
|
+
print(str(e), file=sys.stderr)
|
|
83
|
+
return 1
|
|
84
|
+
for w in result.warnings:
|
|
85
|
+
print(str(w), file=sys.stderr)
|
|
86
|
+
print(py_path)
|
|
87
|
+
return 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
sys.exit(main())
|
ldpy/console.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Console interactive Linked-Data Python .
|
|
2
|
+
|
|
3
|
+
The point of the `ideas` package in v1 was to enter the interpreter and type
|
|
4
|
+
ldpy directly. This console does it without `ideas`: every entry is
|
|
5
|
+
transpiled then compiled; the state of top-level @prefix/@base persists from
|
|
6
|
+
one entry to the next (declarations made inside a block die with the entry —
|
|
7
|
+
block scope obliges).
|
|
8
|
+
|
|
9
|
+
$ python -m ldpy # console
|
|
10
|
+
$ python -m ldpy -i script.ldpy # run, then open the console
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import atexit
|
|
14
|
+
import code
|
|
15
|
+
import codeop
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
import ldpy
|
|
20
|
+
from ldpy.transpiler import LdpySyntaxError
|
|
21
|
+
from ldpy.transpiler.core import Transpiler, PRELUDE
|
|
22
|
+
|
|
23
|
+
HISTORY_FILE = os.path.join(os.path.expanduser("~"), ".ldpy_history")
|
|
24
|
+
HISTORY_LENGTH = 1000
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _setup_readline(locals):
|
|
28
|
+
"""Line editing (arrows, Ctrl-A/E…), persistent history and Tab
|
|
29
|
+
completion on console names. A no-op if the readline module is missing
|
|
30
|
+
(Windows without pyreadline)."""
|
|
31
|
+
try:
|
|
32
|
+
import readline
|
|
33
|
+
import rlcompleter
|
|
34
|
+
except ImportError:
|
|
35
|
+
return
|
|
36
|
+
readline.set_completer(rlcompleter.Completer(locals).complete)
|
|
37
|
+
if "libedit" in (getattr(readline, "__doc__", "") or ""):
|
|
38
|
+
readline.parse_and_bind("bind ^I rl_complete") # macOS libedit
|
|
39
|
+
else:
|
|
40
|
+
readline.parse_and_bind("tab: complete")
|
|
41
|
+
try:
|
|
42
|
+
readline.read_history_file(HISTORY_FILE)
|
|
43
|
+
except OSError:
|
|
44
|
+
pass
|
|
45
|
+
readline.set_history_length(HISTORY_LENGTH)
|
|
46
|
+
|
|
47
|
+
def _save():
|
|
48
|
+
try:
|
|
49
|
+
readline.write_history_file(HISTORY_FILE)
|
|
50
|
+
except OSError:
|
|
51
|
+
pass
|
|
52
|
+
atexit.register(_save)
|
|
53
|
+
|
|
54
|
+
BANNER = ("ldpy %s — console Linked-Data Python (Python %s)\n"
|
|
55
|
+
"RDF islands are accepted: @prefix, <iri>, ex:name, g{ ... }, ?v")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class LdpyConsole(code.InteractiveConsole):
|
|
59
|
+
"""Interactive console: transpile every entry before compiling it;
|
|
60
|
+
top-level @prefix/@base persist between entries."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, locals=None, filename="<console>",
|
|
63
|
+
prefixes=None, base=None):
|
|
64
|
+
if locals is None:
|
|
65
|
+
locals = {"__name__": "__console__", "__doc__": None}
|
|
66
|
+
super().__init__(locals=locals, filename=filename)
|
|
67
|
+
# the runtime prelude is installed once and for all
|
|
68
|
+
if "__namespaces__" not in self.locals:
|
|
69
|
+
exec(PRELUDE, self.locals)
|
|
70
|
+
self._prefixes = dict(prefixes or {})
|
|
71
|
+
self._prefix_cols = {k: 0 for k in self._prefixes}
|
|
72
|
+
self._base = base
|
|
73
|
+
|
|
74
|
+
def runsource(self, source, filename=None, symbol="single"):
|
|
75
|
+
"""Transpile then compile one entry; True = incomplete entry
|
|
76
|
+
(unclosed island or Python block), False = handled."""
|
|
77
|
+
filename = filename or self.filename
|
|
78
|
+
t = Transpiler(source, filename, emit_prelude=False)
|
|
79
|
+
t.prefixes = dict(self._prefixes)
|
|
80
|
+
t._prefix_col = dict(self._prefix_cols)
|
|
81
|
+
t.base = self._base
|
|
82
|
+
try:
|
|
83
|
+
result = t.run()
|
|
84
|
+
except LdpySyntaxError as e:
|
|
85
|
+
if getattr(e, "at_eof", False):
|
|
86
|
+
return True # unclosed island: wait for more
|
|
87
|
+
self.write(str(e) + "\n")
|
|
88
|
+
return False
|
|
89
|
+
try:
|
|
90
|
+
code_obj = codeop.compile_command(result.code, filename, symbol)
|
|
91
|
+
except (SyntaxError, ValueError, OverflowError):
|
|
92
|
+
self.showsyntaxerror(filename)
|
|
93
|
+
return False
|
|
94
|
+
if code_obj is None:
|
|
95
|
+
return True # Python incomplet (def, if, ...)
|
|
96
|
+
# the entry is complete: top-level declarations persist
|
|
97
|
+
t._unwind_scopes(0)
|
|
98
|
+
self._prefixes = dict(t.prefixes)
|
|
99
|
+
self._prefix_cols = dict(t._prefix_col)
|
|
100
|
+
self._base = t.base
|
|
101
|
+
for w in result.warnings:
|
|
102
|
+
self.write(str(w) + "\n")
|
|
103
|
+
self.runcode(code_obj)
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def interact(locals=None, prefixes=None, base=None):
|
|
108
|
+
"""Open the ldpy console (banner, Ctrl-D to leave)."""
|
|
109
|
+
console = LdpyConsole(locals=locals, prefixes=prefixes, base=base)
|
|
110
|
+
_setup_readline(console.locals)
|
|
111
|
+
banner = BANNER % (ldpy.__version__, sys.version.split()[0])
|
|
112
|
+
try:
|
|
113
|
+
console.interact(banner=banner, exitmsg="")
|
|
114
|
+
except SystemExit:
|
|
115
|
+
pass
|
|
116
|
+
return console
|