calkit-python 0.31.4__tar.gz → 0.32.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 (192) hide show
  1. {calkit_python-0.31.4 → calkit_python-0.32.0}/PKG-INFO +4 -2
  2. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/__init__.py +1 -1
  3. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/check.py +29 -0
  4. calkit_python-0.32.0/calkit/cli/latex.py +206 -0
  5. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/main.py +148 -46
  6. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/new.py +3 -1
  7. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/notebooks.py +34 -22
  8. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/overleaf.py +86 -274
  9. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/update.py +110 -0
  10. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/core.py +9 -21
  11. calkit_python-0.32.0/calkit/environments.py +400 -0
  12. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/git.py +6 -0
  13. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/models/pipeline.py +146 -32
  14. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/notebooks.py +49 -3
  15. calkit_python-0.32.0/calkit/overleaf.py +518 -0
  16. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/pipeline.py +21 -28
  17. calkit_python-0.32.0/calkit/tests/cli/test_latex.py +118 -0
  18. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/cli/test_main.py +37 -0
  19. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/cli/test_new.py +2 -5
  20. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/cli/test_overleaf.py +54 -1
  21. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/models/test_pipeline.py +48 -2
  22. calkit_python-0.32.0/calkit/tests/test_environments.py +76 -0
  23. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/test_pipeline.py +6 -13
  24. {calkit_python-0.31.4 → calkit_python-0.32.0}/pyproject.toml +4 -1
  25. {calkit_python-0.31.4 → calkit_python-0.32.0}/uv.lock +531 -470
  26. calkit_python-0.31.4/calkit/environments.py +0 -207
  27. calkit_python-0.31.4/calkit/overleaf.py +0 -15
  28. {calkit_python-0.31.4 → calkit_python-0.32.0}/.github/FUNDING.yml +0 -0
  29. {calkit_python-0.31.4 → calkit_python-0.32.0}/.github/workflows/docs.yml +0 -0
  30. {calkit_python-0.31.4 → calkit_python-0.32.0}/.github/workflows/format.yml +0 -0
  31. {calkit_python-0.31.4 → calkit_python-0.32.0}/.github/workflows/publish-test.yml +0 -0
  32. {calkit_python-0.31.4 → calkit_python-0.32.0}/.github/workflows/publish.yml +0 -0
  33. {calkit_python-0.31.4 → calkit_python-0.32.0}/.github/workflows/test.yml +0 -0
  34. {calkit_python-0.31.4 → calkit_python-0.32.0}/.gitignore +0 -0
  35. {calkit_python-0.31.4 → calkit_python-0.32.0}/.pre-commit-config.yaml +0 -0
  36. {calkit_python-0.31.4 → calkit_python-0.32.0}/.python-version +0 -0
  37. {calkit_python-0.31.4 → calkit_python-0.32.0}/CITATION.cff +0 -0
  38. {calkit_python-0.31.4 → calkit_python-0.32.0}/CONTRIBUTING.md +0 -0
  39. {calkit_python-0.31.4 → calkit_python-0.32.0}/LICENSE +0 -0
  40. {calkit_python-0.31.4 → calkit_python-0.32.0}/Makefile +0 -0
  41. {calkit_python-0.31.4 → calkit_python-0.32.0}/README.md +0 -0
  42. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/__main__.py +0 -0
  43. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/calc.py +0 -0
  44. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/check.py +0 -0
  45. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/__init__.py +0 -0
  46. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/cloud.py +0 -0
  47. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/config.py +0 -0
  48. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/core.py +0 -0
  49. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/describe.py +0 -0
  50. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/import_.py +0 -0
  51. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/list.py +0 -0
  52. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/office.py +0 -0
  53. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cli/slurm.py +0 -0
  54. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/cloud.py +0 -0
  55. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/conda.py +0 -0
  56. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/config.py +0 -0
  57. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/datasets.py +0 -0
  58. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/docker.py +0 -0
  59. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/dvc.py +0 -0
  60. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/github.py +0 -0
  61. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/gui.py +0 -0
  62. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/invenio.py +0 -0
  63. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/jupyter.py +0 -0
  64. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/licenses.py +0 -0
  65. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/magics.py +0 -0
  66. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/matlab.py +0 -0
  67. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/models/__init__.py +0 -0
  68. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/models/core.py +0 -0
  69. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/models/io.py +0 -0
  70. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/models/iteration.py +0 -0
  71. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/office.py +0 -0
  72. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/ops.py +0 -0
  73. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/releases.py +0 -0
  74. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/server.py +0 -0
  75. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/templates/__init__.py +0 -0
  76. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/templates/core.py +0 -0
  77. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/templates/latex/__init__.py +0 -0
  78. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/templates/latex/article/paper.tex +0 -0
  79. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/templates/latex/core.py +0 -0
  80. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/templates/latex/jfm/jfm.bst +0 -0
  81. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/templates/latex/jfm/jfm.cls +0 -0
  82. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  83. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/templates/latex/jfm/paper.tex +0 -0
  84. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/templates/latex/jfm/upmath.sty +0 -0
  85. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/__init__.py +0 -0
  86. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/cli/__init__.py +0 -0
  87. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/cli/test_check.py +0 -0
  88. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/cli/test_config.py +0 -0
  89. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/cli/test_list.py +0 -0
  90. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/cli/test_notebooks.py +0 -0
  91. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/models/__init__.py +0 -0
  92. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/models/test_iteration.py +0 -0
  93. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/test_calc.py +0 -0
  94. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/test_check.py +0 -0
  95. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/test_conda.py +0 -0
  96. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/test_core.py +0 -0
  97. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/test_dvc.py +0 -0
  98. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/test_invenio.py +0 -0
  99. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/test_jupyter.py +0 -0
  100. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/test_magics.py +0 -0
  101. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/test_notebooks.py +0 -0
  102. {calkit_python-0.31.4 → calkit_python-0.32.0}/calkit/tests/test_templates.py +0 -0
  103. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/CNAME +0 -0
  104. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/apps.md +0 -0
  105. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/calculations.md +0 -0
  106. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/calkit-yaml.md +0 -0
  107. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/cli-reference.md +0 -0
  108. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/cloud-integration.md +0 -0
  109. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/datasets.md +0 -0
  110. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/dependencies.md +0 -0
  111. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/environments.md +0 -0
  112. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/examples.md +0 -0
  113. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/help.md +0 -0
  114. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/img/c-to-the-k-white.svg +0 -0
  115. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/img/calkit-no-bg.png +0 -0
  116. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/img/connect-zenodo.png +0 -0
  117. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/img/jupyterlab-params.png +0 -0
  118. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/img/vscode-nb-params.png +0 -0
  119. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/index.md +0 -0
  120. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/installation.md +0 -0
  121. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/local-server.md +0 -0
  122. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/notebooks.md +0 -0
  123. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/overleaf.md +0 -0
  124. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/pipeline/index.md +0 -0
  125. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/pipeline/manual-steps.md +0 -0
  126. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/pipeline/running-and-logging.md +0 -0
  127. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/pipeline/slurm.md +0 -0
  128. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/quickstart.md +0 -0
  129. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/references.md +0 -0
  130. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/releases.md +0 -0
  131. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  132. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/conda-envs.md +0 -0
  133. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/existing-project.md +0 -0
  134. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/first-project.md +0 -0
  135. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/github-actions.md +0 -0
  136. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/actions-repo-secrets.png +0 -0
  137. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  138. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  139. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  140. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  141. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  142. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  143. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  144. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  145. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  146. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  147. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  148. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  149. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  150. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  151. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/create-project.png +0 -0
  152. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  153. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/excel-chart.png +0 -0
  154. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/excel-data.png +0 -0
  155. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  156. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/needs-clone.png +0 -0
  157. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/new-stage.png +0 -0
  158. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  159. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  160. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/status-more-rows.png +0 -0
  161. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  162. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/untracked-data.png +0 -0
  163. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/updated-publication.png +0 -0
  164. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  165. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/office/workflow-page.png +0 -0
  166. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/openfoam/clone.png +0 -0
  167. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/openfoam/create-project.png +0 -0
  168. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  169. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  170. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  171. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/openfoam/new-token.png +0 -0
  172. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/openfoam/reclone.png +0 -0
  173. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  174. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/quick-actions.png +0 -0
  175. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/img/run-proc.png +0 -0
  176. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/index.md +0 -0
  177. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/latex-codespaces.md +0 -0
  178. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/matlab.md +0 -0
  179. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/notebook-pipeline.md +0 -0
  180. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/office.md +0 -0
  181. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/openfoam.md +0 -0
  182. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/tutorials/procedures.md +0 -0
  183. {calkit_python-0.31.4 → calkit_python-0.32.0}/docs/version-control.md +0 -0
  184. {calkit_python-0.31.4 → calkit_python-0.32.0}/mkdocs.yml +0 -0
  185. {calkit_python-0.31.4 → calkit_python-0.32.0}/scripts/install.ps1 +0 -0
  186. {calkit_python-0.31.4 → calkit_python-0.32.0}/scripts/install.sh +0 -0
  187. {calkit_python-0.31.4 → calkit_python-0.32.0}/test/nb-julia.ipynb +0 -0
  188. {calkit_python-0.31.4 → calkit_python-0.32.0}/test/nb-params.ipynb +0 -0
  189. {calkit_python-0.31.4 → calkit_python-0.32.0}/test/nb-subdir.ipynb +0 -0
  190. {calkit_python-0.31.4 → calkit_python-0.32.0}/test/pipeline.ipynb +0 -0
  191. {calkit_python-0.31.4 → calkit_python-0.32.0}/test/script.py +0 -0
  192. {calkit_python-0.31.4 → calkit_python-0.32.0}/test/test-log.log +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.31.4
