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 MilieuxCLI(fancy_dataclass.cli.CLIDataclass):
41@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.

config: Optional[pathlib.Path] = None
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.

subcommand_field_name: ClassVar[Optional[str]] = 'subcommand'
subcommand_dest_name: ClassVar[str] = '_subcommand_MilieuxCLI'