milieux.cli.main
1#!/usr/bin/env python3 2 3from dataclasses import dataclass, field 4from pathlib import Path 5import sys 6import traceback 7from typing import Optional, Union 8 9from fancy_dataclass import CLIDataclass 10 11from milieux import PROG, __version__, logger 12from milieux.cli.config import ConfigCmd 13from milieux.cli.distro import DistroCmd 14from milieux.cli.doc import DocCmd 15from milieux.cli.env import EnvCmd 16from milieux.cli.scaffold import ScaffoldCmd 17from milieux.config import Config, set_config_path, user_default_config_path 18from milieux.errors import MilieuxError 19 20 21def _exit_with_error(msg: str) -> None: 22 if msg: 23 logger.error(f'[bold red]ERROR[/] - [red]{msg}[/]') 24 sys.exit(1) 25 26def _handle_error(exc: BaseException) -> None: 27 if isinstance(exc, MilieuxError): # expected error: just show the message 28 msg = str(exc) 29 elif isinstance(exc, (EOFError, KeyboardInterrupt)): # interrupted user input 30 msg = '' 31 elif isinstance(exc, SystemExit): 32 raise 33 else: # no cov 34 # unexpected error: show full traceback 35 lines = traceback.format_exception(type(exc), exc, exc.__traceback__) 36 msg = ''.join(lines) 37 _exit_with_error(msg) 38 39 40@dataclass 41class MilieuxCLI(CLIDataclass, version=f'%(prog)s {__version__}'): 42 """Tool to assist in developing, building, and installing Python packages.""" 43 subcommand: Union[ 44 ConfigCmd, 45 DistroCmd, 46 DocCmd, 47 EnvCmd, 48 ScaffoldCmd, 49 ] = field(metadata={'subcommand': True}) 50 config: Optional[Path] = field( 51 default=None, 52 metadata={'args': ['-c', '--config'], 'help': 'path to TOML config file'} 53 ) 54 55 def _load_config(self) -> None: 56 try: 57 config_path = self.config or user_default_config_path() 58 set_config_path(config_path.absolute()) 59 Config.load_config(config_path) 60 except FileNotFoundError: 61 msg = f"Could not find config file {config_path}: run '{PROG} config new' to create one" 62 if config_path != user_default_config_path(): 63 _exit_with_error(msg) 64 if self.subcommand_name != 'config': 65 logger.warning(msg) 66 # use the default config 67 Config().update_config() 68 except ValueError as e: 69 _exit_with_error(f'Invalid config file {config_path}: {e}') 70 71 def run(self) -> None: 72 """Top-level CLI app for milieux.""" 73 self._load_config() 74 super().run() 75 76 @classmethod 77 def main(cls, arg_list: Optional[list[str]] = None) -> None: 78 """Add custom error handling to main function to exit gracefully when possible.""" 79 try: 80 super().main() # delegate to subcommand 81 except BaseException as exc: 82 _handle_error(exc) 83 84 85if __name__ == '__main__': 86 MilieuxCLI.main()
@dataclass
class
MilieuxCLI41@dataclass 42class MilieuxCLI(CLIDataclass, version=f'%(prog)s {__version__}'): 43 """Tool to assist in developing, building, and installing Python packages.""" 44 subcommand: Union[ 45 ConfigCmd, 46 DistroCmd, 47 DocCmd, 48 EnvCmd, 49 ScaffoldCmd, 50 ] = field(metadata={'subcommand': True}) 51 config: Optional[Path] = field( 52 default=None, 53 metadata={'args': ['-c', '--config'], 'help': 'path to TOML config file'} 54 ) 55 56 def _load_config(self) -> None: 57 try: 58 config_path = self.config or user_default_config_path() 59 set_config_path(config_path.absolute()) 60 Config.load_config(config_path) 61 except FileNotFoundError: 62 msg = f"Could not find config file {config_path}: run '{PROG} config new' to create one" 63 if config_path != user_default_config_path(): 64 _exit_with_error(msg) 65 if self.subcommand_name != 'config': 66 logger.warning(msg) 67 # use the default config 68 Config().update_config() 69 except ValueError as e: 70 _exit_with_error(f'Invalid config file {config_path}: {e}') 71 72 def run(self) -> None: 73 """Top-level CLI app for milieux.""" 74 self._load_config() 75 super().run() 76 77 @classmethod 78 def main(cls, arg_list: Optional[list[str]] = None) -> None: 79 """Add custom error handling to main function to exit gracefully when possible.""" 80 try: 81 super().main() # delegate to subcommand 82 except BaseException as exc: 83 _handle_error(exc)
Tool to assist in developing, building, and installing Python packages.
MilieuxCLI( subcommand: Union[milieux.cli.config.ConfigCmd, milieux.cli.distro.DistroCmd, milieux.cli.doc.DocCmd, milieux.cli.env.EnvCmd, milieux.cli.scaffold.ScaffoldCmd], config: Optional[pathlib.Path] = None)
subcommand: Union[milieux.cli.config.ConfigCmd, milieux.cli.distro.DistroCmd, milieux.cli.doc.DocCmd, milieux.cli.env.EnvCmd, milieux.cli.scaffold.ScaffoldCmd]
def
run(self) -> None:
72 def run(self) -> None: 73 """Top-level CLI app for milieux.""" 74 self._load_config() 75 super().run()
Top-level CLI app for milieux.
@classmethod
def
main(cls, arg_list: Optional[list[str]] = None) -> None:
77 @classmethod 78 def main(cls, arg_list: Optional[list[str]] = None) -> None: 79 """Add custom error handling to main function to exit gracefully when possible.""" 80 try: 81 super().main() # delegate to subcommand 82 except BaseException as exc: 83 _handle_error(exc)
Add custom error handling to main function to exit gracefully when possible.