3
+ Version: 0.32.0
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -15,9 +15,10 @@ Requires-Dist: arithmeval
15
15
  Requires-Dist: bibtexparser
16
16
  Requires-Dist: checksumdir
17
17
  Requires-Dist: docx2pdf
18
- Requires-Dist: dvc==3.62.0
18
+ Requires-Dist: dvc==3.64.0
19
19
  Requires-Dist: fastapi
20
20
  Requires-Dist: gitpython
21
+ Requires-Dist: json2latex>=0.0.2
21
22
  Requires-Dist: jupyterlab>=4.4.5
22
23
  Requires-Dist: keyring
23
24
  Requires-Dist: nbconvert
@@ -30,6 +31,7 @@ Requires-Dist: pyjwt
30
31
  Requires-Dist: python-dotenv>=1
31
32
  Requires-Dist: pywin32; platform_system == 'Windows'
32
33
  Requires-Dist: requests
34
+ Requires-Dist: sqlitedict>=2.1.0
33
35
  Requires-Dist: tqdm>=4.67.1
34
36
  Requires-Dist: typer==0.20.0
35
37
  Requires-Dist: uvicorn
@@ -1,4 +1,4 @@
1
- __version__ = "0.31.4"
1
+ __version__ = "0.32.0"
2
2
 
