latextify 0.1.0__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.
Files changed (298) hide show
  1. latextify-0.1.0/.dockerignore +20 -0
  2. latextify-0.1.0/.gitattributes +5 -0
  3. latextify-0.1.0/.github/dependabot.yml +13 -0
  4. latextify-0.1.0/.github/workflows/ci.yml +172 -0
  5. latextify-0.1.0/.github/workflows/codeql.yml +23 -0
  6. latextify-0.1.0/.github/workflows/dependency-audit.yml +45 -0
  7. latextify-0.1.0/.github/workflows/publish.yml +72 -0
  8. latextify-0.1.0/.github/workflows/release.yml +120 -0
  9. latextify-0.1.0/.gitignore +52 -0
  10. latextify-0.1.0/CONTRIBUTING.md +51 -0
  11. latextify-0.1.0/LICENSE +202 -0
  12. latextify-0.1.0/NOTICE +9 -0
  13. latextify-0.1.0/PKG-INFO +285 -0
  14. latextify-0.1.0/README.md +241 -0
  15. latextify-0.1.0/SECURITY.md +96 -0
  16. latextify-0.1.0/deploy/hf-space/DEPLOY.md +77 -0
  17. latextify-0.1.0/deploy/hf-space/Dockerfile +46 -0
  18. latextify-0.1.0/deploy/hf-space/README.md +43 -0
  19. latextify-0.1.0/deploy/hf-space/warm_cache.py +83 -0
  20. latextify-0.1.0/deploy/render/DEPLOY.md +50 -0
  21. latextify-0.1.0/deploy/render/Dockerfile +44 -0
  22. latextify-0.1.0/examples/01-all-embedded/.gitignore +4 -0
  23. latextify-0.1.0/examples/01-all-embedded/README.md +69 -0
  24. latextify-0.1.0/examples/01-all-embedded/make_manuscript.py +135 -0
  25. latextify-0.1.0/examples/01-all-embedded/run.py +53 -0
  26. latextify-0.1.0/examples/02-word-plus-figures/.gitignore +6 -0
  27. latextify-0.1.0/examples/02-word-plus-figures/README.md +71 -0
  28. latextify-0.1.0/examples/02-word-plus-figures/make_manuscript.py +135 -0
  29. latextify-0.1.0/examples/02-word-plus-figures/run.py +50 -0
  30. latextify-0.1.0/examples/03-multipart-refmanager/.gitignore +5 -0
  31. latextify-0.1.0/examples/03-multipart-refmanager/README.md +109 -0
  32. latextify-0.1.0/examples/03-multipart-refmanager/make_manuscript.py +279 -0
  33. latextify-0.1.0/examples/03-multipart-refmanager/run.py +53 -0
  34. latextify-0.1.0/examples/README.md +59 -0
  35. latextify-0.1.0/latextify/__init__.py +15 -0
  36. latextify-0.1.0/latextify/__main__.py +11 -0
  37. latextify-0.1.0/latextify/audit/__init__.py +20 -0
  38. latextify-0.1.0/latextify/audit/equations.py +433 -0
  39. latextify-0.1.0/latextify/citations/__init__.py +37 -0
  40. latextify-0.1.0/latextify/citations/authoryear_index.py +54 -0
  41. latextify-0.1.0/latextify/citations/bib.py +353 -0
  42. latextify-0.1.0/latextify/citations/bibmatch.py +57 -0
  43. latextify-0.1.0/latextify/citations/bibtex_in.py +212 -0
  44. latextify-0.1.0/latextify/citations/corrections.py +173 -0
  45. latextify-0.1.0/latextify/citations/crossref.py +342 -0
  46. latextify-0.1.0/latextify/citations/csl_json_in.py +60 -0
  47. latextify-0.1.0/latextify/citations/endnote.py +208 -0
  48. latextify-0.1.0/latextify/citations/endnote_xml_in.py +72 -0
  49. latextify-0.1.0/latextify/citations/fields.py +334 -0
  50. latextify-0.1.0/latextify/citations/mendeley.py +27 -0
  51. latextify-0.1.0/latextify/citations/merge.py +77 -0
  52. latextify-0.1.0/latextify/citations/nbib_in.py +143 -0
  53. latextify-0.1.0/latextify/citations/plaintext.py +622 -0
  54. latextify-0.1.0/latextify/citations/reconcile.py +245 -0
  55. latextify-0.1.0/latextify/citations/reflist_nondocx.py +94 -0
  56. latextify-0.1.0/latextify/citations/refs_import.py +72 -0
  57. latextify-0.1.0/latextify/citations/validate.py +303 -0
  58. latextify-0.1.0/latextify/citations/wordnative.py +211 -0
  59. latextify-0.1.0/latextify/citations/zotero.py +128 -0
  60. latextify-0.1.0/latextify/cli.py +517 -0
  61. latextify-0.1.0/latextify/cli_batch.py +226 -0
  62. latextify-0.1.0/latextify/cli_clean.py +50 -0
  63. latextify-0.1.0/latextify/cli_equations.py +92 -0
  64. latextify-0.1.0/latextify/cli_export.py +100 -0
  65. latextify-0.1.0/latextify/cli_review.py +114 -0
  66. latextify-0.1.0/latextify/compile/__init__.py +12 -0
  67. latextify-0.1.0/latextify/compile/logs.py +148 -0
  68. latextify-0.1.0/latextify/compile/pdf.py +42 -0
  69. latextify-0.1.0/latextify/compile/tectonic.py +383 -0
  70. latextify-0.1.0/latextify/emit/__init__.py +41 -0
  71. latextify-0.1.0/latextify/emit/alt_formats.py +458 -0
  72. latextify-0.1.0/latextify/emit/alt_formats_render.py +264 -0
  73. latextify-0.1.0/latextify/emit/anchors.py +272 -0
  74. latextify-0.1.0/latextify/emit/metadata.py +36 -0
  75. latextify-0.1.0/latextify/emit/project.py +971 -0
  76. latextify-0.1.0/latextify/emit/submission.py +312 -0
  77. latextify-0.1.0/latextify/figures/__init__.py +29 -0
  78. latextify-0.1.0/latextify/figures/convert.py +476 -0
  79. latextify-0.1.0/latextify/figures/crop.py +254 -0
  80. latextify-0.1.0/latextify/figures/extract.py +288 -0
  81. latextify-0.1.0/latextify/figures/override.py +186 -0
  82. latextify-0.1.0/latextify/gui/__init__.py +18 -0
  83. latextify-0.1.0/latextify/gui/demo.py +178 -0
  84. latextify-0.1.0/latextify/gui/downloads.py +140 -0
  85. latextify-0.1.0/latextify/gui/exporting.py +55 -0
  86. latextify-0.1.0/latextify/gui/folder_picker.py +47 -0
  87. latextify-0.1.0/latextify/gui/guard.py +131 -0
  88. latextify-0.1.0/latextify/gui/schemas.py +192 -0
  89. latextify-0.1.0/latextify/gui/server.py +921 -0
  90. latextify-0.1.0/latextify/gui/static/alt-export.js +73 -0
  91. latextify-0.1.0/latextify/gui/static/app.js +458 -0
  92. latextify-0.1.0/latextify/gui/static/clean.js +64 -0
  93. latextify-0.1.0/latextify/gui/static/export.js +90 -0
  94. latextify-0.1.0/latextify/gui/static/index.html +204 -0
  95. latextify-0.1.0/latextify/gui/static/results.js +88 -0
  96. latextify-0.1.0/latextify/gui/static/review.js +193 -0
  97. latextify-0.1.0/latextify/gui/static/style.css +182 -0
  98. latextify-0.1.0/latextify/gui/upload_utils.py +80 -0
  99. latextify-0.1.0/latextify/gui/uploads_routes.py +161 -0
  100. latextify-0.1.0/latextify/ingest/__init__.py +16 -0
  101. latextify-0.1.0/latextify/ingest/_xml.py +39 -0
  102. latextify-0.1.0/latextify/ingest/archive_guard.py +198 -0
  103. latextify-0.1.0/latextify/ingest/citation_sentinels.py +162 -0
  104. latextify-0.1.0/latextify/ingest/docx_clean.py +431 -0
  105. latextify-0.1.0/latextify/ingest/filters.py +1061 -0
  106. latextify-0.1.0/latextify/ingest/formats.py +105 -0
  107. latextify-0.1.0/latextify/ingest/frontmatter.py +70 -0
  108. latextify-0.1.0/latextify/ingest/metadata_guess.py +919 -0
  109. latextify-0.1.0/latextify/ingest/metadata_guess_nondocx.py +93 -0
  110. latextify-0.1.0/latextify/ingest/pandoc.py +242 -0
  111. latextify-0.1.0/latextify/ingest/portable_anchors.py +68 -0
  112. latextify-0.1.0/latextify/ingest/preflight.py +353 -0
  113. latextify-0.1.0/latextify/kit/README-OFFLINE.md +119 -0
  114. latextify-0.1.0/latextify/kit/__init__.py +10 -0
  115. latextify-0.1.0/latextify/kit/build.py +468 -0
  116. latextify-0.1.0/latextify/kit/install_template.py +209 -0
  117. latextify-0.1.0/latextify/model/__init__.py +76 -0
  118. latextify-0.1.0/latextify/model/_compat.py +53 -0
  119. latextify-0.1.0/latextify/model/body.py +52 -0
  120. latextify-0.1.0/latextify/model/compile.py +54 -0
  121. latextify-0.1.0/latextify/model/emit.py +127 -0
  122. latextify-0.1.0/latextify/model/equations.py +107 -0
  123. latextify-0.1.0/latextify/model/figure.py +146 -0
  124. latextify-0.1.0/latextify/model/meta.py +59 -0
  125. latextify-0.1.0/latextify/model/preflight.py +72 -0
  126. latextify-0.1.0/latextify/model/reconcile.py +74 -0
  127. latextify-0.1.0/latextify/model/refs.py +77 -0
  128. latextify-0.1.0/latextify/model/validate.py +123 -0
  129. latextify-0.1.0/latextify/report/__init__.py +13 -0
  130. latextify-0.1.0/latextify/report/render.py +344 -0
  131. latextify-0.1.0/latextify/templates/__init__.py +17 -0
  132. latextify-0.1.0/latextify/templates/authors.py +131 -0
  133. latextify-0.1.0/latextify/templates/journals/.gitkeep +0 -0
  134. latextify-0.1.0/latextify/templates/journals/achemso/manifest.yaml +91 -0
  135. latextify-0.1.0/latextify/templates/journals/achemso/metadata.tex.j2 +36 -0
  136. latextify-0.1.0/latextify/templates/journals/achemso/preamble.tex.j2 +26 -0
  137. latextify-0.1.0/latextify/templates/journals/aip-advances/manifest.yaml +10 -0
  138. latextify-0.1.0/latextify/templates/journals/aip-apl/manifest.yaml +12 -0
  139. latextify-0.1.0/latextify/templates/journals/aip-jap/manifest.yaml +10 -0
  140. latextify-0.1.0/latextify/templates/journals/aps-prapplied/manifest.yaml +4 -0
  141. latextify-0.1.0/latextify/templates/journals/aps-prl/manifest.yaml +6 -0
  142. latextify-0.1.0/latextify/templates/journals/aps-prx/manifest.yaml +4 -0
  143. latextify-0.1.0/latextify/templates/journals/aps-rmp/manifest.yaml +4 -0
  144. latextify-0.1.0/latextify/templates/journals/elsarticle/manifest.yaml +57 -0
  145. latextify-0.1.0/latextify/templates/journals/elsarticle/metadata.tex.j2 +36 -0
  146. latextify-0.1.0/latextify/templates/journals/elsarticle/preamble.tex.j2 +12 -0
  147. latextify-0.1.0/latextify/templates/journals/elsarticle/vendor/elsarticle.cls +1403 -0
  148. latextify-0.1.0/latextify/templates/journals/ieeetran/manifest.yaml +42 -0
  149. latextify-0.1.0/latextify/templates/journals/ieeetran/metadata.tex.j2 +20 -0
  150. latextify-0.1.0/latextify/templates/journals/ieeetran/preamble.tex.j2 +17 -0
  151. latextify-0.1.0/latextify/templates/journals/iopart/VENDOR_LICENSE.txt +63 -0
  152. latextify-0.1.0/latextify/templates/journals/iopart/manifest.yaml +73 -0
  153. latextify-0.1.0/latextify/templates/journals/iopart/metadata.tex.j2 +32 -0
  154. latextify-0.1.0/latextify/templates/journals/iopart/preamble.tex.j2 +20 -0
  155. latextify-0.1.0/latextify/templates/journals/iopart/vendor/.gitattributes +6 -0
  156. latextify-0.1.0/latextify/templates/journals/iopart/vendor/iopart.cls +1107 -0
  157. latextify-0.1.0/latextify/templates/journals/iopart/vendor/iopart10.clo +1 -0
  158. latextify-0.1.0/latextify/templates/journals/iopart/vendor/iopart12.clo +1 -0
  159. latextify-0.1.0/latextify/templates/journals/revtex4-2/manifest.yaml +57 -0
  160. latextify-0.1.0/latextify/templates/journals/revtex4-2/metadata.tex.j2 +22 -0
  161. latextify-0.1.0/latextify/templates/journals/revtex4-2/preamble.tex.j2 +17 -0
  162. latextify-0.1.0/latextify/templates/journals/sn-jnl/VENDOR_LICENSE.txt +30 -0
  163. latextify-0.1.0/latextify/templates/journals/sn-jnl/manifest.yaml +99 -0
  164. latextify-0.1.0/latextify/templates/journals/sn-jnl/metadata.tex.j2 +23 -0
  165. latextify-0.1.0/latextify/templates/journals/sn-jnl/preamble.tex.j2 +22 -0
  166. latextify-0.1.0/latextify/templates/journals/sn-jnl/vendor/.gitattributes +6 -0
  167. latextify-0.1.0/latextify/templates/journals/sn-jnl/vendor/sn-jnl.cls +1 -0
  168. latextify-0.1.0/latextify/templates/journals/sn-jnl/vendor/sn-mathphys-ay.bst +3208 -0
  169. latextify-0.1.0/latextify/templates/journals/sn-jnl/vendor/sn-mathphys-num.bst +3211 -0
  170. latextify-0.1.0/latextify/templates/journals/wiley/manifest.yaml +103 -0
  171. latextify-0.1.0/latextify/templates/journals/wiley/metadata.tex.j2 +23 -0
  172. latextify-0.1.0/latextify/templates/journals/wiley/preamble.tex.j2 +14 -0
  173. latextify-0.1.0/latextify/templates/loader.py +539 -0
  174. latextify-0.1.0/plans/FORMATS_AND_PRIVACY_PLAN.md +180 -0
  175. latextify-0.1.0/plans/GUI_MULTIFILE_PLAN.md +94 -0
  176. latextify-0.1.0/plans/GUI_OPTIONS_FORMATS_PLAN.md +195 -0
  177. latextify-0.1.0/plans/MANUSCRIPT_FIDELITY_PLAN.md +228 -0
  178. latextify-0.1.0/plans/OFFLINE_DISTRIBUTION_PLAN.md +182 -0
  179. latextify-0.1.0/plans/archive/GUI_JOURNALS_EXPORT_PLAN.md +100 -0
  180. latextify-0.1.0/plans/archive/LATEXTIFY_PLAN.md +438 -0
  181. latextify-0.1.0/plans/archive/OFFLINE_PORTABILITY_PLAN.md +219 -0
  182. latextify-0.1.0/plans/archive/SECURITY_RELIABILITY_AUDIT_PLAN.md +392 -0
  183. latextify-0.1.0/pyproject.toml +130 -0
  184. latextify-0.1.0/render.yaml +20 -0
  185. latextify-0.1.0/startup.bat +59 -0
  186. latextify-0.1.0/startup.sh +75 -0
  187. latextify-0.1.0/tests/fixtures/README.md +56 -0
  188. latextify-0.1.0/tests/fixtures/bracket_cited.docx +0 -0
  189. latextify-0.1.0/tests/fixtures/clean.docx +0 -0
  190. latextify-0.1.0/tests/fixtures/cropped_figure.docx +0 -0
  191. latextify-0.1.0/tests/fixtures/endnote_cited.docx +0 -0
  192. latextify-0.1.0/tests/fixtures/equations.docx +0 -0
  193. latextify-0.1.0/tests/fixtures/figures.docx +0 -0
  194. latextify-0.1.0/tests/fixtures/golden/achemso_metadata.tex +27 -0
  195. latextify-0.1.0/tests/fixtures/golden/achemso_preamble.tex +20 -0
  196. latextify-0.1.0/tests/fixtures/golden/elsarticle_metadata.tex +18 -0
  197. latextify-0.1.0/tests/fixtures/golden/elsarticle_preamble_authoryear.tex +11 -0
  198. latextify-0.1.0/tests/fixtures/golden/elsarticle_preamble_numeric.tex +11 -0
  199. latextify-0.1.0/tests/fixtures/golden/ieeetran_metadata.tex +16 -0
  200. latextify-0.1.0/tests/fixtures/golden/ieeetran_preamble.tex +9 -0
  201. latextify-0.1.0/tests/fixtures/golden/iopart_metadata.tex +21 -0
  202. latextify-0.1.0/tests/fixtures/golden/iopart_preamble.tex +15 -0
  203. latextify-0.1.0/tests/fixtures/golden/revtex4-2_metadata.tex +13 -0
  204. latextify-0.1.0/tests/fixtures/golden/revtex4-2_preamble.tex +9 -0
  205. latextify-0.1.0/tests/fixtures/golden/sn-jnl_metadata.tex +15 -0
  206. latextify-0.1.0/tests/fixtures/golden/sn-jnl_preamble.tex +13 -0
  207. latextify-0.1.0/tests/fixtures/golden/sn-jnl_preamble_authoryear.tex +13 -0
  208. latextify-0.1.0/tests/fixtures/golden/wiley_metadata.tex +15 -0
  209. latextify-0.1.0/tests/fixtures/golden/wiley_preamble_authoryear.tex +12 -0
  210. latextify-0.1.0/tests/fixtures/golden/wiley_preamble_numeric.tex +12 -0
  211. latextify-0.1.0/tests/fixtures/hand_cited.docx +0 -0
  212. latextify-0.1.0/tests/fixtures/make_bracket_cited.py +75 -0
  213. latextify-0.1.0/tests/fixtures/make_clean.py +38 -0
  214. latextify-0.1.0/tests/fixtures/make_cropped_figure.py +83 -0
  215. latextify-0.1.0/tests/fixtures/make_endnote_cited.py +261 -0
  216. latextify-0.1.0/tests/fixtures/make_equations.py +169 -0
  217. latextify-0.1.0/tests/fixtures/make_figures.py +85 -0
  218. latextify-0.1.0/tests/fixtures/make_hand_cited.py +93 -0
  219. latextify-0.1.0/tests/fixtures/make_metadata_titlepage.py +74 -0
  220. latextify-0.1.0/tests/fixtures/make_supplement.py +194 -0
  221. latextify-0.1.0/tests/fixtures/make_tables.py +93 -0
  222. latextify-0.1.0/tests/fixtures/make_unsupported.py +139 -0
  223. latextify-0.1.0/tests/fixtures/make_wordnative_cited.py +195 -0
  224. latextify-0.1.0/tests/fixtures/make_zotero_cited.py +272 -0
  225. latextify-0.1.0/tests/fixtures/metadata_titlepage.docx +0 -0
  226. latextify-0.1.0/tests/fixtures/paper.yaml +16 -0
  227. latextify-0.1.0/tests/fixtures/supplement.docx +0 -0
  228. latextify-0.1.0/tests/fixtures/tables.docx +0 -0
  229. latextify-0.1.0/tests/fixtures/unsupported.docx +0 -0
  230. latextify-0.1.0/tests/fixtures/wordnative_cited.docx +0 -0
  231. latextify-0.1.0/tests/fixtures/zotero_cited.docx +0 -0
  232. latextify-0.1.0/tests/test_archive_guard.py +209 -0
  233. latextify-0.1.0/tests/test_audit_equations.py +475 -0
  234. latextify-0.1.0/tests/test_citation_sentinels.py +161 -0
  235. latextify-0.1.0/tests/test_citations_bib.py +281 -0
  236. latextify-0.1.0/tests/test_citations_bibmatch.py +161 -0
  237. latextify-0.1.0/tests/test_citations_bibtex_in.py +186 -0
  238. latextify-0.1.0/tests/test_citations_bracket_refs.py +219 -0
  239. latextify-0.1.0/tests/test_citations_compile_stub.py +158 -0
  240. latextify-0.1.0/tests/test_citations_corrections.py +201 -0
  241. latextify-0.1.0/tests/test_citations_crossref.py +428 -0
  242. latextify-0.1.0/tests/test_citations_csl_json_in.py +109 -0
  243. latextify-0.1.0/tests/test_citations_degradation.py +148 -0
  244. latextify-0.1.0/tests/test_citations_endnote.py +143 -0
  245. latextify-0.1.0/tests/test_citations_endnote_compile.py +116 -0
  246. latextify-0.1.0/tests/test_citations_endnote_xml_in.py +136 -0
  247. latextify-0.1.0/tests/test_citations_extract.py +176 -0
  248. latextify-0.1.0/tests/test_citations_extract_endnote.py +99 -0
  249. latextify-0.1.0/tests/test_citations_extract_wordnative.py +83 -0
  250. latextify-0.1.0/tests/test_citations_fields.py +144 -0
  251. latextify-0.1.0/tests/test_citations_merge.py +200 -0
  252. latextify-0.1.0/tests/test_citations_nbib_in.py +105 -0
  253. latextify-0.1.0/tests/test_citations_parsers.py +112 -0
  254. latextify-0.1.0/tests/test_citations_plaintext.py +638 -0
  255. latextify-0.1.0/tests/test_citations_reconstruct.py +255 -0
  256. latextify-0.1.0/tests/test_citations_refs_import.py +70 -0
  257. latextify-0.1.0/tests/test_citations_stress.py +372 -0
  258. latextify-0.1.0/tests/test_citations_validate.py +293 -0
  259. latextify-0.1.0/tests/test_citations_wordnative.py +171 -0
  260. latextify-0.1.0/tests/test_cli.py +1370 -0
  261. latextify-0.1.0/tests/test_cli_review.py +150 -0
  262. latextify-0.1.0/tests/test_compat_strenum.py +53 -0
  263. latextify-0.1.0/tests/test_compile_logs.py +190 -0
  264. latextify-0.1.0/tests/test_compile_tectonic.py +232 -0
  265. latextify-0.1.0/tests/test_docx_clean.py +373 -0
  266. latextify-0.1.0/tests/test_emit.py +993 -0
  267. latextify-0.1.0/tests/test_emit_submission.py +183 -0
  268. latextify-0.1.0/tests/test_examples.py +107 -0
  269. latextify-0.1.0/tests/test_export_formats.py +388 -0
  270. latextify-0.1.0/tests/test_fidelity_captions_reflist.py +197 -0
  271. latextify-0.1.0/tests/test_figures.py +313 -0
  272. latextify-0.1.0/tests/test_figures_convert.py +528 -0
  273. latextify-0.1.0/tests/test_figures_crop.py +250 -0
  274. latextify-0.1.0/tests/test_figures_tiff.py +131 -0
  275. latextify-0.1.0/tests/test_figures_wide.py +110 -0
  276. latextify-0.1.0/tests/test_filters.py +505 -0
  277. latextify-0.1.0/tests/test_filters_headings.py +124 -0
  278. latextify-0.1.0/tests/test_frontmatter.py +297 -0
  279. latextify-0.1.0/tests/test_gui.py +1717 -0
  280. latextify-0.1.0/tests/test_gui_demo.py +234 -0
  281. latextify-0.1.0/tests/test_journal_pdf_layout.py +119 -0
  282. latextify-0.1.0/tests/test_kit_build.py +242 -0
  283. latextify-0.1.0/tests/test_manuscript_formats.py +278 -0
  284. latextify-0.1.0/tests/test_metadata_guess.py +835 -0
  285. latextify-0.1.0/tests/test_package.py +25 -0
  286. latextify-0.1.0/tests/test_pandoc.py +179 -0
  287. latextify-0.1.0/tests/test_pdf_combine.py +67 -0
  288. latextify-0.1.0/tests/test_preflight.py +187 -0
  289. latextify-0.1.0/tests/test_repo_integrity.py +107 -0
  290. latextify-0.1.0/tests/test_report.py +557 -0
  291. latextify-0.1.0/tests/test_supplement.py +450 -0
  292. latextify-0.1.0/tests/test_tables.py +342 -0
  293. latextify-0.1.0/tests/test_tectonic_integrity.py +210 -0
  294. latextify-0.1.0/tests/test_templates.py +819 -0
  295. latextify-0.1.0/tests/test_templates_item22.py +537 -0
  296. latextify-0.1.0/tests/test_templates_sn_jnl.py +308 -0
  297. latextify-0.1.0/tests/test_xml_hardening.py +117 -0
  298. latextify-0.1.0/uv.lock +1422 -0
