typsphinx 0.6.2__tar.gz → 0.6.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.6.2 → typsphinx-0.6.3}/PKG-INFO +2 -3
- {typsphinx-0.6.2 → typsphinx-0.6.3}/README.md +1 -2
- {typsphinx-0.6.2 → typsphinx-0.6.3}/pyproject.toml +1 -1
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_pdf_render_gate.py +328 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_preview_version_sync.py +44 -1
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_template_engine.py +428 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_translator.py +309 -0
- typsphinx-0.6.3/tests/test_typst_elements_pass_through_gate.py +422 -0
- typsphinx-0.6.3/tests/test_typst_lang_gate.py +860 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/typsphinx/__init__.py +0 -1
- {typsphinx-0.6.2 → typsphinx-0.6.3}/typsphinx/template_engine.py +267 -24
- {typsphinx-0.6.2 → typsphinx-0.6.3}/typsphinx/templates/base.typ +2 -1
- {typsphinx-0.6.2 → typsphinx-0.6.3}/typsphinx/translator.py +167 -25
- {typsphinx-0.6.2 → typsphinx-0.6.3}/typsphinx/writer.py +51 -6
- {typsphinx-0.6.2 → typsphinx-0.6.3}/typsphinx.egg-info/PKG-INFO +2 -3
- {typsphinx-0.6.2 → typsphinx-0.6.3}/typsphinx.egg-info/SOURCES.txt +2 -2
- typsphinx-0.6.2/tests/test_config_toctree_defaults.py +0 -245
- typsphinx-0.6.2/tests/test_documentation_configuration.py +0 -156
- {typsphinx-0.6.2 → typsphinx-0.6.3}/LICENSE +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/setup.cfg +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_abbr_pep_separator_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_admonitions.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_builder.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_builder_output_stem.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_builder_requirement13.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_codly_caption_listitem_leak_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_config.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_config_other_options.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_config_template_mapping.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_confval_field_body_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_confval_field_spacing_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_corpus_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_cross_doc_label_namespace_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_deflist_definition_multiblock_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_deflist_nested_definition_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_deflist_term_concat_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_deflist_term_inline_children_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_desc_bodyless_concat_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_desc_container_propagated_target_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_desc_sig_space_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_desc_signature_anchor_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_desc_signature_concat_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_documentation_installation.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_documentation_usage.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_duplicate_include_label_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_entry_points.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_epigraph_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_examples_basic.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_examples_charged_ieee_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_extension.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_external_link_style_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_field_list_in_list_item_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_footnotes.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_glob_image_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_inline_literal_overflow_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_inline_references.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_integration_advanced.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_integration_basic.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_integration_multi_doc.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_integration_nested_toctree.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_label_at_char_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_line_blocks.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_list_item_nested_block_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_math_fallback.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_math_mitex.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_math_native.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_missing_and_malformed_master_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_nested_master_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_nested_toctree_paths.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_package_only_config_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_package_template_routing.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_paragraph_concat_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_paragraph_propagated_target_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_paragraph_soft_newline_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_pdf_generation.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_preview_smoke_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_readme_version_sync.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_ref_target_nested_list_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_rubric_option_concat_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_rubric_propagated_target_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_static_asset_copy_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_substitution_definition_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_table_in_list_item_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_target_label_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_target_name_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_template_assets.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_template_codly.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_template_import_path.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_template_mitex.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_toctree_requirement13.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_topics.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_typst_string_escape_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_wide_table_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/tests/test_xref_orphan_degrade_render_gate.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/typsphinx/builder.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/typsphinx/pdf.py +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/typsphinx.egg-info/dependency_links.txt +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/typsphinx.egg-info/entry_points.txt +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/typsphinx.egg-info/requires.txt +0 -0
- {typsphinx-0.6.2 → typsphinx-0.6.3}/typsphinx.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: typsphinx
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: Sphinx extension for Typst output
|
|
5
5
|
Author-email: YuSabo <yusabo90002@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -249,7 +249,6 @@ Below are the main configuration options. This is not the complete set — see [
|
|
|
249
249
|
- `typst_template`: Custom template path
|
|
250
250
|
- `typst_elements`: Template parameters (paper size, fonts, etc.)
|
|
251
251
|
- `typst_template_mapping`: Sphinx metadata to template parameter mapping
|
|
252
|
-
- `typst_toctree_defaults`: Default toctree options
|
|
253
252
|
|
|
254
253
|
## Development
|
|
255
254
|
|
|
@@ -357,5 +356,5 @@ See [CHANGELOG.md](CHANGELOG.md) for detailed version history.
|
|
|
357
356
|
|
|
358
357
|
---
|
|
359
358
|
|
|
360
|
-
**Status**: Stable (v0.6.
|
|
359
|
+
**Status**: Stable (v0.6.3) - Production ready
|
|
361
360
|
**Python**: 3.12+ | **Sphinx**: 9.1+ | **Typst**: 0.15+
|
|
@@ -205,7 +205,6 @@ Below are the main configuration options. This is not the complete set — see [
|
|
|
205
205
|
- `typst_template`: Custom template path
|
|
206
206
|
- `typst_elements`: Template parameters (paper size, fonts, etc.)
|
|
207
207
|
- `typst_template_mapping`: Sphinx metadata to template parameter mapping
|
|
208
|
-
- `typst_toctree_defaults`: Default toctree options
|
|
209
208
|
|
|
210
209
|
## Development
|
|
211
210
|
|
|
@@ -313,5 +312,5 @@ See [CHANGELOG.md](CHANGELOG.md) for detailed version history.
|
|
|
313
312
|
|
|
314
313
|
---
|
|
315
314
|
|
|
316
|
-
**Status**: Stable (v0.6.
|
|
315
|
+
**Status**: Stable (v0.6.3) - Production ready
|
|
317
316
|
**Python**: 3.12+ | **Sphinx**: 9.1+ | **Typst**: 0.15+
|
|
@@ -126,6 +126,12 @@ def table_width_render_gate_dir(fixtures_dir):
|
|
|
126
126
|
return fixtures_dir / "table_width_render_gate"
|
|
127
127
|
|
|
128
128
|
|
|
129
|
+
@pytest.fixture
|
|
130
|
+
def captioned_table_render_gate_dir(fixtures_dir):
|
|
131
|
+
"""Return the path to the captioned_table_render_gate fixture project."""
|
|
132
|
+
return fixtures_dir / "captioned_table_render_gate"
|
|
133
|
+
|
|
134
|
+
|
|
129
135
|
@pytest.fixture
|
|
130
136
|
def temp_build_dir(tmp_path):
|
|
131
137
|
"""Provide a temporary directory for build output."""
|
|
@@ -2333,3 +2339,325 @@ class TestTableWidthRenderGate:
|
|
|
2333
2339
|
"rendered PDF text -- table-width markup/code-mode "
|
|
2334
2340
|
"regression"
|
|
2335
2341
|
)
|
|
2342
|
+
|
|
2343
|
+
|
|
2344
|
+
# ---------------------------------------------------------------------------
|
|
2345
|
+
# Phase 25 (TBL-01, TBL-02, GATE-01): extend the render-gate pattern to prove
|
|
2346
|
+
# the Plan 25-01 depart_table figure-wrap + single <label> fix renders
|
|
2347
|
+
# correctly through a real sphinx-build -> typst.compile() -> pypdf
|
|
2348
|
+
# round-trip -- two consecutive captioned tables (the stale-buffer proof:
|
|
2349
|
+
# 25-RESEARCH.md Verified Mechanism 2), a caption+:width: composition case,
|
|
2350
|
+
# a :numref:/:ref:-resolves case, and a captioned csv-table + list-table
|
|
2351
|
+
# case (D-05). ``TestCaptionedTablePreFixBasisFailureProof`` below closes
|
|
2352
|
+
# D-06's durable fail-pre-fix red->green requirement by reconstructing the
|
|
2353
|
+
# two pre-fix defect shapes from first principles now that Plan 25-01's fix
|
|
2354
|
+
# has replaced the buggy code -- the double-anchor collision (Critical
|
|
2355
|
+
# Pitfall 3, 25-RESEARCH.md) and the stale-buffer caption loss (Verified
|
|
2356
|
+
# Mechanism 2) -- mirroring the ``TestPreFixBasisFailureProof`` convention
|
|
2357
|
+
# already established in tests/test_package_only_config_gate.py.
|
|
2358
|
+
# ---------------------------------------------------------------------------
|
|
2359
|
+
|
|
2360
|
+
# Sentinel tokens for the captioned-table render gate -- distinctive,
|
|
2361
|
+
# ALLCAPS-no-space words chosen so `full_text.count(...)` can prove each
|
|
2362
|
+
# caption reached the compiled PDF exactly once (guarding against both the
|
|
2363
|
+
# stray-heading bug -- Verified Mechanism 1 -- and the 2nd-table
|
|
2364
|
+
# stale-buffer caption-loss bug -- Verified Mechanism 2). Must match the
|
|
2365
|
+
# sentinel tokens embedded in captioned_table_render_gate/index.rst.
|
|
2366
|
+
TBLCAP_FIRST_SENTINEL = "TBLCAPFIRSTSENTINEL"
|
|
2367
|
+
TBLCAP_SECOND_SENTINEL = "TBLCAPSECONDSENTINEL"
|
|
2368
|
+
TBLCAP_WIDTH_SENTINEL = "TBLCAPWIDTHSENTINEL"
|
|
2369
|
+
TBLCAP_CSV_SENTINEL = "TBLCAPCSVSENTINEL"
|
|
2370
|
+
TBLCAP_LIST_SENTINEL = "TBLCAPLISTSENTINEL"
|
|
2371
|
+
|
|
2372
|
+
|
|
2373
|
+
class _CaptionedTableRenderGateArtifacts:
|
|
2374
|
+
"""
|
|
2375
|
+
Plain container for the one real-compile artifact shared by every
|
|
2376
|
+
``TestCaptionedTableRenderGate`` test method: the emitted Typst source
|
|
2377
|
+
text and the pypdf-extracted PDF text. A plain class (not a dict/tuple)
|
|
2378
|
+
so each thin test method below can read ``.typ_source``/``.full_text``
|
|
2379
|
+
by name.
|
|
2380
|
+
"""
|
|
2381
|
+
|
|
2382
|
+
def __init__(self, typ_source: str, full_text: str) -> None:
|
|
2383
|
+
self.typ_source = typ_source
|
|
2384
|
+
self.full_text = full_text
|
|
2385
|
+
|
|
2386
|
+
|
|
2387
|
+
@pytest.fixture(scope="class")
|
|
2388
|
+
def captioned_table_render_gate_artifacts(tmp_path_factory):
|
|
2389
|
+
"""
|
|
2390
|
+
Build + real-compile the captioned_table_render_gate fixture ONCE per
|
|
2391
|
+
class and return both the emitted Typst source text and the
|
|
2392
|
+
pypdf-extracted PDF text.
|
|
2393
|
+
|
|
2394
|
+
Class-scoped so the four thin test methods below each assert a
|
|
2395
|
+
disjoint slice of the SAME real-compile artifact rather than
|
|
2396
|
+
re-running sphinx-build/typst.compile() four times (mirrors the
|
|
2397
|
+
``topic_line_block_render_gate_pdf_text`` class-scoped fixture pattern
|
|
2398
|
+
above). Depends only on ``tmp_path_factory`` (session-scoped-
|
|
2399
|
+
compatible) -- not on the narrower, function-scoped
|
|
2400
|
+
``captioned_table_render_gate_dir``/``fixtures_dir`` fixtures, to avoid
|
|
2401
|
+
a pytest ScopeMismatch (a class-scoped fixture may not depend on a
|
|
2402
|
+
narrower, function-scoped one).
|
|
2403
|
+
"""
|
|
2404
|
+
source_dir = Path(__file__).parent / "fixtures" / "captioned_table_render_gate"
|
|
2405
|
+
build_dir = tmp_path_factory.mktemp("captioned_table_gate") / "_build"
|
|
2406
|
+
|
|
2407
|
+
result = _run_sphinx_build_typst(source_dir, build_dir)
|
|
2408
|
+
assert (
|
|
2409
|
+
result.returncode == 0
|
|
2410
|
+
), f"sphinx-build failed:\nstdout: {result.stdout}\nstderr: {result.stderr}"
|
|
2411
|
+
|
|
2412
|
+
index_typ = build_dir / "index.typ"
|
|
2413
|
+
assert index_typ.exists(), "index.typ was not generated"
|
|
2414
|
+
typ_source = index_typ.read_text(encoding="utf-8")
|
|
2415
|
+
|
|
2416
|
+
# Compile the emitted .typ to PDF with typst-py, WITHOUT try/except:
|
|
2417
|
+
# this is the crux of GATE-01 -- the double-anchor Critical Pitfall 3
|
|
2418
|
+
# fatal (a captioned table's ids[0] defined twice) would abort the
|
|
2419
|
+
# ENTIRE compile here, failing every dependent test method loudly,
|
|
2420
|
+
# rather than merely failing a string-agreement check.
|
|
2421
|
+
pdf_output = build_dir / "index.pdf"
|
|
2422
|
+
typst.compile(str(index_typ), output=str(pdf_output))
|
|
2423
|
+
|
|
2424
|
+
assert pdf_output.exists(), "PDF file was not created"
|
|
2425
|
+
assert pdf_output.stat().st_size > 0, "PDF file is empty"
|
|
2426
|
+
with open(pdf_output, "rb") as f:
|
|
2427
|
+
magic = f.read(4)
|
|
2428
|
+
assert magic == b"%PDF", "Generated file is not a valid PDF"
|
|
2429
|
+
|
|
2430
|
+
reader = pypdf.PdfReader(str(pdf_output))
|
|
2431
|
+
full_text = "\n".join(page.extract_text() for page in reader.pages)
|
|
2432
|
+
|
|
2433
|
+
return _CaptionedTableRenderGateArtifacts(typ_source, full_text)
|
|
2434
|
+
|
|
2435
|
+
|
|
2436
|
+
@pytest.mark.slow
|
|
2437
|
+
@pytest.mark.skipif(
|
|
2438
|
+
not (TYPST_AVAILABLE and PYPDF_AVAILABLE),
|
|
2439
|
+
reason="typst-py and pypdf are both required for the GATE-01 render gate",
|
|
2440
|
+
)
|
|
2441
|
+
class TestCaptionedTableRenderGate:
|
|
2442
|
+
"""
|
|
2443
|
+
Real-compile acceptance gate for TBL-01/TBL-02: the Plan 25-01
|
|
2444
|
+
depart_table figure-wrap + single <label> fix, covering two
|
|
2445
|
+
consecutive captioned tables (the stale-buffer proof), a
|
|
2446
|
+
caption+:width: composition case, a :numref:/:ref:-resolves case, and
|
|
2447
|
+
a captioned csv-table + list-table case (D-05).
|
|
2448
|
+
|
|
2449
|
+
Requirements: TBL-01, TBL-02, GATE-01 (25-CONTEXT.md, 25-RESEARCH.md
|
|
2450
|
+
Validation Architecture, 25-01-SUMMARY.md).
|
|
2451
|
+
|
|
2452
|
+
All four test methods share the SAME real-compile artifact via the
|
|
2453
|
+
class-scoped ``captioned_table_render_gate_artifacts`` fixture above --
|
|
2454
|
+
sphinx-build/typst.compile() run exactly once per class, not once per
|
|
2455
|
+
test method.
|
|
2456
|
+
"""
|
|
2457
|
+
|
|
2458
|
+
def test_each_caption_sentinel_appears_exactly_once(
|
|
2459
|
+
self, captioned_table_render_gate_artifacts
|
|
2460
|
+
):
|
|
2461
|
+
"""
|
|
2462
|
+
Each of the five caption sentinels appears EXACTLY ONCE in the
|
|
2463
|
+
extracted PDF text -- proving neither the stray-heading bug
|
|
2464
|
+
(Verified Mechanism 1) nor the 2nd-table stale-buffer
|
|
2465
|
+
caption-loss bug (Verified Mechanism 2) regressed. The 2nd
|
|
2466
|
+
table's sentinel (TBLCAPSECONDSENTINEL) is the specific
|
|
2467
|
+
stale-buffer proof: a single-table fixture cannot expose it.
|
|
2468
|
+
"""
|
|
2469
|
+
full_text = captioned_table_render_gate_artifacts.full_text
|
|
2470
|
+
|
|
2471
|
+
for sentinel in (
|
|
2472
|
+
TBLCAP_FIRST_SENTINEL,
|
|
2473
|
+
TBLCAP_SECOND_SENTINEL,
|
|
2474
|
+
TBLCAP_WIDTH_SENTINEL,
|
|
2475
|
+
TBLCAP_CSV_SENTINEL,
|
|
2476
|
+
TBLCAP_LIST_SENTINEL,
|
|
2477
|
+
):
|
|
2478
|
+
assert full_text.count(sentinel) == 1, (
|
|
2479
|
+
f"Expected caption sentinel '{sentinel}' exactly once in "
|
|
2480
|
+
"extracted PDF text -- possible stray-heading or "
|
|
2481
|
+
"stale-buffer caption-loss regression (TBL-01)"
|
|
2482
|
+
)
|
|
2483
|
+
|
|
2484
|
+
def test_typ_source_uses_figure_kind_table_with_no_heading_above(
|
|
2485
|
+
self, captioned_table_render_gate_artifacts
|
|
2486
|
+
):
|
|
2487
|
+
"""
|
|
2488
|
+
The emitted index.typ wraps every captioned table in
|
|
2489
|
+
figure(..., kind: table) -- native Typst "Table N" numbering --
|
|
2490
|
+
and no heading() call appears anywhere in the captioned-table
|
|
2491
|
+
region (from the first figure( call onward): this fixture's only
|
|
2492
|
+
heading() is the document's own top-level title, which precedes
|
|
2493
|
+
every table (SC#1).
|
|
2494
|
+
"""
|
|
2495
|
+
typ_source = captioned_table_render_gate_artifacts.typ_source
|
|
2496
|
+
|
|
2497
|
+
assert "figure(" in typ_source, (
|
|
2498
|
+
"Expected 'figure(' in generated Typst source -- depart_table "
|
|
2499
|
+
"figure-wrap regression"
|
|
2500
|
+
)
|
|
2501
|
+
assert "kind: table" in typ_source, (
|
|
2502
|
+
"Expected 'kind: table' in generated Typst source -- "
|
|
2503
|
+
"depart_table figure-wrap regression"
|
|
2504
|
+
)
|
|
2505
|
+
|
|
2506
|
+
first_figure_index = typ_source.index("figure(")
|
|
2507
|
+
captioned_table_region = typ_source[first_figure_index:]
|
|
2508
|
+
assert "heading(" not in captioned_table_region, (
|
|
2509
|
+
"Found a stray 'heading(' call inside the captioned-table "
|
|
2510
|
+
"region -- a table caption fell through to the generic "
|
|
2511
|
+
"section-heading path instead of being figure-wrapped "
|
|
2512
|
+
"(Verified Mechanism 1 regression)"
|
|
2513
|
+
)
|
|
2514
|
+
|
|
2515
|
+
def test_xref_numref_ref_resolve_no_empty_link(
|
|
2516
|
+
self, captioned_table_render_gate_artifacts
|
|
2517
|
+
):
|
|
2518
|
+
"""
|
|
2519
|
+
The :numref:/:ref: cross-references to the first captioned table
|
|
2520
|
+
both resolve to a real link(<label>, ...) anchor reference in the
|
|
2521
|
+
generated Typst source, with no link("", ...) leak -- mirrors
|
|
2522
|
+
TestXrefRefidRenderGate's D-04 contract, applied to a captioned
|
|
2523
|
+
table's own <label> instead of a section/glossary anchor.
|
|
2524
|
+
"""
|
|
2525
|
+
typ_source = captioned_table_render_gate_artifacts.typ_source
|
|
2526
|
+
|
|
2527
|
+
assert "link(<" in typ_source, (
|
|
2528
|
+
"Expected at least one refid link(<...>, ...) anchor "
|
|
2529
|
+
"reference in the generated Typst source -- the "
|
|
2530
|
+
":numref:/:ref: to a captioned table may have regressed"
|
|
2531
|
+
)
|
|
2532
|
+
assert 'link("",' not in typ_source, (
|
|
2533
|
+
'Found link("", ...) in generated Typst source -- the '
|
|
2534
|
+
'never-link("", ...) contract has regressed'
|
|
2535
|
+
)
|
|
2536
|
+
|
|
2537
|
+
def test_no_literal_source_leak(self, captioned_table_render_gate_artifacts):
|
|
2538
|
+
"""No LEAK_SIGNATURES token reaches the extracted PDF text."""
|
|
2539
|
+
full_text = captioned_table_render_gate_artifacts.full_text
|
|
2540
|
+
|
|
2541
|
+
for leaked_token in LEAK_SIGNATURES:
|
|
2542
|
+
assert leaked_token not in full_text, (
|
|
2543
|
+
f"Literal Typst source '{leaked_token}' leaked into "
|
|
2544
|
+
"rendered PDF text -- captioned-table markup/code-mode "
|
|
2545
|
+
"regression"
|
|
2546
|
+
)
|
|
2547
|
+
|
|
2548
|
+
|
|
2549
|
+
@pytest.mark.slow
|
|
2550
|
+
@pytest.mark.skipif(
|
|
2551
|
+
not TYPST_AVAILABLE,
|
|
2552
|
+
reason="typst-py is required for the GATE-01 pre-fix-basis failure proof",
|
|
2553
|
+
)
|
|
2554
|
+
class TestCaptionedTablePreFixBasisFailureProof:
|
|
2555
|
+
"""
|
|
2556
|
+
Standing pre-fix-basis failure proof (D-06): reconstructs the two
|
|
2557
|
+
pre-fix defect shapes from first principles -- the buggy code no
|
|
2558
|
+
longer exists post Plan 25-01 -- and proves each one is genuinely
|
|
2559
|
+
fail-pre-fix, mirroring the
|
|
2560
|
+
``tests/test_package_only_config_gate.py::TestPreFixBasisFailureProof``
|
|
2561
|
+
convention (reconstruct the pre-fix compile basis; never assert on the
|
|
2562
|
+
TEXT of a compiler error message).
|
|
2563
|
+
|
|
2564
|
+
(a) Double-anchor basis (Critical Pitfall 3, 25-RESEARCH.md): a
|
|
2565
|
+
minimal Typst source in which one label id is defined TWICE -- once
|
|
2566
|
+
as a ``[#metadata(none) <dup>]`` anchor (the unconditional
|
|
2567
|
+
``_emit_id_anchors`` call ``visit_table`` used to make even for a
|
|
2568
|
+
captioned table) and once as a ``figure(...) <dup>`` postfix (the
|
|
2569
|
+
NEW figure-wrap self-anchor this phase adds) -- plus a
|
|
2570
|
+
``link(<dup>, ...)`` reference to that label, reproducing the actual
|
|
2571
|
+
:numref:/:ref: xref shape that resolves it. Live-verified while
|
|
2572
|
+
writing this module: Typst does NOT raise merely from defining a
|
|
2573
|
+
label twice with no reference to it; the "label ... occurs multiple
|
|
2574
|
+
times" fatal fires only when something (here, the xref's own
|
|
2575
|
+
``link(<dup>, ...)``) tries to RESOLVE the ambiguous label -- exactly
|
|
2576
|
+
the real-world shape a :numref:/:ref: to a captioned table produces.
|
|
2577
|
+
|
|
2578
|
+
(b) Stale-buffer basis (Verified Mechanism 2, 25-RESEARCH.md): a
|
|
2579
|
+
reconstructed pre-fix two-table Typst source -- a plain ``table(...)``
|
|
2580
|
+
for each, a stray ``heading(...)`` above the first (the OLD
|
|
2581
|
+
stray-heading caption bug), and the SECOND caption sentinel
|
|
2582
|
+
deliberately ABSENT (the observed pre-fix stale-buffer shape, where a
|
|
2583
|
+
2nd table's caption silently vanished into a leftover buffer from the
|
|
2584
|
+
first). This half is a pure string reconstruction (no compile needed)
|
|
2585
|
+
proving the positive gate's ``full_text.count(sentinel) == 1``
|
|
2586
|
+
assertion is genuinely fail-pre-fix: applied to this reconstruction,
|
|
2587
|
+
the second sentinel's count is 0, not 1.
|
|
2588
|
+
|
|
2589
|
+
Requirements: TBL-01, TBL-02, D-06.
|
|
2590
|
+
"""
|
|
2591
|
+
|
|
2592
|
+
def test_double_anchor_basis_raises(self, tmp_path):
|
|
2593
|
+
"""
|
|
2594
|
+
BUG basis (Critical Pitfall 3): reconstruct a captioned table
|
|
2595
|
+
whose id is anchored TWICE -- once via the old unconditional
|
|
2596
|
+
``_emit_id_anchors`` metadata anchor, once via the new figure
|
|
2597
|
+
``<label>`` postfix -- with a ``link(<dup>, ...)`` xref
|
|
2598
|
+
resolving it, and assert a real ``typst.compile()`` RAISES. No
|
|
2599
|
+
assertion matches the exception's message text (D-06) -- only
|
|
2600
|
+
that it raises.
|
|
2601
|
+
"""
|
|
2602
|
+
reconstructed = (
|
|
2603
|
+
"#metadata(none) <dup>\n"
|
|
2604
|
+
"\n"
|
|
2605
|
+
"#figure(\n"
|
|
2606
|
+
" table(\n"
|
|
2607
|
+
" columns: (1fr, 1fr),\n"
|
|
2608
|
+
" [A], [B],\n"
|
|
2609
|
+
" ),\n"
|
|
2610
|
+
" caption: [Caption],\n"
|
|
2611
|
+
" kind: table,\n"
|
|
2612
|
+
") <dup>\n"
|
|
2613
|
+
"\n"
|
|
2614
|
+
"See #link(<dup>, [Ref]).\n"
|
|
2615
|
+
)
|
|
2616
|
+
target = tmp_path / "double_anchor_basis.typ"
|
|
2617
|
+
target.write_text(reconstructed, encoding="utf-8")
|
|
2618
|
+
|
|
2619
|
+
with pytest.raises(Exception):
|
|
2620
|
+
typst.compile(str(target), root=str(tmp_path))
|
|
2621
|
+
|
|
2622
|
+
def test_stale_buffer_basis_second_sentinel_count_is_zero(self):
|
|
2623
|
+
"""
|
|
2624
|
+
Stale-buffer basis (Verified Mechanism 2): a reconstructed pre-fix
|
|
2625
|
+
two-table Typst source in which the SECOND table's caption
|
|
2626
|
+
sentinel is deliberately absent (the observed pre-fix shape --
|
|
2627
|
+
the second table's caption silently vanished into a stale
|
|
2628
|
+
``table_cell_content`` buffer left over from the first table).
|
|
2629
|
+
Asserts the positive gate's ``count(...) == 1`` check FAILS
|
|
2630
|
+
(count is 0) against this reconstruction -- proof the gate is
|
|
2631
|
+
genuinely fail-pre-fix, not vacuously green.
|
|
2632
|
+
"""
|
|
2633
|
+
reconstructed_pre_fix_shape = (
|
|
2634
|
+
"heading(level: 1, {text(" + repr(TBLCAP_FIRST_SENTINEL) + ")})\n"
|
|
2635
|
+
"\n"
|
|
2636
|
+
"table(\n"
|
|
2637
|
+
" columns: (1fr, 1fr),\n"
|
|
2638
|
+
" [A], [B],\n"
|
|
2639
|
+
")\n"
|
|
2640
|
+
"\n"
|
|
2641
|
+
"table(\n"
|
|
2642
|
+
" columns: (1fr, 1fr),\n"
|
|
2643
|
+
" [C], [D],\n"
|
|
2644
|
+
")\n"
|
|
2645
|
+
)
|
|
2646
|
+
|
|
2647
|
+
# The pre-fix stray-heading bug: the FIRST table's caption leaked
|
|
2648
|
+
# into a heading() call (Verified Mechanism 1) instead of a
|
|
2649
|
+
# figure caption -- present here to keep the reconstruction
|
|
2650
|
+
# faithful to the actually-observed pre-fix output shape.
|
|
2651
|
+
assert TBLCAP_FIRST_SENTINEL in reconstructed_pre_fix_shape
|
|
2652
|
+
|
|
2653
|
+
# The proof this test exists for: the SECOND table's caption
|
|
2654
|
+
# sentinel never reached the reconstructed output at all -- the
|
|
2655
|
+
# stale-buffer bug swallowed it. count() is 0, not 1, so the
|
|
2656
|
+
# positive gate's exact-once assertion would have failed loudly
|
|
2657
|
+
# against this pre-fix basis.
|
|
2658
|
+
assert reconstructed_pre_fix_shape.count(TBLCAP_SECOND_SENTINEL) == 0, (
|
|
2659
|
+
"Expected the reconstructed pre-fix stale-buffer basis to have "
|
|
2660
|
+
"ZERO occurrences of the second table's caption sentinel -- "
|
|
2661
|
+
"if this fails, the reconstruction no longer reproduces the "
|
|
2662
|
+
"pre-fix bug shape and the fail-pre-fix proof is void"
|
|
2663
|
+
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Tests guarding the
|
|
2
|
+
Tests guarding the `@preview` version-sync hazard.
|
|
3
3
|
|
|
4
4
|
typsphinx declares the same four Typst Universe `@preview` package versions
|
|
5
5
|
(codly, codly-languages, mitex, gentle-clues) in three separate places:
|
|
@@ -9,6 +9,17 @@ Typst documents can end up importing mismatched package versions depending
|
|
|
9
9
|
on which code path produced them. This module asserts the three declaration
|
|
10
10
|
sites agree, so a future single-file edit fails CI loudly instead of
|
|
11
11
|
silently (D-03).
|
|
12
|
+
|
|
13
|
+
The bundled `examples/` templates are a fourth surface. They are not part of
|
|
14
|
+
the extension's own import generation, so they carry no lockstep *identity*
|
|
15
|
+
requirement across all four packages -- an example may legitimately use only
|
|
16
|
+
some of them, or a different package entirely (charged-ieee). But when an
|
|
17
|
+
example does pin one of the four, a stale pin is not cosmetic: it makes the
|
|
18
|
+
shipped sample fail to compile outright (`codly-languages` older than 0.1.10
|
|
19
|
+
aborts with `unknown variable: kai`). That is exactly what happened to
|
|
20
|
+
`examples/advanced/_templates/custom.typ`, which sat three milestones behind
|
|
21
|
+
the v0.5.0 bump because nothing watched it. `test_example_templates_match_canonical_versions`
|
|
22
|
+
closes that gap.
|
|
12
23
|
"""
|
|
13
24
|
|
|
14
25
|
import re
|
|
@@ -20,6 +31,8 @@ WRITER_PATH = REPO_ROOT / "typsphinx" / "writer.py"
|
|
|
20
31
|
TEMPLATE_ENGINE_PATH = REPO_ROOT / "typsphinx" / "template_engine.py"
|
|
21
32
|
BASE_TYP_PATH = REPO_ROOT / "typsphinx" / "templates" / "base.typ"
|
|
22
33
|
|
|
34
|
+
EXAMPLES_DIR = REPO_ROOT / "examples"
|
|
35
|
+
|
|
23
36
|
EXPECTED_PACKAGES = {"codly", "codly-languages", "mitex", "gentle-clues"}
|
|
24
37
|
|
|
25
38
|
# Matches an actual Typst `#import "@preview/<name>:<version>"` statement
|
|
@@ -103,3 +116,33 @@ def test_all_four_packages_declared():
|
|
|
103
116
|
f"{filename} is missing expected @preview packages: {missing} "
|
|
104
117
|
f"(declared: {declared})"
|
|
105
118
|
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_example_templates_match_canonical_versions():
|
|
122
|
+
"""Bundled `examples/` .typ templates must not pin a stale version.
|
|
123
|
+
|
|
124
|
+
Scans every `.typ` file under `examples/` and, for each of the four
|
|
125
|
+
packages typsphinx itself pins, asserts the example agrees with the
|
|
126
|
+
canonical version in `base.typ`. Packages outside that set (e.g. an
|
|
127
|
+
example built on `charged-ieee`) are ignored, and an example that
|
|
128
|
+
imports none of the four is trivially fine -- this guards against
|
|
129
|
+
*drift*, not against an example choosing a different toolkit.
|
|
130
|
+
"""
|
|
131
|
+
canonical = _extract_preview_versions(BASE_TYP_PATH)
|
|
132
|
+
|
|
133
|
+
stale = []
|
|
134
|
+
for typ_path in sorted(EXAMPLES_DIR.rglob("*.typ")):
|
|
135
|
+
for package, version in _extract_preview_versions(typ_path).items():
|
|
136
|
+
if package not in canonical:
|
|
137
|
+
continue
|
|
138
|
+
if version != canonical[package]:
|
|
139
|
+
stale.append(
|
|
140
|
+
f"{typ_path.relative_to(REPO_ROOT)}: {package} pins "
|
|
141
|
+
f"{version}, base.typ pins {canonical[package]}"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
assert not stale, (
|
|
145
|
+
"bundled example template(s) pin @preview versions that diverge from "
|
|
146
|
+
"typsphinx/templates/base.typ -- a shipped sample that cannot compile: "
|
|
147
|
+
+ "; ".join(stale)
|
|
148
|
+
)
|