cmds.distro: Move all modules to lib

Functions abstracting the distribution are not only needed in the
context of the distro subcommand, but also by other code, so make the
bulk of the code abstracting the distribution available in some place
more universally useful than below cmds.distro.

This commit leaves the source files mostly unchanged. They are only
patched to fix import paths, so that functionality is preserved.
Refactoring the code from command-line API to library API will be
done by the next commit.

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2026-03-05 11:38:29 +01:00
commit 7e7cee6d11
45 changed files with 44 additions and 42 deletions

View file

@ -2,13 +2,15 @@
import os, importlib import os, importlib
from ...lib.log import *
from ...lib.distros.Util import Util
from ..Cmd import Cmd as Base from ..Cmd import Cmd as Base
from ..CmdDistro import CmdDistro from ..CmdDistro import CmdDistro
from .backend.Util import Util
class Cmd(Base): # export class Cmd(Base): # export
from .backend.Backend import Backend from ...lib.distros.Backend import Backend
def __init__(self, parent: CmdDistro, name: str, help: str) -> None: def __init__(self, parent: CmdDistro, name: str, help: str) -> None:
super().__init__(parent, name, help) super().__init__(parent, name, help)
@ -35,16 +37,16 @@ class Cmd(Base): # export
backend_id = 'redhat' backend_id = 'redhat'
case 'opensuse' | 'suse': case 'opensuse' | 'suse':
backend_id = 'suse' backend_id = 'suse'
self.__backend_path = ( self.__backend_path = 'jw.pkg.lib.distros.' + backend_id + '.'
os.path.splitext(__name__)[0]
+ '.backend.'
+ backend_id
+ '.'
)
return self.__backend_path return self.__backend_path
def _instantiate(self, name: str, *args, **kwargs): def _instantiate(self, name: str, *args, **kwargs):
module = importlib.import_module(self._backend_path + name) module_path = self._backend_path + name
try:
module = importlib.import_module(module_path)
except Exception as e:
log(ERR, f'Failed to import module {module_path} ({str(e)})')
raise
cls = getattr(module, name) cls = getattr(module, name)
return cls(self, *args, **kwargs) return cls(self, *args, **kwargs)

View file

@ -3,10 +3,10 @@
from argparse import Namespace, ArgumentParser from argparse import Namespace, ArgumentParser
import re import re
from ...lib.Package import Package
from ...lib.distros.BeSelect import BeSelect
from ..CmdDistro import CmdDistro from ..CmdDistro import CmdDistro
from .lib.Package import Package
from .Cmd import Cmd from .Cmd import Cmd
from .backend.BeSelect import BeSelect
class CmdSelect(Cmd): # export class CmdSelect(Cmd): # export

View file

@ -7,7 +7,7 @@ from ..CmdPkg import CmdPkg as Parent
class Cmd(Base): # export class Cmd(Base): # export
from ..backend.Backend import Backend from ....lib.distros.Backend import Backend
def __init__(self, parent: Parent, name: str, help: str) -> None: def __init__(self, parent: Parent, name: str, help: str) -> None:
super().__init__(parent, name, help) super().__init__(parent, name, help)

View file

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
from typing import Any
meta_tags = [
"name",
"vendor",
"packager",
"url",
"maintainer",
]
class Package:
name: str = None
vendor: str|None = None
packager: str|None = None
url: str|None = None
maintainer: str|None = None
@classmethod
def parse_spec_str(cls, spec: str, delimiter='|'):
tags = spec.split(delimiter)
if len(tags) != 5:
raise ValueError(f'Invalid package spec string \"{spec}\"')
return cls(name=tags[0], vendor=tags[1], packager=tags[2], url=tags[3], maintainer=tags[4])
@classmethod
def parse_specs_str(cls, specs: str, delimiter='|'):
ret: list[Package] = []
for spec in specs.splitlines():
ret.append(cls.parse_spec_str(spec))
return ret
@classmethod
def order_tags(cls, mapping: dict[str, Any]):
ret: dict[str, Any] = {}
for tag in meta_tags:
ret[tag] = mapping.get(tag, '')
return ret
def __init__(self, name: str, vendor: str|None=None, packager: str|None=None, url: str|None=None, maintainer: str|None=None):
self.name = name
self.vendor = vendor
self.packager = packager
self.url = url
self.maintainer = maintainer
def __repr__(self) -> str:
global meta_tags
return '\n'.join([f'{key:<15}: {getattr(self, key)}' for key in meta_tags])

