calkit-python 0.41.8__tar.gz → 0.41.9__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 (337) hide show
  1. {calkit_python-0.41.8 → calkit_python-0.41.9}/PKG-INFO +1 -1
  2. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/new.py +33 -39
  3. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/releases.py +103 -0
  4. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/test_new.py +15 -0
  5. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_releases.py +85 -0
  6. {calkit_python-0.41.8 → calkit_python-0.41.9}/.claude-plugin/marketplace.json +0 -0
  7. {calkit_python-0.41.8 → calkit_python-0.41.9}/.gitattributes +0 -0
  8. {calkit_python-0.41.8 → calkit_python-0.41.9}/.gitignore +0 -0
  9. {calkit_python-0.41.8 → calkit_python-0.41.9}/.pre-commit-config.yaml +0 -0
  10. {calkit_python-0.41.8 → calkit_python-0.41.9}/.prettierignore +0 -0
  11. {calkit_python-0.41.8 → calkit_python-0.41.9}/.python-version +0 -0
  12. {calkit_python-0.41.8 → calkit_python-0.41.9}/.vscode/launch.json +0 -0
  13. {calkit_python-0.41.8 → calkit_python-0.41.9}/.vscode/tasks.json +0 -0
  14. {calkit_python-0.41.8 → calkit_python-0.41.9}/.yarnrc.yml +0 -0
  15. {calkit_python-0.41.8 → calkit_python-0.41.9}/AGENTS.md +0 -0
  16. {calkit_python-0.41.8 → calkit_python-0.41.9}/CITATION.cff +0 -0
  17. {calkit_python-0.41.8 → calkit_python-0.41.9}/CODE_OF_CONDUCT.md +0 -0
  18. {calkit_python-0.41.8 → calkit_python-0.41.9}/CONTRIBUTING.md +0 -0
  19. {calkit_python-0.41.8 → calkit_python-0.41.9}/LICENSE +0 -0
  20. {calkit_python-0.41.8 → calkit_python-0.41.9}/Makefile +0 -0
  21. {calkit_python-0.41.8 → calkit_python-0.41.9}/README.md +0 -0
  22. {calkit_python-0.41.8 → calkit_python-0.41.9}/agent-plugin/.claude-plugin/plugin.json +0 -0
  23. {calkit_python-0.41.8 → calkit_python-0.41.9}/agent-plugin/skills/add-pipeline-stage/SKILL.md +0 -0
  24. {calkit_python-0.41.8 → calkit_python-0.41.9}/agent-plugin/skills/conventions/SKILL.md +0 -0
  25. {calkit_python-0.41.8 → calkit_python-0.41.9}/agent-plugin/skills/create-pipeline/SKILL.md +0 -0
  26. {calkit_python-0.41.8 → calkit_python-0.41.9}/babel.config.js +0 -0
  27. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/__init__.py +0 -0
  28. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/__main__.py +0 -0
  29. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/calc.py +0 -0
  30. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/check.py +0 -0
  31. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/__init__.py +0 -0
  32. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/check.py +0 -0
  33. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/cloud.py +0 -0
  34. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/config.py +0 -0
  35. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/core.py +0 -0
  36. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/describe.py +0 -0
  37. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/dev.py +0 -0
  38. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/import_.py +0 -0
  39. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/latex.py +0 -0
  40. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/list.py +0 -0
  41. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/main/__init__.py +0 -0
  42. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/main/core.py +0 -0
  43. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/main/xr.py +0 -0
  44. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/notebooks.py +0 -0
  45. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/office.py +0 -0
  46. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/overleaf.py +0 -0
  47. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/scheduler.py +0 -0
  48. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cli/update.py +0 -0
  49. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/cloud.py +0 -0
  50. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/conda.py +0 -0
  51. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/config.py +0 -0
  52. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/core.py +0 -0
  53. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/datasets.py +0 -0
  54. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/dependencies.py +0 -0
  55. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/detect.py +0 -0
  56. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/docker.py +0 -0
  57. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/dvc/__init__.py +0 -0
  58. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/dvc/core.py +0 -0
  59. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/dvc/zip.py +0 -0
  60. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/environments.py +0 -0
  61. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/fs.py +0 -0
  62. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/git.py +0 -0
  63. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/github.py +0 -0
  64. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/gui.py +0 -0
  65. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/install.py +0 -0
  66. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/invenio.py +0 -0
  67. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/julia.py +0 -0
  68. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/jupyter.py +0 -0
  69. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/jupyterlab/__init__.py +0 -0
  70. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/jupyterlab/routes.py +0 -0
  71. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/labextension/package.json +0 -0
  72. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/labextension/schemas/calkit/package.json.orig +0 -0
  73. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/labextension/schemas/calkit/plugin.json +0 -0
  74. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/labextension/static/502.9a2c5772a15466e923ef.js +0 -0
  75. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/labextension/static/695.2c41003a452d43d2b358.js +0 -0
  76. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/labextension/static/867.a42a046aa5108f54f8fb.js +0 -0
  77. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/labextension/static/909.e3f9cc3408834a7fdcc3.js +0 -0
  78. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js +0 -0
  79. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
  80. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/labextension/static/b2f1c3efe70cb539d121.png +0 -0
  81. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/labextension/static/remoteEntry.65469af996e7a96aa983.js +0 -0
  82. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/labextension/static/style.js +0 -0
  83. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/labextension/static/third-party-licenses.json +0 -0
  84. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/licenses.py +0 -0
  85. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/magics.py +0 -0
  86. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/matlab.py +0 -0
  87. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/models/__init__.py +0 -0
  88. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/models/core.py +0 -0
  89. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/models/io.py +0 -0
  90. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/models/iteration.py +0 -0
  91. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/models/pipeline.py +0 -0
  92. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/notebooks.py +0 -0
  93. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/office.py +0 -0
  94. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/ops.py +0 -0
  95. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/overleaf.py +0 -0
  96. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/pipeline.py +0 -0
  97. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/server.py +0 -0
  98. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/templates/__init__.py +0 -0
  99. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/templates/core.py +0 -0
  100. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/templates/latex/__init__.py +0 -0
  101. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/templates/latex/article/paper.tex +0 -0
  102. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/templates/latex/core.py +0 -0
  103. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/templates/latex/jfm/jfm.bst +0 -0
  104. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/templates/latex/jfm/jfm.cls +0 -0
  105. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
  106. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/templates/latex/jfm/paper.tex +0 -0
  107. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/templates/latex/jfm/upmath.sty +0 -0
  108. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/__init__.py +0 -0
  109. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/__init__.py +0 -0
  110. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/main/__init__.py +0 -0
  111. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/main/test_core.py +0 -0
  112. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/main/test_subprojects.py +0 -0
  113. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/main/test_xr.py +0 -0
  114. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/test_check.py +0 -0
  115. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/test_cloud.py +0 -0
  116. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/test_config.py +0 -0
  117. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/test_import.py +0 -0
  118. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/test_latex.py +0 -0
  119. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/test_list.py +0 -0
  120. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/test_notebooks.py +0 -0
  121. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/test_overleaf.py +0 -0
  122. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/test_scheduler.py +0 -0
  123. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/cli/test_update.py +0 -0
  124. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/dvc/__init__.py +0 -0
  125. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/dvc/test_core.py +0 -0
  126. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/dvc/test_zip.py +0 -0
  127. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/jupyterlab/__init__.py +0 -0
  128. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/jupyterlab/test_routes.py +0 -0
  129. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/models/__init__.py +0 -0
  130. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/models/test_iteration.py +0 -0
  131. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/models/test_pipeline.py +0 -0
  132. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_calc.py +0 -0
  133. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_check.py +0 -0
  134. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_cloud.py +0 -0
  135. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_conda.py +0 -0
  136. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_core.py +0 -0
  137. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_dependencies.py +0 -0
  138. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_detect.py +0 -0
  139. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_docker.py +0 -0
  140. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_environments.py +0 -0
  141. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_fs.py +0 -0
  142. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_git.py +0 -0
  143. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_install.py +0 -0
  144. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_invenio.py +0 -0
  145. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_julia.py +0 -0
  146. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_jupyter.py +0 -0
  147. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_licenses.py +0 -0
  148. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_magics.py +0 -0
  149. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_matlab.py +0 -0
  150. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_notebooks.py +0 -0
  151. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_pipeline.py +0 -0
  152. {calkit_python-0.41.8 → calkit_python-0.41.9}/calkit/tests/test_templates.py +0 -0
  153. {calkit_python-0.41.8 → calkit_python-0.41.9}/conftest.py +0 -0
  154. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/CNAME +0 -0
  155. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/ai-tools.md +0 -0
  156. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/apps.md +0 -0
  157. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/calculations.md +0 -0
  158. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/calkit-yaml.md +0 -0
  159. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/cli-reference.md +0 -0
  160. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/cloud-integration.md +0 -0
  161. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/datasets.md +0 -0
  162. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/dependencies.md +0 -0
  163. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/environments.md +0 -0
  164. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/examples.md +0 -0
  165. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/governance.md +0 -0
  166. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/help.md +0 -0
  167. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/hpc.md +0 -0
  168. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/img/c-to-the-k-white.svg +0 -0
  169. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/img/calkit-fragmentation-compendium.png +0 -0
  170. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/img/calkit-no-bg.png +0 -0
  171. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/img/connect-zenodo.png +0 -0
  172. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/img/jupyterlab/all-green.png +0 -0
  173. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/img/jupyterlab/collect-data-stale.png +0 -0
  174. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/img/jupyterlab/new-env.png +0 -0
  175. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/img/jupyterlab/new-notebook.png +0 -0
  176. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/img/jupyterlab/pipeline-badge.png +0 -0
  177. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/img/jupyterlab-params.png +0 -0
  178. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/img/plos-osi-code-2024-03.png +0 -0
  179. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/img/vscode-nb-params.png +0 -0
  180. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/index.md +0 -0
  181. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/installation.md +0 -0
  182. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/jupyterlab.md +0 -0
  183. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/local-server.md +0 -0
  184. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/notebooks.md +0 -0
  185. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/overleaf.md +0 -0
  186. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/pipeline/index.md +0 -0
  187. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/pipeline/manual-steps.md +0 -0
  188. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/pipeline/running-and-logging.md +0 -0
  189. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/quickstart.md +0 -0
  190. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/references.md +0 -0
  191. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/releases.md +0 -0
  192. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/reproducibility.md +0 -0
  193. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/subprojects.md +0 -0
  194. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/adding-latex-pub-docker.md +0 -0
  195. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/conda-envs.md +0 -0
  196. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/existing-project.md +0 -0
  197. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/first-project.md +0 -0
  198. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/github-actions.md +0 -0
  199. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/actions-repo-secrets.png +0 -0
  200. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
  201. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
  202. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
  203. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
  204. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
  205. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
  206. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
  207. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
  208. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
  209. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
  210. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/push.png +0 -0
  211. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
  212. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
  213. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/chart-more-rows.png +0 -0
  214. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/create-project.png +0 -0
  215. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
  216. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/excel-chart.png +0 -0
  217. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/excel-data.png +0 -0
  218. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
  219. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/needs-clone.png +0 -0
  220. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/new-stage.png +0 -0
  221. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
  222. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
  223. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/status-more-rows.png +0 -0
  224. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
  225. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/untracked-data.png +0 -0
  226. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/updated-publication.png +0 -0
  227. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
  228. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/office/workflow-page.png +0 -0
  229. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/clone.png +0 -0
  230. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/create-project.png +0 -0
  231. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
  232. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
  233. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
  234. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/new-token.png +0 -0
  235. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/reclone.png +0 -0
  236. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
  237. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/quick-actions.png +0 -0
  238. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/run-proc.png +0 -0
  239. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/create-calkit-env.png +0 -0
  240. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/create-inner-env.png +0 -0
  241. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/create-new-calkit-env.png +0 -0
  242. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/select-calkit-env.png +0 -0
  243. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/slurm-job-running.png +0 -0
  244. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/slurm-launch-options.png +0 -0
  245. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/img/vscode-slurm-notebook/starting-slurm-job.png +0 -0
  246. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/index.md +0 -0
  247. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/jupyterlab.md +0 -0
  248. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/latex-codespaces.md +0 -0
  249. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/matlab.md +0 -0
  250. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/notebook-pipeline.md +0 -0
  251. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/office.md +0 -0
  252. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/openfoam.md +0 -0
  253. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/procedures.md +0 -0
  254. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/tutorials/vscode-slurm-notebook.md +0 -0
  255. {calkit_python-0.41.8 → calkit_python-0.41.9}/docs/version-control.md +0 -0
  256. {calkit_python-0.41.8 → calkit_python-0.41.9}/flake.lock +0 -0
  257. {calkit_python-0.41.8 → calkit_python-0.41.9}/flake.nix +0 -0
  258. {calkit_python-0.41.8 → calkit_python-0.41.9}/install.json +0 -0
  259. {calkit_python-0.41.8 → calkit_python-0.41.9}/jest.config.js +0 -0
  260. {calkit_python-0.41.8 → calkit_python-0.41.9}/jupyter-config/server-config/calkit.json +0 -0
  261. {calkit_python-0.41.8 → calkit_python-0.41.9}/mkdocs.yml +0 -0
  262. {calkit_python-0.41.8 → calkit_python-0.41.9}/package.json +0 -0
  263. {calkit_python-0.41.8 → calkit_python-0.41.9}/pyproject.toml +0 -0
  264. {calkit_python-0.41.8 → calkit_python-0.41.9}/schema/plugin.json +0 -0
  265. {calkit_python-0.41.8 → calkit_python-0.41.9}/scripts/generate-docs-references.py +0 -0
  266. {calkit_python-0.41.8 → calkit_python-0.41.9}/scripts/install.ps1 +0 -0
  267. {calkit_python-0.41.8 → calkit_python-0.41.9}/scripts/install.sh +0 -0
  268. {calkit_python-0.41.8 → calkit_python-0.41.9}/scripts/make-calk9.sh +0 -0
  269. {calkit_python-0.41.8 → calkit_python-0.41.9}/scripts/sync-docs.py +0 -0
  270. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/__tests__/useQueries.spec.ts +0 -0
  271. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/calkit-config.ts +0 -0
  272. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/cell-output-marker.ts +0 -0
  273. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/components/commit-dialog.tsx +0 -0
  274. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/components/environment-editor.tsx +0 -0
  275. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/components/notebook-registration.tsx +0 -0
  276. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/components/notebook-toolbar.tsx +0 -0
  277. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/components/pipeline-status-bar.tsx +0 -0
  278. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/components/project-info-editor.tsx +0 -0
  279. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/components/sidebar-settings.tsx +0 -0
  280. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/components/sidebar.tsx +0 -0
  281. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/components/stage-editor.tsx +0 -0
  282. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/feature-flags.ts +0 -0
  283. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/file-browser-menu.ts +0 -0
  284. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/hooks/__tests__/useQueries.test.tsx +0 -0
  285. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/hooks/useQueries.ts +0 -0
  286. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/icons.ts +0 -0
  287. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/index.ts +0 -0
  288. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/io-tracker.ts +0 -0
  289. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/pipeline-state.ts +0 -0
  290. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/queryClient.ts +0 -0
  291. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/request.ts +0 -0
  292. {calkit_python-0.41.8 → calkit_python-0.41.9}/src/shims-mainmenu.d.ts +0 -0
  293. {calkit_python-0.41.8 → calkit_python-0.41.9}/style/base.css +0 -0
  294. {calkit_python-0.41.8 → calkit_python-0.41.9}/style/cell-output-marker.css +0 -0
  295. {calkit_python-0.41.8 → calkit_python-0.41.9}/style/environment-editor.css +0 -0
  296. {calkit_python-0.41.8 → calkit_python-0.41.9}/style/environment-selector.css +0 -0
  297. {calkit_python-0.41.8 → calkit_python-0.41.9}/style/img/calkit-no-bg.png +0 -0
  298. {calkit_python-0.41.8 → calkit_python-0.41.9}/style/index.css +0 -0
  299. {calkit_python-0.41.8 → calkit_python-0.41.9}/style/index.js +0 -0
  300. {calkit_python-0.41.8 → calkit_python-0.41.9}/style/notebook-toolbar.css +0 -0
  301. {calkit_python-0.41.8 → calkit_python-0.41.9}/style/pipeline-status-bar.css +0 -0
  302. {calkit_python-0.41.8 → calkit_python-0.41.9}/style/sidebar.css +0 -0
  303. {calkit_python-0.41.8 → calkit_python-0.41.9}/test/dvc-md5-dir/osx-arm64.txt +0 -0
  304. {calkit_python-0.41.8 → calkit_python-0.41.9}/test/nb-julia.ipynb +0 -0
  305. {calkit_python-0.41.8 → calkit_python-0.41.9}/test/nb-params.ipynb +0 -0
  306. {calkit_python-0.41.8 → calkit_python-0.41.9}/test/nb-subdir.ipynb +0 -0
  307. {calkit_python-0.41.8 → calkit_python-0.41.9}/test/pipeline.ipynb +0 -0
  308. {calkit_python-0.41.8 → calkit_python-0.41.9}/test/script.py +0 -0
  309. {calkit_python-0.41.8 → calkit_python-0.41.9}/test/test-log.log +0 -0
  310. {calkit_python-0.41.8 → calkit_python-0.41.9}/tsconfig.json +0 -0
  311. {calkit_python-0.41.8 → calkit_python-0.41.9}/tsconfig.test.json +0 -0
  312. {calkit_python-0.41.8 → calkit_python-0.41.9}/ui-tests/.gitignore +0 -0
  313. {calkit_python-0.41.8 → calkit_python-0.41.9}/ui-tests/README.md +0 -0
  314. {calkit_python-0.41.8 → calkit_python-0.41.9}/ui-tests/jupyter_server_test_config.py +0 -0
  315. {calkit_python-0.41.8 → calkit_python-0.41.9}/ui-tests/package.json +0 -0
  316. {calkit_python-0.41.8 → calkit_python-0.41.9}/ui-tests/playwright.config.js +0 -0
  317. {calkit_python-0.41.8 → calkit_python-0.41.9}/ui-tests/tests/calkit.spec.ts +0 -0
  318. {calkit_python-0.41.8 → calkit_python-0.41.9}/ui-tests/yarn.lock +0 -0
  319. {calkit_python-0.41.8 → calkit_python-0.41.9}/uv.lock +0 -0
  320. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/.gitignore +0 -0
  321. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/.vscodeignore +0 -0
  322. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/CHANGELOG.md +0 -0
  323. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/LICENSE +0 -0
  324. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/README.md +0 -0
  325. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/images/calkit-no-bg.png +0 -0
  326. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/package-lock.json +0 -0
  327. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/package.json +0 -0
  328. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/scripts/set-proposed-api.js +0 -0
  329. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/src/environments.ts +0 -0
  330. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/src/extension.ts +0 -0
  331. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/src/notebooks.ts +0 -0
  332. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/src/test/environments.test.ts +0 -0
  333. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/src/test/kernel-selection.test.ts +0 -0
  334. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/src/test/notebooks.test.ts +0 -0
  335. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/src/types.ts +0 -0
  336. {calkit_python-0.41.8 → calkit_python-0.41.9}/vscode-ext/tsconfig.json +0 -0
  337. {calkit_python-0.41.8 → calkit_python-0.41.9}/yarn.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.41.8
