msw-core 0.2.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.
Files changed (57) hide show
  1. msw_core-0.2.0/.forgejo/workflows/ci.yml +41 -0
  2. msw_core-0.2.0/.githooks/fix_commit_msg.py +52 -0
  3. msw_core-0.2.0/.github/workflows/ci.yml +41 -0
  4. msw_core-0.2.0/.github/workflows/release.yml +73 -0
  5. msw_core-0.2.0/.gitignore +72 -0
  6. msw_core-0.2.0/.pre-commit-config.yaml +49 -0
  7. msw_core-0.2.0/LICENSE +0 -0
  8. msw_core-0.2.0/PKG-INFO +47 -0
  9. msw_core-0.2.0/README.md +11 -0
  10. msw_core-0.2.0/VERSION +1 -0
  11. msw_core-0.2.0/pyproject.toml +112 -0
  12. msw_core-0.2.0/src/murineshiftwork/_version.py +24 -0
  13. msw_core-0.2.0/src/murineshiftwork/cli/__init__.py +68 -0
  14. msw_core-0.2.0/src/murineshiftwork/cli/__main__.py +4 -0
  15. msw_core-0.2.0/src/murineshiftwork/cli/defaults.py +44 -0
  16. msw_core-0.2.0/src/murineshiftwork/cli/evaluate.py +541 -0
  17. msw_core-0.2.0/src/murineshiftwork/cli/execute.py +460 -0
  18. msw_core-0.2.0/src/murineshiftwork/cli/parser.py +734 -0
  19. msw_core-0.2.0/src/murineshiftwork/cli/post.py +113 -0
  20. msw_core-0.2.0/src/murineshiftwork/cli/preflight.py +75 -0
  21. msw_core-0.2.0/src/murineshiftwork/cli/tasks.py +243 -0
  22. msw_core-0.2.0/src/murineshiftwork/hardware/__init__.py +0 -0
  23. msw_core-0.2.0/src/murineshiftwork/hardware/bpod/__init__.py +19 -0
  24. msw_core-0.2.0/src/murineshiftwork/hardware/bpod/actions.py +96 -0
  25. msw_core-0.2.0/src/murineshiftwork/hardware/bpod/device.py +54 -0
  26. msw_core-0.2.0/src/murineshiftwork/hardware/bpod/factory.py +205 -0
  27. msw_core-0.2.0/src/murineshiftwork/hardware/bpod/override.py +130 -0
  28. msw_core-0.2.0/src/murineshiftwork/hardware/bpod/sim.py +150 -0
  29. msw_core-0.2.0/src/murineshiftwork/hardware/bpod/ttl.py +35 -0
  30. msw_core-0.2.0/src/murineshiftwork/hardware/bpod/user_settings.py +22 -0
  31. msw_core-0.2.0/src/murineshiftwork/hardware/bpod/user_settings_8port.py +7 -0
  32. msw_core-0.2.0/src/murineshiftwork/hardware/bpod/valve.py +79 -0
  33. msw_core-0.2.0/src/murineshiftwork/hardware/camera/__init__.py +3 -0
  34. msw_core-0.2.0/src/murineshiftwork/hardware/camera/client.py +303 -0
  35. msw_core-0.2.0/src/murineshiftwork/hardware/host_session.py +70 -0
  36. msw_core-0.2.0/src/murineshiftwork/hardware/manager.py +98 -0
  37. msw_core-0.2.0/src/murineshiftwork/hardware/pulsepal/__init__.py +1 -0
  38. msw_core-0.2.0/src/murineshiftwork/hardware/pulsepal/device.py +62 -0
  39. msw_core-0.2.0/src/murineshiftwork/hardware/scale.py +182 -0
  40. msw_core-0.2.0/src/murineshiftwork/hardware/stimulation.py +507 -0
  41. msw_core-0.2.0/src/murineshiftwork/hooks/__init__.py +167 -0
  42. msw_core-0.2.0/src/murineshiftwork/logic/__init__.py +0 -0
  43. msw_core-0.2.0/src/murineshiftwork/logic/barcode.py +59 -0
  44. msw_core-0.2.0/src/murineshiftwork/logic/calibration.py +608 -0
  45. msw_core-0.2.0/src/murineshiftwork/logic/config/__init__.py +43 -0
  46. msw_core-0.2.0/src/murineshiftwork/logic/config/_defaults.py +15 -0
  47. msw_core-0.2.0/src/murineshiftwork/logic/config/ini.py +75 -0
  48. msw_core-0.2.0/src/murineshiftwork/logic/config/io.py +241 -0
  49. msw_core-0.2.0/src/murineshiftwork/logic/config/models.py +399 -0
  50. msw_core-0.2.0/src/murineshiftwork/logic/log.py +139 -0
  51. msw_core-0.2.0/src/murineshiftwork/logic/machine_config.py +171 -0
  52. msw_core-0.2.0/src/murineshiftwork/logic/maths.py +44 -0
  53. msw_core-0.2.0/src/murineshiftwork/logic/misc.py +71 -0
  54. msw_core-0.2.0/src/murineshiftwork/logic/paths.py +55 -0
  55. msw_core-0.2.0/src/murineshiftwork/logic/sounds.py +267 -0
  56. msw_core-0.2.0/src/murineshiftwork/logic/task_process.py +498 -0
  57. msw_core-0.2.0/src/murineshiftwork/logic/task_settings.py +99 -0
