milieux.cli.scaffold

 1from abc import ABC, abstractmethod
 2from dataclasses import dataclass, field
 3from pathlib import Path
 4import subprocess
 5from typing import Literal
 6
 7from fancy_dataclass.cli import CLIDataclass
 8
 9from milieux import logger
10from milieux.utils import run_command
11
12
13# utility for scaffolding
14ScaffoldUtility = Literal['hatch', 'uv']
15
16
17class Scaffolder(ABC):
18    """Class for setting up a project scaffold."""
19
20    @abstractmethod
21    def make_scaffold(self, base_dir: Path, project_name: str) -> None:
22        """Creates a scaffold for a new project in the given base directory."""
23
24
25class HatchScaffolder(Scaffolder):
26    """Project scaffolder that uses the 'hatch' command-line tool."""
27
28    def make_scaffold(self, base_dir: Path, project_name: str) -> None:  # noqa: D102
29        cmd = ['hatch', 'config', 'find']
30        config_path = subprocess.check_output(cmd, text=True).rstrip('\n')
31        logger.info(f'Using hatch configurations in {config_path}')
32        location = base_dir / project_name
33        cmd = ['hatch', 'new', project_name, str(location)]
34        run_command(cmd)
35
36
37class UVScaffolder(Scaffolder):
38    """Project scaffolder that uses the 'uv' command-line tool."""
39
40    def make_scaffold(self, base_dir: Path, project_name: str) -> None:  # noqa: D102
41        location = base_dir / project_name
42        if not location.exists():
43            logger.info(f'mkdir {location}')
44            location.mkdir()
45        cmd = ['uv', 'init', '--directory', str(location), '--verbose']
46        run_command(cmd)
47
48
49SCAFFOLDERS = {
50    'hatch': HatchScaffolder,
51    'uv': UVScaffolder,
52}
53
54
55@dataclass
56class ScaffoldCmd(CLIDataclass, command_name='scaffold'):
57    """Set up a project scaffold."""
58    project_name: str = field(metadata={'help': 'name of project'})
59    utility: ScaffoldUtility = field(default='hatch', metadata={'help': 'utility for creating the scaffold'})
60
61    def run(self) -> None:  # noqa: D102
62        cls = SCAFFOLDERS[self.utility]
63        scaffolder = cls()
64        logger.info(f'Creating new project {self.project_name!r} with {self.utility!r} utility')
65        scaffolder.make_scaffold(Path.cwd(), self.project_name)
ScaffoldUtility = typing.Literal['hatch', 'uv']
class Scaffolder(abc.ABC):
18class Scaffolder(ABC):
19    """Class for setting up a project scaffold."""
20
21    @abstractmethod
22    def make_scaffold(self, base_dir: Path, project_name: str) -> None:
23        """Creates a scaffold for a new project in the given base directory."""

Class for setting up a project scaffold.

@abstractmethod
def make_scaffold(self, base_dir: pathlib.Path, project_name: str) -> None:
21    @abstractmethod
22    def make_scaffold(self, base_dir: Path, project_name: str) -> None:
23        """Creates a scaffold for a new project in the given base directory."""

Creates a scaffold for a new project in the given base directory.

class HatchScaffolder(Scaffolder):
26class HatchScaffolder(Scaffolder):
27    """Project scaffolder that uses the 'hatch' command-line tool."""
28
29    def make_scaffold(self, base_dir: Path, project_name: str) -> None:  # noqa: D102
30        cmd = ['hatch', 'config', 'find']
31        config_path = subprocess.check_output(cmd, text=True).rstrip('\n')
32        logger.info(f'Using hatch configurations in {config_path}')
33        location = base_dir / project_name
34        cmd = ['hatch', 'new', project_name, str(location)]
35        run_command(cmd)

Project scaffolder that uses the 'hatch' command-line tool.

def make_scaffold(self, base_dir: pathlib.Path, project_name: str) -> None:
29    def make_scaffold(self, base_dir: Path, project_name: str) -> None:  # noqa: D102
30        cmd = ['hatch', 'config', 'find']
31        config_path = subprocess.check_output(cmd, text=True).rstrip('\n')
32        logger.info(f'Using hatch configurations in {config_path}')
33        location = base_dir / project_name
34        cmd = ['hatch', 'new', project_name, str(location)]
35        run_command(cmd)

Creates a scaffold for a new project in the given base directory.

class UVScaffolder(Scaffolder):
38class UVScaffolder(Scaffolder):
39    """Project scaffolder that uses the 'uv' command-line tool."""
40
41    def make_scaffold(self, base_dir: Path, project_name: str) -> None:  # noqa: D102
42        location = base_dir / project_name
43        if not location.exists():
44            logger.info(f'mkdir {location}')
45            location.mkdir()
46        cmd = ['uv', 'init', '--directory', str(location), '--verbose']
47        run_command(cmd)

Project scaffolder that uses the 'uv' command-line tool.

def make_scaffold(self, base_dir: pathlib.Path, project_name: str) -> None:
41    def make_scaffold(self, base_dir: Path, project_name: str) -> None:  # noqa: D102
42        location = base_dir / project_name
43        if not location.exists():
44            logger.info(f'mkdir {location}')
45            location.mkdir()
46        cmd = ['uv', 'init', '--directory', str(location), '--verbose']
47        run_command(cmd)

Creates a scaffold for a new project in the given base directory.

SCAFFOLDERS = {'hatch': <class 'HatchScaffolder'>, 'uv': <class 'UVScaffolder'>}
@dataclass
class ScaffoldCmd(fancy_dataclass.cli.CLIDataclass):
56@dataclass
57class ScaffoldCmd(CLIDataclass, command_name='scaffold'):
58    """Set up a project scaffold."""
59    project_name: str = field(metadata={'help': 'name of project'})
60    utility: ScaffoldUtility = field(default='hatch', metadata={'help': 'utility for creating the scaffold'})
61
62    def run(self) -> None:  # noqa: D102
63        cls = SCAFFOLDERS[self.utility]
64        scaffolder = cls()
65        logger.info(f'Creating new project {self.project_name!r} with {self.utility!r} utility')
66        scaffolder.make_scaffold(Path.cwd(), self.project_name)

Set up a project scaffold.

ScaffoldCmd(project_name: str, utility: Literal['hatch', 'uv'] = 'hatch')
project_name: str
utility: Literal['hatch', 'uv'] = 'hatch'
def run(self) -> None:
62    def run(self) -> None:  # noqa: D102
63        cls = SCAFFOLDERS[self.utility]
64        scaffolder = cls()
65        logger.info(f'Creating new project {self.project_name!r} with {self.utility!r} utility')
66        scaffolder.make_scaffold(Path.cwd(), self.project_name)

Runs the main body of the program.

Subclasses should implement this to provide custom behavior.

If the class has a subcommand defined, and it is an instance of CLIDataclass, the default implementation of run will be to call the subcommand's own implementation.

subcommand_field_name: ClassVar[Optional[str]] = None
subcommand_dest_name: ClassVar[str] = '_subcommand_ScaffoldCmd'