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.