mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-07-23 11:20:53 -05:00
235 lines
6.2 KiB
Python
235 lines
6.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Remove UTF-8 BOMs from files under a directory.
|
|
|
|
By default this scans the current working directory recursively and skips
|
|
repository areas that should not be touched by ordinary maintenance scripts.
|
|
Only files whose first three bytes are the UTF-8 BOM are rewritten.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
|
|
UTF8_BOM = b"\xef\xbb\xbf"
|
|
|
|
DEFAULT_EXCLUDED_DIR_NAMES = {
|
|
".git",
|
|
".hg",
|
|
".svn",
|
|
".mypy_cache",
|
|
".pytest_cache",
|
|
"__pycache__",
|
|
"test",
|
|
}
|
|
|
|
DEFAULT_EXCLUDED_DIR_PREFIXES = {
|
|
"build",
|
|
}
|
|
|
|
DEFAULT_EXCLUDED_REL_DIRS = {
|
|
"examples/server/frontend",
|
|
"ggml",
|
|
"models",
|
|
"src/vocab",
|
|
"thirdparty",
|
|
}
|
|
|
|
|
|
def rel_posix(path: Path, root: Path) -> str:
|
|
try:
|
|
return path.relative_to(root).as_posix()
|
|
except ValueError:
|
|
return path.as_posix()
|
|
|
|
|
|
def should_skip_dir(
|
|
path: Path,
|
|
root: Path,
|
|
excluded_rel_dirs: set[str],
|
|
excluded_names: set[str],
|
|
excluded_prefixes: set[str],
|
|
) -> bool:
|
|
rel = rel_posix(path, root)
|
|
return (
|
|
path.name in excluded_names
|
|
or rel in excluded_rel_dirs
|
|
or any(path.name.startswith(prefix) for prefix in excluded_prefixes)
|
|
)
|
|
|
|
|
|
def iter_files(
|
|
root: Path,
|
|
recursive: bool,
|
|
excluded_rel_dirs: set[str],
|
|
excluded_names: set[str],
|
|
excluded_prefixes: set[str],
|
|
follow_symlinks: bool,
|
|
):
|
|
if recursive:
|
|
for dirpath, dirnames, filenames in os.walk(root, followlinks=follow_symlinks):
|
|
current_dir = Path(dirpath)
|
|
dirnames[:] = [
|
|
name
|
|
for name in dirnames
|
|
if not should_skip_dir(
|
|
current_dir / name,
|
|
root,
|
|
excluded_rel_dirs,
|
|
excluded_names,
|
|
excluded_prefixes,
|
|
)
|
|
]
|
|
for filename in filenames:
|
|
path = current_dir / filename
|
|
if path.is_symlink() and not follow_symlinks:
|
|
continue
|
|
yield path
|
|
else:
|
|
for path in root.iterdir():
|
|
if path.is_file() and (follow_symlinks or not path.is_symlink()):
|
|
yield path
|
|
|
|
|
|
def has_utf8_bom(path: Path) -> bool:
|
|
with path.open("rb") as f:
|
|
return f.read(len(UTF8_BOM)) == UTF8_BOM
|
|
|
|
|
|
def strip_utf8_bom(path: Path) -> None:
|
|
tmp_path = None
|
|
try:
|
|
with path.open("rb") as src:
|
|
if src.read(len(UTF8_BOM)) != UTF8_BOM:
|
|
return
|
|
|
|
fd, tmp_name = tempfile.mkstemp(
|
|
prefix=f".{path.name}.",
|
|
suffix=".tmp",
|
|
dir=str(path.parent),
|
|
)
|
|
tmp_path = Path(tmp_name)
|
|
with os.fdopen(fd, "wb") as dst:
|
|
shutil.copyfileobj(src, dst, length=1024 * 1024)
|
|
|
|
shutil.copystat(path, tmp_path, follow_symlinks=False)
|
|
os.replace(tmp_path, path)
|
|
tmp_path = None
|
|
finally:
|
|
if tmp_path is not None:
|
|
try:
|
|
tmp_path.unlink()
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
|
|
def parse_args() -> argparse.Namespace:
|
|
parser = argparse.ArgumentParser(
|
|
description="Scan files and convert UTF-8 BOM files to UTF-8 without BOM.",
|
|
)
|
|
parser.add_argument(
|
|
"root",
|
|
nargs="?",
|
|
default=".",
|
|
help="Directory to scan. Defaults to the current directory.",
|
|
)
|
|
parser.add_argument(
|
|
"-n",
|
|
"--dry-run",
|
|
action="store_true",
|
|
help="Only list files that would be converted.",
|
|
)
|
|
parser.add_argument(
|
|
"--no-recursive",
|
|
action="store_true",
|
|
help="Only scan files directly under root.",
|
|
)
|
|
parser.add_argument(
|
|
"--include-repo-excluded",
|
|
action="store_true",
|
|
help="Do not skip default repository excluded directories.",
|
|
)
|
|
parser.add_argument(
|
|
"--exclude-dir",
|
|
action="append",
|
|
default=[],
|
|
metavar="DIR",
|
|
help="Additional directory name or root-relative path to skip. Can be used multiple times.",
|
|
)
|
|
parser.add_argument(
|
|
"--follow-symlinks",
|
|
action="store_true",
|
|
help="Follow symlinked directories and files.",
|
|
)
|
|
parser.add_argument(
|
|
"-q",
|
|
"--quiet",
|
|
action="store_true",
|
|
help="Only print the final summary.",
|
|
)
|
|
return parser.parse_args()
|
|
|
|
|
|
def main() -> int:
|
|
args = parse_args()
|
|
root = Path(args.root).resolve()
|
|
|
|
if not root.is_dir():
|
|
print(f"error: not a directory: {root}", file=sys.stderr)
|
|
return 2
|
|
|
|
excluded_names = set()
|
|
excluded_rel_dirs = set()
|
|
excluded_prefixes = set()
|
|
if not args.include_repo_excluded:
|
|
excluded_names.update(DEFAULT_EXCLUDED_DIR_NAMES)
|
|
excluded_rel_dirs.update(DEFAULT_EXCLUDED_REL_DIRS)
|
|
excluded_prefixes.update(DEFAULT_EXCLUDED_DIR_PREFIXES)
|
|
|
|
for item in args.exclude_dir:
|
|
normalized = Path(item).as_posix().strip("/")
|
|
if "/" in normalized:
|
|
excluded_rel_dirs.add(normalized)
|
|
else:
|
|
excluded_names.add(normalized)
|
|
|
|
scanned = 0
|
|
converted = 0
|
|
errors = 0
|
|
|
|
for path in iter_files(
|
|
root=root,
|
|
recursive=not args.no_recursive,
|
|
excluded_rel_dirs=excluded_rel_dirs,
|
|
excluded_names=excluded_names,
|
|
excluded_prefixes=excluded_prefixes,
|
|
follow_symlinks=args.follow_symlinks,
|
|
):
|
|
scanned += 1
|
|
try:
|
|
if not has_utf8_bom(path):
|
|
continue
|
|
converted += 1
|
|
rel = rel_posix(path, root)
|
|
if args.dry_run:
|
|
if not args.quiet:
|
|
print(f"would convert: {rel}")
|
|
else:
|
|
strip_utf8_bom(path)
|
|
if not args.quiet:
|
|
print(f"converted: {rel}")
|
|
except OSError as exc:
|
|
errors += 1
|
|
print(f"error: {rel_posix(path, root)}: {exc}", file=sys.stderr)
|
|
|
|
action = "would convert" if args.dry_run else "converted"
|
|
print(f"scanned {scanned} file(s), {action} {converted}, errors {errors}")
|
|
return 1 if errors else 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|