calkit-python 0.41.14__py3-none-any.whl → 0.41.16__py3-none-any.whl

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 (37) hide show
  1. calkit/cli/check.py +5 -2
  2. calkit/cli/config.py +53 -6
  3. calkit/cli/main/core.py +14 -1
  4. calkit/cli/new.py +1 -1
  5. calkit/cli/notebooks.py +17 -10
  6. calkit/cli/update.py +1 -1
  7. calkit/core.py +7 -3
  8. calkit/detect.py +64 -0
  9. calkit/environments.py +18 -2
  10. calkit/julia.py +29 -2
  11. calkit/notebooks.py +1 -3
  12. calkit/pipeline.py +32 -8
  13. calkit/tests/jupyterlab/conftest.py +7 -0
  14. calkit/tests/test_core.py +15 -0
  15. calkit/tests/test_detect.py +49 -0
  16. calkit/tests/test_julia.py +46 -0
  17. calkit/tests/test_pipeline.py +49 -0
  18. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/share/jupyter/labextensions/calkit/package.json +3 -23
  19. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/share/jupyter/labextensions/calkit/schemas/calkit/package.json.orig +2 -22
  20. calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/static/remoteEntry.65469af996e7a96aa983.js → calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/static/remoteEntry.ac9035764d5b2adbb542.js +1 -1
  21. {calkit_python-0.41.14.dist-info → calkit_python-0.41.16.dist-info}/METADATA +1 -1
  22. {calkit_python-0.41.14.dist-info → calkit_python-0.41.16.dist-info}/RECORD +37 -36
  23. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/etc/jupyter/jupyter_server_config.d/calkit.json +0 -0
  24. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/share/jupyter/labextensions/calkit/install.json +0 -0
  25. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/share/jupyter/labextensions/calkit/schemas/calkit/plugin.json +0 -0
  26. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/share/jupyter/labextensions/calkit/static/502.9a2c5772a15466e923ef.js +0 -0
  27. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/share/jupyter/labextensions/calkit/static/695.2c41003a452d43d2b358.js +0 -0
  28. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/share/jupyter/labextensions/calkit/static/867.a42a046aa5108f54f8fb.js +0 -0
  29. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/share/jupyter/labextensions/calkit/static/909.e3f9cc3408834a7fdcc3.js +0 -0
  30. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/share/jupyter/labextensions/calkit/static/946.050af2abf7845cfbdbd2.js +0 -0
  31. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/share/jupyter/labextensions/calkit/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt +0 -0
  32. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/share/jupyter/labextensions/calkit/static/b2f1c3efe70cb539d121.png +0 -0
  33. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/share/jupyter/labextensions/calkit/static/style.js +0 -0
  34. {calkit_python-0.41.14.data → calkit_python-0.41.16.data}/data/share/jupyter/labextensions/calkit/static/third-party-licenses.json +0 -0
  35. {calkit_python-0.41.14.dist-info → calkit_python-0.41.16.dist-info}/WHEEL +0 -0
  36. {calkit_python-0.41.14.dist-info → calkit_python-0.41.16.dist-info}/entry_points.txt +0 -0
  37. {calkit_python-0.41.14.dist-info → calkit_python-0.41.16.dist-info}/licenses/LICENSE +0 -0
