@yeyuan98/opencode-bioresearcher-plugin 1.4.0 → 1.5.0-alpha.0
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.
- package/README.md +35 -20
- package/dist/db-tools/backends/index.d.ts +11 -0
- package/dist/db-tools/backends/index.js +48 -0
- package/dist/db-tools/backends/mongodb/backend.d.ts +15 -0
- package/dist/db-tools/backends/mongodb/backend.js +76 -0
- package/dist/db-tools/backends/mongodb/connection.d.ts +27 -0
- package/dist/db-tools/backends/mongodb/connection.js +107 -0
- package/dist/db-tools/backends/mongodb/index.d.ts +4 -0
- package/dist/db-tools/backends/mongodb/index.js +3 -0
- package/dist/db-tools/backends/mongodb/translator.d.ts +30 -0
- package/dist/db-tools/backends/mongodb/translator.js +407 -0
- package/dist/db-tools/backends/mysql/backend.d.ts +15 -0
- package/dist/db-tools/backends/mysql/backend.js +57 -0
- package/dist/db-tools/backends/mysql/connection.d.ts +25 -0
- package/dist/db-tools/backends/mysql/connection.js +83 -0
- package/dist/db-tools/backends/mysql/index.d.ts +3 -0
- package/dist/db-tools/backends/mysql/index.js +2 -0
- package/dist/db-tools/backends/mysql/translator.d.ts +7 -0
- package/dist/db-tools/backends/mysql/translator.js +67 -0
- package/dist/db-tools/core/base.d.ts +17 -0
- package/dist/db-tools/core/base.js +51 -0
- package/dist/db-tools/core/config-loader.d.ts +3 -0
- package/dist/db-tools/core/config-loader.js +46 -0
- package/dist/db-tools/core/index.d.ts +2 -0
- package/dist/db-tools/core/index.js +2 -0
- package/dist/db-tools/core/jsonc-parser.d.ts +2 -0
- package/dist/db-tools/core/jsonc-parser.js +77 -0
- package/dist/db-tools/core/validator.d.ts +16 -0
- package/dist/db-tools/core/validator.js +118 -0
- package/dist/db-tools/executor.d.ts +13 -0
- package/dist/db-tools/executor.js +54 -0
- package/dist/db-tools/index.d.ts +51 -0
- package/dist/db-tools/index.js +27 -0
- package/dist/db-tools/interface/backend.d.ts +24 -0
- package/dist/db-tools/interface/backend.js +1 -0
- package/dist/db-tools/interface/connection.d.ts +21 -0
- package/dist/db-tools/interface/connection.js +11 -0
- package/dist/db-tools/interface/index.d.ts +4 -0
- package/dist/db-tools/interface/index.js +4 -0
- package/dist/db-tools/interface/query.d.ts +60 -0
- package/dist/db-tools/interface/query.js +1 -0
- package/dist/db-tools/interface/schema.d.ts +22 -0
- package/dist/db-tools/interface/schema.js +1 -0
- package/dist/db-tools/pool.d.ts +8 -0
- package/dist/db-tools/pool.js +49 -0
- package/dist/db-tools/tools/index.d.ts +27 -0
- package/dist/db-tools/tools/index.js +191 -0
- package/dist/db-tools/tools.d.ts +27 -0
- package/dist/db-tools/tools.js +111 -0
- package/dist/db-tools/types.d.ts +94 -0
- package/dist/db-tools/types.js +40 -0
- package/dist/db-tools/utils.d.ts +33 -0
- package/dist/db-tools/utils.js +94 -0
- package/dist/index.js +2 -0
- package/dist/skills/bioresearcher-core/README.md +210 -210
- package/dist/skills/bioresearcher-core/SKILL.md +128 -128
- package/dist/skills/bioresearcher-core/examples/contexts.json +29 -29
- package/dist/skills/bioresearcher-core/examples/data-exchange-example.md +303 -303
- package/dist/skills/bioresearcher-core/examples/template.md +49 -49
- package/dist/skills/bioresearcher-core/patterns/calculator.md +215 -215
- package/dist/skills/bioresearcher-core/patterns/data-exchange.md +406 -406
- package/dist/skills/bioresearcher-core/patterns/json-tools.md +263 -263
- package/dist/skills/bioresearcher-core/patterns/progress.md +127 -127
- package/dist/skills/bioresearcher-core/patterns/retry.md +110 -110
- package/dist/skills/bioresearcher-core/patterns/shell-commands.md +79 -79
- package/dist/skills/bioresearcher-core/patterns/subagent-waves.md +186 -186
- package/dist/skills/bioresearcher-core/patterns/table-tools.md +260 -260
- package/dist/skills/bioresearcher-core/patterns/user-confirmation.md +187 -187
- package/dist/skills/bioresearcher-core/python/template.md +273 -273
- package/dist/skills/bioresearcher-core/python/template.py +323 -323
- package/dist/skills/env-jsonc-setup/SKILL.md +206 -0
- package/package.json +3 -1
|
@@ -1,323 +1,323 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""Template engine for generating files from templates with placeholder replacement.
|
|
3
|
-
|
|
4
|
-
This module provides functionality for:
|
|
5
|
-
- Filling templates with placeholder replacement
|
|
6
|
-
- Escaping text for markdown code blocks
|
|
7
|
-
- Batch generation from template + contexts
|
|
8
|
-
|
|
9
|
-
Usage:
|
|
10
|
-
uv run python template.py fill --template template.md --context context.json --output output.md
|
|
11
|
-
uv run python template.py generate-batches --template template.md --contexts contexts.json --output-dir ./outputs
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
import argparse
|
|
15
|
-
import json
|
|
16
|
-
import os
|
|
17
|
-
import re
|
|
18
|
-
import sys
|
|
19
|
-
from pathlib import Path
|
|
20
|
-
from typing import Any, Dict, List, Optional
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class TemplateEngine:
|
|
24
|
-
"""Template engine for placeholder replacement and batch generation."""
|
|
25
|
-
|
|
26
|
-
PLACEHOLDER_PATTERN = re.compile(r"\{(\w+)\}")
|
|
27
|
-
|
|
28
|
-
@staticmethod
|
|
29
|
-
def escape_for_markdown(text: str) -> str:
|
|
30
|
-
"""Escape text for safe use in markdown code blocks.
|
|
31
|
-
|
|
32
|
-
Args:
|
|
33
|
-
text: Text to escape
|
|
34
|
-
|
|
35
|
-
Returns:
|
|
36
|
-
Escaped text safe for markdown code blocks
|
|
37
|
-
"""
|
|
38
|
-
escaped = text.replace("\\", "\\\\")
|
|
39
|
-
escaped = escaped.replace("`", "\\`")
|
|
40
|
-
escaped = escaped.replace("$", "\\$")
|
|
41
|
-
return escaped
|
|
42
|
-
|
|
43
|
-
@staticmethod
|
|
44
|
-
def fill_template(
|
|
45
|
-
template: str, replacements: Dict[str, Any], escape_values: bool = False
|
|
46
|
-
) -> str:
|
|
47
|
-
"""Fill template by replacing {placeholder} patterns.
|
|
48
|
-
|
|
49
|
-
Args:
|
|
50
|
-
template: Template string with {placeholder} patterns
|
|
51
|
-
replacements: Dictionary mapping placeholder names to values
|
|
52
|
-
escape_values: If True, escape values for markdown code blocks
|
|
53
|
-
|
|
54
|
-
Returns:
|
|
55
|
-
Template with placeholders replaced
|
|
56
|
-
"""
|
|
57
|
-
|
|
58
|
-
def replace_match(match: re.Match) -> str:
|
|
59
|
-
key = match.group(1)
|
|
60
|
-
if key not in replacements:
|
|
61
|
-
return match.group(0)
|
|
62
|
-
value = replacements[key]
|
|
63
|
-
if value is None:
|
|
64
|
-
return ""
|
|
65
|
-
str_value = str(value)
|
|
66
|
-
if escape_values:
|
|
67
|
-
str_value = TemplateEngine.escape_for_markdown(str_value)
|
|
68
|
-
return str_value
|
|
69
|
-
|
|
70
|
-
return TemplateEngine.PLACEHOLDER_PATTERN.sub(replace_match, template)
|
|
71
|
-
|
|
72
|
-
@staticmethod
|
|
73
|
-
def generate_batch(
|
|
74
|
-
template_path: str,
|
|
75
|
-
context: Dict[str, Any],
|
|
76
|
-
output_path: str,
|
|
77
|
-
escape_values: bool = False,
|
|
78
|
-
) -> Dict[str, Any]:
|
|
79
|
-
"""Generate a single file from template and context.
|
|
80
|
-
|
|
81
|
-
Args:
|
|
82
|
-
template_path: Path to template file
|
|
83
|
-
context: Dictionary with placeholder values
|
|
84
|
-
output_path: Path for output file
|
|
85
|
-
escape_values: If True, escape values for markdown
|
|
86
|
-
|
|
87
|
-
Returns:
|
|
88
|
-
Result dictionary with success status
|
|
89
|
-
"""
|
|
90
|
-
if not os.path.exists(template_path):
|
|
91
|
-
return {
|
|
92
|
-
"success": False,
|
|
93
|
-
"error": f"Template file not found: {template_path}",
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
try:
|
|
97
|
-
with open(template_path, "r", encoding="utf-8") as f:
|
|
98
|
-
template = f.read()
|
|
99
|
-
except Exception as e:
|
|
100
|
-
return {"success": False, "error": f"Failed to read template: {e}"}
|
|
101
|
-
|
|
102
|
-
filled = TemplateEngine.fill_template(template, context, escape_values)
|
|
103
|
-
|
|
104
|
-
try:
|
|
105
|
-
output_dir = os.path.dirname(output_path)
|
|
106
|
-
if output_dir:
|
|
107
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
108
|
-
|
|
109
|
-
with open(output_path, "w", encoding="utf-8") as f:
|
|
110
|
-
f.write(filled)
|
|
111
|
-
except Exception as e:
|
|
112
|
-
return {"success": False, "error": f"Failed to write output: {e}"}
|
|
113
|
-
|
|
114
|
-
return {
|
|
115
|
-
"success": True,
|
|
116
|
-
"output_path": output_path,
|
|
117
|
-
"template_path": template_path,
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
@staticmethod
|
|
121
|
-
def generate_batches(
|
|
122
|
-
template_path: str,
|
|
123
|
-
contexts: List[Dict[str, Any]],
|
|
124
|
-
output_dir: str,
|
|
125
|
-
filename_pattern: str = "output_{index:03d}.md",
|
|
126
|
-
escape_values: bool = False,
|
|
127
|
-
dry_run: bool = False,
|
|
128
|
-
verbose: bool = False,
|
|
129
|
-
) -> Dict[str, Any]:
|
|
130
|
-
"""Generate multiple files from template and list of contexts.
|
|
131
|
-
|
|
132
|
-
Args:
|
|
133
|
-
template_path: Path to template file
|
|
134
|
-
contexts: List of context dictionaries
|
|
135
|
-
output_dir: Directory for output files
|
|
136
|
-
filename_pattern: Pattern for output filenames (use {index} placeholder)
|
|
137
|
-
escape_values: If True, escape values for markdown
|
|
138
|
-
dry_run: If True, don't write files
|
|
139
|
-
verbose: If True, print progress
|
|
140
|
-
|
|
141
|
-
Returns:
|
|
142
|
-
Result dictionary with success status and generated files
|
|
143
|
-
"""
|
|
144
|
-
if not os.path.exists(template_path):
|
|
145
|
-
return {
|
|
146
|
-
"success": False,
|
|
147
|
-
"error": f"Template file not found: {template_path}",
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if not contexts:
|
|
151
|
-
return {"success": False, "error": "No contexts provided"}
|
|
152
|
-
|
|
153
|
-
try:
|
|
154
|
-
with open(template_path, "r", encoding="utf-8") as f:
|
|
155
|
-
template = f.read()
|
|
156
|
-
except Exception as e:
|
|
157
|
-
return {"success": False, "error": f"Failed to read template: {e}"}
|
|
158
|
-
|
|
159
|
-
if not dry_run:
|
|
160
|
-
os.makedirs(output_dir, exist_ok=True)
|
|
161
|
-
|
|
162
|
-
generated_files = []
|
|
163
|
-
errors = []
|
|
164
|
-
|
|
165
|
-
for index, context in enumerate(contexts):
|
|
166
|
-
filename = filename_pattern.format(index=index + 1, **context)
|
|
167
|
-
output_path = os.path.join(output_dir, filename)
|
|
168
|
-
|
|
169
|
-
if dry_run:
|
|
170
|
-
if verbose:
|
|
171
|
-
print(f"Would generate: {output_path}")
|
|
172
|
-
generated_files.append(output_path)
|
|
173
|
-
continue
|
|
174
|
-
|
|
175
|
-
filled = TemplateEngine.fill_template(template, context, escape_values)
|
|
176
|
-
|
|
177
|
-
try:
|
|
178
|
-
with open(output_path, "w", encoding="utf-8") as f:
|
|
179
|
-
f.write(filled)
|
|
180
|
-
|
|
181
|
-
if verbose:
|
|
182
|
-
print(f"Generated: {output_path}")
|
|
183
|
-
generated_files.append(output_path)
|
|
184
|
-
except Exception as e:
|
|
185
|
-
error_msg = f"Failed to write {output_path}: {e}"
|
|
186
|
-
if verbose:
|
|
187
|
-
print(f"Error: {error_msg}", file=sys.stderr)
|
|
188
|
-
errors.append(error_msg)
|
|
189
|
-
|
|
190
|
-
result = {
|
|
191
|
-
"success": len(errors) == 0,
|
|
192
|
-
"total_contexts": len(contexts),
|
|
193
|
-
"generated_count": len(generated_files),
|
|
194
|
-
"output_dir": output_dir,
|
|
195
|
-
"generated_files": generated_files,
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if errors:
|
|
199
|
-
result["errors"] = errors
|
|
200
|
-
result["error_count"] = len(errors)
|
|
201
|
-
|
|
202
|
-
return result
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def cmd_fill(args: argparse.Namespace) -> None:
|
|
206
|
-
"""Handle fill command."""
|
|
207
|
-
try:
|
|
208
|
-
with open(args.context, "r", encoding="utf-8") as f:
|
|
209
|
-
context = json.load(f)
|
|
210
|
-
except Exception as e:
|
|
211
|
-
result = {"success": False, "error": f"Failed to read context file: {e}"}
|
|
212
|
-
print(json.dumps(result, indent=2))
|
|
213
|
-
sys.exit(1)
|
|
214
|
-
|
|
215
|
-
result = TemplateEngine.generate_batch(
|
|
216
|
-
template_path=args.template,
|
|
217
|
-
context=context,
|
|
218
|
-
output_path=args.output,
|
|
219
|
-
escape_values=args.escape,
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
print(json.dumps(result, indent=2))
|
|
223
|
-
|
|
224
|
-
if not result["success"]:
|
|
225
|
-
sys.exit(1)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def cmd_generate_batches(args: argparse.Namespace) -> None:
|
|
229
|
-
"""Handle generate-batches command."""
|
|
230
|
-
try:
|
|
231
|
-
with open(args.contexts, "r", encoding="utf-8") as f:
|
|
232
|
-
contexts = json.load(f)
|
|
233
|
-
except Exception as e:
|
|
234
|
-
result = {"success": False, "error": f"Failed to read contexts file: {e}"}
|
|
235
|
-
print(json.dumps(result, indent=2))
|
|
236
|
-
sys.exit(1)
|
|
237
|
-
|
|
238
|
-
if not isinstance(contexts, list):
|
|
239
|
-
result = {"success": False, "error": "Contexts must be a JSON array"}
|
|
240
|
-
print(json.dumps(result, indent=2))
|
|
241
|
-
sys.exit(1)
|
|
242
|
-
|
|
243
|
-
result = TemplateEngine.generate_batches(
|
|
244
|
-
template_path=args.template,
|
|
245
|
-
contexts=contexts,
|
|
246
|
-
output_dir=args.output_dir,
|
|
247
|
-
filename_pattern=args.filename_pattern,
|
|
248
|
-
escape_values=args.escape,
|
|
249
|
-
dry_run=args.dry_run,
|
|
250
|
-
verbose=args.verbose,
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
print(json.dumps(result, indent=2))
|
|
254
|
-
|
|
255
|
-
if not result["success"]:
|
|
256
|
-
sys.exit(1)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
def cmd_escape(args: argparse.Namespace) -> None:
|
|
260
|
-
"""Handle escape command."""
|
|
261
|
-
escaped = TemplateEngine.escape_for_markdown(args.text)
|
|
262
|
-
result = {"success": True, "original": args.text, "escaped": escaped}
|
|
263
|
-
print(json.dumps(result, indent=2))
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def main() -> None:
|
|
267
|
-
"""Main entry point."""
|
|
268
|
-
parser = argparse.ArgumentParser(
|
|
269
|
-
description="Template engine for generating files from templates"
|
|
270
|
-
)
|
|
271
|
-
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
272
|
-
|
|
273
|
-
fill_parser = subparsers.add_parser("fill", help="Fill single template")
|
|
274
|
-
fill_parser.add_argument("--template", required=True, help="Path to template file")
|
|
275
|
-
fill_parser.add_argument(
|
|
276
|
-
"--context", required=True, help="Path to context JSON file"
|
|
277
|
-
)
|
|
278
|
-
fill_parser.add_argument("--output", required=True, help="Path for output file")
|
|
279
|
-
fill_parser.add_argument(
|
|
280
|
-
"--escape", action="store_true", help="Escape values for markdown"
|
|
281
|
-
)
|
|
282
|
-
|
|
283
|
-
batch_parser = subparsers.add_parser(
|
|
284
|
-
"generate-batches", help="Generate multiple files"
|
|
285
|
-
)
|
|
286
|
-
batch_parser.add_argument("--template", required=True, help="Path to template file")
|
|
287
|
-
batch_parser.add_argument(
|
|
288
|
-
"--contexts", required=True, help="Path to contexts JSON file"
|
|
289
|
-
)
|
|
290
|
-
batch_parser.add_argument(
|
|
291
|
-
"--output-dir", required=True, help="Directory for output files"
|
|
292
|
-
)
|
|
293
|
-
batch_parser.add_argument(
|
|
294
|
-
"--filename-pattern",
|
|
295
|
-
default="output_{index:03d}.md",
|
|
296
|
-
help="Filename pattern (default: output_{index:03d}.md)",
|
|
297
|
-
)
|
|
298
|
-
batch_parser.add_argument(
|
|
299
|
-
"--escape", action="store_true", help="Escape values for markdown"
|
|
300
|
-
)
|
|
301
|
-
batch_parser.add_argument(
|
|
302
|
-
"--dry-run", action="store_true", help="Validate without writing"
|
|
303
|
-
)
|
|
304
|
-
batch_parser.add_argument("--verbose", action="store_true", help="Print progress")
|
|
305
|
-
|
|
306
|
-
escape_parser = subparsers.add_parser("escape", help="Escape text for markdown")
|
|
307
|
-
escape_parser.add_argument("--text", required=True, help="Text to escape")
|
|
308
|
-
|
|
309
|
-
args = parser.parse_args()
|
|
310
|
-
|
|
311
|
-
if args.command == "fill":
|
|
312
|
-
cmd_fill(args)
|
|
313
|
-
elif args.command == "generate-batches":
|
|
314
|
-
cmd_generate_batches(args)
|
|
315
|
-
elif args.command == "escape":
|
|
316
|
-
cmd_escape(args)
|
|
317
|
-
else:
|
|
318
|
-
parser.print_help()
|
|
319
|
-
sys.exit(1)
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if __name__ == "__main__":
|
|
323
|
-
main()
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Template engine for generating files from templates with placeholder replacement.
|
|
3
|
+
|
|
4
|
+
This module provides functionality for:
|
|
5
|
+
- Filling templates with placeholder replacement
|
|
6
|
+
- Escaping text for markdown code blocks
|
|
7
|
+
- Batch generation from template + contexts
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
uv run python template.py fill --template template.md --context context.json --output output.md
|
|
11
|
+
uv run python template.py generate-batches --template template.md --contexts contexts.json --output-dir ./outputs
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any, Dict, List, Optional
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TemplateEngine:
|
|
24
|
+
"""Template engine for placeholder replacement and batch generation."""
|
|
25
|
+
|
|
26
|
+
PLACEHOLDER_PATTERN = re.compile(r"\{(\w+)\}")
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def escape_for_markdown(text: str) -> str:
|
|
30
|
+
"""Escape text for safe use in markdown code blocks.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
text: Text to escape
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Escaped text safe for markdown code blocks
|
|
37
|
+
"""
|
|
38
|
+
escaped = text.replace("\\", "\\\\")
|
|
39
|
+
escaped = escaped.replace("`", "\\`")
|
|
40
|
+
escaped = escaped.replace("$", "\\$")
|
|
41
|
+
return escaped
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def fill_template(
|
|
45
|
+
template: str, replacements: Dict[str, Any], escape_values: bool = False
|
|
46
|
+
) -> str:
|
|
47
|
+
"""Fill template by replacing {placeholder} patterns.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
template: Template string with {placeholder} patterns
|
|
51
|
+
replacements: Dictionary mapping placeholder names to values
|
|
52
|
+
escape_values: If True, escape values for markdown code blocks
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Template with placeholders replaced
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
def replace_match(match: re.Match) -> str:
|
|
59
|
+
key = match.group(1)
|
|
60
|
+
if key not in replacements:
|
|
61
|
+
return match.group(0)
|
|
62
|
+
value = replacements[key]
|
|
63
|
+
if value is None:
|
|
64
|
+
return ""
|
|
65
|
+
str_value = str(value)
|
|
66
|
+
if escape_values:
|
|
67
|
+
str_value = TemplateEngine.escape_for_markdown(str_value)
|
|
68
|
+
return str_value
|
|
69
|
+
|
|
70
|
+
return TemplateEngine.PLACEHOLDER_PATTERN.sub(replace_match, template)
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def generate_batch(
|
|
74
|
+
template_path: str,
|
|
75
|
+
context: Dict[str, Any],
|
|
76
|
+
output_path: str,
|
|
77
|
+
escape_values: bool = False,
|
|
78
|
+
) -> Dict[str, Any]:
|
|
79
|
+
"""Generate a single file from template and context.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
template_path: Path to template file
|
|
83
|
+
context: Dictionary with placeholder values
|
|
84
|
+
output_path: Path for output file
|
|
85
|
+
escape_values: If True, escape values for markdown
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
Result dictionary with success status
|
|
89
|
+
"""
|
|
90
|
+
if not os.path.exists(template_path):
|
|
91
|
+
return {
|
|
92
|
+
"success": False,
|
|
93
|
+
"error": f"Template file not found: {template_path}",
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
with open(template_path, "r", encoding="utf-8") as f:
|
|
98
|
+
template = f.read()
|
|
99
|
+
except Exception as e:
|
|
100
|
+
return {"success": False, "error": f"Failed to read template: {e}"}
|
|
101
|
+
|
|
102
|
+
filled = TemplateEngine.fill_template(template, context, escape_values)
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
output_dir = os.path.dirname(output_path)
|
|
106
|
+
if output_dir:
|
|
107
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
108
|
+
|
|
109
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
110
|
+
f.write(filled)
|
|
111
|
+
except Exception as e:
|
|
112
|
+
return {"success": False, "error": f"Failed to write output: {e}"}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"success": True,
|
|
116
|
+
"output_path": output_path,
|
|
117
|
+
"template_path": template_path,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def generate_batches(
|
|
122
|
+
template_path: str,
|
|
123
|
+
contexts: List[Dict[str, Any]],
|
|
124
|
+
output_dir: str,
|
|
125
|
+
filename_pattern: str = "output_{index:03d}.md",
|
|
126
|
+
escape_values: bool = False,
|
|
127
|
+
dry_run: bool = False,
|
|
128
|
+
verbose: bool = False,
|
|
129
|
+
) -> Dict[str, Any]:
|
|
130
|
+
"""Generate multiple files from template and list of contexts.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
template_path: Path to template file
|
|
134
|
+
contexts: List of context dictionaries
|
|
135
|
+
output_dir: Directory for output files
|
|
136
|
+
filename_pattern: Pattern for output filenames (use {index} placeholder)
|
|
137
|
+
escape_values: If True, escape values for markdown
|
|
138
|
+
dry_run: If True, don't write files
|
|
139
|
+
verbose: If True, print progress
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
Result dictionary with success status and generated files
|
|
143
|
+
"""
|
|
144
|
+
if not os.path.exists(template_path):
|
|
145
|
+
return {
|
|
146
|
+
"success": False,
|
|
147
|
+
"error": f"Template file not found: {template_path}",
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if not contexts:
|
|
151
|
+
return {"success": False, "error": "No contexts provided"}
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
with open(template_path, "r", encoding="utf-8") as f:
|
|
155
|
+
template = f.read()
|
|
156
|
+
except Exception as e:
|
|
157
|
+
return {"success": False, "error": f"Failed to read template: {e}"}
|
|
158
|
+
|
|
159
|
+
if not dry_run:
|
|
160
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
161
|
+
|
|
162
|
+
generated_files = []
|
|
163
|
+
errors = []
|
|
164
|
+
|
|
165
|
+
for index, context in enumerate(contexts):
|
|
166
|
+
filename = filename_pattern.format(index=index + 1, **context)
|
|
167
|
+
output_path = os.path.join(output_dir, filename)
|
|
168
|
+
|
|
169
|
+
if dry_run:
|
|
170
|
+
if verbose:
|
|
171
|
+
print(f"Would generate: {output_path}")
|
|
172
|
+
generated_files.append(output_path)
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
filled = TemplateEngine.fill_template(template, context, escape_values)
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
with open(output_path, "w", encoding="utf-8") as f:
|
|
179
|
+
f.write(filled)
|
|
180
|
+
|
|
181
|
+
if verbose:
|
|
182
|
+
print(f"Generated: {output_path}")
|
|
183
|
+
generated_files.append(output_path)
|
|
184
|
+
except Exception as e:
|
|
185
|
+
error_msg = f"Failed to write {output_path}: {e}"
|
|
186
|
+
if verbose:
|
|
187
|
+
print(f"Error: {error_msg}", file=sys.stderr)
|
|
188
|
+
errors.append(error_msg)
|
|
189
|
+
|
|
190
|
+
result = {
|
|
191
|
+
"success": len(errors) == 0,
|
|
192
|
+
"total_contexts": len(contexts),
|
|
193
|
+
"generated_count": len(generated_files),
|
|
194
|
+
"output_dir": output_dir,
|
|
195
|
+
"generated_files": generated_files,
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if errors:
|
|
199
|
+
result["errors"] = errors
|
|
200
|
+
result["error_count"] = len(errors)
|
|
201
|
+
|
|
202
|
+
return result
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def cmd_fill(args: argparse.Namespace) -> None:
|
|
206
|
+
"""Handle fill command."""
|
|
207
|
+
try:
|
|
208
|
+
with open(args.context, "r", encoding="utf-8") as f:
|
|
209
|
+
context = json.load(f)
|
|
210
|
+
except Exception as e:
|
|
211
|
+
result = {"success": False, "error": f"Failed to read context file: {e}"}
|
|
212
|
+
print(json.dumps(result, indent=2))
|
|
213
|
+
sys.exit(1)
|
|
214
|
+
|
|
215
|
+
result = TemplateEngine.generate_batch(
|
|
216
|
+
template_path=args.template,
|
|
217
|
+
context=context,
|
|
218
|
+
output_path=args.output,
|
|
219
|
+
escape_values=args.escape,
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
print(json.dumps(result, indent=2))
|
|
223
|
+
|
|
224
|
+
if not result["success"]:
|
|
225
|
+
sys.exit(1)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def cmd_generate_batches(args: argparse.Namespace) -> None:
|
|
229
|
+
"""Handle generate-batches command."""
|
|
230
|
+
try:
|
|
231
|
+
with open(args.contexts, "r", encoding="utf-8") as f:
|
|
232
|
+
contexts = json.load(f)
|
|
233
|
+
except Exception as e:
|
|
234
|
+
result = {"success": False, "error": f"Failed to read contexts file: {e}"}
|
|
235
|
+
print(json.dumps(result, indent=2))
|
|
236
|
+
sys.exit(1)
|
|
237
|
+
|
|
238
|
+
if not isinstance(contexts, list):
|
|
239
|
+
result = {"success": False, "error": "Contexts must be a JSON array"}
|
|
240
|
+
print(json.dumps(result, indent=2))
|
|
241
|
+
sys.exit(1)
|
|
242
|
+
|
|
243
|
+
result = TemplateEngine.generate_batches(
|
|
244
|
+
template_path=args.template,
|
|
245
|
+
contexts=contexts,
|
|
246
|
+
output_dir=args.output_dir,
|
|
247
|
+
filename_pattern=args.filename_pattern,
|
|
248
|
+
escape_values=args.escape,
|
|
249
|
+
dry_run=args.dry_run,
|
|
250
|
+
verbose=args.verbose,
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
print(json.dumps(result, indent=2))
|
|
254
|
+
|
|
255
|
+
if not result["success"]:
|
|
256
|
+
sys.exit(1)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def cmd_escape(args: argparse.Namespace) -> None:
|
|
260
|
+
"""Handle escape command."""
|
|
261
|
+
escaped = TemplateEngine.escape_for_markdown(args.text)
|
|
262
|
+
result = {"success": True, "original": args.text, "escaped": escaped}
|
|
263
|
+
print(json.dumps(result, indent=2))
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def main() -> None:
|
|
267
|
+
"""Main entry point."""
|
|
268
|
+
parser = argparse.ArgumentParser(
|
|
269
|
+
description="Template engine for generating files from templates"
|
|
270
|
+
)
|
|
271
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
272
|
+
|
|
273
|
+
fill_parser = subparsers.add_parser("fill", help="Fill single template")
|
|
274
|
+
fill_parser.add_argument("--template", required=True, help="Path to template file")
|
|
275
|
+
fill_parser.add_argument(
|
|
276
|
+
"--context", required=True, help="Path to context JSON file"
|
|
277
|
+
)
|
|
278
|
+
fill_parser.add_argument("--output", required=True, help="Path for output file")
|
|
279
|
+
fill_parser.add_argument(
|
|
280
|
+
"--escape", action="store_true", help="Escape values for markdown"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
batch_parser = subparsers.add_parser(
|
|
284
|
+
"generate-batches", help="Generate multiple files"
|
|
285
|
+
)
|
|
286
|
+
batch_parser.add_argument("--template", required=True, help="Path to template file")
|
|
287
|
+
batch_parser.add_argument(
|
|
288
|
+
"--contexts", required=True, help="Path to contexts JSON file"
|
|
289
|
+
)
|
|
290
|
+
batch_parser.add_argument(
|
|
291
|
+
"--output-dir", required=True, help="Directory for output files"
|
|
292
|
+
)
|
|
293
|
+
batch_parser.add_argument(
|
|
294
|
+
"--filename-pattern",
|
|
295
|
+
default="output_{index:03d}.md",
|
|
296
|
+
help="Filename pattern (default: output_{index:03d}.md)",
|
|
297
|
+
)
|
|
298
|
+
batch_parser.add_argument(
|
|
299
|
+
"--escape", action="store_true", help="Escape values for markdown"
|
|
300
|
+
)
|
|
301
|
+
batch_parser.add_argument(
|
|
302
|
+
"--dry-run", action="store_true", help="Validate without writing"
|
|
303
|
+
)
|
|
304
|
+
batch_parser.add_argument("--verbose", action="store_true", help="Print progress")
|
|
305
|
+
|
|
306
|
+
escape_parser = subparsers.add_parser("escape", help="Escape text for markdown")
|
|
307
|
+
escape_parser.add_argument("--text", required=True, help="Text to escape")
|
|
308
|
+
|
|
309
|
+
args = parser.parse_args()
|
|
310
|
+
|
|
311
|
+
if args.command == "fill":
|
|
312
|
+
cmd_fill(args)
|
|
313
|
+
elif args.command == "generate-batches":
|
|
314
|
+
cmd_generate_batches(args)
|
|
315
|
+
elif args.command == "escape":
|
|
316
|
+
cmd_escape(args)
|
|
317
|
+
else:
|
|
318
|
+
parser.print_help()
|
|
319
|
+
sys.exit(1)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
if __name__ == "__main__":
|
|
323
|
+
main()
|