qparse 0.1.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.
qmd_cli.py ADDED
@@ -0,0 +1,262 @@
1
+ # -*- coding: utf-8 -*-
2
+ """QParse 命令行工具:打包 / 解包 / 渲染。"""
3
+
4
+ from __future__ import annotations
5
+
6
+ from pathlib import Path
7
+ from typing import Optional
8
+
9
+ import typer
10
+
11
+ app = typer.Typer(
12
+ name="qmd",
13
+ help="QParse .qmd 打包、解包与渲染工具",
14
+ add_completion=False,
15
+ no_args_is_help=True,
16
+ )
17
+
18
+
19
+ def _print_path(label: str, path: Path) -> None:
20
+ typer.echo(f"{label}: {path}")
21
+
22
+
23
+ DEFAULT_RENDER_TEMPLATE = """\
24
+ <render src="{src}">
25
+ <file type="docx" dist="{dist}">
26
+ <doc theme="{theme}">
27
+ <after>
28
+ <pagebreak></pagebreak>
29
+ # 参考答案
30
+
31
+ <reference-answer></reference-answer>
32
+
33
+ </after>
34
+ </doc>
35
+ </file>
36
+ </render>
37
+ """
38
+
39
+
40
+ def generate_render_md(
41
+ main_md: Path,
42
+ output: Optional[Path] = None,
43
+ dist: str = "习题版.docx",
44
+ theme: str = "Worksheet",
45
+ force: bool = False,
46
+ ) -> Path:
47
+ """根据 main.md 生成默认 render.md。"""
48
+ main_md = main_md.resolve()
49
+ if not main_md.exists():
50
+ raise FileNotFoundError(f"main.md 不存在: {main_md}")
51
+
52
+ # 默认写到 main.md 同级目录
53
+ output_path = (output or (main_md.parent / "render.md")).resolve()
54
+ if output_path.exists() and not force:
55
+ raise FileExistsError(
56
+ f"已存在: {output_path},如需覆盖请加 --force"
57
+ )
58
+
59
+ content = DEFAULT_RENDER_TEMPLATE.format(
60
+ src=str(main_md),
61
+ dist=dist,
62
+ theme=theme,
63
+ )
64
+ output_path.parent.mkdir(parents=True, exist_ok=True)
65
+ output_path.write_text(content, encoding="utf-8")
66
+ return output_path
67
+
68
+
69
+ @app.command("init")
70
+ def init_cmd(
71
+ main_md: Path = typer.Argument(
72
+ ...,
73
+ exists=True,
74
+ dir_okay=False,
75
+ readable=True,
76
+ help="入口 main.md 路径",
77
+ ),
78
+ output: Optional[Path] = typer.Option(
79
+ None,
80
+ "--output",
81
+ "-o",
82
+ help="render.md 输出路径,默认写到 main.md 同级目录",
83
+ ),
84
+ dist: str = typer.Option(
85
+ "习题版.docx",
86
+ "--dist",
87
+ "-d",
88
+ help="默认 DOCX 输出文件名",
89
+ ),
90
+ theme: str = typer.Option(
91
+ "Worksheet",
92
+ "--theme",
93
+ help="默认主题名",
94
+ ),
95
+ force: bool = typer.Option(
96
+ False,
97
+ "--force",
98
+ "-f",
99
+ help="若目标已存在则覆盖",
100
+ ),
101
+ ) -> None:
102
+ """输入 main.md,自动生成默认 render.md。"""
103
+ try:
104
+ path = generate_render_md(
105
+ main_md=main_md,
106
+ output=output,
107
+ dist=dist,
108
+ theme=theme,
109
+ force=force,
110
+ )
111
+ except FileExistsError as exc:
112
+ typer.echo(str(exc), err=True)
113
+ raise typer.Exit(code=1) from exc
114
+
115
+ _print_path("render", path)
116
+
117
+
118
+ @app.command("pack")
119
+ def pack_cmd(
120
+ entry: Path = typer.Argument(
121
+ ...,
122
+ exists=True,
123
+ dir_okay=False,
124
+ readable=True,
125
+ help="入口 render.md 路径",
126
+ ),
127
+ output: Optional[Path] = typer.Option(
128
+ None,
129
+ "--output",
130
+ "-o",
131
+ help="输出 .qmd 路径,默认与 entry 同名",
132
+ ),
133
+ workspace: Optional[Path] = typer.Option(
134
+ None,
135
+ "--workspace",
136
+ "-w",
137
+ exists=True,
138
+ dir_okay=False,
139
+ help="code-workspace 路径,默认使用项目根目录 QParse.code-workspace",
140
+ ),
141
+ ) -> None:
142
+ """将 render.md 及其依赖打包为 .qmd。"""
143
+ from qparse import QmdPacker
144
+
145
+ packer = QmdPacker(workspace=str(workspace) if workspace else None)
146
+ packed = packer.pack(
147
+ entry=str(entry),
148
+ output=str(output) if output else None,
149
+ )
150
+ _print_path("qmd", packed)
151
+ typer.echo(f"documents: {len(packer._documents)}")
152
+ typer.echo(f"resources: {len(packer._resources)}")
153
+
154
+
155
+ @app.command("unpack")
156
+ def unpack_cmd(
157
+ package: Path = typer.Argument(
158
+ ...,
159
+ exists=True,
160
+ dir_okay=False,
161
+ readable=True,
162
+ help=".qmd 包路径",
163
+ ),
164
+ target: Optional[Path] = typer.Option(
165
+ None,
166
+ "--target",
167
+ "-t",
168
+ help="解包目录,默认创建到包旁 .qmd_runtime/<uuid>",
169
+ ),
170
+ ) -> None:
171
+ """解包 .qmd 到工作目录(保留,不自动删除)。"""
172
+ from qparse import QmdUnpacker
173
+
174
+ unpacker = QmdUnpacker(str(package))
175
+ out_dir = unpacker.unpack(target=str(target) if target else None)
176
+ _print_path("dir", out_dir)
177
+ _print_path("entry", unpacker.entry_path)
178
+
179
+
180
+ @app.command("render")
181
+ def render_cmd(
182
+ entry: Path = typer.Argument(
183
+ ...,
184
+ exists=True,
185
+ dir_okay=False,
186
+ readable=True,
187
+ help="render.md 或解包后的入口文件",
188
+ ),
189
+ ) -> None:
190
+ """根据 render.md 渲染 DOCX,并打印输出路径。"""
191
+ from qparse import MarkdwonRender
192
+
193
+ render = MarkdwonRender(src=str(entry))
194
+ outputs = render.render()
195
+ if not outputs:
196
+ typer.echo("未生成任何文件", err=True)
197
+ raise typer.Exit(code=1)
198
+ for path in outputs:
199
+ _print_path("docx", Path(path))
200
+
201
+
202
+ @app.command("build")
203
+ def build_cmd(
204
+ entry: Path = typer.Argument(
205
+ ...,
206
+ exists=True,
207
+ dir_okay=False,
208
+ readable=True,
209
+ help="入口 render.md 路径",
210
+ ),
211
+ output: Optional[Path] = typer.Option(
212
+ None,
213
+ "--output",
214
+ "-o",
215
+ help="输出 .qmd 路径,默认与 entry 同名",
216
+ ),
217
+ unpack_dir: Optional[Path] = typer.Option(
218
+ None,
219
+ "--unpack-dir",
220
+ "-u",
221
+ help="解包目录,默认 <output_stem>_unpacked",
222
+ ),
223
+ workspace: Optional[Path] = typer.Option(
224
+ None,
225
+ "--workspace",
226
+ "-w",
227
+ exists=True,
228
+ dir_okay=False,
229
+ help="code-workspace 路径",
230
+ ),
231
+ ) -> None:
232
+ """一键:打包 → 解包 → 渲染。中间产物均保留。"""
233
+ from qparse import MarkdwonRender, QmdPacker, QmdUnpacker
234
+
235
+ packer = QmdPacker(workspace=str(workspace) if workspace else None)
236
+ packed = packer.pack(
237
+ entry=str(entry),
238
+ output=str(output) if output else None,
239
+ )
240
+ _print_path("qmd", packed)
241
+
242
+ target = unpack_dir or packed.with_name(f"{packed.stem}_unpacked")
243
+ unpacker = QmdUnpacker(str(packed))
244
+ out_dir = unpacker.unpack(target=str(target))
245
+ _print_path("dir", out_dir)
246
+ _print_path("entry", unpacker.entry_path)
247
+
248
+ render = MarkdwonRender(src=str(unpacker.entry_path))
249
+ outputs = render.render()
250
+ if not outputs:
251
+ typer.echo("未生成任何文件", err=True)
252
+ raise typer.Exit(code=1)
253
+ for path in outputs:
254
+ _print_path("docx", Path(path))
255
+
256
+
257
+ def main() -> None:
258
+ app()
259
+
260
+
261
+ if __name__ == "__main__":
262
+ main()
qparse/__init__.py ADDED
@@ -0,0 +1,16 @@
1
+ # -*- coding: utf-8 -*-
2
+ from .markdwon_loader import MarkdownLoader
3
+ from .markdwon_render import MarkdwonRender
4
+ from .qmd_packer import QmdPacker
5
+ from .qmd_unpacker import QmdUnpacker
6
+
7
+ __version__ = "0.1.0"
8
+
9
+ __all__ = [
10
+ "MarkdownLoader",
11
+ "MarkdwonRender",
12
+ "QmdPacker",
13
+ "QmdUnpacker",
14
+ "__version__",
15
+ ]
16
+
@@ -0,0 +1,3 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .docx_render import DocxRender
@@ -0,0 +1,68 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from pathlib import Path
4
+ from shutil import copy2, rmtree
5
+ from uuid import uuid4
6
+ from qparse.markdwon_document import *
7
+ from typing import List
8
+ from .write_buffer import *
9
+
10
+ class DocxRender():
11
+
12
+
13
+ def __init__(self,MdDoc:MarkdwonDocument):
14
+ self._mdoc = MdDoc
15
+
16
+ self._temp_file_list = []
17
+ self._run_time_dir = None
18
+
19
+ def __enter__(self):
20
+ self._run_time_dir = self.src.parent.joinpath(
21
+ ".qmd_runtime",
22
+ uuid4().hex
23
+ )
24
+ self._run_time_dir.mkdir(parents=True, exist_ok=True)
25
+ self.render_buffer = DocumentBuffer(self)
26
+
27
+ return self
28
+
29
+ def __exit__(self, exc_type, exc_value, traceback):
30
+ if self._run_time_dir is not None and self._run_time_dir.exists():
31
+ rmtree(self._run_time_dir)
32
+ return False
33
+ @property
34
+ def src(self)->Path:
35
+ return Path( self._mdoc.attrs['src'] )
36
+ @property
37
+ def run_time_dir(self)->Path:
38
+ if self._run_time_dir is None:
39
+ self._run_time_dir = self.src.parent.joinpath(
40
+ ".qmd_runtime",
41
+ uuid4().hex
42
+ )
43
+ return self._run_time_dir
44
+ @property
45
+ def dist(self):
46
+ return self.src.parent.joinpath("{}.docx".format(self.src.stem))
47
+ @property
48
+ def referance_path(self)->Path:
49
+ return Path(self._mdoc.render_option['docx']['referance'])
50
+
51
+ def create_temp_file_path(self,fext:str=".md"):
52
+ file_name = "{:02d}{}".format( len(self._temp_file_list)+1,fext)
53
+ fpath = self.run_time_dir.joinpath(file_name)
54
+ self._temp_file_list.append(fpath)
55
+ return fpath
56
+ def render(self,dist:str=None):
57
+ buffer = self.render_buffer.create(
58
+ type='document',keep_together=False,children=self._mdoc.dump_docx_buffer()
59
+ )
60
+ temp_file_dist = buffer.write_to_docx_file()
61
+ output_path = Path(dist) if dist is not None else self.dist
62
+ output_path.parent.mkdir(parents=True, exist_ok=True)
63
+ copy2(temp_file_dist, output_path)
64
+
65
+ return output_path
66
+
67
+
68
+