milieux.utils

 1from __future__ import annotations
 2
 3from pathlib import Path
 4import shlex
 5import subprocess
 6from typing import Any, Union
 7
 8from rich.prompt import InvalidResponse, Prompt
 9
10from milieux import console, logger
11
12
13AnyPath = Union[str, Path]
14
15
16def run_command(cmd: list[Any], **kwargs: Any) -> subprocess.CompletedProcess[str]:
17    """Runs a command (provided as a list) via subprocess.
18    Passes any kwargs to subprocess.run."""
19    cmd = [str(token) for token in cmd]
20    logger.info(shlex.join(cmd))
21    kwargs = {'text': True, **kwargs}  # use text mode by default
22    return subprocess.run(cmd, **kwargs)
23
24
25############
26# FILE I/O #
27############
28
29def resolve_path(path: str, base_dir: Path) -> Path:
30    """Attempts to resolve a path to an absolute path.
31    If it is a relative path, resolves it relative to the given base_dir."""
32    p = Path(path)
33    first_part = p.parts[0] if p.parts else None
34    if p.is_absolute() or (first_part == '.'):
35        if p.exists():
36            return p
37        raise FileNotFoundError(path)
38    if first_part == '..':
39        return base_dir.parent / '/'.join(p.parts[1:])
40    # otherwise, a relative path
41    return base_dir / path
42
43def ensure_path(path: Path) -> Path:
44    """If the given path does not exist, creates it.
45    Then returns the Path."""
46    if not path.exists():
47        logger.info(f'mkdir -p {path}')
48        path.mkdir(parents=True)
49    return path
50
51def read_lines(path: AnyPath) -> list[str]:
52    """Reads lines of text from a file."""
53    return Path(path).read_text().splitlines()
54
55
56##########
57# PROMPT #
58##########
59
60class NonemptyPrompt(Prompt):
61    """Subclass of rich.prompt.Prompt requiring the input to be non-empty."""
62
63    def process_response(self, value: str) -> str:  # noqa: D102
64        if not value.strip():
65            raise InvalidResponse(self.validate_error_message)
66        return super().process_response(value)
67
68
69########
70# TEXT #
71########
72
73def eprint(s: str, **kwargs: Any) -> None:
74    """Prints a string to stderr."""
75    console.print(s, **kwargs)
76
77PALETTE = {
78    'distro': 'dark_orange3',
79    'env': 'green4',
80}
81
82def distro_sty(distro: str) -> str:
83    """Styles a distro name."""
84    color = PALETTE['distro']
85    return f'[bold {color}]{distro}[/]'
86
87def env_sty(env: str) -> str:
88    """Styles an environment name."""
89    color = PALETTE['env']
90    return f'[bold {color}]{env}[/]'
AnyPath = typing.Union[str, pathlib.Path]
def run_command(cmd: list[typing.Any], **kwargs: Any) -> subprocess.CompletedProcess[str]:
17def run_command(cmd: list[Any], **kwargs: Any) -> subprocess.CompletedProcess[str]:
18    """Runs a command (provided as a list) via subprocess.
19    Passes any kwargs to subprocess.run."""
20    cmd = [str(token) for token in cmd]
21    logger.info(shlex.join(cmd))
22    kwargs = {'text': True, **kwargs}  # use text mode by default
23    return subprocess.run(cmd, **kwargs)

Runs a command (provided as a list) via subprocess. Passes any kwargs to subprocess.run.

def resolve_path(path: str, base_dir: pathlib.Path) -> pathlib.Path:
30def resolve_path(path: str, base_dir: Path) -> Path:
31    """Attempts to resolve a path to an absolute path.
32    If it is a relative path, resolves it relative to the given base_dir."""
33    p = Path(path)
34    first_part = p.parts[0] if p.parts else None
35    if p.is_absolute() or (first_part == '.'):
36        if p.exists():
37            return p
38        raise FileNotFoundError(path)
39    if first_part == '..':
40        return base_dir.parent / '/'.join(p.parts[1:])
41    # otherwise, a relative path
42    return base_dir / path

Attempts to resolve a path to an absolute path. If it is a relative path, resolves it relative to the given base_dir.

def ensure_path(path: pathlib.Path) -> pathlib.Path:
44def ensure_path(path: Path) -> Path:
45    """If the given path does not exist, creates it.
46    Then returns the Path."""
47    if not path.exists():
48        logger.info(f'mkdir -p {path}')
49        path.mkdir(parents=True)
50    return path

If the given path does not exist, creates it. Then returns the Path.

def read_lines(path: Union[str, pathlib.Path]) -> list[str]:
52def read_lines(path: AnyPath) -> list[str]:
53    """Reads lines of text from a file."""
54    return Path(path).read_text().splitlines()

Reads lines of text from a file.

class NonemptyPrompt(rich.prompt.PromptBase[str]):
61class NonemptyPrompt(Prompt):
62    """Subclass of rich.prompt.Prompt requiring the input to be non-empty."""
63
64    def process_response(self, value: str) -> str:  # noqa: D102
65        if not value.strip():
66            raise InvalidResponse(self.validate_error_message)
67        return super().process_response(value)

Subclass of rich.prompt.Prompt requiring the input to be non-empty.

def process_response(self, value: str) -> str:
64    def process_response(self, value: str) -> str:  # noqa: D102
65        if not value.strip():
66            raise InvalidResponse(self.validate_error_message)
67        return super().process_response(value)

Process response from user, convert to prompt type.

Args: value (str): String typed by user.

Raises: InvalidResponse: If value is invalid.

Returns: PromptType: The value to be returned from ask method.

def eprint(s: str, **kwargs: Any) -> None:
74def eprint(s: str, **kwargs: Any) -> None:
75    """Prints a string to stderr."""
76    console.print(s, **kwargs)

Prints a string to stderr.

PALETTE = {'distro': 'dark_orange3', 'env': 'green4'}
def distro_sty(distro: str) -> str:
83def distro_sty(distro: str) -> str:
84    """Styles a distro name."""
85    color = PALETTE['distro']
86    return f'[bold {color}]{distro}[/]'

Styles a distro name.

def env_sty(env: str) -> str:
88def env_sty(env: str) -> str:
89    """Styles an environment name."""
90    color = PALETTE['env']
91    return f'[bold {color}]{env}[/]'

Styles an environment name.