typsphinx 0.4.2__tar.gz → 0.4.3__tar.gz
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.
- {typsphinx-0.4.2 → typsphinx-0.4.3}/PKG-INFO +5 -5
- {typsphinx-0.4.2 → typsphinx-0.4.3}/README.md +1 -1
- {typsphinx-0.4.2 → typsphinx-0.4.3}/pyproject.toml +4 -4
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_inline_references.py +151 -0
- typsphinx-0.4.3/tests/test_template_assets.py +403 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/typsphinx/__init__.py +4 -2
- {typsphinx-0.4.2 → typsphinx-0.4.3}/typsphinx/builder.py +176 -1
- {typsphinx-0.4.2 → typsphinx-0.4.3}/typsphinx/translator.py +23 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/typsphinx.egg-info/PKG-INFO +5 -5
- {typsphinx-0.4.2 → typsphinx-0.4.3}/typsphinx.egg-info/SOURCES.txt +1 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/typsphinx.egg-info/requires.txt +1 -1
- {typsphinx-0.4.2 → typsphinx-0.4.3}/LICENSE +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/setup.cfg +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_admonitions.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_builder.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_builder_requirement13.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_config.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_config_other_options.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_config_template_mapping.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_config_toctree_defaults.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_documentation_configuration.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_documentation_installation.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_documentation_usage.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_entry_points.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_examples_basic.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_extension.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_integration_advanced.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_integration_basic.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_integration_multi_doc.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_integration_nested_toctree.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_math_fallback.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_math_mitex.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_math_native.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_nested_toctree_paths.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_pdf_generation.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_template_codly.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_template_engine.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_template_mitex.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_toctree_requirement13.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/tests/test_translator.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/typsphinx/pdf.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/typsphinx/template_engine.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/typsphinx/templates/base.typ +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/typsphinx/writer.py +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/typsphinx.egg-info/dependency_links.txt +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/typsphinx.egg-info/entry_points.txt +0 -0
- {typsphinx-0.4.2 → typsphinx-0.4.3}/typsphinx.egg-info/top_level.txt +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: typsphinx
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Sphinx extension for Typst output
|
|
5
|
-
Author-email:
|
|
5
|
+
Author-email: YuSabo <yusabo90002@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/YuSabo90002/typsphinx
|
|
8
8
|
Project-URL: Documentation, https://github.com/YuSabo90002/typsphinx#readme
|
|
9
9
|
Project-URL: Repository, https://github.com/YuSabo90002/typsphinx
|
|
10
10
|
Project-URL: Issues, https://github.com/YuSabo90002/typsphinx/issues
|
|
11
11
|
Keywords: sphinx,typst,documentation,pdf
|
|
12
|
-
Classifier: Development Status ::
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Framework :: Sphinx :: Extension
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -24,7 +24,7 @@ Description-Content-Type: text/markdown
|
|
|
24
24
|
License-File: LICENSE
|
|
25
25
|
Requires-Dist: sphinx>=5.0
|
|
26
26
|
Requires-Dist: docutils>=0.18
|
|
27
|
-
Requires-Dist: typst>=0.
|
|
27
|
+
Requires-Dist: typst>=0.14.1
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
30
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
@@ -84,7 +84,7 @@ typsphinx is a Sphinx extension that enables generating Typst documents from reS
|
|
|
84
84
|
|
|
85
85
|
## Installation
|
|
86
86
|
|
|
87
|
-
### From PyPI
|
|
87
|
+
### From PyPI
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
90
|
pip install typsphinx
|
|
@@ -4,17 +4,17 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "typsphinx"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.3"
|
|
8
8
|
description = "Sphinx extension for Typst output"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
authors = [
|
|
13
|
-
{name = "
|
|
13
|
+
{name = "YuSabo", email = "yusabo90002@gmail.com"}
|
|
14
14
|
]
|
|
15
15
|
keywords = ["sphinx", "typst", "documentation", "pdf"]
|
|
16
16
|
classifiers = [
|
|
17
|
-
"Development Status ::
|
|
17
|
+
"Development Status :: 5 - Production/Stable",
|
|
18
18
|
"Framework :: Sphinx :: Extension",
|
|
19
19
|
"Intended Audience :: Developers",
|
|
20
20
|
"Programming Language :: Python :: 3",
|
|
@@ -29,7 +29,7 @@ classifiers = [
|
|
|
29
29
|
dependencies = [
|
|
30
30
|
"sphinx>=5.0",
|
|
31
31
|
"docutils>=0.18",
|
|
32
|
-
"typst>=0.
|
|
32
|
+
"typst>=0.14.1",
|
|
33
33
|
]
|
|
34
34
|
|
|
35
35
|
[project.optional-dependencies]
|
|
@@ -186,3 +186,154 @@ class TestInlineReferenceConversion:
|
|
|
186
186
|
output = translator.astext()
|
|
187
187
|
# Literal should use raw() function in unified code mode
|
|
188
188
|
assert 'raw("code_reference")' in output
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class TestEmptyURLHandling:
|
|
192
|
+
"""Test empty URL handling in references (Typst 0.14+ compatibility)."""
|
|
193
|
+
|
|
194
|
+
def test_empty_refuri_skips_link_wrapper(self, temp_sphinx_app: SphinxTestApp):
|
|
195
|
+
"""Test that empty refuri skips link() generation."""
|
|
196
|
+
ref = nodes.reference()
|
|
197
|
+
ref["refuri"] = "" # Empty URL
|
|
198
|
+
ref += nodes.Text("broken reference")
|
|
199
|
+
|
|
200
|
+
doc = create_document()
|
|
201
|
+
para = nodes.paragraph()
|
|
202
|
+
para += ref
|
|
203
|
+
doc += para
|
|
204
|
+
|
|
205
|
+
writer = TypstWriter(temp_sphinx_app.builder)
|
|
206
|
+
writer.document = doc
|
|
207
|
+
translator = TypstTranslator(doc, temp_sphinx_app.builder)
|
|
208
|
+
doc.walkabout(translator)
|
|
209
|
+
|
|
210
|
+
output = translator.astext()
|
|
211
|
+
# Should NOT generate link()
|
|
212
|
+
assert 'link("")' not in output
|
|
213
|
+
assert 'link("", ' not in output
|
|
214
|
+
# Should render content as plain text
|
|
215
|
+
assert "broken reference" in output
|
|
216
|
+
|
|
217
|
+
def test_empty_refuri_renders_content_as_text(self, temp_sphinx_app: SphinxTestApp):
|
|
218
|
+
"""Test that content is rendered as plain text when refuri is empty."""
|
|
219
|
+
ref = nodes.reference()
|
|
220
|
+
ref["refuri"] = ""
|
|
221
|
+
ref += nodes.Text("nonexistent-section")
|
|
222
|
+
|
|
223
|
+
doc = create_document()
|
|
224
|
+
para = nodes.paragraph()
|
|
225
|
+
para += ref
|
|
226
|
+
doc += para
|
|
227
|
+
|
|
228
|
+
writer = TypstWriter(temp_sphinx_app.builder)
|
|
229
|
+
writer.document = doc
|
|
230
|
+
translator = TypstTranslator(doc, temp_sphinx_app.builder)
|
|
231
|
+
doc.walkabout(translator)
|
|
232
|
+
|
|
233
|
+
output = translator.astext()
|
|
234
|
+
# Content should be present as text
|
|
235
|
+
assert "nonexistent-section" in output
|
|
236
|
+
# No link wrapper
|
|
237
|
+
assert "link(" not in output
|
|
238
|
+
|
|
239
|
+
def test_empty_refuri_emits_warning(self, temp_sphinx_app: SphinxTestApp):
|
|
240
|
+
"""Test that warning is emitted for empty refuri."""
|
|
241
|
+
ref = nodes.reference()
|
|
242
|
+
ref["refuri"] = ""
|
|
243
|
+
ref += nodes.Text("broken-link")
|
|
244
|
+
|
|
245
|
+
doc = create_document()
|
|
246
|
+
para = nodes.paragraph()
|
|
247
|
+
para += ref
|
|
248
|
+
doc += para
|
|
249
|
+
|
|
250
|
+
writer = TypstWriter(temp_sphinx_app.builder)
|
|
251
|
+
writer.document = doc
|
|
252
|
+
translator = TypstTranslator(doc, temp_sphinx_app.builder)
|
|
253
|
+
|
|
254
|
+
# Capture warnings
|
|
255
|
+
import io
|
|
256
|
+
from contextlib import redirect_stderr
|
|
257
|
+
|
|
258
|
+
stderr_capture = io.StringIO()
|
|
259
|
+
with redirect_stderr(stderr_capture):
|
|
260
|
+
doc.walkabout(translator)
|
|
261
|
+
|
|
262
|
+
# Sphinx logger output goes to stderr in test context
|
|
263
|
+
# We just verify the code runs without error and produces expected output
|
|
264
|
+
output = translator.astext()
|
|
265
|
+
assert "broken-link" in output
|
|
266
|
+
assert 'link("")' not in output
|
|
267
|
+
|
|
268
|
+
def test_valid_refuri_unchanged(self, temp_sphinx_app: SphinxTestApp):
|
|
269
|
+
"""Test that valid refuri generates link() as before (regression test)."""
|
|
270
|
+
ref = nodes.reference()
|
|
271
|
+
ref["refuri"] = "https://python.org"
|
|
272
|
+
ref += nodes.Text("Python")
|
|
273
|
+
|
|
274
|
+
doc = create_document()
|
|
275
|
+
para = nodes.paragraph()
|
|
276
|
+
para += ref
|
|
277
|
+
doc += para
|
|
278
|
+
|
|
279
|
+
writer = TypstWriter(temp_sphinx_app.builder)
|
|
280
|
+
writer.document = doc
|
|
281
|
+
translator = TypstTranslator(doc, temp_sphinx_app.builder)
|
|
282
|
+
doc.walkabout(translator)
|
|
283
|
+
|
|
284
|
+
output = translator.astext()
|
|
285
|
+
# Should generate link()
|
|
286
|
+
assert 'link("https://python.org"' in output
|
|
287
|
+
assert "Python" in output
|
|
288
|
+
|
|
289
|
+
def test_internal_reference_with_hash(self, temp_sphinx_app: SphinxTestApp):
|
|
290
|
+
"""Test that internal references (starting with #) work correctly."""
|
|
291
|
+
ref = nodes.reference()
|
|
292
|
+
ref["refuri"] = "#section-label"
|
|
293
|
+
ref += nodes.Text("See section")
|
|
294
|
+
|
|
295
|
+
doc = create_document()
|
|
296
|
+
para = nodes.paragraph()
|
|
297
|
+
para += ref
|
|
298
|
+
doc += para
|
|
299
|
+
|
|
300
|
+
writer = TypstWriter(temp_sphinx_app.builder)
|
|
301
|
+
writer.document = doc
|
|
302
|
+
translator = TypstTranslator(doc, temp_sphinx_app.builder)
|
|
303
|
+
doc.walkabout(translator)
|
|
304
|
+
|
|
305
|
+
output = translator.astext()
|
|
306
|
+
# Should generate link(<label>, ...)
|
|
307
|
+
assert "link(<section-label>" in output
|
|
308
|
+
assert "See section" in output
|
|
309
|
+
|
|
310
|
+
def test_multiple_empty_urls(self, temp_sphinx_app: SphinxTestApp):
|
|
311
|
+
"""Test that multiple empty URLs are handled correctly."""
|
|
312
|
+
# First empty reference
|
|
313
|
+
ref1 = nodes.reference()
|
|
314
|
+
ref1["refuri"] = ""
|
|
315
|
+
ref1 += nodes.Text("ref1")
|
|
316
|
+
|
|
317
|
+
# Second empty reference
|
|
318
|
+
ref2 = nodes.reference()
|
|
319
|
+
ref2["refuri"] = ""
|
|
320
|
+
ref2 += nodes.Text("ref2")
|
|
321
|
+
|
|
322
|
+
doc = create_document()
|
|
323
|
+
para = nodes.paragraph()
|
|
324
|
+
para += ref1
|
|
325
|
+
para += nodes.Text(" and ")
|
|
326
|
+
para += ref2
|
|
327
|
+
doc += para
|
|
328
|
+
|
|
329
|
+
writer = TypstWriter(temp_sphinx_app.builder)
|
|
330
|
+
writer.document = doc
|
|
331
|
+
translator = TypstTranslator(doc, temp_sphinx_app.builder)
|
|
332
|
+
doc.walkabout(translator)
|
|
333
|
+
|
|
334
|
+
output = translator.astext()
|
|
335
|
+
# Both should be rendered as text
|
|
336
|
+
assert "ref1" in output
|
|
337
|
+
assert "ref2" in output
|
|
338
|
+
# No link wrappers
|
|
339
|
+
assert 'link("")' not in output
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for template asset copying functionality (Issue #75).
|
|
3
|
+
|
|
4
|
+
This module tests the copy_template_assets() method and related functionality
|
|
5
|
+
for automatically copying assets (fonts, images, logos) referenced by custom templates.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import pytest
|
|
9
|
+
from sphinx.testing.util import SphinxTestApp
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def temp_app_with_template(tmp_path):
|
|
14
|
+
"""
|
|
15
|
+
Create a temporary Sphinx app with a custom template and assets.
|
|
16
|
+
|
|
17
|
+
Directory structure:
|
|
18
|
+
source/
|
|
19
|
+
index.rst
|
|
20
|
+
_templates/
|
|
21
|
+
template.typ
|
|
22
|
+
logo.png
|
|
23
|
+
assets/
|
|
24
|
+
font.otf
|
|
25
|
+
icon.svg
|
|
26
|
+
"""
|
|
27
|
+
srcdir = tmp_path / "source"
|
|
28
|
+
srcdir.mkdir()
|
|
29
|
+
|
|
30
|
+
# Create index.rst
|
|
31
|
+
(srcdir / "index.rst").write_text(
|
|
32
|
+
"Test Document\n" "=============\n" "\n" "This is a test document.\n"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
# Create template directory and files
|
|
36
|
+
template_dir = srcdir / "_templates"
|
|
37
|
+
template_dir.mkdir()
|
|
38
|
+
|
|
39
|
+
# Create template file
|
|
40
|
+
(template_dir / "template.typ").write_text(
|
|
41
|
+
'#import "logo.png"\n'
|
|
42
|
+
'#set text(font: "assets/font.otf")\n'
|
|
43
|
+
'#image("assets/icon.svg")\n'
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Create asset files
|
|
47
|
+
(template_dir / "logo.png").write_bytes(b"fake png data")
|
|
48
|
+
|
|
49
|
+
assets_dir = template_dir / "assets"
|
|
50
|
+
assets_dir.mkdir()
|
|
51
|
+
(assets_dir / "font.otf").write_bytes(b"fake font data")
|
|
52
|
+
(assets_dir / "icon.svg").write_text("<svg></svg>")
|
|
53
|
+
|
|
54
|
+
# Create conf.py
|
|
55
|
+
(srcdir / "conf.py").write_text(
|
|
56
|
+
"project = 'Test'\n"
|
|
57
|
+
"extensions = ['typsphinx']\n"
|
|
58
|
+
"typst_template = '_templates/template.typ'\n"
|
|
59
|
+
"typst_documents = [('index', 'index', 'Test', 'Author')]\n"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
outdir = tmp_path / "build"
|
|
63
|
+
|
|
64
|
+
app = SphinxTestApp(buildername="typst", srcdir=srcdir, builddir=outdir)
|
|
65
|
+
|
|
66
|
+
return app, srcdir, outdir
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_copy_template_assets_automatic_directory_copy(temp_app_with_template):
|
|
70
|
+
"""
|
|
71
|
+
Test automatic directory copy (default behavior).
|
|
72
|
+
|
|
73
|
+
When typst_template_assets is None, all files in the template directory
|
|
74
|
+
should be automatically copied (except .typ files).
|
|
75
|
+
"""
|
|
76
|
+
app, srcdir, outdir = temp_app_with_template
|
|
77
|
+
|
|
78
|
+
# Build the project
|
|
79
|
+
app.build()
|
|
80
|
+
|
|
81
|
+
# Check that assets were copied to output directory
|
|
82
|
+
typst_outdir = outdir / "typst"
|
|
83
|
+
template_out_dir = typst_outdir / "_templates"
|
|
84
|
+
|
|
85
|
+
assert (template_out_dir / "logo.png").exists()
|
|
86
|
+
assert (template_out_dir / "assets" / "font.otf").exists()
|
|
87
|
+
assert (template_out_dir / "assets" / "icon.svg").exists()
|
|
88
|
+
|
|
89
|
+
# Verify file contents match
|
|
90
|
+
assert (template_out_dir / "logo.png").read_bytes() == b"fake png data"
|
|
91
|
+
assert (template_out_dir / "assets" / "font.otf").read_bytes() == b"fake font data"
|
|
92
|
+
assert (template_out_dir / "assets" / "icon.svg").read_text() == "<svg></svg>"
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_copy_template_assets_explicit_list(tmp_path):
|
|
96
|
+
"""
|
|
97
|
+
Test explicit asset list specification.
|
|
98
|
+
|
|
99
|
+
When typst_template_assets is configured, only specified assets should be copied.
|
|
100
|
+
"""
|
|
101
|
+
srcdir = tmp_path / "source"
|
|
102
|
+
srcdir.mkdir()
|
|
103
|
+
|
|
104
|
+
# Create index.rst
|
|
105
|
+
(srcdir / "index.rst").write_text(
|
|
106
|
+
"Test Document\n" "=============\n" "\n" "This is a test document.\n"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Create template directory and files
|
|
110
|
+
template_dir = srcdir / "_templates"
|
|
111
|
+
template_dir.mkdir()
|
|
112
|
+
|
|
113
|
+
(template_dir / "template.typ").write_text('#image("logo.png")')
|
|
114
|
+
(template_dir / "logo.png").write_bytes(b"logo data")
|
|
115
|
+
(template_dir / "unused.png").write_bytes(b"unused data")
|
|
116
|
+
|
|
117
|
+
# Create conf.py with explicit asset list
|
|
118
|
+
(srcdir / "conf.py").write_text(
|
|
119
|
+
"project = 'Test'\n"
|
|
120
|
+
"extensions = ['typsphinx']\n"
|
|
121
|
+
"typst_template = '_templates/template.typ'\n"
|
|
122
|
+
"typst_template_assets = ['_templates/logo.png']\n"
|
|
123
|
+
"typst_documents = [('index', 'index', 'Test', 'Author')]\n"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
outdir = tmp_path / "build"
|
|
127
|
+
|
|
128
|
+
app = SphinxTestApp(buildername="typst", srcdir=srcdir, builddir=outdir)
|
|
129
|
+
|
|
130
|
+
# Build the project
|
|
131
|
+
app.build()
|
|
132
|
+
|
|
133
|
+
# Check that only specified asset was copied
|
|
134
|
+
typst_outdir = outdir / "typst"
|
|
135
|
+
template_out_dir = typst_outdir / "_templates"
|
|
136
|
+
|
|
137
|
+
assert (template_out_dir / "logo.png").exists()
|
|
138
|
+
assert not (template_out_dir / "unused.png").exists()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_copy_template_assets_glob_pattern(tmp_path):
|
|
142
|
+
"""
|
|
143
|
+
Test glob pattern support in asset list.
|
|
144
|
+
|
|
145
|
+
Patterns like "*.png" should match multiple files.
|
|
146
|
+
"""
|
|
147
|
+
srcdir = tmp_path / "source"
|
|
148
|
+
srcdir.mkdir()
|
|
149
|
+
|
|
150
|
+
# Create index.rst
|
|
151
|
+
(srcdir / "index.rst").write_text(
|
|
152
|
+
"Test Document\n" "=============\n" "\n" "This is a test document.\n"
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
# Create template directory and files
|
|
156
|
+
template_dir = srcdir / "_templates"
|
|
157
|
+
template_dir.mkdir()
|
|
158
|
+
|
|
159
|
+
(template_dir / "template.typ").write_text('#image("logo.png")')
|
|
160
|
+
(template_dir / "logo.png").write_bytes(b"logo data")
|
|
161
|
+
(template_dir / "icon.png").write_bytes(b"icon data")
|
|
162
|
+
(template_dir / "readme.txt").write_text("readme")
|
|
163
|
+
|
|
164
|
+
# Create conf.py with glob pattern
|
|
165
|
+
(srcdir / "conf.py").write_text(
|
|
166
|
+
"project = 'Test'\n"
|
|
167
|
+
"extensions = ['typsphinx']\n"
|
|
168
|
+
"typst_template = '_templates/template.typ'\n"
|
|
169
|
+
"typst_template_assets = ['_templates/*.png']\n"
|
|
170
|
+
"typst_documents = [('index', 'index', 'Test', 'Author')]\n"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
outdir = tmp_path / "build"
|
|
174
|
+
|
|
175
|
+
app = SphinxTestApp(buildername="typst", srcdir=srcdir, builddir=outdir)
|
|
176
|
+
|
|
177
|
+
# Build the project
|
|
178
|
+
app.build()
|
|
179
|
+
|
|
180
|
+
# Check that all PNG files were copied, but not TXT
|
|
181
|
+
typst_outdir = outdir / "typst"
|
|
182
|
+
template_out_dir = typst_outdir / "_templates"
|
|
183
|
+
|
|
184
|
+
assert (template_out_dir / "logo.png").exists()
|
|
185
|
+
assert (template_out_dir / "icon.png").exists()
|
|
186
|
+
assert not (template_out_dir / "readme.txt").exists()
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_copy_template_assets_empty_list_disables(tmp_path):
|
|
190
|
+
"""
|
|
191
|
+
Test that empty list disables automatic copying.
|
|
192
|
+
|
|
193
|
+
typst_template_assets = [] should disable all automatic asset copying.
|
|
194
|
+
"""
|
|
195
|
+
srcdir = tmp_path / "source"
|
|
196
|
+
srcdir.mkdir()
|
|
197
|
+
|
|
198
|
+
# Create index.rst
|
|
199
|
+
(srcdir / "index.rst").write_text(
|
|
200
|
+
"Test Document\n" "=============\n" "\n" "This is a test document.\n"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Create template directory and files
|
|
204
|
+
template_dir = srcdir / "_templates"
|
|
205
|
+
template_dir.mkdir()
|
|
206
|
+
|
|
207
|
+
(template_dir / "template.typ").write_text('#image("logo.png")')
|
|
208
|
+
(template_dir / "logo.png").write_bytes(b"logo data")
|
|
209
|
+
|
|
210
|
+
# Create conf.py with empty asset list
|
|
211
|
+
(srcdir / "conf.py").write_text(
|
|
212
|
+
"project = 'Test'\n"
|
|
213
|
+
"extensions = ['typsphinx']\n"
|
|
214
|
+
"typst_template = '_templates/template.typ'\n"
|
|
215
|
+
"typst_template_assets = []\n"
|
|
216
|
+
"typst_documents = [('index', 'index', 'Test', 'Author')]\n"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
outdir = tmp_path / "build"
|
|
220
|
+
|
|
221
|
+
app = SphinxTestApp(buildername="typst", srcdir=srcdir, builddir=outdir)
|
|
222
|
+
|
|
223
|
+
# Build the project
|
|
224
|
+
app.build()
|
|
225
|
+
|
|
226
|
+
# Check that assets were NOT copied
|
|
227
|
+
typst_outdir = outdir / "typst"
|
|
228
|
+
template_out_dir = typst_outdir / "_templates"
|
|
229
|
+
|
|
230
|
+
# Template file should exist (copied by _write_template_file)
|
|
231
|
+
assert (typst_outdir / "_template.typ").exists()
|
|
232
|
+
|
|
233
|
+
# But logo.png should NOT be copied
|
|
234
|
+
assert not (template_out_dir / "logo.png").exists()
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def test_copy_template_assets_no_template(tmp_path):
|
|
238
|
+
"""
|
|
239
|
+
Test that no assets are copied when no template is configured.
|
|
240
|
+
|
|
241
|
+
This ensures backward compatibility with projects not using templates.
|
|
242
|
+
"""
|
|
243
|
+
srcdir = tmp_path / "source"
|
|
244
|
+
srcdir.mkdir()
|
|
245
|
+
|
|
246
|
+
# Create index.rst
|
|
247
|
+
(srcdir / "index.rst").write_text(
|
|
248
|
+
"Test Document\n" "=============\n" "\n" "This is a test document.\n"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Create conf.py WITHOUT template configuration
|
|
252
|
+
(srcdir / "conf.py").write_text(
|
|
253
|
+
"project = 'Test'\n"
|
|
254
|
+
"extensions = ['typsphinx']\n"
|
|
255
|
+
"typst_documents = [('index', 'index', 'Test', 'Author')]\n"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
outdir = tmp_path / "build"
|
|
259
|
+
|
|
260
|
+
app = SphinxTestApp(buildername="typst", srcdir=srcdir, builddir=outdir)
|
|
261
|
+
|
|
262
|
+
# Build should succeed without errors
|
|
263
|
+
app.build()
|
|
264
|
+
|
|
265
|
+
# No template directory should be created
|
|
266
|
+
typst_outdir = outdir / "typst"
|
|
267
|
+
template_out_dir = typst_outdir / "_templates"
|
|
268
|
+
|
|
269
|
+
assert not template_out_dir.exists()
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def test_copy_template_assets_with_typst_package(tmp_path):
|
|
273
|
+
"""
|
|
274
|
+
Test that assets are NOT copied when using Typst Universe packages.
|
|
275
|
+
|
|
276
|
+
Typst Universe packages handle assets automatically, so we should skip copying.
|
|
277
|
+
"""
|
|
278
|
+
srcdir = tmp_path / "source"
|
|
279
|
+
srcdir.mkdir()
|
|
280
|
+
|
|
281
|
+
# Create index.rst
|
|
282
|
+
(srcdir / "index.rst").write_text(
|
|
283
|
+
"Test Document\n" "=============\n" "\n" "This is a test document.\n"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Create template directory (even though using package)
|
|
287
|
+
template_dir = srcdir / "_templates"
|
|
288
|
+
template_dir.mkdir()
|
|
289
|
+
(template_dir / "logo.png").write_bytes(b"logo data")
|
|
290
|
+
|
|
291
|
+
# Create conf.py with Typst package (takes precedence over template)
|
|
292
|
+
(srcdir / "conf.py").write_text(
|
|
293
|
+
"project = 'Test'\n"
|
|
294
|
+
"extensions = ['typsphinx']\n"
|
|
295
|
+
"typst_package = '@preview/charged-ieee:0.1.0'\n"
|
|
296
|
+
"typst_template = '_templates/template.typ'\n"
|
|
297
|
+
"typst_documents = [('index', 'index', 'Test', 'Author')]\n"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
outdir = tmp_path / "build"
|
|
301
|
+
|
|
302
|
+
app = SphinxTestApp(buildername="typst", srcdir=srcdir, builddir=outdir)
|
|
303
|
+
|
|
304
|
+
# Build the project
|
|
305
|
+
app.build()
|
|
306
|
+
|
|
307
|
+
# Check that assets were NOT copied (package handles them)
|
|
308
|
+
typst_outdir = outdir / "typst"
|
|
309
|
+
template_out_dir = typst_outdir / "_templates"
|
|
310
|
+
|
|
311
|
+
assert not (template_out_dir / "logo.png").exists()
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def test_copy_template_assets_missing_source(tmp_path, caplog):
|
|
315
|
+
"""
|
|
316
|
+
Test graceful handling of missing source files.
|
|
317
|
+
|
|
318
|
+
Should log warning but not fail the build.
|
|
319
|
+
"""
|
|
320
|
+
srcdir = tmp_path / "source"
|
|
321
|
+
srcdir.mkdir()
|
|
322
|
+
|
|
323
|
+
# Create index.rst
|
|
324
|
+
(srcdir / "index.rst").write_text(
|
|
325
|
+
"Test Document\n" "=============\n" "\n" "This is a test document.\n"
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
# Create template directory
|
|
329
|
+
template_dir = srcdir / "_templates"
|
|
330
|
+
template_dir.mkdir()
|
|
331
|
+
|
|
332
|
+
(template_dir / "template.typ").write_text('#image("logo.png")')
|
|
333
|
+
# Note: logo.png does NOT exist
|
|
334
|
+
|
|
335
|
+
# Create conf.py with explicit asset that doesn't exist
|
|
336
|
+
(srcdir / "conf.py").write_text(
|
|
337
|
+
"project = 'Test'\n"
|
|
338
|
+
"extensions = ['typsphinx']\n"
|
|
339
|
+
"typst_template = '_templates/template.typ'\n"
|
|
340
|
+
"typst_template_assets = ['_templates/logo.png']\n"
|
|
341
|
+
"typst_documents = [('index', 'index', 'Test', 'Author')]\n"
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
outdir = tmp_path / "build"
|
|
345
|
+
|
|
346
|
+
app = SphinxTestApp(buildername="typst", srcdir=srcdir, builddir=outdir)
|
|
347
|
+
|
|
348
|
+
# Build should succeed despite missing file
|
|
349
|
+
app.build()
|
|
350
|
+
|
|
351
|
+
# Check that warning was logged in Sphinx's warning stream
|
|
352
|
+
warnings = app._warning.getvalue()
|
|
353
|
+
assert "Template asset not found" in warnings
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def test_copy_template_assets_typstpdf_builder(tmp_path):
|
|
357
|
+
"""
|
|
358
|
+
Test that asset copying works with TypstPDFBuilder.
|
|
359
|
+
|
|
360
|
+
Assets should be copied before PDF compilation.
|
|
361
|
+
"""
|
|
362
|
+
srcdir = tmp_path / "source"
|
|
363
|
+
srcdir.mkdir()
|
|
364
|
+
|
|
365
|
+
# Create index.rst
|
|
366
|
+
(srcdir / "index.rst").write_text(
|
|
367
|
+
"Test Document\n" "=============\n" "\n" "This is a test document.\n"
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Create template directory and files
|
|
371
|
+
template_dir = srcdir / "_templates"
|
|
372
|
+
template_dir.mkdir()
|
|
373
|
+
|
|
374
|
+
(template_dir / "template.typ").write_text('#image("logo.png")')
|
|
375
|
+
(template_dir / "logo.png").write_bytes(b"logo data")
|
|
376
|
+
|
|
377
|
+
# Create conf.py
|
|
378
|
+
(srcdir / "conf.py").write_text(
|
|
379
|
+
"project = 'Test'\n"
|
|
380
|
+
"extensions = ['typsphinx']\n"
|
|
381
|
+
"typst_template = '_templates/template.typ'\n"
|
|
382
|
+
"typst_documents = [('index', 'index', 'Test', 'Author')]\n"
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
outdir = tmp_path / "build"
|
|
386
|
+
|
|
387
|
+
app = SphinxTestApp(
|
|
388
|
+
buildername="typstpdf", srcdir=srcdir, builddir=outdir # Use PDF builder
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Build the project
|
|
392
|
+
# Note: This may fail at PDF compilation if typst-py is not properly set up,
|
|
393
|
+
# but asset copying should still happen
|
|
394
|
+
try:
|
|
395
|
+
app.build()
|
|
396
|
+
except Exception:
|
|
397
|
+
pass # Ignore PDF compilation errors
|
|
398
|
+
|
|
399
|
+
# Check that assets were copied before PDF compilation
|
|
400
|
+
typstpdf_outdir = outdir / "typstpdf"
|
|
401
|
+
template_out_dir = typstpdf_outdir / "_templates"
|
|
402
|
+
|
|
403
|
+
assert (template_out_dir / "logo.png").exists()
|
|
@@ -11,8 +11,8 @@ sources using Sphinx, which can then be compiled to PDF using the Typst compiler
|
|
|
11
11
|
:license: MIT, see LICENSE for details.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
__version__ = "0.4.
|
|
15
|
-
__author__ = "
|
|
14
|
+
__version__ = "0.4.3"
|
|
15
|
+
__author__ = "YuSabo"
|
|
16
16
|
|
|
17
17
|
from typing import Any, Dict
|
|
18
18
|
|
|
@@ -55,6 +55,8 @@ def setup(app: Sphinx) -> Dict[str, Any]:
|
|
|
55
55
|
# Task 13.4: Output directory and debug mode
|
|
56
56
|
app.add_config_value("typst_output_dir", "_build/typst", "html", [str])
|
|
57
57
|
app.add_config_value("typst_debug", False, "html", [bool])
|
|
58
|
+
# Issue #75: Template asset support
|
|
59
|
+
app.add_config_value("typst_template_assets", None, "html", [list, type(None)])
|
|
58
60
|
|
|
59
61
|
return {
|
|
60
62
|
"version": __version__,
|
|
@@ -280,14 +280,189 @@ class TypstBuilder(Builder):
|
|
|
280
280
|
except Exception as e:
|
|
281
281
|
logger.warning(f"Failed to copy image {imguri}: {e}")
|
|
282
282
|
|
|
283
|
+
def copy_template_assets(self) -> None:
|
|
284
|
+
"""
|
|
285
|
+
Copy template-associated assets to the output directory.
|
|
286
|
+
|
|
287
|
+
When using custom Typst templates via typst_template configuration,
|
|
288
|
+
this method copies assets (fonts, images, logos, etc.) referenced by
|
|
289
|
+
the template to the output directory.
|
|
290
|
+
|
|
291
|
+
Behavior:
|
|
292
|
+
- If typst_template_assets is configured, copies only specified files/directories
|
|
293
|
+
- If typst_template_assets is None (default), automatically copies entire template directory
|
|
294
|
+
- If typst_template_assets is empty list, disables automatic copying
|
|
295
|
+
- Skips .typ files to avoid duplicating template file (already handled by _write_template_file)
|
|
296
|
+
|
|
297
|
+
This follows the same pattern as copy_image_files() from Issue #38.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
# Early return if no custom template is configured
|
|
301
|
+
template_path = getattr(self.config, "typst_template", None)
|
|
302
|
+
if not template_path:
|
|
303
|
+
return # No custom template
|
|
304
|
+
|
|
305
|
+
# Early return if using Typst Universe package (assets handled by Typst compiler)
|
|
306
|
+
typst_package = getattr(self.config, "typst_package", None)
|
|
307
|
+
if typst_package:
|
|
308
|
+
return
|
|
309
|
+
|
|
310
|
+
# Get template assets configuration
|
|
311
|
+
template_assets = getattr(self.config, "typst_template_assets", None)
|
|
312
|
+
|
|
313
|
+
# Check if explicitly disabled (empty list)
|
|
314
|
+
if template_assets is not None and len(template_assets) == 0:
|
|
315
|
+
logger.debug("Template asset copying disabled (empty list)")
|
|
316
|
+
return
|
|
317
|
+
|
|
318
|
+
logger.info("Copying template assets...")
|
|
319
|
+
|
|
320
|
+
if template_assets:
|
|
321
|
+
# Option 2: Explicit asset list
|
|
322
|
+
self._copy_explicit_assets(template_assets)
|
|
323
|
+
else:
|
|
324
|
+
# Option 1: Automatic directory copy
|
|
325
|
+
self._copy_template_directory(template_path)
|
|
326
|
+
|
|
327
|
+
def _copy_template_directory(self, template_path: str) -> None:
|
|
328
|
+
"""
|
|
329
|
+
Copy entire template directory to output (default behavior).
|
|
330
|
+
|
|
331
|
+
Automatically copies all files in the template directory,
|
|
332
|
+
excluding .typ files (which are handled separately).
|
|
333
|
+
|
|
334
|
+
Args:
|
|
335
|
+
template_path: Path to template file relative to source directory
|
|
336
|
+
"""
|
|
337
|
+
import os
|
|
338
|
+
|
|
339
|
+
# Get template directory path
|
|
340
|
+
template_dir = path.dirname(template_path)
|
|
341
|
+
if not template_dir:
|
|
342
|
+
# Template is in root directory, no assets to copy
|
|
343
|
+
return
|
|
344
|
+
|
|
345
|
+
# Resolve absolute paths
|
|
346
|
+
src_dir = path.join(self.srcdir, template_dir)
|
|
347
|
+
dest_dir = path.join(self.outdir, template_dir)
|
|
348
|
+
|
|
349
|
+
# Check if template directory exists
|
|
350
|
+
if not path.exists(src_dir):
|
|
351
|
+
logger.warning(f"Template directory not found: {src_dir}")
|
|
352
|
+
return
|
|
353
|
+
|
|
354
|
+
# Track copied files for logging
|
|
355
|
+
copied_count = 0
|
|
356
|
+
|
|
357
|
+
# Walk through directory and copy all files except .typ
|
|
358
|
+
for root, _dirs, files in os.walk(src_dir):
|
|
359
|
+
for file in files:
|
|
360
|
+
# Skip .typ files (already handled by _write_template_file)
|
|
361
|
+
if file.endswith(".typ"):
|
|
362
|
+
continue
|
|
363
|
+
|
|
364
|
+
# Get source and destination paths
|
|
365
|
+
src_file = path.join(root, file)
|
|
366
|
+
rel_path = path.relpath(src_file, src_dir)
|
|
367
|
+
dest_file = path.join(dest_dir, rel_path)
|
|
368
|
+
|
|
369
|
+
# Ensure destination directory exists
|
|
370
|
+
ensuredir(path.dirname(dest_file))
|
|
371
|
+
|
|
372
|
+
# Copy the file
|
|
373
|
+
try:
|
|
374
|
+
shutil.copy2(src_file, dest_file)
|
|
375
|
+
logger.debug(f"Copied template asset: {rel_path}")
|
|
376
|
+
copied_count += 1
|
|
377
|
+
except Exception as e:
|
|
378
|
+
logger.warning(f"Failed to copy template asset {rel_path}: {e}")
|
|
379
|
+
|
|
380
|
+
if copied_count > 0:
|
|
381
|
+
logger.info(f"Copied {copied_count} template asset(s) from {template_dir}/")
|
|
382
|
+
|
|
383
|
+
def _copy_explicit_assets(self, assets: list) -> None:
|
|
384
|
+
"""
|
|
385
|
+
Copy explicitly specified assets.
|
|
386
|
+
|
|
387
|
+
Supports individual files, directories, and glob patterns.
|
|
388
|
+
|
|
389
|
+
Args:
|
|
390
|
+
assets: List of asset paths (relative to source directory)
|
|
391
|
+
May include glob patterns like "*.png" or "fonts/*.otf"
|
|
392
|
+
"""
|
|
393
|
+
import glob
|
|
394
|
+
|
|
395
|
+
copied_count = 0
|
|
396
|
+
|
|
397
|
+
for asset_pattern in assets:
|
|
398
|
+
# Resolve absolute pattern path
|
|
399
|
+
abs_pattern = path.join(self.srcdir, asset_pattern)
|
|
400
|
+
|
|
401
|
+
# Check if pattern contains wildcards
|
|
402
|
+
if "*" in asset_pattern or "?" in asset_pattern:
|
|
403
|
+
# Expand glob pattern
|
|
404
|
+
matches = glob.glob(abs_pattern, recursive=True)
|
|
405
|
+
if not matches:
|
|
406
|
+
logger.warning(f"No files matched pattern: {asset_pattern}")
|
|
407
|
+
continue
|
|
408
|
+
|
|
409
|
+
for match in matches:
|
|
410
|
+
if self._copy_single_asset(match, asset_pattern):
|
|
411
|
+
copied_count += 1
|
|
412
|
+
else:
|
|
413
|
+
# Single file or directory
|
|
414
|
+
if self._copy_single_asset(abs_pattern, asset_pattern):
|
|
415
|
+
copied_count += 1
|
|
416
|
+
|
|
417
|
+
if copied_count > 0:
|
|
418
|
+
logger.info(f"Copied {copied_count} explicitly specified template asset(s)")
|
|
419
|
+
|
|
420
|
+
def _copy_single_asset(self, src_path: str, original_pattern: str) -> bool:
|
|
421
|
+
"""
|
|
422
|
+
Copy a single asset file or directory.
|
|
423
|
+
|
|
424
|
+
Args:
|
|
425
|
+
src_path: Absolute source path
|
|
426
|
+
original_pattern: Original pattern from configuration (for error messages)
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
True if successfully copied, False otherwise
|
|
430
|
+
"""
|
|
431
|
+
|
|
432
|
+
# Check if source exists
|
|
433
|
+
if not path.exists(src_path):
|
|
434
|
+
logger.warning(f"Template asset not found: {original_pattern}")
|
|
435
|
+
return False
|
|
436
|
+
|
|
437
|
+
# Calculate relative path from source directory
|
|
438
|
+
rel_path = path.relpath(src_path, self.srcdir)
|
|
439
|
+
dest_path = path.join(self.outdir, rel_path)
|
|
440
|
+
|
|
441
|
+
try:
|
|
442
|
+
if path.isdir(src_path):
|
|
443
|
+
# Copy directory recursively
|
|
444
|
+
# Use copytree with dirs_exist_ok for Python 3.8+
|
|
445
|
+
shutil.copytree(src_path, dest_path, dirs_exist_ok=True)
|
|
446
|
+
logger.debug(f"Copied template asset directory: {rel_path}/")
|
|
447
|
+
else:
|
|
448
|
+
# Copy single file
|
|
449
|
+
ensuredir(path.dirname(dest_path))
|
|
450
|
+
shutil.copy2(src_path, dest_path)
|
|
451
|
+
logger.debug(f"Copied template asset: {rel_path}")
|
|
452
|
+
return True
|
|
453
|
+
except Exception as e:
|
|
454
|
+
logger.warning(f"Failed to copy template asset {rel_path}: {e}")
|
|
455
|
+
return False
|
|
456
|
+
|
|
283
457
|
def finish(self) -> None:
|
|
284
458
|
"""
|
|
285
459
|
Finish the build process.
|
|
286
460
|
|
|
287
461
|
This method is called once after all documents have been written.
|
|
288
|
-
Copies image files to the output directory.
|
|
462
|
+
Copies image files and template assets to the output directory.
|
|
289
463
|
"""
|
|
290
464
|
self.copy_image_files()
|
|
465
|
+
self.copy_template_assets()
|
|
291
466
|
|
|
292
467
|
|
|
293
468
|
class TypstPDFBuilder(TypstBuilder):
|
|
@@ -1930,6 +1930,19 @@ class TypstTranslator(SphinxTranslator):
|
|
|
1930
1930
|
# Get the reference URI
|
|
1931
1931
|
refuri = node.get("refuri", "")
|
|
1932
1932
|
|
|
1933
|
+
# Handle empty URLs (Typst 0.14+ rejects empty URLs)
|
|
1934
|
+
# This can occur with unresolved references, broken cross-references,
|
|
1935
|
+
# or malformed reStructuredText. Instead of generating invalid link("", ...),
|
|
1936
|
+
# we skip the link wrapper and render content as plain text.
|
|
1937
|
+
if not refuri:
|
|
1938
|
+
logger.warning(
|
|
1939
|
+
f"Reference node has empty URL. "
|
|
1940
|
+
f"Link will be rendered as plain text. "
|
|
1941
|
+
f"Check for broken references in source: {node.astext()}"
|
|
1942
|
+
)
|
|
1943
|
+
self._skip_link_wrapper = True
|
|
1944
|
+
return
|
|
1945
|
+
|
|
1933
1946
|
# Determine if we need # prefix (in markup mode)
|
|
1934
1947
|
prefix = "#" if self._in_markup_mode else ""
|
|
1935
1948
|
|
|
@@ -1961,6 +1974,16 @@ class TypstTranslator(SphinxTranslator):
|
|
|
1961
1974
|
Args:
|
|
1962
1975
|
node: The reference node
|
|
1963
1976
|
"""
|
|
1977
|
+
# Skip link wrapper closing if we skipped it in visit
|
|
1978
|
+
if getattr(self, "_skip_link_wrapper", False):
|
|
1979
|
+
self._skip_link_wrapper = False
|
|
1980
|
+
# Restore list item separator state if needed
|
|
1981
|
+
if hasattr(self, "_reference_was_list_item_needs_separator"):
|
|
1982
|
+
if self.in_list_item:
|
|
1983
|
+
self.list_item_needs_separator = True
|
|
1984
|
+
delattr(self, "_reference_was_list_item_needs_separator")
|
|
1985
|
+
return
|
|
1986
|
+
|
|
1964
1987
|
# Close the link function
|
|
1965
1988
|
self.add_text(")")
|
|
1966
1989
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: typsphinx
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Sphinx extension for Typst output
|
|
5
|
-
Author-email:
|
|
5
|
+
Author-email: YuSabo <yusabo90002@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/YuSabo90002/typsphinx
|
|
8
8
|
Project-URL: Documentation, https://github.com/YuSabo90002/typsphinx#readme
|
|
9
9
|
Project-URL: Repository, https://github.com/YuSabo90002/typsphinx
|
|
10
10
|
Project-URL: Issues, https://github.com/YuSabo90002/typsphinx/issues
|
|
11
11
|
Keywords: sphinx,typst,documentation,pdf
|
|
12
|
-
Classifier: Development Status ::
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Framework :: Sphinx :: Extension
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -24,7 +24,7 @@ Description-Content-Type: text/markdown
|
|
|
24
24
|
License-File: LICENSE
|
|
25
25
|
Requires-Dist: sphinx>=5.0
|
|
26
26
|
Requires-Dist: docutils>=0.18
|
|
27
|
-
Requires-Dist: typst>=0.
|
|
27
|
+
Requires-Dist: typst>=0.14.1
|
|
28
28
|
Provides-Extra: dev
|
|
29
29
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
30
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
@@ -84,7 +84,7 @@ typsphinx is a Sphinx extension that enables generating Typst documents from reS
|
|
|
84
84
|
|
|
85
85
|
## Installation
|
|
86
86
|
|
|
87
|
-
### From PyPI
|
|
87
|
+
### From PyPI
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
90
|
pip install typsphinx
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|