View file

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from typing import TYPE_CHECKING
from ..util import run_sudo
if TYPE_CHECKING:
from ..Cmd import Cmd
class Backend:
def __init__(self, parent: Cmd):
self.__parent = parent
async def _sudo(self, *args, **kwargs):
return await run_sudo(*args, interactive=self.interactive, **kwargs)
@property
def util(self):
return self.__parent.util
@property
def parent(self):
return self.__parent
@property
def interactive(self) -> bool:
return self.__parent.interactive

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
import abc
from argparse import Namespace
from .Backend import Backend as Base
from ..Cmd import Cmd as Parent
class BeDelete(Base):
def __init__(self, parent: Parent):
super().__init__(parent)
@abc.abstractmethod
async def run(self, args: Namespace) -> None:
pass

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
import abc
from argparse import Namespace
from .Backend import Backend as Base
from ..Cmd import Cmd as Parent
class BeDup(Base):
def __init__(self, parent: Parent):
super().__init__(parent)
@abc.abstractmethod
async def run(self, args: Namespace) -> None:
pass

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
import abc
from argparse import Namespace
from .Backend import Backend as Base
from ..Cmd import Cmd as Parent
class BeInstall(Base):
def __init__(self, parent: Parent):
super().__init__(parent)
@abc.abstractmethod
async def run(self, args: Namespace) -> None:
pass

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from typing import Iterable, TYPE_CHECKING
import abc
from ..Package import Package
from .Backend import Backend as Base
if TYPE_CHECKING:
from ..Cmd import Cmd as Parent
class BePkg(Base):
def __init__(self, parent: Parent):
super().__init__(parent)
async def files(self, name: str) -> Iterable[str]:
return await self._files(name)
@abc.abstractmethod
async def _files(self, name: str) -> Iterable[str]:
pass
async def meta_data(self, names: Iterable[str]) -> Iterable[Package]:
return await self._meta_data(names)
@abc.abstractmethod
async def _meta_data(self, names: Iterable[str]) -> Iterable[Package]:
pass

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
import abc
from argparse import Namespace
from .Backend import Backend as Base
from ..Cmd import Cmd as Parent
class BeRebootRequired(Base):
def __init__(self, parent: Parent):
super().__init__(parent)
@abc.abstractmethod
async def run(self, args: Namespace) -> None:
pass

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
import abc
from argparse import Namespace
from .Backend import Backend as Base
from ..Cmd import Cmd as Parent
class BeRefresh(Base):
def __init__(self, parent: Parent):
super().__init__(parent)
@abc.abstractmethod
async def run(self, args: Namespace) -> None:
pass

View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from typing import TYPE_CHECKING
import abc
from typing import Iterable
from .Backend import Backend as Base
if TYPE_CHECKING:
from ..Cmd import Cmd as Parent
from .Package import Package
class BeSelect(Base):
def __init__(self, parent: Parent):
super().__init__(parent)
@property
async def all_installed_packages(self) -> Iterable[Package]:
return await self._all_installed_packages()
@abc.abstractmethod
async def _all_installed_packages(self) -> Iterable[Package]:
pass

View file

@ -0,0 +1,4 @@
TOPDIR = ../../../../../..
include $(TOPDIR)/make/proj.mk
include $(JWBDIR)/make/py-mod.mk

View file

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from __future__ import annotations
from typing import TYPE_CHECKING
from ..util import run_sudo
if TYPE_CHECKING:
from ..Cmd import Cmd
class Util:
def __init__(self, parent: Cmd):
self.__parent = parent
async def _sudo(self, *args, **kwargs):
return await run_sudo(*args, interactive=self.interactive, **kwargs)
@property
def parent(self):
return self.__parent
@property
def interactive(self) -> bool:
return self.__parent.interactive

View file

@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
from argparse import Namespace
from ...Cmd import Cmd
from ..BeDup import BeDup as Base
class Dup(Base):
def __init__(self, parent: Cmd):
super().__init__(parent)
async def run(self, args: Namespace):
pm_args = ['-Su']
if args.download_only:
pm_args.append('-w')
return await self.util.pacman(pm_args)

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
from argparse import Namespace
from ...Cmd import Cmd
from ..BeInstall import BeInstall as Base
class Install(Base):
def __init__(self, parent: Cmd):
super().__init__(parent)
async def run(self, args: Namespace):
if args.only_update:
raise NotImplementedError('--only-update is not yet implemented for pacman')
pacman_args = ['-S', '--needed']
pacman_args.extend(args.packages)
await self.util.pacman(*pacman_args)

View file

@ -0,0 +1,4 @@
TOPDIR = ../../../../../../..
include $(TOPDIR)/make/proj.mk
include $(JWBDIR)/make/py-mod.mk

View file

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from argparse import Namespace
from ...Cmd import Cmd
from ..BeRefresh import BeRefresh as Base
class Refresh(Base):
def __init__(self, parent: Cmd):
super().__init__(parent)
async def run(self, args: Namespace):
raise NotImplementedError('distro refresh is not yet implemented for Arch-like distributions')

View file

@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
from ...Cmd import Cmd
from ..Util import Util as Base
class Util(Base):
def __init__(self, parent: Cmd):
super().__init__(parent)
async def pacman(self, *args):
cmd = ['/usr/bin/pacman']
if not self.interactive:
cmd.extend(['--noconfirm'])
cmd.extend(args)
return await self._sudo(cmd)

View file

@ -0,0 +1,14 @@
# -*- coding: utf-8 -*-
from argparse import Namespace
from ...Cmd import Cmd
from ..BeDelete import BeDelete as Base
class Delete(Base):
def __init__(self, parent: Cmd):
super().__init__(parent)
async def run(self, args: Namespace):
return await self.util.dpkg(['-P', *args.names], sudo=True)

Some files were not shown because too many files have changed in this diff Show more