Repository navigation
Expand file tree
/
Copy pathturtle_to_stdout.py
More file actions
executable file
·212 lines (160 loc) · 6.22 KB
/
Copy pathturtle_to_stdout.py
File metadata and controls
executable file
·212 lines (160 loc) · 6.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#!/usr/bin/env python3
"""Convert an RDF/Turtle CST back to source files, output as JSON array."""
import json
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
from rdflib import Graph, Namespace, RDF
from rdflib.collection import Collection
from pylibsrcml import srcMLArchiveRead
SRCML_NS = "http://www.srcML.org/srcML/src"
CST = Namespace("http://trustgraph.ai/cst#")
def string_to_source_literal(s: str, is_docstring: bool = False) -> str:
"""Convert actual string value back to source representation.
F-strings are returned unchanged (they were never normalized).
Docstrings use triple double quotes.
Regular strings use double quotes to match Python conventions.
"""
# F-strings were not normalized, return as-is
if s.startswith(('f"', "f'", 'F"', "F'", 'rf"', "rf'", 'fr"', "fr'")):
return s
# Docstrings use triple quotes
if is_docstring:
return f'"""{s}"""'
# Use double quotes, escaping internal double quotes and backslashes
escaped = s.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r').replace('\t', '\\t')
return f'"{escaped}"'
def dict_to_xml_element(d: dict, ns: str = SRCML_NS) -> ET.Element | str:
"""Convert a dictionary back to an XML element."""
if d.get("tag") == "#text":
return d.get("text", "")
tag = d["tag"]
full_tag = f"{{{ns}}}{tag}"
elem = ET.Element(full_tag)
if "attrs" in d:
for k, v in d["attrs"].items():
elem.set(k, v)
if "text" in d:
elem.text = d["text"]
if "children" in d:
last_elem = None
for child_dict in d["children"]:
child = dict_to_xml_element(child_dict, ns)
if isinstance(child, str):
if last_elem is not None:
last_elem.tail = (last_elem.tail or "") + child
else:
elem.text = (elem.text or "") + child
else:
elem.append(child)
last_elem = child
return elem
def cst_to_source(cst: dict, filename: str) -> str:
"""Convert a JSON CST back to source code via srcML."""
unit_elem = dict_to_xml_element(cst)
unit_elem.set("revision", "1.0.0")
unit_elem.set("language", "Python")
unit_elem.set("filename", filename)
ET.register_namespace("", SRCML_NS)
root = ET.Element(f"{{{SRCML_NS}}}unit")
root.set("revision", "1.0.0")
root.append(unit_elem)
# Use UTF-8 encoding for proper Unicode support
xml_bytes = ET.tostring(root, encoding="utf-8", xml_declaration=True)
xml_str = xml_bytes.decode("utf-8")
archive = srcMLArchiveRead(xml_str)
archive.set_src_encoding("UTF-8")
unit = archive.read_unit()
if unit is None:
raise ValueError(f"Failed to read unit for {filename}")
unit.set_src_encoding("UTF-8")
source = unit.unparse_string()
archive.close()
return source
def rdf_node_to_dict(graph: Graph, node) -> dict:
"""Convert an RDF node back to a CST dict."""
# Get the type
node_type = graph.value(node, RDF.type)
if node_type is None:
raise ValueError(f"Node {node} has no type")
type_str = str(node_type)
if type_str.startswith(str(CST)):
tag = type_str[len(str(CST)):]
else:
tag = type_str
# Handle TextNode specially
if tag == "TextNode":
text = graph.value(node, CST.text)
return {"tag": "#text", "text": str(text) if text else ""}
result = {"tag": tag}
# Get attributes first (need to know if it's a string literal)
attrs = {}
for pred, obj in graph.predicate_objects(node):
pred_str = str(pred)
if pred_str.startswith(str(CST) + "attr_"):
attr_name = pred_str[len(str(CST) + "attr_"):]
attrs[attr_name] = str(obj)
if attrs:
result["attrs"] = attrs
# Get text content, converting string literals back to source form
text = graph.value(node, CST.text)
if text is not None:
text_str = str(text)
# Convert normalized string back to source literal
if tag == "literal" and attrs.get("type") == "string":
is_docstring = attrs.get("format") == "docstring"
text_str = string_to_source_literal(text_str, is_docstring=is_docstring)
result["text"] = text_str
# Get children (rdf:List)
children_list = graph.value(node, CST.children)
if children_list is not None:
children = []
collection = Collection(graph, children_list)
for child_node in collection:
children.append(rdf_node_to_dict(graph, child_node))
result["children"] = children
return result
def turtle_to_files(turtle_input: str | Path) -> list[dict]:
"""
Read Turtle CST and convert back to source files.
Returns list of {"path": ..., "source": ...} dicts.
"""
graph = Graph()
# Load Turtle
if isinstance(turtle_input, Path) or (
isinstance(turtle_input, str) and not turtle_input.strip().startswith("@")
):
path = Path(turtle_input)
if not path.exists():
print(f"Error: {path} not found", file=sys.stderr)
sys.exit(1)
graph.parse(path, format="turtle")
else:
graph.parse(data=turtle_input, format="turtle")
results = []
# Find all File nodes
for file_node in graph.subjects(RDF.type, CST.File):
filepath = str(graph.value(file_node, CST.path))
root_node = graph.value(file_node, CST.root)
if root_node is None:
print(f"Warning: File {filepath} has no root node", file=sys.stderr)
continue
cst = rdf_node_to_dict(graph, root_node)
source = cst_to_source(cst, filepath)
results.append({
"path": filepath,
"source": source
})
# Sort by path for consistent output
results.sort(key=lambda x: x["path"])
return results
def main():
if len(sys.argv) < 2:
print(f"Usage: {sys.argv[0]} <archive.ttl>", file=sys.stderr)
print(f" Reads Turtle CST and outputs source files as JSON array", file=sys.stderr)
sys.exit(1)
turtle_file = sys.argv[1]
files = turtle_to_files(turtle_file)
print(json.dumps(files, indent=2))
if __name__ == "__main__":
main()