3
3
  from .core import * # noqa: F403, I001
4
4
  from . import git # noqa: F401
@@ -208,6 +208,35 @@ def check_environment(
208
208
  return get_env_lock_fpath(env=env, env_name=env_name, as_posix=False)
209
209
 
210
210
 
211
+ @check_app.command(
212
+ name="envs",
213
+ help="Check that all environments are up-to-date.",
214
+ )
215
+ @check_app.command(name="environments")
216
+ def check_environments(
217
+ verbose: Annotated[
218
+ bool, typer.Option("--verbose", help="Print verbose output.")
219
+ ] = False,
220
+ ) -> str | None:
221
+ ck_info = calkit.load_calkit_info(process_includes="environments")
222
+ envs = ck_info.get("environments", {})
223
+ if not envs:
224
+ typer.echo("No environments defined in calkit.yaml")
225
+ return
226
+ failures = []
227
+ for env_name in envs.keys():
228
+ typer.echo(f"Checking environment: '{env_name}'")
229
+ try:
230
+ check_environment(env_name=env_name, verbose=verbose)
231
+ except Exception as e:
232
+ warn(f"Error checking environment '{env_name}': {e}")
233
+ failures.append(env_name)
234
+ if failures:
235
+ raise_error(
236
+ f"Failed to check the following environments: {', '.join(failures)}"
237
+ )
238
+
239
+
211
240
  @check_app.command(name="docker-env")
212
241
  def check_docker_env(
213
242
  tag: Annotated[str, typer.Argument(help="Image tag.")],
@@ -0,0 +1,206 @@
1
+ """Commands for working with LaTeX."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import string
8
+ import subprocess
9
+ from copy import deepcopy
10
+
11
+ import typer
12
+ from typing_extensions import Annotated
13
+
14
+ import calkit
15
+ from calkit.cli import raise_error
16
+
17
+ latex_app = typer.Typer(no_args_is_help=True)
18
+
19
+
20
+ @latex_app.command(name="from-json")
21
+ def from_json(
22
+ input_fpaths: Annotated[
23
+ list[str], typer.Argument(help="Input JSON file path(s).")
24
+ ],
25
+ output_fpaths: Annotated[
26
+ list[str],
27
+ typer.Option("--output", "-o", help="Output LaTeX file path(s)."),
28
+ ],
29
+ command_name: Annotated[
30
+ str | None,
31
+ typer.Option("--command", help="Command name to use in LaTeX output."),
32
+ ] = None,
33
+ fmt_json: Annotated[
34
+ str | None,
35
+ typer.Option(
36
+ "--format-json",
37
+ help=(
38
+ "Additional JSON input to use for formatting. "
39
+ "Can be used to add extra keys with simple expressions, etc."
40
+ ),
41
+ ),
42
+ ] = None,
43
+ ):
44
+ """Convert a JSON file to LaTeX.
45
+
46
+ This is useful for referencing calculated values in LaTeX documents.
47
+ """
48
+ import arithmetic_eval
49
+ import json2latex
50
+
51
+ def tokens_from_format_string(fmt: str):
52
+ return [
53
+ field.strip()
54
+ for _, field, _, _ in string.Formatter().parse(fmt)
55
+ if field
56
+ ]
57
+
58
+ # Validate some stuff
59
+ if fmt_json is not None:
60
+ try:
61
+ fmt_dict = json.loads(fmt_json)
62
+ except json.JSONDecodeError:
63
+ raise_error("Format JSON is not valid JSON")
64
+ else:
65
+ fmt_dict = {}
66
+ data = {}
67
+ for input_fpath in input_fpaths:
68
+ if not os.path.isfile(input_fpath):
69
+ raise_error(f"Input file {input_fpath} does not exist")
70
+ if not input_fpath.endswith(".json"):
71
+ raise_error("Input file must be a JSON file")
72
+ with open(input_fpath) as f:
73
+ try:
74
+ data_i = json.load(f)
75
+ data.update(data_i)
76
+ except json.JSONDecodeError:
77
+ raise_error("Input JSON file is not valid JSON")
78
+ for output_fpath in output_fpaths:
79
+ if not output_fpath.endswith(".tex"):
80
+ raise_error("Output file must be a .tex file")
81
+ # Format the data
82
+ formatted = deepcopy(data)
83
+ for tex_var_name, fmt_string in fmt_dict.items():
84
+ fmt_string = str(fmt_string)
85
+ data_for_formatting = deepcopy(data)
86
+ # Do any relevant evals and add them to the data for formatting
87
+ tokens = tokens_from_format_string(fmt_string)
88
+ for t in tokens:
89
+ try:
90
+ data_for_formatting[t] = arithmetic_eval.evaluate(t, data)
91
+ except Exception:
92
+ raise_error(
93
+ f"Error evaluating expression '{t}' for formatting"
94
+ )
95
+ formatted[tex_var_name] = fmt_string.format(**data_for_formatting)
96
+ for out_path in output_fpaths:
97
+ # If no command is provided, use the output file name without extension
98
+ if command_name is None:
99
+ cmd_name = os.path.splitext(os.path.basename(out_path))[0]
100
+ else:
101
+ cmd_name = command_name
102
+ # Create output directory if it doesn't exist
103
+ outdir = os.path.dirname(out_path)
104
+ if outdir:
105
+ os.makedirs(outdir, exist_ok=True)
106
+ with open(out_path, "w") as f:
107
+ json2latex.dump(cmd_name, formatted, f)
108
+
109
+
110
+ @latex_app.command(name="build")
111
+ def build(
112
+ tex_file: Annotated[str, typer.Argument(help="The .tex file to compile.")],
113
+ environment: Annotated[
114
+ str | None,
115
+ typer.Option(
116
+ "--env",
117
+ "-e",
118
+ help=("Environment in which to run latexmk, if applicable."),
119
+ ),
120
+ ] = None,
121
+ no_check: Annotated[
122
+ bool,
123
+ typer.Option(
124
+ "--no-check",
125
+ help=(
126
+ "Don't check the environment is valid before running latexmk."
127
+ ),
128
+ ),
129
+ ] = False,
130
+ latexmk_rc_path: Annotated[
131
+ str | None,
132
+ typer.Option(
133
+ "--latexmk-rc",
134
+ "-r",
135
+ help="Path to a latexmkrc file to use for compilation.",
136
+ ),
137
+ ] = None,
138
+ no_synctex: Annotated[
139
+ bool,
140
+ typer.Option(
141
+ "--no-synctex",
142
+ help="Don't generate synctex file for source-to-pdf mapping.",
143
+ ),
144
+ ] = False,
145
+ force: Annotated[
146
+ bool,
147
+ typer.Option(
148
+ "--force",
149
+ "-f",
150
+ help=(
151
+ "Force latexmk to recompile all files, even if they are up to "
152
+ "date."
153
+ ),
154
+ ),
155
+ ] = False,
156
+ verbose: Annotated[
157
+ bool, typer.Option("--verbose", "-v", help="Print verbose output.")
158
+ ] = False,
159
+ ):
160
+ """Build a PDF of a LaTeX document with latexmk.
161
+
162
+ If a Calkit environment is not specified, latexmk will be run in the
163
+ system environment if available. If not available, a TeX Live Docker
164
+ container will be used.
165
+ """
166
+ # Now formulate the command
167
+ latexmk_cmd = ["latexmk", "-pdf", "-cd"]
168
+ if latexmk_rc_path is not None:
169
+ latexmk_cmd += ["-r", latexmk_rc_path]
170
+ if not no_synctex:
171
+ latexmk_cmd.append("-synctex=1")
172
+ if not verbose:
173
+ latexmk_cmd.append("-silent")
174
+ if force:
175
+ latexmk_cmd.append("-f")
176
+ latexmk_cmd += ["-interaction=nonstopmode", tex_file]
177
+ if environment is not None:
178
+ if no_check:
179
+ check_cmd = ["--no-check"]
180
+ else:
181
+ check_cmd = []
182
+ cmd = (
183
+ ["calkit", "xenv", "--name", environment]
184
+ + check_cmd
185
+ + ["--"]
186
+ + latexmk_cmd
187
+ )
188
+ if verbose:
189
+ typer.echo(f"Running command: {cmd}")
190
+ elif calkit.check_dep_exists("latexmk"):
191
+ cmd = latexmk_cmd
192
+ else:
193
+ cmd = [
194
+ "docker",
195
+ "run",
196
+ "--rm",
197
+ "-v",
198
+ f"{os.getcwd()}:/work",
199
+ "-w",
200
+ "/work",
201
+ "texlive/texlive:latest-full",
202
+ ] + latexmk_cmd
203
+ try:
204
+ subprocess.check_call(cmd)
205
+ except subprocess.CalledProcessError:
206
+ raise_error("latexmk failed")
@@ -16,7 +16,7 @@ import time
16
16
  import uuid
17
17
  from copy import deepcopy
18
18
  from datetime import datetime
19
- from pathlib import PurePosixPath
19
+ from pathlib import Path
20
20
 
21
21
  import dotenv
22
22
  import git
@@ -40,6 +40,7 @@ from calkit.cli.cloud import cloud_app
40
40
  from calkit.cli.config import config_app
41
41
  from calkit.cli.describe import describe_app
42
42
  from calkit.cli.import_ import import_app
43
+ from calkit.cli.latex import latex_app
43
44
  from calkit.cli.list import list_app
44
45
  from calkit.cli.new import new_app
45
46
  from calkit.cli.notebooks import notebooks_app
@@ -70,6 +71,7 @@ app.add_typer(import_app, name="import", help="Import objects.")
70
71
  app.add_typer(office_app, name="office", help="Work with Microsoft Office.")
71
72
  app.add_typer(update_app, name="update", help="Update objects.")
72
73
  app.add_typer(check_app, name="check", help="Check things.")
74
+ app.add_typer(latex_app, name="latex", help="Work with LaTeX.")
73
75
  app.add_typer(overleaf_app, name="overleaf", help="Interact with Overleaf.")
74
76
  app.add_typer(cloud_app, name="cloud", help="Interact with a Calkit Cloud.")
75
77
  app.add_typer(slurm_app, name="slurm", help="Work with SLURM.")
@@ -264,6 +266,11 @@ def get_status():
264
266
  calkit.pipeline.to_dvc(ck_info=ck_info, write=True)
265
267
  except Exception as e:
266
268
  warn(f"Failed to compile pipeline: {e.__class__.__name__}: {e}")
269
+ # Clean all notebooks in the pipeline
270
+ try:
271
+ calkit.notebooks.clean_all_in_pipeline(ck_info=ck_info)
272
+ except Exception as e:
273
+ warn(f"Failed to clean notebooks: {e.__class__.__name__}: {e}")
267
274
  print_sep("Project")
268
275
  # Print latest status
269
276
  status = calkit.get_latest_project_status()
@@ -287,7 +294,7 @@ def get_status():
287
294
  print_sep("Data (DVC)")
288
295
  run_cmd([sys.executable, "-m", "dvc", "data", "status"])
289
296
  typer.echo()
290
- print_sep("Pipeline (DVC)")
297
+ print_sep("Pipeline")
291
298
  run_cmd([sys.executable, "-m", "dvc", "status"])
292
299
 
293
300
 
@@ -769,7 +776,7 @@ def ignore(
769
776
  """Ignore a file, i.e., keep it out of version control."""
770
777
  repo = git.Repo()
771
778
  # Ensure path makes it into .gitignore as a POSIX path
772
- path = PurePosixPath(path).as_posix()
779
+ path = Path(path).as_posix()
773
780
  if repo.ignored(path):
774
781
  typer.echo(f"{path} is already ignored")
775
782
  return
@@ -1022,6 +1029,9 @@ def run(
1022
1029
  import dvc.ui
1023
1030
  from dvc.cli import main as dvc_cli_main
1024
1031
 
1032
+ import calkit.environments
1033
+ import calkit.pipeline
1034
+
1025
1035
  if (target_inputs or target_outputs) and targets:
1026
1036
  raise_error("Cannot specify both targets and inputs")
1027
1037
  os.environ["CALKIT_PIPELINE_RUNNING"] = "1"
@@ -1029,8 +1039,14 @@ def run(
1029
1039
  ck_info = calkit.load_calkit_info()
1030
1040
  # Set env vars
1031
1041
  calkit.set_env_vars(ck_info=ck_info)
1042
+ # Clean all notebooks in the pipeline
1043
+ try:
1044
+ calkit.notebooks.clean_all_in_pipeline(ck_info=ck_info)
1045
+ except Exception as e:
1046
+ raise_error(f"Failed to clean notebooks: {e.__class__.__name__}: {e}")
1032
1047
  if not quiet:
1033
1048
  typer.echo("Getting system information")
1049
+ # Get system information
1034
1050
  system_info = calkit.get_system_info()
1035
1051
  if save_log:
1036
1052
  # Save the system to .calkit/systems
@@ -1047,11 +1063,23 @@ def run(
1047
1063
  if not quiet:
1048
1064
  typer.echo("Checking system-level dependencies")
1049
1065
  try:
1050
- calkit.check_system_deps(system_info=system_info)
1066
+ calkit.check_system_deps(ck_info=ck_info, system_info=system_info)
1051
1067
  except Exception as e:
1052
1068
  os.environ.pop("CALKIT_PIPELINE_RUNNING", None)
1053
1069
  raise_error(str(e))
1054
- # Compile the pipeline
1070
+ # Check all environments in the pipeline (with caching)
1071
+ # If any failed, warn the user that we might have problems running
1072
+ typer.echo("Checking environments")
1073
+ env_check_results = calkit.environments.check_all_in_pipeline(
1074
+ ck_info=ck_info, targets=targets, force=force
1075
+ )
1076
+ for env_name, result in env_check_results.items():
1077
+ if verbose:
1078
+ typer.echo(f"{env_name}: {result}")
1079
+ failed = not result.get("success", False)
1080
+ if failed:
1081
+ warn(f"Failed to check environment '{env_name}'")
1082
+ # Compile the DVC pipeline
1055
1083
  dvc_stages = None
1056
1084
  if ck_info.get("pipeline", {}):
1057
1085
  if not quiet:
@@ -2013,49 +2041,123 @@ def run_jupyter(
2013
2041
  sys.exit(process.returncode)
2014
2042
 
2015
2043
 
2016
- @app.command(name="latexmk")
2017
- def run_latexmk(
2018
- tex_file: Annotated[str, typer.Argument(help="The .tex file to compile.")],
2019
- environment: Annotated[
2020
- str | None,
2044
+ @app.command(name="map-paths")
2045
+ def map_paths(
2046
+ file_to_file: Annotated[
2047
+ list[str],
2021
2048
  typer.Option(
2022
- "--env",
2023
- "-e",
2024
- help=("Environment in which to run latexmk, if applicable."),
2049
+ "--file-to-file",
2050
+ help=(
2051
+ "Map a file to another file, e.g., "
2052
+ "--file-to-file 'results.tex->paper/results.tex'."
2053
+ ),
2025
2054
  ),
2026
- ] = None,
2055
+ ] = [],
2056
+ file_to_dir: Annotated[
2057
+ list[str],
2058
+ typer.Option(
2059
+ "--file-to-dir",
2060
+ help=(
2061
+ "Map a file into a directory, e.g., "
2062
+ "--file-to-dir 'results.tex->paper/results'."
2063
+ ),
2064
+ ),
2065
+ ] = [],
2066
+ dir_to_dir_replace: Annotated[
2067
+ list[str],
2068
+ typer.Option(
2069
+ "--dir-to-dir-replace",
2070
+ help=(
2071
+ "Copy directory to another directory and replace it, "
2072
+ "e.g., --dir-to-dir-replace 'figures->paper/figures'."
2073
+ ),
2074
+ ),
2075
+ ] = [],
2076
+ dir_to_dir_merge: Annotated[
2077
+ list[str],
2078
+ typer.Option(
2079
+ "--dir-to-dir-merge",
2080
+ help=(
2081
+ "Merge directory into another directory. "
2082
+ "This is useful for merging contents of one directory into "
2083
+ "another, e.g., --dir-to-dir-merge 'figures->paper/figures'."
2084
+ ),
2085
+ ),
2086
+ ] = [],
2027
2087
  ):
2028
- """Compile a LaTeX document with latexmk.
2088
+ """Map paths in a project.
2029
2089
 
2030
- If a Calkit environment is not specified, latexmk will be run in the
2031
- system environment if available. If not available, a TeX Live Docker
2032
- container will be used.
2090
+ Currently this is done with copying. Outputs are ensured to be ignored by
2091
+ Git.
2033
2092
  """
2034
- latexmk_cmd = [
2035
- "latexmk",
2036
- "-pdf",
2037
- "-cd",
2038
- "-silent",
2039
- "-synctex=1",
2040
- "-interaction=nonstopmode",
2041
- tex_file,
2042
- ]
2043
- if environment is not None:
2044
- cmd = ["calkit", "xenv", "--name", environment] + latexmk_cmd
2045
- elif calkit.check_dep_exists("latexmk"):
2046
- cmd = latexmk_cmd
2047
- else:
2048
- cmd = [
2049
- "docker",
2050
- "run",
2051
- "--rm",
2052
- "-v",
2053
- f"{os.getcwd()}:/work",
2054
- "-w",
2055
- "/work",
2056
- "texlive/texlive:latest-full",
2057
- ] + latexmk_cmd
2058
- try:
2059
- subprocess.check_call(cmd)
2060
- except subprocess.CalledProcessError:
2061
- raise_error("latexmk failed")
2093
+ repo = git.Repo()
2094
+
2095
+ def ensure_path_is_ignored(path):
2096
+ if not repo.ignored(path):
2097
+ typer.echo(f"Adding {path} to .gitignore")
2098
+ with open(".gitignore", "a") as f:
2099
+ f.write(f"\n{path}\n")
2100
+
2101
+ def validate_and_split(mapping: str) -> tuple[str, str]:
2102
+ if "->" not in mapping:
2103
+ raise_error(
2104
+ f"Invalid path mapping format: '{mapping}'; "
2105
+ "Expected format: 'src->dest'"
2106
+ )
2107
+ parts = mapping.split("->")
2108
+ if len(parts) != 2:
2109
+ raise_error(
2110
+ f"Invalid path mapping format: '{mapping}'; "
2111
+ "Expected exactly one '->' separator"
2112
+ )
2113
+ return parts[0].strip(), parts[1].strip()
2114
+
2115
+ for copy_file in file_to_file:
2116
+ src_path, dest_path = validate_and_split(copy_file)
2117
+ if os.path.isdir(dest_path):
2118
+ raise_error(f"Destination path '{dest_path}' is a directory")
2119
+ parent_dir = os.path.dirname(dest_path)
2120
+ if parent_dir:
2121
+ os.makedirs(parent_dir, exist_ok=True)
2122
+ shutil.copy2(src_path, dest_path)
2123
+ ensure_path_is_ignored(dest_path)
2124
+ for copy_file in file_to_dir:
2125
+ src_path, dest_dir = validate_and_split(copy_file)
2126
+ if os.path.isfile(dest_dir):
2127
+ raise_error(f"Destination path '{dest_dir}' is a file")
2128
+ if not os.path.isdir(dest_dir):
2129
+ os.makedirs(dest_dir, exist_ok=True)
2130
+ dest_path = os.path.join(dest_dir, os.path.basename(src_path))
2131
+ shutil.copy2(src_path, dest_path)
2132
+ ensure_path_is_ignored(dest_path)
2133
+ for replace_dir_with_dir in dir_to_dir_replace:
2134
+ src_dir, dest_dir = validate_and_split(replace_dir_with_dir)
2135
+ if os.path.isfile(dest_dir):
2136
+ raise_error(f"Destination path '{dest_dir}' is a file")
2137
+ if os.path.isfile(src_dir):
2138
+ raise_error(f"Source path '{src_dir}' is a file")
2139
+ if os.path.isdir(dest_dir):
2140
+ shutil.rmtree(dest_dir)
2141
+ parent_dir = os.path.dirname(dest_dir)
2142
+ if parent_dir:
2143
+ os.makedirs(parent_dir, exist_ok=True)
2144
+ shutil.copytree(src_dir, dest_dir)
2145
+ ensure_path_is_ignored(dest_dir)
2146
+ for merge_dir_to_dir in dir_to_dir_merge:
2147
+ src_dir, dest_dir = validate_and_split(merge_dir_to_dir)
2148
+ if os.path.isfile(dest_dir):
2149
+ raise_error(f"Destination path '{dest_dir}' is a file")
2150
+ if os.path.isfile(src_dir):
2151
+ raise_error(f"Source path '{src_dir}' is a file")
2152
+ if not os.path.isdir(dest_dir):
2153
+ os.makedirs(dest_dir, exist_ok=True)
2154
+ for item in os.listdir(src_dir):
2155
+ if item.startswith("."):
2156
+ continue
2157
+ src_item = os.path.join(src_dir, item)
2158
+ dest_item = os.path.join(dest_dir, item)
2159
+ if os.path.isdir(src_item):
2160
+ shutil.copytree(src_item, dest_item, dirs_exist_ok=True)
2161
+ else:
2162
+ shutil.copy2(src_item, dest_item)
2163
+ ensure_path_is_ignored(dest_item)
@@ -2838,7 +2838,9 @@ def new_stage(
2838
2838
  if kind.value == "python-script":
2839
2839
  cmd += f"python {target}"
2840
2840
  elif kind.value == "latex":
2841
- cmd += f"latexmk -cd -interaction=nonstopmode -pdf {target}"
2841
+ cmd = f"calkit latex build {target}"
2842
+ if environment is not None:
2843
+ cmd += f" --environment {environment}"
2842
2844
  out_target = target.removesuffix(".tex") + ".pdf"
2843
2845
  if out_target not in (
2844
2846
  outs + outs_no_cache + outs_persist + outs_persist_no_cache
@@ -7,12 +7,13 @@ import json
7
7
  import os
8
8
  import subprocess
9
9
  import sys
10
- from pathlib import PurePosixPath
10
+ from pathlib import Path
11
11
  from typing import Any
12
12
 
13
13
  import typer
14
14
  from typing_extensions import Annotated
15
15
 
16
+ import calkit
16
17
  import calkit.notebooks
17
18
  from calkit.cli.core import raise_error
18
19
 
@@ -20,32 +21,43 @@ notebooks_app = typer.Typer(no_args_is_help=True)
20
21
 
21
22
 
22
23
  @notebooks_app.command("clean")
23
- def clean_notebook_outputs(path: str):
24
+ def clean_notebook_outputs(
25
+ path: str,
26
+ quiet: Annotated[
27
+ bool, typer.Option("--quiet", "-q", help="Do not print output.")
28
+ ] = False,
29
+ ):
24
30
  """Clean notebook and place a copy in the cleaned notebooks directory.
25
31
 
26
32
  This can be useful to use as a preprocessing DVC stage to use a clean
27
33
  notebook as a dependency for a stage that caches and executed notebook.
28
34
  """
29
35
  if os.path.isabs(path):
30
- raise ValueError("Path must be relative")
31
- fpath_out = calkit.notebooks.get_cleaned_notebook_path(path)
32
- folder = os.path.dirname(fpath_out)
33
- os.makedirs(folder, exist_ok=True)
34
- fpath_out = os.path.abspath(fpath_out)
35
- subprocess.call(
36
- [
37
- sys.executable,
38
- "-m",
39
- "jupyter",
40
- "nbconvert",
41
- path,
42
- "--clear-output",
43
- "--to",
44
- "notebook",
45
- "--output",
46
- fpath_out,
47
- ]
48
- )
36
+ raise_error("Path must be relative")
37
+ if not quiet:
38
+ typer.echo(f"Cleaning notebook: {path}")
39
+ try:
40
+ calkit.notebooks.clean_notebook_outputs(path)
41
+ except Exception as e:
42
+ raise_error(str(e))
43
+
44
+
45
+ @notebooks_app.command("clean-all")
46
+ def clean_all_in_pipeline(
47
+ quiet: Annotated[
48
+ bool, typer.Option("--quiet", "-q", help="Do not print output.")
49
+ ] = False,
50
+ ):
51
+ """Clean all notebooks in the pipeline."""
52
+ if not quiet:
53
+ typer.echo("Cleaning all notebooks in pipeline")
54
+ try:
55
+ cleaned = calkit.notebooks.clean_all_in_pipeline()
56
+ if not quiet:
57
+ for path in cleaned:
58
+ typer.echo(f"Cleaned: {path}")
59
+ except Exception as e:
60
+ raise_error(str(e))
49
61
 
50
62
 
51
63
  def _parse_params(params: list[str]) -> dict[str, Any]:
@@ -379,7 +391,7 @@ def execute_notebook(
379
391
  "--to",
380
392
  to_fmt,
381
393
  "--output-dir",
382
- PurePosixPath(folder).as_posix(),
394
+ Path(folder).as_posix(),
383
395
  "--output",
384
396
  fname_out,
385
397
  ]