calkit-python 0.30.8__tar.gz → 0.30.10__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 (186) hide show
  1. {calkit_python-0.30.8 → calkit_python-0.30.10}/.github/workflows/test.yml +2 -0
  2. {calkit_python-0.30.8 → calkit_python-0.30.10}/PKG-INFO +1 -1
  3. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/__init__.py +2 -1
  4. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/main.py +7 -1
  5. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/overleaf.py +151 -74
  6. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/update.py +31 -3
  7. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/core.py +20 -0
  8. calkit_python-0.30.10/calkit/overleaf.py +15 -0
  9. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/cli/test_main.py +25 -0
  10. calkit_python-0.30.10/calkit/tests/cli/test_overleaf.py +90 -0
  11. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/dependencies.md +11 -0
  12. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/overleaf.md +35 -2
  13. {calkit_python-0.30.8 → calkit_python-0.30.10}/pyproject.toml +1 -1
  14. {calkit_python-0.30.8 → calkit_python-0.30.10}/.github/FUNDING.yml +0 -0
  15. {calkit_python-0.30.8 → calkit_python-0.30.10}/.github/workflows/docs.yml +0 -0
  16. {calkit_python-0.30.8 → calkit_python-0.30.10}/.github/workflows/format.yml +0 -0
  17. {calkit_python-0.30.8 → calkit_python-0.30.10}/.github/workflows/publish-test.yml +0 -0
  18. {calkit_python-0.30.8 → calkit_python-0.30.10}/.github/workflows/publish.yml +0 -0
  19. {calkit_python-0.30.8 → calkit_python-0.30.10}/.gitignore +0 -0
  20. {calkit_python-0.30.8 → calkit_python-0.30.10}/.pre-commit-config.yaml +0 -0
  21. {calkit_python-0.30.8 → calkit_python-0.30.10}/.python-version +0 -0
  22. {calkit_python-0.30.8 → calkit_python-0.30.10}/CITATION.cff +0 -0
  23. {calkit_python-0.30.8 → calkit_python-0.30.10}/CONTRIBUTING.md +0 -0
  24. {calkit_python-0.30.8 → calkit_python-0.30.10}/LICENSE +0 -0
  25. {calkit_python-0.30.8 → calkit_python-0.30.10}/Makefile +0 -0
  26. {calkit_python-0.30.8 → calkit_python-0.30.10}/README.md +0 -0
  27. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/__main__.py +0 -0
  28. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/calc.py +0 -0
  29. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/check.py +0 -0
  30. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/__init__.py +0 -0
  31. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/check.py +0 -0
  32. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/cloud.py +0 -0
  33. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/config.py +0 -0
  34. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/core.py +0 -0
  35. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/describe.py +0 -0
  36. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/import_.py +0 -0
  37. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/list.py +0 -0
  38. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/new.py +0 -0
  39. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/notebooks.py +0 -0
  40. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/office.py +0 -0
  41. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cli/slurm.py +0 -0
  42. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/cloud.py +0 -0
  43. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/conda.py +0 -0
  44. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/config.py +0 -0
  45. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/datasets.py +0 -0
  46. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/docker.py +0 -0
  47. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/dvc.py +0 -0
  48. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/environments.py +0 -0
  49. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/git.py +0 -0
  50. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/github.py +0 -0
  51. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/gui.py +0 -0
  52. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/invenio.py +0 -0
  53. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/jupyter.py +0 -0
  54. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/licenses.py +0 -0
  55. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/magics.py +0 -0
  56. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/matlab.py +0 -0
  57. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/models/__init__.py +0 -0
  58. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/models/core.py +0 -0
  59. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/models/io.py +0 -0
  60. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/models/iteration.py +0 -0
  61. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/models/pipeline.py +0 -0
  62. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/notebooks.py +0 -0
  63. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/office.py +0 -0
  64. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/ops.py +0 -0
  65. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/pipeline.py +0 -0
  66. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/releases.py +0 -0
  67. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/server.py +0 -0
  68. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/templates/__init__.py +0 -0
  69. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/templates/core.py +0 -0
  70. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/templates/latex/__init__.py +0 -0
  71. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/templates/latex/article/paper.tex +0 -0
  72. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/templates/latex/core.py +0 -0
  73. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/templates/latex/jfm/jfm.bst +0 -0
  74. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/templates/latex/jfm/jfm.cls +0 -0
  75. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  76. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/templates/latex/jfm/paper.tex +0 -0
  77. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/templates/latex/jfm/upmath.sty +0 -0
  78. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/__init__.py +0 -0
  79. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/cli/__init__.py +0 -0
  80. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/cli/test_check.py +0 -0
  81. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/cli/test_config.py +0 -0
  82. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/cli/test_list.py +0 -0
  83. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/cli/test_new.py +0 -0
  84. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/cli/test_notebooks.py +0 -0
  85. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/models/__init__.py +0 -0
  86. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/models/test_iteration.py +0 -0
  87. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/models/test_pipeline.py +0 -0
  88. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/test_calc.py +0 -0
  89. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/test_check.py +0 -0
  90. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/test_conda.py +0 -0
  91. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/test_core.py +0 -0
  92. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/test_dvc.py +0 -0
  93. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/test_invenio.py +0 -0
  94. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/test_jupyter.py +0 -0
  95. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/test_magics.py +0 -0
  96. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/test_notebooks.py +0 -0
  97. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/test_pipeline.py +0 -0
  98. {calkit_python-0.30.8 → calkit_python-0.30.10}/calkit/tests/test_templates.py +0 -0
  99. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/CNAME +0 -0
  100. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/apps.md +0 -0
  101. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/calculations.md +0 -0
  102. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/calkit-yaml.md +0 -0
  103. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/cli-reference.md +0 -0
  104. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/cloud-integration.md +0 -0
  105. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/datasets.md +0 -0
  106. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/environments.md +0 -0
  107. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/examples.md +0 -0
  108. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/help.md +0 -0
  109. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/img/c-to-the-k-white.svg +0 -0
  110. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/img/calkit-no-bg.png +0 -0
  111. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/img/connect-zenodo.png +0 -0
  112. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/img/jupyterlab-params.png +0 -0
  113. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/img/vscode-nb-params.png +0 -0
  114. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/index.md +0 -0
  115. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/installation.md +0 -0
  116. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/local-server.md +0 -0
  117. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/notebooks.md +0 -0
  118. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/pipeline/index.md +0 -0
  119. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/pipeline/manual-steps.md +0 -0
  120. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/pipeline/running-and-logging.md +0 -0
  121. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/pipeline/slurm.md +0 -0
  122. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/quickstart.md +0 -0
  123. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/references.md +0 -0
  124. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/releases.md +0 -0
  125. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  126. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/conda-envs.md +0 -0
  127. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/existing-project.md +0 -0
  128. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/first-project.md +0 -0
  129. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/github-actions.md +0 -0
  130. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/actions-repo-secrets.png +0 -0
  131. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  132. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  133. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  134. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  135. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  136. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  137. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  138. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  139. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  140. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  141. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  142. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  143. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  144. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  145. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/create-project.png +0 -0
  146. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  147. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/excel-chart.png +0 -0
  148. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/excel-data.png +0 -0
  149. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  150. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/needs-clone.png +0 -0
  151. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/new-stage.png +0 -0
  152. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  153. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  154. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/status-more-rows.png +0 -0
  155. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  156. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/untracked-data.png +0 -0
  157. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/updated-publication.png +0 -0
  158. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  159. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/office/workflow-page.png +0 -0
  160. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/openfoam/clone.png +0 -0
  161. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/openfoam/create-project.png +0 -0
  162. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  163. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  164. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  165. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/openfoam/new-token.png +0 -0
  166. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/openfoam/reclone.png +0 -0
  167. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  168. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/quick-actions.png +0 -0
  169. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/img/run-proc.png +0 -0
  170. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/index.md +0 -0
  171. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/latex-codespaces.md +0 -0
  172. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/matlab.md +0 -0
  173. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/notebook-pipeline.md +0 -0
  174. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/office.md +0 -0
  175. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/openfoam.md +0 -0
  176. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/tutorials/procedures.md +0 -0
  177. {calkit_python-0.30.8 → calkit_python-0.30.10}/docs/version-control.md +0 -0
  178. {calkit_python-0.30.8 → calkit_python-0.30.10}/mkdocs.yml +0 -0
  179. {calkit_python-0.30.8 → calkit_python-0.30.10}/scripts/install.ps1 +0 -0
  180. {calkit_python-0.30.8 → calkit_python-0.30.10}/scripts/install.sh +0 -0
  181. {calkit_python-0.30.8 → calkit_python-0.30.10}/test/nb-julia.ipynb +0 -0
  182. {calkit_python-0.30.8 → calkit_python-0.30.10}/test/nb-params.ipynb +0 -0
  183. {calkit_python-0.30.8 → calkit_python-0.30.10}/test/nb-subdir.ipynb +0 -0
  184. {calkit_python-0.30.8 → calkit_python-0.30.10}/test/pipeline.ipynb +0 -0
  185. {calkit_python-0.30.8 → calkit_python-0.30.10}/test/test-log.log +0 -0
  186. {calkit_python-0.30.8 → calkit_python-0.30.10}/uv.lock +0 -0
