Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cluv/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import subprocess
import sys
import typing
from pathlib import Path
from typing import Callable

import rich
Expand Down Expand Up @@ -215,6 +216,14 @@ def add_init_args(subparsers: Subparsers) -> argparse.ArgumentParser:
help="Initialize the current project across clusters.",
formatter_class=rich_argparse.RichHelpFormatter,
)
init_parser.add_argument(
"path",
nargs="?",
default=None,
metavar="<path>",
type=Path,
help="Path to initialize the project in. Creates the directory if it doesn't exist. Defaults to the current directory.",
)
init_parser.set_defaults(func=init)
return init_parser

Expand Down
6 changes: 5 additions & 1 deletion cluv/cli/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
REPO_ROOT = Path(__file__).resolve().parents[2]


def init() -> None:
def init(path: Path | None = None) -> None:
"""Initialize a new project with cluv.

This does the following:
Expand All @@ -29,6 +29,10 @@ def init() -> None:
console.rule("[bold cyan]cluv init[/bold cyan]")
console.print()

if path is not None:
path.mkdir(parents=True, exist_ok=True)
os.chdir(path)

# Check if the current directory is under the home directory. If not, raise an error and exit.
check_home_dir()

Expand Down
53 changes: 53 additions & 0 deletions tests/test_init.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Unit tests for cluv/cli/init.py check functions."""

import importlib
import shutil
import textwrap
from pathlib import Path

Expand All @@ -14,6 +15,7 @@
check_home_dir,
check_job_script,
check_symlink_to_scratch,
init,
)
from cluv.config import load_cluv_config

Expand Down Expand Up @@ -199,3 +201,54 @@ def test_replace_results_dir_from_legacy_template(
assert 'results_path="outputs"' in generated_legacy_script_content
assert "Using $results_path" in generated_legacy_script_content
assert "results_dir" not in generated_legacy_script_content


class TestInitIntegration:
"""Integration tests for the init() function that run the full init flow locally."""

@pytest.mark.skipif(shutil.which("uv") is None, reason="uv is not installed")
def test_init_with_path_creates_project(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""init(path=<name>) creates and initializes a project directory end-to-end."""
fake_home = tmp_path / "home"
fake_home.mkdir()
new_project = fake_home / "my_project"

monkeypatch.setattr(Path, "home", lambda: fake_home)
monkeypatch.delenv("SCRATCH", raising=False)
monkeypatch.chdir(tmp_path) # ensures cwd is restored after the test

init(path=new_project)

assert new_project.is_dir()
pyproject_path = new_project / "pyproject.toml"
assert pyproject_path.exists()

config = load_cluv_config(pyproject_path)
assert config.results_path is not None

assert (new_project / "scripts").is_dir()
assert (new_project / JOB_SCRIPT_PATH).exists()

@pytest.mark.skipif(shutil.which("uv") is None, reason="uv is not installed")
def test_init_without_path_uses_cwd(
self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""init() without a path argument runs in the current directory."""
fake_home = tmp_path / "home"
project_dir = fake_home / "my_project"
project_dir.mkdir(parents=True)

monkeypatch.setattr(Path, "home", lambda: fake_home)
monkeypatch.delenv("SCRATCH", raising=False)
monkeypatch.chdir(project_dir)

init()

pyproject_path = project_dir / "pyproject.toml"
assert pyproject_path.exists()

config = load_cluv_config(pyproject_path)
assert config.results_path is not None

Loading