Files
wehub-resource-sync 8ef9db2513
Deploy to GitHub Pages / deploy (push) Has been cancelled
Deploy to GitHub Pages / build (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:56:49 +08:00

151 lines
4.4 KiB
Python

#!/usr/bin/env python3
"""
PPT Master - Source Converter Batch Helpers
Share explicit multi-file and directory expansion logic across source_to_md
backend converters.
Usage:
Imported by scripts/source_to_md/*_to_md.py
Examples:
run_path_batch(["docs"], {".docx"}, None, convert_one)
Dependencies:
None
"""
from __future__ import annotations
import sys
from collections.abc import Callable
from pathlib import Path
ConvertOne = Callable[[Path, Path], bool]
IsSupportedFile = Callable[[Path], bool]
def _print_status(message: str) -> None:
print(message, file=sys.stderr)
def expand_directory_inputs(
inputs: list[str],
is_supported_file: IsSupportedFile,
is_external_ref: Callable[[str], bool] | None = None,
) -> tuple[list[str], list[str], bool]:
"""Expand non-recursive directory inputs while preserving other items."""
expanded: list[str] = []
errors: list[str] = []
saw_directory = False
is_external = is_external_ref or (lambda _item: False)
for item in inputs:
if is_external(item):
expanded.append(item)
continue
path = Path(item)
if path.is_dir():
saw_directory = True
matches = sorted(
child for child in path.iterdir()
if child.is_file() and is_supported_file(child)
)
if matches:
expanded.extend(str(match) for match in matches)
else:
errors.append(f"{item}: no supported files found")
continue
expanded.append(item)
return expanded, errors, saw_directory
def _output_key(path: Path) -> Path:
return path.resolve(strict=False)
def unique_output_path(output_dir: Path, stem: str, used_outputs: set[Path]) -> Path:
"""Return an in-run unique Markdown path without consulting the filesystem."""
base = stem or "output"
candidate = output_dir / f"{base}.md"
suffix = 2
while _output_key(candidate) in used_outputs:
candidate = output_dir / f"{base}_{suffix}.md"
suffix += 1
used_outputs.add(_output_key(candidate))
return candidate
def _output_for(
source: Path,
output_arg: str | None,
batch_mode: bool,
used_outputs: set[Path],
) -> Path:
if output_arg and batch_mode:
return unique_output_path(Path(output_arg), source.stem, used_outputs)
if output_arg:
return Path(output_arg)
return source.with_suffix(".md")
def run_path_batch(
inputs: list[str],
supported_suffixes: set[str],
output_arg: str | None,
convert_one: ConvertOne,
) -> int:
"""Run one converter across explicit files and non-recursive directories."""
expanded, expansion_errors, saw_directory = expand_directory_inputs(
inputs,
lambda path: path.suffix.lower() in supported_suffixes,
)
sources = [Path(item) for item in expanded]
batch_mode = saw_directory or len(sources) > 1
if output_arg and batch_mode:
output_dir = Path(output_arg)
if output_dir.exists() and not output_dir.is_dir():
_print_status(f"[ERROR] Batch output path is not a directory: {output_arg}")
return 1
output_dir.mkdir(parents=True, exist_ok=True)
success_count = 0
failed: list[str] = []
skipped: list[str] = list(expansion_errors)
used_outputs: set[Path] = set()
for source in sources:
output = _output_for(source, output_arg, batch_mode, used_outputs)
if batch_mode:
_print_status(f"\n==> {source}")
try:
converted = convert_one(source, output)
sys.stdout.flush()
if converted:
success_count += 1
else:
failed.append(str(source))
except Exception as exc:
sys.stdout.flush()
failed.append(f"{source}: {exc}")
_print_status(f"[ERROR] {source}: {exc}")
if batch_mode:
sys.stdout.flush()
_print_status(f"\n[Done] Success: {success_count}/{len(sources)}, Failed: {len(failed)}")
if skipped:
_print_status("\n[Skipped directories]:")
for item in skipped:
_print_status(f" - {item}")
if failed:
_print_status("\n[Failed inputs]:")
for item in failed:
_print_status(f" - {item}")
if not sources:
return 1
return 0 if not failed and not skipped else 1