@@ -0,0 +1,41 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags-ignore: ["v*"]
7
+ pull_request:
8
+ branches: [main]
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ lint:
15
+ name: Lint
16
+ runs-on: ubuntu-latest
17
+ if: "!startsWith(github.event.head_commit.message, 'bump:')"
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - uses: astral-sh/setup-uv@v7
24
+ with:
25
+ python-version: "3.12"
26
+
27
+ - name: Run pre-commit (ruff + commitizen + gitleaks)
28
+ run: uvx pre-commit run --all-files --show-diff-on-failure
29
+
30
+ ci:
31
+ name: CI
32
+ runs-on: ubuntu-latest
33
+ needs: [lint]
34
+ if: always()
35
+ steps:
36
+ - name: Check required jobs
37
+ run: |
38
+ if [[ "${{ needs.lint.result }}" != "success" && "${{ needs.lint.result }}" != "skipped" ]]; then
39
+ echo "lint failed" && exit 1
40
+ fi
41
+ echo "All required checks passed."
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env python3
2
+ """Auto-fix mechanical commit message formatting issues before linting."""
3
+
4
+ import sys
5
+ from pathlib import Path
6
+
7
+
8
+ def fix(path: Path) -> None:
9
+ text = path.read_text()
10
+ lines = text.splitlines(keepends=True)
11
+
12
+ fixed = []
13
+ for i, line in enumerate(lines):
14
+ if line.startswith("#"):
15
+ fixed.append(line)
16
+ continue
17
+
18
+ # Strip trailing whitespace on every line
19
+ line = line.rstrip() + "\n"
20
+
21
+ # Strip leading whitespace from subject line (first content line)
22
+ if i == 0 or all(ln.startswith("#") for ln in lines[:i]):
23
+ line = line.lstrip()
24
+
25
+ # Convert hard tabs to four spaces
26
+ line = line.replace("\t", " ")
27
+
28
+ fixed.append(line)
29
+
30
+ # Ensure blank line between subject and body if body is present
31
+ first = next((i for i, ln in enumerate(fixed) if not ln.startswith("#")), None)
32
+ if first is not None and first + 1 < len(fixed):
33
+ next_content = next(
34
+ (
35
+ i
36
+ for i, ln in enumerate(fixed[first + 1 :], first + 1)
37
+ if not ln.startswith("#")
38
+ ),
39
+ None,
40
+ )
41
+ if (
42
+ next_content is not None
43
+ and next_content == first + 1
44
+ and fixed[first + 1].strip()
45
+ ):
46
+ fixed.insert(first + 1, "\n")
47
+
48
+ path.write_text("".join(fixed))
49
+
50
+
51
+ if __name__ == "__main__":
52
+ fix(Path(sys.argv[1]))
@@ -0,0 +1,41 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["**"]
6
+ tags-ignore: ["v*"]
7
+ pull_request:
8
+ branches: [main]
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ lint:
15
+ name: Lint
16
+ runs-on: ubuntu-latest
17
+ if: "!startsWith(github.event.head_commit.message, 'bump:')"
18
+ steps:
19
+ - uses: actions/checkout@v6
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - uses: astral-sh/setup-uv@v7
24
+ with:
25
+ python-version: "3.12"
26
+
27
+ - name: Run pre-commit (ruff + commitizen + gitleaks)
28
+ run: uvx pre-commit run --all-files --show-diff-on-failure
29
+
30
+ ci:
31
+ name: CI
32
+ runs-on: ubuntu-latest
33
+ needs: [lint]
34
+ if: always()
35
+ steps:
36
+ - name: Check required jobs
37
+ run: |
38
+ if [[ "${{ needs.lint.result }}" != "success" && "${{ needs.lint.result }}" != "skipped" ]]; then
39
+ echo "lint failed" && exit 1
40
+ fi
41
+ echo "All required checks passed."
@@ -0,0 +1,73 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+ inputs:
8
+ publish_only:
9
+ description: 'Skip version bump and publish the current VERSION tag'
10
+ type: boolean
11
+ default: false
12
+
13
+ permissions:
14
+ contents: write
15
+ id-token: write
16
+
17
+ jobs:
18
+ release:
19
+ name: Bump, build, publish
20
+ runs-on: ubuntu-latest
21
+ if: "!startsWith(github.event.head_commit.message, 'bump:')"
22
+ steps:
23
+ - uses: actions/checkout@v6
24
+ with:
25
+ fetch-depth: 0
26
+
27
+ - name: Configure git identity
28
+ run: |
29
+ git config user.name "github-actions[bot]"
30
+ git config user.email "github-actions[bot]@users.noreply.github.com"
31
+
32
+ - uses: astral-sh/setup-uv@v7
33
+ with:
34
+ python-version: "3.11"
35
+
36
+ - name: Bump version
37
+ id: bump
38
+ if: inputs.publish_only != true
39
+ run: |
40
+ uvx --from commitizen cz bump --yes || EXIT=$?
41
+ if [ "${EXIT:-0}" -eq 21 ]; then
42
+ echo "No bumpable commits since last tag — skipping."
43
+ echo "skipped=true" >> "$GITHUB_OUTPUT"
44
+ exit 0
45
+ fi
46
+ [ "${EXIT:-0}" -eq 0 ] || exit $EXIT
47
+ VERSION=$(cat VERSION)
48
+ git push origin HEAD:main
49
+ git push origin "v${VERSION}"
50
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
51
+
52
+ - name: Read version (publish-only mode)
53
+ id: read_version
54
+ if: inputs.publish_only == true
55
+ run: |
56
+ VERSION=$(cat VERSION)
57
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
58
+
59
+ - name: Build
60
+ if: steps.bump.outputs.skipped != 'true'
61
+ run: uv build
62
+
63
+ - name: Create GitHub release
64
+ if: steps.bump.outputs.skipped != 'true' && inputs.publish_only != true
65
+ uses: softprops/action-gh-release@v3
66
+ with:
67
+ tag_name: "v${{ steps.bump.outputs.version || steps.read_version.outputs.version }}"
68
+ files: dist/*
69
+ generate_release_notes: false
70
+
71
+ - name: Publish to PyPI
72
+ if: steps.bump.outputs.skipped != 'true'
73
+ run: uv publish --trusted-publishing automatic
@@ -0,0 +1,72 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ *.so
7
+ *.egg
8
+ *.egg-info/
9
+ dist/
10
+ build/
11
+ eggs/
12
+ parts/
13
+ var/
14
+ sdist/
15
+ develop-eggs/
16
+ .installed.cfg
17
+ lib/
18
+ lib64/
19
+ wheels/
20
+
21
+ # Virtual environments
22
+ .venv/
23
+ venv/
24
+ env/
25
+ ENV/
26
+ .python-version
27
+
28
+ # uv
29
+ uv.lock
30
+
31
+ # Distribution / packaging
32
+ MANIFEST
33
+
34
+ # Testing
35
+ .pytest_cache/
36
+ .coverage
37
+ .coverage.*
38
+ coverage.xml
39
+ htmlcov/
40
+ *.cover
41
+ .hypothesis/
42
+
43
+ # Type checking
44
+ .mypy_cache/
45
+ .dmypy.json
46
+ dmypy.json
47
+ .pytype/
48
+ .pyre/
49
+
50
+ # Build artifacts (hatch-vcs generated)
51
+ src/*/_version.py
52
+
53
+ # Jupyter
54
+ .ipynb_checkpoints
55
+ *.ipynb
56
+
57
+ # IDEs
58
+ .idea/
59
+ .vscode/
60
+ *.swp
61
+ *.swo
62
+ *~
63
+
64
+ # OS
65
+ .DS_Store
66
+ Thumbs.db
67
+
68
+ # Secrets / local config
69
+ .env
70
+ .env.*
71
+ !.env.example
72
+ site/
@@ -0,0 +1,49 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v6.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-toml
9
+ - id: check-merge-conflict
10
+ - id: mixed-line-ending
11
+
12
+ - repo: https://github.com/astral-sh/ruff-pre-commit
13
+ rev: v0.15.14
14
+ hooks:
15
+ - id: ruff
16
+ args: [--fix]
17
+ - id: ruff-format
18
+
19
+ - repo: https://github.com/pre-commit/mirrors-mypy
20
+ rev: v2.1.0
21
+ hooks:
22
+ - id: mypy
23
+ additional_dependencies: []
24
+
25
+ # commit-msg stage: fix first, then validate
26
+ - repo: local
27
+ hooks:
28
+ - id: fix-commit-msg
29
+ name: Auto-fix commit message formatting
30
+ language: system
31
+ entry: python3 .githooks/fix_commit_msg.py
32
+ stages: [commit-msg]
33
+
34
+ - repo: https://github.com/commitizen-tools/commitizen
35
+ rev: v4.16.2
36
+ hooks:
37
+ - id: commitizen
38
+ stages: [commit-msg]
39
+
40
+ - repo: https://github.com/jorisroovers/gitlint
41
+ rev: v0.19.1
42
+ hooks:
43
+ - id: gitlint
44
+ stages: [commit-msg]
45
+
46
+ - repo: https://github.com/gitleaks/gitleaks
47
+ rev: v8.30.0
48
+ hooks:
49
+ - id: gitleaks
msw_core-0.2.0/LICENSE ADDED
File without changes
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: msw-core
3
+ Version: 0.2.0
4
+ Summary: Murine Shift Work acquisition stack: CLI, hardware, hooks, and logic.
5
+ Project-URL: Homepage, https://github.com/MurineShiftWork/msw-core
6
+ Project-URL: Issue Tracker, https://github.com/MurineShiftWork/msw-core/issues
7
+ Author-email: "Lars B. Rollik" <lars@rollik.me>
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.12
10
+ Requires-Dist: matplotlib
11
+ Requires-Dist: msw-io>=1.0.2
12
+ Requires-Dist: msw-plugin-api>=0.1.0
13
+ Requires-Dist: numpy
14
+ Requires-Dist: one-axis-stage
15
+ Requires-Dist: pandas
16
+ Requires-Dist: pybpod-api
17
+ Requires-Dist: pydantic>=2.0
18
+ Requires-Dist: pyserial
19
+ Requires-Dist: pyyaml
20
+ Requires-Dist: rich
21
+ Requires-Dist: scipy
22
+ Requires-Dist: seaborn
23
+ Requires-Dist: sounddevice
24
+ Requires-Dist: ttl-barcoder>=0.4.1
25
+ Provides-Extra: dev
26
+ Requires-Dist: commitizen; extra == 'dev'
27
+ Requires-Dist: mypy; extra == 'dev'
28
+ Requires-Dist: pre-commit; extra == 'dev'
29
+ Requires-Dist: pytest-cov; extra == 'dev'
30
+ Requires-Dist: pytest>=8; extra == 'dev'
31
+ Requires-Dist: types-pyyaml; extra == 'dev'
32
+ Provides-Extra: docs
33
+ Requires-Dist: mkdocs-material; extra == 'docs'
34
+ Requires-Dist: mkdocstrings[python]; extra == 'docs'
35
+ Description-Content-Type: text/markdown
36
+
37
+ # msw-core
38
+
39
+ [![PyPI](https://img.shields.io/pypi/v/msw-core.svg)](https://pypi.org/project/msw-core)
40
+
41
+ Murine Shift Work acquisition stack: CLI, hardware drivers, session hooks, and task logic.
42
+
43
+ Provides the `murineshiftwork.cli`, `murineshiftwork.hardware`, `murineshiftwork.hooks`,
44
+ and `murineshiftwork.logic` namespace sub-packages. Installs the `msw` / `murineshiftwork`
45
+ CLI entry points.
46
+
47
+ Part of the [MurineShiftWork](https://github.com/MurineShiftWork) namespace package ecosystem.
@@ -0,0 +1,11 @@
1
+ # msw-core
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/msw-core.svg)](https://pypi.org/project/msw-core)
4
+
5
+ Murine Shift Work acquisition stack: CLI, hardware drivers, session hooks, and task logic.
6
+
7
+ Provides the `murineshiftwork.cli`, `murineshiftwork.hardware`, `murineshiftwork.hooks`,
8
+ and `murineshiftwork.logic` namespace sub-packages. Installs the `msw` / `murineshiftwork`
9
+ CLI entry points.
10
+
11
+ Part of the [MurineShiftWork](https://github.com/MurineShiftWork) namespace package ecosystem.
msw_core-0.2.0/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
@@ -0,0 +1,112 @@
1
+ [build-system]
2
+ requires = ["hatchling", "hatch-vcs"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "msw-core"
7
+ dynamic = ["version"]
8
+ description = "Murine Shift Work acquisition stack: CLI, hardware, hooks, and logic."
9
+ readme = { file = "README.md", content-type = "text/markdown" }
10
+ license = { file = "LICENSE" }
11
+ authors = [
12
+ { name = "Lars B. Rollik", email = "lars@rollik.me" },
13
+ ]
14
+ requires-python = ">=3.12"
15
+ dependencies = [
16
+ "msw-io>=1.0.2",
17
+ "msw-plugin-api>=0.1.0",
18
+ "ttl-barcoder>=0.4.1",
19
+ "pydantic>=2.0",
20
+ "rich",
21
+ "pyyaml",
22
+ "numpy",
23
+ "scipy",
24
+ "pandas",
25
+ "matplotlib",
26
+ "seaborn",
27
+ "pyserial",
28
+ "pybpod-api",
29
+ "one-axis-stage",
30
+ "sounddevice",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://github.com/MurineShiftWork/msw-core"
35
+ "Issue Tracker" = "https://github.com/MurineShiftWork/msw-core/issues"
36
+
37
+ [project.scripts]
38
+ murineshiftwork = "murineshiftwork.cli:run_cli"
39
+ msw = "murineshiftwork.cli:run_cli"
40
+
41
+ [project.optional-dependencies]
42
+ dev = [
43
+ "commitizen",
44
+ "pytest>=8",
45
+ "pytest-cov",
46
+ "pre-commit",
47
+ "mypy",
48
+ "types-PyYAML",
49
+ ]
50
+ docs = [
51
+ "mkdocs-material",
52
+ "mkdocstrings[python]",
53
+ ]
54
+
55
+ [tool.hatch.version]
56
+ source = "vcs"
57
+ fallback-version = "0.1.0"
58
+
59
+ [tool.hatch.build.hooks.vcs]
60
+ version-file = "src/murineshiftwork/_version.py"
61
+
62
+ [tool.hatch.build.targets.wheel]
63
+ packages = ["src/murineshiftwork"]
64
+
65
+ # ---------------------------------------------------------------------------
66
+ # Pytest
67
+ # ---------------------------------------------------------------------------
68
+
69
+ [tool.pytest.ini_options]
70
+ testpaths = ["tests"]
71
+ addopts = "--cov=src/murineshiftwork --durations=0"
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # Ruff
75
+ # ---------------------------------------------------------------------------
76
+
77
+ [tool.ruff]
78
+ line-length = 88
79
+ indent-width = 4
80
+ src = ["src"]
81
+
82
+ [tool.ruff.lint]
83
+ select = ["E", "W", "F", "I", "B", "N", "UP", "SIM", "PTH"]
84
+ ignore = ["E501", "B017", "N817"]
85
+ fixable = ["ALL"]
86
+
87
+ [tool.ruff.format]
88
+ quote-style = "double"
89
+ indent-style = "space"
90
+ line-ending = "auto"
91
+ docstring-code-format = true
92
+
93
+ # ---------------------------------------------------------------------------
94
+ # Mypy
95
+ # ---------------------------------------------------------------------------
96
+
97
+ [tool.mypy]
98
+ python_version = "3.12"
99
+ warn_return_any = false
100
+ ignore_missing_imports = true
101
+
102
+ # ---------------------------------------------------------------------------
103
+ # Commitizen
104
+ # ---------------------------------------------------------------------------
105
+
106
+ [tool.commitizen]
107
+ name = "cz_conventional_commits"
108
+ version_provider = "commitizen"
109
+ version = "0.2.0"
110
+ tag_format = "v$version"
111
+ update_changelog_on_bump = false
112
+ version_files = ["VERSION"]
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.2.0'
22
+ __version_tuple__ = version_tuple = (0, 2, 0)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1,68 @@
1
+ import logging
2
+ import sys
3
+
4
+ from murineshiftwork.cli.evaluate import evaluate_args
5
+ from murineshiftwork.cli.parser import parse_args
6
+ from murineshiftwork.hardware.bpod import patch_user_settings
7
+ from murineshiftwork.logic.log import patch_logging_levels
8
+
9
+
10
+ def _print_run_banner():
11
+ from importlib.metadata import version as _v
12
+
13
+ try:
14
+ ver = _v("murineshiftwork")
15
+ except Exception:
16
+ ver = "unknown"
17
+ print(f"msw {ver} | © Lars B. Rollik | PolyForm Internal Use 1.0.0")
18
+
19
+
20
+ def run_cli(*args):
21
+ """Command line interface for Murine Shift Work."""
22
+ patch_logging_levels()
23
+ patch_user_settings()
24
+
25
+ if not args:
26
+ args = sys.argv[1:]
27
+
28
+ if len(args) > 0 and not isinstance(args[0], str):
29
+ args = args[0]
30
+
31
+ if len(args) > 0 and str(args[0]).endswith(".py"):
32
+ _, args = args[0], args[1:]
33
+
34
+ if len(args) <= 1:
35
+ args = args + ["-h"]
36
+
37
+ args_dict = parse_args(args=args)
38
+
39
+ # These subcommands bypass evaluate_args (no hardware/subject/task context needed)
40
+ if args_dict.get("command") in (
41
+ "init",
42
+ "setup",
43
+ "subject",
44
+ "calibration",
45
+ "action",
46
+ "tasks",
47
+ "post",
48
+ ):
49
+ args_dict["func"](**args_dict)
50
+ logging.debug("EXITING CLI.")
51
+ return
52
+
53
+ if args_dict.get("command") == "run":
54
+ _print_run_banner()
55
+
56
+ args_dict = evaluate_args(args_dict=args_dict)
57
+
58
+ if "exit_flag" in args_dict:
59
+ return
60
+
61
+ # Call module
62
+ args_dict["func"](**args_dict)
63
+
64
+ logging.debug("EXITING CLI.")
65
+
66
+
67
+ if __name__ == "__main__":
68
+ run_cli()
@@ -0,0 +1,4 @@
1
+ from murineshiftwork.cli import run_cli
2
+
3
+ if __name__ == "__main__":
4
+ run_cli()
@@ -0,0 +1,44 @@
1
+ """Module-load-time defaults for the CLI.
2
+
3
+ Factored out of evaluate.py so parser.py can import them without importing the
4
+ full evaluate pipeline, avoiding any risk of a circular dependency.
5
+ """
6
+
7
+ from pathlib import Path
8
+
9
+ from murineshiftwork.cli.tasks import list_available_tasks
10
+ from murineshiftwork.logic.machine_config import (
11
+ resolve_config_dir,
12
+ resolve_data_dir,
13
+ )
14
+
15
+ default_out_path = resolve_data_dir()
16
+ default_config_dir = resolve_config_dir()
17
+
18
+ CALIBRATION_FILE_PATH = Path("~/.murineshiftwork").expanduser()
19
+ DEFAULT_CALIBRATION_FILE_LIQUID = str(
20
+ CALIBRATION_FILE_PATH / "calibration.liquid.default.csv"
21
+ )
22
+ DEFAULT_CALIBRATION_FILE_SOUND = "calibration.sound.default.csv"
23
+ DEFAULT_CALIBRATION_FILE_STAGE = str(
24
+ CALIBRATION_FILE_PATH / "calibration.stage.default.yaml"
25
+ )
26
+
27
+
28
+ def _build_task_list() -> str:
29
+ all_tasks = list_available_tasks()
30
+ main = sorted(t for t in all_tasks if not t.startswith("_"))
31
+ calibration = sorted(t for t in all_tasks if t.startswith("_calibration"))
32
+ tests = sorted(t for t in all_tasks if t.startswith("_test"))
33
+ lines = []
34
+ for heading, group in (
35
+ ("Tasks", main),
36
+ ("Calibration", calibration),
37
+ ("Tests", tests),
38
+ ):
39
+ lines.append(f" {heading}:")
40
+ lines.extend(f" - {t}" for t in group)
41
+ return "\n".join(lines)
42
+
43
+
44
+ available_tasks = _build_task_list()