milieux.cli.distro

  1from dataclasses import dataclass, field
  2from datetime import datetime
  3from typing import Any, Optional, Union
  4
  5from fancy_dataclass.cli import CLIDataclass
  6
  7from milieux.distro import Distro
  8from milieux.errors import DistroExistsError
  9from milieux.utils import NonemptyPrompt, distro_sty
 10
 11
 12def _get_name_field(required: bool) -> Any:
 13    # make 'name' a positional argument
 14    metadata = {'args': ['name'], 'help': 'name of distro'}
 15    if not required:
 16        metadata['nargs'] = '?'
 17    return field(default=None, metadata=metadata)
 18
 19_force_field = field(
 20    default=False,
 21    metadata={
 22        'args': ['-f', '--force'],
 23        'help': 'force overwrite of distro if it exists'
 24    }
 25)
 26
 27
 28@dataclass
 29class DistroList(CLIDataclass, command_name='list'):
 30    """List all distros."""
 31
 32    def run(self) -> None:
 33        Distro.list()
 34
 35
 36@dataclass
 37class DistroLock(CLIDataclass, command_name='lock'):
 38    """Lock dependencies in a distro."""
 39    name: str = _get_name_field(required=True)
 40    new: Optional[str] = field(default=None, metadata={'nargs': '?', 'const': '', 'help': 'name of new locked distro'})
 41    force: bool = _force_field
 42    annotate: bool = field(default=False, metadata={'help': 'include comment annotations indicating the source of each package'})
 43
 44    def run(self) -> None:
 45        distro = Distro(self.name)
 46        if self.new is None:
 47            new_name = None
 48        else:
 49            if self.new == '':
 50                now = datetime.now()
 51                new_name = self.name + '.' + now.strftime('%Y%m%d')
 52            else:
 53                new_name = self.new
 54            if (not self.force) and Distro(new_name).exists():
 55                raise DistroExistsError(f'Distro {distro_sty(new_name)} already exists')
 56        output = distro.lock(annotate=self.annotate)
 57        if self.new is None:  # print new file to stdout
 58            print(output)
 59        else:
 60            assert new_name is not None
 61            Distro.new(new_name, packages=output.splitlines(), force=self.force)
 62
 63
 64@dataclass
 65class DistroNew(CLIDataclass, command_name='new'):
 66    """Create a new distro."""
 67    name: str = _get_name_field(required=False)
 68    packages: list[str] = field(
 69        default_factory=list,
 70        metadata={'nargs': '+', 'args': ['-p', '--packages'], 'help': 'list of packages to include in the distro'}
 71    )
 72    requirements: list[str] = field(
 73        default_factory=list,
 74        metadata={'nargs': '+', 'args': ['-r', '--requirements'], 'help': 'requirements file(s) listing packages'}
 75    )
 76    distros: list[str] = field(
 77        default_factory=list,
 78        metadata={'nargs': '+', 'args': ['-d', '--distros'], 'help': 'existing distro name(s) to include'}
 79    )
 80    force: bool = _force_field
 81
 82    def run(self) -> None:
 83        name = self.name or NonemptyPrompt.ask('Name of distro')
 84        if (not self.packages) and (not self.requirements) and (not self.distros):
 85            packages_str = NonemptyPrompt.ask('Packages to include (comma-separated)')
 86            packages = [tok.strip() for tok in packages_str.split(',')]
 87        else:
 88            packages = self.packages
 89        Distro.new(name, packages=packages, requirements=self.requirements, distros=self.distros, force=self.force)
 90
 91
 92@dataclass
 93class DistroRemove(CLIDataclass, command_name='remove'):
 94    """Remove a distro."""
 95    name: str = _get_name_field(required=True)
 96
 97    def run(self) -> None:
 98        Distro(self.name).remove()
 99
100
101@dataclass
102class DistroShow(CLIDataclass, command_name='show'):
103    """Show the contents of a distro."""
104    name: str = _get_name_field(required=True)
105
106    def run(self) -> None:
107        Distro(self.name).show()
108
109
110@dataclass
111class DistroCmd(CLIDataclass, command_name='distro'):
112    """Manage distros."""
113    subcommand: Union[
114        DistroList,
115        DistroLock,
116        DistroNew,
117        DistroRemove,
118        DistroShow,
119    ] = field(metadata={'subcommand': True})
@dataclass
class DistroList(fancy_dataclass.cli.CLIDataclass):
29@dataclass
30class DistroList(CLIDataclass, command_name='list'):
31    """List all distros."""
32
33    def run(self) -> None:
34        Distro.list()

List all distros.

def run(self) -> None:
33    def run(self) -> None:
34        Distro.list()

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_DistroList'
@dataclass
class DistroLock(fancy_dataclass.cli.CLIDataclass):
37@dataclass
38class DistroLock(CLIDataclass, command_name='lock'):
39    """Lock dependencies in a distro."""
40    name: str = _get_name_field(required=True)
41    new: Optional[str] = field(default=None, metadata={'nargs': '?', 'const': '', 'help': 'name of new locked distro'})
42    force: bool = _force_field
43    annotate: bool = field(default=False, metadata={'help': 'include comment annotations indicating the source of each package'})
44
45    def run(self) -> None:
46        distro = Distro(self.name)
47        if self.new is None:
48            new_name = None
49        else:
50            if self.new == '':
51                now = datetime.now()
52                new_name = self.name + '.' + now.strftime('%Y%m%d')
53            else:
54                new_name = self.new
55            if (not self.force) and Distro(new_name).exists():
56                raise DistroExistsError(f'Distro {distro_sty(new_name)} already exists')
57        output = distro.lock(annotate=self.annotate)
58        if self.new is None:  # print new file to stdout
59            print(output)
60        else:
61            assert new_name is not None
62            Distro.new(new_name, packages=output.splitlines(), force=self.force)