3
+ Version: 0.41.9
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -3143,7 +3143,7 @@ def new_release(
3143
3143
  )
3144
3144
  with open("LICENSE", "w") as f:
3145
3145
  f.write(license_txt)
3146
- repo.git.add("LICENSE")
3146
+ repo.git.add("LICENSE")
3147
3147
  license_ids = ["mit", "cc-by-4.0"]
3148
3148
  else:
3149
3149
  typer.echo(f"Detecting license(s) from {license_file}")
@@ -3394,38 +3394,23 @@ def new_release(
3394
3394
  doi_base_url = calkit.releases.SERVICES[to]["url"]
3395
3395
  doi_md = (
3396
3396
  f"[![DOI]({doi_base_url}/badge/DOI/{doi}.svg)]"
3397
- f"(https://handle.stage.datacite.org/{doi})"
3397
+ f"(https://doi.org/{doi})"
3398
3398
  )
3399
3399
  if os.path.isfile("README.md"):
3400
3400
  with open("README.md") as f:
3401
3401
  readme_txt = f.read()
3402
3402
  else:
3403
- readme_txt = f"# {title}\n"
3404
- existing_lines = readme_txt.split("\n")
3405
- new_lines = []
3406
- first_content_line_index = None
3407
- for n, line in enumerate(existing_lines):
3408
- if line.startswith(doi_md[:6]):
3409
- pass # Skip DOI lines
3410
- else:
3411
- if (
3412
- n != 0
3413
- and line.strip()
3414
- and first_content_line_index is None
3415
- ):
3416
- first_content_line_index = len(new_lines)
3417
- new_lines.append(line)
3418
- # Ensure first 3 lines are title, blank, DOI lines
3419
- new_lines = (
3420
- [new_lines[0]]
3421
- + ["", doi_md, ""]
3422
- + new_lines[first_content_line_index:]
3403
+ readme_txt = ""
3404
+ readme_txt = calkit.releases.add_doi_badge_to_readme(
3405
+ readme_txt, badge=doi_md, title=title
3423
3406
  )
3424
- readme_txt = "\n".join(new_lines)
3425
3407
  if not dry_run:
3426
3408
  with open("README.md", "w") as f:
3427
3409
  f.write(readme_txt)
3428
- repo.git.add("README.md")
3410
+ # Stage only after the file is closed; otherwise the buffered
3411
+ # contents may not be flushed to disk yet and Git would stage an
3412
+ # empty file
3413
+ repo.git.add("README.md")
3429
3414
  else:
3430
3415
  typer.echo(f"Would have updated README.md to:\n{readme_txt}")
3431
3416
  # Create Git tag
@@ -3480,11 +3465,13 @@ def new_release(
3480
3465
  references = reference_collections[0]
3481
3466
  ref_path = references.get("path", "references.bib")
3482
3467
  try:
3468
+ # Read the existing references as raw text so we can append the new
3469
+ # entry without reformatting the user's existing entries
3483
3470
  if os.path.isfile(ref_path):
3484
3471
  with open(ref_path) as f:
3485
- reflib = bibtexparser.load(f)
3472
+ existing_text = f.read()
3486
3473
  else:
3487
- reflib = bibtexparser.bibdatabase.BibDatabase()
3474
+ existing_text = ""
3488
3475
  bibtex_doi = doi
3489
3476
  if bibtex_doi is None and dry_run:
3490
3477
  mock_suffix = "".join(
@@ -3504,31 +3491,38 @@ def new_release(
3504
3491
  if not new_entries:
3505
3492
  raise ValueError("Failed to parse generated BibTeX entry")
3506
3493
  new_entry = new_entries[0]
3507
- # Search through entries for one with the same DOI, and replace if
3508
- # there is a match
3494
+ # Search through existing entries for any with the same DOI, and
3495
+ # replace them (by citation key) if there is a match. Tolerate parse
3496
+ # errors here (e.g., non-standard or temporarily-invalid BibTeX) by
3497
+ # skipping replacement detection and still appending the new entry.
3509
3498
  new_doi = new_entry.get("doi")
3510
- matching_indices = []
3499
+ replace_ids = []
3511
3500
  if new_doi:
3512
- matching_indices = [
3513
- n
3514
- for n, entry in enumerate(reflib.entries)
3501
+ try:
3502
+ existing_entries = bibtexparser.loads(existing_text).entries
3503
+ except Exception as e:
3504
+ warn(f"Could not parse existing references to dedupe: {e}")
3505
+ existing_entries = []
3506
+ replace_ids = [
3507
+ entry["ID"]
3508
+ for entry in existing_entries
3515
3509
  if entry.get("doi") == new_doi
3516
3510
  ]
3517
- if matching_indices:
3511
+ if replace_ids:
3518
3512
  typer.echo(
3519
3513
  "Found matching DOI in existing references "
3520
- f"({len(matching_indices)} entr"
3521
- f"{'y' if len(matching_indices) == 1 else 'ies'}); "
3514
+ f"({len(replace_ids)} entr"
3515
+ f"{'y' if len(replace_ids) == 1 else 'ies'}); "
3522
3516
  "replacing"
3523
3517
  )
3524
- for n in reversed(matching_indices):
3525
- _ = reflib.entries.pop(n)
3526
- reflib.entries.append(new_entry)
3518
+ new_text = calkit.releases.add_bibtex_entry(
3519
+ existing_text, invenio_bibtex, replace_ids=replace_ids
3520
+ )
3527
3521
  if dry_run:
3528
3522
  typer.echo(f"Would write updated references to {ref_path}")
3529
3523
  else:
3530
3524
  with open(ref_path, "w") as f:
3531
- bibtexparser.dump(reflib, f)
3525
+ f.write(new_text)
3532
3526
  repo.git.add(ref_path)
3533
3527
  except Exception as e:
3534
3528
  warn(f"Failed to add to references: {e}")
@@ -86,6 +86,109 @@ def create_bibtex(
86
86
  )
87
87
 
88
88
 
89
+ def _find_bibtex_entry_span(
90
+ text: str, entry_id: str
91
+ ) -> tuple[int, int] | None:
92
+ """Locate the raw character span of a BibTeX entry by its citation key.
93
+
94
+ Returns ``(start, end)`` offsets into ``text`` where ``end`` is exclusive
95
+ (just past the entry's closing brace), or ``None`` if no entry with that
96
+ key is found.
97
+ """
98
+ # Match e.g. ``@misc{the-key,`` allowing arbitrary surrounding whitespace
99
+ pattern = re.compile(
100
+ r"@\w+\s*\{\s*" + re.escape(entry_id) + r"\s*,", re.IGNORECASE
101
+ )
102
+ match = pattern.search(text)
103
+ if match is None:
104
+ return None
105
+ # Walk from the entry's opening brace to its matching close, counting depth
106
+ depth = 0
107
+ i = text.index("{", match.start())
108
+ while i < len(text):
109
+ if text[i] == "{":
110
+ depth += 1
111
+ elif text[i] == "}":
112
+ depth -= 1
113
+ if depth == 0:
114
+ return match.start(), i + 1
115
+ i += 1
116
+ return None
117
+
118
+
119
+ def add_bibtex_entry(
120
+ existing_text: str, new_entry: str, replace_ids: list[str] | None = None
121
+ ) -> str:
122
+ """Append a BibTeX entry to existing references without reformatting them.
123
+
124
+ Any entries whose citation keys are in ``replace_ids`` are removed first
125
+ (so a re-released version replaces its prior entry); all other existing
126
+ content is preserved byte-for-byte. ``new_entry`` is appended at the end.
127
+ """
128
+ text = existing_text
129
+ # Remove replaced entries from the raw text, last-to-first so earlier spans
130
+ # stay valid as we splice. De-duplicate spans in case a key matched more
131
+ # than once.
132
+ spans = set()
133
+ for entry_id in replace_ids or []:
134
+ span = _find_bibtex_entry_span(text, entry_id)
135
+ if span is not None:
136
+ spans.add(span)
137
+ for start, end in sorted(spans, reverse=True):
138
+ # Consume any trailing newlines so we don't leave a gap behind
139
+ while end < len(text) and text[end] in "\r\n":
140
+ end += 1
141
+ text = text[:start] + text[end:]
142
+ # Append the new entry, leaving the existing content untouched and adding
143
+ # only the separator newlines needed for exactly one blank line between the
144
+ # last existing entry and the new one
145
+ if not text.strip():
146
+ return new_entry.strip() + "\n"
147
+ if text.endswith("\n\n"):
148
+ separator = ""
149
+ elif text.endswith("\n"):
150
+ separator = "\n"
151
+ else:
152
+ separator = "\n\n"
153
+ return text + separator + new_entry.strip() + "\n"
154
+
155
+
156
+ def add_doi_badge_to_readme(
157
+ readme_text: str, badge: str, title: str | None
158
+ ) -> str:
159
+ """Insert (or refresh) a DOI badge near the top of a README.
160
+
161
+ The badge is placed directly beneath the title with exactly one blank line
162
+ on either side. Any pre-existing DOI badge line is replaced rather than
163
+ duplicated, the title is never duplicated, and all other existing content
164
+ is preserved.
165
+ """
166
+ # Drop any existing DOI badge line(s); the current badge is re-added below
167
+ lines = [
168
+ line
169
+ for line in (readme_text.split("\n") if readme_text else [])
170
+ if not line.startswith("[![DOI")
171
+ ]
172
+ # The first non-blank line is treated as the title; synthesize one from the
173
+ # project title if the README has no content yet
174
+ while lines and not lines[0].strip():
175
+ lines.pop(0)
176
+ if lines:
177
+ title_line = lines[0]
178
+ rest = lines[1:]
179
+ else:
180
+ title_line = f"# {title}"
181
+ rest = []
182
+ # Drop leading blank lines from the remaining content so the spacing around
183
+ # the badge is controlled entirely here
184
+ while rest and not rest[0].strip():
185
+ rest.pop(0)
186
+ new_lines = [title_line, "", badge]
187
+ if rest:
188
+ new_lines += [""] + rest
189
+ return "\n".join(new_lines)
190
+
191
+
89
192
  CITATION_CFF_TEMPLATE = """
90
193
  cff-version: 1.2.0
91
194
  message: If you use this software, please cite it using these metadata.
@@ -810,6 +810,21 @@ def test_new_release(tmp_dir, monkeypatch, httpserver):
810
810
  assert "v0.1.0" in ck_info["releases"]
811
811
  release = ck_info["releases"]["v0.1.0"]
812
812
  assert release["doi"] is not None
813
+ # Regression test for issue #931: the README must not be emptied, the
814
+ # committed README must match the working tree (otherwise it shows up as a
815
+ # spurious unstaged change), and the DOI badge must link to doi.org rather
816
+ # than the DataCite staging host (which 502s)
817
+ repo = git.Repo()
818
+ readme_working = (tmp_dir / "README.md").read_text()
819
+ assert readme_working.strip()
820
+ readme_committed = repo.git.show("HEAD:README.md")
821
+ assert readme_committed.strip() == readme_working.strip()
822
+ badge_link = f"](https://doi.org/{release['doi']})"
823
+ assert badge_link in readme_working
824
+ assert "datacite.org" not in readme_working
825
+ # The release BibTeX entry should be added to references.bib
826
+ references = (tmp_dir / "references.bib").read_text()
827
+ assert release["doi"] in references
813
828
  # TODO: Test that the GitHub link is in the related works
814
829
  # Test that we can update this release
815
830
  # Side note: This is revealing some design weirdness where we're grouping
@@ -12,6 +12,8 @@ import pytest
12
12
  import calkit
13
13
  from calkit.dvc.zip import write_zip_path_map
14
14
  from calkit.releases import (
15
+ add_bibtex_entry,
16
+ add_doi_badge_to_readme,
15
17
  check_project_release_archive,
16
18
  create_bibtex,
17
19
  create_citation_cff,
@@ -163,6 +165,89 @@ def test_create_bibtex():
163
165
  assert len(entries) == 1
164
166
 
165
167
 
168
+ def test_add_bibtex_entry():
169
+ new_entry = create_bibtex(
170
+ authors=[{"first_name": "Jane", "last_name": "Doe"}],
171
+ release_date="2026-06-03",
172
+ title="New release",
173
+ doi="10.5281/zenodo.999",
174
+ record_id="999",
175
+ )
176
+ # Appending to existing references preserves the original formatting
177
+ # (e.g., tab-indented fields) byte-for-byte and only adds the new entry
178
+ existing = (
179
+ "@article{smith2020,\n"
180
+ "\tauthor = {Smith, John},\n"
181
+ "\tdoi = {10.1/abc},\n"
182
+ "}\n"
183
+ )
184
+ appended = add_bibtex_entry(existing, new_entry, replace_ids=[])
185
+ # The existing content is preserved byte-for-byte at the start, with a
186
+ # single blank line separating it from the appended entry
187
+ assert appended.startswith(existing)
188
+ assert appended == existing + "\n" + new_entry.strip() + "\n"
189
+ assert appended.count("@misc{Doe2026_999,") == 1
190
+ assert bibtexparser.loads(appended).entries[0]["doi"] == "10.1/abc"
191
+ # Appending to an empty/nonexistent file yields just the new entry
192
+ from_empty = add_bibtex_entry("", new_entry, replace_ids=[])
193
+ assert from_empty.strip().startswith("@misc{Doe2026_999,")
194
+ # A matching entry (by citation key) is replaced, while other entries are
195
+ # left untouched and keep their original formatting
196
+ with_old = existing + (
197
+ "\n@misc{old2021,\n"
198
+ "\tauthor = {Doe, Jane},\n"
199
+ "\ttitle = {Old release},\n"
200
+ "\tdoi = {10.5281/zenodo.999},\n"
201
+ "}\n"
202
+ )
203
+ replaced = add_bibtex_entry(with_old, new_entry, replace_ids=["old2021"])
204
+ assert "Old release" not in replaced
205
+ assert "@article{smith2020,\n\tauthor = {Smith, John}," in replaced
206
+ assert replaced.count("@misc{Doe2026_999,") == 1
207
+
208
+
209
+ def test_add_doi_badge_to_readme():
210
+ badge = "[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.999.svg)](https://doi.org/10.5281/zenodo.999)" # noqa: E501
211
+ # A README with a title and body gets the badge inserted directly beneath
212
+ # the title with exactly one blank line on either side, and no content is
213
+ # removed or duplicated
214
+ readme = "# My Project\nSome description.\n\n## Section\nDetails here.\n"
215
+ out = add_doi_badge_to_readme(readme, badge=badge, title="My Project")
216
+ assert out.startswith(f"# My Project\n\n{badge}\n\nSome description.")
217
+ assert out.count("# My Project\n") == 1
218
+ assert "Some description." in out
219
+ assert "## Section" in out
220
+ assert "Details here." in out
221
+ # The badge has exactly one blank line above and below it
222
+ lines = out.split("\n")
223
+ idx = lines.index(badge)
224
+ assert lines[idx - 1] == ""
225
+ assert lines[idx - 2] == "# My Project"
226
+ assert lines[idx + 1] == ""
227
+ assert lines[idx + 2].strip()
228
+ # A title-only README does not duplicate the title
229
+ title_only = add_doi_badge_to_readme(
230
+ "# My Project\n", badge=badge, title="My Project"
231
+ )
232
+ assert title_only == f"# My Project\n\n{badge}"
233
+ # An empty/missing README falls back to the provided project title
234
+ from_empty = add_doi_badge_to_readme("", badge=badge, title="My Project")
235
+ assert from_empty == f"# My Project\n\n{badge}"
236
+ # An existing DOI badge is replaced, not duplicated, and sibling badges are
237
+ # preserved in place
238
+ other_badge = "[![CI](https://example.com/ci.svg)](https://example.com)"
239
+ old_badge = "[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.111.svg)](https://doi.org/10.5281/zenodo.111)" # noqa: E501
240
+ with_old = f"# My Project\n{other_badge}\n\n{old_badge}\n\nBody.\n"
241
+ refreshed = add_doi_badge_to_readme(
242
+ with_old, badge=badge, title="My Project"
243
+ )
244
+ assert refreshed.count("[![DOI](") == 1
245
+ assert badge in refreshed
246
+ assert "zenodo.111" not in refreshed
247
+ assert other_badge in refreshed
248
+ assert "Body." in refreshed
249
+
250
+
166
251
  def test_read_authors_from_cff(tmp_dir):
167
252
  # No file present yields an empty list
168
253
  assert read_authors_from_cff() == []
File without changes
File without changes
File without changes
File without changes