style: modernise typing and clean up lint findings
Repository-wide mechanical cleanup so `ruff check src tests` has a chance of passing; no behavioural changes. - Add `from __future__ import annotations` where PEP 604 unions are used. This has to come first: pyproject declares requires-python >= 3.9, where `X | None` is not evaluable at runtime unless annotations are stringified. - Replace typing.List/Dict/Tuple/Set with the builtin generics and Optional[X] with X | None, then drop the imports that became unused. The four actions/*/__init__.py files needed this by hand because ruff leaves unused imports in __init__.py alone (possible re-exports). - Strip shebangs from 72 importable modules. None of them are executable or invoked directly; the entry points are console_scripts and runpy. - Flatten nested `with` blocks, collapse needless-bool returns, and apply the remaining mechanical ruff fixes (PIE810, FLY002, PERF102, FURB192, RUF059, I001). - Pass check=False explicitly to the four subprocess.run() calls that inspect returncode themselves. That is the existing default. Two rewrites are visible to mocks, so their tests move with them: subprocess.run(stdout=PIPE, stderr=PIPE) became capture_output=True, and open(path, "r", ...) lost the redundant mode. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Top-level pkgmgr package.
|
Top-level pkgmgr package.
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pkgmgr.core.git.commands import (
|
from pkgmgr.core.git.commands import (
|
||||||
GitDeleteRemoteBranchError,
|
GitDeleteRemoteBranchError,
|
||||||
checkout,
|
checkout,
|
||||||
@@ -17,7 +15,7 @@ from pkgmgr.core.git.queries import get_current_branch, resolve_base_branch
|
|||||||
|
|
||||||
|
|
||||||
def close_branch(
|
def close_branch(
|
||||||
name: Optional[str],
|
name: str | None,
|
||||||
base_branch: str = "main",
|
base_branch: str = "main",
|
||||||
fallback_base: str = "master",
|
fallback_base: str = "master",
|
||||||
cwd: str = ".",
|
cwd: str = ".",
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pkgmgr.core.git.commands import (
|
from pkgmgr.core.git.commands import (
|
||||||
GitDeleteRemoteBranchError,
|
GitDeleteRemoteBranchError,
|
||||||
delete_local_branch,
|
delete_local_branch,
|
||||||
@@ -12,7 +10,7 @@ from pkgmgr.core.git.queries import get_current_branch, resolve_base_branch
|
|||||||
|
|
||||||
|
|
||||||
def drop_branch(
|
def drop_branch(
|
||||||
name: Optional[str],
|
name: str | None,
|
||||||
base_branch: str = "main",
|
base_branch: str = "main",
|
||||||
fallback_base: str = "master",
|
fallback_base: str = "master",
|
||||||
cwd: str = ".",
|
cwd: str = ".",
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pkgmgr.core.git.commands import (
|
from pkgmgr.core.git.commands import (
|
||||||
checkout,
|
checkout,
|
||||||
create_branch,
|
create_branch,
|
||||||
@@ -13,7 +11,7 @@ from pkgmgr.core.git.queries import resolve_base_branch
|
|||||||
|
|
||||||
|
|
||||||
def open_branch(
|
def open_branch(
|
||||||
name: Optional[str],
|
name: str | None,
|
||||||
base_branch: str = "main",
|
base_branch: str = "main",
|
||||||
fallback_base: str = "master",
|
fallback_base: str = "master",
|
||||||
cwd: str = ".",
|
cwd: str = ".",
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Helpers to generate changelog information from Git history.
|
Helpers to generate changelog information from Git history.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pkgmgr.core.git.queries import (
|
from pkgmgr.core.git.queries import (
|
||||||
GitChangelogQueryError,
|
GitChangelogQueryError,
|
||||||
get_changelog,
|
get_changelog,
|
||||||
@@ -16,8 +12,8 @@ from pkgmgr.core.git.queries import (
|
|||||||
|
|
||||||
def generate_changelog(
|
def generate_changelog(
|
||||||
cwd: str,
|
cwd: str,
|
||||||
from_ref: Optional[str] = None,
|
from_ref: str | None = None,
|
||||||
to_ref: Optional[str] = None,
|
to_ref: str | None = None,
|
||||||
include_merges: bool = False,
|
include_merges: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -27,14 +27,14 @@ class CodeScanningResult:
|
|||||||
repo: str
|
repo: str
|
||||||
output_dir: str
|
output_dir: str
|
||||||
alert_count: int
|
alert_count: int
|
||||||
files: List[str] = field(default_factory=list)
|
files: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
|
||||||
def _gh(args: List[str]) -> subprocess.CompletedProcess[str]:
|
def _gh(args: list[str]) -> subprocess.CompletedProcess[str]:
|
||||||
return subprocess.run(["gh", *args], capture_output=True, text=True)
|
return subprocess.run(["gh", *args], capture_output=True, text=True, check=False)
|
||||||
|
|
||||||
|
|
||||||
def _resolve_repo(repo: Optional[str]) -> str:
|
def _resolve_repo(repo: str | None) -> str:
|
||||||
if repo:
|
if repo:
|
||||||
return repo
|
return repo
|
||||||
proc = _gh(["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"])
|
proc = _gh(["repo", "view", "--json", "nameWithOwner", "-q", ".nameWithOwner"])
|
||||||
@@ -48,7 +48,7 @@ def _resolve_repo(repo: Optional[str]) -> str:
|
|||||||
return name
|
return name
|
||||||
|
|
||||||
|
|
||||||
def _fetch_json(endpoint: str, params: Optional[List[str]] = None) -> Any:
|
def _fetch_json(endpoint: str, params: list[str] | None = None) -> Any:
|
||||||
args = ["api", endpoint, "--paginate"]
|
args = ["api", endpoint, "--paginate"]
|
||||||
for param in params or []:
|
for param in params or []:
|
||||||
args += ["-f", param]
|
args += ["-f", param]
|
||||||
@@ -74,7 +74,7 @@ def _alert_row(alert: dict) -> str:
|
|||||||
return f"- [{severity}] {rule_id} — {path}:{line} ({state})\n {message}"
|
return f"- [{severity}] {rule_id} — {path}:{line} ({state})\n {message}"
|
||||||
|
|
||||||
|
|
||||||
def _build_summary(repo: str, generated_at: str, alerts: List[dict]) -> str:
|
def _build_summary(repo: str, generated_at: str, alerts: list[dict]) -> str:
|
||||||
by_severity: Counter = Counter()
|
by_severity: Counter = Counter()
|
||||||
by_state: Counter = Counter()
|
by_state: Counter = Counter()
|
||||||
by_rule: Counter = Counter()
|
by_rule: Counter = Counter()
|
||||||
@@ -112,9 +112,9 @@ def _build_summary(repo: str, generated_at: str, alerts: List[dict]) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def download_code_scanning(
|
def download_code_scanning(
|
||||||
repo: Optional[str] = None,
|
repo: str | None = None,
|
||||||
output_dir: Optional[str] = None,
|
output_dir: str | None = None,
|
||||||
state: Optional[str] = None,
|
state: str | None = None,
|
||||||
) -> CodeScanningResult:
|
) -> CodeScanningResult:
|
||||||
if not shutil.which("gh"):
|
if not shutil.which("gh"):
|
||||||
raise CodeScanningError("the GitHub CLI 'gh' is not installed or not on PATH")
|
raise CodeScanningError("the GitHub CLI 'gh' is not installed or not on PATH")
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ def interactive_add(config, USER_CONFIG_PATH: str):
|
|||||||
confirm = input("Add this entry to user config? (y/N): ").strip().lower()
|
confirm = input("Add this entry to user config? (y/N): ").strip().lower()
|
||||||
if confirm == "y":
|
if confirm == "y":
|
||||||
if os.path.exists(USER_CONFIG_PATH):
|
if os.path.exists(USER_CONFIG_PATH):
|
||||||
with open(USER_CONFIG_PATH, "r") as f:
|
with open(USER_CONFIG_PATH) as f:
|
||||||
user_config = yaml.safe_load(f) or {}
|
user_config = yaml.safe_load(f) or {}
|
||||||
else:
|
else:
|
||||||
user_config = {"repositories": []}
|
user_config = {"repositories": []}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Initialize user configuration by scanning the repositories base directory.
|
Initialize user configuration by scanning the repositories base directory.
|
||||||
|
|
||||||
@@ -22,7 +20,7 @@ For each discovered repository, the function:
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.core.command.alias import generate_alias
|
from pkgmgr.core.command.alias import generate_alias
|
||||||
from pkgmgr.core.config.save import save_user_config
|
from pkgmgr.core.config.save import save_user_config
|
||||||
@@ -30,8 +28,8 @@ from pkgmgr.core.git.queries import get_latest_commit
|
|||||||
|
|
||||||
|
|
||||||
def config_init(
|
def config_init(
|
||||||
user_config: Dict[str, Any],
|
user_config: dict[str, Any],
|
||||||
defaults_config: Dict[str, Any],
|
defaults_config: dict[str, Any],
|
||||||
bin_dir: str,
|
bin_dir: str,
|
||||||
user_config_path: str,
|
user_config_path: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -128,7 +126,7 @@ def config_init(
|
|||||||
"[WARN] Could not read commit (not a git repo or no commits)."
|
"[WARN] Could not read commit (not a git repo or no commits)."
|
||||||
)
|
)
|
||||||
|
|
||||||
entry: Dict[str, Any] = {
|
entry: dict[str, Any] = {
|
||||||
"provider": provider,
|
"provider": provider,
|
||||||
"account": account,
|
"account": account,
|
||||||
"repository": repo_name,
|
"repository": repo_name,
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
# src/pkgmgr/actions/install/__init__.py
|
# src/pkgmgr/actions/install/__init__.py
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
High-level entry point for repository installation.
|
High-level entry point for repository installation.
|
||||||
@@ -16,7 +14,7 @@ Responsibilities:
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.actions.install.context import RepoContext
|
from pkgmgr.actions.install.context import RepoContext
|
||||||
from pkgmgr.actions.install.installers.makefile import (
|
from pkgmgr.actions.install.installers.makefile import (
|
||||||
@@ -37,7 +35,7 @@ from pkgmgr.core.repository.dir import get_repo_dir
|
|||||||
from pkgmgr.core.repository.identifier import get_repo_identifier
|
from pkgmgr.core.repository.identifier import get_repo_identifier
|
||||||
from pkgmgr.core.repository.verify import verify_repository
|
from pkgmgr.core.repository.verify import verify_repository
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
INSTALLERS = [
|
INSTALLERS = [
|
||||||
ArchPkgbuildInstaller(),
|
ArchPkgbuildInstaller(),
|
||||||
@@ -52,12 +50,12 @@ INSTALLERS = [
|
|||||||
def _ensure_repo_dir(
|
def _ensure_repo_dir(
|
||||||
repo: Repository,
|
repo: Repository,
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
preview: bool,
|
preview: bool,
|
||||||
no_verification: bool,
|
no_verification: bool,
|
||||||
clone_mode: str,
|
clone_mode: str,
|
||||||
identifier: str,
|
identifier: str,
|
||||||
) -> Optional[str]:
|
) -> str | None:
|
||||||
"""
|
"""
|
||||||
Compute and, if necessary, clone the repository directory.
|
Compute and, if necessary, clone the repository directory.
|
||||||
|
|
||||||
@@ -127,7 +125,7 @@ def _create_context(
|
|||||||
repo_dir: str,
|
repo_dir: str,
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
bin_dir: str,
|
bin_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
no_verification: bool,
|
no_verification: bool,
|
||||||
preview: bool,
|
preview: bool,
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
@@ -155,10 +153,10 @@ def _create_context(
|
|||||||
|
|
||||||
|
|
||||||
def install_repos(
|
def install_repos(
|
||||||
selected_repos: List[Repository],
|
selected_repos: list[Repository],
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
bin_dir: str,
|
bin_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
no_verification: bool,
|
no_verification: bool,
|
||||||
preview: bool,
|
preview: bool,
|
||||||
quiet: bool,
|
quiet: bool,
|
||||||
@@ -179,7 +177,7 @@ def install_repos(
|
|||||||
overall command never exits non-zero because of per-repository failures.
|
overall command never exits non-zero because of per-repository failures.
|
||||||
"""
|
"""
|
||||||
pipeline = InstallationPipeline(INSTALLERS)
|
pipeline = InstallationPipeline(INSTALLERS)
|
||||||
failures: List[Tuple[str, str]] = []
|
failures: list[tuple[str, str]] = []
|
||||||
|
|
||||||
for repo in selected_repos:
|
for repo in selected_repos:
|
||||||
identifier = get_repo_identifier(repo, all_repos)
|
identifier = get_repo_identifier(repo, all_repos)
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Capability detection for pkgmgr.
|
Capability detection for pkgmgr.
|
||||||
|
|
||||||
@@ -35,7 +33,7 @@ import glob
|
|||||||
import os
|
import os
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from pkgmgr.actions.install.context import RepoContext
|
from pkgmgr.actions.install.context import RepoContext
|
||||||
@@ -46,12 +44,12 @@ if TYPE_CHECKING:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def _read_text_if_exists(path: str) -> Optional[str]:
|
def _read_text_if_exists(path: str) -> str | None:
|
||||||
"""Read a file as UTF-8 text, returning None if it does not exist or fails."""
|
"""Read a file as UTF-8 text, returning None if it does not exist or fails."""
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
with open(path, encoding="utf-8") as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
except OSError:
|
except OSError:
|
||||||
return None
|
return None
|
||||||
@@ -75,12 +73,12 @@ def _scan_files_for_patterns(files: Iterable[str], patterns: Iterable[str]) -> b
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _first_spec_file(repo_dir: str) -> Optional[str]:
|
def _first_spec_file(repo_dir: str) -> str | None:
|
||||||
"""Return the first *.spec file in repo_dir, if any."""
|
"""Return the first *.spec file in repo_dir, if any."""
|
||||||
matches = glob.glob(os.path.join(repo_dir, "*.spec"))
|
matches = glob.glob(os.path.join(repo_dir, "*.spec"))
|
||||||
if not matches:
|
if not matches:
|
||||||
return None
|
return None
|
||||||
return sorted(matches)[0]
|
return min(matches)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -216,7 +214,7 @@ class MakeInstallCapability(CapabilityMatcher):
|
|||||||
if not os.path.exists(makefile):
|
if not os.path.exists(makefile):
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
with open(makefile, "r", encoding="utf-8") as f:
|
with open(makefile, encoding="utf-8") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
if line.strip().startswith("install:"):
|
if line.strip().startswith("install:"):
|
||||||
return True
|
return True
|
||||||
@@ -360,7 +358,7 @@ def detect_capabilities(
|
|||||||
|
|
||||||
def resolve_effective_capabilities(
|
def resolve_effective_capabilities(
|
||||||
ctx: RepoContext,
|
ctx: RepoContext,
|
||||||
layers: Optional[Iterable[str]] = None,
|
layers: Iterable[str] | None = None,
|
||||||
) -> dict[str, set[str]]:
|
) -> dict[str, set[str]]:
|
||||||
"""
|
"""
|
||||||
Resolve *effective* capabilities for each layer using a bottom-up strategy.
|
Resolve *effective* capabilities for each layer using a bottom-up strategy.
|
||||||
@@ -381,10 +379,7 @@ def resolve_effective_capabilities(
|
|||||||
This means *any* higher layer can overshadow a lower layer, not just
|
This means *any* higher layer can overshadow a lower layer, not just
|
||||||
a specific one like Nix. The resolver is completely generic.
|
a specific one like Nix. The resolver is completely generic.
|
||||||
"""
|
"""
|
||||||
if layers is None:
|
layers_list = list(LAYER_ORDER) if layers is None else list(layers)
|
||||||
layers_list = list(LAYER_ORDER)
|
|
||||||
else:
|
|
||||||
layers_list = list(layers)
|
|
||||||
|
|
||||||
raw_caps = detect_capabilities(ctx, layers_list)
|
raw_caps = detect_capabilities(ctx, layers_list)
|
||||||
effective: dict[str, set[str]] = {layer: set() for layer in layers_list}
|
effective: dict[str, set[str]] = {layer: set() for layer in layers_list}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
# src/pkgmgr/actions/install/context.py
|
# src/pkgmgr/actions/install/context.py
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Shared context object for repository installation steps.
|
Shared context object for repository installation steps.
|
||||||
@@ -10,19 +8,19 @@ they do not depend on global state or long parameter lists.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RepoContext:
|
class RepoContext:
|
||||||
"""Container for all repository-related data used during installation."""
|
"""Container for all repository-related data used during installation."""
|
||||||
|
|
||||||
repo: Dict[str, Any]
|
repo: dict[str, Any]
|
||||||
identifier: str
|
identifier: str
|
||||||
repo_dir: str
|
repo_dir: str
|
||||||
repositories_base_dir: str
|
repositories_base_dir: str
|
||||||
bin_dir: str
|
bin_dir: str
|
||||||
all_repos: List[Dict[str, Any]]
|
all_repos: list[dict[str, Any]]
|
||||||
|
|
||||||
no_verification: bool
|
no_verification: bool
|
||||||
preview: bool
|
preview: bool
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Installer package for pkgmgr.
|
Installer package for pkgmgr.
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Base interface for all installer components in the pkgmgr installation pipeline.
|
Base interface for all installer components in the pkgmgr installation pipeline.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Optional, Set
|
|
||||||
|
|
||||||
from pkgmgr.actions.install.capabilities import CAPABILITY_MATCHERS
|
from pkgmgr.actions.install.capabilities import CAPABILITY_MATCHERS
|
||||||
from pkgmgr.actions.install.context import RepoContext
|
from pkgmgr.actions.install.context import RepoContext
|
||||||
@@ -23,9 +21,9 @@ class BaseInstaller(ABC):
|
|||||||
# Examples: "nix", "python", "makefile".
|
# Examples: "nix", "python", "makefile".
|
||||||
# This is used by capability matchers to decide which patterns to
|
# This is used by capability matchers to decide which patterns to
|
||||||
# search for in the repository.
|
# search for in the repository.
|
||||||
layer: Optional[str] = None
|
layer: str | None = None
|
||||||
|
|
||||||
def discover_capabilities(self, ctx: RepoContext) -> Set[str]:
|
def discover_capabilities(self, ctx: RepoContext) -> set[str]:
|
||||||
"""
|
"""
|
||||||
Determine which logical capabilities this installer will provide
|
Determine which logical capabilities this installer will provide
|
||||||
for this specific repository instance.
|
for this specific repository instance.
|
||||||
@@ -35,7 +33,7 @@ class BaseInstaller(ABC):
|
|||||||
Makefile, etc.) and decide, via string matching, whether a given
|
Makefile, etc.) and decide, via string matching, whether a given
|
||||||
capability is actually provided by this layer.
|
capability is actually provided by this layer.
|
||||||
"""
|
"""
|
||||||
caps: Set[str] = set()
|
caps: set[str] = set()
|
||||||
if not self.layer:
|
if not self.layer:
|
||||||
return caps
|
return caps
|
||||||
|
|
||||||
|
|||||||
@@ -26,16 +26,14 @@ class MakefileInstaller(BaseInstaller):
|
|||||||
|
|
||||||
def _has_install_target(self, makefile_path: str) -> bool:
|
def _has_install_target(self, makefile_path: str) -> bool:
|
||||||
try:
|
try:
|
||||||
with open(makefile_path, "r", encoding="utf-8", errors="ignore") as f:
|
with open(makefile_path, encoding="utf-8", errors="ignore") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
except OSError:
|
except OSError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if re.search(r"^install\s*:", content, flags=re.MULTILINE):
|
if re.search(r"^install\s*:", content, flags=re.MULTILINE):
|
||||||
return True
|
return True
|
||||||
if re.search(r"^install-[a-zA-Z0-9_-]*\s*:", content, flags=re.MULTILINE):
|
return bool(re.search(r"^install-[a-zA-Z0-9_-]*\s*:", content, flags=re.MULTILINE))
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def run(self, ctx: RepoContext) -> None:
|
def run(self, ctx: RepoContext) -> None:
|
||||||
makefile_path = os.path.join(ctx.repo_dir, self.MAKEFILE_NAME)
|
makefile_path = os.path.join(ctx.repo_dir, self.MAKEFILE_NAME)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, List
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .profile import NixProfileInspector
|
from .profile import NixProfileInspector
|
||||||
from .retry import GitHubRateLimitRetry
|
from .retry import GitHubRateLimitRetry
|
||||||
@@ -49,7 +49,7 @@ class NixConflictResolver:
|
|||||||
store_prefixes = self._parser.existing_store_prefixes(combined)
|
store_prefixes = self._parser.existing_store_prefixes(combined)
|
||||||
|
|
||||||
# 2) Resolve them to concrete remove tokens
|
# 2) Resolve them to concrete remove tokens
|
||||||
tokens: List[str] = self._profile.find_remove_tokens_for_store_prefixes(
|
tokens: list[str] = self._profile.find_remove_tokens_for_store_prefixes(
|
||||||
ctx,
|
ctx,
|
||||||
self._runner,
|
self._runner,
|
||||||
store_prefixes,
|
store_prefixes,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from typing import TYPE_CHECKING, List, Tuple
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from pkgmgr.actions.install.installers.base import BaseInstaller
|
from pkgmgr.actions.install.installers.base import BaseInstaller
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ class NixFlakeInstaller(BaseInstaller):
|
|||||||
|
|
||||||
return os.path.exists(os.path.join(ctx.repo_dir, self.FLAKE_FILE))
|
return os.path.exists(os.path.join(ctx.repo_dir, self.FLAKE_FILE))
|
||||||
|
|
||||||
def _profile_outputs(self, ctx: RepoContext) -> List[Tuple[str, bool]]:
|
def _profile_outputs(self, ctx: RepoContext) -> list[tuple[str, bool]]:
|
||||||
# (output_name, allow_failure)
|
# (output_name, allow_failure)
|
||||||
if ctx.identifier in {"pkgmgr", "package-manager"}:
|
if ctx.identifier in {"pkgmgr", "package-manager"}:
|
||||||
return [("pkgmgr", False), ("default", True)]
|
return [("pkgmgr", False), ("default", True)]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Any, List
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from .matcher import (
|
from .matcher import (
|
||||||
entry_matches_output,
|
entry_matches_output,
|
||||||
@@ -43,11 +43,11 @@ class NixProfileInspector:
|
|||||||
ctx: RepoContext,
|
ctx: RepoContext,
|
||||||
runner: CommandRunner,
|
runner: CommandRunner,
|
||||||
output: str,
|
output: str,
|
||||||
) -> List[int]:
|
) -> list[int]:
|
||||||
data = self.list_json(ctx, runner)
|
data = self.list_json(ctx, runner)
|
||||||
entries = normalize_elements(data)
|
entries = normalize_elements(data)
|
||||||
|
|
||||||
hits: List[int] = []
|
hits: list[int] = []
|
||||||
for e in entries:
|
for e in entries:
|
||||||
if e.index is None:
|
if e.index is None:
|
||||||
continue
|
continue
|
||||||
@@ -61,7 +61,7 @@ class NixProfileInspector:
|
|||||||
ctx: RepoContext,
|
ctx: RepoContext,
|
||||||
runner: CommandRunner,
|
runner: CommandRunner,
|
||||||
store_path: str,
|
store_path: str,
|
||||||
) -> List[int]:
|
) -> list[int]:
|
||||||
needle = (store_path or "").strip()
|
needle = (store_path or "").strip()
|
||||||
if not needle:
|
if not needle:
|
||||||
return []
|
return []
|
||||||
@@ -69,7 +69,7 @@ class NixProfileInspector:
|
|||||||
data = self.list_json(ctx, runner)
|
data = self.list_json(ctx, runner)
|
||||||
entries = normalize_elements(data)
|
entries = normalize_elements(data)
|
||||||
|
|
||||||
hits: List[int] = []
|
hits: list[int] = []
|
||||||
for e in entries:
|
for e in entries:
|
||||||
if e.index is None:
|
if e.index is None:
|
||||||
continue
|
continue
|
||||||
@@ -87,7 +87,7 @@ class NixProfileInspector:
|
|||||||
ctx: RepoContext,
|
ctx: RepoContext,
|
||||||
runner: CommandRunner,
|
runner: CommandRunner,
|
||||||
output: str,
|
output: str,
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Returns profile remove tokens to remove entries matching a given output.
|
Returns profile remove tokens to remove entries matching a given output.
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ class NixProfileInspector:
|
|||||||
data = self.list_json(ctx, runner)
|
data = self.list_json(ctx, runner)
|
||||||
entries = normalize_elements(data)
|
entries = normalize_elements(data)
|
||||||
|
|
||||||
tokens: List[str] = [
|
tokens: list[str] = [
|
||||||
out
|
out
|
||||||
] # critical: matches nix's own suggestion for conflicts
|
] # critical: matches nix's own suggestion for conflicts
|
||||||
|
|
||||||
@@ -119,7 +119,7 @@ class NixProfileInspector:
|
|||||||
|
|
||||||
# stable unique preserving order
|
# stable unique preserving order
|
||||||
seen: set[str] = set()
|
seen: set[str] = set()
|
||||||
uniq: List[str] = []
|
uniq: list[str] = []
|
||||||
for t in tokens:
|
for t in tokens:
|
||||||
if t and t not in seen:
|
if t and t not in seen:
|
||||||
uniq.append(t)
|
uniq.append(t)
|
||||||
@@ -130,8 +130,8 @@ class NixProfileInspector:
|
|||||||
self,
|
self,
|
||||||
ctx: RepoContext,
|
ctx: RepoContext,
|
||||||
runner: CommandRunner,
|
runner: CommandRunner,
|
||||||
prefixes: List[str],
|
prefixes: list[str],
|
||||||
) -> List[str]:
|
) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Returns remove tokens for entries whose store path matches any prefix.
|
Returns remove tokens for entries whose store path matches any prefix.
|
||||||
"""
|
"""
|
||||||
@@ -143,7 +143,7 @@ class NixProfileInspector:
|
|||||||
data = self.list_json(ctx, runner)
|
data = self.list_json(ctx, runner)
|
||||||
entries = normalize_elements(data)
|
entries = normalize_elements(data)
|
||||||
|
|
||||||
tokens: List[str] = []
|
tokens: list[str] = []
|
||||||
for e in entries:
|
for e in entries:
|
||||||
if not e.store_paths:
|
if not e.store_paths:
|
||||||
continue
|
continue
|
||||||
@@ -156,7 +156,7 @@ class NixProfileInspector:
|
|||||||
tokens.append(n)
|
tokens.append(n)
|
||||||
|
|
||||||
seen: set[str] = set()
|
seen: set[str] = set()
|
||||||
uniq: List[str] = []
|
uniq: list[str] = []
|
||||||
for t in tokens:
|
for t in tokens:
|
||||||
if t and t not in seen:
|
if t and t not in seen:
|
||||||
uniq.append(t)
|
uniq.append(t)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from .models import NixProfileEntry
|
from .models import NixProfileEntry
|
||||||
|
|
||||||
|
|
||||||
@@ -51,9 +49,9 @@ def entry_matches_store_path(entry: NixProfileEntry, store_path: str) -> bool:
|
|||||||
return any((p or "") == needle for p in entry.store_paths)
|
return any((p or "") == needle for p in entry.store_paths)
|
||||||
|
|
||||||
|
|
||||||
def stable_unique_ints(values: List[int]) -> List[int]:
|
def stable_unique_ints(values: list[int]) -> list[int]:
|
||||||
seen: set[int] = set()
|
seen: set[int] = set()
|
||||||
uniq: List[int] = []
|
uniq: list[int] = []
|
||||||
for v in values:
|
for v in values:
|
||||||
if v in seen:
|
if v in seen:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -11,7 +10,7 @@ class NixProfileEntry:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
key: str
|
key: str
|
||||||
index: Optional[int]
|
index: int | None
|
||||||
name: str
|
name: str
|
||||||
attr_path: str
|
attr_path: str
|
||||||
store_paths: List[str]
|
store_paths: list[str]
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any
|
||||||
|
|
||||||
from .models import NixProfileEntry
|
from .models import NixProfileEntry
|
||||||
|
|
||||||
|
|
||||||
def coerce_index(key: str, entry: Dict[str, Any]) -> Optional[int]:
|
def coerce_index(key: str, entry: dict[str, Any]) -> int | None:
|
||||||
"""
|
"""
|
||||||
Nix JSON schema varies:
|
Nix JSON schema varies:
|
||||||
- elements keys might be "0", "1", ...
|
- elements keys might be "0", "1", ...
|
||||||
@@ -73,7 +73,7 @@ def iter_store_paths(entry: Dict[str, Any]) -> Iterable[str]:
|
|||||||
|
|
||||||
outs = entry.get("outputs")
|
outs = entry.get("outputs")
|
||||||
if isinstance(outs, dict):
|
if isinstance(outs, dict):
|
||||||
for _, ov in outs.items():
|
for ov in outs.values():
|
||||||
if isinstance(ov, dict):
|
if isinstance(ov, dict):
|
||||||
p = ov.get("storePath")
|
p = ov.get("storePath")
|
||||||
if isinstance(p, str):
|
if isinstance(p, str):
|
||||||
@@ -88,7 +88,7 @@ def normalize_store_path(store_path: str) -> str:
|
|||||||
return (store_path or "").strip()
|
return (store_path or "").strip()
|
||||||
|
|
||||||
|
|
||||||
def normalize_elements(data: Dict[str, Any]) -> List[NixProfileEntry]:
|
def normalize_elements(data: dict[str, Any]) -> list[NixProfileEntry]:
|
||||||
"""
|
"""
|
||||||
Converts nix profile list JSON into a list of normalized entries.
|
Converts nix profile list JSON into a list of normalized entries.
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ def normalize_elements(data: Dict[str, Any]) -> List[NixProfileEntry]:
|
|||||||
if not isinstance(elements, dict):
|
if not isinstance(elements, dict):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
normalized: List[NixProfileEntry] = []
|
normalized: list[NixProfileEntry] = []
|
||||||
|
|
||||||
for k, entry in elements.items():
|
for k, entry in elements.items():
|
||||||
if not isinstance(entry, dict):
|
if not isinstance(entry, dict):
|
||||||
@@ -110,7 +110,7 @@ def normalize_elements(data: Dict[str, Any]) -> List[NixProfileEntry]:
|
|||||||
name = str(entry.get("name", "") or "")
|
name = str(entry.get("name", "") or "")
|
||||||
attr = str(entry.get("attrPath", "") or "")
|
attr = str(entry.get("attrPath", "") or "")
|
||||||
|
|
||||||
store_paths: List[str] = []
|
store_paths: list[str] = []
|
||||||
for p in iter_store_paths(entry):
|
for p in iter_store_paths(entry):
|
||||||
sp = normalize_store_path(p)
|
sp = normalize_store_path(p)
|
||||||
if sp:
|
if sp:
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import json
|
import json
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
def parse_profile_list_json(raw: str) -> Dict[str, Any]:
|
def parse_profile_list_json(raw: str) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Parse JSON output from `nix profile list --json`.
|
Parse JSON output from `nix profile list --json`.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from typing import TYPE_CHECKING, List, Tuple
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from .runner import CommandRunner
|
from .runner import CommandRunner
|
||||||
|
|
||||||
@@ -19,12 +19,12 @@ class NixProfileListReader:
|
|||||||
m = re.match(r"^(/nix/store/[0-9a-z]{32}-[^/ \t]+)", raw)
|
m = re.match(r"^(/nix/store/[0-9a-z]{32}-[^/ \t]+)", raw)
|
||||||
return m.group(1) if m else raw
|
return m.group(1) if m else raw
|
||||||
|
|
||||||
def entries(self, ctx: RepoContext) -> List[Tuple[int, str]]:
|
def entries(self, ctx: RepoContext) -> list[tuple[int, str]]:
|
||||||
res = self._runner.run(ctx, "nix profile list", allow_failure=True)
|
res = self._runner.run(ctx, "nix profile list", allow_failure=True)
|
||||||
if res.returncode != 0:
|
if res.returncode != 0:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
entries: List[Tuple[int, str]] = []
|
entries: list[tuple[int, str]] = []
|
||||||
pat = re.compile(
|
pat = re.compile(
|
||||||
r"^\s*(\d+)\s+.*?(/nix/store/[0-9a-z]{32}-[^/ \t]+)",
|
r"^\s*(\d+)\s+.*?(/nix/store/[0-9a-z]{32}-[^/ \t]+)",
|
||||||
re.MULTILINE,
|
re.MULTILINE,
|
||||||
@@ -49,20 +49,20 @@ class NixProfileListReader:
|
|||||||
return uniq
|
return uniq
|
||||||
|
|
||||||
def indices_matching_store_prefixes(
|
def indices_matching_store_prefixes(
|
||||||
self, ctx: RepoContext, prefixes: List[str]
|
self, ctx: RepoContext, prefixes: list[str]
|
||||||
) -> List[int]:
|
) -> list[int]:
|
||||||
prefixes = [self._store_prefix(p) for p in prefixes if p]
|
prefixes = [self._store_prefix(p) for p in prefixes if p]
|
||||||
prefixes = [p for p in prefixes if p]
|
prefixes = [p for p in prefixes if p]
|
||||||
if not prefixes:
|
if not prefixes:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
hits: List[int] = []
|
hits: list[int] = []
|
||||||
for idx, sp in self.entries(ctx):
|
for idx, sp in self.entries(ctx):
|
||||||
if any(sp == p for p in prefixes):
|
if any(sp == p for p in prefixes):
|
||||||
hits.append(idx)
|
hits.append(idx)
|
||||||
|
|
||||||
seen: set[int] = set()
|
seen: set[int] = set()
|
||||||
uniq: List[int] = []
|
uniq: list[int] = []
|
||||||
for i in hits:
|
for i in hits:
|
||||||
if i not in seen:
|
if i not in seen:
|
||||||
seen.add(i)
|
seen.add(i)
|
||||||
|
|||||||
@@ -31,8 +31,7 @@ class CommandRunner:
|
|||||||
shell=True,
|
shell=True,
|
||||||
cwd=repo_dir,
|
cwd=repo_dir,
|
||||||
check=False,
|
check=False,
|
||||||
stdout=subprocess.PIPE,
|
capture_output=True,
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
text=True,
|
text=True,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
class NixConflictTextParser:
|
class NixConflictTextParser:
|
||||||
@@ -11,13 +10,13 @@ class NixConflictTextParser:
|
|||||||
m = re.match(r"^(/nix/store/[0-9a-z]{32}-[^/ \t]+)", raw)
|
m = re.match(r"^(/nix/store/[0-9a-z]{32}-[^/ \t]+)", raw)
|
||||||
return m.group(1) if m else raw
|
return m.group(1) if m else raw
|
||||||
|
|
||||||
def remove_tokens(self, text: str) -> List[str]:
|
def remove_tokens(self, text: str) -> list[str]:
|
||||||
pat = re.compile(
|
pat = re.compile(
|
||||||
r"^\s*nix profile remove\s+([^\s'\"`]+|'[^']+'|\"[^\"]+\")\s*$",
|
r"^\s*nix profile remove\s+([^\s'\"`]+|'[^']+'|\"[^\"]+\")\s*$",
|
||||||
re.MULTILINE,
|
re.MULTILINE,
|
||||||
)
|
)
|
||||||
|
|
||||||
tokens: List[str] = []
|
tokens: list[str] = []
|
||||||
for m in pat.finditer(text or ""):
|
for m in pat.finditer(text or ""):
|
||||||
t = (m.group(1) or "").strip()
|
t = (m.group(1) or "").strip()
|
||||||
if (t.startswith("'") and t.endswith("'")) or (
|
if (t.startswith("'") and t.endswith("'")) or (
|
||||||
@@ -28,7 +27,7 @@ class NixConflictTextParser:
|
|||||||
tokens.append(t)
|
tokens.append(t)
|
||||||
|
|
||||||
seen: set[str] = set()
|
seen: set[str] = set()
|
||||||
uniq: List[str] = []
|
uniq: list[str] = []
|
||||||
for t in tokens:
|
for t in tokens:
|
||||||
if t not in seen:
|
if t not in seen:
|
||||||
seen.add(t)
|
seen.add(t)
|
||||||
@@ -36,9 +35,9 @@ class NixConflictTextParser:
|
|||||||
|
|
||||||
return uniq
|
return uniq
|
||||||
|
|
||||||
def existing_store_prefixes(self, text: str) -> List[str]:
|
def existing_store_prefixes(self, text: str) -> list[str]:
|
||||||
lines = (text or "").splitlines()
|
lines = (text or "").splitlines()
|
||||||
prefixes: List[str] = []
|
prefixes: list[str] = []
|
||||||
|
|
||||||
in_existing = False
|
in_existing = False
|
||||||
in_new = False
|
in_new = False
|
||||||
@@ -69,7 +68,7 @@ class NixConflictTextParser:
|
|||||||
norm = [self._store_prefix(p) for p in prefixes if p]
|
norm = [self._store_prefix(p) for p in prefixes if p]
|
||||||
|
|
||||||
seen: set[str] = set()
|
seen: set[str] = set()
|
||||||
uniq: List[str] = []
|
uniq: list[str] = []
|
||||||
for p in norm:
|
for p in norm:
|
||||||
if p and p not in seen:
|
if p and p not in seen:
|
||||||
seen.add(p)
|
seen.add(p)
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Installer for Debian/Ubuntu packages defined via debian/control.
|
Installer for Debian/Ubuntu packages defined via debian/control.
|
||||||
|
|
||||||
@@ -12,11 +10,11 @@ This installer:
|
|||||||
It is intended for Debian-based systems where dpkg-buildpackage and
|
It is intended for Debian-based systems where dpkg-buildpackage and
|
||||||
apt/dpkg tooling are available.
|
apt/dpkg tooling are available.
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pkgmgr.actions.install.context import RepoContext
|
from pkgmgr.actions.install.context import RepoContext
|
||||||
from pkgmgr.actions.install.installers.base import BaseInstaller
|
from pkgmgr.actions.install.installers.base import BaseInstaller
|
||||||
@@ -55,7 +53,7 @@ class DebianControlInstaller(BaseInstaller):
|
|||||||
|
|
||||||
return os.path.exists(self._control_path(ctx))
|
return os.path.exists(self._control_path(ctx))
|
||||||
|
|
||||||
def _find_built_debs(self, repo_dir: str) -> List[str]:
|
def _find_built_debs(self, repo_dir: str) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Find .deb files built by dpkg-buildpackage.
|
Find .deb files built by dpkg-buildpackage.
|
||||||
|
|
||||||
@@ -66,7 +64,7 @@ class DebianControlInstaller(BaseInstaller):
|
|||||||
pattern = os.path.join(parent, "*.deb")
|
pattern = os.path.join(parent, "*.deb")
|
||||||
return sorted(glob.glob(pattern))
|
return sorted(glob.glob(pattern))
|
||||||
|
|
||||||
def _privileged_prefix(self) -> Optional[str]:
|
def _privileged_prefix(self) -> str | None:
|
||||||
"""
|
"""
|
||||||
Determine how to run privileged commands:
|
Determine how to run privileged commands:
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Installer for RPM-based packages defined in *.spec files.
|
Installer for RPM-based packages defined in *.spec files.
|
||||||
|
|
||||||
@@ -13,12 +11,12 @@ This installer:
|
|||||||
|
|
||||||
It targets RPM-based systems (Fedora / RHEL / CentOS / Rocky / Alma, etc.).
|
It targets RPM-based systems (Fedora / RHEL / CentOS / Rocky / Alma, etc.).
|
||||||
"""
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import glob
|
import glob
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import tarfile
|
import tarfile
|
||||||
from typing import List, Optional, Tuple
|
|
||||||
|
|
||||||
from pkgmgr.actions.install.context import RepoContext
|
from pkgmgr.actions.install.context import RepoContext
|
||||||
from pkgmgr.actions.install.installers.base import BaseInstaller
|
from pkgmgr.actions.install.installers.base import BaseInstaller
|
||||||
@@ -52,7 +50,7 @@ class RpmSpecInstaller(BaseInstaller):
|
|||||||
|
|
||||||
return has_dnf or has_yum or has_yum_builddep
|
return has_dnf or has_yum or has_yum_builddep
|
||||||
|
|
||||||
def _spec_path(self, ctx: RepoContext) -> Optional[str]:
|
def _spec_path(self, ctx: RepoContext) -> str | None:
|
||||||
"""Return the first *.spec file in the repository root, if any."""
|
"""Return the first *.spec file in the repository root, if any."""
|
||||||
pattern = os.path.join(ctx.repo_dir, "*.spec")
|
pattern = os.path.join(ctx.repo_dir, "*.spec")
|
||||||
matches = sorted(glob.glob(pattern))
|
matches = sorted(glob.glob(pattern))
|
||||||
@@ -91,7 +89,7 @@ class RpmSpecInstaller(BaseInstaller):
|
|||||||
for sub in ("BUILD", "BUILDROOT", "RPMS", "SOURCES", "SPECS", "SRPMS"):
|
for sub in ("BUILD", "BUILDROOT", "RPMS", "SOURCES", "SPECS", "SRPMS"):
|
||||||
os.makedirs(os.path.join(topdir, sub), exist_ok=True)
|
os.makedirs(os.path.join(topdir, sub), exist_ok=True)
|
||||||
|
|
||||||
def _parse_name_version(self, spec_path: str) -> Optional[Tuple[str, str]]:
|
def _parse_name_version(self, spec_path: str) -> tuple[str, str] | None:
|
||||||
"""
|
"""
|
||||||
Parse Name and Version from the given .spec file.
|
Parse Name and Version from the given .spec file.
|
||||||
|
|
||||||
@@ -100,7 +98,7 @@ class RpmSpecInstaller(BaseInstaller):
|
|||||||
name = None
|
name = None
|
||||||
version = None
|
version = None
|
||||||
|
|
||||||
with open(spec_path, "r", encoding="utf-8") as f:
|
with open(spec_path, encoding="utf-8") as f:
|
||||||
for raw_line in f:
|
for raw_line in f:
|
||||||
line = raw_line.strip()
|
line = raw_line.strip()
|
||||||
# Ignore comments
|
# Ignore comments
|
||||||
@@ -182,7 +180,7 @@ class RpmSpecInstaller(BaseInstaller):
|
|||||||
|
|
||||||
return self._spec_path(ctx) is not None
|
return self._spec_path(ctx) is not None
|
||||||
|
|
||||||
def _find_built_rpms(self) -> List[str]:
|
def _find_built_rpms(self) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Find RPMs built by rpmbuild.
|
Find RPMs built by rpmbuild.
|
||||||
|
|
||||||
@@ -212,7 +210,7 @@ class RpmSpecInstaller(BaseInstaller):
|
|||||||
|
|
||||||
run_command(cmd, cwd=ctx.repo_dir, preview=ctx.preview)
|
run_command(cmd, cwd=ctx.repo_dir, preview=ctx.preview)
|
||||||
|
|
||||||
def _install_built_rpms(self, ctx: RepoContext, rpms: List[str]) -> None:
|
def _install_built_rpms(self, ctx: RepoContext, rpms: list[str]) -> None:
|
||||||
"""
|
"""
|
||||||
Install or upgrade the built RPMs.
|
Install or upgrade the built RPMs.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
CLI layer model for the pkgmgr installation pipeline.
|
CLI layer model for the pkgmgr installation pipeline.
|
||||||
|
|
||||||
@@ -18,7 +16,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
class CliLayer(str, Enum):
|
class CliLayer(str, Enum):
|
||||||
@@ -37,7 +34,7 @@ CLI_LAYERS: list[CliLayer] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def layer_priority(layer: Optional[CliLayer]) -> int:
|
def layer_priority(layer: CliLayer | None) -> int:
|
||||||
"""
|
"""
|
||||||
Return a numeric priority index for a given layer.
|
Return a numeric priority index for a given layer.
|
||||||
|
|
||||||
@@ -69,13 +66,11 @@ def classify_command_layer(command: str, repo_dir: str) -> CliLayer:
|
|||||||
home = os.path.expanduser("~")
|
home = os.path.expanduser("~")
|
||||||
|
|
||||||
# OS package managers
|
# OS package managers
|
||||||
if command_abs.startswith("/usr/") or command_abs.startswith("/bin/"):
|
if command_abs.startswith(("/usr/", "/bin/")):
|
||||||
return CliLayer.OS_PACKAGES
|
return CliLayer.OS_PACKAGES
|
||||||
|
|
||||||
# Nix store / profile
|
# Nix store / profile
|
||||||
if command_abs.startswith("/nix/store/") or command_abs.startswith(
|
if command_abs.startswith(("/nix/store/", os.path.join(home, ".nix-profile"))):
|
||||||
os.path.join(home, ".nix-profile")
|
|
||||||
):
|
|
||||||
return CliLayer.NIX
|
return CliLayer.NIX
|
||||||
|
|
||||||
# User-local bin
|
# User-local bin
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
# src/pkgmgr/actions/install/pipeline.py
|
# src/pkgmgr/actions/install/pipeline.py
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Installation pipeline orchestration for repositories.
|
Installation pipeline orchestration for repositories.
|
||||||
@@ -10,7 +8,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional, Set
|
|
||||||
|
|
||||||
from pkgmgr.actions.install.context import RepoContext
|
from pkgmgr.actions.install.context import RepoContext
|
||||||
from pkgmgr.actions.install.installers.base import BaseInstaller
|
from pkgmgr.actions.install.installers.base import BaseInstaller
|
||||||
@@ -25,8 +22,8 @@ from pkgmgr.core.command.resolve import resolve_command_for_repo
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class CommandState:
|
class CommandState:
|
||||||
command: Optional[str]
|
command: str | None
|
||||||
layer: Optional[CliLayer]
|
layer: CliLayer | None
|
||||||
|
|
||||||
|
|
||||||
class CommandResolver:
|
class CommandResolver:
|
||||||
@@ -84,7 +81,7 @@ class InstallationPipeline:
|
|||||||
else:
|
else:
|
||||||
repo.pop("command", None)
|
repo.pop("command", None)
|
||||||
|
|
||||||
provided_capabilities: Set[str] = set()
|
provided_capabilities: set[str] = set()
|
||||||
|
|
||||||
for installer in self._installers:
|
for installer in self._installers:
|
||||||
layer_name = getattr(installer, "layer", None)
|
layer_name = getattr(installer, "layer", None)
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from pkgmgr.core.repository.dir import get_repo_dir
|
from pkgmgr.core.repository.dir import get_repo_dir
|
||||||
from pkgmgr.core.repository.identifier import get_repo_identifier
|
from pkgmgr.core.repository.identifier import get_repo_identifier
|
||||||
|
|
||||||
@@ -12,7 +10,7 @@ from .types import MirrorMap, RepoMirrorContext, Repository
|
|||||||
def build_context(
|
def build_context(
|
||||||
repo: Repository,
|
repo: Repository,
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
) -> RepoMirrorContext:
|
) -> RepoMirrorContext:
|
||||||
"""
|
"""
|
||||||
Build a RepoMirrorContext for a single repository.
|
Build a RepoMirrorContext for a single repository.
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from .context import build_context
|
from .context import build_context
|
||||||
from .printing import print_header
|
from .printing import print_header
|
||||||
from .types import Repository
|
from .types import Repository
|
||||||
|
|
||||||
|
|
||||||
def diff_mirrors(
|
def diff_mirrors(
|
||||||
selected_repos: List[Repository],
|
selected_repos: list[Repository],
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Show differences between config mirrors and MIRRORS file.
|
Show differences between config mirrors and MIRRORS file.
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Optional, Set
|
|
||||||
|
|
||||||
from pkgmgr.core.git.commands import (
|
from pkgmgr.core.git.commands import (
|
||||||
GitAddRemoteError,
|
GitAddRemoteError,
|
||||||
@@ -38,13 +37,10 @@ def _is_git_remote_url(url: str) -> bool:
|
|||||||
if u.startswith("ssh://"):
|
if u.startswith("ssh://"):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if (u.startswith("https://") or u.startswith("http://")) and u.endswith(".git"):
|
return bool((u.startswith(("https://", "http://"))) and u.endswith(".git"))
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def build_default_ssh_url(repo: Repository) -> Optional[str]:
|
def build_default_ssh_url(repo: Repository) -> str | None:
|
||||||
provider = repo.get("provider")
|
provider = repo.get("provider")
|
||||||
account = repo.get("account")
|
account = repo.get("account")
|
||||||
name = repo.get("repository")
|
name = repo.get("repository")
|
||||||
@@ -66,7 +62,7 @@ def _git_mirrors_only(m: MirrorMap) -> MirrorMap:
|
|||||||
def determine_primary_remote_url(
|
def determine_primary_remote_url(
|
||||||
repo: Repository,
|
repo: Repository,
|
||||||
ctx: RepoMirrorContext,
|
ctx: RepoMirrorContext,
|
||||||
) -> Optional[str]:
|
) -> str | None:
|
||||||
"""
|
"""
|
||||||
Priority order (GIT URLS ONLY):
|
Priority order (GIT URLS ONLY):
|
||||||
1. origin from resolved mirrors (if it is a git URL)
|
1. origin from resolved mirrors (if it is a git URL)
|
||||||
@@ -80,7 +76,7 @@ def determine_primary_remote_url(
|
|||||||
return origin
|
return origin
|
||||||
|
|
||||||
for mirrors in (ctx.file_mirrors, ctx.config_mirrors):
|
for mirrors in (ctx.file_mirrors, ctx.config_mirrors):
|
||||||
for _, url in mirrors.items():
|
for url in mirrors.values():
|
||||||
if url and _is_git_remote_url(url):
|
if url and _is_git_remote_url(url):
|
||||||
return url
|
return url
|
||||||
|
|
||||||
@@ -116,7 +112,7 @@ def _ensure_additional_push_urls(
|
|||||||
Non-git URLs (like PyPI) are ignored and will never land in git config.
|
Non-git URLs (like PyPI) are ignored and will never land in git config.
|
||||||
"""
|
"""
|
||||||
git_only = _git_mirrors_only(mirrors)
|
git_only = _git_mirrors_only(mirrors)
|
||||||
desired: Set[str] = {u for u in git_only.values() if u and u != primary}
|
desired: set[str] = {u for u in git_only.values() if u and u != primary}
|
||||||
if not desired:
|
if not desired:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ def read_mirrors_file(repo_dir: str, filename: str = "MIRRORS") -> MirrorMap:
|
|||||||
return mirrors
|
return mirrors
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(path, "r", encoding="utf-8") as fh:
|
with open(path, encoding="utf-8") as fh:
|
||||||
for line in fh:
|
for line in fh:
|
||||||
stripped = line.strip()
|
stripped = line.strip()
|
||||||
if not stripped or stripped.startswith("#"):
|
if not stripped or stripped.startswith("#"):
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from .context import build_context
|
from .context import build_context
|
||||||
from .printing import print_header, print_named_mirrors
|
from .printing import print_header, print_named_mirrors
|
||||||
from .types import Repository
|
from .types import Repository
|
||||||
|
|
||||||
|
|
||||||
def list_mirrors(
|
def list_mirrors(
|
||||||
selected_repos: List[Repository],
|
selected_repos: list[Repository],
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
source: str = "all",
|
source: str = "all",
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Dict, List, Optional, Tuple
|
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ from .types import MirrorMap, Repository
|
|||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def _repo_key(repo: Repository) -> Tuple[str, str, str]:
|
def _repo_key(repo: Repository) -> tuple[str, str, str]:
|
||||||
"""
|
"""
|
||||||
Normalised key for identifying a repository in config files.
|
Normalised key for identifying a repository in config files.
|
||||||
"""
|
"""
|
||||||
@@ -27,7 +26,7 @@ def _repo_key(repo: Repository) -> Tuple[str, str, str]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _load_user_config(path: str) -> Dict[str, object]:
|
def _load_user_config(path: str) -> dict[str, object]:
|
||||||
"""
|
"""
|
||||||
Load a user config YAML file as dict.
|
Load a user config YAML file as dict.
|
||||||
Non-dicts yield {}.
|
Non-dicts yield {}.
|
||||||
@@ -49,13 +48,13 @@ def _load_user_config(path: str) -> Dict[str, object]:
|
|||||||
|
|
||||||
|
|
||||||
def merge_mirrors(
|
def merge_mirrors(
|
||||||
selected_repos: List[Repository],
|
selected_repos: list[Repository],
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
source: str,
|
source: str,
|
||||||
target: str,
|
target: str,
|
||||||
preview: bool = False,
|
preview: bool = False,
|
||||||
user_config_path: Optional[str] = None,
|
user_config_path: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Merge mirrors between config and MIRRORS file.
|
Merge mirrors between config and MIRRORS file.
|
||||||
@@ -73,8 +72,8 @@ def merge_mirrors(
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# Load user config once if we intend to write to it.
|
# Load user config once if we intend to write to it.
|
||||||
user_cfg: Optional[Dict[str, object]] = None
|
user_cfg: dict[str, object] | None = None
|
||||||
user_cfg_path_expanded: Optional[str] = None
|
user_cfg_path_expanded: str | None = None
|
||||||
|
|
||||||
if target == "config" and user_config_path and not preview:
|
if target == "config" and user_config_path and not preview:
|
||||||
user_cfg_path_expanded = os.path.expanduser(user_config_path)
|
user_cfg_path_expanded = os.path.expanduser(user_config_path)
|
||||||
@@ -129,7 +128,7 @@ def merge_mirrors(
|
|||||||
|
|
||||||
repos = user_cfg.get("repositories")
|
repos = user_cfg.get("repositories")
|
||||||
target_key = _repo_key(repo)
|
target_key = _repo_key(repo)
|
||||||
existing_repo: Optional[Repository] = None
|
existing_repo: Repository | None = None
|
||||||
|
|
||||||
# Find existing repo entry
|
# Find existing repo entry
|
||||||
for entry in repos:
|
for entry in repos:
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from pkgmgr.core.remote_provisioning import ProviderHint, RepoSpec, ensure_remote_repo
|
from pkgmgr.core.remote_provisioning import ProviderHint, RepoSpec, ensure_remote_repo
|
||||||
from pkgmgr.core.remote_provisioning.ensure import EnsureOptions
|
from pkgmgr.core.remote_provisioning.ensure import EnsureOptions
|
||||||
|
|
||||||
@@ -64,7 +62,7 @@ def ensure_remote_repository_for_url(
|
|||||||
def ensure_remote_repository(
|
def ensure_remote_repository(
|
||||||
repo: Repository,
|
repo: Repository,
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
preview: bool,
|
preview: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from pkgmgr.core.git.queries import probe_remote_reachable_detail
|
from pkgmgr.core.git.queries import probe_remote_reachable_detail
|
||||||
from pkgmgr.core.remote_provisioning import ProviderHint, RepoSpec, set_repo_visibility
|
from pkgmgr.core.remote_provisioning import ProviderHint, RepoSpec, set_repo_visibility
|
||||||
from pkgmgr.core.remote_provisioning.visibility import VisibilityOptions
|
from pkgmgr.core.remote_provisioning.visibility import VisibilityOptions
|
||||||
@@ -23,9 +21,7 @@ def _is_git_remote_url(url: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
if u.startswith("ssh://"):
|
if u.startswith("ssh://"):
|
||||||
return True
|
return True
|
||||||
if (u.startswith("https://") or u.startswith("http://")) and u.endswith(".git"):
|
return bool((u.startswith(("https://", "http://"))) and u.endswith(".git"))
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _provider_hint_from_host(host: str) -> str | None:
|
def _provider_hint_from_host(host: str) -> str | None:
|
||||||
@@ -89,7 +85,7 @@ def _print_probe_result(name: str | None, url: str, *, cwd: str) -> None:
|
|||||||
def _setup_local_mirrors_for_repo(
|
def _setup_local_mirrors_for_repo(
|
||||||
repo: Repository,
|
repo: Repository,
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
preview: bool,
|
preview: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
ctx = build_context(repo, repositories_base_dir, all_repos)
|
ctx = build_context(repo, repositories_base_dir, all_repos)
|
||||||
@@ -106,7 +102,7 @@ def _setup_local_mirrors_for_repo(
|
|||||||
def _setup_remote_mirrors_for_repo(
|
def _setup_remote_mirrors_for_repo(
|
||||||
repo: Repository,
|
repo: Repository,
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
preview: bool,
|
preview: bool,
|
||||||
ensure_remote: bool,
|
ensure_remote: bool,
|
||||||
ensure_visibility: str | None,
|
ensure_visibility: str | None,
|
||||||
@@ -195,9 +191,9 @@ def _setup_remote_mirrors_for_repo(
|
|||||||
|
|
||||||
|
|
||||||
def setup_mirrors(
|
def setup_mirrors(
|
||||||
selected_repos: List[Repository],
|
selected_repos: list[Repository],
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
preview: bool = False,
|
preview: bool = False,
|
||||||
local: bool = True,
|
local: bool = True,
|
||||||
remote: bool = True,
|
remote: bool = True,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
MirrorMap = Dict[str, str]
|
MirrorMap = dict[str, str]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
# src/pkgmgr/actions/mirror/url_utils.py
|
# src/pkgmgr/actions/mirror/url_utils.py
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Optional, Tuple
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
|
||||||
def hostport_from_git_url(url: str) -> Tuple[str, Optional[str]]:
|
def hostport_from_git_url(url: str) -> tuple[str, str | None]:
|
||||||
url = (url or "").strip()
|
url = (url or "").strip()
|
||||||
if not url:
|
if not url:
|
||||||
return "", None
|
return "", None
|
||||||
@@ -58,7 +57,7 @@ def _strip_dot_git(name: str) -> str:
|
|||||||
return n
|
return n
|
||||||
|
|
||||||
|
|
||||||
def parse_repo_from_git_url(url: str) -> Tuple[str, Optional[str], Optional[str]]:
|
def parse_repo_from_git_url(url: str) -> tuple[str, str | None, str | None]:
|
||||||
"""
|
"""
|
||||||
Parse (host, owner, repo_name) from common Git remote URLs.
|
Parse (host, owner, repo_name) from common Git remote URLs.
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from pkgmgr.core.remote_provisioning import ProviderHint, RepoSpec, set_repo_visibility
|
from pkgmgr.core.remote_provisioning import ProviderHint, RepoSpec, set_repo_visibility
|
||||||
from pkgmgr.core.remote_provisioning.visibility import VisibilityOptions
|
from pkgmgr.core.remote_provisioning.visibility import VisibilityOptions
|
||||||
|
|
||||||
@@ -20,9 +18,7 @@ def _is_git_remote_url(url: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
if u.startswith("ssh://"):
|
if u.startswith("ssh://"):
|
||||||
return True
|
return True
|
||||||
if (u.startswith("https://") or u.startswith("http://")) and u.endswith(".git"):
|
return bool((u.startswith(("https://", "http://"))) and u.endswith(".git"))
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def _provider_hint_from_host(host: str) -> str | None:
|
def _provider_hint_from_host(host: str) -> str | None:
|
||||||
@@ -66,9 +62,9 @@ def _apply_visibility_for_url(
|
|||||||
|
|
||||||
|
|
||||||
def set_mirror_visibility(
|
def set_mirror_visibility(
|
||||||
selected_repos: List[Repository],
|
selected_repos: list[Repository],
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
*,
|
*,
|
||||||
visibility: str,
|
visibility: str,
|
||||||
preview: bool = False,
|
preview: bool = False,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Backwards-compatible facade for the release file update helpers.
|
Backwards-compatible facade for the release file update helpers.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
@@ -70,6 +71,7 @@ def _markdownlint(reference_path: str, document: str) -> list[str]:
|
|||||||
cwd=directory,
|
cwd=directory,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
|
check=False,
|
||||||
)
|
)
|
||||||
if proc.returncode == 0:
|
if proc.returncode == 0:
|
||||||
return []
|
return []
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from .changelog_lint import (
|
from .changelog_lint import (
|
||||||
ChangelogLintError,
|
ChangelogLintError,
|
||||||
@@ -55,7 +54,7 @@ def _insert_after_h1(existing: str, entry: str) -> str:
|
|||||||
def update_changelog(
|
def update_changelog(
|
||||||
changelog_path: str,
|
changelog_path: str,
|
||||||
new_version: str,
|
new_version: str,
|
||||||
message: Optional[str] = None,
|
message: str | None = None,
|
||||||
preview: bool = False,
|
preview: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Insert a new release entry into CHANGELOG.md.
|
"""Insert a new release entry into CHANGELOG.md.
|
||||||
@@ -87,7 +86,7 @@ def update_changelog(
|
|||||||
elif preview or not sys.stdin.isatty():
|
elif preview or not sys.stdin.isatty():
|
||||||
body, entry = _entry_for(message or f"Release {new_version}")
|
body, entry = _entry_for(message or f"Release {new_version}")
|
||||||
else:
|
else:
|
||||||
attempt: Optional[str] = None
|
attempt: str | None = None
|
||||||
while True:
|
while True:
|
||||||
print(
|
print(
|
||||||
"\n[INFO] Provide the changelog entry — a leading '#' becomes "
|
"\n[INFO] Provide the changelog entry — a leading '#' becomes "
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, Tuple
|
|
||||||
|
|
||||||
from pkgmgr.core.git.queries import get_config_value
|
from pkgmgr.core.git.queries import get_config_value
|
||||||
|
|
||||||
|
|
||||||
def _get_debian_author() -> Tuple[str, str]:
|
def _get_debian_author() -> tuple[str, str]:
|
||||||
name = os.environ.get("DEBFULLNAME")
|
name = os.environ.get("DEBFULLNAME")
|
||||||
email = os.environ.get("DEBEMAIL")
|
email = os.environ.get("DEBEMAIL")
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ def update_debian_changelog(
|
|||||||
debian_changelog_path: str,
|
debian_changelog_path: str,
|
||||||
package_name: str,
|
package_name: str,
|
||||||
new_version: str,
|
new_version: str,
|
||||||
message: Optional[str] = None,
|
message: str | None = None,
|
||||||
preview: bool = False,
|
preview: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not os.path.exists(debian_changelog_path):
|
if not os.path.exists(debian_changelog_path):
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
def _open_editor_for_changelog(initial_message: Optional[str] = None) -> str:
|
def _open_editor_for_changelog(initial_message: str | None = None) -> str:
|
||||||
editor = os.environ.get("EDITOR", "nano")
|
editor = os.environ.get("EDITOR", "nano")
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(
|
with tempfile.NamedTemporaryFile(
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ def update_pyproject_version(
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(pyproject_path, "r", encoding="utf-8") as f:
|
with open(pyproject_path, encoding="utf-8") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
print(f"[WARN] Could not read pyproject.toml: {exc}")
|
print(f"[WARN] Could not read pyproject.toml: {exc}")
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from .debian import _get_debian_author
|
from .debian import _get_debian_author
|
||||||
|
|
||||||
@@ -11,7 +10,7 @@ def update_spec_changelog(
|
|||||||
spec_path: str,
|
spec_path: str,
|
||||||
package_name: str,
|
package_name: str,
|
||||||
new_version: str,
|
new_version: str,
|
||||||
message: Optional[str] = None,
|
message: str | None = None,
|
||||||
preview: bool = False,
|
preview: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
if not os.path.exists(spec_path):
|
if not os.path.exists(spec_path):
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pkgmgr.core.repository.paths import RepoPaths
|
from pkgmgr.core.repository.paths import RepoPaths
|
||||||
|
|
||||||
@@ -33,17 +32,17 @@ _PKGBUILD_NAME_RE = re.compile(r"^pkgname=([^\s#]+)\s*$", re.MULTILINE)
|
|||||||
_RPM_NAME_RE = re.compile(r"^Name:\s*(\S+)\s*$", re.MULTILINE)
|
_RPM_NAME_RE = re.compile(r"^Name:\s*(\S+)\s*$", re.MULTILINE)
|
||||||
|
|
||||||
|
|
||||||
def _read(path: Optional[str]) -> str:
|
def _read(path: str | None) -> str:
|
||||||
if not path or not os.path.isfile(path):
|
if not path or not os.path.isfile(path):
|
||||||
return ""
|
return ""
|
||||||
try:
|
try:
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
with open(path, encoding="utf-8") as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
except OSError:
|
except OSError:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _extract(pattern: re.Pattern[str], text: str) -> Optional[str]:
|
def _extract(pattern: re.Pattern[str], text: str) -> str | None:
|
||||||
if not text:
|
if not text:
|
||||||
return None
|
return None
|
||||||
match = pattern.search(text)
|
match = pattern.search(text)
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Version discovery and bumping helpers for the release workflow.
|
Version discovery and bumping helpers for the release workflow.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pkgmgr.actions.branch import close_branch
|
from pkgmgr.actions.branch import close_branch
|
||||||
from pkgmgr.core.git import GitRunError, run
|
from pkgmgr.core.git import GitRunError, run
|
||||||
@@ -34,7 +33,7 @@ def _release_impl(
|
|||||||
pyproject_path: str = "pyproject.toml",
|
pyproject_path: str = "pyproject.toml",
|
||||||
changelog_path: str = "CHANGELOG.md",
|
changelog_path: str = "CHANGELOG.md",
|
||||||
release_type: str = "patch",
|
release_type: str = "patch",
|
||||||
message: Optional[str] = None,
|
message: str | None = None,
|
||||||
preview: bool = False,
|
preview: bool = False,
|
||||||
close: bool = False,
|
close: bool = False,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
@@ -87,7 +86,7 @@ def _release_impl(
|
|||||||
else:
|
else:
|
||||||
print("[INFO] No RPM spec file found. Skipping spec version update.")
|
print("[INFO] No RPM spec file found. Skipping spec version update.")
|
||||||
|
|
||||||
effective_message: Optional[str] = message
|
effective_message: str | None = message
|
||||||
if isinstance(changelog_message, str) and changelog_message.strip():
|
if isinstance(changelog_message, str) and changelog_message.strip():
|
||||||
effective_message = changelog_message.strip()
|
effective_message = changelog_message.strip()
|
||||||
|
|
||||||
@@ -195,7 +194,7 @@ def release(
|
|||||||
pyproject_path: str = "pyproject.toml",
|
pyproject_path: str = "pyproject.toml",
|
||||||
changelog_path: str = "CHANGELOG.md",
|
changelog_path: str = "CHANGELOG.md",
|
||||||
release_type: str = "patch",
|
release_type: str = "patch",
|
||||||
message: Optional[str] = None,
|
message: str | None = None,
|
||||||
preview: bool = False,
|
preview: bool = False,
|
||||||
force: bool = False,
|
force: bool = False,
|
||||||
close: bool = False,
|
close: bool = False,
|
||||||
|
|||||||
@@ -3,29 +3,29 @@ from __future__ import annotations
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
from typing import Any, Callable, Dict, List, Tuple
|
from typing import Any, Callable
|
||||||
|
|
||||||
from pkgmgr.core.repository.dir import get_repo_dir
|
from pkgmgr.core.repository.dir import get_repo_dir
|
||||||
from pkgmgr.core.repository.identifier import get_repo_identifier
|
from pkgmgr.core.repository.identifier import get_repo_identifier
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
RepoRef = Tuple[str, str]
|
RepoRef = tuple[str, str]
|
||||||
OpResult = Tuple[bool, str]
|
OpResult = tuple[bool, str]
|
||||||
RepoOp = Callable[[str], OpResult]
|
RepoOp = Callable[[str], OpResult]
|
||||||
|
|
||||||
|
|
||||||
def resolve_repos(
|
def resolve_repos(
|
||||||
selected_repos: List[Repository],
|
selected_repos: list[Repository],
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
) -> List[RepoRef]:
|
) -> list[RepoRef]:
|
||||||
"""
|
"""
|
||||||
Resolve ``(identifier, repo_dir)`` pairs for ``selected_repos``.
|
Resolve ``(identifier, repo_dir)`` pairs for ``selected_repos``.
|
||||||
|
|
||||||
Repositories whose directory does not exist on disk are reported and
|
Repositories whose directory does not exist on disk are reported and
|
||||||
skipped, matching the prior behavior of pull/push handlers.
|
skipped, matching the prior behavior of pull/push handlers.
|
||||||
"""
|
"""
|
||||||
resolved: List[RepoRef] = []
|
resolved: list[RepoRef] = []
|
||||||
for repo in selected_repos:
|
for repo in selected_repos:
|
||||||
ident = get_repo_identifier(repo, all_repos)
|
ident = get_repo_identifier(repo, all_repos)
|
||||||
rd = get_repo_dir(repositories_base_dir, repo)
|
rd = get_repo_dir(repositories_base_dir, repo)
|
||||||
@@ -37,7 +37,7 @@ def resolve_repos(
|
|||||||
|
|
||||||
|
|
||||||
def run_on_repos(
|
def run_on_repos(
|
||||||
repos: List[RepoRef],
|
repos: list[RepoRef],
|
||||||
op: RepoOp,
|
op: RepoOp,
|
||||||
*,
|
*,
|
||||||
jobs: int,
|
jobs: int,
|
||||||
@@ -55,7 +55,7 @@ def run_on_repos(
|
|||||||
return
|
return
|
||||||
|
|
||||||
effective_jobs = max(1, min(jobs, len(repos)))
|
effective_jobs = max(1, min(jobs, len(repos)))
|
||||||
failed: List[Tuple[str, str]] = []
|
failed: list[tuple[str, str]] = []
|
||||||
|
|
||||||
if effective_jobs == 1:
|
if effective_jobs == 1:
|
||||||
for ident, rd in repos:
|
for ident, rd in repos:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.core.git.commands import GitCloneError
|
from pkgmgr.core.git.commands import GitCloneError
|
||||||
from pkgmgr.core.git.commands import clone as git_clone
|
from pkgmgr.core.git.commands import clone as git_clone
|
||||||
@@ -9,10 +9,10 @@ from pkgmgr.core.repository.dir import get_repo_dir
|
|||||||
from pkgmgr.core.repository.identifier import get_repo_identifier
|
from pkgmgr.core.repository.identifier import get_repo_identifier
|
||||||
from pkgmgr.core.repository.verify import verify_repository
|
from pkgmgr.core.repository.verify import verify_repository
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def _build_clone_url(repo: Repository, clone_mode: str) -> Optional[str]:
|
def _build_clone_url(repo: Repository, clone_mode: str) -> str | None:
|
||||||
provider = repo.get("provider")
|
provider = repo.get("provider")
|
||||||
account = repo.get("account")
|
account = repo.get("account")
|
||||||
name = repo.get("repository")
|
name = repo.get("repository")
|
||||||
@@ -34,9 +34,9 @@ def _build_clone_url(repo: Repository, clone_mode: str) -> Optional[str]:
|
|||||||
|
|
||||||
|
|
||||||
def clone_repos(
|
def clone_repos(
|
||||||
selected_repos: List[Repository],
|
selected_repos: list[Repository],
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
preview: bool,
|
preview: bool,
|
||||||
no_verification: bool,
|
no_verification: bool,
|
||||||
clone_mode: str,
|
clone_mode: str,
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from .service import CreateRepoService
|
from .service import CreateRepoService
|
||||||
|
|
||||||
RepositoryConfig = Dict[str, Any]
|
RepositoryConfig = dict[str, Any]
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"CreateRepoService",
|
"CreateRepoService",
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict, Set
|
from typing import Any
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from pkgmgr.core.command.alias import generate_alias
|
from pkgmgr.core.command.alias import generate_alias
|
||||||
from pkgmgr.core.config.save import save_user_config
|
from pkgmgr.core.config.save import save_user_config
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
class ConfigRepoWriter:
|
class ConfigRepoWriter:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
config_merged: Dict[str, Any],
|
config_merged: dict[str, Any],
|
||||||
user_config_path: str,
|
user_config_path: str,
|
||||||
bin_dir: str,
|
bin_dir: str,
|
||||||
):
|
):
|
||||||
@@ -43,7 +43,7 @@ class ConfigRepoWriter:
|
|||||||
):
|
):
|
||||||
return repo
|
return repo
|
||||||
|
|
||||||
existing_aliases: Set[str] = {
|
existing_aliases: set[str] = {
|
||||||
str(r.get("alias")) for r in repositories if r.get("alias")
|
str(r.get("alias")) for r in repositories if r.get("alias")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ class ConfigRepoWriter:
|
|||||||
return repo
|
return repo
|
||||||
|
|
||||||
if os.path.exists(self.user_config_path):
|
if os.path.exists(self.user_config_path):
|
||||||
with open(self.user_config_path, "r", encoding="utf-8") as f:
|
with open(self.user_config_path, encoding="utf-8") as f:
|
||||||
user_cfg = yaml.safe_load(f) or {}
|
user_cfg = yaml.safe_load(f) or {}
|
||||||
else:
|
else:
|
||||||
user_cfg = {}
|
user_cfg = {}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.actions.mirror.io import write_mirrors_file
|
from pkgmgr.actions.mirror.io import write_mirrors_file
|
||||||
from pkgmgr.actions.mirror.setup_cmd import setup_mirrors
|
from pkgmgr.actions.mirror.setup_cmd import setup_mirrors
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
class MirrorBootstrapper:
|
class MirrorBootstrapper:
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class RepoParts:
|
class RepoParts:
|
||||||
host: str
|
host: str
|
||||||
port: Optional[str]
|
port: str | None
|
||||||
owner: str
|
owner: str
|
||||||
name: str
|
name: str
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import re
|
import re
|
||||||
from typing import Tuple
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from .model import RepoParts
|
from .model import RepoParts
|
||||||
@@ -50,7 +49,7 @@ def _parse_git_url(url: str) -> RepoParts:
|
|||||||
return RepoParts(host=host, port=port, owner=owner, name=name)
|
return RepoParts(host=host, port=port, owner=owner, name=name)
|
||||||
|
|
||||||
|
|
||||||
def _split_host_port(host: str) -> Tuple[str, str | None]:
|
def _split_host_port(host: str) -> tuple[str, str | None]:
|
||||||
if ":" in host:
|
if ":" in host:
|
||||||
h, p = host.split(":", 1)
|
h, p = host.split(":", 1)
|
||||||
return h, p or None
|
return h, p or None
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from .model import RepoParts
|
from .model import RepoParts
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@ class CreateRepoPlanner:
|
|||||||
*,
|
*,
|
||||||
author_name: str,
|
author_name: str,
|
||||||
author_email: str,
|
author_email: str,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"provider": self.parts.host,
|
"provider": self.parts.host,
|
||||||
"port": self.parts.port,
|
"port": self.parts.port,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.core.git.queries import get_config_value
|
from pkgmgr.core.git.queries import get_config_value
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ class CreateRepoService:
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
config_merged: Dict[str, Any],
|
config_merged: dict[str, Any],
|
||||||
user_config_path: str,
|
user_config_path: str,
|
||||||
bin_dir: str,
|
bin_dir: str,
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class TemplateRenderer:
|
|||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
repo_dir: str,
|
repo_dir: str,
|
||||||
context: Dict[str, Any],
|
context: dict[str, Any],
|
||||||
preview: bool,
|
preview: bool,
|
||||||
) -> None:
|
) -> None:
|
||||||
if preview:
|
if preview:
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Pretty-print repository list with status, categories, tags and path.
|
Pretty-print repository list with status, categories, tags and path.
|
||||||
|
|
||||||
@@ -15,9 +13,9 @@ from __future__ import annotations
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from textwrap import wrap
|
from textwrap import wrap
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
RESET = "\033[0m"
|
RESET = "\033[0m"
|
||||||
BOLD = "\033[1m"
|
BOLD = "\033[1m"
|
||||||
@@ -29,7 +27,7 @@ MAGENTA = "\033[35m"
|
|||||||
GREY = "\033[90m"
|
GREY = "\033[90m"
|
||||||
|
|
||||||
|
|
||||||
def _compile_maybe_regex(pattern: str) -> Optional[re.Pattern[str]]:
|
def _compile_maybe_regex(pattern: str) -> re.Pattern[str] | None:
|
||||||
"""
|
"""
|
||||||
If pattern is of the form /.../, return a compiled regex (case-insensitive).
|
If pattern is of the form /.../, return a compiled regex (case-insensitive).
|
||||||
Otherwise return None.
|
Otherwise return None.
|
||||||
@@ -88,7 +86,7 @@ def _compute_status(
|
|||||||
"""
|
"""
|
||||||
Compute a human-readable status string, e.g. 'present,alias,ignored'.
|
Compute a human-readable status string, e.g. 'present,alias,ignored'.
|
||||||
"""
|
"""
|
||||||
parts: List[str] = []
|
parts: list[str] = []
|
||||||
|
|
||||||
exists = os.path.isdir(repo_dir)
|
exists = os.path.isdir(repo_dir)
|
||||||
if exists:
|
if exists:
|
||||||
@@ -128,7 +126,7 @@ def _color_status(status_padded: str) -> str:
|
|||||||
pad_spaces = len(status_padded) - len(core)
|
pad_spaces = len(status_padded) - len(core)
|
||||||
|
|
||||||
plain_parts = core.split(",") if core else []
|
plain_parts = core.split(",") if core else []
|
||||||
colored_parts: List[str] = []
|
colored_parts: list[str] = []
|
||||||
|
|
||||||
for raw_part in plain_parts:
|
for raw_part in plain_parts:
|
||||||
name = raw_part.strip()
|
name = raw_part.strip()
|
||||||
@@ -156,12 +154,12 @@ def _color_status(status_padded: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def list_repositories(
|
def list_repositories(
|
||||||
repositories: List[Repository],
|
repositories: list[Repository],
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
binaries_dir: str,
|
binaries_dir: str,
|
||||||
search_filter: str = "",
|
search_filter: str = "",
|
||||||
status_filter: str = "",
|
status_filter: str = "",
|
||||||
extra_tags: Optional[List[str]] = None,
|
extra_tags: list[str] | None = None,
|
||||||
show_description: bool = False,
|
show_description: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -188,7 +186,7 @@ def list_repositories(
|
|||||||
extra_tags = []
|
extra_tags = []
|
||||||
|
|
||||||
search_regex = _compile_maybe_regex(search_filter)
|
search_regex = _compile_maybe_regex(search_filter)
|
||||||
rows: List[Dict[str, Any]] = []
|
rows: list[dict[str, Any]] = []
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Build rows
|
# Build rows
|
||||||
@@ -208,17 +206,7 @@ def list_repositories(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if search_filter:
|
if search_filter:
|
||||||
haystack = " ".join(
|
haystack = f"{identifier} {alias} {provider} {account} {description} {homepage} {repo_dir}"
|
||||||
[
|
|
||||||
identifier,
|
|
||||||
alias,
|
|
||||||
provider,
|
|
||||||
account,
|
|
||||||
description,
|
|
||||||
homepage,
|
|
||||||
repo_dir,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
if search_regex:
|
if search_regex:
|
||||||
if not search_regex.search(haystack):
|
if not search_regex.search(haystack):
|
||||||
continue
|
continue
|
||||||
@@ -226,13 +214,13 @@ def list_repositories(
|
|||||||
if search_filter.lower() not in haystack.lower():
|
if search_filter.lower() not in haystack.lower():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
categories: List[str] = []
|
categories: list[str] = []
|
||||||
categories.extend(map(str, repo.get("category_files", [])))
|
categories.extend(map(str, repo.get("category_files", [])))
|
||||||
if repo.get("category"):
|
if repo.get("category"):
|
||||||
categories.append(str(repo["category"]))
|
categories.append(str(repo["category"]))
|
||||||
|
|
||||||
yaml_tags: List[str] = list(map(str, repo.get("tags", [])))
|
yaml_tags: list[str] = list(map(str, repo.get("tags", [])))
|
||||||
display_tags: List[str] = sorted(set(yaml_tags + list(map(str, extra_tags))))
|
display_tags: list[str] = sorted(set(yaml_tags + list(map(str, extra_tags))))
|
||||||
|
|
||||||
rows.append(
|
rows.append(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from typing import Any, Dict, List, Tuple
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.actions.repository._parallel import RepoRef, run_on_repos
|
from pkgmgr.actions.repository._parallel import RepoRef, run_on_repos
|
||||||
from pkgmgr.core.git.commands import GitPullArgsError, pull_args
|
from pkgmgr.core.git.commands import GitPullArgsError, pull_args
|
||||||
@@ -10,10 +10,10 @@ from pkgmgr.core.repository.dir import get_repo_dir
|
|||||||
from pkgmgr.core.repository.identifier import get_repo_identifier
|
from pkgmgr.core.repository.identifier import get_repo_identifier
|
||||||
from pkgmgr.core.repository.verify import verify_repository
|
from pkgmgr.core.repository.verify import verify_repository
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def _pull_one(repo_dir: str, extra_args: List[str], preview: bool) -> Tuple[bool, str]:
|
def _pull_one(repo_dir: str, extra_args: list[str], preview: bool) -> tuple[bool, str]:
|
||||||
try:
|
try:
|
||||||
pull_args(extra_args, cwd=repo_dir, preview=preview)
|
pull_args(extra_args, cwd=repo_dir, preview=preview)
|
||||||
return (True, "")
|
return (True, "")
|
||||||
@@ -25,7 +25,7 @@ def _verify_one(
|
|||||||
repo: Repository,
|
repo: Repository,
|
||||||
repo_dir: str,
|
repo_dir: str,
|
||||||
no_verification: bool,
|
no_verification: bool,
|
||||||
) -> Tuple[bool, bool, List[str]]:
|
) -> tuple[bool, bool, list[str]]:
|
||||||
"""Returns (has_verified_info, verified_ok, errors)."""
|
"""Returns (has_verified_info, verified_ok, errors)."""
|
||||||
verified_ok, errors, _commit, _key = verify_repository(
|
verified_ok, errors, _commit, _key = verify_repository(
|
||||||
repo,
|
repo,
|
||||||
@@ -37,10 +37,10 @@ def _verify_one(
|
|||||||
|
|
||||||
|
|
||||||
def _verify_all(
|
def _verify_all(
|
||||||
candidates: List[Tuple[Repository, str, str]],
|
candidates: list[tuple[Repository, str, str]],
|
||||||
no_verification: bool,
|
no_verification: bool,
|
||||||
jobs: int,
|
jobs: int,
|
||||||
) -> List[Tuple[str, str, bool, bool, List[str]]]:
|
) -> list[tuple[str, str, bool, bool, list[str]]]:
|
||||||
"""
|
"""
|
||||||
Verify all candidates (parallel if ``jobs > 1``), preserving input order.
|
Verify all candidates (parallel if ``jobs > 1``), preserving input order.
|
||||||
|
|
||||||
@@ -63,10 +63,10 @@ def _verify_all(
|
|||||||
|
|
||||||
|
|
||||||
def pull_with_verification(
|
def pull_with_verification(
|
||||||
selected_repos: List[Repository],
|
selected_repos: list[Repository],
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
extra_args: List[str],
|
extra_args: list[str],
|
||||||
no_verification: bool,
|
no_verification: bool,
|
||||||
preview: bool,
|
preview: bool,
|
||||||
jobs: int = 1,
|
jobs: int = 1,
|
||||||
@@ -80,7 +80,7 @@ def pull_with_verification(
|
|||||||
- Approved repos are then pulled in parallel when ``jobs > 1``.
|
- Approved repos are then pulled in parallel when ``jobs > 1``.
|
||||||
- On any pull failure, prints a summary and exits with status 1.
|
- On any pull failure, prints a summary and exits with status 1.
|
||||||
"""
|
"""
|
||||||
candidates: List[Tuple[Repository, str, str]] = []
|
candidates: list[tuple[Repository, str, str]] = []
|
||||||
for repo in selected_repos:
|
for repo in selected_repos:
|
||||||
ident = get_repo_identifier(repo, all_repos)
|
ident = get_repo_identifier(repo, all_repos)
|
||||||
rd = get_repo_dir(repositories_base_dir, repo)
|
rd = get_repo_dir(repositories_base_dir, repo)
|
||||||
@@ -94,7 +94,7 @@ def pull_with_verification(
|
|||||||
|
|
||||||
verify_results = _verify_all(candidates, no_verification, jobs)
|
verify_results = _verify_all(candidates, no_verification, jobs)
|
||||||
|
|
||||||
approved: List[RepoRef] = []
|
approved: list[RepoRef] = []
|
||||||
for ident, rd, has_verified_info, verified_ok, errors in verify_results:
|
for ident, rd, has_verified_info, verified_ok, errors in verify_results:
|
||||||
if (
|
if (
|
||||||
not preview
|
not preview
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Dict, List, Tuple
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.actions.repository._parallel import (
|
from pkgmgr.actions.repository._parallel import (
|
||||||
resolve_repos,
|
resolve_repos,
|
||||||
@@ -8,10 +8,10 @@ from pkgmgr.actions.repository._parallel import (
|
|||||||
)
|
)
|
||||||
from pkgmgr.core.git.commands import GitPushArgsError, push_args
|
from pkgmgr.core.git.commands import GitPushArgsError, push_args
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def _push_one(repo_dir: str, extra_args: List[str], preview: bool) -> Tuple[bool, str]:
|
def _push_one(repo_dir: str, extra_args: list[str], preview: bool) -> tuple[bool, str]:
|
||||||
try:
|
try:
|
||||||
push_args(extra_args, cwd=repo_dir, preview=preview)
|
push_args(extra_args, cwd=repo_dir, preview=preview)
|
||||||
return (True, "")
|
return (True, "")
|
||||||
@@ -20,10 +20,10 @@ def _push_one(repo_dir: str, extra_args: List[str], preview: bool) -> Tuple[bool
|
|||||||
|
|
||||||
|
|
||||||
def push_in_parallel(
|
def push_in_parallel(
|
||||||
selected_repos: List[Repository],
|
selected_repos: list[Repository],
|
||||||
repositories_base_dir: str,
|
repositories_base_dir: str,
|
||||||
all_repos: List[Repository],
|
all_repos: list[Repository],
|
||||||
extra_args: List[str],
|
extra_args: list[str],
|
||||||
preview: bool,
|
preview: bool,
|
||||||
jobs: int = 1,
|
jobs: int = 1,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from pkgmgr.actions.update.manager import UpdateManager
|
from pkgmgr.actions.update.manager import UpdateManager
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Iterable
|
from collections.abc import Iterable
|
||||||
from typing import Any, List, Tuple
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.actions.update.system_updater import SystemUpdater
|
from pkgmgr.actions.update.system_updater import SystemUpdater
|
||||||
|
|
||||||
@@ -37,7 +35,7 @@ class UpdateManager:
|
|||||||
from pkgmgr.actions.repository.pull import pull_with_verification
|
from pkgmgr.actions.repository.pull import pull_with_verification
|
||||||
from pkgmgr.core.repository.identifier import get_repo_identifier
|
from pkgmgr.core.repository.identifier import get_repo_identifier
|
||||||
|
|
||||||
failures: List[Tuple[str, str]] = []
|
failures: list[tuple[str, str]] = []
|
||||||
|
|
||||||
for repo in list(selected_repos):
|
for repo in list(selected_repos):
|
||||||
identifier = get_repo_identifier(repo, all_repos)
|
identifier = get_repo_identifier(repo, all_repos)
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
|
|
||||||
def read_os_release(path: str = "/etc/os-release") -> Dict[str, str]:
|
def read_os_release(path: str = "/etc/os-release") -> dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Parse /etc/os-release into a dict. Returns empty dict if missing.
|
Parse /etc/os-release into a dict. Returns empty dict if missing.
|
||||||
"""
|
"""
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
result: Dict[str, str] = {}
|
result: dict[str, str] = {}
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
with open(path, encoding="utf-8") as f:
|
||||||
for line in f:
|
for line in f:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if not line or line.startswith("#") or "=" not in line:
|
if not line or line.startswith("#") or "=" not in line:
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import platform
|
import platform
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ def _find_previous_and_current_tag(
|
|||||||
def handle_changelog(
|
def handle_changelog(
|
||||||
args,
|
args,
|
||||||
ctx: CLIContext,
|
ctx: CLIContext,
|
||||||
selected: List[Repository],
|
selected: list[Repository],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Handle the 'changelog' command.
|
Handle the 'changelog' command.
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
# src/pkgmgr/cli/commands/config.py
|
# src/pkgmgr/cli/commands/config.py
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -8,7 +6,7 @@ import os
|
|||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@@ -21,7 +19,7 @@ from pkgmgr.core.config.save import save_user_config
|
|||||||
from pkgmgr.core.repository.resolve import resolve_repos
|
from pkgmgr.core.repository.resolve import resolve_repos
|
||||||
|
|
||||||
|
|
||||||
def _load_user_config(user_config_path: str) -> Dict[str, Any]:
|
def _load_user_config(user_config_path: str) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Load the user config from ~/.config/pkgmgr/config.yaml
|
Load the user config from ~/.config/pkgmgr/config.yaml
|
||||||
(or whatever ctx.user_config_path is), creating the directory if needed.
|
(or whatever ctx.user_config_path is), creating the directory if needed.
|
||||||
@@ -32,12 +30,12 @@ def _load_user_config(user_config_path: str) -> Dict[str, Any]:
|
|||||||
os.makedirs(cfg_dir, exist_ok=True)
|
os.makedirs(cfg_dir, exist_ok=True)
|
||||||
|
|
||||||
if os.path.exists(user_config_path_expanded):
|
if os.path.exists(user_config_path_expanded):
|
||||||
with open(user_config_path_expanded, "r", encoding="utf-8") as f:
|
with open(user_config_path_expanded, encoding="utf-8") as f:
|
||||||
return yaml.safe_load(f) or {"repositories": []}
|
return yaml.safe_load(f) or {"repositories": []}
|
||||||
return {"repositories": []}
|
return {"repositories": []}
|
||||||
|
|
||||||
|
|
||||||
def _find_defaults_source_dir() -> Optional[str]:
|
def _find_defaults_source_dir() -> str | None:
|
||||||
"""
|
"""
|
||||||
Find the directory inside the installed pkgmgr package that contains
|
Find the directory inside the installed pkgmgr package that contains
|
||||||
the default config files.
|
the default config files.
|
||||||
@@ -75,7 +73,7 @@ def _update_default_configs(user_config_path: str) -> None:
|
|||||||
|
|
||||||
for name in os.listdir(source_dir):
|
for name in os.listdir(source_dir):
|
||||||
lower = name.lower()
|
lower = name.lower()
|
||||||
if not (lower.endswith(".yml") or lower.endswith(".yaml")):
|
if not (lower.endswith((".yml", ".yaml"))):
|
||||||
continue
|
continue
|
||||||
if name == "config.yaml":
|
if name == "config.yaml":
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.actions.proxy import exec_proxy_command
|
from pkgmgr.actions.proxy import exec_proxy_command
|
||||||
from pkgmgr.cli.context import CLIContext
|
from pkgmgr.cli.context import CLIContext
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def handle_make(
|
def handle_make(
|
||||||
args,
|
args,
|
||||||
ctx: CLIContext,
|
ctx: CLIContext,
|
||||||
selected: List[Repository],
|
selected: list[Repository],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Handle the 'make' command by delegating to exec_proxy_command.
|
Handle the 'make' command by delegating to exec_proxy_command.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.actions.mirror import (
|
from pkgmgr.actions.mirror import (
|
||||||
diff_mirrors,
|
diff_mirrors,
|
||||||
@@ -13,13 +13,13 @@ from pkgmgr.actions.mirror import (
|
|||||||
)
|
)
|
||||||
from pkgmgr.cli.context import CLIContext
|
from pkgmgr.cli.context import CLIContext
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def handle_mirror_command(
|
def handle_mirror_command(
|
||||||
ctx: CLIContext,
|
ctx: CLIContext,
|
||||||
args: Any,
|
args: Any,
|
||||||
selected: List[Repository],
|
selected: list[Repository],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Entry point for 'pkgmgr mirror' subcommands.
|
Entry point for 'pkgmgr mirror' subcommands.
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.actions.publish import publish
|
from pkgmgr.actions.publish import publish
|
||||||
from pkgmgr.cli.context import CLIContext
|
from pkgmgr.cli.context import CLIContext
|
||||||
from pkgmgr.core.repository.dir import get_repo_dir
|
from pkgmgr.core.repository.dir import get_repo_dir
|
||||||
from pkgmgr.core.repository.identifier import get_repo_identifier
|
from pkgmgr.core.repository.identifier import get_repo_identifier
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def handle_publish(args, ctx: CLIContext, selected: List[Repository]) -> None:
|
def handle_publish(args, ctx: CLIContext, selected: list[Repository]) -> None:
|
||||||
if not selected:
|
if not selected:
|
||||||
print("[pkgmgr] No repositories selected for publish.")
|
print("[pkgmgr] No repositories selected for publish.")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.actions.publish import publish as run_publish
|
from pkgmgr.actions.publish import publish as run_publish
|
||||||
from pkgmgr.actions.release import release as run_release
|
from pkgmgr.actions.release import release as run_release
|
||||||
@@ -12,13 +10,13 @@ from pkgmgr.cli.context import CLIContext
|
|||||||
from pkgmgr.core.repository.dir import get_repo_dir
|
from pkgmgr.core.repository.dir import get_repo_dir
|
||||||
from pkgmgr.core.repository.identifier import get_repo_identifier
|
from pkgmgr.core.repository.identifier import get_repo_identifier
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def handle_release(
|
def handle_release(
|
||||||
args,
|
args,
|
||||||
ctx: CLIContext,
|
ctx: CLIContext,
|
||||||
selected: List[Repository],
|
selected: list[Repository],
|
||||||
) -> None:
|
) -> None:
|
||||||
if not selected:
|
if not selected:
|
||||||
print("[pkgmgr] No repositories selected for release.")
|
print("[pkgmgr] No repositories selected for release.")
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.actions.install import install_repos
|
from pkgmgr.actions.install import install_repos
|
||||||
from pkgmgr.actions.repository.create import create_repo
|
from pkgmgr.actions.repository.create import create_repo
|
||||||
@@ -15,7 +13,7 @@ from pkgmgr.cli.context import CLIContext
|
|||||||
from pkgmgr.core.command.run import run_command
|
from pkgmgr.core.command.run import run_command
|
||||||
from pkgmgr.core.repository.dir import get_repo_dir
|
from pkgmgr.core.repository.dir import get_repo_dir
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def _resolve_repository_directory(repository: Repository, ctx: CLIContext) -> str:
|
def _resolve_repository_directory(repository: Repository, ctx: CLIContext) -> str:
|
||||||
@@ -45,7 +43,7 @@ def _resolve_repository_directory(repository: Repository, ctx: CLIContext) -> st
|
|||||||
def handle_repos_command(
|
def handle_repos_command(
|
||||||
args,
|
args,
|
||||||
ctx: CLIContext,
|
ctx: CLIContext,
|
||||||
selected: List[Repository],
|
selected: list[Repository],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Handle core repository commands (install/update/deinstall/delete/status/list/path/shell/create).
|
Handle core repository commands (install/update/deinstall/delete/status/list/path/shell/create).
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.cli.context import CLIContext
|
from pkgmgr.cli.context import CLIContext
|
||||||
from pkgmgr.cli.tools import open_vscode_workspace
|
from pkgmgr.cli.tools import open_vscode_workspace
|
||||||
from pkgmgr.cli.tools.paths import resolve_repository_path
|
from pkgmgr.cli.tools.paths import resolve_repository_path
|
||||||
from pkgmgr.core.command.run import run_command
|
from pkgmgr.core.command.run import run_command
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def handle_tools_command(
|
def handle_tools_command(
|
||||||
args,
|
args,
|
||||||
ctx: CLIContext,
|
ctx: CLIContext,
|
||||||
selected: List[Repository],
|
selected: list[Repository],
|
||||||
) -> None:
|
) -> None:
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# nautilus "explore" command
|
# nautilus "explore" command
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from pkgmgr.core.version.source import (
|
|||||||
read_spec_version,
|
read_spec_version,
|
||||||
)
|
)
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def _print_pkgmgr_self_version() -> None:
|
def _print_pkgmgr_self_version() -> None:
|
||||||
@@ -74,7 +74,7 @@ def _print_pkgmgr_self_version() -> None:
|
|||||||
def handle_version(
|
def handle_version(
|
||||||
args,
|
args,
|
||||||
ctx: CLIContext,
|
ctx: CLIContext,
|
||||||
selected: List[Repository],
|
selected: list[Repository],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Handle the 'version' command.
|
Handle the 'version' command.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -13,8 +13,8 @@ class CLIContext:
|
|||||||
keeps the CLI layer thin and structured.
|
keeps the CLI layer thin and structured.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
config_merged: Dict[str, Any]
|
config_merged: dict[str, Any]
|
||||||
repositories_base_dir: str
|
repositories_base_dir: str
|
||||||
all_repositories: List[Dict[str, Any]]
|
all_repositories: list[dict[str, Any]]
|
||||||
binaries_dir: str
|
binaries_dir: str
|
||||||
user_config_path: str
|
user_config_path: str
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.cli.commands import (
|
from pkgmgr.cli.commands import (
|
||||||
handle_archive,
|
handle_archive,
|
||||||
@@ -34,7 +34,7 @@ def _has_explicit_selection(args) -> bool:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _select_repo_for_current_directory(ctx: CLIContext) -> List[Dict[str, Any]]:
|
def _select_repo_for_current_directory(ctx: CLIContext) -> list[dict[str, Any]]:
|
||||||
cwd = os.path.abspath(os.getcwd())
|
cwd = os.path.abspath(os.getcwd())
|
||||||
matches = []
|
matches = []
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from typing import Optional, Tuple
|
|
||||||
|
|
||||||
|
|
||||||
class SortedSubParsersAction(argparse._SubParsersAction):
|
class SortedSubParsersAction(argparse._SubParsersAction):
|
||||||
@@ -19,8 +18,8 @@ class SortedSubParsersAction(argparse._SubParsersAction):
|
|||||||
def _has_action(
|
def _has_action(
|
||||||
parser: argparse.ArgumentParser,
|
parser: argparse.ArgumentParser,
|
||||||
*,
|
*,
|
||||||
positional: Optional[str] = None,
|
positional: str | None = None,
|
||||||
options: Tuple[str, ...] = (),
|
options: tuple[str, ...] = (),
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Check whether the parser already has an action.
|
Check whether the parser already has an action.
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from pkgmgr.cli.parser.common import (
|
from pkgmgr.cli.parser.common import (
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.actions.proxy import exec_proxy_command
|
from pkgmgr.actions.proxy import exec_proxy_command
|
||||||
from pkgmgr.actions.repository.clone import clone_repos
|
from pkgmgr.actions.repository.clone import clone_repos
|
||||||
@@ -15,7 +13,7 @@ from pkgmgr.cli.context import CLIContext
|
|||||||
from pkgmgr.core.repository.dir import get_repo_dir
|
from pkgmgr.core.repository.dir import get_repo_dir
|
||||||
from pkgmgr.core.repository.selected import get_selected_repos
|
from pkgmgr.core.repository.selected import get_selected_repos
|
||||||
|
|
||||||
PROXY_COMMANDS: Dict[str, List[str]] = {
|
PROXY_COMMANDS: dict[str, list[str]] = {
|
||||||
"git": [
|
"git": [
|
||||||
"pull",
|
"pull",
|
||||||
"push",
|
"push",
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any, Dict
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.cli.context import CLIContext
|
from pkgmgr.cli.context import CLIContext
|
||||||
from pkgmgr.core.repository.dir import get_repo_dir
|
from pkgmgr.core.repository.dir import get_repo_dir
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def resolve_repository_path(repository: Repository, ctx: CLIContext) -> str:
|
def resolve_repository_path(repository: Repository, ctx: CLIContext) -> str:
|
||||||
|
|||||||
@@ -3,14 +3,14 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from typing import Any, Dict, List
|
from typing import Any
|
||||||
|
|
||||||
from pkgmgr.cli.context import CLIContext
|
from pkgmgr.cli.context import CLIContext
|
||||||
from pkgmgr.cli.tools.paths import resolve_repository_path
|
from pkgmgr.cli.tools.paths import resolve_repository_path
|
||||||
from pkgmgr.core.command.run import run_command
|
from pkgmgr.core.command.run import run_command
|
||||||
from pkgmgr.core.repository.identifier import get_repo_identifier
|
from pkgmgr.core.repository.identifier import get_repo_identifier
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def _ensure_vscode_cli_available() -> None:
|
def _ensure_vscode_cli_available() -> None:
|
||||||
@@ -26,7 +26,7 @@ def _ensure_vscode_cli_available() -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _ensure_identifiers_are_filename_safe(identifiers: List[str]) -> None:
|
def _ensure_identifiers_are_filename_safe(identifiers: list[str]) -> None:
|
||||||
"""
|
"""
|
||||||
Ensure identifiers can be used in a filename.
|
Ensure identifiers can be used in a filename.
|
||||||
|
|
||||||
@@ -52,14 +52,14 @@ def _resolve_workspaces_dir(ctx: CLIContext) -> str:
|
|||||||
return os.path.expanduser(directories_cfg.get("workspaces", "~/Workspaces"))
|
return os.path.expanduser(directories_cfg.get("workspaces", "~/Workspaces"))
|
||||||
|
|
||||||
|
|
||||||
def _build_workspace_filename(identifiers: List[str]) -> str:
|
def _build_workspace_filename(identifiers: list[str]) -> str:
|
||||||
sorted_identifiers = sorted(identifiers)
|
sorted_identifiers = sorted(identifiers)
|
||||||
return "_".join(sorted_identifiers) + ".code-workspace"
|
return "_".join(sorted_identifiers) + ".code-workspace"
|
||||||
|
|
||||||
|
|
||||||
def _build_workspace_data(
|
def _build_workspace_data(
|
||||||
selected: List[Repository], ctx: CLIContext
|
selected: list[Repository], ctx: CLIContext
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
folders = [{"path": resolve_repository_path(repo, ctx)} for repo in selected]
|
folders = [{"path": resolve_repository_path(repo, ctx)} for repo in selected]
|
||||||
return {
|
return {
|
||||||
"folders": folders,
|
"folders": folders,
|
||||||
@@ -67,7 +67,7 @@ def _build_workspace_data(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def open_vscode_workspace(ctx: CLIContext, selected: List[Repository]) -> None:
|
def open_vscode_workspace(ctx: CLIContext, selected: list[Repository]) -> None:
|
||||||
"""
|
"""
|
||||||
Create (if missing) and open a VS Code workspace for the selected repositories.
|
Create (if missing) and open a VS Code workspace for the selected repositories.
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from pkgmgr.core.repository.dir import get_repo_dir
|
from pkgmgr.core.repository.dir import get_repo_dir
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any
|
||||||
|
|
||||||
Repository = Dict[str, Any]
|
Repository = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
def _is_executable(path: str) -> bool:
|
def _is_executable(path: str) -> bool:
|
||||||
return os.path.exists(path) and os.access(path, os.X_OK)
|
return os.path.exists(path) and os.access(path, os.X_OK)
|
||||||
|
|
||||||
|
|
||||||
def _find_python_package_root(repo_dir: str) -> Optional[str]:
|
def _find_python_package_root(repo_dir: str) -> str | None:
|
||||||
"""
|
"""
|
||||||
Detect a Python src-layout package:
|
Detect a Python src-layout package:
|
||||||
|
|
||||||
@@ -29,19 +31,19 @@ def _find_python_package_root(repo_dir: str) -> Optional[str]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _nix_binary_candidates(home: str, names: List[str]) -> List[str]:
|
def _nix_binary_candidates(home: str, names: list[str]) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Build possible Nix profile binary paths for a list of candidate names.
|
Build possible Nix profile binary paths for a list of candidate names.
|
||||||
"""
|
"""
|
||||||
return [os.path.join(home, ".nix-profile", "bin", name) for name in names if name]
|
return [os.path.join(home, ".nix-profile", "bin", name) for name in names if name]
|
||||||
|
|
||||||
|
|
||||||
def _path_binary_candidates(names: List[str]) -> List[str]:
|
def _path_binary_candidates(names: list[str]) -> list[str]:
|
||||||
"""
|
"""
|
||||||
Resolve candidate names via PATH using shutil.which.
|
Resolve candidate names via PATH using shutil.which.
|
||||||
Returns only existing, executable paths.
|
Returns only existing, executable paths.
|
||||||
"""
|
"""
|
||||||
binaries: List[str] = []
|
binaries: list[str] = []
|
||||||
for name in names:
|
for name in names:
|
||||||
if not name:
|
if not name:
|
||||||
continue
|
continue
|
||||||
@@ -55,7 +57,7 @@ def resolve_command_for_repo(
|
|||||||
repo: Repository,
|
repo: Repository,
|
||||||
repo_identifier: str,
|
repo_identifier: str,
|
||||||
repo_dir: str,
|
repo_dir: str,
|
||||||
) -> Optional[str]:
|
) -> str | None:
|
||||||
"""
|
"""
|
||||||
Resolve the executable command for a repository.
|
Resolve the executable command for a repository.
|
||||||
|
|
||||||
@@ -110,7 +112,7 @@ def resolve_command_for_repo(
|
|||||||
else:
|
else:
|
||||||
python_package_name = None
|
python_package_name = None
|
||||||
|
|
||||||
candidate_names: List[str] = []
|
candidate_names: list[str] = []
|
||||||
seen: set[str] = set()
|
seen: set[str] = set()
|
||||||
|
|
||||||
for name in (
|
for name in (
|
||||||
@@ -130,8 +132,8 @@ def resolve_command_for_repo(
|
|||||||
path_binaries = _path_binary_candidates(candidate_names)
|
path_binaries = _path_binary_candidates(candidate_names)
|
||||||
|
|
||||||
# b) Classify system (/usr/...) vs non-system
|
# b) Classify system (/usr/...) vs non-system
|
||||||
system_binary: Optional[str] = None
|
system_binary: str | None = None
|
||||||
non_system_binary: Optional[str] = None
|
non_system_binary: str | None = None
|
||||||
|
|
||||||
for bin_path in path_binaries:
|
for bin_path in path_binaries:
|
||||||
if bin_path.startswith("/usr"):
|
if bin_path.startswith("/usr"):
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
import selectors
|
import selectors
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from typing import List, Optional, Union
|
from typing import Union
|
||||||
|
|
||||||
CommandType = Union[str, List[str]]
|
CommandType = Union[str, list[str]]
|
||||||
|
|
||||||
|
|
||||||
def run_command(
|
def run_command(
|
||||||
cmd: CommandType,
|
cmd: CommandType,
|
||||||
cwd: Optional[str] = None,
|
cwd: str | None = None,
|
||||||
preview: bool = False,
|
preview: bool = False,
|
||||||
allow_failure: bool = False,
|
allow_failure: bool = False,
|
||||||
) -> subprocess.CompletedProcess:
|
) -> subprocess.CompletedProcess:
|
||||||
@@ -48,8 +49,8 @@ def run_command(
|
|||||||
sel.register(process.stdout, selectors.EVENT_READ, data="stdout")
|
sel.register(process.stdout, selectors.EVENT_READ, data="stdout")
|
||||||
sel.register(process.stderr, selectors.EVENT_READ, data="stderr")
|
sel.register(process.stderr, selectors.EVENT_READ, data="stderr")
|
||||||
|
|
||||||
stdout_lines: List[str] = []
|
stdout_lines: list[str] = []
|
||||||
stderr_lines: List[str] = []
|
stderr_lines: list[str] = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while sel.get_map():
|
while sel.get_map():
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
# src/pkgmgr/core/config/load.py
|
# src/pkgmgr/core/config/load.py
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Load and merge pkgmgr configuration.
|
Load and merge pkgmgr configuration.
|
||||||
@@ -38,11 +36,11 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
Repo = Dict[str, Any]
|
Repo = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -50,7 +48,7 @@ Repo = Dict[str, Any]
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def _deep_merge(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any]:
|
def _deep_merge(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Recursively merge two dictionaries.
|
Recursively merge two dictionaries.
|
||||||
|
|
||||||
@@ -64,7 +62,7 @@ def _deep_merge(base: Dict[str, Any], override: Dict[str, Any]) -> Dict[str, Any
|
|||||||
return base
|
return base
|
||||||
|
|
||||||
|
|
||||||
def _repo_key(repo: Repo) -> Tuple[str, str, str]:
|
def _repo_key(repo: Repo) -> tuple[str, str, str]:
|
||||||
"""
|
"""
|
||||||
Normalised key for identifying a repository across config files.
|
Normalised key for identifying a repository across config files.
|
||||||
"""
|
"""
|
||||||
@@ -76,10 +74,10 @@ def _repo_key(repo: Repo) -> Tuple[str, str, str]:
|
|||||||
|
|
||||||
|
|
||||||
def _merge_repo_lists(
|
def _merge_repo_lists(
|
||||||
base_list: List[Repo],
|
base_list: list[Repo],
|
||||||
new_list: List[Repo],
|
new_list: list[Repo],
|
||||||
category_name: Optional[str] = None,
|
category_name: str | None = None,
|
||||||
) -> List[Repo]:
|
) -> list[Repo]:
|
||||||
"""
|
"""
|
||||||
Merge two repository lists, matching by (provider, account, repository).
|
Merge two repository lists, matching by (provider, account, repository).
|
||||||
|
|
||||||
@@ -87,7 +85,7 @@ def _merge_repo_lists(
|
|||||||
- If it exists, its fields are deep-merged (override wins).
|
- If it exists, its fields are deep-merged (override wins).
|
||||||
- If category_name is set, it is appended to repo["category_files"].
|
- If category_name is set, it is appended to repo["category_files"].
|
||||||
"""
|
"""
|
||||||
index: Dict[Tuple[str, str, str], Repo] = {_repo_key(r): r for r in base_list}
|
index: dict[tuple[str, str, str], Repo] = {_repo_key(r): r for r in base_list}
|
||||||
|
|
||||||
for src in new_list:
|
for src in new_list:
|
||||||
key = _repo_key(src)
|
key = _repo_key(src)
|
||||||
@@ -120,7 +118,7 @@ def _merge_repo_lists(
|
|||||||
return base_list
|
return base_list
|
||||||
|
|
||||||
|
|
||||||
def _load_yaml_file(path: Path) -> Dict[str, Any]:
|
def _load_yaml_file(path: Path) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Load a single YAML file as dict. Non-dicts yield {}.
|
Load a single YAML file as dict. Non-dicts yield {}.
|
||||||
"""
|
"""
|
||||||
@@ -135,8 +133,8 @@ def _load_yaml_file(path: Path) -> Dict[str, Any]:
|
|||||||
|
|
||||||
def _load_layer_dir(
|
def _load_layer_dir(
|
||||||
config_dir: Path,
|
config_dir: Path,
|
||||||
skip_filename: Optional[str] = None,
|
skip_filename: str | None = None,
|
||||||
) -> Dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Load all *.yml/*.yaml from a directory as layered defaults.
|
Load all *.yml/*.yaml from a directory as layered defaults.
|
||||||
|
|
||||||
@@ -148,7 +146,7 @@ def _load_layer_dir(
|
|||||||
"repositories": [...],
|
"repositories": [...],
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
defaults: Dict[str, Any] = {"directories": {}, "repositories": []}
|
defaults: dict[str, Any] = {"directories": {}, "repositories": []}
|
||||||
|
|
||||||
if not config_dir.is_dir():
|
if not config_dir.is_dir():
|
||||||
return defaults
|
return defaults
|
||||||
@@ -186,7 +184,7 @@ def _load_layer_dir(
|
|||||||
return defaults
|
return defaults
|
||||||
|
|
||||||
|
|
||||||
def _load_defaults_from_package_or_project() -> Dict[str, Any]:
|
def _load_defaults_from_package_or_project() -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Fallback: load default configs from possible install or dev layouts.
|
Fallback: load default configs from possible install or dev layouts.
|
||||||
|
|
||||||
@@ -224,7 +222,7 @@ def _load_defaults_from_package_or_project() -> Dict[str, Any]:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def load_config(user_config_path: str) -> Dict[str, Any]:
|
def load_config(user_config_path: str) -> dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Load and merge configuration for pkgmgr.
|
Load and merge configuration for pkgmgr.
|
||||||
|
|
||||||
@@ -258,14 +256,14 @@ def load_config(user_config_path: str) -> Dict[str, Any]:
|
|||||||
defaults.setdefault("repositories", [])
|
defaults.setdefault("repositories", [])
|
||||||
|
|
||||||
# 4) User config
|
# 4) User config
|
||||||
user_cfg: Dict[str, Any] = {}
|
user_cfg: dict[str, Any] = {}
|
||||||
if user_cfg_path.is_file():
|
if user_cfg_path.is_file():
|
||||||
user_cfg = _load_yaml_file(user_cfg_path)
|
user_cfg = _load_yaml_file(user_cfg_path)
|
||||||
user_cfg.setdefault("directories", {})
|
user_cfg.setdefault("directories", {})
|
||||||
user_cfg.setdefault("repositories", [])
|
user_cfg.setdefault("repositories", [])
|
||||||
|
|
||||||
# 5) Merge
|
# 5) Merge
|
||||||
merged: Dict[str, Any] = {}
|
merged: dict[str, Any] = {}
|
||||||
|
|
||||||
merged["directories"] = {}
|
merged["directories"] = {}
|
||||||
_deep_merge(merged["directories"], defaults["directories"])
|
_deep_merge(merged["directories"], defaults["directories"])
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from ..store_keys import env_var_candidates
|
from ..store_keys import env_var_candidates
|
||||||
from ..types import TokenRequest, TokenResult
|
from ..types import TokenRequest, TokenResult
|
||||||
@@ -15,7 +14,7 @@ class EnvTokenProvider:
|
|||||||
|
|
||||||
source_name: str = "env"
|
source_name: str = "env"
|
||||||
|
|
||||||
def get(self, request: TokenRequest) -> Optional[TokenResult]:
|
def get(self, request: TokenRequest) -> TokenResult | None:
|
||||||
for key in env_var_candidates(
|
for key in env_var_candidates(
|
||||||
request.provider_kind, request.host, request.owner
|
request.provider_kind, request.host, request.owner
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from ..types import TokenRequest, TokenResult
|
from ..types import TokenRequest, TokenResult
|
||||||
|
|
||||||
@@ -18,7 +17,7 @@ class GhTokenProvider:
|
|||||||
|
|
||||||
source_name: str = "gh"
|
source_name: str = "gh"
|
||||||
|
|
||||||
def get(self, request: TokenRequest) -> Optional[TokenResult]:
|
def get(self, request: TokenRequest) -> TokenResult | None:
|
||||||
# Only meaningful for GitHub-like providers
|
# Only meaningful for GitHub-like providers
|
||||||
kind = (request.provider_kind or "").strip().lower()
|
kind = (request.provider_kind or "").strip().lower()
|
||||||
if kind not in ("github", "github.com"):
|
if kind not in ("github", "github.com"):
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ from __future__ import annotations
|
|||||||
import sys
|
import sys
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from ..types import TokenRequest, TokenResult
|
from ..types import TokenRequest, TokenResult
|
||||||
|
|
||||||
|
|
||||||
def _token_help_url(provider_kind: str, host: str) -> Optional[str]:
|
def _token_help_url(provider_kind: str, host: str) -> str | None:
|
||||||
"""
|
"""
|
||||||
Return a provider-specific URL where a user can create/get an API token.
|
Return a provider-specific URL where a user can create/get an API token.
|
||||||
|
|
||||||
@@ -51,7 +50,7 @@ class PromptTokenProvider:
|
|||||||
|
|
||||||
source_name: str = "prompt"
|
source_name: str = "prompt"
|
||||||
|
|
||||||
def get(self, request: TokenRequest) -> Optional[TokenResult]:
|
def get(self, request: TokenRequest) -> TokenResult | None:
|
||||||
if not sys.stdin.isatty():
|
if not sys.stdin.isatty():
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -14,7 +13,7 @@ class KeyringKey:
|
|||||||
|
|
||||||
|
|
||||||
def build_keyring_key(
|
def build_keyring_key(
|
||||||
provider_kind: str, host: str, owner: Optional[str]
|
provider_kind: str, host: str, owner: str | None
|
||||||
) -> KeyringKey:
|
) -> KeyringKey:
|
||||||
"""Build a stable keyring key.
|
"""Build a stable keyring key.
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ def build_keyring_key(
|
|||||||
|
|
||||||
|
|
||||||
def env_var_candidates(
|
def env_var_candidates(
|
||||||
provider_kind: str, host: str, owner: Optional[str]
|
provider_kind: str, host: str, owner: str | None
|
||||||
) -> list[str]:
|
) -> list[str]:
|
||||||
"""Return a list of environment variable names to try.
|
"""Return a list of environment variable names to try.
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user