@@ -0,0 +1,20 @@
1
+ # Build context hygiene for deploy/render/Dockerfile (context = repo root).
2
+ # Only the installable package + metadata should reach the image.
3
+ .git
4
+ .venv
5
+ .claude
6
+ __pycache__
7
+ **/__pycache__
8
+ *.pyc
9
+ build
10
+ dist
11
+ output
12
+ png
13
+ plans
14
+ tests
15
+ examples
16
+ deploy/hf-space/README.md
17
+ deploy/hf-space/DEPLOY.md
18
+ deploy/render/DEPLOY.md
19
+ *.log
20
+ uv.lock
@@ -0,0 +1,5 @@
1
+ # Keep launcher line endings correct on every platform, regardless of
2
+ # the contributor's core.autocrlf setting.
3
+ *.bat text eol=crlf
4
+ *.cmd text eol=crlf
5
+ *.sh text eol=lf
@@ -0,0 +1,13 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "uv"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ groups:
8
+ python-deps:
9
+ patterns: ["*"]
10
+ - package-ecosystem: "github-actions"
11
+ directory: "/"
12
+ schedule:
13
+ interval: "weekly"
@@ -0,0 +1,172 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ concurrency:
9
+ group: ${{ github.workflow }}-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ lint:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
20
+ - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
21
+ with:
22
+ python-version: "3.12"
23
+ - name: Install
24
+ run: uv sync --locked
25
+ - name: Ruff
26
+ run: uv run ruff check .
27
+
28
+ test:
29
+ name: test (${{ matrix.os }}, py${{ matrix.python }})
30
+ strategy:
31
+ fail-fast: false
32
+ matrix:
33
+ os: [ubuntu-latest, windows-latest, macos-latest]
34
+ python: ["3.10", "3.11", "3.13"]
35
+ runs-on: ${{ matrix.os }}
36
+ steps:
37
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
38
+ - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
39
+ with:
40
+ python-version: ${{ matrix.python }}
41
+ - name: Install
42
+ run: uv sync --locked
43
+ - name: Unit tests (no TeX engine, no network)
44
+ run: uv run pytest -q -m "not tectonic and not network"
45
+
46
+ integration:
47
+ # Full suite including real Tectonic compiles. One platform is enough:
48
+ # the compile path is exercised on Windows by local development.
49
+ runs-on: ubuntu-latest
50
+ steps:
51
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
52
+ - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
53
+ with:
54
+ python-version: "3.12"
55
+ - name: Cache Tectonic binary and package bundle
56
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
57
+ with:
58
+ path: |
59
+ ~/.cache/latextify
60
+ ~/.cache/Tectonic
61
+ key: tectonic-${{ runner.os }}-v1
62
+ - name: Install
63
+ run: uv sync --locked
64
+ - name: Pre-fetch Tectonic binary
65
+ # Test skip-guards only DETECT the binary at collection time (no
66
+ # download), so the integration job fetches it explicitly first. The
67
+ # download is a pinned, checksum-verified release-asset URL served by a
68
+ # CDN (not the rate-limited releases API), so no token is needed.
69
+ run: uv run python -c "from latextify.compile.tectonic import ensure_tectonic; print(ensure_tectonic())"
70
+ - name: Full test suite (real docx -> PDF compiles)
71
+ run: uv run pytest -q -m "not network"
72
+
73
+ offline-kit:
74
+ # Proves the whole offline story end to end: build a kit (network ON),
75
+ # then install it and compile a PDF with the network POISONED. This is the
76
+ # regression gate that keeps a stray runtime network call from silently
77
+ # breaking air-gapped use. Kept small (one Python, one journal) for speed;
78
+ # the release workflow builds the full multi-target kits.
79
+ runs-on: ubuntu-latest
80
+ steps:
81
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
82
+ - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
83
+ with:
84
+ python-version: "3.13"
85
+ - name: Cache Tectonic binary and package bundle
86
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
87
+ with:
88
+ path: |
89
+ ~/.cache/latextify
90
+ ~/.cache/Tectonic
91
+ key: tectonic-${{ runner.os }}-v1
92
+ - name: Install
93
+ run: uv sync --locked
94
+ - name: Pre-fetch Tectonic (pinned, checksum-verified release asset)
95
+ run: uv run python -c "from latextify.compile.tectonic import ensure_tectonic; print(ensure_tectonic())"
96
+ - name: Build a current-platform kit (network ON) and a fixture manuscript
97
+ run: |
98
+ uv run latextify make-kit --target current --python-versions 3.13 \
99
+ --journals revtex4-2 -o build
100
+ uv run python - <<'PY'
101
+ from docx import Document
102
+ d = Document()
103
+ d.add_heading("Offline CI Demo", 0)
104
+ d.add_paragraph("Ada Offline")
105
+ d.add_heading("Abstract", 1)
106
+ d.add_paragraph("A fixture manuscript compiled with no network access.")
107
+ d.add_heading("Introduction", 1)
108
+ d.add_paragraph("Body text exercising headings, an abstract, and a PDF compile.")
109
+ d.save("demo.docx")
110
+ PY
111
+ echo "PYBIN=$(uv python find 3.13)" >> "$GITHUB_ENV"
112
+ - name: Install kit + convert to PDF with the network POISONED
113
+ env:
114
+ # GitHub de-dupes env keys case-insensitively, so set ONE case only.
115
+ # Uppercase is honoured by Rust/reqwest (Tectonic) and Python alike.
116
+ HTTPS_PROXY: http://127.0.0.1:9
117
+ HTTP_PROXY: http://127.0.0.1:9
118
+ ALL_PROXY: http://127.0.0.1:9
119
+ run: |
120
+ KIT=build/latextify-offline-linux-x64
121
+ "$PYBIN" "$KIT/install.py"
122
+ "$KIT/latextify" convert demo.docx -j revtex4-2 -o demo-out --pdf
123
+ test -f demo-out/revtex4-2/main.pdf || { echo "no PDF produced offline"; exit 1; }
124
+ echo "offline PDF compiled: $(du -h demo-out/revtex4-2/main.pdf)"
125
+
126
+ offline-kit-py310-windows:
127
+ name: offline kit installs on Python 3.10 (Windows)
128
+ # Every other kit-install gate above targets the newest supported
129
+ # interpreter (3.13) on Linux; none proves the offline kit against the
130
+ # OLDEST supported Python (3.10, our documented floor -- see
131
+ # requires-python in pyproject.toml) on the platform most kit users
132
+ # actually run: Windows. A future dependency bump could silently break
133
+ # the 3.10 floor without this catching it. Kept fast: emit-only
134
+ # (--no-warm-tex, no --pdf) -- the PDF/Tectonic offline path is already
135
+ # covered by the `offline-kit` job above.
136
+ runs-on: windows-latest
137
+ steps:
138
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
139
+ - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
140
+ with:
141
+ python-version: "3.13"
142
+ - name: Install
143
+ run: uv sync --locked
144
+ - name: Set up the TARGET Python 3.10 (what install.py will run under)
145
+ id: py310
146
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
147
+ with:
148
+ python-version: "3.10"
149
+ - name: Build an emit-only kit covering Python 3.10 (network ON)
150
+ run: uv run latextify make-kit --target current --python-versions 3.10 --no-warm-tex --output build
151
+ - name: Install the kit under Python 3.10, network POISONED
152
+ env:
153
+ # Same reasoning as the Linux offline-kit job: uppercase only, one
154
+ # case, so GitHub's case-insensitive env-key de-dupe can't collide.
155
+ HTTPS_PROXY: http://127.0.0.1:9
156
+ HTTP_PROXY: http://127.0.0.1:9
157
+ ALL_PROXY: http://127.0.0.1:9
158
+ run: |
159
+ & "${{ steps.py310.outputs.python-path }}" "build/latextify-offline-windows-x64/install.py"
160
+ if ($LASTEXITCODE -ne 0) { throw "install.py failed under Python 3.10 (exit $LASTEXITCODE)" }
161
+ - name: Convert a fixture manuscript offline (emit-only, no --pdf)
162
+ env:
163
+ HTTPS_PROXY: http://127.0.0.1:9
164
+ HTTP_PROXY: http://127.0.0.1:9
165
+ ALL_PROXY: http://127.0.0.1:9
166
+ run: |
167
+ & "build/latextify-offline-windows-x64/LaTeXtify.bat" convert tests/fixtures/clean.docx -j revtex4-2 -o demo-out
168
+ if ($LASTEXITCODE -ne 0) { throw "convert failed under the Python 3.10 kit (exit $LASTEXITCODE)" }
169
+ if (-not (Test-Path "demo-out/revtex4-2/main.tex")) {
170
+ throw "Python 3.10 kit did not produce demo-out/revtex4-2/main.tex"
171
+ }
172
+ Write-Host "offline (Python 3.10, Windows): produced demo-out/revtex4-2/main.tex"
@@ -0,0 +1,23 @@
1
+ name: CodeQL
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+ schedule:
9
+ - cron: "31 7 * * 1"
10
+
11
+ jobs:
12
+ analyze:
13
+ runs-on: ubuntu-latest
14
+ permissions:
15
+ security-events: write
16
+ actions: read
17
+ contents: read
18
+ steps:
19
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
20
+ - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4
21
+ with:
22
+ languages: python
23
+ - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4
@@ -0,0 +1,45 @@
1
+ name: Dependency audit
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ schedule:
8
+ # Weekly (Mon 06:00 UTC): catch advisories newly disclosed against pinned,
9
+ # otherwise-unchanged dependencies between code changes.
10
+ - cron: "0 6 * * 1"
11
+
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ pip-audit:
21
+ name: pip-audit (locked runtime deps)
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
25
+ - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
26
+ with:
27
+ python-version: "3.12"
28
+ - name: Export locked runtime dependencies
29
+ # Audit exactly what ships to users: runtime deps only (no dev tools),
30
+ # resolved from uv.lock so the audit matches the pinned tree. --no-deps
31
+ # in the next step then audits precisely this closure without
32
+ # re-resolving. Hashes are omitted here because uv already verifies them
33
+ # on install; pip-audit's job in CI is CVE scanning, not integrity.
34
+ run: >-
35
+ uv export --frozen --no-dev --no-emit-project --no-hashes
36
+ --format requirements-txt -o requirements-audit.txt
37
+ - name: Audit for known vulnerabilities
38
+ # Exits non-zero if any dependency has a known advisory (PyPI + OSV).
39
+ #
40
+ # Failure policy: a PR that introduces or inherits a vulnerable
41
+ # dependency must upgrade it. An advisory may be waived only with a
42
+ # documented exception -- add `--ignore-vuln <ID>` below with a comment
43
+ # naming the package, advisory ID, why LaTeXtify is not exposed, and a
44
+ # review/expiry date.
45
+ run: uvx pip-audit --requirement requirements-audit.txt --no-deps
@@ -0,0 +1,72 @@
1
+ name: Publish to PyPI
2
+
3
+ # Publishes the sdist + wheel to PyPI. Triggers ONLY on a published GitHub
4
+ # Release -- never on a plain push, a pull request, or even a tag push (tag
5
+ # pushes drive release.yml's offline-kit build instead). This is the
6
+ # machinery only: it cannot fire a real publish until an OWNER completes the
7
+ # two one-time setup steps below.
8
+ #
9
+ # ---------------------------------------------------------------------------
10
+ # OWNER SETUP REQUIRED before the first real publish (both are one-time,
11
+ # human actions on pypi.org / GitHub -- this workflow cannot do either):
12
+ #
13
+ # 1. Register a PENDING trusted publisher at
14
+ # https://pypi.org/manage/account/publishing/ for a new project named
15
+ # "latextify":
16
+ # Owner: pquarterman17
17
+ # Repository name: LaTeXtify
18
+ # Workflow name: publish.yml
19
+ # Environment name: pypi
20
+ # (PyPI creates the "latextify" project automatically on the first
21
+ # successful publish from this trusted publisher -- no manual project
22
+ # creation needed.)
23
+ #
24
+ # 2. Publish a GitHub Release (e.g. tag v0.1.0). The `release: published`
25
+ # event below is what triggers the `publish` job -- nothing publishes
26
+ # before that happens.
27
+ # ---------------------------------------------------------------------------
28
+
29
+ on:
30
+ release:
31
+ types: [published]
32
+
33
+ concurrency:
34
+ group: publish-${{ github.ref }}
35
+ cancel-in-progress: false
36
+
37
+ permissions:
38
+ contents: read
39
+
40
+ jobs:
41
+ build:
42
+ runs-on: ubuntu-latest
43
+ steps:
44
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
45
+ - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
46
+ with:
47
+ python-version: "3.12"
48
+ - name: Build sdist + wheel
49
+ run: uv build
50
+ - name: Validate distribution metadata
51
+ run: uvx twine check dist/*
52
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
53
+ with:
54
+ name: dist
55
+ path: dist/
56
+ if-no-files-found: error
57
+
58
+ publish:
59
+ name: Publish to PyPI (trusted publishing)
60
+ needs: build
61
+ runs-on: ubuntu-latest
62
+ environment:
63
+ name: pypi
64
+ url: https://pypi.org/project/latextify/
65
+ permissions:
66
+ id-token: write # OIDC trusted publishing -- no API token stored in secrets
67
+ steps:
68
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
69
+ with:
70
+ name: dist
71
+ path: dist/
72
+ - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
@@ -0,0 +1,120 @@
1
+ name: Release kits
2
+
3
+ # On a version tag, build the three offline kits and attach them (plus a
4
+ # source zip and checksums) to a GitHub release, so an air-gapped machine can
5
+ # "download from the releases page" with no online build host at all.
6
+ # Complements the CI `offline-kit` job, which proves one kit installs and
7
+ # compiles a PDF offline on every push.
8
+
9
+ on:
10
+ push:
11
+ tags: ["v*"]
12
+
13
+ permissions:
14
+ contents: write # create the release and upload assets
15
+
16
+ concurrency:
17
+ group: release-${{ github.ref }}
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ build-kits:
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 60
24
+ steps:
25
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
26
+ - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7
27
+ with:
28
+ python-version: "3.13"
29
+ - name: Cache Tectonic binary and package bundle
30
+ uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6
31
+ with:
32
+ path: |
33
+ ~/.cache/latextify
34
+ ~/.cache/Tectonic
35
+ key: tectonic-${{ runner.os }}-v1
36
+ - name: Install
37
+ run: uv sync --locked
38
+ - name: Pre-fetch Tectonic (authenticated; anonymous API rate-limits on runners)
39
+ env:
40
+ GITHUB_TOKEN: ${{ github.token }}
41
+ run: uv run python -c "from latextify.compile.tectonic import ensure_tectonic; print(ensure_tectonic())"
42
+
43
+ - name: Build native Linux kit (warms the shared, portable TeX cache)
44
+ env:
45
+ GITHUB_TOKEN: ${{ github.token }}
46
+ run: uv run latextify make-kit --target linux-x64 -o build
47
+
48
+ # NOTE -- native-build preference: this workflow cross-builds the
49
+ # win-x64 (and macos-arm64) kits from a single Linux runner, on a
50
+ # GitHub-hosted-runner cost/simplicity tradeoff (one TeX warm-up pass
51
+ # instead of three, no matrix). A NATIVE Windows kit build --
52
+ # `latextify make-kit --target win-x64` run ON Windows, warming its own
53
+ # TeX cache directly -- is the PREFERRED way to produce the Windows
54
+ # kit: it exercises the real target-platform Tectonic binary end to
55
+ # end, rather than shipping `--no-warm-tex` and trusting that a
56
+ # Linux-warmed, source-only cache regenerates its engine format
57
+ # correctly the first time on Windows. (The CI `offline-kit-py310-windows`
58
+ # job partially covers this by installing+running a Windows kit, but it
59
+ # is also `--no-warm-tex`/emit-only for speed -- it does not warm-test
60
+ # this path either.) If a native Windows release runner ever becomes
61
+ # available, prefer switching the win-x64 build to it over this
62
+ # cross-build path.
63
+ - name: Build cross Windows/macOS kits, sharing the warmed cache
64
+ env:
65
+ GITHUB_TOKEN: ${{ github.token }}
66
+ run: |
67
+ # The warmed cache ships TeX *sources* only (host-independent); the
68
+ # target regenerates its engine format offline. So warm ONCE on Linux
69
+ # and reuse it for the cross targets rather than re-downloading the
70
+ # whole TeX bundle two more times.
71
+ for tgt in win-x64 macos-arm64; do
72
+ uv run latextify make-kit --target "$tgt" --no-warm-tex -o build
73
+ done
74
+ uv run python - <<'PY'
75
+ import json
76
+ import shutil
77
+ from pathlib import Path
78
+
79
+ build = Path("build")
80
+ src_kit = build / "latextify-offline-linux-x64"
81
+ src_cache = src_kit / "tex-bundle-cache"
82
+ warmed = json.loads((src_kit / "bundle-info.json").read_text())["warmed_journals"]
83
+
84
+ for name in ("latextify-offline-windows-x64", "latextify-offline-macos-arm64"):
85
+ kit = build / name
86
+ shutil.copytree(src_cache, kit / "tex-bundle-cache", dirs_exist_ok=True)
87
+ info_path = kit / "bundle-info.json"
88
+ info = json.loads(info_path.read_text())
89
+ info["warm_tex"] = True
90
+ info["warmed_journals"] = warmed
91
+ info_path.write_text(json.dumps(info, indent=2) + "\n")
92
+ print(f"shared warmed cache into {name} ({len(warmed)} journals)")
93
+ PY
94
+
95
+ - name: Package kits, source zip, and checksums
96
+ run: |
97
+ git archive --format=zip --prefix=latextify-src/ -o build/latextify-source.zip HEAD
98
+ cd build
99
+ for d in latextify-offline-*/; do
100
+ name="${d%/}"
101
+ zip -qr "$name.zip" "$name"
102
+ done
103
+ sha256sum *.zip > SHA256SUMS.txt
104
+ echo "--- release assets ---"
105
+ ls -lh *.zip SHA256SUMS.txt
106
+ cat SHA256SUMS.txt
107
+
108
+ - name: Create release and upload assets
109
+ env:
110
+ GH_TOKEN: ${{ github.token }}
111
+ run: |
112
+ cd build
113
+ gh release create "${GITHUB_REF_NAME}" \
114
+ --title "LaTeXtify ${GITHUB_REF_NAME}" \
115
+ --generate-notes \
116
+ latextify-offline-windows-x64.zip \
117
+ latextify-offline-linux-x64.zip \
118
+ latextify-offline-macos-arm64.zip \
119
+ latextify-source.zip \
120
+ SHA256SUMS.txt
@@ -0,0 +1,52 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ .venv/
5
+ *.egg-info/
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ .mypy_cache/
9
+
10
+ # Packaging output (uv build / python -m build)
11
+ dist/
12
+
13
+ # Secrets / local env (none tracked; ignore proactively)
14
+ .env
15
+ .env.*
16
+
17
+ # Editors / IDEs (may embed local absolute paths)
18
+ .vscode/
19
+ .idea/
20
+
21
+ # LaTeX build artifacts
22
+ *.aux
23
+ *.log
24
+ *.bbl
25
+ *.blg
26
+ *.out
27
+ *.fls
28
+ *.fdb_latexmk
29
+ *.synctex.gz
30
+
31
+ # Tool output trees (per-paper conversion output lives next to the paper, not here)
32
+ output/
33
+
34
+ # Launcher error log (startup.bat / startup.sh write this next to themselves)
35
+ latextify-startup.log
36
+
37
+ # OS
38
+ .DS_Store
39
+ Thumbs.db
40
+
41
+ # Claude Code files (never tracked in this public repo: local agent config,
42
+ # session worktrees, and any project-instruction files stay local-only)
43
+ .claude/
44
+ CLAUDE.md
45
+ CLAUDE.local.md
46
+
47
+ # Local-only working notes (hosting/ops docs, parked roadmaps -- not for the
48
+ # public repo; see the owner's notes/ directory)
49
+ notes/
50
+
51
+ # Offline kit build output (latextify make-kit)
52
+ /build/
@@ -0,0 +1,51 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest! This is a young project; issues and PRs are welcome.
4
+
5
+ ## Development setup
6
+
7
+ ```
8
+ git clone https://github.com/pquarterman17/LaTeXtify
9
+ cd LaTeXtify
10
+ uv sync # creates .venv, installs everything incl. dev deps
11
+ uv run pytest # full suite (downloads the Tectonic binary on first run)
12
+ uv run ruff check .
13
+ ```
14
+
15
+ Fast subset (no TeX engine, no network):
16
+
17
+ ```
18
+ uv run pytest -m "not tectonic and not network"
19
+ ```
20
+
21
+ The `tectonic`-marked tests skip unless the Tectonic binary is already on
22
+ PATH or in the cache — the skip guards deliberately never download at
23
+ collection time. Fetch it once and they activate:
24
+
25
+ ```
26
+ uv run python -c "from latextify.compile.tectonic import ensure_tectonic; print(ensure_tectonic())"
27
+ ```
28
+
29
+ ## Ground rules
30
+
31
+ - **Tests first-class:** every bug fix carries a minimal reproducing test;
32
+ every feature carries tests. The suite must be green (`pytest` + `ruff
33
+ check .`) before a PR.
34
+ - **Journals are data:** adding a journal means adding a folder under
35
+ `latextify/templates/journals/` (manifest.yaml + two Jinja templates +
36
+ golden-file tests) — never editing converter code. Copy `revtex4-2/` as
37
+ the worked example, and check vendored files' licenses before committing
38
+ them (see `sn-jnl/` and `iopart/` for the pattern; Wiley shows the
39
+ documented-skip pattern for non-redistributable classes).
40
+ - **IR discipline:** data crossing stage boundaries uses the frozen
41
+ dataclasses in `latextify/model/` — no ad-hoc dicts.
42
+ - **Commit style:** `type(scope): imperative description` (feat, fix,
43
+ refactor, docs, test, chore).
44
+ - `.docx` test fixtures are generated, never hand-edited: each has a
45
+ committed `tests/fixtures/make_<name>.py` script.
46
+
47
+ ## Architecture orientation
48
+
49
+ Read `plans/archive/LATEXTIFY_PLAN.md`'s Context section — it documents the
50
+ pipeline, the write-once/`generated/` output contract, and the design
51
+ decisions with dates.