@@ -15,6 +15,8 @@ jobs:
15
15
  git config --global user.name github-actions[bot]
16
16
  git config --global user.email 41898282+github-actions[bot]@users.noreply.github.com
17
17
  git config --global init.defaultBranch main
18
+ - name: Configure Git push behavior
19
+ run: git config --global push.autoSetupRemote true
18
20
  - name: Setup uv
19
21
  uses: astral-sh/setup-uv@v5
20
22
  - name: Setup Miniforge
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.30.8
3
+ Version: 0.30.10
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -1,4 +1,4 @@
1
- __version__ = "0.30.8"
1
+ __version__ = "0.30.10"
2
2
 
3
3
  from .core import * # noqa: F403, I001
4
4
  from . import git # noqa: F401
@@ -16,4 +16,5 @@ from . import github # noqa: F401
16
16
  from . import invenio # noqa: F401
17
17
  from . import releases # noqa: F401
18
18
  from . import licenses # noqa: F401
19
+ from . import overleaf # noqa: F401
19
20
  from .notebooks import declare_notebook # noqa: F401
@@ -990,6 +990,9 @@ def run(
990
990
  """Check dependencies and run the pipeline."""
991
991
  os.environ["CALKIT_PIPELINE_RUNNING"] = "1"
992
992
  dotenv.load_dotenv(dotenv_path=".env", verbose=verbose)
993
+ ck_info = calkit.load_calkit_info()
994
+ # Set env vars
995
+ calkit.set_env_vars(ck_info=ck_info)
993
996
  if not quiet:
994
997
  typer.echo("Getting system information")
995
998
  system_info = calkit.get_system_info()
@@ -1013,7 +1016,6 @@ def run(
1013
1016
  os.environ.pop("CALKIT_PIPELINE_RUNNING", None)
1014
1017
  raise_error(str(e))
1015
1018
  # Compile the pipeline
1016
- ck_info = calkit.load_calkit_info()
1017
1019
  if ck_info.get("pipeline", {}):
1018
1020
  if not quiet:
1019
1021
  typer.echo("Compiling DVC pipeline")
@@ -1251,6 +1253,7 @@ def run_in_env(
1251
1253
  ):
1252
1254
  dotenv.load_dotenv(dotenv_path=".env", verbose=verbose)
1253
1255
  ck_info = calkit.load_calkit_info(process_includes="environments")
1256
+ calkit.set_env_vars(ck_info=ck_info)
1254
1257
  envs = ck_info.get("environments", {})
1255
1258
  if not envs:
1256
1259
  raise_error("No environments defined in calkit.yaml")
@@ -1314,6 +1317,8 @@ def run_in_env(
1314
1317
  if "env-vars" in env:
1315
1318
  warn("The 'env-vars' key is deprecated; use 'env_vars' instead.")
1316
1319
  env_vars.update(env["env-vars"])
1320
+ # Add project-level env vars (non-secret)
1321
+ env_vars.update(ck_info.get("env_vars", {}))
1317
1322
  # Also add any project-level environmental variable dependencies
1318
1323
  project_env_vars = calkit.get_env_var_dep_names()
1319
1324
  if project_env_vars:
@@ -1668,6 +1673,7 @@ def run_procedure(
1668
1673
  return value
1669
1674
 
1670
1675
  ck_info = calkit.load_calkit_info(process_includes="procedures")
1676
+ calkit.set_env_vars(ck_info=ck_info)
1671
1677
  procs = ck_info.get("procedures", {})
1672
1678
  if name not in procs:
1673
1679
  raise_error(f"'{name}' is not defined as a procedure")
@@ -3,6 +3,7 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import filecmp
6
+ import json
6
7
  import os
7
8
  import shutil
8
9
  import subprocess
@@ -36,17 +37,6 @@ def import_publication(
36
37
  help="Directory at which to save in the project, e.g., 'paper'."
37
38
  ),
38
39
  ],
39
- sync_paths: Annotated[
40
- list[str],
41
- typer.Option(
42
- "--sync-path",
43
- "-s",
44
- help=(
45
- "Paths to sync from the Overleaf project, e.g., 'main.tex'. "
46
- "Note that multiple can be specified."
47
- ),
48
- ),
49
- ],
50
40
  title: Annotated[
51
41
  str,
52
42
  typer.Option(
@@ -55,6 +45,14 @@ def import_publication(
55
45
  help="Title of the publication.",
56
46
  ),
57
47
  ],
48
+ target_path: Annotated[
49
+ str | None,
50
+ typer.Option(
51
+ "--target",
52
+ "-T",
53
+ help="Target TeX file path inside Overleaf project.",
54
+ ),
55
+ ] = None,
58
56
  description: Annotated[
59
57
  str | None,
60
58
  typer.Option(
@@ -70,6 +68,17 @@ def import_publication(
70
68
  help="What of the publication this is, e.g., 'journal-article'.",
71
69
  ),
72
70
  ] = None,
71
+ sync_paths: Annotated[
72
+ list[str],
73
+ typer.Option(
74
+ "--sync-path",
75
+ "-s",
76
+ help=(
77
+ "Paths to sync from the Overleaf project, e.g., 'main.tex'. "
78
+ "Note that multiple can be specified."
79
+ ),
80
+ ),
81
+ ] = [],
73
82
  push_paths: Annotated[
74
83
  list[str],
75
84
  typer.Option(
@@ -82,18 +91,6 @@ def import_publication(
82
91
  ),
83
92
  ),
84
93
  ] = [],
85
- pdf_path: Annotated[
86
- str | None,
87
- typer.Option(
88
- "--pdf-path",
89
- "-o",
90
- help=(
91
- "PDF output file in the Overleaf project, e.g., 'main.pdf'. "
92
- "If not provided, it will be determined from the first sync "
93
- "path."
94
- ),
95
- ),
96
- ] = None,
97
94
  no_commit: Annotated[
98
95
  bool,
99
96
  typer.Option("--no-commit", help="Do not commit changes to repo."),
@@ -127,13 +124,19 @@ def import_publication(
127
124
  typer.echo("Storing Overleaf token in Calkit config")
128
125
  config.overleaf_token = overleaf_token
129
126
  config.write()
130
- if not src_url.startswith("https://www.overleaf.com/project/"):
127
+ if (
128
+ not src_url.startswith("https://www.overleaf.com/project/")
129
+ and calkit.config.get_env() != "test"
130
+ ):
131
131
  raise_error(
132
132
  "Invalid URL; must start with 'https://www.overleaf.com/project/'"
133
133
  )
134
134
  overleaf_project_id = src_url.split("/")[-1]
135
135
  if not overleaf_project_id:
136
136
  raise_error("Invalid Overleaf project ID")
137
+ # Check target path
138
+ if target_path is not None and not target_path.endswith(".tex"):
139
+ raise_error("Target path should have a .tex extension")
137
140
  # Make sure destination directory exists, and isn't a file
138
141
  if os.path.isfile(dest_dir):
139
142
  raise_error("Destination must be a directory, not a file")
@@ -141,21 +144,6 @@ def import_publication(
141
144
  ck_info = calkit.load_calkit_info(process_includes="environments")
142
145
  pubs = ck_info.get("publications", [])
143
146
  # TODO: Don't allow the same Overleaf project ID in multiple publications
144
- # Determine the PDF output path
145
- if pdf_path is None:
146
- # Use the first sync path as the PDF path
147
- pdf_path = sync_paths[0].removesuffix(".tex") + ".pdf"
148
- typer.echo(f"Using PDF path: {pdf_path}")
149
- tex_path = pdf_path.removesuffix(".pdf") + ".tex"
150
- pub_path = PurePosixPath(dest_dir, pdf_path).as_posix()
151
- pub_paths = [pub.get("path") for pub in pubs]
152
- if not overwrite and pub_path in pub_paths:
153
- raise_error(
154
- f"A publication already exists in this project at {pub_path}"
155
- )
156
- elif overwrite and pub_path in pub_paths:
157
- # Note: This publication will go to the end of the list
158
- pubs = [p for p in pubs if p.get("path") != pub_path]
159
147
  repo = git.Repo()
160
148
  # Clone the Overleaf project into .calkit/overleaf if it doesn't exist
161
149
  # otherwise pull
@@ -163,8 +151,8 @@ def import_publication(
163
151
  os.makedirs(overleaf_dir, exist_ok=True)
164
152
  git_ignore(overleaf_dir, no_commit=no_commit)
165
153
  overleaf_project_dir = os.path.join(overleaf_dir, overleaf_project_id)
166
- git_clone_url = (
167
- f"https://git:{overleaf_token}@git.overleaf.com/{overleaf_project_id}"
154
+ git_clone_url = calkit.overleaf.get_git_remote_url(
155
+ project_id=overleaf_project_id, token=overleaf_token
168
156
  )
169
157
  if os.path.isdir(overleaf_project_dir):
170
158
  warn("This Overleaf project has already been cloned; removing")
@@ -176,6 +164,36 @@ def import_publication(
176
164
  overleaf_project_dir,
177
165
  depth=1,
178
166
  )
167
+ # Detect target path if not specified
168
+ if target_path is None:
169
+ ol_contents = os.listdir(overleaf_project_dir)
170
+ for cand in ["main.tex", "report.tex", "paper.tex"]:
171
+ if cand in ol_contents:
172
+ target_path = cand
173
+ break
174
+ if target_path is None:
175
+ # Fall back to lone .tex file if there is one
176
+ tex_files = [p for p in ol_contents if p.endswith(".tex")]
177
+ if len(tex_files) == 1:
178
+ target_path = tex_files[0]
179
+ if target_path is None:
180
+ raise_error(
181
+ "Target TeX file path cannot be detected; "
182
+ "please specify with --target"
183
+ )
184
+ # Determine the PDF output path
185
+ pdf_path = target_path.removesuffix(".tex") + ".pdf" # type: ignore
186
+ typer.echo(f"Using PDF path: {pdf_path}")
187
+ tex_path = pdf_path.removesuffix(".pdf") + ".tex"
188
+ pub_path = PurePosixPath(dest_dir, pdf_path).as_posix()
189
+ pub_paths = [pub.get("path") for pub in pubs]
190
+ if not overwrite and pub_path in pub_paths:
191
+ raise_error(
192
+ f"A publication already exists in this project at {pub_path}"
193
+ )
194
+ elif overwrite and pub_path in pub_paths:
195
+ # Note: This publication will go to the end of the list
196
+ pubs = [p for p in pubs if p.get("path") != pub_path]
179
197
  # Check that we have a LaTeX environment
180
198
  typer.echo("Checking that this project has a LaTeX environment")
181
199
  envs = ck_info.get("environments", {})
@@ -197,6 +215,8 @@ def import_publication(
197
215
  description="TeXlive via Docker.",
198
216
  )
199
217
  ck_info["environments"] = envs
218
+ with open("calkit.yaml", "w") as f:
219
+ calkit.ryaml.dump(ck_info, f)
200
220
  # Check that we have a build stage
201
221
  # TODO: Use Calkit pipeline for this
202
222
  typer.echo("Checking for a build stage in the pipeline")
@@ -224,7 +244,6 @@ def import_publication(
224
244
  name=stage_name,
225
245
  environment=tex_env_name,
226
246
  target_path=PurePosixPath(dest_dir, tex_path).as_posix(),
227
- outputs=[pub_path],
228
247
  inputs=[
229
248
  os.path.join(dest_dir, p) for p in sync_paths + push_paths
230
249
  ],
@@ -234,6 +253,7 @@ def import_publication(
234
253
  repo.git.add("calkit.yaml")
235
254
  # Add to publications in calkit.yaml
236
255
  typer.echo("Adding publication to calkit.yaml")
256
+ ck_info = calkit.load_calkit_info()
237
257
  new_pub = dict(
238
258
  path=pub_path,
239
259
  title=title,
@@ -305,12 +325,12 @@ def sync(
305
325
  help="Enable verbose output.",
306
326
  ),
307
327
  ] = False,
308
- force: Annotated[
328
+ resolve: Annotated[
309
329
  bool,
310
330
  typer.Option(
311
- "--force",
312
- "-f",
313
- help="Force push to Overleaf even if there are conflicts.",
331
+ "--resolve",
332
+ "-r",
333
+ help="Mark merge conflicts as resolved before committing.",
314
334
  ),
315
335
  ] = False,
316
336
  ):
@@ -327,11 +347,33 @@ def sync(
327
347
  calkit_config = calkit.config.read()
328
348
  overleaf_token = calkit_config.overleaf_token
329
349
  if not overleaf_token:
350
+ # See if we can get it from the cloud
351
+ if calkit_config.token is not None:
352
+ try:
353
+ resp = calkit.cloud.get("/user/overleaf-token")
354
+ overleaf_token = resp["access_token"]
355
+ calkit_config.overleaf_token = overleaf_token
356
+ calkit_config.write()
357
+ except Exception:
358
+ raise_error(
359
+ "Overleaf token not set; "
360
+ "Please set it using 'calkit config set overleaf_token'"
361
+ )
362
+ repo = git.Repo()
363
+ conflict_fpath = os.path.join(".calkit", "overleaf", "CONFLICT.json")
364
+ in_am_session = "in the middle of an am session" in repo.git.status()
365
+ # Check if we're in the middle of resolving a merge conflict
366
+ if in_am_session and not resolve:
330
367
  raise_error(
331
- "Overleaf token not set; "
332
- "Please set it using 'calkit config set overleaf_token'"
368
+ "You are in the middle of resolving a merge conflict; "
369
+ "use 'calkit overleaf sync --resolve' after editing file(s)"
333
370
  )
334
- repo = git.Repo()
371
+ elif resolve:
372
+ if not os.path.isfile(conflict_fpath):
373
+ raise_error("No merge conflict to resolve")
374
+ # Figure out which wdir has the conflict in it
375
+ with open(conflict_fpath) as f:
376
+ resolving_info = json.load(f)
335
377
  for pub in pubs:
336
378
  overleaf_config = pub.get("overleaf", {})
337
379
  if not overleaf_config:
@@ -354,6 +396,16 @@ def sync(
354
396
  "No working directory defined for this publication; "
355
397
  "please set it in the publication's Overleaf config"
356
398
  )
399
+ if resolve and wdir == resolving_info["wdir"]:
400
+ repo.git.add(wdir)
401
+ if repo.git.diff(["--staged", wdir]):
402
+ repo.git.commit(
403
+ [wdir, "-m", f"Resolve Overleaf merge conflict in {wdir}"]
404
+ )
405
+ if in_am_session:
406
+ repo.git.am("--skip")
407
+ elif resolve:
408
+ continue
357
409
  # If there are any uncommitted changes in the publication working
358
410
  # directory, raise an error
359
411
  if repo.git.diff(wdir) or repo.index.diff("HEAD", wdir):
@@ -365,9 +417,8 @@ def sync(
365
417
  overleaf_project_dir = os.path.join(
366
418
  ".calkit", "overleaf", overleaf_project_id
367
419
  )
368
- overleaf_remote_url = (
369
- f"https://git:{overleaf_token}@git.overleaf.com/"
370
- f"{overleaf_project_id}"
420
+ overleaf_remote_url = calkit.overleaf.get_git_remote_url(
421
+ project_id=overleaf_project_id, token=str(overleaf_token)
371
422
  )
372
423
  if not os.path.isdir(overleaf_project_dir):
373
424
  overleaf_repo = git.Repo.clone_from(
@@ -389,7 +440,10 @@ def sync(
389
440
  "it matches one in your Overleaf account settings "
390
441
  "(https://overleaf.com/user/settings)"
391
442
  )
392
- last_sync_commit = pub["overleaf"].get("last_sync_commit")
443
+ if resolve:
444
+ last_sync_commit = resolving_info["last_overleaf_commit"]
445
+ else:
446
+ last_sync_commit = pub["overleaf"].get("last_sync_commit")
393
447
  if last_sync_commit:
394
448
  commits_since = list(
395
449
  overleaf_repo.iter_commits(rev=f"{last_sync_commit}..HEAD")
@@ -398,6 +452,8 @@ def sync(
398
452
  f"There have been {len(commits_since)} changes on "
399
453
  "Overleaf since last sync"
400
454
  )
455
+ else:
456
+ commits_since = []
401
457
  # Determine which paths to sync and push
402
458
  # TODO: Support glob patterns
403
459
  git_sync_paths = pub["overleaf"].get("sync_paths", [])
@@ -405,6 +461,14 @@ def sync(
405
461
  dvc_sync_paths = pub["overleaf"].get("dvc_sync_paths", [])
406
462
  sync_paths = git_sync_paths + dvc_sync_paths
407
463
  push_paths = pub["overleaf"].get("push_paths", [])
464
+ implicit_sync_paths = os.listdir(overleaf_repo.working_dir)
465
+ for p in implicit_sync_paths:
466
+ if p.startswith("."):
467
+ continue
468
+ if p not in sync_paths:
469
+ sync_paths.append(p)
470
+ if p not in git_sync_paths and p not in dvc_sync_paths:
471
+ git_sync_paths.append(p)
408
472
  git_sync_paths_in_project = [
409
473
  os.path.join(wdir, p) for p in git_sync_paths
410
474
  ]
@@ -439,7 +503,6 @@ def sync(
439
503
  "git",
440
504
  "am",
441
505
  "--3way",
442
- "--reject",
443
506
  "--directory",
444
507
  wdir,
445
508
  "-",
@@ -447,25 +510,38 @@ def sync(
447
510
  input=patch,
448
511
  text=True,
449
512
  encoding="utf-8",
513
+ capture_output=True,
450
514
  )
451
- if process.returncode != 0:
452
- if force:
453
- # Skip any patches we might be in the middle of
454
- try:
455
- repo.git.am("--skip")
456
- except Exception:
457
- pass
458
- typer.echo(
459
- "Failed to apply Overleaf patch to project repo. "
460
- "Proceeding to push project changes to Overleaf "
461
- "anyway."
462
- )
463
- else:
464
- raise_error(
465
- "Failed to apply Overleaf patch to project repo. "
466
- "Check the .rej files and manually apply changes, "
467
- "delete them, then rerun with --force."
515
+ # Handle merge conflicts
516
+ if (
517
+ process.returncode != 0
518
+ and "merge conflict" in process.stdout.lower()
519
+ ):
520
+ msg = ""
521
+ for line in process.stdout.split("\n"):
522
+ if "merge conflict" in line.lower():
523
+ msg += line + "\n"
524
+ # Save a file to track this merge conflict
525
+ c = overleaf_repo.head.commit.hexsha
526
+ with open(conflict_fpath, "w") as f:
527
+ json.dump(
528
+ {
529
+ "wdir": wdir,
530
+ "last_overleaf_commit": c,
531
+ "pub_path": pub["path"],
532
+ },
533
+ f,
468
534
  )
535
+ raise_error(
536
+ f"{msg}Edit the file(s) and then call:\n\n"
537
+ " calkit overleaf sync --resolve"
538
+ )
539
+ elif process.returncode != 0:
540
+ raise_error(f"Could not apply:\n{process.stdout}")
541
+ elif resolve:
542
+ # We have no patch since the last sync, but we need to update
543
+ # our latest sync commit
544
+ typer.echo("Merge conflict resolved")
469
545
  else:
470
546
  typer.echo("No changes to apply")
471
547
  else:
@@ -535,6 +611,8 @@ def sync(
535
611
  with open("calkit.yaml", "w") as f:
536
612
  calkit.ryaml.dump(ck_info, f)
537
613
  repo.git.add("calkit.yaml")
614
+ if resolve and os.path.isfile(conflict_fpath):
615
+ os.remove(conflict_fpath)
538
616
  # Stage the changes in the project repo
539
617
  # Add any DVC sync paths to DVC
540
618
  for dvc_sync_path in dvc_sync_paths_in_project:
@@ -660,9 +738,8 @@ def get_status(
660
738
  overleaf_project_dir = os.path.join(
661
739
  ".calkit", "overleaf", overleaf_project_id
662
740
  )
663
- overleaf_remote_url = (
664
- f"https://git:{overleaf_token}@git.overleaf.com/"
665
- f"{overleaf_project_id}"
741
+ overleaf_remote_url = calkit.overleaf.get_git_remote_url(
742
+ project_id=overleaf_project_id, token=str(overleaf_token)
666
743
  )
667
744
  if not os.path.isdir(overleaf_project_dir):
668
745
  overleaf_repo = git.Repo.clone_from(
@@ -64,7 +64,7 @@ def update_license(
64
64
  ] = False,
65
65
  ):
66
66
  """Update license with a reasonable default
67
- (MIT for code, CC-BY-4.0 for other files)
67
+ (MIT for code, CC-BY-4.0 for other files).
68
68
  """
69
69
  with open("LICENSE", "w") as f:
70
70
  f.write(
@@ -202,14 +202,38 @@ def update_release(
202
202
  # TODO: Enable reuploading artifact releases
203
203
  if path != "." or release_type != "project":
204
204
  raise_error("Can only handle updating project releases")
205
- release_files_dir = f".calkit/releases/{name}/files"
205
+ release_dir = f".calkit/releases/{name}"
206
+ release_files_dir = release_dir + "/files"
207
+ os.makedirs(release_files_dir, exist_ok=True)
208
+ # Save a metadata file with each DVC file's MD5 checksum
209
+ dvc_md5s = calkit.releases.make_dvc_md5s(
210
+ zipfile="archive.zip" if path == "." else None,
211
+ paths=None if path == "." else [path],
212
+ )
213
+ dvc_md5s_path = release_dir + "/dvc-md5s.yaml"
214
+ typer.echo(f"Saving DVC MD5 info to {dvc_md5s_path}")
215
+ with open(dvc_md5s_path, "w") as f:
216
+ calkit.ryaml.dump(dvc_md5s, f)
217
+ # Create a README for the Invenio release
218
+ typer.echo("Creating README.md for release")
219
+ title = ck_info.get("title")
220
+ if title is None:
221
+ raise_error("Project has no title")
222
+ readme_txt = f"# {title}\n"
223
+ git_rev = repo.git.rev_parse(["--short", "HEAD"])
224
+ readme_txt += (
225
+ f"\nThis is a {release_type} release ({name}) generated with "
226
+ f"Calkit from Git rev {git_rev}.\n"
227
+ )
228
+ readme_path = release_files_dir + "/README.md"
229
+ with open(readme_path, "w") as f:
230
+ f.write(readme_txt)
206
231
  zip_path = release_files_dir + "/archive.zip"
207
232
  all_paths = calkit.releases.ls_files()
208
233
  typer.echo(f"Adding files to {zip_path}")
209
234
  with zipfile.ZipFile(zip_path, "w") as zipf:
210
235
  for fpath in all_paths:
211
236
  zipf.write(fpath)
212
- files = os.listdir(release_files_dir)
213
237
  try:
214
238
  files_in_record = [
215
239
  entry["key"]
@@ -224,6 +248,10 @@ def update_release(
224
248
  "Failed to get existing files in record: "
225
249
  f"{e.__class__.__name__}: {e}"
226
250
  )
251
+ # Check size of files dir
252
+ size = calkit.get_size(release_files_dir)
253
+ typer.echo(f"Release size: {(size / 1e6):.1f} MB")
254
+ files = os.listdir(release_files_dir)
227
255
  for filename in files:
228
256
  if filename in files_in_record:
229
257
  typer.echo(f"Deleting existing file {filename} from draft")
@@ -602,3 +602,23 @@ def get_md5(path: str, exclude_files: list[str] | None = None) -> str:
602
602
  with open(path) as f:
603
603
  content = f.read()
604
604
  return hashlib.md5(content.encode()).hexdigest()
605
+
606
+
607
+ def set_env_vars(ck_info: dict, cli: bool = True) -> None:
608
+ """Set environmental variables according to the values read from
609
+ ``calkit.yaml``.
610
+ """
611
+ env_vars = ck_info.get("env_vars", {})
612
+ if not isinstance(env_vars, dict):
613
+ msg = (
614
+ "Environmental variables in Calkit project info must be a "
615
+ "map/dictionary"
616
+ )
617
+ if cli:
618
+ from calkit.cli import raise_error
619
+
620
+ raise_error(msg)
621
+ else:
622
+ raise ValueError(msg)
623
+ for k, v in env_vars.items():
624
+ os.environ[str(k)] = str(v)
@@ -0,0 +1,15 @@
1
+ """Functionality for working with Overleaf."""
2
+
3
+ import os
4
+
5
+ import calkit
6
+
7
+
8
+ def get_git_remote_url(project_id: str, token: str) -> str:
9
+ """Form the Git remote URL for an Overleaf project.
10
+
11
+ If running against a test environment, this will use a local directory.
12
+ """
13
+ if calkit.config.get_env() == "test":
14
+ return os.path.join("/tmp", "overleaf", project_id)
15
+ return f"https://git:{token}@git.overleaf.com/{project_id}"
@@ -50,6 +50,18 @@ def test_run_in_env(tmp_dir):
50
50
  stdin=stdin,
51
51
  check=True,
52
52
  )
53
+ # Check that we can pass project env vars into the container
54
+ ck_info = calkit.load_calkit_info()
55
+ ck_info["env_vars"] = {"MY_COOL_ENV_VAR": "my cool value"}
56
+ with open("calkit.yaml", "w") as f:
57
+ calkit.ryaml.dump(ck_info, f)
58
+ p = subprocess.run(
59
+ ["calkit", "xenv", "echo", "$MY_COOL_ENV_VAR"],
60
+ text=True,
61
+ capture_output=True,
62
+ check=True,
63
+ )
64
+ assert "my cool value" in p.stdout
53
65
  # Now let's create a 2nd Docker env and make sure we need to call it by
54
66
  # name when trying to run
55
67
  subprocess.check_call(
@@ -452,6 +464,19 @@ def test_run(tmp_dir):
452
464
  subprocess.check_call(
453
465
  ["calkit", "save", "-am", "Run pipeline", "--no-push"]
454
466
  )
467
+ # Test that we can set env vars at the project level
468
+ ck_info = calkit.load_calkit_info()
469
+ ck_info["env_vars"] = {"MY_ENV_VAR": "some-value"}
470
+ with open("calkit.yaml", "w") as f:
471
+ calkit.ryaml.dump(ck_info, f)
472
+ with open("script.py", "w") as f:
473
+ f.write("import os\nprint(os.environ['MY_ENV_VAR'])")
474
+ out = subprocess.check_output(["calkit", "run"], text=True)
475
+ print(out)
476
+ assert "some-value" in out
477
+ subprocess.check_call(
478
+ ["calkit", "save", "-am", "Run pipeline", "--no-push"]
479
+ )
455
480
  # Make sure we can run on a detached head
456
481
  repo = git.Repo()
457
482
  repo.git.checkout("HEAD^")