Files
watermarks-remover/service/scripts/clean_text.py
T
Guillaume Meyer (The Opinionated Man)andGitHub 723627716c chore: add Ruff linting and formatting with CI enforcement (#103)
Introduce Ruff (pinned at 0.16.3) as the project linter + formatter and
enforce it in CI:

- requirements-dev.txt: pin ruff==0.16.3 (exact pins, no drift)
- ruff.toml: line-length 100, target py312; rule set E/F/W/I/UP/B/SIM/RUF/PLW/S
  with deliberate ignores (E501 for content strings, S603 for safe_arg
  subprocess calls, S101 asserts in tests) and per-file test ignores
- Makefile: add lint / format / lint-fix targets
- .github/workflows/ci.yml: add lint job (ruff check + format --check)
- .gitignore: whitelist ruff.toml

Also fix every finding the new gate surfaced so CI is green:
- 109+ auto-fixes from ruff --fix (import sorting, simplifications,
  unused vars, re.I aliases, etc.)
- explicit check=False on all subprocess.run calls (PLW1510)
- harden sitemap XML parsing: reject DTD/entity declarations (S314)
- replace hardcoded /tmp paths in tests with tmp_path (S108)
- narrow/annotate intentional bare excepts (S110/S112), bind loop vars
  in closures (B023), raise ... from None (B904), strict= for zip (B905)
- ruff format applied across service/ and tests/

Verified: ruff check + ruff format --check pass; 287 tests pass, 1 skip.
2026-08-16 17:32:40 -07:00

97 lines
3.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""Strip invisible Unicode / normalize space homoglyphs (Layer A)."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from common import (
backup_path,
cleaned_path,
eprint,
read_text_input,
write_text_output,
)
from text_unicode import clean_text
def main() -> int:
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("path", nargs="?", default="-", help="Input text file, or - for stdin")
p.add_argument("-o", "--output", help="Output path (default: stdout or *.cleaned.*)")
p.add_argument("--nfkc", action="store_true", help="Apply Unicode NFKC after scrub")
p.add_argument(
"--aggressive-homoglyphs",
action="store_true",
help="Map Cyrillic/fullwidth Latin confusables to ASCII Latin",
)
p.add_argument(
"--no-normalize-spaces",
action="store_true",
help="Do not rewrite exotic spaces to U+0020",
)
p.add_argument(
"--strip-emoji-glue",
action="store_true",
help="Paranoid: strip all load-bearing invisibles too (emoji glue, script joiners, flag tags, same-script fillers/selectors, orthographic Cf)",
)
p.add_argument(
"--strip-bidi",
action="store_true",
help="Also strip legitimate RTL/LTR directional marks and isolates",
)
p.add_argument("--stats", action="store_true", help="Print stats JSON to stderr")
p.add_argument(
"--force-text",
action="store_true",
help="Clean even when the input looks like a binary container "
"(this rewrites the bytes and will corrupt the file)",
)
p.add_argument(
"--in-place",
action="store_true",
help="Overwrite input file (creates .bak backup)",
)
args = p.parse_args()
text = read_text_input(args.path, allow_binary=args.force_text)
cleaned, stats = clean_text(
text,
nfkc=args.nfkc,
aggressive_homoglyphs=args.aggressive_homoglyphs,
normalize_spaces=not args.no_normalize_spaces,
strip_emoji_glue=args.strip_emoji_glue,
strip_bidi=args.strip_bidi,
)
out = args.output
if args.in_place:
if args.path in (None, "-"):
eprint("--in-place requires a file path")
return 2
src = Path(args.path)
backup_path(src) # side effect: keep a .bak of the original before overwriting
out = str(src)
elif out is None and args.path not in (None, "-"):
out = str(cleaned_path(Path(args.path)))
write_text_output(cleaned, out)
if args.stats:
eprint(json.dumps(stats, indent=2, ensure_ascii=False))
else:
eprint(
f"removed={stats['removed_count']} replaced={stats['replaced_count']} "
f"len {stats['input_length']}->{stats['output_length']}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())