Skip to content

Commit 302ebc2

Browse files
committed
Fix dnf search parsing and distro-aware suggestions
1 parent fb77d08 commit 302ebc2

2 files changed

Lines changed: 56 additions & 25 deletions

File tree

archpkg/cli.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,25 @@ def github_fallback(query: str, unavailable_sources: Optional[List[str]] = None)
337337
unavailable_sources: List of package managers that failed to search
338338
"""
339339
logger.info(f"No packages found for query '{query}', providing GitHub fallback")
340-
340+
341+
# Determine distro-aware install commands
342+
detected_distro = detect_distro()
343+
install_snap_cmd = "Check your distro docs for snapd"
344+
install_flatpak_cmd = "Check your distro docs for flatpak"
345+
346+
if detected_distro in {"ubuntu", "debian"}:
347+
install_snap_cmd = "sudo apt install snapd"
348+
install_flatpak_cmd = "sudo apt install flatpak"
349+
elif detected_distro == "fedora":
350+
install_snap_cmd = "sudo dnf install snapd"
351+
install_flatpak_cmd = "sudo dnf install flatpak"
352+
elif detected_distro == "suse":
353+
install_snap_cmd = "sudo zypper install snapd"
354+
install_flatpak_cmd = "sudo zypper install flatpak"
355+
elif detected_distro in {"arch", "manjaro"}:
356+
install_snap_cmd = "sudo pacman -S snapd"
357+
install_flatpak_cmd = "sudo pacman -S flatpak"
358+
341359
# Build alternative options message
342360
alt_options = [
343361
"- Install from GitHub repository (source code)",
@@ -349,9 +367,9 @@ def github_fallback(query: str, unavailable_sources: Optional[List[str]] = None)
349367
# If Snap or Flatpak were unavailable, offer them as options
350368
if unavailable_sources:
351369
if "Snap" in unavailable_sources:
352-
alt_options.insert(0, "- [yellow]Install Snap[/yellow]: [cyan]sudo apt install snapd[/cyan] (then retry search)")
370+
alt_options.insert(0, f"- [yellow]Install Snap[/yellow]: [cyan]{install_snap_cmd}[/cyan] (then retry search)")
353371
if "Flatpak" in unavailable_sources:
354-
alt_options.insert(0, "- [yellow]Install Flatpak[/yellow]: [cyan]sudo apt install flatpak[/cyan] (then retry search)")
372+
alt_options.insert(0, f"- [yellow]Install Flatpak[/yellow]: [cyan]{install_flatpak_cmd}[/cyan] (then retry search)")
355373

356374
panel_content = f"[yellow]No packages found for '{query}' in available repositories.[/yellow]\n\n"
357375
panel_content += "[bold cyan]Alternative options:[/bold cyan]\n"

archpkg/search_dnf.py

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -108,39 +108,52 @@ def search_dnf(query: str, cache_manager: Optional[object] = None) -> List[Tuple
108108
return []
109109

110110
logger.debug("Parsing DNF search results")
111-
# Parse DNF output
111+
# Parse DNF/DNF5 output. DNF5 may not emit explicit headers and can use tabs or
112+
# multiple spaces between package name and description. We avoid relying on a
113+
# header toggle and instead parse any reasonable package line.
112114
packages = []
113-
in_results = False
114115
lines_processed = 0
115116

117+
arch_pattern = r"\.(x86_64|i686|armv7hl|aarch64|ppc64le|s390x|noarch)$"
118+
116119
for line in output.split("\n"):
117120
line = line.strip()
118121
lines_processed += 1
119-
122+
120123
if not line:
121124
continue
122125

123-
# Detect start of results section
124-
if "====" in line or ("Name" in line and "Matched" in line):
125-
logger.debug("Found DNF results section header")
126-
in_results = True
126+
# Skip meta/info lines
127+
lower_line = line.lower()
128+
if lower_line.startswith((
129+
"last metadata",
130+
"updating",
131+
"repositories",
132+
"matched fields",
133+
"error:",
134+
"warning:"
135+
)):
136+
continue
137+
138+
# DNF classic format: "name : description"
139+
match = re.match(r"^(\S+)\s*:\s*(.+)$", line)
140+
141+
# DNF5 format often uses tabs or multiple spaces: "name<TAB>description"
142+
if not match:
143+
match = re.match(r"^(\S+)\s{2,}(.+)$", line) or re.match(r"^(\S+)\t+(.+)$", line)
144+
145+
if match:
146+
name_version = match.group(1).strip()
147+
desc = match.group(2).strip()
148+
149+
# Remove architecture suffix (e.g., .x86_64, .noarch) if present
150+
name = re.sub(arch_pattern, "", name_version)
151+
152+
packages.append((name, desc, "dnf"))
153+
logger.debug(f"Found DNF package: {name}")
127154
continue
128155

129-
# Process package lines
130-
if in_results and line and not line.startswith("Last metadata"):
131-
if " : " in line: # standard DNF format: "package : description"
132-
parts = line.split(" : ", 1)
133-
if len(parts) == 2:
134-
name_version = parts[0].strip()
135-
desc = parts[1].strip()
136-
137-
# Remove architecture suffix (e.g., .x86_64, .noarch) if present
138-
arch_pattern = r"\.(x86_64|i686|armv7hl|aarch64|ppc64le|s390x|noarch)$"
139-
name = re.sub(arch_pattern, "", name_version)
140-
141-
# IMPROVED: Standardized source name to lowercase
142-
packages.append((name, desc, "dnf"))
143-
logger.debug(f"Found DNF package: {name}")
156+
logger.info(f"DNF search completed: {len(packages)} packages found from {lines_processed} lines")
144157

145158
logger.info(f"DNF search completed: {len(packages)} packages found from {lines_processed} lines")
146159

0 commit comments

Comments
 (0)