powrap/powrap/powrap.py

179 lines
6.1 KiB
Python

"""Fix style of uncommitted po files, or all if --all is given."""
import argparse
import sys
import os
from typing import Iterable
import difflib
from pathlib import Path
from subprocess import check_output, run, CalledProcessError
from tempfile import NamedTemporaryFile
from tqdm import tqdm
from powrap import __version__
def check_style(po_files: Iterable[str], no_wrap=False, quiet=False, diff=False) -> int:
"""Check style of given po_files.
Prints errors on stderr and returns the number of errors found.
"""
errors = 0
for po_path in tqdm(po_files, desc="Checking wrapping of po files", disable=quiet):
try:
with open(po_path, encoding="UTF-8") as po_file:
po_content = po_file.read()
except OSError as open_error:
tqdm.write(f"Error opening '{po_path}': {open_error}")
continue
delete = os.name == "posix" and sys.platform != "cygwin"
with NamedTemporaryFile("w+", delete=delete, encoding="utf-8") as tmpfile:
args = ["msgcat", "-", "-o", tmpfile.name]
if no_wrap:
args[1:1] = ["--no-wrap"]
try:
run(args, encoding="utf-8", check=True, input=po_content)
except CalledProcessError as run_error:
tqdm.write(f"Error processing '{po_path}': {run_error}")
continue
except FileNotFoundError as run_error:
tqdm.write("Error running " + " ".join(args) + f": {run_error}")
sys.exit(127)
new_po_content = tmpfile.read()
if po_content != new_po_content:
errors += 1
print("Would rewrap:", po_path, file=sys.stderr)
if diff:
for line in difflib.unified_diff(
po_content.splitlines(keepends=True),
new_po_content.splitlines(keepends=True),
):
print(line, end="", file=sys.stderr)
if not delete:
os.remove(tmpfile.name)
return errors
def fix_style(po_files, no_wrap=False, quiet=False):
"""Fix style of given po_files."""
for po_path in tqdm(po_files, desc="Fixing wrapping of po files", disable=quiet):
with open(po_path, encoding="UTF-8") as po_file:
po_content = po_file.read()
args = ["msgcat", "-", "-o", po_path]
if no_wrap:
args[1:1] = ["--no-wrap"]
try:
run(args, encoding="utf-8", check=True, input=po_content)
except CalledProcessError as run_error:
tqdm.write(f"Error processing '{po_path}': {run_error}")
except FileNotFoundError as run_error:
tqdm.write("Error running " + " ".join(args) + f": {run_error}")
sys.exit(127)
def parse_args():
"""Parse powrap command line arguments."""
def path(path_str):
path_obj = Path(path_str)
if not path_obj.exists():
raise argparse.ArgumentTypeError(f"File {path_str!r} does not exists.")
if not path_obj.is_file():
raise argparse.ArgumentTypeError(f"{path_str!r} is not a file.")
try:
path_obj.read_text(encoding="utf-8")
except PermissionError as read_error:
raise argparse.ArgumentTypeError(
"{path_str!r}: Permission denied."
) from read_error
return path_obj
parser = argparse.ArgumentParser(
prog="powrap",
description="Ensure po files are using the standard gettext format",
formatter_class=argparse.RawTextHelpFormatter,
epilog="""exit code:
0:nothing to do
1:would rewrap
127:error running msgcat""",
)
parser.add_argument(
"-m",
"--modified",
action="store_true",
help="Use git to find modified files instead of passing them as arguments.",
)
parser.add_argument(
"-C",
help="To use with --modified to tell where the git "
"repo is, in case it's not in the current working directory.",
type=Path,
dest="git_root",
default=Path.cwd(),
)
parser.add_argument(
"-q", "--quiet", action="store_true", help="Do not show progress bar."
)
parser.add_argument(
"-d",
"--diff",
action="store_true",
help="Don't write the files back, just output a diff for each file on stdout "
"(implies --check).",
)
parser.add_argument(
"--check",
action="store_true",
help="Don't write the files back, just return the status. "
"Return code 0 means nothing would change. "
"Return code 1 means some files would be reformatted.",
)
parser.add_argument(
"--version", action="version", version="%(prog)s " + __version__
)
parser.add_argument(
"--no-wrap",
action="store_true",
help="see `man msgcat`, useful to sed right after.",
)
parser.add_argument("po_files", nargs="*", help="po files.", type=path)
args = parser.parse_args()
if not args.po_files and not args.modified:
parser.print_help()
sys.exit(1)
if args.po_files and args.modified:
parser.print_help()
sys.exit(1)
return args
def main():
"""Powrap main entrypoint (parsing command line and all)."""
args = parse_args()
if args.git_root:
os.chdir(args.git_root)
if args.modified:
git_status = check_output(
["git", "status", "--porcelain", "--no-renames"],
encoding="utf-8",
)
git_status_lines = [
line.split(maxsplit=2) for line in git_status.split("\n") if line
]
args.po_files.extend(
Path(filename)
for status, filename in git_status_lines
if filename.endswith(".po") and status != "D"
)
if not args.po_files:
print("Nothing to do, exiting.")
sys.exit(0)
if args.check or args.diff:
errors = check_style(args.po_files, args.no_wrap, args.quiet, args.diff)
sys.exit(errors > 0)
else:
fix_style(args.po_files, args.no_wrap, args.quiet)