pkgmap-py 0.1.1__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: pkgmap-py
3
- Version: 0.1.1
3
+ Version: 0.2.0
4
4
  Summary: Cross-distribution Linux package name resolution
5
5
  Author: Levizor
6
6
  Author-email: Levizor <levizor@disroot.org>
@@ -19,9 +19,10 @@ Cross-distribution Linux package name resolution.
19
19
 
20
20
  Different distributions often package the exact same software under different names (for example, `ninja` on Arch is `ninja-build` on Debian and Alpine; `bat` on Arch is `rust-bat` on Debian and Fedora; `fd` on Arch is `rust-fd-find` on Debian; Nixpkgs uses specific attribute paths). `pkgmap` provides an offline SQLite database and a companion Python library/CLI to translate package names between any pair of Linux distributions, resolve packages across all repositories, or look up native packages by upstream project name.
21
21
 
22
- Data is updated daily from [Repology](https://repology.org) and indexed for instant local lookups. On first run, `pkgmap` automatically downloads and decompresses the latest daily database release (`~/.cache/pkgmap/pkgmap.db`).
22
+ Data is updated daily from [Repology](https://repology.org) and indexed for instant local lookups. On first run, `pkgmap` automatically downloads and decompresses the latest daily database snapshot (`~/.cache/pkgmap/`).
23
23
 
24
- The package is available on PyPI as [`pkgmap-py`](https://pypi.org/project/pkgmap-py/).
24
+ - **PyPI**: [`pkgmap-py`](https://pypi.org/project/pkgmap-py/)
25
+ - **Web Interface**: [levizor.github.io/pkgmap](https://levizor.github.io/pkgmap/) (runs 100% client-side via SQLite WebAssembly + OPFS)
25
26
 
26
27
  ---
27
28
 
@@ -34,13 +35,28 @@ Use `--from` (`-f`) and `--to` (`-t`):
34
35
  ```bash
35
36
  # What is Arch's 'bat' called in Debian?
36
37
  pkgmap resolve bat --from arch --to debian
37
- # Output: rust-bat (0.22.1) [category: misc]
38
+ # Output: rust-bat (0.24.0)
38
39
 
39
40
  # What is Arch's 'fd' in Nixpkgs?
40
41
  pkgmap resolve fd -f arch -t nixpkgs
41
42
  # Output: fd (10.5.0)
42
43
  ```
43
44
 
45
+ ### Batch resolution
46
+
47
+ Pass multiple package names to resolve them in a single query:
48
+
49
+ ```bash
50
+ pkgmap resolve git gcc bat --from nix --to gnuguix
51
+ ```
52
+
53
+ Output:
54
+ ```text
55
+ git -> git (2.54.0)
56
+ gcc -> gcc (15.2.0)
57
+ bat -> bat (0.26.1)
58
+ ```
59
+
44
60
  ### Resolve across all distributions
45
61
 
46
62
  Omit `--to` to see equivalents across every indexed distribution:
@@ -51,8 +67,8 @@ pkgmap resolve bat --from arch
51
67
 
52
68
  Output:
53
69
  ```text
54
- alpine_3_20: bat (0.24.0)
55
- debian_12: rust-bat (0.22.1) [category: misc]
70
+ alpine_edge: bat (0.24.0)
71
+ debian_unstable: rust-bat (0.24.0)
56
72
  fedora_rawhide: rust-bat (0.26.1)
57
73
  nix_unstable: bat (0.26.1)
58
74
  ubuntu_24_04: rust-bat (0.24.0)
@@ -65,7 +81,7 @@ Omit `--from` (or pass `--project` / `-p`) to resolve packages directly from an
65
81
 
66
82
  ```bash
67
83
  pkgmap resolve ripgrep --to debian
68
- # Output: rust-ripgrep (13.0.0) [category: misc]
84
+ # Output: rust-ripgrep (14.1.0)
69
85
  ```
70
86
 
71
87
  ### JSON output
@@ -80,13 +96,15 @@ pkgmap resolve bat -f arch -t debian --json
80
96
  [
81
97
  {
82
98
  "name": "rust-bat",
83
- "version": "0.22.1",
84
- "repo": "debian_12",
99
+ "version": "0.24.0",
100
+ "repo": "debian_unstable",
85
101
  "repo_family": "debuntu"
86
102
  }
87
103
  ]
88
104
  ```
89
105
 
106
+ When multiple packages are passed with `--json`, the output is a dictionary keyed by each input package name.
107
+
90
108
  ### Other commands
91
109
 
92
110
  ```bash
@@ -95,22 +113,42 @@ pkgmap repos
95
113
 
96
114
  # Manually refresh the local database snapshot
97
115
  pkgmap update
116
+
117
+ # Download the full database (all 330+ Repology repositories)
118
+ pkgmap update --full
98
119
  ```
99
120
 
100
121
  ---
101
122
 
123
+ ## Core vs. Full Database Variants
124
+
125
+ `pkgmap` publishes two database snapshots with every daily release:
126
+
127
+ | Variant | File | Compressed Size | Coverage | Default For |
128
+ | :--- | :--- | :--- | :--- | :--- |
129
+ | **Core** | `pkgmap-core.db` | ~7 MB | Top 10 repositories (Nixpkgs, Arch, AUR, Debian, Ubuntu, Fedora, Alpine, openSUSE, Gentoo, Void) | **CLI & Web UI** |
130
+ | **Full** | `pkgmap.db` | ~37 MB | All 330+ Repology repositories | **Python Library** |
131
+
132
+ - **CLI behavior**: Defaults to the **Core** database (`~/.cache/pkgmap/pkgmap-core.db`) unless the Full database is already downloaded locally or `--full` is passed. If you query a repository not present in Core, the CLI prompts you to run `pkgmap update --full`. You can also pass `--core` or `--full` to any CLI command (`resolve`, `update`, `repos`).
133
+ - **Python library behavior**: Defaults to the **Full** database (`~/.cache/pkgmap/pkgmap.db`). Call `set_default_variant("core")` (or set `PKGMAP_DB_VARIANT=core`) to use the Core database instead.
134
+
135
+ ---
136
+
102
137
  ## Python API
103
138
 
104
- You can also use `pkgmap` as a Python library:
139
+ You can use `pkgmap` as a Python library:
105
140
 
106
141
  ```python
107
- from pkgmap import resolve, resolve_all, resolve_batch
142
+ from pkgmap import resolve, resolve_all, resolve_batch, set_default_variant
143
+
144
+ # Optional: switch library default from 'full' to 'core'
145
+ # set_default_variant("core")
108
146
 
109
147
  # 1. Single package resolution
110
148
  res = resolve("bat", from_repo="arch", to_repo="debian")
111
149
  if res:
112
150
  print(res.name, res.version)
113
- # Output: rust-bat 0.22.1
151
+ # Output: rust-bat 0.24.0
114
152
 
115
153
  # 2. Query all repositories for equivalents
116
154
  matches = resolve_all("bat", from_repo="arch")
@@ -133,20 +171,24 @@ for pkg, match in results.items():
133
171
  - `resolve_batch(packages, from_repo=None, to_repo=None, db_path=None)`: Resolves a sequence of packages in a single batch query, returning `{package: PackageResolution | None}`.
134
172
  - `resolve_batch_all(packages, from_repo=None, to_repo=None, db_path=None)`: Batch resolution returning `{package: list[PackageResolution]}`.
135
173
  - `list_repositories(db_path=None)`: Returns all repositories with package counts and families.
174
+ - `set_default_variant(variant)`: Sets the default database variant (`"full"` or `"core"`).
175
+ - `get_default_variant()`: Returns the active default database variant.
136
176
 
137
177
  ---
138
178
 
139
179
  ## Configuration
140
180
 
141
- By default, the database is stored at:
142
- - `~/.cache/pkgmap/pkgmap.db`
181
+ By default, databases are cached at:
182
+ - Full database: `~/.cache/pkgmap/pkgmap.db`
183
+ - Core database: `~/.cache/pkgmap/pkgmap-core.db`
143
184
 
144
- You can customize the location using:
145
- - The `PKGMAP_DB_PATH` environment variable:
185
+ You can customize behavior using environment variables:
186
+ - `PKGMAP_DB_VARIANT`: Set to `core` or `full` to override the default database variant.
187
+ - `PKGMAP_DB_PATH`: Override the database file path directly:
146
188
  ```bash
147
189
  export PKGMAP_DB_PATH="/path/to/custom/pkgmap.db"
148
190
  ```
149
- - Or the `--db-path` argument on the CLI / `db_path` parameter in Python.
191
+ - Or pass `--db-path` on the CLI / `db_path` in Python.
150
192
 
151
193
  ---
152
194
 
@@ -4,9 +4,10 @@ Cross-distribution Linux package name resolution.
4
4
 
5
5
  Different distributions often package the exact same software under different names (for example, `ninja` on Arch is `ninja-build` on Debian and Alpine; `bat` on Arch is `rust-bat` on Debian and Fedora; `fd` on Arch is `rust-fd-find` on Debian; Nixpkgs uses specific attribute paths). `pkgmap` provides an offline SQLite database and a companion Python library/CLI to translate package names between any pair of Linux distributions, resolve packages across all repositories, or look up native packages by upstream project name.
6
6
 
7
- Data is updated daily from [Repology](https://repology.org) and indexed for instant local lookups. On first run, `pkgmap` automatically downloads and decompresses the latest daily database release (`~/.cache/pkgmap/pkgmap.db`).
7
+ Data is updated daily from [Repology](https://repology.org) and indexed for instant local lookups. On first run, `pkgmap` automatically downloads and decompresses the latest daily database snapshot (`~/.cache/pkgmap/`).
8
8
 
9
- The package is available on PyPI as [`pkgmap-py`](https://pypi.org/project/pkgmap-py/).
9
+ - **PyPI**: [`pkgmap-py`](https://pypi.org/project/pkgmap-py/)
10
+ - **Web Interface**: [levizor.github.io/pkgmap](https://levizor.github.io/pkgmap/) (runs 100% client-side via SQLite WebAssembly + OPFS)
10
11
 
11
12
  ---
12
13
 
@@ -19,13 +20,28 @@ Use `--from` (`-f`) and `--to` (`-t`):
19
20
  ```bash
20
21
  # What is Arch's 'bat' called in Debian?
21
22
  pkgmap resolve bat --from arch --to debian
22
- # Output: rust-bat (0.22.1) [category: misc]
23
+ # Output: rust-bat (0.24.0)
23
24
 
24
25
  # What is Arch's 'fd' in Nixpkgs?
25
26
  pkgmap resolve fd -f arch -t nixpkgs
26
27
  # Output: fd (10.5.0)
27
28
  ```
28
29
 
30
+ ### Batch resolution
31
+
32
+ Pass multiple package names to resolve them in a single query:
33
+
34
+ ```bash
35
+ pkgmap resolve git gcc bat --from nix --to gnuguix
36
+ ```
37
+
38
+ Output:
39
+ ```text
40
+ git -> git (2.54.0)
41
+ gcc -> gcc (15.2.0)
42
+ bat -> bat (0.26.1)
43
+ ```
44
+
29
45
  ### Resolve across all distributions
30
46
 
31
47
  Omit `--to` to see equivalents across every indexed distribution:
@@ -36,8 +52,8 @@ pkgmap resolve bat --from arch
36
52
 
37
53
  Output:
38
54
  ```text
39
- alpine_3_20: bat (0.24.0)
40
- debian_12: rust-bat (0.22.1) [category: misc]
55
+ alpine_edge: bat (0.24.0)
56
+ debian_unstable: rust-bat (0.24.0)
41
57
  fedora_rawhide: rust-bat (0.26.1)
42
58
  nix_unstable: bat (0.26.1)
43
59
  ubuntu_24_04: rust-bat (0.24.0)
@@ -50,7 +66,7 @@ Omit `--from` (or pass `--project` / `-p`) to resolve packages directly from an
50
66
 
51
67
  ```bash
52
68
  pkgmap resolve ripgrep --to debian
53
- # Output: rust-ripgrep (13.0.0) [category: misc]
69
+ # Output: rust-ripgrep (14.1.0)
54
70
  ```
55
71
 
56
72
  ### JSON output
@@ -65,13 +81,15 @@ pkgmap resolve bat -f arch -t debian --json
65
81
  [
66
82
  {
67
83
  "name": "rust-bat",
68
- "version": "0.22.1",
69
- "repo": "debian_12",
84
+ "version": "0.24.0",
85
+ "repo": "debian_unstable",
70
86
  "repo_family": "debuntu"
71
87
  }
72
88
  ]
73
89
  ```
74
90
 
91
+ When multiple packages are passed with `--json`, the output is a dictionary keyed by each input package name.
92
+
75
93
  ### Other commands
76
94
 
77
95
  ```bash
@@ -80,22 +98,42 @@ pkgmap repos
80
98
 
81
99
  # Manually refresh the local database snapshot
82
100
  pkgmap update
101
+
102
+ # Download the full database (all 330+ Repology repositories)
103
+ pkgmap update --full
83
104
  ```
84
105
 
85
106
  ---
86
107
 
108
+ ## Core vs. Full Database Variants
109
+
110
+ `pkgmap` publishes two database snapshots with every daily release:
111
+
112
+ | Variant | File | Compressed Size | Coverage | Default For |
113
+ | :--- | :--- | :--- | :--- | :--- |
114
+ | **Core** | `pkgmap-core.db` | ~7 MB | Top 10 repositories (Nixpkgs, Arch, AUR, Debian, Ubuntu, Fedora, Alpine, openSUSE, Gentoo, Void) | **CLI & Web UI** |
115
+ | **Full** | `pkgmap.db` | ~37 MB | All 330+ Repology repositories | **Python Library** |
116
+
117
+ - **CLI behavior**: Defaults to the **Core** database (`~/.cache/pkgmap/pkgmap-core.db`) unless the Full database is already downloaded locally or `--full` is passed. If you query a repository not present in Core, the CLI prompts you to run `pkgmap update --full`. You can also pass `--core` or `--full` to any CLI command (`resolve`, `update`, `repos`).
118
+ - **Python library behavior**: Defaults to the **Full** database (`~/.cache/pkgmap/pkgmap.db`). Call `set_default_variant("core")` (or set `PKGMAP_DB_VARIANT=core`) to use the Core database instead.
119
+
120
+ ---
121
+
87
122
  ## Python API
88
123
 
89
- You can also use `pkgmap` as a Python library:
124
+ You can use `pkgmap` as a Python library:
90
125
 
91
126
  ```python
92
- from pkgmap import resolve, resolve_all, resolve_batch
127
+ from pkgmap import resolve, resolve_all, resolve_batch, set_default_variant
128
+
129
+ # Optional: switch library default from 'full' to 'core'
130
+ # set_default_variant("core")
93
131
 
94
132
  # 1. Single package resolution
95
133
  res = resolve("bat", from_repo="arch", to_repo="debian")
96
134
  if res:
97
135
  print(res.name, res.version)
98
- # Output: rust-bat 0.22.1
136
+ # Output: rust-bat 0.24.0
99
137
 
100
138
  # 2. Query all repositories for equivalents
101
139
  matches = resolve_all("bat", from_repo="arch")
@@ -118,20 +156,24 @@ for pkg, match in results.items():
118
156
  - `resolve_batch(packages, from_repo=None, to_repo=None, db_path=None)`: Resolves a sequence of packages in a single batch query, returning `{package: PackageResolution | None}`.
119
157
  - `resolve_batch_all(packages, from_repo=None, to_repo=None, db_path=None)`: Batch resolution returning `{package: list[PackageResolution]}`.
120
158
  - `list_repositories(db_path=None)`: Returns all repositories with package counts and families.
159
+ - `set_default_variant(variant)`: Sets the default database variant (`"full"` or `"core"`).
160
+ - `get_default_variant()`: Returns the active default database variant.
121
161
 
122
162
  ---
123
163
 
124
164
  ## Configuration
125
165
 
126
- By default, the database is stored at:
127
- - `~/.cache/pkgmap/pkgmap.db`
166
+ By default, databases are cached at:
167
+ - Full database: `~/.cache/pkgmap/pkgmap.db`
168
+ - Core database: `~/.cache/pkgmap/pkgmap-core.db`
128
169
 
129
- You can customize the location using:
130
- - The `PKGMAP_DB_PATH` environment variable:
170
+ You can customize behavior using environment variables:
171
+ - `PKGMAP_DB_VARIANT`: Set to `core` or `full` to override the default database variant.
172
+ - `PKGMAP_DB_PATH`: Override the database file path directly:
131
173
  ```bash
132
174
  export PKGMAP_DB_PATH="/path/to/custom/pkgmap.db"
133
175
  ```
134
- - Or the `--db-path` argument on the CLI / `db_path` parameter in Python.
176
+ - Or pass `--db-path` on the CLI / `db_path` in Python.
135
177
 
136
178
  ---
137
179
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pkgmap-py"
3
- version = "0.1.1"
3
+ version = "0.2.0"
4
4
  description = "Cross-distribution Linux package name resolution"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pkgmap-py"
3
- version = "0.1.1"
3
+ version = "0.2.0"
4
4
  description = "Cross-distribution Linux package name resolution"
5
5
  authors = [{ name = "Levizor", email = "levizor@disroot.org" }]
6
6
  license = { text = "MIT" }
@@ -1,6 +1,13 @@
1
1
  """Cross-distribution Linux package name resolution library."""
2
2
 
3
- from pkgmap.db import ensure_database, get_database_path, update_database
3
+ from pkgmap.db import (
4
+ DatabaseVariant,
5
+ ensure_database,
6
+ get_database_path,
7
+ get_default_variant,
8
+ set_default_variant,
9
+ update_database,
10
+ )
4
11
  from pkgmap.resolver import (
5
12
  PackageResolution,
6
13
  list_repositories,
@@ -11,16 +18,19 @@ from pkgmap.resolver import (
11
18
  resolve_project,
12
19
  )
13
20
 
14
- __version__ = "0.1.0"
21
+ __version__ = "0.2.0"
15
22
  __all__ = [
23
+ "DatabaseVariant",
16
24
  "PackageResolution",
17
25
  "ensure_database",
18
26
  "get_database_path",
27
+ "get_default_variant",
19
28
  "list_repositories",
20
29
  "resolve",
21
30
  "resolve_all",
22
31
  "resolve_batch",
23
32
  "resolve_batch_all",
24
33
  "resolve_project",
34
+ "set_default_variant",
25
35
  "update_database",
26
36
  ]
@@ -53,6 +53,21 @@ def pipeline_build(
53
53
  help="Path for the output SQLite database",
54
54
  ),
55
55
  ] = Path("pkgmap.db"),
56
+ core_output_file: Annotated[
57
+ Path | None,
58
+ typer.Option(
59
+ "-c",
60
+ "--core-output-path",
61
+ help="Optional path for the core subset SQLite database (e.g. pkgmap-core.db)",
62
+ ),
63
+ ] = None,
64
+ repos_config: Annotated[
65
+ Path | None,
66
+ typer.Option(
67
+ "--repos-config",
68
+ help="Optional path to repos.toml configuration file",
69
+ ),
70
+ ] = None,
56
71
  batch_size: Annotated[
57
72
  int,
58
73
  typer.Option(
@@ -73,6 +88,8 @@ def pipeline_build(
73
88
  pipeline.main(
74
89
  dump_file=input_dump,
75
90
  output_file=output_file,
91
+ core_output_file=core_output_file,
92
+ repos_config=repos_config,
76
93
  batch_size=batch_size,
77
94
  release_notes=release_notes,
78
95
  dump_date=dump_date,
@@ -0,0 +1,267 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import Annotated
4
+
5
+ import typer
6
+
7
+ from pkgmap.db import (
8
+ DatabaseVariant,
9
+ get_database_path,
10
+ resolve_cli_variant,
11
+ set_default_variant,
12
+ update_database,
13
+ )
14
+ from pkgmap.resolver import (
15
+ get_missing_repositories,
16
+ list_repositories,
17
+ resolve_batch_all,
18
+ )
19
+
20
+ cli_app = typer.Typer(
21
+ name="pkgmap",
22
+ help="Cross-distribution Linux package name resolution CLI",
23
+ add_completion=False,
24
+ )
25
+
26
+
27
+ def _select_variant(core: bool, full: bool) -> DatabaseVariant:
28
+ if core and full:
29
+ typer.secho(
30
+ "Error: Cannot specify both --core and --full.",
31
+ fg=typer.colors.RED,
32
+ )
33
+ raise typer.Exit(code=2)
34
+ variant = resolve_cli_variant(full=full, core=core)
35
+ set_default_variant(variant)
36
+ return variant
37
+
38
+
39
+ @cli_app.callback()
40
+ def cli_callback(
41
+ verbose: Annotated[
42
+ bool,
43
+ typer.Option("--verbose", "-v", help="Enable verbose debug logging"),
44
+ ] = False,
45
+ ) -> None:
46
+ if verbose:
47
+ import logging
48
+
49
+ for name in ("pkgmap", "pkgmap-db", "pkgmap-downloader", "pkgmap-consumer"):
50
+ log = logging.getLogger(name)
51
+ log.setLevel(logging.DEBUG)
52
+ for handler in log.handlers:
53
+ handler.setLevel(logging.DEBUG)
54
+
55
+
56
+ def _format_not_found_msg(
57
+ package: str,
58
+ from_repo: str | None,
59
+ to_repo: str | None,
60
+ project: bool,
61
+ ) -> str:
62
+ if from_repo and not project:
63
+ if to_repo:
64
+ return f"No equivalent package found for '{package}' from '{from_repo}' to '{to_repo}'."
65
+ return f"No equivalent package found for '{package}' from '{from_repo}'."
66
+ if to_repo:
67
+ return f"No equivalent package found for project '{package}' in '{to_repo}'."
68
+ return f"No equivalent package found for project '{package}'."
69
+
70
+
71
+ @cli_app.command(name="resolve")
72
+ def resolve_cmd(
73
+ packages: Annotated[
74
+ list[str],
75
+ typer.Argument(help="Package or upstream project name(s) to resolve"),
76
+ ],
77
+ to_repo: Annotated[
78
+ str | None,
79
+ typer.Option(
80
+ "--to",
81
+ "-t",
82
+ help="Target repository/distribution (e.g. nixpkgs, alpine). If omitted, matches across all repositories.",
83
+ ),
84
+ ] = None,
85
+ from_repo: Annotated[
86
+ str | None,
87
+ typer.Option(
88
+ "--from",
89
+ "-f",
90
+ help="Source repository/distribution (e.g. debian, arch). If omitted, package is resolved by upstream project name.",
91
+ ),
92
+ ] = None,
93
+ project: Annotated[
94
+ bool,
95
+ typer.Option(
96
+ "--project",
97
+ "-p",
98
+ help="Resolve package directly as an upstream project name",
99
+ ),
100
+ ] = False,
101
+ all_matches: Annotated[
102
+ bool,
103
+ typer.Option("--all", "-a", help="Show all matching target packages"),
104
+ ] = False,
105
+ json_output: Annotated[
106
+ bool,
107
+ typer.Option("--json", "-j", help="Output results in JSON format"),
108
+ ] = False,
109
+ core: Annotated[
110
+ bool,
111
+ typer.Option("--core", help="Use the core database (top 10 repositories)"),
112
+ ] = False,
113
+ full: Annotated[
114
+ bool,
115
+ typer.Option("--full", help="Use the full database (all repositories)"),
116
+ ] = False,
117
+ db_path: Annotated[
118
+ Path | None,
119
+ typer.Option("--db-path", help="Custom path to pkgmap.db database"),
120
+ ] = None,
121
+ ) -> None:
122
+ """Resolve one or more package names across Linux distributions."""
123
+ if project and from_repo:
124
+ typer.secho(
125
+ "Error: Cannot specify both --from and --project.",
126
+ fg=typer.colors.RED,
127
+ )
128
+ raise typer.Exit(code=2)
129
+
130
+ variant = _select_variant(core=core, full=full)
131
+ effective_from = from_repo if not project else None
132
+ batch_results = resolve_batch_all(
133
+ packages,
134
+ from_repo=effective_from,
135
+ to_repo=to_repo,
136
+ db_path=db_path,
137
+ )
138
+
139
+ is_batch = len(packages) > 1
140
+ any_found = any(batch_results.get(pkg) for pkg in packages)
141
+
142
+ if not any_found:
143
+ if json_output:
144
+ empty_payload: list[object] | dict[str, list[object]] = (
145
+ {pkg: [] for pkg in packages} if is_batch else []
146
+ )
147
+ typer.echo(json.dumps(empty_payload, indent=2 if is_batch else None))
148
+ else:
149
+ for pkg in packages:
150
+ msg = _format_not_found_msg(pkg, from_repo, to_repo, project)
151
+ typer.secho(msg, fg=typer.colors.YELLOW)
152
+
153
+ requested_repos = [r for r in (from_repo, to_repo) if r]
154
+ missing_repos = get_missing_repositories(requested_repos, db_path=db_path)
155
+ if missing_repos and variant == "core":
156
+ missing_str = ", ".join(f"'{r}'" for r in missing_repos)
157
+ typer.secho(
158
+ f"Repository {missing_str} is not in the core database. "
159
+ "Try the full database with: pkgmap update --full",
160
+ fg=typer.colors.CYAN,
161
+ )
162
+ raise typer.Exit(code=1)
163
+
164
+ if json_output:
165
+ if not is_batch:
166
+ pkg = packages[0]
167
+ matches = batch_results.get(pkg, [])
168
+ items = matches if (all_matches or to_repo is None) else matches[:1]
169
+ data = [
170
+ {
171
+ "name": r.name,
172
+ "version": r.version,
173
+ "repo": r.repo,
174
+ "repo_family": r.repo_family,
175
+ }
176
+ for r in items
177
+ ]
178
+ typer.echo(json.dumps(data, indent=2))
179
+ else:
180
+ batch_data = {
181
+ pkg: [
182
+ {
183
+ "name": r.name,
184
+ "version": r.version,
185
+ "repo": r.repo,
186
+ "repo_family": r.repo_family,
187
+ }
188
+ for r in (
189
+ batch_results.get(pkg, [])
190
+ if (all_matches or to_repo is None)
191
+ else batch_results.get(pkg, [])[:1]
192
+ )
193
+ ]
194
+ for pkg in packages
195
+ }
196
+ typer.echo(json.dumps(batch_data, indent=2))
197
+ return
198
+
199
+ for pkg in packages:
200
+ matches = batch_results.get(pkg, [])
201
+ items_to_show = matches if (all_matches or to_repo is None) else matches[:1]
202
+ if not items_to_show:
203
+ typer.secho(f"{pkg} -> not found", fg=typer.colors.YELLOW)
204
+ continue
205
+ for res in items_to_show:
206
+ version_str = f" ({res.version})" if res.version else ""
207
+ repo_prefix = f"{res.repo}: " if to_repo is None else ""
208
+ pkg_prefix = f"{pkg} -> " if is_batch else ""
209
+ typer.echo(f"{pkg_prefix}{repo_prefix}{res.name}{version_str}")
210
+
211
+
212
+ @cli_app.command(name="update")
213
+ def update_cmd(
214
+ core: Annotated[
215
+ bool,
216
+ typer.Option("--core", help="Download the core database (top 10 repositories)"),
217
+ ] = False,
218
+ full: Annotated[
219
+ bool,
220
+ typer.Option("--full", help="Download the full database (all repositories)"),
221
+ ] = False,
222
+ db_path: Annotated[
223
+ Path | None,
224
+ typer.Option("--db-path", help="Custom path to save pkgmap.db database"),
225
+ ] = None,
226
+ ) -> None:
227
+ """Explicitly download and update the local package map database."""
228
+ variant = _select_variant(core=core, full=full)
229
+ target_path = get_database_path(db_path, variant=variant)
230
+ typer.echo(f"Updating {variant} package database at {target_path}...")
231
+ update_database(db_path=target_path, show_progress=True, variant=variant)
232
+ typer.secho(
233
+ f"Successfully updated {variant} database at {target_path}",
234
+ fg=typer.colors.GREEN,
235
+ )
236
+
237
+
238
+ @cli_app.command(name="repos")
239
+ def repos_cmd(
240
+ core: Annotated[
241
+ bool,
242
+ typer.Option("--core", help="List repositories from the core database"),
243
+ ] = False,
244
+ full: Annotated[
245
+ bool,
246
+ typer.Option("--full", help="List repositories from the full database"),
247
+ ] = False,
248
+ db_path: Annotated[
249
+ Path | None,
250
+ typer.Option("--db-path", help="Custom path to pkgmap.db database"),
251
+ ] = None,
252
+ ) -> None:
253
+ """List all available distributions and repositories."""
254
+ _select_variant(core=core, full=full)
255
+ repos = list_repositories(db_path=db_path)
256
+ typer.echo(f"{'Repository':<25} {'Family':<15} {'Packages':>10}")
257
+ typer.echo("-" * 52)
258
+ for r in sorted(repos, key=lambda x: x.name):
259
+ typer.echo(f"{r.name:<25} {r.family:<15} {r.package_count:>10,}")
260
+
261
+
262
+ def main() -> None:
263
+ cli_app()
264
+
265
+
266
+ if __name__ == "__main__":
267
+ main()