Lock dependencies in a distro.

DistroLock( name: str = None, new: Optional[str] = None, force: bool = False, annotate: bool = False)
name: str = None
new: Optional[str] = None
force: bool = False
annotate: bool = False
def run(self) -> None:
45    def run(self) -> None:
46        distro = Distro(self.name)
47        if self.new is None:
48            new_name = None
49        else:
50            if self.new == '':
51                now = datetime.now()
52                new_name = self.name + '.' + now.strftime('%Y%m%d')
53            else:
54                new_name = self.new
55            if (not self.force) and Distro(new_name).exists():
56                raise DistroExistsError(f'Distro {distro_sty(new_name)} already exists')
57        output = distro.lock(annotate=self.annotate)
58        if self.new is None:  # print new file to stdout
59            print(output)
60        else:
61            assert new_name is not None
62            Distro.new(new_name, packages=output.splitlines(), force=self.force)

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_DistroLock'
@dataclass
class DistroNew(fancy_dataclass.cli.CLIDataclass):
65@dataclass
66class DistroNew(CLIDataclass, command_name='new'):
67    """Create a new distro."""
68    name: str = _get_name_field(required=False)
69    packages: list[str] = field(
70        default_factory=list,
71        metadata={'nargs': '+', 'args': ['-p', '--packages'], 'help': 'list of packages to include in the distro'}
72    )
73    requirements: list[str] = field(
74        default_factory=list,
75        metadata={'nargs': '+', 'args': ['-r', '--requirements'], 'help': 'requirements file(s) listing packages'}
76    )
77    distros: list[str] = field(
78        default_factory=list,
79        metadata={'nargs': '+', 'args': ['-d', '--distros'], 'help': 'existing distro name(s) to include'}
80    )
81    force: bool = _force_field
82
83    def run(self) -> None:
84        name = self.name or NonemptyPrompt.ask('Name of distro')
85        if (not self.packages) and (not self.requirements) and (not self.distros):
86            packages_str = NonemptyPrompt.ask('Packages to include (comma-separated)')
87            packages = [tok.strip() for tok in packages_str.split(',')]
88        else:
89            packages = self.packages
90        Distro.new(name, packages=packages, requirements=self.requirements, distros=self.distros, force=self.force)

Create a new distro.

DistroNew( name: str = None, packages: list[str] = <factory>, requirements: list[str] = <factory>, distros: list[str] = <factory>, force: bool = False)
name: str = None
packages: list[str]
requirements: list[str]
distros: list[str]
force: bool = False
def run(self) -> None:
83    def run(self) -> None:
84        name = self.name or NonemptyPrompt.ask('Name of distro')
85        if (not self.packages) and (not self.requirements) and (not self.distros):
86            packages_str = NonemptyPrompt.ask('Packages to include (comma-separated)')
87            packages = [tok.strip() for tok in packages_str.split(',')]
88        else:
89            packages = self.packages
90        Distro.new(name, packages=packages, requirements=self.requirements, distros=self.distros, force=self.force)

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_DistroNew'
@dataclass
class DistroRemove(fancy_dataclass.cli.CLIDataclass):
93@dataclass
94class DistroRemove(CLIDataclass, command_name='remove'):
95    """Remove a distro."""
96    name: str = _get_name_field(required=True)
97
98    def run(self) -> None:
99        Distro(self.name).remove()

Remove a distro.

DistroRemove(name: str = None)
name: str = None
def run(self) -> None:
98    def run(self) -> None:
99        Distro(self.name).remove()

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_DistroRemove'
@dataclass
class DistroShow(fancy_dataclass.cli.CLIDataclass):
102@dataclass
103class DistroShow(CLIDataclass, command_name='show'):
104    """Show the contents of a distro."""
105    name: str = _get_name_field(required=True)
106
107    def run(self) -> None:
108        Distro(self.name).show()

Show the contents of a distro.

DistroShow(name: str = None)
name: str = None
def run(self) -> None:
107    def run(self) -> None:
108        Distro(self.name).show()

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_DistroShow'
@dataclass
class DistroCmd(fancy_dataclass.cli.CLIDataclass):
111@dataclass
112class DistroCmd(CLIDataclass, command_name='distro'):
113    """Manage distros."""
114    subcommand: Union[
115        DistroList,
116        DistroLock,
117        DistroNew,
118        DistroRemove,
119        DistroShow,
120    ] = field(metadata={'subcommand': True})

Manage distros.

DistroCmd( subcommand: Union[DistroList, DistroLock, DistroNew, DistroRemove, DistroShow])
subcommand_field_name: ClassVar[Optional[str]] = 'subcommand'
subcommand_dest_name: ClassVar[str] = '_subcommand_DistroCmd'