calkit-python 0.35.4__tar.gz → 0.35.6__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.
- {calkit_python-0.35.4 → calkit_python-0.35.6}/Makefile +7 -2
- {calkit_python-0.35.4 → calkit_python-0.35.6}/PKG-INFO +1 -1
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/conda.py +78 -28
- calkit_python-0.35.6/calkit/git.py +297 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/overleaf.py +32 -1
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/pipeline.py +27 -8
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/cli/test_overleaf.py +38 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_conda.py +120 -2
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_git.py +59 -10
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_pipeline.py +100 -0
- calkit_python-0.35.6/docs/cli-reference.md +2586 -0
- calkit_python-0.35.6/scripts/generate-cli-reference.py +257 -0
- calkit_python-0.35.4/calkit/git.py +0 -197
- calkit_python-0.35.4/docs/cli-reference.md +0 -3
- {calkit_python-0.35.4 → calkit_python-0.35.6}/.gitignore +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/.pre-commit-config.yaml +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/.prettierignore +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/.python-version +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/.yarnrc.yml +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/CITATION.cff +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/CODE_OF_CONDUCT.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/CONTRIBUTING.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/LICENSE +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/README.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/babel.config.js +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/__init__.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/__main__.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/calc.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/check.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/__init__.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/check.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/cloud.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/config.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/core.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/describe.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/import_.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/latex.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/list.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/main/__init__.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/main/core.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/main/xr.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/new.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/notebooks.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/office.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/overleaf.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/slurm.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cli/update.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/cloud.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/config.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/core.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/datasets.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/detect.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/docker.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/dvc.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/environments.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/fs.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/github.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/gui.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/invenio.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/julia.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/jupyter.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/jupyterlab/__init__.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/jupyterlab/routes.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/labextension/package.json +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/labextension/schemas/calkit/package.json.orig +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/labextension/schemas/calkit/plugin.json +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/labextension/static/502.9a2c5772a15466e923ef.js +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/labextension/static/695.2c41003a452d43d2b358.js +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/labextension/static/867.a42a046aa5108f54f8fb.js +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/labextension/static/909.651be47ca47390b78a92.js +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/labextension/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/labextension/static/b2f1c3efe70cb539d121.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/labextension/static/remoteEntry.c091821b3d7f2d287a67.js +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/labextension/static/style.js +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/labextension/static/third-party-licenses.json +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/licenses.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/magics.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/matlab.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/models/__init__.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/models/core.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/models/io.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/models/iteration.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/models/pipeline.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/notebooks.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/office.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/ops.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/releases.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/server.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/templates/__init__.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/templates/core.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/templates/latex/__init__.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/templates/latex/article/paper.tex +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/templates/latex/core.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/templates/latex/jfm/jfm.bst +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/templates/latex/jfm/jfm.cls +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/templates/latex/jfm/paper.tex +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/templates/latex/jfm/upmath.sty +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/__init__.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/cli/__init__.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/cli/main/__init__.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/cli/main/test_core.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/cli/main/test_xr.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/cli/test_check.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/cli/test_config.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/cli/test_import.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/cli/test_latex.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/cli/test_list.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/cli/test_new.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/cli/test_notebooks.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/jupyterlab/__init__.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/jupyterlab/test_routes.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/models/__init__.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/models/test_iteration.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/models/test_pipeline.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_calc.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_check.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_core.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_detect.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_docker.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_dvc.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_environments.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_fs.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_invenio.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_julia.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_jupyter.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_magics.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_matlab.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_notebooks.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_releases.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/calkit/tests/test_templates.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/conftest.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/CNAME +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/apps.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/calculations.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/calkit-yaml.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/cloud-integration.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/datasets.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/dependencies.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/environments.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/examples.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/governance.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/help.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/img/c-to-the-k-white.svg +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/img/calkit-fragmentation-compendium.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/img/calkit-no-bg.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/img/connect-zenodo.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/img/jupyterlab/all-green.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/img/jupyterlab/collect-data-stale.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/img/jupyterlab/new-env.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/img/jupyterlab/new-notebook.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/img/jupyterlab/pipeline-badge.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/img/jupyterlab-params.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/img/plos-osi-code-2024-03.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/img/vscode-nb-params.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/index.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/installation.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/jupyterlab.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/local-server.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/notebooks.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/overleaf.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/pipeline/index.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/pipeline/manual-steps.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/pipeline/running-and-logging.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/pipeline/slurm.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/quickstart.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/references.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/releases.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/reproducibility.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/adding-latex-pub-docker.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/conda-envs.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/existing-project.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/first-project.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/github-actions.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/actions-repo-secrets.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/push.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/chart-more-rows.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/create-project.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/excel-chart.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/excel-data.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/needs-clone.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/new-stage.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/status-more-rows.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/untracked-data.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/updated-publication.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/office/workflow-page.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/clone.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/create-project.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/new-token.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/reclone.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/quick-actions.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/img/run-proc.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/index.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/jupyterlab.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/latex-codespaces.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/matlab.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/notebook-pipeline.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/office.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/openfoam.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/tutorials/procedures.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/docs/version-control.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/install.json +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/jest.config.js +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/jupyter-config/server-config/calkit.json +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/mkdocs.yml +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/package.json +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/pyproject.toml +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/schema/plugin.json +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/scripts/install.ps1 +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/scripts/install.sh +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/scripts/make-calk9.sh +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/scripts/sync-docs.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/__tests__/useQueries.spec.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/calkit-config.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/cell-output-marker.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/components/commit-dialog.tsx +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/components/environment-editor.tsx +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/components/notebook-registration.tsx +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/components/notebook-toolbar.tsx +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/components/pipeline-status-bar.tsx +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/components/project-info-editor.tsx +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/components/sidebar-settings.tsx +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/components/sidebar.tsx +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/components/stage-editor.tsx +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/feature-flags.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/file-browser-menu.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/hooks/__tests__/useQueries.test.tsx +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/hooks/useQueries.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/icons.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/index.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/io-tracker.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/pipeline-state.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/queryClient.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/request.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/src/shims-mainmenu.d.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/style/base.css +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/style/cell-output-marker.css +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/style/environment-editor.css +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/style/environment-selector.css +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/style/img/calkit-no-bg.png +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/style/index.css +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/style/index.js +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/style/notebook-toolbar.css +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/style/pipeline-status-bar.css +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/style/sidebar.css +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/test/dvc-md5-dir/osx-arm64.txt +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/test/nb-julia.ipynb +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/test/nb-params.ipynb +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/test/nb-subdir.ipynb +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/test/pipeline.ipynb +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/test/script.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/test/test-log.log +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/tsconfig.json +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/tsconfig.test.json +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/ui-tests/.gitignore +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/ui-tests/README.md +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/ui-tests/jupyter_server_test_config.py +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/ui-tests/package.json +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/ui-tests/playwright.config.js +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/ui-tests/tests/calkit.spec.ts +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/ui-tests/yarn.lock +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/uv.lock +0 -0
- {calkit_python-0.35.4 → calkit_python-0.35.6}/yarn.lock +0 -0
|
@@ -37,12 +37,17 @@ test-docs: sync-docs ## Test if documentation can be built without warnings or e
|
|
|
37
37
|
@uv run mkdocs build -s
|
|
38
38
|
|
|
39
39
|
.PHONY: sync-docs
|
|
40
|
-
sync-docs: ## Sync documentation content from docs/*.md into README.md.
|
|
40
|
+
sync-docs: cli-reference ## Sync documentation content from docs/*.md into README.md.
|
|
41
41
|
@echo "🚀 Syncing documentation"
|
|
42
42
|
@uv run python scripts/sync-docs.py
|
|
43
43
|
|
|
44
|
+
.PHONY: cli-reference
|
|
45
|
+
cli-reference: ## Generate docs/cli-reference.md from CLI help output.
|
|
46
|
+
@echo "🚀 Generating CLI reference"
|
|
47
|
+
@uv run python scripts/generate-cli-reference.py
|
|
48
|
+
|
|
44
49
|
.PHONY: docs
|
|
45
|
-
docs: ## Build and serve the documentation.
|
|
50
|
+
docs: sync-docs ## Build and serve the documentation.
|
|
46
51
|
@uv run mkdocs serve --livereload
|
|
47
52
|
|
|
48
53
|
.PHONY: import-profile
|
|
@@ -9,6 +9,7 @@ import shutil
|
|
|
9
9
|
import subprocess
|
|
10
10
|
import warnings
|
|
11
11
|
from pathlib import Path
|
|
12
|
+
from typing import cast
|
|
12
13
|
|
|
13
14
|
import toml
|
|
14
15
|
from packaging.specifiers import SpecifierSet
|
|
@@ -123,7 +124,13 @@ def _editable_package_name_from_dir(dir_path: str) -> str:
|
|
|
123
124
|
elif os.path.isfile(os.path.join(dir_path, "pyproject.toml")):
|
|
124
125
|
# Read pyproject.toml to get the package name
|
|
125
126
|
with open(os.path.join(dir_path, "pyproject.toml")) as f:
|
|
126
|
-
|
|
127
|
+
try:
|
|
128
|
+
pyproject = toml.load(f)
|
|
129
|
+
except Exception as e:
|
|
130
|
+
raise type(e)(
|
|
131
|
+
f"Failed to load pyproject.toml from {dir_path}; "
|
|
132
|
+
"check that it is valid TOML"
|
|
133
|
+
) from e
|
|
127
134
|
if "project" in pyproject:
|
|
128
135
|
if "name" in pyproject["project"]:
|
|
129
136
|
return pyproject["project"]["name"]
|
|
@@ -139,6 +146,7 @@ def _check_single(
|
|
|
139
146
|
"""
|
|
140
147
|
# If this is an editable install it needs to be handled specially
|
|
141
148
|
# It also needs to be relative to the env spec dir
|
|
149
|
+
editable = False
|
|
142
150
|
if req.startswith("-e ") or req.startswith("--editable "):
|
|
143
151
|
req = req.split(" ", 1)[1]
|
|
144
152
|
if "#" in req:
|
|
@@ -147,6 +155,7 @@ def _check_single(
|
|
|
147
155
|
# Create path relative to env spec dir
|
|
148
156
|
req = os.path.join(env_spec_dir, req)
|
|
149
157
|
req = _editable_package_name_from_dir(req)
|
|
158
|
+
editable = True
|
|
150
159
|
# If this is a Git version, we can't check it
|
|
151
160
|
# TODO: Clone Git repos to check?
|
|
152
161
|
if "@git" in req:
|
|
@@ -182,7 +191,7 @@ def _check_single(
|
|
|
182
191
|
# TODO: Check exact version only
|
|
183
192
|
return True
|
|
184
193
|
spec = SpecifierSet(req_spec)
|
|
185
|
-
return spec.contains(version)
|
|
194
|
+
return spec.contains(version, prereleases=editable)
|
|
186
195
|
|
|
187
196
|
|
|
188
197
|
def _check_list(
|
|
@@ -193,6 +202,10 @@ def _check_list(
|
|
|
193
202
|
if "::" in req:
|
|
194
203
|
req = req.split("::", 1)[1]
|
|
195
204
|
for installed in actual:
|
|
205
|
+
if not isinstance(installed, str):
|
|
206
|
+
raise ValueError(
|
|
207
|
+
f"Expected installed package to be a string, got {installed}"
|
|
208
|
+
)
|
|
196
209
|
if _check_single(
|
|
197
210
|
req, installed, env_spec_dir=env_spec_dir, conda=conda
|
|
198
211
|
):
|
|
@@ -200,6 +213,46 @@ def _check_list(
|
|
|
200
213
|
return False
|
|
201
214
|
|
|
202
215
|
|
|
216
|
+
def _split_env_dependencies(
|
|
217
|
+
dependencies: list[str | dict[str, str | list[str]]],
|
|
218
|
+
) -> tuple[list[str], list[str]]:
|
|
219
|
+
"""Split an environment dependency list into conda and pip deps.
|
|
220
|
+
|
|
221
|
+
Conda environment files commonly include both the plain ``"pip"`` package
|
|
222
|
+
marker and a nested ``{"pip": [...]}`` section. This helper normalizes the
|
|
223
|
+
latter so callers do not need to assume it is the final list entry or that
|
|
224
|
+
the pip section is already represented as a list.
|
|
225
|
+
"""
|
|
226
|
+
conda_deps = []
|
|
227
|
+
pip_deps = []
|
|
228
|
+
for dep in dependencies:
|
|
229
|
+
if isinstance(dep, dict):
|
|
230
|
+
dep_pip = dep.get("pip", [])
|
|
231
|
+
if isinstance(dep_pip, str):
|
|
232
|
+
dep_pip = [dep_pip]
|
|
233
|
+
elif dep_pip is None:
|
|
234
|
+
dep_pip = []
|
|
235
|
+
pip_deps.extend(dep_pip)
|
|
236
|
+
else:
|
|
237
|
+
conda_deps.append(dep)
|
|
238
|
+
return conda_deps, pip_deps
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _get_pip_dependency_list(
|
|
242
|
+
dependencies: list[str | dict[str, str | list[str]]],
|
|
243
|
+
) -> list[str]:
|
|
244
|
+
"""Return a mutable pip dependency list from an env dependency list."""
|
|
245
|
+
for dep in dependencies:
|
|
246
|
+
if isinstance(dep, dict) and "pip" in dep:
|
|
247
|
+
dep_pip = dep["pip"]
|
|
248
|
+
if isinstance(dep_pip, str):
|
|
249
|
+
dep["pip"] = [dep_pip]
|
|
250
|
+
elif dep_pip is None:
|
|
251
|
+
dep["pip"] = []
|
|
252
|
+
return cast(list[str], dep["pip"])
|
|
253
|
+
return []
|
|
254
|
+
|
|
255
|
+
|
|
203
256
|
class EnvCheckResult(BaseModel):
|
|
204
257
|
env_exists: bool | None = None
|
|
205
258
|
env_needs_export: bool | None = None
|
|
@@ -393,18 +446,12 @@ def check_env(
|
|
|
393
446
|
ryaml.dump(env_check, f)
|
|
394
447
|
# Determine if the env matches
|
|
395
448
|
env_needs_rebuild = False
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if isinstance(env_spec["dependencies"][-1], dict):
|
|
403
|
-
required_conda_deps = env_spec["dependencies"][:-1]
|
|
404
|
-
required_pip_deps = env_spec["dependencies"][-1]["pip"]
|
|
405
|
-
else:
|
|
406
|
-
required_conda_deps = env_spec["dependencies"]
|
|
407
|
-
required_pip_deps = []
|
|
449
|
+
existing_conda_deps, existing_pip_deps = _split_env_dependencies(
|
|
450
|
+
env_check["dependencies"]
|
|
451
|
+
)
|
|
452
|
+
required_conda_deps, required_pip_deps = _split_env_dependencies(
|
|
453
|
+
env_spec["dependencies"]
|
|
454
|
+
)
|
|
408
455
|
if relaxed:
|
|
409
456
|
log_func("Running in relaxed mode; combining pip and conda deps")
|
|
410
457
|
for dep in existing_pip_deps:
|
|
@@ -516,20 +563,23 @@ def check_env(
|
|
|
516
563
|
# Note that this needs to be relative to the env lock directory,
|
|
517
564
|
# since that's how pip will interpret it
|
|
518
565
|
editable_pip_deps = {}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
if
|
|
524
|
-
dir_path =
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
566
|
+
required_pip_deps = _get_pip_dependency_list(env_spec["dependencies"])
|
|
567
|
+
for dep in required_pip_deps:
|
|
568
|
+
if dep.startswith("-e ") or dep.startswith("--editable "):
|
|
569
|
+
dir_path = dep.split(" ", 1)[1]
|
|
570
|
+
if "#" in dir_path:
|
|
571
|
+
dir_path = dir_path.split("#", 1)[0]
|
|
572
|
+
dir_path = dir_path.strip()
|
|
573
|
+
dir_path = os.path.join(env_spec_dir, dir_path)
|
|
574
|
+
pkg_name = _editable_package_name_from_dir(dir_path)
|
|
575
|
+
if verbose:
|
|
576
|
+
log_func(
|
|
577
|
+
f"Found editable pip dependency '{pkg_name}' "
|
|
578
|
+
f"at '{dir_path}'"
|
|
579
|
+
)
|
|
580
|
+
editable_pip_deps[pkg_name] = dir_path
|
|
581
|
+
export_pip_deps = _get_pip_dependency_list(env_export["dependencies"])
|
|
582
|
+
if export_pip_deps:
|
|
533
583
|
for i, dep in enumerate(export_pip_deps):
|
|
534
584
|
dep_name = re.split("[=<>]+", dep, maxsplit=1)[0]
|
|
535
585
|
if dep_name in editable_pip_deps:
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Git-related functionality."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from os import PathLike
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
import git
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_staged_files(
|
|
13
|
+
path: str | None = None, repo: git.Repo | None = None
|
|
14
|
+
) -> list[str]:
|
|
15
|
+
"""Get a list of staged files for the repo at ``path`` or the provided
|
|
16
|
+
repo.
|
|
17
|
+
"""
|
|
18
|
+
if repo is None:
|
|
19
|
+
repo = git.Repo(path)
|
|
20
|
+
cmd = ["--staged", "--name-only"]
|
|
21
|
+
if path is not None:
|
|
22
|
+
cmd.append(path)
|
|
23
|
+
diff = repo.git.diff(cmd)
|
|
24
|
+
paths = diff.split("\n")
|
|
25
|
+
return [p for p in paths if p]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_changed_files(
|
|
29
|
+
path: str | None = None, repo: git.Repo | None = None
|
|
30
|
+
) -> list[str]:
|
|
31
|
+
"""Get a list of files that have been changed but not staged."""
|
|
32
|
+
if repo is None:
|
|
33
|
+
repo = git.Repo(path)
|
|
34
|
+
return [
|
|
35
|
+
item.a_path
|
|
36
|
+
for item in repo.index.diff(None)
|
|
37
|
+
if item.a_path is not None
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_untracked_files(
|
|
42
|
+
path: str | None = None, repo: git.Repo | None = None
|
|
43
|
+
) -> list[str]:
|
|
44
|
+
"""Get a list of untracked files."""
|
|
45
|
+
if repo is None:
|
|
46
|
+
repo = git.Repo(path)
|
|
47
|
+
return repo.untracked_files
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_staged_files_with_status(
|
|
51
|
+
path: str | None = None, repo: git.Repo | None = None
|
|
52
|
+
) -> list[dict]:
|
|
53
|
+
if repo is None:
|
|
54
|
+
repo = git.Repo(path)
|
|
55
|
+
cmd = ["--staged", "--name-status"]
|
|
56
|
+
if path is not None:
|
|
57
|
+
cmd.append(path)
|
|
58
|
+
diff = repo.git.diff(cmd)
|
|
59
|
+
paths = diff.split("\n")
|
|
60
|
+
res = []
|
|
61
|
+
for pathi in paths:
|
|
62
|
+
# Make sure line is not empty, e.g., a trailing newline
|
|
63
|
+
if pathi:
|
|
64
|
+
status, p = pathi.split("\t")
|
|
65
|
+
res.append({"status": status, "path": p})
|
|
66
|
+
return res
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def ls_files(repo: git.Repo, *args, **kwargs) -> list[str]:
|
|
70
|
+
"""Get a list of all files tracked by git."""
|
|
71
|
+
output = repo.git.ls_files(*args, **kwargs)
|
|
72
|
+
return [f for f in output.split("\n") if f]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _resolve_repo_and_ignore_path(
|
|
76
|
+
repo: git.Repo, path: str | PathLike
|
|
77
|
+
) -> tuple[git.Repo, str]:
|
|
78
|
+
"""Resolve which repo should own ignore rules for ``path``."""
|
|
79
|
+
# Normalize target path to absolute from the current repo root.
|
|
80
|
+
repo_root = Path(repo.working_dir).resolve()
|
|
81
|
+
path_obj = Path(path)
|
|
82
|
+
if path_obj.is_absolute():
|
|
83
|
+
abs_path = path_obj.resolve()
|
|
84
|
+
else:
|
|
85
|
+
abs_path = (repo_root / path_obj).resolve()
|
|
86
|
+
# If the path is inside a submodule, use that repo and relative path.
|
|
87
|
+
for submodule in repo.submodules:
|
|
88
|
+
submodule_root = (repo_root / submodule.path).resolve()
|
|
89
|
+
if abs_path == submodule_root:
|
|
90
|
+
continue
|
|
91
|
+
if abs_path.is_relative_to(submodule_root):
|
|
92
|
+
sub_repo = submodule.module()
|
|
93
|
+
rel_path = abs_path.relative_to(submodule_root).as_posix()
|
|
94
|
+
return sub_repo, rel_path
|
|
95
|
+
# Fall back to a repo-relative path when possible.
|
|
96
|
+
try:
|
|
97
|
+
rel_path = abs_path.relative_to(repo_root).as_posix()
|
|
98
|
+
except ValueError:
|
|
99
|
+
rel_path = path_obj.as_posix()
|
|
100
|
+
return repo, rel_path
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _get_matching_gitignore_details(
|
|
104
|
+
repo: git.Repo, path: str
|
|
105
|
+
) -> tuple[Path | None, str | None]:
|
|
106
|
+
"""Return the repo-local gitignore file and pattern matching ``path``."""
|
|
107
|
+
try:
|
|
108
|
+
check_ignore = repo.git.check_ignore("-v", "--", path)
|
|
109
|
+
except git.GitCommandError:
|
|
110
|
+
return None, None
|
|
111
|
+
line = check_ignore.splitlines()[0]
|
|
112
|
+
try:
|
|
113
|
+
source_info, _ = line.split("\t", 1)
|
|
114
|
+
source_path, _, pattern = source_info.rsplit(":", 2)
|
|
115
|
+
except ValueError:
|
|
116
|
+
return None, None
|
|
117
|
+
if not source_path.endswith(".gitignore"):
|
|
118
|
+
return None, pattern
|
|
119
|
+
gitignore_path = (Path(repo.working_dir) / source_path).resolve()
|
|
120
|
+
try:
|
|
121
|
+
gitignore_path.relative_to(Path(repo.working_dir).resolve())
|
|
122
|
+
except ValueError:
|
|
123
|
+
return None, pattern
|
|
124
|
+
return gitignore_path, pattern
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def ensure_path_is_ignored(
|
|
128
|
+
repo: git.Repo, path: str | PathLike
|
|
129
|
+
) -> None | bool:
|
|
130
|
+
"""Ensure that the given path is ignored by Git.
|
|
131
|
+
|
|
132
|
+
Returns True if ``.gitignore`` was modified.
|
|
133
|
+
"""
|
|
134
|
+
# Resolve whether the ignore rule belongs to this repo or a submodule.
|
|
135
|
+
target_repo, target_path = _resolve_repo_and_ignore_path(repo, path)
|
|
136
|
+
# No-op if Git already ignores this path.
|
|
137
|
+
if target_repo.ignored(target_path):
|
|
138
|
+
return
|
|
139
|
+
# Read gitignore first to check if the path is already ignored
|
|
140
|
+
# If not, we don't want to add a line for it since it was added
|
|
141
|
+
# TODO: Add an option to remove cached (`git rm --cached`)
|
|
142
|
+
gitignore_path = os.path.join(target_repo.working_dir, ".gitignore")
|
|
143
|
+
if os.path.isfile(gitignore_path):
|
|
144
|
+
with open(gitignore_path) as f:
|
|
145
|
+
gitignore_txt = f.read()
|
|
146
|
+
lines = gitignore_txt.splitlines()
|
|
147
|
+
if target_path in lines:
|
|
148
|
+
return
|
|
149
|
+
with open(gitignore_path, "a") as f:
|
|
150
|
+
if (
|
|
151
|
+
os.path.isfile(gitignore_path)
|
|
152
|
+
and os.path.getsize(gitignore_path) > 0
|
|
153
|
+
):
|
|
154
|
+
f.write("\n")
|
|
155
|
+
f.write(f"{target_path}\n")
|
|
156
|
+
return True
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def ensure_path_is_not_ignored(
|
|
160
|
+
repo: git.Repo, path: str | PathLike
|
|
161
|
+
) -> None | bool:
|
|
162
|
+
"""Ensure a path is not ignored by Git."""
|
|
163
|
+
# Resolve whether the unignore rule belongs to this repo or a submodule.
|
|
164
|
+
target_repo, target_path = _resolve_repo_and_ignore_path(repo, path)
|
|
165
|
+
# No-op if Git does not ignore this path.
|
|
166
|
+
if not target_repo.ignored(target_path):
|
|
167
|
+
return
|
|
168
|
+
matching_gitignore_path, matched_pattern = _get_matching_gitignore_details(
|
|
169
|
+
target_repo, target_path
|
|
170
|
+
)
|
|
171
|
+
if matching_gitignore_path is not None:
|
|
172
|
+
gitignore_path = matching_gitignore_path.as_posix()
|
|
173
|
+
path_for_gitignore = (
|
|
174
|
+
(Path(target_repo.working_dir) / target_path)
|
|
175
|
+
.resolve()
|
|
176
|
+
.relative_to(matching_gitignore_path.parent.resolve())
|
|
177
|
+
.as_posix()
|
|
178
|
+
)
|
|
179
|
+
else:
|
|
180
|
+
gitignore_path = os.path.join(target_repo.working_dir, ".gitignore")
|
|
181
|
+
path_for_gitignore = target_path
|
|
182
|
+
if not os.path.isfile(gitignore_path):
|
|
183
|
+
with open(gitignore_path, "w") as f:
|
|
184
|
+
f.write(f"!{path_for_gitignore}\n")
|
|
185
|
+
return True
|
|
186
|
+
with open(gitignore_path) as f:
|
|
187
|
+
gitignore_txt = f.read()
|
|
188
|
+
lines = gitignore_txt.splitlines()
|
|
189
|
+
direct_rule_variants = [path_for_gitignore, f"/{path_for_gitignore}"]
|
|
190
|
+
if matched_pattern is not None and matched_pattern.startswith("/"):
|
|
191
|
+
no_ignore_line = f"!/{path_for_gitignore}"
|
|
192
|
+
else:
|
|
193
|
+
no_ignore_line = f"!{path_for_gitignore}"
|
|
194
|
+
path_parts = Path(path_for_gitignore).parts
|
|
195
|
+
|
|
196
|
+
def ancestor_requires_recursive_unignore() -> bool:
|
|
197
|
+
"""Return True if any ancestor-level ignore rule would block this path.
|
|
198
|
+
|
|
199
|
+
This includes explicit directory ignores (e.g. 'dir/' or '/dir/')
|
|
200
|
+
as well as ancestor-based glob patterns like 'dir/*' or '/dir/*',
|
|
201
|
+
i.e., any rule that would prevent reaching the nested path without
|
|
202
|
+
adding recursive unignore patterns.
|
|
203
|
+
"""
|
|
204
|
+
for i in range(1, len(path_parts)):
|
|
205
|
+
ancestor = "/".join(path_parts[:i])
|
|
206
|
+
if (
|
|
207
|
+
ancestor in lines
|
|
208
|
+
or f"/{ancestor}" in lines
|
|
209
|
+
or f"{ancestor}/" in lines
|
|
210
|
+
or f"/{ancestor}/" in lines
|
|
211
|
+
or f"{ancestor}/*" in lines
|
|
212
|
+
or f"/{ancestor}/*" in lines
|
|
213
|
+
):
|
|
214
|
+
return True
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
if len(path_parts) == 1:
|
|
218
|
+
# Simple (non-nested) path: remove the direct ignore rule, or add a
|
|
219
|
+
# negation if the ignore comes from a glob or other pattern
|
|
220
|
+
direct_rule = next(
|
|
221
|
+
(rule for rule in direct_rule_variants if rule in lines), None
|
|
222
|
+
)
|
|
223
|
+
if direct_rule is not None:
|
|
224
|
+
lines.remove(direct_rule)
|
|
225
|
+
else:
|
|
226
|
+
# Remove any stale negation and re-append at the end so it takes
|
|
227
|
+
# precedence over any later re-ignore rule
|
|
228
|
+
if no_ignore_line in lines:
|
|
229
|
+
lines.remove(no_ignore_line)
|
|
230
|
+
lines.append(no_ignore_line)
|
|
231
|
+
else:
|
|
232
|
+
# Nested path: only apply recursive un-ignore rules when an ancestor
|
|
233
|
+
# directory is explicitly ignored
|
|
234
|
+
# Otherwise, remove a direct ignore
|
|
235
|
+
# rule for this path or add a simple negation if needed
|
|
236
|
+
removed_direct_rule = False
|
|
237
|
+
direct_rule = next(
|
|
238
|
+
(rule for rule in direct_rule_variants if rule in lines), None
|
|
239
|
+
)
|
|
240
|
+
if direct_rule is not None:
|
|
241
|
+
lines.remove(direct_rule)
|
|
242
|
+
removed_direct_rule = True
|
|
243
|
+
if ancestor_requires_recursive_unignore():
|
|
244
|
+
# Git will not traverse into a directory excluded by a "dir/"
|
|
245
|
+
# pattern, so a bare "!dir/sub/file" negation has no effect.
|
|
246
|
+
# We need to:
|
|
247
|
+
# 1. Convert any "ancestor/" (or "ancestor") exclude to
|
|
248
|
+
# "ancestor/*" so git traverses the directory while still
|
|
249
|
+
# ignoring direct children by default.
|
|
250
|
+
# 2. Add "!ancestor/" rules for intermediate directories.
|
|
251
|
+
# 3. Add "ancestor/*" re-ignore rules for each intermediate dir.
|
|
252
|
+
# 4. Add "!target_path" for the specific file.
|
|
253
|
+
for i in range(1, len(path_parts)):
|
|
254
|
+
ancestor = "/".join(path_parts[:i])
|
|
255
|
+
reignore_glob = f"{ancestor}/*"
|
|
256
|
+
if f"{ancestor}/" in lines:
|
|
257
|
+
idx = lines.index(f"{ancestor}/")
|
|
258
|
+
lines[idx] = reignore_glob
|
|
259
|
+
elif f"/{ancestor}/" in lines:
|
|
260
|
+
idx = lines.index(f"/{ancestor}/")
|
|
261
|
+
lines[idx] = f"/{ancestor}/*"
|
|
262
|
+
elif ancestor in lines:
|
|
263
|
+
idx = lines.index(ancestor)
|
|
264
|
+
lines[idx] = reignore_glob
|
|
265
|
+
elif f"/{ancestor}" in lines:
|
|
266
|
+
idx = lines.index(f"/{ancestor}")
|
|
267
|
+
lines[idx] = f"/{ancestor}/*"
|
|
268
|
+
no_ignore_dir = f"!{ancestor}/"
|
|
269
|
+
anchored_no_ignore_dir = f"!/{ancestor}/"
|
|
270
|
+
# The first ancestor does not need an explicit un-ignore once
|
|
271
|
+
# converted to "ancestor/*". Deeper ancestors do.
|
|
272
|
+
if i > 1:
|
|
273
|
+
# Remove stale entry and re-append so it takes precedence
|
|
274
|
+
if no_ignore_dir in lines:
|
|
275
|
+
lines.remove(no_ignore_dir)
|
|
276
|
+
elif anchored_no_ignore_dir in lines:
|
|
277
|
+
lines.remove(anchored_no_ignore_dir)
|
|
278
|
+
lines.append(no_ignore_dir)
|
|
279
|
+
if (
|
|
280
|
+
reignore_glob not in lines
|
|
281
|
+
and f"/{ancestor}/*" not in lines
|
|
282
|
+
):
|
|
283
|
+
lines.append(reignore_glob)
|
|
284
|
+
# Remove stale negation and re-append at the end so it takes
|
|
285
|
+
# precedence over any later re-ignore rule
|
|
286
|
+
if no_ignore_line in lines:
|
|
287
|
+
lines.remove(no_ignore_line)
|
|
288
|
+
lines.append(no_ignore_line)
|
|
289
|
+
elif not removed_direct_rule:
|
|
290
|
+
# The path may be ignored by a non-directory pattern (e.g., glob);
|
|
291
|
+
# remove stale negation and append at end so it takes precedence
|
|
292
|
+
if no_ignore_line in lines:
|
|
293
|
+
lines.remove(no_ignore_line)
|
|
294
|
+
lines.append(no_ignore_line)
|
|
295
|
+
with open(gitignore_path, "w") as f:
|
|
296
|
+
f.write(os.linesep.join(lines))
|
|
297
|
+
return True
|
|
@@ -317,6 +317,32 @@ class OverleafSyncPaths:
|
|
|
317
317
|
- self.files_in_overleaf_last_sync
|
|
318
318
|
)
|
|
319
319
|
|
|
320
|
+
@cached_property
|
|
321
|
+
def dvc_files(self) -> set[str]:
|
|
322
|
+
"""Files tracked by DVC within the Overleaf project folder.
|
|
323
|
+
|
|
324
|
+
These paths are relative to the project directory (i.e., relative to
|
|
325
|
+
the Overleaf repo root). Files tracked by DVC may not exist on disk if
|
|
326
|
+
they haven't been pulled, but should still be kept on Overleaf rather
|
|
327
|
+
than deleted.
|
|
328
|
+
"""
|
|
329
|
+
try:
|
|
330
|
+
import calkit.dvc
|
|
331
|
+
|
|
332
|
+
dvc_paths = calkit.dvc.list_paths(
|
|
333
|
+
wdir=str(self.main_repo.working_dir), recursive=True
|
|
334
|
+
)
|
|
335
|
+
except Exception as e:
|
|
336
|
+
warnings.warn(f"Could not list DVC files: {e}")
|
|
337
|
+
return set()
|
|
338
|
+
prefix = Path(self.path_in_project).as_posix().rstrip("/") + "/"
|
|
339
|
+
result = set()
|
|
340
|
+
for p in dvc_paths:
|
|
341
|
+
p_posix = Path(p).as_posix()
|
|
342
|
+
if p_posix.startswith(prefix):
|
|
343
|
+
result.add(p_posix[len(prefix) :])
|
|
344
|
+
return result
|
|
345
|
+
|
|
320
346
|
@cached_property
|
|
321
347
|
def files_to_keep_on_overleaf(self) -> set[str]:
|
|
322
348
|
"""Files that should be preserved on Overleaf.
|
|
@@ -324,9 +350,14 @@ class OverleafSyncPaths:
|
|
|
324
350
|
This includes:
|
|
325
351
|
1. All files being copied from local
|
|
326
352
|
2. Any files newly added on Overleaf since last sync
|
|
353
|
+
3. Any files tracked by DVC within the project path (these may not
|
|
354
|
+
exist on disk if not pulled, but should not be deleted from
|
|
355
|
+
Overleaf)
|
|
327
356
|
"""
|
|
328
357
|
return (
|
|
329
|
-
set(self.files_to_copy_to_overleaf)
|
|
358
|
+
set(self.files_to_copy_to_overleaf)
|
|
359
|
+
| self.newly_added_on_overleaf
|
|
360
|
+
| self.dvc_files
|
|
330
361
|
)
|
|
331
362
|
|
|
332
363
|
@cached_property
|
|
@@ -117,6 +117,20 @@ def to_dvc(
|
|
|
117
117
|
except Exception as e:
|
|
118
118
|
raise ValueError(f"Pipeline is not defined properly: {e}")
|
|
119
119
|
dvc_stages = {}
|
|
120
|
+
# Read existing dvc.yaml now so we can clean up stale .gitignore entries
|
|
121
|
+
# when stage outputs are renamed or removed
|
|
122
|
+
if write:
|
|
123
|
+
dvc_yaml_path = os.path.join(wdir, "dvc.yaml") if wdir else "dvc.yaml"
|
|
124
|
+
if os.path.isfile(dvc_yaml_path):
|
|
125
|
+
with open(dvc_yaml_path) as f:
|
|
126
|
+
existing_dvc_yaml = calkit.ryaml.load(f)
|
|
127
|
+
else:
|
|
128
|
+
existing_dvc_yaml = {}
|
|
129
|
+
if existing_dvc_yaml is None:
|
|
130
|
+
existing_dvc_yaml = {}
|
|
131
|
+
existing_dvc_stages = existing_dvc_yaml.get("stages", {})
|
|
132
|
+
else:
|
|
133
|
+
existing_dvc_stages = {}
|
|
120
134
|
# First, gather up any env lock paths we might need for DVC deps
|
|
121
135
|
used_envs = set(
|
|
122
136
|
[stage.inner_environment for stage in pipeline.stages.values()]
|
|
@@ -227,6 +241,17 @@ def to_dvc(
|
|
|
227
241
|
outputs += stage.notebook_outputs
|
|
228
242
|
elif stage.kind == "sbatch":
|
|
229
243
|
outputs.append(stage.log_output)
|
|
244
|
+
# Build the set of current DVC output paths so we can detect stale
|
|
245
|
+
# .gitignore entries from the previous version of the stage,
|
|
246
|
+
# including synthesized outputs like LaTeX PDFs
|
|
247
|
+
current_out_paths = set(calkit.dvc.out_paths_from_stage(dvc_stage))
|
|
248
|
+
# If this stage already existed, un-ignore any outputs that have
|
|
249
|
+
# been renamed or removed so .gitignore does not accumulate stale
|
|
250
|
+
# entries (e.g., after a capitalization change in the path)
|
|
251
|
+
old_stage = existing_dvc_stages.get(stage_name, {})
|
|
252
|
+
for old_path in calkit.dvc.out_paths_from_stage(old_stage):
|
|
253
|
+
if old_path not in current_out_paths:
|
|
254
|
+
calkit.git.ensure_path_is_not_ignored(repo, path=old_path)
|
|
230
255
|
# Deal with any gitignore changes necessary
|
|
231
256
|
for out in outputs:
|
|
232
257
|
if isinstance(out, PathOutput) and out.storage is None:
|
|
@@ -268,14 +293,8 @@ def to_dvc(
|
|
|
268
293
|
else:
|
|
269
294
|
dvc_stages[stage_name]["deps"].append(out)
|
|
270
295
|
if write:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
dvc_yaml = calkit.ryaml.load(f)
|
|
274
|
-
else:
|
|
275
|
-
dvc_yaml = {}
|
|
276
|
-
if dvc_yaml is None:
|
|
277
|
-
dvc_yaml = {}
|
|
278
|
-
existing_stages = dvc_yaml.get("stages", {})
|
|
296
|
+
dvc_yaml = existing_dvc_yaml
|
|
297
|
+
existing_stages = existing_dvc_stages
|
|
279
298
|
for stage_name, stage in existing_stages.items():
|
|
280
299
|
# Skip private stages (ones whose names start with an underscore)
|
|
281
300
|
# and stages that are automatically generated
|
|
@@ -181,6 +181,44 @@ def test_overleaf(tmp_dir):
|
|
|
181
181
|
subprocess.run(["calkit", "overleaf", "sync", "--verbose"], check=True)
|
|
182
182
|
print("Overleaf Git show after adding fig2 back:", ol_repo.git.show())
|
|
183
183
|
assert "ol-project/figs/fig2.txt" in ls_files(repo)
|
|
184
|
+
# Test that if a file is deleted from Git but added to DVC, it is not
|
|
185
|
+
# deleted from Overleaf (the file still logically exists in the DVC repo)
|
|
186
|
+
with open(
|
|
187
|
+
os.path.join(repo.working_dir, "ol-project", "figs", "fig3.txt"), "w"
|
|
188
|
+
) as f:
|
|
189
|
+
f.write("Fig3 created in main repo")
|
|
190
|
+
repo.git.add("ol-project/figs/fig3.txt")
|
|
191
|
+
repo.git.commit(["-m", "Add figure 3"])
|
|
192
|
+
assert "ol-project/figs/fig3.txt" in ls_files(repo)
|
|
193
|
+
subprocess.run(["calkit", "overleaf", "sync", "--verbose"], check=True)
|
|
194
|
+
ol_repo_git_show = ol_repo.git.show()
|
|
195
|
+
assert "diff --git a/figs/fig3.txt b/figs/fig3.txt" in ol_repo_git_show
|
|
196
|
+
# Now move from Git to DVC: first remove from Git index (keeping file on
|
|
197
|
+
# disk), then add to DVC so it gets moved to DVC cache
|
|
198
|
+
repo.git.rm(["--cached", "ol-project/figs/fig3.txt"])
|
|
199
|
+
subprocess.run(
|
|
200
|
+
["dvc", "add", "ol-project/figs/fig3.txt"],
|
|
201
|
+
check=True,
|
|
202
|
+
cwd=repo.working_dir,
|
|
203
|
+
)
|
|
204
|
+
# Commit the DVC pointer file (fig3.txt is now tracked by DVC, not Git)
|
|
205
|
+
repo.git.add("ol-project/figs/fig3.txt.dvc", "ol-project/figs/.gitignore")
|
|
206
|
+
repo.git.commit(["-m", "Move figure 3 from git to DVC"])
|
|
207
|
+
assert "ol-project/figs/fig3.txt" not in ls_files(repo)
|
|
208
|
+
# Also remove the local file to simulate the file not being pulled from
|
|
209
|
+
# DVC (i.e., only the DVC pointer exists locally, not the actual file)
|
|
210
|
+
fig3_path = os.path.join(
|
|
211
|
+
repo.working_dir, "ol-project", "figs", "fig3.txt"
|
|
212
|
+
)
|
|
213
|
+
if os.path.exists(fig3_path):
|
|
214
|
+
os.remove(fig3_path)
|
|
215
|
+
assert not os.path.exists(fig3_path)
|
|
216
|
+
subprocess.run(["calkit", "overleaf", "sync", "--verbose"], check=True)
|
|
217
|
+
ol_repo_git_show = ol_repo.git.show()
|
|
218
|
+
print("Git show in OL repo after moving fig3 to DVC:\n", ol_repo_git_show)
|
|
219
|
+
# The file should not have been deleted from Overleaf
|
|
220
|
+
assert "deleted file mode" not in ol_repo_git_show
|
|
221
|
+
assert "--- a/figs/fig3.txt" not in ol_repo_git_show
|
|
184
222
|
|
|
185
223
|
|
|
186
224
|
def test_extract_title_from_tex(tmp_dir):
|