Pattern generation and URL handling for AppImage files.
This module contains functions for parsing GitHub URLs, normalizing repository URLs,
generating regex patterns for AppImage file matching, and detecting prerelease-only
repositories.
create_pattern_from_filenames(filenames, include_both_formats=False)
Create a regex pattern from actual AppImage/ZIP filenames.
Source code in src/appimage_updater/pattern_generator.py
| def create_pattern_from_filenames(filenames: list[str], include_both_formats: bool = False) -> str:
"""Create a regex pattern from actual AppImage/ZIP filenames."""
if not filenames:
return _build_pattern("", include_both_formats, empty_ok=True)
base_filenames = _strip_extensions_list(filenames)
common_prefix = _derive_common_prefix(base_filenames, filenames)
common_prefix = _generalize_pattern_prefix(common_prefix)
pattern = _build_pattern(common_prefix, include_both_formats)
fmt = "both ZIP and AppImage" if include_both_formats else "AppImage"
logger.debug(f"Created {fmt} pattern '{pattern}' from {len(filenames)} files: {filenames[:3]}...")
return pattern
|
detect_source_type(url)
Detect the source type based on the URL.
Source code in src/appimage_updater/pattern_generator.py
| def detect_source_type(url: str) -> str:
"""Detect the source type based on the URL."""
return detect_repository_type(url)
|
fetch_appimage_pattern_from_github(url)
async
Async function to fetch AppImage pattern from repository releases.
Looks for both direct AppImage files and ZIP files that might contain AppImages.
Prioritizes stable releases over prereleases for better pattern generation.
Source code in src/appimage_updater/pattern_generator.py
| async def fetch_appimage_pattern_from_github(url: str) -> str | None:
"""Async function to fetch AppImage pattern from repository releases.
Looks for both direct AppImage files and ZIP files that might contain AppImages.
Prioritizes stable releases over prereleases for better pattern generation.
"""
try:
client = get_repository_client(url)
releases = await client.get_releases(url, limit=20)
groups = _collect_release_files(releases)
target_files = _select_target_files(groups)
if not target_files:
logger.debug("No AppImage or ZIP files found in any releases")
return None
return create_pattern_from_filenames(target_files, include_both_formats=True)
except Exception as e:
logger.debug(f"Error fetching releases: {e}")
return None
|
find_common_prefix(strings)
Find the longest common prefix among a list of strings.
Source code in src/appimage_updater/pattern_generator.py
| def find_common_prefix(strings: list[str]) -> str:
"""Find the longest common prefix among a list of strings."""
if not strings:
return ""
# Start with the first string
prefix = strings[0]
for string in strings[1:]:
# Find common prefix with current string
common_len = _find_common_length(prefix, string)
prefix = prefix[:common_len]
# If prefix becomes too short, stop
if len(prefix) < 2:
break
return prefix
|
generate_appimage_pattern_async(app_name, url)
async
Async version of pattern generation for use in async contexts.
First attempts to fetch actual AppImage files from GitHub releases to create
an accurate pattern. Falls back to intelligent defaults if that fails.
Source code in src/appimage_updater/pattern_generator.py
| async def generate_appimage_pattern_async(app_name: str, url: str) -> str:
"""Async version of pattern generation for use in async contexts.
First attempts to fetch actual AppImage files from GitHub releases to create
an accurate pattern. Falls back to intelligent defaults if that fails.
"""
try:
# Try to get pattern from actual GitHub releases
pattern = await fetch_appimage_pattern_from_github(url)
if pattern:
logger.debug(f"Generated pattern from releases: {pattern}")
return pattern
except Exception as e:
logger.debug(f"Failed to generate pattern from releases: {e}")
# Fall through to fallback logic
# Fallback: Use intelligent defaults based on the app name and URL
logger.debug("Using fallback pattern generation")
return generate_fallback_pattern(app_name, url)
|
generate_fallback_pattern(app_name, url)
Generate a fallback pattern using app name and URL heuristics.
This is the original logic, kept as a fallback when we can't fetch
actual release data from GitHub. Now includes both ZIP and AppImage formats
to handle projects that package AppImages inside ZIP files.
Source code in src/appimage_updater/pattern_generator.py
| def generate_fallback_pattern(app_name: str, url: str) -> str:
"""Generate a fallback pattern using app name and URL heuristics.
This is the original logic, kept as a fallback when we can't fetch
actual release data from GitHub. Now includes both ZIP and AppImage formats
to handle projects that package AppImages inside ZIP files.
"""
# Start with the app name as base (prefer app name over repo name for better matching)
base_name = re.escape(app_name)
# Check if it's a GitHub URL - but prioritize app name since it's usually more accurate
github_info = parse_github_url(url)
if github_info:
owner, repo = github_info
# Only use repo name if app_name seems generic or is very different
# This prevents issues like "desktop" matching "GitHubDesktop"
if (
app_name.lower() in ["app", "application", "tool"] # Generic app names
or (len(repo) > len(app_name) and app_name.lower() in repo.lower()) # App name is subset of repo
):
base_name = re.escape(repo)
# Create a flexible pattern that handles common naming conventions
# Support both ZIP and AppImage formats to handle projects that package AppImages in ZIP files
# Make pattern flexible for common character substitutions (underscore/hyphen, etc.)
# Replace both underscores and hyphens with character class allowing either
flexible_name = re.sub(r"[_-]", "[_-]", base_name)
pattern = f"(?i){flexible_name}.*\\.(?:zip|AppImage)(\\.(|current|old))?$"
return pattern
|
normalize_github_url(url)
Normalize GitHub URL to repository format and detect if it was corrected.
Detects GitHub download URLs (releases/download/...) and converts them to repository URLs.
Returns (normalized_url, was_corrected) tuple.
Source code in src/appimage_updater/pattern_generator.py
| def normalize_github_url(url: str) -> tuple[str, bool]:
"""Normalize GitHub URL to repository format and detect if it was corrected.
Detects GitHub download URLs (releases/download/...) and converts them to repository URLs.
Returns (normalized_url, was_corrected) tuple.
"""
try:
if not _is_github_url(url):
return url, False
path_parts = _extract_url_path_parts(url)
if len(path_parts) < 2:
return url, False
owner, repo = path_parts[0], path_parts[1]
return _normalize_github_path(path_parts, owner, repo, url)
except Exception as e:
logger.debug(f"Failed to normalize GitHub URL {url}: {e}")
return url, False
|
parse_github_url(url)
Parse GitHub URL and extract owner/repo information.
Returns (owner, repo) tuple or None if not a GitHub URL.
Source code in src/appimage_updater/pattern_generator.py
| def parse_github_url(url: str) -> tuple[str, str] | None:
"""Parse GitHub URL and extract owner/repo information.
Returns (owner, repo) tuple or None if not a GitHub URL.
"""
try:
parsed = urllib.parse.urlparse(url)
if parsed.netloc.lower() not in ("github.com", "www.github.com"):
logger.debug(f"URL {url} is not a GitHub repository URL (netloc: {parsed.netloc})")
return None
path_parts = parsed.path.strip("/").split("/")
if len(path_parts) >= 2:
return (path_parts[0], path_parts[1])
logger.debug(f"URL {url} does not have enough path components for owner/repo")
except Exception as e:
logger.debug(f"Failed to parse URL {url}: {e}")
return None
|
should_enable_prerelease(url)
async
Check if prerelease should be automatically enabled for a repository.
Returns True if the repository only has prerelease versions (like continuous builds)
and no stable releases, indicating that prerelease support should be enabled.
Parameters:
Name |
Type |
Description |
Default |
url
|
str
|
|
required
|
Returns:
Name | Type |
Description |
bool |
bool
|
True if only prereleases are found, False if stable releases exist or on error
|
Source code in src/appimage_updater/pattern_generator.py
| async def should_enable_prerelease(url: str) -> bool:
"""Check if prerelease should be automatically enabled for a repository.
Returns True if the repository only has prerelease versions (like continuous builds)
and no stable releases, indicating that prerelease support should be enabled.
Args:
url: Repository URL
Returns:
bool: True if only prereleases are found, False if stable releases exist or on error
"""
try:
releases = await _fetch_releases_for_prerelease_check(url)
if not releases:
return False
valid_releases = _filter_valid_releases(releases, url)
if not valid_releases:
return False
return _analyze_prerelease_status(valid_releases, url)
except Exception as e:
# Don't fail the add command if prerelease detection fails
logger.debug(f"Failed to check prerelease status for {url}: {e}")
return False
|