calkit/cli/check.py CHANGED
@@ -180,7 +180,8 @@ def _check_julia_env(
180
180
  try:
181
181
  subprocess.check_call(
182
182
  cmd,
183
- env=os.environ.copy() | {"JULIA_LOAD_PATH": "@:@stdlib"},
183
+ env=os.environ.copy()
184
+ | {"JULIA_LOAD_PATH": calkit.julia.load_path()},
184
185
  )
185
186
  except subprocess.CalledProcessError:
186
187
  calkit.environments.save_cache(
@@ -206,7 +207,9 @@ def _check_julia_env(
206
207
  cmd = calkit.julia.ensure_startup_file_disabled_in_command(cmd)
207
208
  try:
208
209
  subprocess.check_call(
209
- cmd, env=os.environ.copy() | {"JULIA_LOAD_PATH": "@:@stdlib"}
210
+ cmd,
211
+ env=os.environ.copy()
212
+ | {"JULIA_LOAD_PATH": calkit.julia.load_path()},
210
213
  )
211
214
  except subprocess.CalledProcessError:
212
215
  calkit.environments.save_cache(
calkit/cli/config.py CHANGED
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  import glob
6
6
  import os
7
+ import platform
8
+ import re
7
9
  import subprocess
8
10
 
9
11
  import typer
@@ -233,12 +235,57 @@ def config_github_ssh():
233
235
  subprocess.run(keygen_cmd)
234
236
  # Start the SSH agent in the background
235
237
  typer.echo("Checking that the SSH agent is running")
236
- ssh_agent_cmd = subprocess.run(
237
- ["ssh-agent", "-s"], capture_output=True, text=True
238
- ).stdout
239
- p = subprocess.run(ssh_agent_cmd, shell=True)
240
- if p.returncode != 0:
241
- raise_error("Failed to start ssh-agent")
238
+ # Note that under git bash / MSYS (MSYSTEM is set) we get a POSIX
239
+ # ssh-agent on PATH, so we use the POSIX path even on Windows there.
240
+ native_windows = platform.system() == "Windows" and not os.environ.get(
241
+ "MSYSTEM"
242
+ )
243
+ if native_windows:
244
+ # On native Windows, OpenSSH's ssh-agent runs as a Windows service
245
+ # rather than exporting environment variables to the current shell.
246
+ # The service ships disabled by default, so ensure it's enabled
247
+ # before starting it (enabling requires administrator privileges).
248
+ p = subprocess.run(
249
+ [
250
+ "powershell",
251
+ "-NoProfile",
252
+ "-Command",
253
+ (
254
+ "if ((Get-Service ssh-agent).StartType -eq 'Disabled') "
255
+ "{ Set-Service ssh-agent -StartupType Manual }; "
256
+ "Start-Service ssh-agent"
257
+ ),
258
+ ],
259
+ capture_output=True,
260
+ text=True,
261
+ )
262
+ if p.returncode != 0:
263
+ err = (p.stderr.strip() or p.stdout.strip()).lower()
264
+ if "access" in err and "denied" in err:
265
+ raise_error(
266
+ "Failed to start ssh-agent service: access denied.\n"
267
+ "The ssh-agent service is disabled and enabling it "
268
+ "requires administrator privileges. Please run the "
269
+ "following in an elevated (Run as administrator) "
270
+ "PowerShell, then try again:\n\n"
271
+ " Set-Service ssh-agent -StartupType Manual; "
272
+ "Start-Service ssh-agent"
273
+ )
274
+ raise_error(
275
+ "Failed to start ssh-agent service: "
276
+ f"{p.stderr.strip() or p.stdout.strip()}"
277
+ )
278
+ else:
279
+ # On POSIX systems, ssh-agent -s prints shell commands that export
280
+ # SSH_AUTH_SOCK and SSH_AGENT_PID; parse them into our environment so
281
+ # the subsequent ssh-add call can find the agent
282
+ p = subprocess.run(["ssh-agent", "-s"], capture_output=True, text=True)
283
+ if p.returncode != 0:
284
+ raise_error("Failed to start ssh-agent")
285
+ for match in re.finditer(
286
+ r"(SSH_AUTH_SOCK|SSH_AGENT_PID)=([^;\n]+);", p.stdout
287
+ ):
288
+ os.environ[match.group(1)] = match.group(2)
242
289
  # Add the SSH key to the ssh-agent
243
290
  typer.echo(f"Adding SSH key to ssh-agent: {key_path}")
244
291
  cmd = ["ssh-add", key_path]
calkit/cli/main/core.py CHANGED
@@ -581,6 +581,18 @@ def get_status(
581
581
  if pipeline_status and not pipeline_status.has_pipeline:
582
582
  typer.echo("This project has no pipeline.")
583
583
  return
584
+ # A failed environment check makes get_status bail out before computing
585
+ # staleness, leaving stale_stages empty. Don't let that masquerade as an
586
+ # up-to-date pipeline---say plainly that the status is unknown.
587
+ elif pipeline_status and pipeline_status.failed_environment_checks:
588
+ # The failing environments were already named in the warning emitted
589
+ # right after computing pipeline_status, so keep this explanatory
590
+ # only rather than repeating the list.
591
+ typer.echo(
592
+ "Pipeline status unavailable until the failed environment "
593
+ "checks above are resolved."
594
+ )
595
+ return
584
596
  elif pipeline_status and pipeline_status.is_stale:
585
597
  typer.echo("Stale stages:")
586
598
  for stage_name in pipeline_status.stale_stage_names:
@@ -2586,7 +2598,8 @@ def run_in_env(
2586
2598
  subprocess.check_call(
2587
2599
  julia_cmd,
2588
2600
  cwd=wdir,
2589
- env=os.environ.copy() | {"JULIA_LOAD_PATH": "@:@stdlib"},
2601
+ env=os.environ.copy()
2602
+ | {"JULIA_LOAD_PATH": calkit.julia.load_path()},
2590
2603
  )
2591
2604
  except subprocess.CalledProcessError:
2592
2605
  raise_error("Failed to run in julia environment")
calkit/cli/new.py CHANGED
@@ -2108,7 +2108,7 @@ def new_julia_env(
2108
2108
  except subprocess.CalledProcessError:
2109
2109
  raise_error(f"Failed to install Julia version {julia_version}")
2110
2110
  cmd = ["julia", f"+{julia_version}", f"--project={env_dir}", "-e"]
2111
- install_cmd = f'using Pkg; Pkg.activate("{env_dir}");'
2111
+ install_cmd = f'using Pkg; Pkg.activate("{calkit.julia.escape_string(env_dir)}");'
2112
2112
  for package in packages or []:
2113
2113
  install_cmd += f' Pkg.add("{package}");'
2114
2114
  cmd.append(install_cmd)
calkit/cli/notebooks.py CHANGED
@@ -310,14 +310,18 @@ def check_env_kernel(
310
310
  "still failed."
311
311
  )
312
312
  # Don't include version in display_name; IJulia appends it automatically
313
+ # Escape interpolated values for the Julia string literals---notably the
314
+ # Windows project path, whose backslashes are otherwise read as invalid
315
+ # unicode escapes.
316
+ esc = calkit.julia.escape_string
313
317
  julia_cmd = (
314
318
  "import IJulia;"
315
319
  "kp=IJulia.installkernel("
316
- f'"{kernel_name}",'
317
- f'"--project={env_dir_abs}",'
320
+ f'"{esc(kernel_name)}",'
321
+ f'"--project={esc(env_dir_abs)}",'
318
322
  '"--startup-file=no",'
319
- f'displayname="{display_name}",'
320
- 'env=Dict("JULIA_LOAD_PATH" => "@:@stdlib")'
323
+ f'displayname="{esc(display_name)}",'
324
+ f'env=Dict("JULIA_LOAD_PATH" => "{esc(calkit.julia.load_path())}")'
321
325
  ");"
322
326
  "println(kp);"
323
327
  )
@@ -474,8 +478,7 @@ def execute_notebook(
474
478
  # Create this environment and write it to file
475
479
  envs[res.name] = res.env
476
480
  ck_info["environments"] = envs
477
- with open("calkit.yaml", "w") as f:
478
- calkit.ryaml.dump(ck_info, f)
481
+ calkit.save_calkit_info(ck_info)
479
482
  # Detect language from environment
480
483
  if language is None:
481
484
  detected_language = language_from_notebook(path)
@@ -501,16 +504,20 @@ def execute_notebook(
501
504
  raise ValueError(
502
505
  "Language must be one of 'python', 'matlab', 'julia', or 'r'"
503
506
  )
504
- # Try to set kernelspec metadata so execution uses the expected kernel
507
+ # Try to set kernelspec metadata so execution uses the expected kernel.
508
+ # Always read/write as UTF-8: notebooks routinely contain non-ASCII (e.g.,
509
+ # Greek letters such as "ν" in code), and on Windows the default open()
510
+ # encoding is cp1252, which mangles UTF-8 content into mojibake ("ν" ->
511
+ # "ν") and breaks execution.
505
512
  try:
506
- with open(path, "r") as f:
513
+ with open(path, "r", encoding="utf-8") as f:
507
514
  nb_json = json.load(f)
508
515
  metadata = nb_json.setdefault("metadata", {})
509
516
  kernelspec = metadata.setdefault("kernelspec", {})
510
517
  kernelspec["name"] = kernel_name
511
518
  kernelspec["display_name"] = display_name
512
- kernelspec.setdefault("language", language.lower())
513
- with open(path, "w") as f:
519
+ kernelspec["language"] = language.lower()
520
+ with open(path, "w", encoding="utf-8") as f:
514
521
  json.dump(nb_json, f, indent=1)
515
522
  except Exception as e:
516
523
  if verbose:
calkit/cli/update.py CHANGED
@@ -746,7 +746,7 @@ def update_julia_env(
746
746
  env_dir = os.path.dirname(spec_path) or "."
747
747
  julia_version = env.get("julia")
748
748
  julia_bin = ["julia", f"+{julia_version}"] if julia_version else ["julia"]
749
- cmds = [f'Pkg.activate("{env_dir}")']
749
+ cmds = [f'Pkg.activate("{calkit.julia.escape_string(env_dir)}")']
750
750
  if add:
751
751
  pkg_list = "[" + ", ".join(f'"{p}"' for p in add) + "]"
752
752
  cmds.append(f"Pkg.add({pkg_list})")
calkit/core.py CHANGED
@@ -144,7 +144,9 @@ def load_calkit_info(
144
144
  if wdir is not None:
145
145
  fpath = os.path.join(wdir, fpath)
146
146
  if os.path.isfile(fpath):
147
- with open(fpath) as f:
147
+ # Always read as UTF-8; on Windows the default open() encoding is
148
+ # cp1252, which mangles non-ASCII content (e.g., Greek letters).
149
+ with open(fpath, encoding="utf-8") as f:
148
150
  info = ryaml.load(f)
149
151
  if info is None:
150
152
  info = {}
@@ -166,7 +168,7 @@ def load_calkit_info(
166
168
  if wdir is not None:
167
169
  include_fpath = os.path.join(wdir, include_fpath)
168
170
  if os.path.isfile(include_fpath):
169
- with open(include_fpath) as f:
171
+ with open(include_fpath, encoding="utf-8") as f:
170
172
  include_data = ryaml.load(f)
171
173
  info[kind][obj_name] |= include_data
172
174
  return info
@@ -180,7 +182,9 @@ def save_calkit_info(
180
182
  fpath = "calkit.yaml"
181
183
  if wdir is not None:
182
184
  fpath = os.path.join(wdir, fpath)
183
- with open(fpath, "w") as f:
185
+ # Always write as UTF-8; on Windows the default open() encoding is cp1252,
186
+ # which mangles non-ASCII content (e.g., Greek letters) into mojibake.
187
+ with open(fpath, "w", encoding="utf-8") as f:
184
188
  ryaml.dump(info, f)
185
189
 
186
190
 
calkit/detect.py CHANGED
@@ -1229,6 +1229,49 @@ def _extract_r_string_assignments(code: str) -> dict[str, str]:
1229
1229
  return assignments
1230
1230
 
1231
1231
 
1232
+ def _extract_r_string_vector_assignments(code: str) -> dict[str, list[str]]:
1233
+ """Extract character-vector assignments in R code.
1234
+
1235
+ Captures assignments of the form ``x <- c("a", "b")`` so that a later
1236
+ ``pacman::p_load(char = x)`` can be resolved back to its package list.
1237
+ """
1238
+ assignments = {}
1239
+ pattern = re.compile(
1240
+ r"([A-Za-z_][A-Za-z0-9_.]*)\s*(?:<-|=)\s*c\(([^)]*)\)",
1241
+ flags=re.DOTALL,
1242
+ )
1243
+ for name, body in pattern.findall(code):
1244
+ strings = re.findall(r'["\']([a-zA-Z0-9._]+)["\']', body)
1245
+ if strings:
1246
+ assignments[name] = strings
1247
+ return assignments
1248
+
1249
+
1250
+ def _resolve_r_package_args(
1251
+ call_args: str, vector_assignments: dict[str, list[str]]
1252
+ ) -> set[str]:
1253
+ """Resolve package names from a ``pacman::p_load()`` argument list.
1254
+
1255
+ Handles quoted literals, inline ``c(...)`` vectors, bare (non-standard
1256
+ evaluation) names like ``p_load(dplyr, tidyr)``, and ``char = x`` where
1257
+ ``x`` is a character vector assigned earlier in the script.
1258
+ """
1259
+ packages = set()
1260
+ # Quoted strings are package names, whether passed directly or inside c()
1261
+ packages.update(re.findall(r'["\']([a-zA-Z0-9._]+)["\']', call_args))
1262
+ # char = <var> referencing a character-vector assignment
1263
+ for var in re.findall(r"char\s*=\s*([A-Za-z_][A-Za-z0-9_.]*)", call_args):
1264
+ packages.update(vector_assignments.get(var, []))
1265
+ # Bare names passed positionally; drop keyword arguments (install=, update=,
1266
+ # character.only=, etc.) and their values before collecting identifiers
1267
+ stripped = re.sub(r"[A-Za-z_][A-Za-z0-9_.]*\s*=\s*[^,]+", "", call_args)
1268
+ for token in re.findall(r"[A-Za-z_][A-Za-z0-9_.]*", stripped):
1269
+ # c() is the vector constructor, not a package; TRUE/FALSE are values
1270
+ if token not in {"c", "TRUE", "FALSE", "T", "F"}:
1271
+ packages.add(token)
1272
+ return packages
1273
+
1274
+
1232
1275
  def _extract_julia_string_assignments(code: str) -> dict[str, str]:
1233
1276
  """Extract simple string assignments in Julia code."""
1234
1277
  assignments = {}
@@ -1622,6 +1665,27 @@ def detect_r_dependencies(
1622
1665
  for pattern in patterns:
1623
1666
  matches = re.findall(pattern, code)
1624
1667
  dependencies.update(matches)
1668
+ # requireNamespace()/loadNamespace() take the package as a string and are
1669
+ # commonly used to guard optional installs (e.g. pacman/here bootstrapping)
1670
+ dependencies.update(
1671
+ re.findall(
1672
+ r'(?:require|load)Namespace\s*\(\s*["\']([a-zA-Z0-9._]+)["\']',
1673
+ code,
1674
+ )
1675
+ )
1676
+ # Qualified calls like pkg::fn() or pkg:::fn() reference a package without
1677
+ # ever attaching it, so the package itself is still a dependency
1678
+ dependencies.update(re.findall(r"\b([a-zA-Z][a-zA-Z0-9._]*)\s*:::?", code))
1679
+ # pacman::p_load() loads (and optionally installs) packages dynamically; its
1680
+ # arguments may be bare names, quoted strings, or a character vector passed
1681
+ # via char=, possibly referencing a variable assigned earlier in the script
1682
+ vector_assignments = _extract_r_string_vector_assignments(code)
1683
+ for call_args in re.findall(
1684
+ r"p_load\s*\(((?:[^()]|\([^()]*\))*)\)", code, flags=re.DOTALL
1685
+ ):
1686
+ dependencies.update(
1687
+ _resolve_r_package_args(call_args, vector_assignments)
1688
+ )
1625
1689
  # Filter out base R packages
1626
1690
  base_packages = {
1627
1691
  "base",
calkit/environments.py CHANGED
@@ -5,6 +5,7 @@ import hashlib
5
5
  import json
6
6
  import os
7
7
  import platform
8
+ import re
8
9
  import subprocess
9
10
  import tempfile
10
11
  from pathlib import Path
@@ -1454,7 +1455,9 @@ def create_julia_project_file_content(
1454
1455
  return content
1455
1456
 
1456
1457
 
1457
- def create_r_description_content(dependencies: list[str]) -> str:
1458
+ def create_r_description_content(
1459
+ dependencies: list[str], project_name: str | None = None
1460
+ ) -> str:
1458
1461
  """Generate R DESCRIPTION file content listing dependencies.
1459
1462
 
1460
1463
  This creates a minimal DESCRIPTION file that renv can work with.
@@ -1463,13 +1466,26 @@ def create_r_description_content(dependencies: list[str]) -> str:
1463
1466
  ----------
1464
1467
  dependencies : list[str]
1465
1468
  List of R package names.
1469
+ project_name : str | None
1470
+ Name for the DESCRIPTION's ``Package`` field. It is sanitized into a
1471
+ valid R package name (letters, numbers and dots, starting with a
1472
+ letter). Falls back to the detected project name, then to
1473
+ ``CalkitProject``.
1466
1474
 
1467
1475
  Returns
1468
1476
  -------
1469
1477
  str
1470
1478
  The DESCRIPTION file content.
1471
1479
  """
1472
- content = """Package: CalkitProject
1480
+ if project_name is None:
1481
+ project_name = calkit.detect_project_name(prepend_owner=False)
1482
+ # Valid R package names may only contain letters, numbers and dots, and
1483
+ # must start with a letter, so replace anything else (e.g. hyphens) with a
1484
+ # dot and prefix a letter if needed
1485
+ package_name = re.sub(r"[^A-Za-z0-9.]+", ".", project_name).strip(".")
1486
+ if not package_name or not package_name[0].isalpha():
1487
+ package_name = "Calkit." + package_name if package_name else "Calkit"
1488
+ content = f"""Package: {package_name}
1473
1489
  Version: 0.0.1
1474
1490
  Title: Auto-generated R environment
1475
1491
  """
calkit/julia.py CHANGED
@@ -27,9 +27,36 @@ def get_julia_exe() -> str:
27
27
  return "julia"
28
28
 
29
29
 
30
+ def load_path() -> str:
31
+ """Return the ``JULIA_LOAD_PATH`` scoping resolution to the active project
32
+ and stdlib.
33
+
34
+ Julia splits ``JULIA_LOAD_PATH`` on the platform path separator---``;`` on
35
+ Windows and ``:`` elsewhere---so a hardcoded ``:`` leaves ``@stdlib`` off
36
+ the load path on Windows and even stdlib packages (e.g. ``Pkg``) fail to
37
+ resolve. ``os.pathsep`` matches Julia's rule on every platform.
38
+ """
39
+ return os.pathsep.join(["@", "@stdlib"])
40
+
41
+
42
+ def escape_string(value: str) -> str:
43
+ """Escape ``value`` for embedding in a Julia double-quoted string literal.
44
+
45
+ Julia treats ``\\``, ``"`` and ``$`` specially inside double quotes. In
46
+ particular a Windows path like ``C:\\Users\\...`` is otherwise read as an
47
+ invalid ``\\U`` unicode escape, so any value interpolated into Julia source
48
+ (e.g., a ``--project`` path passed via ``julia -e``) must be escaped.
49
+ """
50
+ return value.replace("\\", "\\\\").replace('"', '\\"').replace("$", "\\$")
51
+
52
+
30
53
  def _is_julia_command(arg: str) -> bool:
31
- """Whether ``arg`` is a Julia executable (julia or the juliaup launcher)."""
32
- base = os.path.basename(arg)
54
+ """Whether ``arg`` is a Julia executable (julia or the juliaup launcher).
55
+
56
+ The comparison is case-insensitive so Windows paths like ``julia.EXE``
57
+ (e.g. the ``WindowsApps`` execution-alias shim) are recognized.
58
+ """
59
+ base = os.path.basename(arg).lower()
33
60
  if base.endswith(".exe"):
34
61
  base = base[: -len(".exe")]
35
62
  return base in ("julia", "julialauncher")
calkit/notebooks.py CHANGED
@@ -242,11 +242,9 @@ def declare_notebook(
242
242
  }
243
243
  )
244
244
  # Write calkit.yaml
245
- fpath = os.path.join(wdir, "calkit.yaml")
246
245
  ck_info["pipeline"] = pipeline_dict
247
246
  ck_info["notebooks"] = notebooks
248
- with open(fpath, "w") as f:
249
- calkit.ryaml.dump(ck_info, f)
247
+ calkit.save_calkit_info(ck_info, wdir=wdir)
250
248
  if must_be_rerun:
251
249
  raise RuntimeError(
252
250
  f"Notebook stage '{stage_name}' was modified while the pipeline "
calkit/pipeline.py CHANGED
@@ -1011,6 +1011,35 @@ def get_output_storage_map(
1011
1011
  return result
1012
1012
 
1013
1013
 
1014
+ def _dump_yaml_if_changed(data, fpath: str, verbose: bool = False) -> bool:
1015
+ """Write ``data`` to ``fpath`` as YAML only if it changes the file.
1016
+
1017
+ ``calkit status`` recompiles the pipeline to DVC on every call, and the VS
1018
+ Code extension polls status continuously. Blindly rewriting an identical
1019
+ file causes two problems: (1) bumping ``dvc.yaml``'s mtime wakes the
1020
+ extension's file watcher, which triggers yet another status poll---an
1021
+ endless loop that keeps re-taking DVC's lock; and (2) the serializer formats
1022
+ slightly differently across platforms and ruamel versions, so the file
1023
+ churns in Git for no real change. Comparison is on the parsed structure, so
1024
+ trivial reformatting is not treated as a change. DVC keys stage staleness on
1025
+ ``dvc.lock`` (not on ``dvc.yaml``'s mtime), so skipping a no-op write is
1026
+ safe. Returns whether the file was written.
1027
+ """
1028
+ if os.path.isfile(fpath):
1029
+ try:
1030
+ with open(fpath) as f:
1031
+ if (calkit.ryaml.load(f) or {}) == data:
1032
+ return False
1033
+ except Exception:
1034
+ # Unreadable or invalid existing YAML: fall through and overwrite.
1035
+ pass
1036
+ if verbose:
1037
+ typer.echo(f"Writing to {os.path.basename(fpath)}")
1038
+ with open(fpath, "w") as f:
1039
+ calkit.ryaml.dump(data, f)
1040
+ return True
1041
+
1042
+
1014
1043
  def to_dvc(
1015
1044
  ck_info: dict | None = None,
1016
1045
  wdir: str | None = None,
@@ -1099,8 +1128,7 @@ def to_dvc(
1099
1128
  with open(dvc_yaml_fpath) as f:
1100
1129
  existing = calkit.ryaml.load(f) or {}
1101
1130
  existing.setdefault("stages", {}).update(wrapper_stages)
1102
- with open(dvc_yaml_fpath, "w") as f:
1103
- calkit.ryaml.dump(existing, f)
1131
+ _dump_yaml_if_changed(existing, dvc_yaml_fpath)
1104
1132
  return wrapper_stages
1105
1133
  # Detect stages with the old ``slurm:`` field before model validation
1106
1134
  # because model_validate pops it in place.
@@ -1194,8 +1222,7 @@ def to_dvc(
1194
1222
  for sname in slurm_field_stages:
1195
1223
  stage_data = ck_yaml_data["pipeline"]["stages"][sname]
1196
1224
  stage_data["scheduler"] = stage_data.pop("slurm")
1197
- with open(ck_yaml_path, "w") as _f:
1198
- calkit.ryaml.dump(ck_yaml_data, _f)
1225
+ _dump_yaml_if_changed(ck_yaml_data, ck_yaml_path)
1199
1226
  # Validate and initialize scheduler options (SLURM/PBS) for stages
1200
1227
  # whose outer environment is a job scheduler. Env defaults are applied
1201
1228
  # at job-submission time, not here.
@@ -1372,10 +1399,7 @@ def to_dvc(
1372
1399
  dvc_stages[stage_name] = stage
1373
1400
  dvc_yaml["stages"] = dvc_stages
1374
1401
  dvc_yaml_fpath = os.path.join(wdir, "dvc.yaml") if wdir else "dvc.yaml"
1375
- with open(dvc_yaml_fpath, "w") as f:
1376
- if verbose:
1377
- typer.echo("Writing to dvc.yaml")
1378
- calkit.ryaml.dump(dvc_yaml, f)
1402
+ _dump_yaml_if_changed(dvc_yaml, dvc_yaml_fpath, verbose=verbose)
1379
1403
  # DVC errors out (FileIsGitIgnored) if dvc.lock is Git-ignored, so make
1380
1404
  # sure it isn't---this keeps both status and pipeline runs working
1381
1405
  calkit.dvc.ensure_dvc_lock_not_ignored(wdir)
@@ -0,0 +1,7 @@
1
+ import pytest
2
+
3
+
4
+ @pytest.fixture
5
+ def jp_server_config(jp_server_config):
6
+ # Load the Calkit server extension in the test Jupyter server.
7
+ return {"ServerApp": {"jpserver_extensions": {"calkit": True}}}
calkit/tests/test_core.py CHANGED
@@ -43,6 +43,21 @@ def test_detect_project_name(tmp_dir):
43
43
  assert calkit.detect_project_name() == "someone-else/some-project"
44
44
 
45
45
 
46
+ def test_save_calkit_info_preserves_unicode(tmp_dir):
47
+ # Regression: on Windows, opening calkit.yaml without encoding="utf-8"
48
+ # writes as cp1252, which either errors or corrupts non-ASCII content
49
+ # (e.g., the Greek letter "ν") into mojibake ("ν"). ruamel dumps raw
50
+ # unicode (allow_unicode=True), so the encoding of the file handle matters.
51
+ info = {"name": "p", "owner": "o", "title": "viscosity ν"}
52
+ calkit.save_calkit_info(info)
53
+ # The value must survive the round-trip intact.
54
+ assert calkit.load_calkit_info()["title"] == "viscosity ν"
55
+ # Raw bytes must be the UTF-8 encoding of "ν", not cp1252 mojibake.
56
+ raw = open("calkit.yaml", "rb").read()
57
+ assert "ν".encode("utf-8") in raw
58
+ assert "ν".encode("utf-8") not in raw
59
+
60
+
46
61
  def test_load_calkit_info(tmp_dir, monkeypatch):
47
62
  subpath = "some/project"
48
63
  os.makedirs(subpath)
@@ -863,6 +863,41 @@ data <- read.csv("data.csv")
863
863
  assert "tidyr" in deps
864
864
  # Base packages should not be included
865
865
  assert "base" not in deps
866
+ # Qualified calls (pkg::fn), requireNamespace/loadNamespace, and
867
+ # pacman::p_load with a character vector should all be detected
868
+ pacman_content = """
869
+ if (!requireNamespace("here", quietly = TRUE)) install.packages("here")
870
+ here::i_am("code/master.R")
871
+ loadNamespace("withr")
872
+ pkgs <- c(
873
+ "haven", "readxl",
874
+ "dplyr", "ggplot2"
875
+ )
876
+ pacman::p_load(char = pkgs, install = TRUE, character.only = TRUE)
877
+ pacman::p_load(stringr, tidyr)
878
+ pacman::p_load("forcats", c("janitor", "lubridate"))
879
+ """
880
+ deps = detect_r_dependencies(code=pacman_content)
881
+ # Qualified call and the requireNamespace/loadNamespace guards
882
+ assert "here" in deps
883
+ assert "pacman" in deps
884
+ assert "withr" in deps
885
+ # Vector resolved via char =
886
+ assert "haven" in deps
887
+ assert "readxl" in deps
888
+ assert "ggplot2" in deps
889
+ # Bare non-standard-evaluation names
890
+ assert "stringr" in deps
891
+ assert "tidyr" in deps
892
+ # Quoted names and inline c() vectors
893
+ assert "forcats" in deps
894
+ assert "janitor" in deps
895
+ assert "lubridate" in deps
896
+ # The vector constructor and keyword-argument values are not packages
897
+ assert "c" not in deps
898
+ assert "TRUE" not in deps
899
+ assert "install" not in deps
900
+ assert "character.only" not in deps
866
901
 
867
902
 
868
903
  def test_detect_r_dependencies_from_code():
@@ -1064,6 +1099,20 @@ def test_create_r_description_file(tmp_dir):
1064
1099
  assert "dplyr" in content
1065
1100
  assert "tidyr" in content
1066
1101
  assert "Imports:" in content
1102
+ # The Package field reflects the project name (sanitized to a valid R
1103
+ # package name), not a hard-coded placeholder
1104
+ from calkit.environments import create_r_description_content
1105
+
1106
+ content = create_r_description_content(deps, project_name="AI-Games")
1107
+ assert "Package: AI.Games" in content
1108
+ assert "CalkitProject" not in content
1109
+ # Names that don't start with a letter get a valid prefix
1110
+ content = create_r_description_content(deps, project_name="2023-study")
1111
+ package_line = [
1112
+ ln for ln in content.splitlines() if ln.startswith("Package:")
1113
+ ][0]
1114
+ package_name = package_line.split(":", 1)[1].strip()
1115
+ assert package_name[0].isalpha()
1067
1116
 
1068
1117
 
1069
1118
  def test_create_files_in_subdirectory(tmp_dir):
@@ -1,16 +1,40 @@
1
1
  """Tests for ``calkit.julia``"""
2
2
 
3
+ import os
3
4
  from unittest import mock
4
5
 
5
6
  import pytest
6
7
 
7
8
  from calkit.julia import (
9
+ _is_julia_command,
8
10
  check_version_in_command,
9
11
  ensure_startup_file_disabled_in_command,
12
+ escape_string,
10
13
  get_julia_exe,
14
+ load_path,
11
15
  )
12
16
 
13
17
 
18
+ def test_escape_string_windows_path():
19
+ # A Windows path embedded in a Julia string literal must have its
20
+ # backslashes escaped, otherwise "C:\\Users" is read as an invalid \\U
21
+ # unicode escape (ParseError).
22
+ assert escape_string("C:\\Users\\peteb") == "C:\\\\Users\\\\peteb"
23
+ # Double quotes and dollar signs are the other special characters.
24
+ assert escape_string('a"b') == 'a\\"b'
25
+ assert escape_string("a$b") == "a\\$b"
26
+ # Plain values (e.g. the load path) are unchanged.
27
+ assert escape_string("@;@stdlib") == "@;@stdlib"
28
+
29
+
30
+ def test_load_path_uses_platform_separator():
31
+ # Julia splits JULIA_LOAD_PATH on the platform path separator, so the
32
+ # value must use ';' on Windows and ':' elsewhere (== os.pathsep).
33
+ assert load_path() == f"@{os.pathsep}@stdlib"
34
+ assert "@" in load_path()
35
+ assert "@stdlib" in load_path()
36
+
37
+
14
38
  def test_get_julia_exe(monkeypatch, tmp_path):
15
39
  # Prefer julia on the PATH when present
16
40
  def which_julia(name):
@@ -44,6 +68,28 @@ def test_get_julia_exe(monkeypatch, tmp_path):
44
68
  assert get_julia_exe() == "julia"
45
69
 
46
70
 
71
+ def test_is_julia_command_case_insensitive():
72
+ # Windows resolves the execution-alias shim to an uppercase extension
73
+ # (e.g. ``julia.EXE``); the check must still recognize it.
74
+ assert _is_julia_command("C:/x/julia.EXE")
75
+ assert _is_julia_command("C:/x/Julia.Exe")
76
+ assert _is_julia_command("C:/x/julialauncher.EXE")
77
+ assert _is_julia_command("/usr/bin/julia")
78
+ assert _is_julia_command("julialauncher")
79
+ assert not _is_julia_command("python")
80
+ assert not _is_julia_command("C:/x/python.exe")
81
+
82
+
83
+ def test_disable_startup_file_accepts_uppercase_exe():
84
+ # Regression: julia.EXE (Windows execution alias) was rejected as not a
85
+ # Julia command, failing the env check and masking real pipeline status.
86
+ cmd = ensure_startup_file_disabled_in_command(
87
+ ["C:/x/julia.EXE", "--version"]
88
+ )
89
+ assert cmd[0] == "C:/x/julia.EXE"
90
+ assert "--startup-file=no" in cmd
91
+
92
+
47
93
  def test_check_version_in_command():
48
94
  cmd = ["julia", "+1.11", "--project", "whatever"]
49
95
  # Case 1: juliaup is available
@@ -521,6 +521,55 @@ def test_to_dvc_unignores_dvc_lock(tmp_dir):
521
521
  assert not repo.ignored("dvc.lock")
522
522
 
523
523
 
524
+ def test_to_dvc_skips_no_op_writes(tmp_dir):
525
+ # Compiling to DVC happens on every `calkit status` (which the VS Code
526
+ # extension polls continuously). Rewriting an unchanged dvc.yaml churns Git
527
+ # across platforms (trivial reformatting) and wakes the extension's dvc.yaml
528
+ # watcher into another status poll, an endless loop that keeps re-taking
529
+ # DVC's lock. The write must be skipped unless the parsed content changes.
530
+ subprocess.check_call(["calkit", "init"])
531
+ ck_info = {
532
+ "pipeline": {
533
+ "stages": {
534
+ "s1": {
535
+ "kind": "command",
536
+ "environment": "_system",
537
+ "command": "echo a > a.txt",
538
+ "outputs": ["a.txt"],
539
+ }
540
+ }
541
+ },
542
+ }
543
+ with open("calkit.yaml", "w") as f:
544
+ calkit.ryaml.dump(ck_info, f)
545
+ # First compile creates dvc.yaml.
546
+ calkit.pipeline.to_dvc(ck_info=ck_info, write=True)
547
+ assert os.path.isfile("dvc.yaml")
548
+ # Pin an old mtime; a no-op recompile of identical content must leave it.
549
+ pinned = 1000000000.0
550
+ os.utime("dvc.yaml", (pinned, pinned))
551
+ calkit.pipeline.to_dvc(ck_info=ck_info, write=True)
552
+ assert os.path.getmtime("dvc.yaml") == pinned
553
+ # A semantically-equal but reformatted/commented file is also left as-is, so
554
+ # trivial cross-platform reformatting doesn't churn the file in Git.
555
+ with open("dvc.yaml") as f:
556
+ original = f.read()
557
+ with open("dvc.yaml", "w") as f:
558
+ f.write("# hand-written comment\n" + original)
559
+ with open("dvc.yaml") as f:
560
+ reformatted = f.read()
561
+ os.utime("dvc.yaml", (pinned, pinned))
562
+ calkit.pipeline.to_dvc(ck_info=ck_info, write=True)
563
+ with open("dvc.yaml") as f:
564
+ assert f.read() == reformatted
565
+ assert os.path.getmtime("dvc.yaml") == pinned
566
+ # A real change, though, is written.
567
+ ck_info["pipeline"]["stages"]["s1"]["command"] = "echo b > a.txt"
568
+ calkit.pipeline.to_dvc(ck_info=ck_info, write=True)
569
+ with open("dvc.yaml") as f:
570
+ assert f.read() != reformatted
571
+
572
+
524
573
  def test_sbatch_stage_to_dvc(tmp_dir):
525
574
  """Cover the SLURM stage compilation paths.
526
575
 
@@ -39,7 +39,7 @@
39
39
  "clean": "jlpm clean:lib",
40
40
  "clean:lib": "rimraf lib tsconfig.tsbuildinfo",
41
41
  "clean:lintcache": "rimraf .eslintcache .stylelintcache",
42
- "clean:labextension": "rimraf calkit/labextension calkit/_version.py",
42
+ "clean:labextension": "rimraf ../calkit/labextension ../calkit/_version.py",
43
43
  "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache",
44
44
  "eslint": "jlpm eslint:check --fix",
45
45
  "eslint:check": "eslint . --cache --ext .ts,.tsx",
@@ -128,10 +128,10 @@
128
128
  }
129
129
  },
130
130
  "extension": true,
131
- "outputDir": "calkit/labextension",
131
+ "outputDir": "../calkit/labextension",
132
132
  "schemaDir": "schema",
133
133
  "_build": {
134
- "load": "static/remoteEntry.65469af996e7a96aa983.js",
134
+ "load": "static/remoteEntry.ac9035764d5b2adbb542.js",
135
135
  "extension": "./extension",
136
136
  "style": "./style"
137
137
  }
@@ -157,20 +157,6 @@
157
157
  "project": "tsconfig.json",
158
158
  "sourceType": "module"
159
159
  },
160
- "overrides": [
161
- {
162
- "files": [
163
- "vscode-ext/**/*.ts"
164
- ],
165
- "parserOptions": {
166
- "project": "vscode-ext/tsconfig.json",
167
- "sourceType": "module"
168
- },
169
- "rules": {
170
- "@typescript-eslint/naming-convention": "off"
171
- }
172
- }
173
- ],
174
160
  "plugins": [
175
161
  "@typescript-eslint"
176
162
  ],
@@ -205,12 +191,6 @@
205
191
  "prefer-arrow-callback": "error"
206
192
  }
207
193
  },
208
- "prettier": {
209
- "singleQuote": false,
210
- "trailingComma": "all",
211
- "arrowParens": "always",
212
- "endOfLine": "auto"
213
- },
214
194
  "stylelint": {
215
195
  "extends": [
216
196
  "stylelint-config-recommended",
@@ -39,7 +39,7 @@
39
39
  "clean": "jlpm clean:lib",
40
40
  "clean:lib": "rimraf lib tsconfig.tsbuildinfo",
41
41
  "clean:lintcache": "rimraf .eslintcache .stylelintcache",
42
- "clean:labextension": "rimraf calkit/labextension calkit/_version.py",
42
+ "clean:labextension": "rimraf ../calkit/labextension ../calkit/_version.py",
43
43
  "clean:all": "jlpm clean:lib && jlpm clean:labextension && jlpm clean:lintcache",
44
44
  "eslint": "jlpm eslint:check --fix",
45
45
  "eslint:check": "eslint . --cache --ext .ts,.tsx",
@@ -128,7 +128,7 @@
128
128
  }
129
129
  },
130
130
  "extension": true,
131
- "outputDir": "calkit/labextension",
131
+ "outputDir": "../calkit/labextension",
132
132
  "schemaDir": "schema"
133
133
  },
134
134
  "eslintIgnore": [
@@ -152,20 +152,6 @@
152
152
  "project": "tsconfig.json",
153
153
  "sourceType": "module"
154
154
  },
155
- "overrides": [
156
- {
157
- "files": [
158
- "vscode-ext/**/*.ts"
159
- ],
160
- "parserOptions": {
161
- "project": "vscode-ext/tsconfig.json",
162
- "sourceType": "module"
163
- },
164
- "rules": {
165
- "@typescript-eslint/naming-convention": "off"
166
- }
167
- }
168
- ],
169
155
  "plugins": [
170
156
  "@typescript-eslint"
171
157
  ],
@@ -200,12 +186,6 @@
200
186
  "prefer-arrow-callback": "error"
201
187
  }
202
188
  },
203
- "prettier": {
204
- "singleQuote": false,
205
- "trailingComma": "all",
206
- "arrowParens": "always",
207
- "endOfLine": "auto"
208
- },
209
189
  "stylelint": {
210
190
  "extends": [
211
191
  "stylelint-config-recommended",
@@ -1 +1 @@
1
- var _JUPYTERLAB;(()=>{"use strict";var e,r,t,a,n,o,i,l,u,f,s,d,c,p,h,b,v,m,y,g,j,w,k,S={4717(e,r,t){var a={"./index":()=>Promise.all([t.e(867),t.e(345),t.e(909)]).then(()=>()=>t(5909)),"./extension":()=>Promise.all([t.e(867),t.e(345),t.e(909)]).then(()=>()=>t(5909)),"./style":()=>t.e(502).then(()=>()=>t(502))},n=(e,r)=>(t.R=r,r=t.o(a,e)?a[e]():Promise.resolve().then(()=>{throw new Error('Module "'+e+'" does not exist in container.')}),t.R=void 0,r),o=(e,r)=>{if(t.S){var a="default",n=t.S[a];if(n&&n!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[a]=e,t.I(a,r)}};t.d(r,{get:()=>n,init:()=>o})}},P={};function E(e){var r=P[e];if(void 0!==r)return r.exports;var t=P[e]={id:e,exports:{}};return S[e](t,t.exports,E),t.exports}E.m=S,E.c=P,E.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return E.d(r,{a:r}),r},E.d=(e,r)=>{for(var t in r)E.o(r,t)&&!E.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},E.f={},E.e=e=>Promise.all(Object.keys(E.f).reduce((r,t)=>(E.f[t](e,r),r),[])),E.u=e=>e+"."+{502:"9a2c5772a15466e923ef",695:"2c41003a452d43d2b358",867:"a42a046aa5108f54f8fb",909:"e3f9cc3408834a7fdcc3",946:"050af2abf7845cfbdbd2"}[e]+".js?v="+{502:"9a2c5772a15466e923ef",695:"2c41003a452d43d2b358",867:"a42a046aa5108f54f8fb",909:"e3f9cc3408834a7fdcc3",946:"050af2abf7845cfbdbd2"}[e],E.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),E.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="calkit:",E.l=(t,a,n,o)=>{if(e[t])e[t].push(a);else{var i,l;if(void 0!==n)for(var u=document.getElementsByTagName("script"),f=0;f<u.length;f++){var s=u[f];if(s.getAttribute("src")==t||s.getAttribute("data-webpack")==r+n){i=s;break}}i||(l=!0,(i=document.createElement("script")).charset="utf-8",E.nc&&i.setAttribute("nonce",E.nc),i.setAttribute("data-webpack",r+n),i.src=t),e[t]=[a];var d=(r,a)=>{i.onerror=i.onload=null,clearTimeout(c);var n=e[t];if(delete e[t],i.parentNode&&i.parentNode.removeChild(i),n&&n.forEach(e=>e(a)),r)return r(a)},c=setTimeout(d.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=d.bind(null,i.onerror),i.onload=d.bind(null,i.onload),l&&document.head.appendChild(i)}},E.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{E.S={};var e={},r={};E.I=(t,a)=>{a||(a=[]);var n=r[t];if(n||(n=r[t]={}),!(a.indexOf(n)>=0)){if(a.push(n),e[t])return e[t];E.o(E.S,t)||(E.S[t]={});var o=E.S[t],i="calkit",l=(e,r,t,a)=>{var n=o[e]=o[e]||{},l=n[r];(!l||!l.loaded&&(!a!=!l.eager?a:i>l.from))&&(n[r]={get:t,from:i,eager:!!a})},u=[];return"default"===t&&(l("@tanstack/react-query-devtools","5.91.1",()=>E.e(695).then(()=>()=>E(6695))),l("@tanstack/react-query","5.90.12",()=>Promise.all([E.e(946),E.e(345)]).then(()=>()=>E(5946))),l("calkit","0.1.0",()=>Promise.all([E.e(867),E.e(345),E.e(909)]).then(()=>()=>E(5909)))),e[t]=u.length?Promise.all(u).then(()=>e[t]=1):1}}})(),(()=>{var e;E.g.importScripts&&(e=E.g.location+"");var r=E.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var a=t.length-1;a>-1&&(!e||!/^http(s?):/.test(e));)e=t[a--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),E.p=e})(),t=e=>{var r=e=>e.split(".").map(e=>+e==e?+e:e),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),a=t[1]?r(t[1]):[];return t[2]&&(a.length++,a.push.apply(a,r(t[2]))),t[3]&&(a.push([]),a.push.apply(a,r(t[3]))),a},a=(e,r)=>{e=t(e),r=t(r);for(var a=0;;){if(a>=e.length)return a<r.length&&"u"!=(typeof r[a])[0];var n=e[a],o=(typeof n)[0];if(a>=r.length)return"u"==o;var i=r[a],l=(typeof i)[0];if(o!=l)return"o"==o&&"n"==l||"s"==l||"u"==o;if("o"!=o&&"u"!=o&&n!=i)return n<i;a++}},n=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var a=1,o=1;o<e.length;o++)a--,t+="u"==(typeof(l=e[o]))[0]?"-":(a>0?".":"")+(a=2,l);return t}var i=[];for(o=1;o<e.length;o++){var l=e[o];i.push(0===l?"not("+u()+")":1===l?"("+u()+" || "+u()+")":2===l?i.pop()+" "+i.pop():n(l))}return u();function u(){return i.pop().replace(/^\((.+)\)$/,"$1")}},o=(e,r)=>{if(0 in e){r=t(r);var a=e[0],n=a<0;n&&(a=-a-1);for(var i=0,l=1,u=!0;;l++,i++){var f,s,d=l<e.length?(typeof e[l])[0]:"";if(i>=r.length||"o"==(s=(typeof(f=r[i]))[0]))return!u||("u"==d?l>a&&!n:""==d!=n);if("u"==s){if(!u||"u"!=d)return!1}else if(u)if(d==s)if(l<=a){if(f!=e[l])return!1}else{if(n?f>e[l]:f<e[l])return!1;f!=e[l]&&(u=!1)}else if("s"!=d&&"n"!=d){if(n||l<=a)return!1;u=!1,l--}else{if(l<=a||s<d!=n)return!1;u=!1}else"s"!=d&&"n"!=d&&(u=!1,l--)}}var c=[],p=c.pop.bind(c);for(i=1;i<e.length;i++){var h=e[i];c.push(1==h?p()|p():2==h?p()&p():h?o(h,r):!p())}return!!p()},i=(e,r)=>e&&E.o(e,r),l=e=>(e.loaded=1,e.get()),u=e=>Object.keys(e).reduce((r,t)=>(e[t].eager&&(r[t]=e[t]),r),{}),f=(e,r,t,n)=>{var i=n?u(e[r]):e[r];return(r=Object.keys(i).reduce((e,r)=>!o(t,r)||e&&!a(e,r)?e:r,0))&&i[r]},s=(e,r,t)=>{var n=t?u(e[r]):e[r];return Object.keys(n).reduce((e,r)=>!e||!n[e].loaded&&a(e,r)?r:e,0)},d=(e,r,t,a)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+n(a)+")",c=(e,r,t,a,o)=>{var i=e[t];return"No satisfying version ("+n(a)+")"+(o?" for eager consumption":"")+" of shared module "+t+" found in shared scope "+r+".\nAvailable versions: "+Object.keys(i).map(e=>e+" from "+i[e].from).join(", ")},p=e=>{throw new Error(e)},h=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},v=(e,r,t)=>t?t():((e,r)=>p("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),m=(b=e=>function(r,t,a,n,o){var i=E.I(r);return i&&i.then&&!a?i.then(e.bind(e,r,E.S[r],t,!1,n,o)):e(r,E.S[r],t,a,n,o)})((e,r,t,a,n,o)=>{if(!i(r,t))return v(e,t,o);var u=f(r,t,n,a);return u?l(u):o?o():void p(c(r,e,t,n,a))}),y=b((e,r,t,a,n,u)=>{if(!i(r,t))return v(e,t,u);var f=s(r,t,a);return o(n,f)||h(d(r,t,f,n)),l(r[t][f])}),g={},j={3345:()=>y("default","react",!1,[1,18,2,0]),520:()=>y("default","@jupyterlab/coreutils",!1,[1,6,5,5]),1247:()=>y("default","@lumino/widgets",!1,[1,2,3,1,,"alpha",1]),1650:()=>y("default","@jupyterlab/launcher",!1,[1,4,5,5]),3476:()=>y("default","@jupyterlab/application",!1,[1,4,5,5]),3539:()=>y("default","@jupyterlab/settingregistry",!1,[1,4,5,5]),3712:()=>m("default","@tanstack/react-query",!1,[1,5,32,0],()=>E.e(946).then(()=>()=>E(5946))),3895:()=>y("default","@jupyterlab/statusbar",!1,[1,4,5,5]),6167:()=>y("default","@lumino/commands",!1,[1,2,0,1]),6200:()=>y("default","@jupyterlab/filebrowser",!1,[1,4,5,5]),6511:()=>y("default","@jupyterlab/apputils",!1,[1,4,6,5]),6641:()=>y("default","@jupyterlab/notebook",!1,[1,4,5,5]),6911:()=>y("default","@jupyterlab/statedb",!1,[1,4,5,5]),6965:()=>y("default","@jupyterlab/ui-components",!1,[1,4,5,5]),6979:()=>y("default","@jupyterlab/translation",!1,[1,4,5,5]),7034:()=>m("default","@tanstack/react-query-devtools",!1,[1,5,32,0],()=>E.e(695).then(()=>()=>E(6695))),7628:()=>y("default","react-dom",!1,[1,18,2,0]),9080:()=>y("default","@jupyterlab/mainmenu",!1,[1,4,5,5]),9519:()=>y("default","@jupyterlab/services",!1,[1,7,5,5])},w={345:[3345],909:[520,1247,1650,3476,3539,3712,3895,6167,6200,6511,6641,6911,6965,6979,7034,7628,9080,9519]},k={},E.f.consumes=(e,r)=>{E.o(w,e)&&w[e].forEach(e=>{if(E.o(g,e))return r.push(g[e]);if(!k[e]){var t=r=>{g[e]=0,E.m[e]=t=>{delete E.c[e],t.exports=r()}};k[e]=!0;var a=r=>{delete g[e],E.m[e]=t=>{throw delete E.c[e],r}};try{var n=j[e]();n.then?r.push(g[e]=n.then(t).catch(a)):t(n)}catch(e){a(e)}}})},(()=>{E.b="undefined"!=typeof document&&document.baseURI||self.location.href;var e={371:0};E.f.j=(r,t)=>{var a=E.o(e,r)?e[r]:void 0;if(0!==a)if(a)t.push(a[2]);else if(345!=r){var n=new Promise((t,n)=>a=e[r]=[t,n]);t.push(a[2]=n);var o=E.p+E.u(r),i=new Error;E.l(o,t=>{if(E.o(e,r)&&(0!==(a=e[r])&&(e[r]=void 0),a)){var n=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+n+": "+o+")",i.name="ChunkLoadError",i.type=n,i.request=o,a[1](i)}},"chunk-"+r,r)}else e[r]=0};var r=(r,t)=>{var a,n,[o,i,l]=t,u=0;if(o.some(r=>0!==e[r])){for(a in i)E.o(i,a)&&(E.m[a]=i[a]);l&&l(E)}for(r&&r(t);u<o.length;u++)n=o[u],E.o(e,n)&&e[n]&&e[n][0](),e[n]=0},t=self.webpackChunkcalkit=self.webpackChunkcalkit||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),E.nc=void 0;var T=E(4717);(_JUPYTERLAB=void 0===_JUPYTERLAB?{}:_JUPYTERLAB).calkit=T})();
1
+ var _JUPYTERLAB;(()=>{"use strict";var e,r,t,a,n,o,i,l,u,f,s,d,c,p,h,b,v,m,y,g,j,w,k,S={1336(e,r,t){var a={"./index":()=>Promise.all([t.e(867),t.e(345),t.e(909)]).then(()=>()=>t(5909)),"./extension":()=>Promise.all([t.e(867),t.e(345),t.e(909)]).then(()=>()=>t(5909)),"./style":()=>t.e(502).then(()=>()=>t(502))},n=(e,r)=>(t.R=r,r=t.o(a,e)?a[e]():Promise.resolve().then(()=>{throw new Error('Module "'+e+'" does not exist in container.')}),t.R=void 0,r),o=(e,r)=>{if(t.S){var a="default",n=t.S[a];if(n&&n!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[a]=e,t.I(a,r)}};t.d(r,{get:()=>n,init:()=>o})}},P={};function E(e){var r=P[e];if(void 0!==r)return r.exports;var t=P[e]={id:e,exports:{}};return S[e](t,t.exports,E),t.exports}E.m=S,E.c=P,E.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return E.d(r,{a:r}),r},E.d=(e,r)=>{for(var t in r)E.o(r,t)&&!E.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},E.f={},E.e=e=>Promise.all(Object.keys(E.f).reduce((r,t)=>(E.f[t](e,r),r),[])),E.u=e=>e+"."+{502:"9a2c5772a15466e923ef",695:"2c41003a452d43d2b358",867:"a42a046aa5108f54f8fb",909:"e3f9cc3408834a7fdcc3",946:"050af2abf7845cfbdbd2"}[e]+".js?v="+{502:"9a2c5772a15466e923ef",695:"2c41003a452d43d2b358",867:"a42a046aa5108f54f8fb",909:"e3f9cc3408834a7fdcc3",946:"050af2abf7845cfbdbd2"}[e],E.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),E.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="calkit:",E.l=(t,a,n,o)=>{if(e[t])e[t].push(a);else{var i,l;if(void 0!==n)for(var u=document.getElementsByTagName("script"),f=0;f<u.length;f++){var s=u[f];if(s.getAttribute("src")==t||s.getAttribute("data-webpack")==r+n){i=s;break}}i||(l=!0,(i=document.createElement("script")).charset="utf-8",E.nc&&i.setAttribute("nonce",E.nc),i.setAttribute("data-webpack",r+n),i.src=t),e[t]=[a];var d=(r,a)=>{i.onerror=i.onload=null,clearTimeout(c);var n=e[t];if(delete e[t],i.parentNode&&i.parentNode.removeChild(i),n&&n.forEach(e=>e(a)),r)return r(a)},c=setTimeout(d.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=d.bind(null,i.onerror),i.onload=d.bind(null,i.onload),l&&document.head.appendChild(i)}},E.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{E.S={};var e={},r={};E.I=(t,a)=>{a||(a=[]);var n=r[t];if(n||(n=r[t]={}),!(a.indexOf(n)>=0)){if(a.push(n),e[t])return e[t];E.o(E.S,t)||(E.S[t]={});var o=E.S[t],i="calkit",l=(e,r,t,a)=>{var n=o[e]=o[e]||{},l=n[r];(!l||!l.loaded&&(!a!=!l.eager?a:i>l.from))&&(n[r]={get:t,from:i,eager:!!a})},u=[];return"default"===t&&(l("@tanstack/react-query-devtools","5.91.1",()=>E.e(695).then(()=>()=>E(6695))),l("@tanstack/react-query","5.90.12",()=>Promise.all([E.e(946),E.e(345)]).then(()=>()=>E(5946))),l("calkit","0.1.0",()=>Promise.all([E.e(867),E.e(345),E.e(909)]).then(()=>()=>E(5909)))),e[t]=u.length?Promise.all(u).then(()=>e[t]=1):1}}})(),(()=>{var e;E.g.importScripts&&(e=E.g.location+"");var r=E.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var a=t.length-1;a>-1&&(!e||!/^http(s?):/.test(e));)e=t[a--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),E.p=e})(),t=e=>{var r=e=>e.split(".").map(e=>+e==e?+e:e),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),a=t[1]?r(t[1]):[];return t[2]&&(a.length++,a.push.apply(a,r(t[2]))),t[3]&&(a.push([]),a.push.apply(a,r(t[3]))),a},a=(e,r)=>{e=t(e),r=t(r);for(var a=0;;){if(a>=e.length)return a<r.length&&"u"!=(typeof r[a])[0];var n=e[a],o=(typeof n)[0];if(a>=r.length)return"u"==o;var i=r[a],l=(typeof i)[0];if(o!=l)return"o"==o&&"n"==l||"s"==l||"u"==o;if("o"!=o&&"u"!=o&&n!=i)return n<i;a++}},n=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var a=1,o=1;o<e.length;o++)a--,t+="u"==(typeof(l=e[o]))[0]?"-":(a>0?".":"")+(a=2,l);return t}var i=[];for(o=1;o<e.length;o++){var l=e[o];i.push(0===l?"not("+u()+")":1===l?"("+u()+" || "+u()+")":2===l?i.pop()+" "+i.pop():n(l))}return u();function u(){return i.pop().replace(/^\((.+)\)$/,"$1")}},o=(e,r)=>{if(0 in e){r=t(r);var a=e[0],n=a<0;n&&(a=-a-1);for(var i=0,l=1,u=!0;;l++,i++){var f,s,d=l<e.length?(typeof e[l])[0]:"";if(i>=r.length||"o"==(s=(typeof(f=r[i]))[0]))return!u||("u"==d?l>a&&!n:""==d!=n);if("u"==s){if(!u||"u"!=d)return!1}else if(u)if(d==s)if(l<=a){if(f!=e[l])return!1}else{if(n?f>e[l]:f<e[l])return!1;f!=e[l]&&(u=!1)}else if("s"!=d&&"n"!=d){if(n||l<=a)return!1;u=!1,l--}else{if(l<=a||s<d!=n)return!1;u=!1}else"s"!=d&&"n"!=d&&(u=!1,l--)}}var c=[],p=c.pop.bind(c);for(i=1;i<e.length;i++){var h=e[i];c.push(1==h?p()|p():2==h?p()&p():h?o(h,r):!p())}return!!p()},i=(e,r)=>e&&E.o(e,r),l=e=>(e.loaded=1,e.get()),u=e=>Object.keys(e).reduce((r,t)=>(e[t].eager&&(r[t]=e[t]),r),{}),f=(e,r,t,n)=>{var i=n?u(e[r]):e[r];return(r=Object.keys(i).reduce((e,r)=>!o(t,r)||e&&!a(e,r)?e:r,0))&&i[r]},s=(e,r,t)=>{var n=t?u(e[r]):e[r];return Object.keys(n).reduce((e,r)=>!e||!n[e].loaded&&a(e,r)?r:e,0)},d=(e,r,t,a)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+n(a)+")",c=(e,r,t,a,o)=>{var i=e[t];return"No satisfying version ("+n(a)+")"+(o?" for eager consumption":"")+" of shared module "+t+" found in shared scope "+r+".\nAvailable versions: "+Object.keys(i).map(e=>e+" from "+i[e].from).join(", ")},p=e=>{throw new Error(e)},h=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},v=(e,r,t)=>t?t():((e,r)=>p("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),m=(b=e=>function(r,t,a,n,o){var i=E.I(r);return i&&i.then&&!a?i.then(e.bind(e,r,E.S[r],t,!1,n,o)):e(r,E.S[r],t,a,n,o)})((e,r,t,a,n,o)=>{if(!i(r,t))return v(e,t,o);var u=f(r,t,n,a);return u?l(u):o?o():void p(c(r,e,t,n,a))}),y=b((e,r,t,a,n,u)=>{if(!i(r,t))return v(e,t,u);var f=s(r,t,a);return o(n,f)||h(d(r,t,f,n)),l(r[t][f])}),g={},j={3345:()=>y("default","react",!1,[1,18,2,0]),520:()=>y("default","@jupyterlab/coreutils",!1,[1,6,5,5]),1247:()=>y("default","@lumino/widgets",!1,[1,2,3,1,,"alpha",1]),1650:()=>y("default","@jupyterlab/launcher",!1,[1,4,5,5]),3476:()=>y("default","@jupyterlab/application",!1,[1,4,5,5]),3539:()=>y("default","@jupyterlab/settingregistry",!1,[1,4,5,5]),3712:()=>m("default","@tanstack/react-query",!1,[1,5,32,0],()=>E.e(946).then(()=>()=>E(5946))),3895:()=>y("default","@jupyterlab/statusbar",!1,[1,4,5,5]),6167:()=>y("default","@lumino/commands",!1,[1,2,0,1]),6200:()=>y("default","@jupyterlab/filebrowser",!1,[1,4,5,5]),6511:()=>y("default","@jupyterlab/apputils",!1,[1,4,6,5]),6641:()=>y("default","@jupyterlab/notebook",!1,[1,4,5,5]),6911:()=>y("default","@jupyterlab/statedb",!1,[1,4,5,5]),6965:()=>y("default","@jupyterlab/ui-components",!1,[1,4,5,5]),6979:()=>y("default","@jupyterlab/translation",!1,[1,4,5,5]),7034:()=>m("default","@tanstack/react-query-devtools",!1,[1,5,32,0],()=>E.e(695).then(()=>()=>E(6695))),7628:()=>y("default","react-dom",!1,[1,18,2,0]),9080:()=>y("default","@jupyterlab/mainmenu",!1,[1,4,5,5]),9519:()=>y("default","@jupyterlab/services",!1,[1,7,5,5])},w={345:[3345],909:[520,1247,1650,3476,3539,3712,3895,6167,6200,6511,6641,6911,6965,6979,7034,7628,9080,9519]},k={},E.f.consumes=(e,r)=>{E.o(w,e)&&w[e].forEach(e=>{if(E.o(g,e))return r.push(g[e]);if(!k[e]){var t=r=>{g[e]=0,E.m[e]=t=>{delete E.c[e],t.exports=r()}};k[e]=!0;var a=r=>{delete g[e],E.m[e]=t=>{throw delete E.c[e],r}};try{var n=j[e]();n.then?r.push(g[e]=n.then(t).catch(a)):t(n)}catch(e){a(e)}}})},(()=>{E.b="undefined"!=typeof document&&document.baseURI||self.location.href;var e={371:0};E.f.j=(r,t)=>{var a=E.o(e,r)?e[r]:void 0;if(0!==a)if(a)t.push(a[2]);else if(345!=r){var n=new Promise((t,n)=>a=e[r]=[t,n]);t.push(a[2]=n);var o=E.p+E.u(r),i=new Error;E.l(o,t=>{if(E.o(e,r)&&(0!==(a=e[r])&&(e[r]=void 0),a)){var n=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+n+": "+o+")",i.name="ChunkLoadError",i.type=n,i.request=o,a[1](i)}},"chunk-"+r,r)}else e[r]=0};var r=(r,t)=>{var a,n,[o,i,l]=t,u=0;if(o.some(r=>0!==e[r])){for(a in i)E.o(i,a)&&(E.m[a]=i[a]);l&&l(E)}for(r&&r(t);u<o.length;u++)n=o[u],E.o(e,n)&&e[n]&&e[n][0](),e[n]=0},t=self.webpackChunkcalkit=self.webpackChunkcalkit||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),E.nc=void 0;var T=E(1336);(_JUPYTERLAB=void 0===_JUPYTERLAB?{}:_JUPYTERLAB).calkit=T})();
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: calkit-python
3
- Version: 0.41.14
3
+ Version: 0.41.16
4
4
  Summary: Reproducibility simplified.
5
5
  Project-URL: Homepage, https://calkit.org
6
6
  Project-URL: Issues, https://github.com/calkit/calkit/issues
@@ -5,34 +5,34 @@ calkit/check.py,sha256=VVJErHeCzX-bcdRtu0dgMpLLMU7M38SZI_wqyMgdzwg,9080
5
5
  calkit/cloud.py,sha256=Zd-Az72Gao_DuLnL2TueRQQ95SpD8oFKp0Bahb-PYXc,16802
6
6
  calkit/conda.py,sha256=Jb1Fz3jwkk4SZ0NgoWXd4lf50gEC8d8U87ElrYjdiwk,30752
7
7
  calkit/config.py,sha256=tkrdAQU1m3__B9efpYgK6IhtLtS3WWFcVpY2zEQpBio,7573
8
- calkit/core.py,sha256=On8UGUuR0vKBJHdhEvsPJAk5QD9Lzf3LuWl_gmdfUrI,28179
8
+ calkit/core.py,sha256=ferYD6jmvbZ1h1MlNApmuHiMokrLPynWtuU62wudzsc,28534
9
9
  calkit/datasets.py,sha256=9lOlOreey2aJGMw6MiV9MDzSHp-fYkCf1k1AZbWTdQE,2235
10
10
  calkit/dependencies.py,sha256=y5N_4ctwp9rFZVg82Si9_2x8Yh64Ex-WqP0iiN5NNBE,13276
11
- calkit/detect.py,sha256=Y70oOw6p-MEIwwxmm-FVZUS9YgPmahcqbQjSHbnLxsc,76416
11
+ calkit/detect.py,sha256=u8dIFX1oFZ-LJexsXB-bM91yi9XtRrRr5ZUGd7LKTuw,79373
12
12
  calkit/docker.py,sha256=EEcgbXjs-1ttixAZ-G-hmDlvnqSxYbIZmbzeAza37TI,15706
13
- calkit/environments.py,sha256=y30tKE0KSRM9ANQwrI15ej2BaXe4s0kUeDm1Vr_BhXA,77230
13
+ calkit/environments.py,sha256=PoAzpD3Esuupn3AGnR8J9ffvjHcxdBH1SQTXLxB1fY0,78060
14
14
  calkit/fs.py,sha256=dKWUfi51F1ka7uikn_O__jxvPOUwY05ZmydxE1KaJUA,45459
15
15
  calkit/git.py,sha256=rHAWdI13vA35m7q-MhlHj2KHMa42s4ecisTvKbdXKMw,13321
16
16
  calkit/github.py,sha256=8_yZ0ej8TKM4qk8iPeGq47R1I4MHdSoJ_Zpr1gRwNEM,1701
17
17
  calkit/gui.py,sha256=2UCrMyosc5v4CLlDcHO7oDwW9eLn5_WbNaLaG5bjMTc,23
18
18
  calkit/install.py,sha256=n989NLyZSTndj_rEaHceSip5aXP7zZIlG9QwbFs8kTc,7913
19
19
  calkit/invenio.py,sha256=aJH04-X9eFzxnrnbrPs71XBeFYBgKYYPiVM2KtyCXEE,4294
20
- calkit/julia.py,sha256=1025h3vC2WkvwSbatOHpemNX5SexwngwMLIZmkeCOcY,4491
20
+ calkit/julia.py,sha256=4GxUSItNXpYcRlCD9N8dLiuX44-HQaKFcuEudD8At1I,5640
21
21
  calkit/jupyter.py,sha256=lE8kKmauHqspY7aPOiNpLGQyw9jSn4taGZScm6aR87I,1157
22
22
  calkit/licenses.py,sha256=3ZNBFxwRrfSjx3poG3G07z7GRCKAkdWiZ1AFe1yr8Gg,6853
23
23
  calkit/magics.py,sha256=LrwJP7f84AHgKPSzmKmQjxRqzV_ATAUcpt6bHBdyDLQ,12151
24
24
  calkit/matlab.py,sha256=wa7tjD5m8Xghwfj9u0oaYbuZ702CScvgk7pItFeTXoU,26061
25
- calkit/notebooks.py,sha256=obKRJaVf-rrQslcu76yx8lP18QKWGJOD_Irw7yHw9ek,8913
25
+ calkit/notebooks.py,sha256=SGrVF_O5WmghMi7axr2WzHb4xLK7f6OmqTDMxQ5V5EY,8845
26
26
  calkit/office.py,sha256=mZqCxkIsmWthBAeJZ5SiGe21fn5zD2zXsy1E6nQIpWE,1291
27
27
  calkit/ops.py,sha256=lLlZAFeplU8uLeMavr99d__Iof-YYwKijus74WfLipk,854
28
28
  calkit/overleaf.py,sha256=5SI8HwglLVtVlzj7d15Bft_3ZC6AHn3IrAbqMKOAPX0,30170
29
- calkit/pipeline.py,sha256=qnfwnzze-PxF_2iFAx0zmkEHyfxqGgcFud_nV9txA54,65659
29
+ calkit/pipeline.py,sha256=U28GDnnjU4sQXxiEegEgFXkuW4mvVAwZR2CQVyaH2fY,66853
30
30
  calkit/releases.py,sha256=JSmVX2s28WVJ5Lwitwjq5eRgUHZ38k6VbehEK-dR4mg,17478
31
31
  calkit/server.py,sha256=GQm1bI5Q1NLFNwQL-iP6j4iWKUpKpP3cLEyfj5IMwe4,22009
32
32
  calkit/cli/__init__.py,sha256=pP_1wFuewdUUvEWTtaEcdKblPaSYuTvthQb2Ef08UMo,1921
33
- calkit/cli/check.py,sha256=eGh9lOuAw7FhJxlZlNcZx3HPBpDSMCRz7FlspeCCObI,49240
33
+ calkit/cli/check.py,sha256=tdiKIODqDn5y3TjfIA7JyunycPfDINpuRSOEIkYbMPY,49307
34
34
  calkit/cli/cloud.py,sha256=mMFRRFvo8MrzxQ-B_WTCSI8-erAWnFzgN2Hd0kVvB24,1815
35
- calkit/cli/config.py,sha256=cTNEM0AmwTPmxWcnXdWp4VdO_073ye74bLcuXSttNsQ,10340
35
+ calkit/cli/config.py,sha256=MebDQvIByXS0q2Ci-_jPBfJWNI9TKo7JBF7pFfL4L7k,12493
36
36
  calkit/cli/core.py,sha256=oICn4rLv9316X5bThD1Wrcc_qdmBHqye8NE4dIwojmk,3031
37
37
  calkit/cli/delete.py,sha256=aI2mfEpzMT3bKYqgqz0aAui4UZfdbGJJpgYwiw41jTE,954
38
38
  calkit/cli/describe.py,sha256=g6WWqa4nHSDda1Jt4D1g-VUm1k4bMskBfgKwDAhUokU,1817
@@ -40,14 +40,14 @@ calkit/cli/dev.py,sha256=cu4QsfDiA_KurGx4WJcgA2qgfghMoZutE8kl4IZbUcM,1070
40
40
  calkit/cli/import_.py,sha256=5j2ANLq6v7HOku9kJUWylwvQnJfkb_OUKCNOdv1BQXU,18264
41
41
  calkit/cli/latex.py,sha256=dhcbVNcR3YjVSD5mo-OBtZN1FhH4mv4mlGnuP19EbjU,6220
42
42
  calkit/cli/list.py,sha256=rV3gCVvoZ4CkUYXYfNRow11-AV99b98w7sabcDq08uk,10521
43
- calkit/cli/new.py,sha256=SP1zI5JNkoNRZ_vBFaJ6Byu_e46iKjp7Tyu64RdXTQE,129083
44
- calkit/cli/notebooks.py,sha256=W2EHNLMOx6EuqE1dSFirud6puwayXlg4T8DcC_lSEos,21423
43
+ calkit/cli/new.py,sha256=OtXcXmhtywXM54dgyjxQgtYEfCYBAi_UVKvMlPjSI8k,129111
44
+ calkit/cli/notebooks.py,sha256=QV3_0wwgIm9cdTBM7CwlUth1xbTnPGp2LJUFVSAoBm4,21941
45
45
  calkit/cli/office.py,sha256=jVSTvbl91TCzlglST5O2b8hdApZA_QHw_UiEQFJqxdc,1754
46
46
  calkit/cli/overleaf.py,sha256=bS-SkYsSqqhC9YsP-NCzpYJCGS3820w18w_s8JOFihg,25216
47
47
  calkit/cli/scheduler.py,sha256=Mn_G2tx78A_wJ3XYE0hRtTkPj376sKTWpSxMZKirHuA,38660
48
- calkit/cli/update.py,sha256=Ly5kkWqbZg5VSDmcoCOYdKiPDzXfs_kk3au-JJT9cH0,43471
48
+ calkit/cli/update.py,sha256=KaO-VbUxMpR_a9FV42HG8d9p-iaA4-2-L-fAgb2LiYI,43499
49
49
  calkit/cli/main/__init__.py,sha256=qu1POZPyqs33ZKfasOxv_Wc-EzVcEgK3Dt_vwFL8Bi8,65
50
- calkit/cli/main/core.py,sha256=TTkWxU_oxoInxxh2VJkHrT-lGAby_6MtqSxeyuOR2wI,123152
50
+ calkit/cli/main/core.py,sha256=MMKEAI4bAJm1YJcS4E5MTQAR-XluwtGn3wlsNYjK-NE,123878
51
51
  calkit/cli/main/xr.py,sha256=Bug5qJuiIq0tusmdu30t-ZdyaT_Sy5RwPSTiHvCtIqE,25600
52
52
  calkit/dvc/__init__.py,sha256=-B6tilCb_jw7QFmJ2srILez24wDhNKUzIBhZUMt01dE,381
53
53
  calkit/dvc/core.py,sha256=p42i_uCzgZD6ClGnLOn88zaiGTwrdSG09quL8Hb8Aas,30908
@@ -74,22 +74,22 @@ calkit/tests/test_calc.py,sha256=h-wOUIRqfyi06LTzhY_rS-a1xChZTkmwdEfBybMUM7E,203
74
74
  calkit/tests/test_check.py,sha256=ngRZq1_dSRQoa0oSjcx_QpR94omG9Bpas_h0wqjwrK0,1475
75
75
  calkit/tests/test_cloud.py,sha256=yCSuMq0o-i-kN-uWdvbSSBfz6mgKpS_hC3cFA-4DKjs,13542
76
76
  calkit/tests/test_conda.py,sha256=t-Oy1mL3Vb5njj_t3LQ8HwMhsmLN64HAzSlebnN1A9s,14204
77
- calkit/tests/test_core.py,sha256=qZ090GbIu-gqu-0qJqwPj47t7k1Q_N_5mv0sT2VAeZc,5496
77
+ calkit/tests/test_core.py,sha256=CMhTx89ZpaHTK4nvll2S5M0NLJoBAuH_OBPhq9oEFfM,6272
78
78
  calkit/tests/test_dependencies.py,sha256=HskXLzbvvRJz2TXnKPpPAo9lPhoZGYQFy4BmEF31nvk,11139
79
- calkit/tests/test_detect.py,sha256=LXQ0agLAGfFth2u36-nXSXu3qIyx7NXOdZc0GYGdVeI,39793
79
+ calkit/tests/test_detect.py,sha256=lZvxUJbGI9RaMFIZQCbrX8S9KxRHmSblAxYnVhZtnVo,41720
80
80
  calkit/tests/test_docker.py,sha256=27d0K1GOWiCNv_5TkZSob6EaC0EtQ9DSBIDfkEAEzGk,2077
81
81
  calkit/tests/test_environments.py,sha256=ms8avkZy70QPD4GSWAFa04O5rhd2oePaBeo0CBgnXAo,40300
82
82
  calkit/tests/test_fs.py,sha256=merhHGLypHTI4qP5qp0L9QFzKtJ7NcOQ2WzH4_CUnF0,13635
83
83
  calkit/tests/test_git.py,sha256=bxPVTn-0DD9mhlarBjEnm6V8XSJHfvm2etl9Z5Pq-qg,15076
84
84
  calkit/tests/test_install.py,sha256=Yh7-OFQ2L4S0uRkYPfQpqe4V2y1A9DwZddMEi8pQbMU,9228
85
85
  calkit/tests/test_invenio.py,sha256=aDI8cFOUUQEkfgHSK_Nv1cMNMsnmNr2W2weuZhrxshI,998
86
- calkit/tests/test_julia.py,sha256=wZ0K-m9xVdK7i4W5YI4Gqe0--r4MnTik94dhTF8ZgfU,4552
86
+ calkit/tests/test_julia.py,sha256=q7UYIJAvCQ8b6eMaledcV3rV3PPL6hDnpYJEzn2tmCE,6390
87
87
  calkit/tests/test_jupyter.py,sha256=YTL6zI740UM2KUjskSipzvSKxsyQ8rVPFQIX5cb2tMQ,114
88
88
  calkit/tests/test_licenses.py,sha256=AxoydeUG1Z6rDVeJJ64YXZ8xCJeyQyJdq6GSkDjlwJE,4001
89
89
  calkit/tests/test_magics.py,sha256=1O_hYPLMBVQJj8rLCFV3sU2DejlLLnk6Tv4_Tr-FZHQ,2101
90
90
  calkit/tests/test_matlab.py,sha256=7yFKOYDCtxYIkRyFOwTIxlvzx03Pac0FKGIUT4Zx7zA,7589
91
91
  calkit/tests/test_notebooks.py,sha256=6xUpHjCbCv08oJkArj-NkTUMlz9zzm8pf1qam3s27M0,2692
92
- calkit/tests/test_pipeline.py,sha256=SDxZZs3P1yaS2I5zouvScvGSvzpcWuQ0TKZWWih4YAU,84761
92
+ calkit/tests/test_pipeline.py,sha256=URq6n37NLLcw6AXlKPXE5EIgAAt8iK5RQUBjvzfbmtc,86847
93
93
  calkit/tests/test_releases.py,sha256=if6OjeZbUtjyyZ2CMQWhxQPRR8QD2kv5CXkEbOAL9Kc,14199
94
94
  calkit/tests/test_templates.py,sha256=RN4RIgmuFlmFYQFgfJUYS-KaLmie8fnWzmfg5Ay1egk,430
95
95
  calkit/tests/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -114,6 +114,7 @@ calkit/tests/dvc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
114
114
  calkit/tests/dvc/test_core.py,sha256=ywXBEyw1XpoSuu1EnMbFl-ebbdEJjpaBEPG157_3RBY,12914
115
115
  calkit/tests/dvc/test_zip.py,sha256=H6L3uJdUpsaNQcOPKnmRxMb5Imr_MaDQFoAnMrZMErU,11212
116
116
  calkit/tests/jupyterlab/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
117
+ calkit/tests/jupyterlab/conftest.py,sha256=Xsh5Zwg0fdqEFPkHDgG7oCHhHJj6WuZLWbmsl7ATABA,207
117
118
  calkit/tests/jupyterlab/test_routes.py,sha256=TEEyoHiKnzzKOkQsJcMPKdqpKFQ25yBCvVSquqe9nsE,408
118
119
  calkit/tests/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
119
120
  calkit/tests/models/test_core.py,sha256=EY-8CRR-Nh6PIjqRfAvyz_An3fkf6Hzk7ed4da02afg,872
@@ -122,23 +123,23 @@ calkit/tests/models/test_pipeline.py,sha256=AJsMAX4928U3_Omt0_8K96U5LU_KoNErgpHe
122
123
  calkit/agent_skills/add-pipeline-stage/SKILL.md,sha256=kpYYb-rJsS4B9p1Ub3gL21irCG5ZZ4DvWZpdJeEwPZk,3772
123
124
  calkit/agent_skills/conventions/SKILL.md,sha256=nAM2FjSMk6ED56Dr5zh2bL_dhfzDra-h2ZgzHU7ruS4,9524
124
125
  calkit/agent_skills/create-pipeline/SKILL.md,sha256=2FGw1iDQVRk5FUq79GP3lPdgvgof2Wi1O9O-abGGtgs,5422
125
- calkit_python-0.41.14.data/data/etc/jupyter/jupyter_server_config.d/calkit.json,sha256=CWrZP--JDGz8fsvbAlKr_duTL1vDriGT48SL1YCtkY0,81
126
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/package.json,sha256=INxx8QhvSl7KP6yL7A7-m2-FIsnB__mYEWG_Eq5RP-c,6651
127
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/schemas/calkit/package.json.orig,sha256=6w0b5y8LztEv2tErajJ5d39yBryld02crX0CG3zZFNs,6509
128
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/schemas/calkit/plugin.json,sha256=YSpIrwpwB-lQBk9Mwv-npedWTOOZreO6nlWZhqlWyGI,840
129
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/static/502.9a2c5772a15466e923ef.js,sha256=mixXcqFUZukj7_jQVxHTavsYl7cdZv83sEe4phJgkh0,59893
130
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/static/695.2c41003a452d43d2b358.js,sha256=LEEAOkUtQ9KzWEHn-QFv5yGi70B-sBOnrvztzHr0Shw,223
131
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/static/867.a42a046aa5108f54f8fb.js,sha256=pCoEaqUQj1T4-zAc9SUd__9ZGm7uL2wHQve2xwL89NI,8156
132
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/static/909.e3f9cc3408834a7fdcc3.js,sha256=4_nMNAiDSn_cw7UjIHEwone4fizrvqrqSgbwUWvjmoU,114571
133
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/static/946.050af2abf7845cfbdbd2.js,sha256=n_a0yu_gE7l6fauyoXXv9BZ7ZEYt2387mTfs7CbLcs4,51939
134
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt,sha256=eNJ8gc9n9IF8nW1d9sI9niuHstYzjNz5vqXx9UgWSPc,249
135
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/static/b2f1c3efe70cb539d121.png,sha256=svHD7-cMtTnRITFwugwsVaB9nZ-h8A61ose8z32HiRE,24850
136
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/static/remoteEntry.65469af996e7a96aa983.js,sha256=ZUaa-ZbnqWqpgxIpcQcRX7auvhOBkPH2YHNID5udGQs,8737
137
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/static/style.js,sha256=r89Jlk5v1drcwhCpv9FnC3Jig_JH4k24kZNIvxh6Htk,149
138
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/static/third-party-licenses.json,sha256=2BGdItLJwO3fAopLl4eJvp26TEwuscYC0-yFaF-LaLU,13683
139
- calkit_python-0.41.14.data/data/share/jupyter/labextensions/calkit/install.json,sha256=DK9d8G-q-rMVlcT3rAeGIyo3REWKw6FySBZceLU9yaw,187
140
- calkit_python-0.41.14.dist-info/METADATA,sha256=sMV9XX6sZtJ_8h7mnG-yYd1Q3mPT82X-dE52qO2WP3o,12071
141
- calkit_python-0.41.14.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
142
- calkit_python-0.41.14.dist-info/entry_points.txt,sha256=2iQBBzjTAOdk66CwS-f3ecXXW5DjUqkG1KBRj8mHI1I,133
143
- calkit_python-0.41.14.dist-info/licenses/LICENSE,sha256=9ZamCaSUTZk9rcrnf-sWFKLOHr3ws-S_dgKMegW4nw8,1056
144
- calkit_python-0.41.14.dist-info/RECORD,,
126
+ calkit_python-0.41.16.data/data/etc/jupyter/jupyter_server_config.d/calkit.json,sha256=CWrZP--JDGz8fsvbAlKr_duTL1vDriGT48SL1YCtkY0,81
127
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/package.json,sha256=39PQEWOzw0qtqD7Kep3OPbUR113vI7CG5TeD84v7clM,6224
128
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/schemas/calkit/package.json.orig,sha256=NBG3t7gFYb6_2J-yPU29qS7Ck9pYgm8-wC5Q1LrzQeI,6082
129
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/schemas/calkit/plugin.json,sha256=YSpIrwpwB-lQBk9Mwv-npedWTOOZreO6nlWZhqlWyGI,840
130
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/static/502.9a2c5772a15466e923ef.js,sha256=mixXcqFUZukj7_jQVxHTavsYl7cdZv83sEe4phJgkh0,59893
131
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/static/695.2c41003a452d43d2b358.js,sha256=LEEAOkUtQ9KzWEHn-QFv5yGi70B-sBOnrvztzHr0Shw,223
132
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/static/867.a42a046aa5108f54f8fb.js,sha256=pCoEaqUQj1T4-zAc9SUd__9ZGm7uL2wHQve2xwL89NI,8156
133
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/static/909.e3f9cc3408834a7fdcc3.js,sha256=4_nMNAiDSn_cw7UjIHEwone4fizrvqrqSgbwUWvjmoU,114571
134
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/static/946.050af2abf7845cfbdbd2.js,sha256=n_a0yu_gE7l6fauyoXXv9BZ7ZEYt2387mTfs7CbLcs4,51939
135
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/static/946.050af2abf7845cfbdbd2.js.LICENSE.txt,sha256=eNJ8gc9n9IF8nW1d9sI9niuHstYzjNz5vqXx9UgWSPc,249
136
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/static/b2f1c3efe70cb539d121.png,sha256=svHD7-cMtTnRITFwugwsVaB9nZ-h8A61ose8z32HiRE,24850
137
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/static/remoteEntry.ac9035764d5b2adbb542.js,sha256=rJA1dk1bKtu1QkURrp5HEC2WCxzZppPV1SCYz-35GTc,8737
138
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/static/style.js,sha256=r89Jlk5v1drcwhCpv9FnC3Jig_JH4k24kZNIvxh6Htk,149
139
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/static/third-party-licenses.json,sha256=2BGdItLJwO3fAopLl4eJvp26TEwuscYC0-yFaF-LaLU,13683
140
+ calkit_python-0.41.16.data/data/share/jupyter/labextensions/calkit/install.json,sha256=DK9d8G-q-rMVlcT3rAeGIyo3REWKw6FySBZceLU9yaw,187
141
+ calkit_python-0.41.16.dist-info/METADATA,sha256=tUnzT2-o6dU9IUfQO05ysFgxfISBJ4ws9pUnrNGZU9Q,12071
142
+ calkit_python-0.41.16.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
143
+ calkit_python-0.41.16.dist-info/entry_points.txt,sha256=2iQBBzjTAOdk66CwS-f3ecXXW5DjUqkG1KBRj8mHI1I,133
144
+ calkit_python-0.41.16.dist-info/licenses/LICENSE,sha256=9ZamCaSUTZk9rcrnf-sWFKLOHr3ws-S_dgKMegW4nw8,1056
145
+ calkit_python-0.41.16.dist-info/RECORD,,