calkit-python 0.21.8__tar.gz → 0.22.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {calkit_python-0.21.8 → calkit_python-0.22.0}/PKG-INFO +1 -1
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/__init__.py +1 -1
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/cli/config.py +30 -10
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/cli/core.py +2 -2
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/cli/main.py +15 -3
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/cli/new.py +18 -9
- calkit_python-0.22.0/calkit/cli/overleaf.py +451 -0
- calkit_python-0.22.0/calkit/config.py +217 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/core.py +1 -1
- calkit_python-0.22.0/calkit/tests/cli/test_config.py +99 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/tests/test_core.py +2 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/examples.md +10 -0
- calkit_python-0.22.0/docs/overleaf.md +84 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/mkdocs.yml +1 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/pyproject.toml +4 -0
- calkit_python-0.22.0/uv.lock +4859 -0
- calkit_python-0.21.8/calkit/config.py +0 -110
- calkit_python-0.21.8/uv.lock +0 -4688
- {calkit_python-0.21.8 → calkit_python-0.22.0}/.github/FUNDING.yml +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/.github/workflows/docs.yml +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/.github/workflows/format.yml +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/.github/workflows/publish-test.yml +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/.github/workflows/publish.yml +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/.github/workflows/test.yml +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/.gitignore +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/.pre-commit-config.yaml +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/CONTRIBUTING.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/LICENSE +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/Makefile +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/README.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/__main__.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/calc.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/check.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/cli/__init__.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/cli/check.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/cli/cloud.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/cli/import_.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/cli/list.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/cli/notebooks.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/cli/office.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/cli/update.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/cloud.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/conda.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/datasets.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/docker.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/dvc.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/git.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/github.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/gui.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/jupyter.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/magics.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/models.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/office.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/ops.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/releases.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/server.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/templates/__init__.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/templates/core.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/templates/latex/__init__.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/templates/latex/article/paper.tex +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/templates/latex/core.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/templates/latex/jfm/jfm.bst +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/templates/latex/jfm/jfm.cls +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/templates/latex/jfm/lineno-FLM.sty +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/templates/latex/jfm/paper.tex +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/templates/latex/jfm/upmath.sty +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/tests/__init__.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/tests/cli/__init__.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/tests/cli/test_list.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/tests/cli/test_main.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/tests/cli/test_new.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/tests/test_calc.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/tests/test_check.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/tests/test_conda.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/tests/test_dvc.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/tests/test_jupyter.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/tests/test_magics.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/tests/test_templates.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/calkit/zenodo.py +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/CNAME +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/apps.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/calculations.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/calkit-yaml.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/cli-reference.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/cloud-integration.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/datasets.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/environments.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/help.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/img/c-to-the-k-white.svg +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/img/calkit-no-bg.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/img/connect-zenodo.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/index.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/installation.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/local-server.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/pipeline/index.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/pipeline/manual-steps.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/references.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/releases.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/adding-latex-pub-docker.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/conda-envs.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/existing-project.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/first-project.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/latex-codespaces/building-codespace.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/latex-codespaces/codespaces-secrets-2.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/latex-codespaces/editor-split.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/latex-codespaces/go-to-linked-code.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/latex-codespaces/issue-from-selection.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/latex-codespaces/new-project.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/latex-codespaces/new-pub-2.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/latex-codespaces/new-token.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/latex-codespaces/paper.tex.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/latex-codespaces/project-home-3.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/latex-codespaces/push.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/latex-codespaces/stage.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/anakin-excel.jpg +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/chart-more-rows.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/create-project.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/elsevier-research-data-guidelines.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/excel-chart.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/excel-data.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/insert-link-to-file.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/needs-clone.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/new-stage.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/phd-comics-version-control.webp +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/pipeline-out-of-date.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/status-more-rows.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/uncommitted-changes.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/untracked-data.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/updated-publication.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/word-to-pdf-stage-2.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/office/workflow-page.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/openfoam/clone.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/openfoam/create-project.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/openfoam/datasets-page.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/openfoam/figure-on-website-updated.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/openfoam/figure-on-website.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/openfoam/new-token.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/openfoam/reclone.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/openfoam/status-after-import-dataset.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/img/run-proc.png +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/index.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/latex-codespaces.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/matlab.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/notebook-pipeline.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/office.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/openfoam.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/tutorials/procedures.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/docs/version-control.md +0 -0
- {calkit_python-0.21.8 → calkit_python-0.22.0}/test/pipeline.ipynb +0 -0
|
@@ -20,31 +20,51 @@ config_app = typer.Typer(no_args_is_help=True)
|
|
|
20
20
|
@config_app.command(name="set")
|
|
21
21
|
def set_config_value(key: str, value: str):
|
|
22
22
|
"""Set a value in the config."""
|
|
23
|
+
keys = config.Settings.model_fields.keys()
|
|
24
|
+
if key not in keys:
|
|
25
|
+
raise_error(
|
|
26
|
+
f"Invalid config key: '{key}'; Valid keys are: {list(keys)}"
|
|
27
|
+
)
|
|
23
28
|
try:
|
|
24
29
|
cfg = config.read()
|
|
25
30
|
cfg = config.Settings.model_validate(cfg.model_dump() | {key: value})
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
setattr(cfg, key, value)
|
|
29
|
-
except FileNotFoundError:
|
|
30
|
-
# TODO: This fails if we try to set password before any config has
|
|
31
|
-
# been written
|
|
32
|
-
# Username is fine
|
|
33
|
-
cfg = config.Settings.model_validate({key: value})
|
|
31
|
+
except Exception as e:
|
|
32
|
+
raise_error(f"Failed to set {key} in config: {e}")
|
|
34
33
|
cfg.write()
|
|
35
34
|
|
|
36
35
|
|
|
37
36
|
@config_app.command(name="get")
|
|
38
37
|
def get_config_value(key: str) -> None:
|
|
39
38
|
"""Get and print a value from the config."""
|
|
40
|
-
cfg = config.read()
|
|
41
|
-
|
|
39
|
+
cfg = config.read().model_dump()
|
|
40
|
+
if key not in cfg:
|
|
41
|
+
raise_error(
|
|
42
|
+
f"Invalid config key: '{key}'; Valid keys are: {list(cfg.keys())}"
|
|
43
|
+
)
|
|
44
|
+
val = cfg[key]
|
|
42
45
|
if val is not None:
|
|
43
46
|
print(val)
|
|
44
47
|
else:
|
|
45
48
|
print()
|
|
46
49
|
|
|
47
50
|
|
|
51
|
+
@config_app.command(name="unset")
|
|
52
|
+
def unset_config_value(key: str):
|
|
53
|
+
"""Unset a value in the config, returning it to default."""
|
|
54
|
+
model_fields = config.Settings.model_fields
|
|
55
|
+
if key not in model_fields:
|
|
56
|
+
raise_error(
|
|
57
|
+
f"Invalid config key: '{key}'; "
|
|
58
|
+
f"Valid keys: {list(model_fields.keys())}"
|
|
59
|
+
)
|
|
60
|
+
try:
|
|
61
|
+
cfg = config.read()
|
|
62
|
+
setattr(cfg, key, model_fields[key].default)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
raise_error(f"Failed to unset {key} in config: {e}")
|
|
65
|
+
cfg.write()
|
|
66
|
+
|
|
67
|
+
|
|
48
68
|
@config_app.command(name="setup-remote", help="Alias for 'remote'.")
|
|
49
69
|
@config_app.command(name="remote")
|
|
50
70
|
def setup_remote(
|
|
@@ -29,6 +29,7 @@ from calkit.cli.list import list_app
|
|
|
29
29
|
from calkit.cli.new import new_app
|
|
30
30
|
from calkit.cli.notebooks import notebooks_app
|
|
31
31
|
from calkit.cli.office import office_app
|
|
32
|
+
from calkit.cli.overleaf import overleaf_app
|
|
32
33
|
from calkit.cli.update import update_app
|
|
33
34
|
from calkit.models import Procedure
|
|
34
35
|
|
|
@@ -51,6 +52,7 @@ app.add_typer(import_app, name="import", help="Import objects.")
|
|
|
51
52
|
app.add_typer(office_app, name="office", help="Work with Microsoft Office.")
|
|
52
53
|
app.add_typer(update_app, name="update", help="Update objects.")
|
|
53
54
|
app.add_typer(check_app, name="check", help="Check things.")
|
|
55
|
+
app.add_typer(overleaf_app, name="overleaf", help="Interact with Overleaf.")
|
|
54
56
|
app.add_typer(cloud_app, name="cloud", help="Interact with a Calkit Cloud.")
|
|
55
57
|
|
|
56
58
|
# Constants for version control auto-ignore
|
|
@@ -252,10 +254,20 @@ def get_status():
|
|
|
252
254
|
|
|
253
255
|
|
|
254
256
|
@app.command(name="diff")
|
|
255
|
-
def diff(
|
|
257
|
+
def diff(
|
|
258
|
+
staged: Annotated[
|
|
259
|
+
bool,
|
|
260
|
+
typer.Option(
|
|
261
|
+
"--staged", help="Show a diff from files staged with Git."
|
|
262
|
+
),
|
|
263
|
+
] = False,
|
|
264
|
+
):
|
|
256
265
|
"""Get a unified Git and DVC diff."""
|
|
257
266
|
print_sep("Code (Git)")
|
|
258
|
-
|
|
267
|
+
git_cmd = ["git", "diff"]
|
|
268
|
+
if staged:
|
|
269
|
+
git_cmd.append("--staged")
|
|
270
|
+
run_cmd(git_cmd)
|
|
259
271
|
print_sep("Pipeline (DVC)")
|
|
260
272
|
run_cmd([sys.executable, "-m", "dvc", "diff"])
|
|
261
273
|
|
|
@@ -622,7 +634,7 @@ def ignore(
|
|
|
622
634
|
repo = git.Repo()
|
|
623
635
|
if repo.ignored(path):
|
|
624
636
|
typer.echo(f"{path} is already ignored")
|
|
625
|
-
|
|
637
|
+
return
|
|
626
638
|
typer.echo(f"Adding '{path}' to .gitignore")
|
|
627
639
|
txt = "\n" + path + "\n"
|
|
628
640
|
with open(".gitignore", "a") as f:
|
|
@@ -16,7 +16,7 @@ import dotenv
|
|
|
16
16
|
import git
|
|
17
17
|
import requests
|
|
18
18
|
import typer
|
|
19
|
-
from git.exc import GitCommandError, InvalidGitRepositoryError
|
|
19
|
+
from git.exc import GitCommandError, InvalidGitRepositoryError, NoSuchPathError
|
|
20
20
|
from typing_extensions import Annotated
|
|
21
21
|
|
|
22
22
|
import calkit
|
|
@@ -107,7 +107,7 @@ def new_project(
|
|
|
107
107
|
raise_error("Must specify a new directory if using --template")
|
|
108
108
|
try:
|
|
109
109
|
repo = git.Repo(abs_path)
|
|
110
|
-
except InvalidGitRepositoryError:
|
|
110
|
+
except (InvalidGitRepositoryError, NoSuchPathError):
|
|
111
111
|
repo = None
|
|
112
112
|
if repo is not None and git_repo_url is None:
|
|
113
113
|
try:
|
|
@@ -1462,24 +1462,31 @@ def new_stage(
|
|
|
1462
1462
|
help="Overwrite an existing stage with this name if necessary.",
|
|
1463
1463
|
),
|
|
1464
1464
|
] = False,
|
|
1465
|
+
no_check: Annotated[
|
|
1466
|
+
bool,
|
|
1467
|
+
typer.Option(
|
|
1468
|
+
"--no-check",
|
|
1469
|
+
help="Do not check if the target, deps, environment, etc., exist.",
|
|
1470
|
+
),
|
|
1471
|
+
] = False,
|
|
1465
1472
|
no_commit: Annotated[
|
|
1466
1473
|
bool, typer.Option("--no-commit", help="Do not commit changes to Git.")
|
|
1467
1474
|
] = False,
|
|
1468
1475
|
):
|
|
1469
1476
|
"""Create a new pipeline stage."""
|
|
1470
|
-
ck_info = calkit.load_calkit_info()
|
|
1477
|
+
ck_info = calkit.load_calkit_info(process_includes="environments")
|
|
1471
1478
|
if environment is None:
|
|
1472
1479
|
warn("No environment is specified")
|
|
1473
1480
|
cmd = ""
|
|
1474
1481
|
else:
|
|
1475
|
-
if environment not in ck_info["environments"]:
|
|
1482
|
+
if environment not in ck_info["environments"] and not no_check:
|
|
1476
1483
|
raise_error(f"Environment '{environment}' does not exist")
|
|
1477
1484
|
cmd = f"calkit xenv -n {environment} -- "
|
|
1478
|
-
# Add environment path as a dependency
|
|
1479
|
-
env_path = ck_info["environments"]
|
|
1480
|
-
if env_path is not None:
|
|
1485
|
+
# Add environment path as a dependency if applicable
|
|
1486
|
+
env_path = ck_info["environments"].get(environment, {}).get("path")
|
|
1487
|
+
if env_path is not None and env_path not in deps:
|
|
1481
1488
|
deps = [env_path] + deps
|
|
1482
|
-
if not os.path.exists(target):
|
|
1489
|
+
if not os.path.exists(target) and not no_check:
|
|
1483
1490
|
raise_error(f"Target '{target}' does not exist")
|
|
1484
1491
|
if kind.value == "python-script":
|
|
1485
1492
|
cmd += f"python {target}"
|
|
@@ -1501,7 +1508,9 @@ def new_stage(
|
|
|
1501
1508
|
elif kind.value == "r-script":
|
|
1502
1509
|
cmd += f"Rscript {target}"
|
|
1503
1510
|
add_cmd = [sys.executable, "-m", "dvc", "stage", "add", "-n", name]
|
|
1504
|
-
|
|
1511
|
+
if target not in deps:
|
|
1512
|
+
deps = [target] + deps
|
|
1513
|
+
for dep in deps:
|
|
1505
1514
|
add_cmd += ["-d", dep]
|
|
1506
1515
|
for out in outs:
|
|
1507
1516
|
add_cmd += ["-o", out]
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
"""CLI for working with Overleaf."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
|
|
9
|
+
import git
|
|
10
|
+
import typer
|
|
11
|
+
from typing_extensions import Annotated
|
|
12
|
+
|
|
13
|
+
import calkit
|
|
14
|
+
from calkit.cli import raise_error, warn
|
|
15
|
+
|
|
16
|
+
overleaf_app = typer.Typer(no_args_is_help=True)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@overleaf_app.command(name="import")
|
|
20
|
+
def import_publication(
|
|
21
|
+
src_url: Annotated[
|
|
22
|
+
str,
|
|
23
|
+
typer.Argument(
|
|
24
|
+
help=(
|
|
25
|
+
"Overleaf project URL, e.g., "
|
|
26
|
+
"https://www.overleaf.com/project/6800005973cb2e35."
|
|
27
|
+
)
|
|
28
|
+
),
|
|
29
|
+
],
|
|
30
|
+
dest_dir: Annotated[
|
|
31
|
+
str,
|
|
32
|
+
typer.Argument(
|
|
33
|
+
help="Directory at which to save in the project, e.g., 'paper'."
|
|
34
|
+
),
|
|
35
|
+
],
|
|
36
|
+
sync_paths: Annotated[
|
|
37
|
+
list[str],
|
|
38
|
+
typer.Option(
|
|
39
|
+
"--sync-path",
|
|
40
|
+
"-s",
|
|
41
|
+
help=(
|
|
42
|
+
"Paths to sync from the Overleaf project, e.g., 'main.tex'. "
|
|
43
|
+
"Note that multiple can be specified."
|
|
44
|
+
),
|
|
45
|
+
),
|
|
46
|
+
],
|
|
47
|
+
title: Annotated[
|
|
48
|
+
str,
|
|
49
|
+
typer.Option(
|
|
50
|
+
"--title",
|
|
51
|
+
"-t",
|
|
52
|
+
help="Title of the publication.",
|
|
53
|
+
),
|
|
54
|
+
],
|
|
55
|
+
description: Annotated[
|
|
56
|
+
str,
|
|
57
|
+
typer.Option(
|
|
58
|
+
"--description",
|
|
59
|
+
"-d",
|
|
60
|
+
help="Description of the publication.",
|
|
61
|
+
),
|
|
62
|
+
] = None,
|
|
63
|
+
kind: Annotated[
|
|
64
|
+
str,
|
|
65
|
+
typer.Option(
|
|
66
|
+
"--kind",
|
|
67
|
+
help="What of the publication this is, e.g., 'journal-article'.",
|
|
68
|
+
),
|
|
69
|
+
] = None,
|
|
70
|
+
push_paths: Annotated[
|
|
71
|
+
list[str],
|
|
72
|
+
typer.Option(
|
|
73
|
+
"--push-path",
|
|
74
|
+
"-p",
|
|
75
|
+
help=(
|
|
76
|
+
"Paths to push to the Overleaf project, e.g., 'figures'. "
|
|
77
|
+
"Note that these are relative to the publication working "
|
|
78
|
+
"directory."
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
] = [],
|
|
82
|
+
pdf_path: Annotated[
|
|
83
|
+
str,
|
|
84
|
+
typer.Option(
|
|
85
|
+
"--pdf-path",
|
|
86
|
+
"-o",
|
|
87
|
+
help=(
|
|
88
|
+
"PDF output file in the Overleaf project, e.g., 'main.pdf'. "
|
|
89
|
+
"If not provided, it will be determined from the first sync "
|
|
90
|
+
"path."
|
|
91
|
+
),
|
|
92
|
+
),
|
|
93
|
+
] = None,
|
|
94
|
+
no_commit: Annotated[
|
|
95
|
+
bool,
|
|
96
|
+
typer.Option("--no-commit", help="Do not commit changes to repo."),
|
|
97
|
+
] = False,
|
|
98
|
+
overwrite: Annotated[
|
|
99
|
+
bool,
|
|
100
|
+
typer.Option(
|
|
101
|
+
"--overwrite",
|
|
102
|
+
"-f",
|
|
103
|
+
help="Force adding the publication even if it already exists.",
|
|
104
|
+
),
|
|
105
|
+
] = False,
|
|
106
|
+
):
|
|
107
|
+
"""Import a publication from an Overleaf project."""
|
|
108
|
+
from calkit.cli.main import ignore as git_ignore
|
|
109
|
+
from calkit.cli.new import StageKind, new_stage
|
|
110
|
+
|
|
111
|
+
# First check that the user has an Overleaf token set
|
|
112
|
+
config = calkit.config.read()
|
|
113
|
+
overleaf_token = config.overleaf_token
|
|
114
|
+
if not overleaf_token:
|
|
115
|
+
warn("Overleaf token not set in config", prefix="")
|
|
116
|
+
typer.echo(
|
|
117
|
+
"One can be generated at:\n\n"
|
|
118
|
+
" https://www.overleaf.com/user/settings\n\n"
|
|
119
|
+
"under the 'Git Integration' section.\n"
|
|
120
|
+
)
|
|
121
|
+
overleaf_token = typer.prompt(
|
|
122
|
+
"Enter Overleaf Git authentication token", hide_input=True
|
|
123
|
+
)
|
|
124
|
+
typer.echo("Storing Overleaf token in Calkit config")
|
|
125
|
+
config.overleaf_token = overleaf_token
|
|
126
|
+
config.write()
|
|
127
|
+
if not src_url.startswith("https://www.overleaf.com/project/"):
|
|
128
|
+
raise_error(
|
|
129
|
+
"Invalid URL; must start with 'https://www.overleaf.com/project/'"
|
|
130
|
+
)
|
|
131
|
+
overleaf_project_id = src_url.split("/")[-1]
|
|
132
|
+
if not overleaf_project_id:
|
|
133
|
+
raise_error("Invalid Overleaf project ID")
|
|
134
|
+
ck_info = calkit.load_calkit_info(process_includes="environments")
|
|
135
|
+
pubs = ck_info.get("publications", [])
|
|
136
|
+
# TODO: Don't allow the same Overleaf project ID in multiple publications
|
|
137
|
+
# Determine the PDF output path
|
|
138
|
+
if pdf_path is None:
|
|
139
|
+
# Use the first sync path as the PDF path
|
|
140
|
+
pdf_path = sync_paths[0].removesuffix(".tex") + ".pdf"
|
|
141
|
+
typer.echo(f"Using PDF path: {pdf_path}")
|
|
142
|
+
tex_path = pdf_path.removesuffix(".pdf") + ".tex"
|
|
143
|
+
pub_path = os.path.join(dest_dir, pdf_path)
|
|
144
|
+
pub_paths = [pub.get("path") for pub in pubs]
|
|
145
|
+
if not overwrite and pub_path in pub_paths:
|
|
146
|
+
raise_error(
|
|
147
|
+
f"A publication already exists in this project at {pub_path}"
|
|
148
|
+
)
|
|
149
|
+
elif overwrite and pub_path in pub_paths:
|
|
150
|
+
# Note: This publication will go to the end of the list
|
|
151
|
+
pubs = [p for p in pubs if p.get("path") != pub_path]
|
|
152
|
+
repo = git.Repo()
|
|
153
|
+
# Clone the Overleaf project into .calkit/overleaf if it doesn't exist
|
|
154
|
+
# otherwise pull
|
|
155
|
+
overleaf_dir = os.path.join(".calkit", "overleaf")
|
|
156
|
+
os.makedirs(overleaf_dir, exist_ok=True)
|
|
157
|
+
git_ignore(overleaf_dir, no_commit=no_commit)
|
|
158
|
+
overleaf_project_dir = os.path.join(overleaf_dir, overleaf_project_id)
|
|
159
|
+
git_clone_url = (
|
|
160
|
+
f"https://git:{overleaf_token}@git.overleaf.com/{overleaf_project_id}"
|
|
161
|
+
)
|
|
162
|
+
if os.path.isdir(overleaf_project_dir):
|
|
163
|
+
warn("This Overleaf project has already been cloned; removing")
|
|
164
|
+
shutil.rmtree(overleaf_project_dir)
|
|
165
|
+
# Clone the Overleaf project
|
|
166
|
+
typer.echo("Cloning Overleaf project")
|
|
167
|
+
git.Repo.clone_from(
|
|
168
|
+
git_clone_url,
|
|
169
|
+
overleaf_project_dir,
|
|
170
|
+
depth=1,
|
|
171
|
+
)
|
|
172
|
+
# Check that we have a LaTeX environment
|
|
173
|
+
typer.echo("Checking that this project has a LaTeX environment")
|
|
174
|
+
envs = ck_info.get("environments", {})
|
|
175
|
+
tex_env_name = None
|
|
176
|
+
for name, env in envs.items():
|
|
177
|
+
if env.get("kind") == "docker" and "texlive" in env.get("image", ""):
|
|
178
|
+
tex_env_name = name
|
|
179
|
+
break
|
|
180
|
+
if tex_env_name is None:
|
|
181
|
+
typer.echo("Creating TeXlive Docker environment")
|
|
182
|
+
tex_env_name = "tex"
|
|
183
|
+
n = 1
|
|
184
|
+
while tex_env_name in envs:
|
|
185
|
+
tex_env_name = f"tex-{n}"
|
|
186
|
+
n += 1
|
|
187
|
+
envs[tex_env_name] = dict(
|
|
188
|
+
kind="docker",
|
|
189
|
+
image="texlive/texlive:latest-full",
|
|
190
|
+
description="TeXlive via Docker.",
|
|
191
|
+
)
|
|
192
|
+
ck_info["environments"] = envs
|
|
193
|
+
# Check that we have a build stage
|
|
194
|
+
typer.echo("Checking for a build stage in the pipeline")
|
|
195
|
+
stage_name = None
|
|
196
|
+
if os.path.isfile("dvc.yaml"):
|
|
197
|
+
with open("dvc.yaml", "r") as f:
|
|
198
|
+
dvc_info = calkit.ryaml.load(f)
|
|
199
|
+
stages = dvc_info.get("stages", {})
|
|
200
|
+
for stage_name_i, stage in stages.items():
|
|
201
|
+
if pub_path in stage.get("outs", []):
|
|
202
|
+
stage_name = stage_name_i
|
|
203
|
+
typer.echo(f"Found build stage '{stage_name}' in pipeline")
|
|
204
|
+
break
|
|
205
|
+
else:
|
|
206
|
+
stages = {}
|
|
207
|
+
if stage_name is None:
|
|
208
|
+
# Create a new stage
|
|
209
|
+
stage_name = calkit.to_kebab_case("build-" + dest_dir)
|
|
210
|
+
n = 1
|
|
211
|
+
while stage_name in stages:
|
|
212
|
+
stage_name = f"{stage_name}-{n}"
|
|
213
|
+
n += 1
|
|
214
|
+
typer.echo(f"Creating build stage '{stage_name}'")
|
|
215
|
+
new_stage(
|
|
216
|
+
name=stage_name,
|
|
217
|
+
environment=tex_env_name,
|
|
218
|
+
kind=StageKind.latex,
|
|
219
|
+
target=os.path.join(dest_dir, tex_path),
|
|
220
|
+
outs=[pub_path],
|
|
221
|
+
deps=[os.path.join(dest_dir, p) for p in sync_paths + push_paths],
|
|
222
|
+
no_check=True,
|
|
223
|
+
no_commit=True,
|
|
224
|
+
)
|
|
225
|
+
repo.git.add("dvc.yaml")
|
|
226
|
+
# Add to publications in calkit.yaml
|
|
227
|
+
typer.echo("Adding publication to calkit.yaml")
|
|
228
|
+
new_pub = dict(
|
|
229
|
+
path=pub_path,
|
|
230
|
+
title=title,
|
|
231
|
+
description=description,
|
|
232
|
+
kind=kind,
|
|
233
|
+
stage=stage_name,
|
|
234
|
+
overleaf=dict(
|
|
235
|
+
project_id=overleaf_project_id,
|
|
236
|
+
wdir=dest_dir,
|
|
237
|
+
sync_paths=sync_paths,
|
|
238
|
+
push_paths=push_paths,
|
|
239
|
+
last_sync_commit=None,
|
|
240
|
+
),
|
|
241
|
+
)
|
|
242
|
+
pubs.append(new_pub)
|
|
243
|
+
ck_info["publications"] = pubs
|
|
244
|
+
with open("calkit.yaml", "w") as f:
|
|
245
|
+
calkit.ryaml.dump(ck_info, f)
|
|
246
|
+
repo.git.add("calkit.yaml")
|
|
247
|
+
if not no_commit:
|
|
248
|
+
# Commit any necessary changes
|
|
249
|
+
typer.echo("Committing changes")
|
|
250
|
+
repo.git.commit(
|
|
251
|
+
["-m", f"Import Overleaf project ID {overleaf_project_id}"]
|
|
252
|
+
)
|
|
253
|
+
# Sync the project
|
|
254
|
+
sync(paths=[pub_path], no_commit=no_commit)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@overleaf_app.command(name="sync")
|
|
258
|
+
def sync(
|
|
259
|
+
paths: Annotated[
|
|
260
|
+
list[str],
|
|
261
|
+
typer.Argument(
|
|
262
|
+
help=(
|
|
263
|
+
"Paths to sync with Overleaf, e.g., 'paper/paper.pdf'. "
|
|
264
|
+
"If not provided, all Overleaf publications will be synced."
|
|
265
|
+
),
|
|
266
|
+
),
|
|
267
|
+
] = None,
|
|
268
|
+
no_commit: Annotated[
|
|
269
|
+
bool,
|
|
270
|
+
typer.Option(
|
|
271
|
+
"--no-commit",
|
|
272
|
+
help="Do not commit the changes.",
|
|
273
|
+
),
|
|
274
|
+
] = False,
|
|
275
|
+
verbose: Annotated[
|
|
276
|
+
bool,
|
|
277
|
+
typer.Option(
|
|
278
|
+
"--verbose",
|
|
279
|
+
help="Enable verbose output.",
|
|
280
|
+
),
|
|
281
|
+
] = False,
|
|
282
|
+
):
|
|
283
|
+
"""Sync publications with Overleaf."""
|
|
284
|
+
# TODO: We should probably ensure the pipeline isn't stale
|
|
285
|
+
# Find all publications with Overleaf projects linked
|
|
286
|
+
ck_info = calkit.load_calkit_info()
|
|
287
|
+
pubs = ck_info.get("publications", [])
|
|
288
|
+
if paths is not None:
|
|
289
|
+
for path in paths:
|
|
290
|
+
if not any(pub.get("path") == path for pub in pubs):
|
|
291
|
+
raise_error(f"Publication with path '{path}' not found")
|
|
292
|
+
repo = git.Repo()
|
|
293
|
+
for pub in pubs:
|
|
294
|
+
overleaf_config = pub.get("overleaf", {})
|
|
295
|
+
if not overleaf_config:
|
|
296
|
+
continue
|
|
297
|
+
if paths is not None and pub.get("path") not in paths:
|
|
298
|
+
continue
|
|
299
|
+
overleaf_project_id = overleaf_config.get("project_id")
|
|
300
|
+
if not overleaf_project_id:
|
|
301
|
+
raise_error(
|
|
302
|
+
"No Overleaf project ID defined for this publication; "
|
|
303
|
+
"please set it in the publication's Overleaf config"
|
|
304
|
+
)
|
|
305
|
+
typer.echo(
|
|
306
|
+
f"Syncing {pub['path']} with "
|
|
307
|
+
f"Overleaf project ID {overleaf_project_id}"
|
|
308
|
+
)
|
|
309
|
+
wdir = pub["overleaf"].get("wdir")
|
|
310
|
+
if wdir is None:
|
|
311
|
+
raise_error(
|
|
312
|
+
"No working directory defined for this publication; "
|
|
313
|
+
"please set it in the publication's Overleaf config"
|
|
314
|
+
)
|
|
315
|
+
# Ensure we've cloned the Overleaf project
|
|
316
|
+
overleaf_project_dir = os.path.join(
|
|
317
|
+
".calkit", "overleaf", overleaf_project_id
|
|
318
|
+
)
|
|
319
|
+
if not os.path.isdir(overleaf_project_dir):
|
|
320
|
+
calkit_config = calkit.config.read()
|
|
321
|
+
overleaf_token = calkit_config.overleaf_token
|
|
322
|
+
if not overleaf_token:
|
|
323
|
+
raise_error(
|
|
324
|
+
"Overleaf token not set; "
|
|
325
|
+
"Please set it using 'calkit config set overleaf_token'"
|
|
326
|
+
)
|
|
327
|
+
overleaf_clone_url = (
|
|
328
|
+
f"https://git:{overleaf_token}@git.overleaf.com/"
|
|
329
|
+
f"{overleaf_project_id}"
|
|
330
|
+
)
|
|
331
|
+
overleaf_repo = git.Repo.clone_from(
|
|
332
|
+
overleaf_clone_url, to_path=overleaf_project_dir
|
|
333
|
+
)
|
|
334
|
+
else:
|
|
335
|
+
overleaf_repo = git.Repo(overleaf_project_dir)
|
|
336
|
+
# Pull the latest version in the Overleaf project
|
|
337
|
+
typer.echo("Pulling the latest version from Overleaf")
|
|
338
|
+
overleaf_repo.git.pull()
|
|
339
|
+
last_sync_commit = pub["overleaf"].get("last_sync_commit")
|
|
340
|
+
# Determine which paths to sync and push
|
|
341
|
+
# TODO: Support glob patterns
|
|
342
|
+
sync_paths = pub["overleaf"].get("sync_paths", [])
|
|
343
|
+
push_paths = pub["overleaf"].get("push_paths", [])
|
|
344
|
+
sync_paths_in_project = [os.path.join(wdir, p) for p in sync_paths]
|
|
345
|
+
if not sync_paths:
|
|
346
|
+
warn("No sync paths defined in the publication's Overleaf config")
|
|
347
|
+
elif last_sync_commit:
|
|
348
|
+
# Compute a diff in the Overleaf project between HEAD and the last
|
|
349
|
+
# sync
|
|
350
|
+
diff = overleaf_repo.git.diff(
|
|
351
|
+
[last_sync_commit, "HEAD", "--"] + sync_paths
|
|
352
|
+
)
|
|
353
|
+
# Ensure the diff ends with a new line
|
|
354
|
+
if diff and not diff.endswith("\n"):
|
|
355
|
+
diff += "\n"
|
|
356
|
+
if verbose:
|
|
357
|
+
typer.echo(f"Git diff:\n{diff}")
|
|
358
|
+
if diff:
|
|
359
|
+
typer.echo("Applying to project repo")
|
|
360
|
+
process = subprocess.run(
|
|
361
|
+
["git", "apply", "--directory", wdir, "-"],
|
|
362
|
+
input=diff,
|
|
363
|
+
text=True,
|
|
364
|
+
)
|
|
365
|
+
if process.returncode != 0:
|
|
366
|
+
raise_error("Failed to apply diff")
|
|
367
|
+
else:
|
|
368
|
+
typer.echo("No changes to apply")
|
|
369
|
+
else:
|
|
370
|
+
# Simply copy in all files
|
|
371
|
+
typer.echo(
|
|
372
|
+
"No last sync commit defined; "
|
|
373
|
+
"copying all files from Overleaf project"
|
|
374
|
+
)
|
|
375
|
+
for sync_path in sync_paths:
|
|
376
|
+
src = os.path.join(overleaf_project_dir, sync_path)
|
|
377
|
+
dst = os.path.join(wdir, sync_path)
|
|
378
|
+
if os.path.isdir(src):
|
|
379
|
+
# Copy the directory and its contents
|
|
380
|
+
shutil.copytree(src, dst, dirs_exist_ok=True)
|
|
381
|
+
elif os.path.isfile(src):
|
|
382
|
+
# Copy the file
|
|
383
|
+
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
|
384
|
+
shutil.copy2(src, dst)
|
|
385
|
+
else:
|
|
386
|
+
raise_error(
|
|
387
|
+
f"Source path {src} does not exist; "
|
|
388
|
+
"please check your Overleaf config"
|
|
389
|
+
)
|
|
390
|
+
# Copy our versions of sync and push paths into the Overleaf project
|
|
391
|
+
for sync_push_path in sync_paths + push_paths:
|
|
392
|
+
src = os.path.join(wdir, sync_push_path)
|
|
393
|
+
dst = os.path.join(overleaf_project_dir, sync_push_path)
|
|
394
|
+
if os.path.isdir(src):
|
|
395
|
+
# Remove destination directory if it exists
|
|
396
|
+
if os.path.isdir(dst):
|
|
397
|
+
shutil.rmtree(dst)
|
|
398
|
+
# Copy the directory and its contents
|
|
399
|
+
shutil.copytree(src, dst, dirs_exist_ok=True)
|
|
400
|
+
elif os.path.isfile(src):
|
|
401
|
+
# Copy the file
|
|
402
|
+
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
|
403
|
+
shutil.copy2(src, dst)
|
|
404
|
+
else:
|
|
405
|
+
raise_error(
|
|
406
|
+
f"Source path {src} does not exist; "
|
|
407
|
+
"please check your Overleaf config"
|
|
408
|
+
)
|
|
409
|
+
continue
|
|
410
|
+
# Stage the changes in the Overleaf project
|
|
411
|
+
overleaf_repo.git.add(sync_paths + push_paths)
|
|
412
|
+
if (
|
|
413
|
+
overleaf_repo.git.diff("--staged", sync_paths + push_paths)
|
|
414
|
+
and not no_commit
|
|
415
|
+
):
|
|
416
|
+
commit_message = "Sync with Calkit project"
|
|
417
|
+
overleaf_repo.git.commit(
|
|
418
|
+
*(sync_paths + push_paths),
|
|
419
|
+
"-m",
|
|
420
|
+
commit_message,
|
|
421
|
+
)
|
|
422
|
+
# TODO: We should probably always push and pull to we can
|
|
423
|
+
# idempotently run this command
|
|
424
|
+
typer.echo("Pushing changes to Overleaf")
|
|
425
|
+
overleaf_repo.git.push()
|
|
426
|
+
# Update the last sync commit
|
|
427
|
+
last_overleaf_commit = overleaf_repo.head.commit.hexsha
|
|
428
|
+
typer.echo(f"Updating last sync commit as {last_overleaf_commit}")
|
|
429
|
+
pub["overleaf"]["last_sync_commit"] = last_overleaf_commit
|
|
430
|
+
# Write publications back to calkit.yaml
|
|
431
|
+
ck_info["publications"] = pubs
|
|
432
|
+
with open("calkit.yaml", "w") as f:
|
|
433
|
+
calkit.ryaml.dump(ck_info, f)
|
|
434
|
+
repo.git.add("calkit.yaml")
|
|
435
|
+
# Stage the changes in the project repo
|
|
436
|
+
repo.git.add(sync_paths_in_project)
|
|
437
|
+
if (
|
|
438
|
+
repo.git.diff("--staged", sync_paths_in_project + ["calkit.yaml"])
|
|
439
|
+
and not no_commit
|
|
440
|
+
):
|
|
441
|
+
typer.echo("Committing changes to project repo")
|
|
442
|
+
commit_message = f"Sync {wdir} with Overleaf project"
|
|
443
|
+
repo.git.commit(
|
|
444
|
+
*(sync_paths_in_project + ["calkit.yaml"]),
|
|
445
|
+
"-m",
|
|
446
|
+
commit_message,
|
|
447
|
+
)
|
|
448
|
+
# Push to the project remote
|
|
449
|
+
typer.echo("Pushing changes to project Git remote")
|
|
450
|
+
repo.git.push()
|
|
451
|
+
# TODO: Add option to run the pipeline after?
|