potodo/potodo/potodo.py

485 lines
15 KiB
Python

import argparse
import json
import logging
import webbrowser
from pathlib import Path
from typing import Any
from typing import Dict
from typing import List
from typing import Sequence
from typing import Tuple
from gitignore_parser import parse_gitignore
from potodo import __version__
from potodo.arguments_handling import check_args
from potodo.github import get_issue_reservations
from potodo.interactive import _confirmation_menu
from potodo.interactive import _directory_list_menu
from potodo.interactive import _file_list_menu
from potodo.interactive import get_dir_list
from potodo.interactive import get_files_from_dir
from potodo.json import json_dateconv
from potodo.logging import setup_logging
from potodo.po_file import get_po_stats_from_repo_or_cache
from potodo.po_file import PoFileStats
def print_dir_stats(
directory_name: str,
buffer: Sequence[str],
folder_stats: Dict[str, int],
printed_list: Sequence[bool],
) -> None:
"""This function prints the directory name, its stats and the buffer"""
if True in printed_list:
logging.debug("Printing directory %s", directory_name)
# If at least one of the files isn't done then print the
# folder stats and file(s) Each time a file is went over True
# or False is placed in the printed_list list. If False is
# placed it means it doesnt need to be printed
folder_completion = 100 * folder_stats["translated"] / folder_stats["total"]
print(f"\n\n# {directory_name} ({folder_completion:.2f}% done)\n")
print("\n".join(buffer))
logging.debug("Not printing directory %s", directory_name)
def add_dir_stats(
directory_name: str,
buffer: List[Dict[str, str]],
folder_stats: Dict[str, int],
printed_list: Sequence[bool],
all_stats: List[Dict[str, Any]],
) -> None:
"""Appends directory name, its stats and the buffer to stats"""
if any(printed_list):
folder_completion = 100 * folder_stats["translated"] / folder_stats["total"]
all_stats.append(
dict(
name=f"{directory_name}/",
percent_translated=float(f"{folder_completion:.2f}"),
files=buffer,
)
)
def exec_potodo(
path: Path,
exclude: List[Path],
above: int,
below: int,
only_fuzzy: bool,
offline: bool,
hide_reserved: bool,
counts: bool,
json_format: bool,
exclude_fuzzy: bool,
exclude_reserved: bool,
only_reserved: bool,
show_reservation_dates: bool,
no_cache: bool,
is_interactive: bool,
matching_files: bool,
) -> None:
"""
Will run everything based on the given parameters
:param path: The path to search into
:param exclude: folders or files to be ignored
:param above: The above threshold
:param below: The below threshold
:param only_fuzzy: Should only fuzzies be printed
:param offline: Will not connect to internet
:param hide_reserved: Will not show the reserved files
:param counts: Render list with counts not percentage
:param json_format: Format output as JSON.
:param exclude_fuzzy: Will exclude files with fuzzies in output.
:param exclude_reserved: Will print out only files that aren't reserved
:param only_reserved: Will print only reserved files
:param show_reservation_dates: Will show the reservation dates
:param no_cache: Disables cache (Cache is disabled when files are modified)
:param is_interactive: Switches output to an interactive CLI menu
:param matching_files: Should the file paths be printed instead of normal output
"""
cache_args = {
"path": path,
"exclude": exclude,
"above": above,
"below": below,
"only_fuzzy": only_fuzzy,
"offline": offline,
"hide_reserved": hide_reserved,
"counts": counts,
"json_format": json_format,
"exclude_fuzzy": exclude_fuzzy,
"exclude_reserved": exclude_reserved,
"only_reserved": only_reserved,
"show_reservation_dates": show_reservation_dates,
"no_cache": no_cache,
"is_interactive": is_interactive,
}
try:
ignore_matches = parse_gitignore(".potodoignore", base_dir=path)
except FileNotFoundError:
ignore_matches = parse_gitignore("/dev/null")
# Initialize the arguments
issue_reservations = get_issue_reservations(offline, hide_reserved, path)
dir_stats: List[Any] = []
if is_interactive:
directory_options = get_dir_list(
repo_path=path, exclude=exclude, ignore_matches=ignore_matches
)
while True:
selected_dir = _directory_list_menu(directory_options)
if selected_dir == (len(directory_options) - 1):
exit(0)
directory = directory_options[selected_dir]
file_options = get_files_from_dir(
directory=directory, repo_path=path, exclude=exclude
)
# TODO: Add stats on files and also add reservations
selected_file = _file_list_menu(directory, file_options)
if selected_file == (len(file_options) + 1):
exit(0)
elif selected_file == len(file_options):
continue
file = file_options[selected_file]
final_choice = _confirmation_menu(file, directory)
if final_choice == 3:
exit(0)
elif final_choice == 2:
continue
else:
break
if final_choice == 0:
webbrowser.open(
f"https://github.com/python/python-docs-fr/issues/new?title=Je%20travaille%20sur%20"
f"{directory}/{file}"
f"&body=%0A%0A%0A---%0AThis+issue+was+created+using+potodo+interactive+mode."
)
else:
exit()
else:
po_files_and_dirs = get_po_stats_from_repo_or_cache(
path, exclude, cache_args, ignore_matches, no_cache
)
for directory_name, po_files in sorted(po_files_and_dirs.items()):
# For each directory and files in this directory
buffer: List[Any] = []
folder_stats: Dict[str, int] = {"translated": 0, "total": 0}
printed_list: List[bool] = []
for po_file in sorted(po_files):
# For each file in those files from that directory
if not only_fuzzy or po_file.fuzzy_entries:
if exclude_fuzzy and po_file.fuzzy_entries:
continue
buffer_add(
buffer,
folder_stats,
printed_list,
po_file,
issue_reservations,
above,
below,
counts,
json_format,
exclude_reserved,
only_reserved,
show_reservation_dates,
matching_files,
)
# Once all files have been processed, print the dir and the files
# or store them into a dict to print them once all directories have
# been processed.
if json_format:
add_dir_stats(
directory_name, buffer, folder_stats, printed_list, dir_stats
)
else:
print_dir_stats(directory_name, buffer, folder_stats, printed_list)
if json_format:
print(
json.dumps(
dir_stats,
indent=4,
separators=(",", ": "),
sort_keys=False,
default=json_dateconv,
)
)
def buffer_add(
buffer: List[Any],
folder_stats: Dict[str, int],
printed_list: List[bool],
po_file_stats: PoFileStats,
issue_reservations: Dict[str, Tuple[Any, Any]],
above: int,
below: int,
counts: bool,
json_format: bool,
exclude_reserved: bool,
only_reserved: bool,
show_reservation_dates: bool,
matching_files: bool,
) -> None:
"""Will add to the buffer the information to print about the file is
the file isn't translated entirely or above or below requested
values.
"""
# If the file is completely translated,
# or is translated below what's requested
# or is translated above what's requested
if (
po_file_stats.percent_translated == 100
or po_file_stats.percent_translated < above
or po_file_stats.percent_translated > below
):
# add the percentage of the file to the stats of the folder
folder_stats["translated"] += po_file_stats.translated_nb
folder_stats["total"] += po_file_stats.entries_count
if not json_format:
# don't print that file
printed_list.append(False)
# return without adding anything to the buffer
return
fuzzy_entries = po_file_stats.fuzzy_entries
untranslated_entries = po_file_stats.untranslated_entries
# nb of fuzzies in the file IF there are some fuzzies in the file
fuzzy_nb = po_file_stats.fuzzy_nb if fuzzy_entries else 0
# number of entries translated
translated_nb = po_file_stats.translated_nb
# file size
po_file_size = po_file_stats.po_file_size
# percentage of the file already translated
percent_translated = po_file_stats.percent_translated
# `reserved by` if the file is reserved
reserved_by, reservation_date = issue_reservations.get(
po_file_stats.filename_dir.lower(), (None, None)
)
# unless the offline/hide_reservation are enabled
if exclude_reserved and reserved_by:
return
if only_reserved and not reserved_by:
return
directory = po_file_stats.directory
filename = po_file_stats.filename
path = po_file_stats.path
if matching_files:
print(path)
return
elif json_format:
# the order of the keys is the display order
d = dict(
name=f"{directory}/{filename.replace('.po', '')}",
path=str(path),
entries=po_file_size,
fuzzies=fuzzy_nb,
translated=translated_nb,
percent_translated=percent_translated,
reserved_by=reserved_by,
reservation_date=reservation_date,
)
buffer.append(d)
else:
s = f"- {filename:<30} " # The filename
if counts:
missing = len(fuzzy_entries) + len(untranslated_entries)
s += f"{missing:3d} to do"
s += f", including {fuzzy_nb} fuzzies." if fuzzy_nb else ""
else:
s += f"{translated_nb:3d} / {po_file_size:3d} "
s += f"({percent_translated:5.1f}% translated)"
s += f", {fuzzy_nb} fuzzy" if fuzzy_nb else ""
if reserved_by is not None:
s += f", réservé par {reserved_by}"
if show_reservation_dates:
s += f" ({reservation_date})"
buffer.append(s)
# Add the percent translated to the folder statistics
folder_stats["translated"] += po_file_stats.translated_nb
folder_stats["total"] += po_file_stats.entries_count
# Indicate to print the file
printed_list.append(True)
def main() -> None:
parser = argparse.ArgumentParser(
prog="potodo",
description="List and prettify the po files left to translate.",
)
parser.add_argument(
"-p",
"--path",
help="execute Potodo in path",
metavar="path",
)
parser.add_argument(
"-e",
"--exclude",
nargs="+",
default=[],
help="exclude from search",
metavar="path",
)
parser.add_argument(
"-a",
"--above",
default=0,
metavar="X",
type=int,
help="list all TODOs above given X%% completion",
)
parser.add_argument(
"-b",
"--below",
default=100,
metavar="X",
type=int,
help="list all TODOs below given X%% completion",
)
parser.add_argument(
"-f",
"--only-fuzzy",
dest="only_fuzzy",
action="store_true",
help="print only files marked as fuzzys",
)
parser.add_argument(
"-o",
"--offline",
action="store_true",
help="don't perform any fetching to GitHub/online",
)
parser.add_argument(
"-n",
"--no-reserved",
dest="hide_reserved",
action="store_true",
help="don't print info about reserved files",
)
parser.add_argument(
"-c",
"--counts",
action="store_true",
help="render list with the count of remaining entries "
"(translate or review) rather than percentage done",
)
parser.add_argument(
"-j",
"--json",
action="store_true",
dest="json_format",
help="format output as JSON",
)
parser.add_argument(
"--exclude-fuzzy",
action="store_true",
dest="exclude_fuzzy",
help="select only files without fuzzy entries",
)
parser.add_argument(
"--exclude-reserved",
action="store_true",
dest="exclude_reserved",
help="select only files that aren't reserved",
)
parser.add_argument(
"--only-reserved",
action="store_true",
dest="only_reserved",
help="select only only reserved files",
)
parser.add_argument(
"--show-reservation-dates",
action="store_true",
dest="show_reservation_dates",
help="show issue creation dates",
)
parser.add_argument(
"--no-cache",
action="store_true",
dest="no_cache",
help="Disables cache (Cache is disabled when files are modified)",
)
parser.add_argument(
"-i",
"--interactive",
action="store_true",
dest="is_interactive",
help="Activates the interactive menu",
)
parser.add_argument(
"-l",
"--matching-files",
action="store_true",
dest="matching_files",
help="Suppress normal output; instead print the name of each matching po file from which output would normally "
"have been printed.",
)
parser.add_argument(
"--version", action="version", version="%(prog)s " + __version__
)
parser.add_argument(
"-v", "--verbose", action="count", default=0, help="Increases output verbosity"
)
# Initialize args and check consistency
args = vars(parser.parse_args())
args.update(check_args(**args))
if args["logging_level"]:
setup_logging(args["logging_level"])
logging.info("Logging activated.")
logging.debug("Executing potodo with args %s", args)
# Removing useless args before running the process
del args["verbose"]
del args["logging_level"]
# Launch the processing itself
exec_potodo(**args)