Skip to content

Commit 8813c4d

Browse files
committed
fix: expose exports from translator in sandbox
1 parent 2fa1f4f commit 8813c4d

3 files changed

Lines changed: 125 additions & 28 deletions

File tree

scripts/import_and_patch_translators.py

Lines changed: 123 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -97,39 +97,137 @@ def _is_function_defined(text: str, fn_name: str) -> bool:
9797
return any(re.search(pattern, text) for pattern in patterns)
9898

9999

100-
def append_exports(text: str) -> tuple[str, bool]:
101-
present = [fn for fn in TRANSLATOR_EXPORT_CANDIDATES if _is_function_defined(text, fn)]
102-
if not present:
103-
return text, False
100+
def _parse_generated_export_specs(specs_text: str) -> list[str]:
101+
return [spec.strip() for spec in specs_text.split(",") if spec.strip()]
104102

105-
export_line = f"export {{ {', '.join(present)} }};"
106-
export_snippet = (
107-
"\n// Export translator functions as ES module bindings for adapter\n"
108-
+ export_line
109-
+ "\n"
110-
)
111103

112-
# Remove a previously generated metadata+functions trailing block
113-
generated_metadata_block_re = re.compile(
114-
r"\n?// Export translator metadata for sandbox adapter\n"
115-
r"export\s+const\s+ZOTERO_TRANSLATOR_INFO\s*=\s*[^\n]*;\n"
116-
r"(?:// Export translator functions as ES module bindings for adapter\n"
117-
r"export\s*\{[^}]*\};\n)?\s*\Z",
104+
def _build_exports_body_from_specs(specs: list[str]) -> str:
105+
entries = []
106+
for spec in specs:
107+
if " as " in spec:
108+
local_name, export_name = [part.strip() for part in spec.split(" as ", 1)]
109+
entries.append(f"{export_name}: {local_name}")
110+
else:
111+
entries.append(spec)
112+
if not entries:
113+
return ""
114+
return " " + ", ".join(entries) + " "
115+
116+
117+
def _extract_and_remove_exports_object(text: str) -> tuple[str, str | None, bool]:
118+
m = re.search(r"(?:export\s+)?(?:var|let|const)\s+exports\s*=\s*\{", text)
119+
if not m:
120+
return text, None, False
121+
122+
declaration_start = m.start()
123+
open_brace_index = text.find("{", declaration_start)
124+
if open_brace_index == -1:
125+
return text, None, False
126+
127+
i = open_brace_index
128+
depth = 0
129+
in_str = None
130+
esc = False
131+
close_brace_index = None
132+
while i < len(text):
133+
ch = text[i]
134+
if in_str:
135+
if esc:
136+
esc = False
137+
elif ch == "\\":
138+
esc = True
139+
elif ch == in_str:
140+
in_str = None
141+
else:
142+
if ch == '"' or ch == "'":
143+
in_str = ch
144+
elif ch == "{":
145+
depth += 1
146+
elif ch == "}":
147+
depth -= 1
148+
if depth == 0:
149+
close_brace_index = i
150+
break
151+
i += 1
152+
153+
if close_brace_index is None:
154+
return text, None, False
155+
156+
body = text[open_brace_index + 1 : close_brace_index]
157+
158+
end = close_brace_index + 1
159+
if end < len(text) and text[end] == ";":
160+
end += 1
161+
162+
return text[:declaration_start] + text[end:], body, True
163+
164+
165+
def _remove_generated_export_blocks(text: str) -> tuple[str, list[str], str | None, bool]:
166+
generated_block_re = re.compile(
167+
r"\n?(?:(?:// Export translator compatibility exports for adapter\n)+"
168+
r"export\s+const\s+exports\s*=\s*\{([\s\S]*?)\};\n*)?"
169+
r"// Export translator functions as ES module bindings for adapter\n"
170+
r"export\s*\{([^}]*)\};\s*\Z",
118171
re.MULTILINE,
119172
)
120-
text_without_generated = generated_metadata_block_re.sub("", text)
173+
m = generated_block_re.search(text)
174+
if m:
175+
return text[:m.start()].rstrip(), _parse_generated_export_specs(m.group(2)), m.group(1), True
121176

122-
# Backward compatibility: remove old generated function-only trailing block
123-
generated_block_re = re.compile(
124-
r"\n?// Export translator functions as ES module bindings for adapter\n"
125-
r"export\s*\{[^}]*\};\s*\Z",
177+
compatibility_only_re = re.compile(
178+
r"\n?(?:// Export translator compatibility exports for adapter\n)+"
179+
r"export\s+const\s+exports\s*=\s*\{([\s\S]*?)\};\s*\Z",
126180
re.MULTILINE,
127181
)
128-
new_text = generated_block_re.sub("", text_without_generated).rstrip()
182+
m = compatibility_only_re.search(text)
183+
if m:
184+
return text[:m.start()].rstrip(), [], m.group(1), True
129185

130-
candidate = new_text + export_snippet
131-
if candidate == text or candidate + "\n" == text:
132-
return text, False
186+
return text, [], None, False
187+
188+
189+
def append_exports(text: str) -> tuple[str, bool]:
190+
original_text = text
191+
text, old_generated_specs, old_generated_exports_body, removed_generated_blocks = _remove_generated_export_blocks(text)
192+
text, exports_body, removed_exports_object = _extract_and_remove_exports_object(text)
193+
194+
present = [fn for fn in TRANSLATOR_EXPORT_CANDIDATES if _is_function_defined(text, fn)]
195+
specs: list[str] = []
196+
seen_specs: set[str] = set()
197+
198+
for fn in present:
199+
if fn not in seen_specs:
200+
seen_specs.add(fn)
201+
specs.append(fn)
202+
203+
compatibility_exports_body = exports_body or old_generated_exports_body
204+
if compatibility_exports_body is None and old_generated_specs:
205+
extra_specs = [spec for spec in old_generated_specs if spec not in seen_specs]
206+
compatibility_exports_body = _build_exports_body_from_specs(extra_specs)
207+
208+
snippets = []
209+
if compatibility_exports_body and compatibility_exports_body.strip():
210+
snippets.append(
211+
"\n// Export translator compatibility exports for adapter\n"
212+
+ "export const exports = {"
213+
+ compatibility_exports_body
214+
+ "};\n"
215+
)
216+
217+
if specs:
218+
export_line = f"export {{ {', '.join(specs)} }};"
219+
snippets.append(
220+
"\n// Export translator functions as ES module bindings for adapter\n"
221+
+ export_line
222+
+ "\n"
223+
)
224+
225+
if not snippets:
226+
return text, text != original_text
227+
228+
candidate = text.rstrip() + "".join(snippets)
229+
if candidate == original_text or candidate + "\n" == original_text:
230+
return original_text, False
133231

134232
return candidate, True
135233

sources/sandboxManager.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,7 @@ class SandboxManager {
102102

103103
this.sandbox.ZOTERO_TRANSLATOR_INFO = exported.ZOTERO_TRANSLATOR_INFO;
104104

105-
this.sandbox.exports =
106-
(exported && (exported.exports || (exported.default && exported.default.exports))) || {};
105+
this.sandbox.exports = exported.exports;
107106

108107
return exported;
109108
}

0 commit comments

Comments
 (0)