From b3006978b34e8f43cf35e91cb9c6c769bc6d27fe Mon Sep 17 00:00:00 2001 From: drg Date: Fri, 10 Apr 2026 16:24:58 +0200 Subject: [PATCH] Dateiendung angepasst --- README.md | 8 ++++- mdlink/cli.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++--- mdlink/utils.py | 6 ++-- 3 files changed, 102 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 041bfb2..f34e97d 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,18 @@ mdlink . ## Interactive Redirect Rewrite -When a Markdown link redirects, `mdlink` prompts: +Step 1: When a Markdown link redirects, `mdlink` prompts: ```text Replace old URL with final URL? [y/N] ``` +Step 2: For `http://` Markdown links without redirect, `mdlink` can test an `https://` variant and prompt: + +```text +Replace HTTP URL with HTTPS variant? [y/N] +``` + Only confirmed links are updated. ## Test File And Script diff --git a/mdlink/cli.py b/mdlink/cli.py index 42f404e..267aace 100644 --- a/mdlink/cli.py +++ b/mdlink/cli.py @@ -3,6 +3,7 @@ from __future__ import annotations import argparse from collections import defaultdict from pathlib import Path +from typing import Optional from rich.console import Console from rich.table import Table @@ -56,7 +57,51 @@ def _collect_redirects(records: list[LinkRecord], checks: dict[str, LinkCheckRes return redirects +def _is_http_url(url: str) -> bool: + return url.startswith("http://") + + +def _to_https(url: str) -> str: + return "https://" + url[len("http://") :] + + +def _cached_check( + checker: LinkChecker, + cache: dict[str, LinkCheckResult], + url: str, +) -> LinkCheckResult: + result = cache.get(url) + if result is None: + result = checker.check(url) + cache[url] = result + return result + + +def _collect_https_candidates( + records: list[LinkRecord], + checks: dict[str, LinkCheckResult], +) -> list[tuple[LinkRecord, str]]: + candidates: list[tuple[LinkRecord, str]] = [] + seen: set[tuple[Path, str]] = set() + for record in records: + if record.kind != "markdown": + continue + if not _is_http_url(record.url): + continue + original_check = checks.get(record.url) + if original_check and original_check.redirected: + continue + key = (record.file_path, record.url) + if key in seen: + continue + seen.add(key) + candidates.append((record, _to_https(record.url))) + return candidates + + def _handle_rewrites( + records: list[LinkRecord], + checks: dict[str, LinkCheckResult], redirects: list[tuple[LinkRecord, LinkCheckResult]], checker: LinkChecker, editor: ASTMarkdownEditor, @@ -64,6 +109,10 @@ def _handle_rewrites( ) -> None: replacements_by_file: dict[Path, dict[str, str]] = defaultdict(dict) seen_pairs: set[tuple[Path, str, str]] = set() + check_cache: dict[str, LinkCheckResult] = {} + + if redirects: + console.print("\n[bold]Redirect replacements[/bold]") for record, result in redirects: if record.kind != "markdown": @@ -82,7 +131,7 @@ def _handle_rewrites( if answer != "y": continue - verification = checker.check(final_url) + verification = _cached_check(checker=checker, cache=check_cache, url=final_url) if verification.status_code != 200: console.print( f"[red]Skip:[/red] final URL no longer valid ({verification.status_code or verification.error})" @@ -90,6 +139,39 @@ def _handle_rewrites( continue replacements_by_file[record.file_path][record.url] = final_url + https_candidates = _collect_https_candidates(records=records, checks=checks) + if https_candidates: + console.print("\n[bold]HTTPS upgrade candidates[/bold]") + + for record, https_url in https_candidates: + if replacements_by_file[record.file_path].get(record.url): + continue + https_check = _cached_check(checker=checker, cache=check_cache, url=https_url) + if https_check.status_code != 200: + continue + final_url: Optional[str] = https_check.final_url or https_url + if final_url == record.url: + continue + + pair = (record.file_path, record.url, final_url) + if pair in seen_pairs: + continue + seen_pairs.add(pair) + + console.print(f"\n[cyan]{record.file_path}:{record.line}[/cyan]") + console.print(f"[yellow]{record.url}[/yellow] -> [green]{final_url}[/green]") + answer = console.input("Replace HTTP URL with HTTPS variant? [y/N] ").strip().lower() + if answer != "y": + continue + + verification = _cached_check(checker=checker, cache=check_cache, url=final_url) + if verification.status_code != 200: + console.print( + f"[red]Skip:[/red] HTTPS URL no longer valid ({verification.status_code or verification.error})" + ) + continue + replacements_by_file[record.file_path][record.url] = final_url + for file_path, replacements in replacements_by_file.items(): content = file_path.read_text(encoding="utf-8") updated = editor.replace_links(content, replacements) @@ -118,9 +200,15 @@ def main() -> None: console.print("No non-200 links found.") redirects = _collect_redirects(records, checks) - if redirects: - editor = ASTMarkdownEditor() - _handle_rewrites(redirects=redirects, checker=checker, editor=editor, console=console) + editor = ASTMarkdownEditor() + _handle_rewrites( + records=records, + checks=checks, + redirects=redirects, + checker=checker, + editor=editor, + console=console, + ) if __name__ == "__main__": diff --git a/mdlink/utils.py b/mdlink/utils.py index 8c6b11d..f91d80e 100644 --- a/mdlink/utils.py +++ b/mdlink/utils.py @@ -5,7 +5,7 @@ from typing import Iterable, Iterator from urllib.parse import urlparse -MARKDOWN_EXTENSIONS = {".md"} +MARKDOWN_EXTENSIONS = {".md", ".markdown", ".mdown", ".mkd"} def iter_markdown_files(target: Path) -> Iterator[Path]: @@ -13,8 +13,8 @@ def iter_markdown_files(target: Path) -> Iterator[Path]: if target.suffix.lower() in MARKDOWN_EXTENSIONS: yield target return - for path in sorted(target.rglob("*.md")): - if path.is_file(): + for path in sorted(target.rglob("*")): + if path.is_file() and path.suffix.lower() in MARKDOWN_EXTENSIONS: yield path