Skip to content

pattern_generator

appimage_updater.core.pattern_generator

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/core/pattern_generator.py
@deprecated("Use repository-specific pattern generation methods instead")
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/core/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

Legacy function name - now redirects to repository-agnostic version.

Source code in src/appimage_updater/core/pattern_generator.py
async def fetch_appimage_pattern_from_github(url: str) -> str | None:
    """Legacy function name - now redirects to repository-agnostic version."""
    return await generate_appimage_pattern_async("temp", url)

find_common_prefix(strings)

Find the longest common prefix among a list of strings.

Source code in src/appimage_updater/core/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

Repository-agnostic pattern generation for use in async contexts.

an accurate pattern. Works with GitHub, GitLab, and other repository types. Falls back to intelligent defaults if that fails.

Source code in src/appimage_updater/core/pattern_generator.py
async def generate_appimage_pattern_async(app_name: str, url: str) -> str | None:
    """Repository-agnostic pattern generation for use in async contexts.

    an accurate pattern. Works with GitHub, GitLab, and other repository types.
    Falls back to intelligent defaults if that fails.
    """
    try:
        # Create a minimal config for the repository service
        temp_config = ApplicationConfig(
            name=app_name,
            source_type="dynamic_download",
            url=url,
            download_dir=Path(tempfile.gettempdir()),
            pattern="",
            prerelease=False,
        )

        # Try to get pattern from centralized repository service
        pattern = await version_service.generate_pattern_from_repository(temp_config)
        if pattern:
            logger.debug(f"Generated pattern from repository releases: {pattern}")
            return pattern

        # Fallback to old method if centralized service fails
        logger.debug("Centralized service failed, falling back to legacy method")
        return await _legacy_fetch_pattern(url)

    except Exception as e:
        logger.debug(f"Error generating pattern from repository: {e}")
        return None

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/core/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 (ValueError, AttributeError) as e:
        logger.debug(f"Failed to normalize GitHub URL {url}: {e}")
        return url, False

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

Repository URL

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/core/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 (RepositoryError, ValueError, AttributeError) 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