Compare commits

...

10 Commits

8 changed files with 407 additions and 638 deletions

View File

@ -1,20 +1,173 @@
import argparse
import logging
import os
import sys
from argparse import Namespace
from pathlib import Path
from potodo import __version__
def parse_args() -> argparse.Namespace:
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="gitignore-style patterns to 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(
"-u",
"--api-url",
help=(
"API URL to retrieve reservation tickets (https://api.github.com/repos/ORGANISATION/REPOSITORY/issues?state=open or https://git.afpy.org/api/v1/repos/ORGANISATION/REPOSITORY/issues?state=open&type=issues)"
),
)
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 = parser.parse_args()
check_args(args)
return args
def check_args(args: Namespace) -> None:
# If below is lower than above, raise an error
if args.below < args.above:
print("Potodo: 'below' value must be greater than 'above' value.")
exit(1)
print(
"Potodo: 'below' value must be greater than 'above' value.", file=sys.stderr
)
sys.exit(1)
if args.json_format and args.is_interactive:
print(
"Potodo: Json format and interactive modes cannot be activated at the same time."
"Potodo: Json format and interactive modes cannot be activated at the same time.",
file=sys.stderr,
)
exit(1)
sys.exit(1)
if args.is_interactive:
try:
@ -23,20 +176,24 @@ def check_args(args: Namespace) -> None:
import platform
print(
'Potodo: "{}" is not supported for interactive mode'.format(
platform.system()
)
f'Potodo: "{platform.system()}" is not supported for interactive mode',
file=sys.stderr,
)
sys.exit(1)
if args.exclude_fuzzy and args.only_fuzzy:
print("Potodo: Cannot pass --exclude-fuzzy and --only-fuzzy at the same time.")
exit(1)
print(
"Potodo: Cannot pass --exclude-fuzzy and --only-fuzzy at the same time.",
file=sys.stderr,
)
sys.exit(1)
if args.exclude_reserved and args.only_reserved:
print(
"Potodo: Cannot pass --exclude-reserved and --only-reserved at the same time."
"Potodo: Cannot pass --exclude-reserved and --only-reserved at the same time.",
file=sys.stderr,
)
exit(1)
sys.exit(1)
# If no path is specified, use current directory
if not args.path:

View File

@ -4,7 +4,7 @@ import os
import pickle
from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import Any, Callable, Dict, List, Optional, Sequence, Set, cast
from typing import Any, Callable, Dict, List, Optional, Sequence, cast
import polib
@ -134,22 +134,16 @@ class PoDirectoryStats:
class PoProjectStats:
"""Represents a hierarchy of `.po` files."""
"""Represents the root of the hierarchy of `.po` files."""
def __init__(
self, path: Path, filter_function: Optional[Callable[[str], bool]] = None
):
"""filter_function is a function to include/exclude po files
or directories, it should return True for the file to be
included.
"""
def __init__(self, path: Path):
self.path = path
if filter_function is None:
filter_function = self.allow_all
self.filter_function = filter_function
# self.files can be persisted on disk
# using `.write_cache()` and `.read_cache()
self.files: Dict[Path, PoFileStats] = {}
self.files: List[PoFileStats] = []
def filter(self, filter_func: Callable[[PoFileStats], bool]) -> None:
self.files = [po_file for po_file in self.files if filter_func(po_file)]
@property
def translated(self) -> int:
@ -166,41 +160,21 @@ class PoProjectStats:
"""Return % of completion of this project."""
return 100 * self.translated / self.entries
@staticmethod
def allow_all(path: str) -> bool:
"""Default filtering function: allow all files."""
return True
def rescan(self) -> None:
"""Scan disk to search for po files.
def find_all_files(self) -> List[Path]:
"""Get all the files matching `**/*.po`.
File can be filtered using `self.filter_function`, see __init__.
This is the only function that hit the disk.
"""
return [
file for file in self.path.rglob("*.po") if self.filter_function(str(file))
]
def files_by_directory(self) -> Dict[Path, Set[Path]]:
return {
name: set(files)
# We assume the output of rglob to be sorted,
# so each 'name' is unique within groupby
for name, files in itertools.groupby(
self.find_all_files(), key=lambda path: path.parent
)
}
def stats_for_file(self, path: Path) -> PoFileStats:
"""Get a PoFileStats for a given Path."""
if path not in self.files:
self.files[path] = PoFileStats(path)
return self.files[path]
for path in list(self.path.rglob("*.po")):
if path not in self.files:
self.files.append(PoFileStats(path))
def stats_by_directory(self) -> List[PoDirectoryStats]:
return [
PoDirectoryStats(
directory, [self.stats_for_file(po_file) for po_file in po_files]
PoDirectoryStats(directory, list(po_files))
for directory, po_files in itertools.groupby(
self.files, key=lambda po_file: po_file.path.parent
)
for directory, po_files in self.files_by_directory().items()
]
def read_cache(
@ -222,9 +196,9 @@ class PoProjectStats:
if data.get("version") != VERSION:
logging.info("Found old cache, ignored it.")
return
for path, stats in cast(Dict[Path, PoFileStats], data["data"]).items():
if os.path.getmtime(path.resolve()) == stats.mtime:
self.files[path] = stats
for po_file in cast(List[PoFileStats], data["data"]):
if os.path.getmtime(po_file.path.resolve()) == po_file.mtime:
self.files.append(po_file)
def write_cache(self, cache_path: Path = Path(".potodo/cache.pickle")) -> None:
"""Persists all PoFileStats to disk."""

View File

@ -1,13 +1,11 @@
import argparse
import json
import logging
from pathlib import Path
from typing import Any, Callable, List
from typing import Any, Callable, Dict, List
from gitignore_parser import rule_from_pattern
from potodo import __version__
from potodo.arguments_handling import check_args
from potodo.arguments_handling import parse_args
from potodo.forge_api import get_issue_reservations
from potodo.json import json_dateconv
from potodo.logging import setup_logging
@ -18,11 +16,10 @@ def scan_path(
path: Path,
no_cache: bool,
hide_reserved: bool,
ignore_matches: Callable[[str], bool],
api_url: str,
) -> PoProjectStats:
logging.debug("Finding po files in %s", path)
po_project = PoProjectStats(path, lambda file: not ignore_matches(file))
po_project = PoProjectStats(path)
cache_path = path.resolve() / ".potodo" / "cache.pickle"
if no_cache:
@ -30,12 +27,14 @@ def scan_path(
else:
po_project.read_cache(cache_path)
po_project.rescan()
if not no_cache:
po_project.write_cache(cache_path)
if api_url and not hide_reserved:
issue_reservations = get_issue_reservations(api_url)
for po_file_stats in po_project.files.values():
for po_file_stats in po_project.files:
reserved_by, reservation_date = issue_reservations.get(
po_file_stats.filename_dir.lower(), (None, None)
)
@ -46,100 +45,45 @@ def scan_path(
return po_project
def non_interactive_output(
path: Path,
hide_reserved: bool,
counts: bool,
json_format: bool,
select: Callable[[PoFileStats], bool],
show_reservation_dates: bool,
no_cache: bool,
is_interactive: bool,
matching_files: bool,
ignore_matches: Callable[[str], bool],
api_url: str,
) -> None:
po_project = scan_path(path, no_cache, hide_reserved, ignore_matches, api_url)
if matching_files:
print_matching_files(po_project, select)
elif json_format:
print_po_project_as_json(
po_project,
select,
)
else:
print_po_project(
po_project,
counts,
select,
show_reservation_dates,
)
def print_matching_files(
po_project: PoProjectStats,
select: Callable[[PoFileStats], bool],
) -> None:
def print_matching_files(po_project: PoProjectStats) -> None:
for directory in sorted(po_project.stats_by_directory()):
for po_file in sorted(directory.files):
if select(po_file):
print(po_file.path)
print(po_file.path)
def print_po_project(
po_project: PoProjectStats,
counts: bool,
select: Callable[[PoFileStats], bool],
show_reservation_dates: bool,
po_project: PoProjectStats, counts: bool, show_reservation_dates: bool
) -> None:
for directory in sorted(po_project.stats_by_directory()):
if any(select(po_file) for po_file in directory.files):
print(f"\n\n# {directory.path.name} ({directory.completion:.2f}% done)\n")
print(f"\n\n# {directory.path.name} ({directory.completion:.2f}% done)\n")
for po_file in sorted(directory.files):
if select(po_file):
line = f"- {po_file.filename:<30} "
if counts:
line += f"{po_file.missing:3d} to do"
else:
line += (
f"{po_file.translated_nb:3d} / {po_file.entries:3d}"
f" ({po_file.percent_translated:5.1f}% translated)"
)
if po_file.fuzzy_nb:
line += f", {po_file.fuzzy_nb} fuzzy"
if po_file.reserved_by is not None:
line += ", " + po_file.reservation_str(show_reservation_dates)
print(line + ".")
line = f"- {po_file.filename:<30} "
if counts:
line += f"{po_file.missing:3d} to do"
else:
line += f"{po_file.translated_nb:3d} / {po_file.entries:3d}"
line += f" ({po_file.percent_translated:5.1f}% translated)"
if po_file.fuzzy_nb:
line += f", {po_file.fuzzy_nb} fuzzy"
if po_file.reserved_by is not None:
line += ", " + po_file.reservation_str(show_reservation_dates)
print(line + ".")
if po_project.entries != 0:
print(f"\n\n# TOTAL ({po_project.completion:.2f}% done)\n")
def print_po_project_as_json(
po_project: PoProjectStats,
select: Callable[[PoFileStats], bool],
) -> None:
dir_stats: List[Any] = []
def print_po_project_as_json(po_project: PoProjectStats) -> None:
dir_stats: List[Dict[str, Any]] = []
for directory in sorted(po_project.stats_by_directory()):
buffer: List[Any] = []
for po_file in sorted(directory.files):
if not select(po_file):
continue
buffer.append(po_file.as_dict())
# 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 buffer:
folder_completion = 100 * directory.translated / directory.entries
dir_stats.append(
{
"name": f"{directory.path.name}/",
"percent_translated": float(f"{folder_completion:.2f}"),
"files": buffer,
}
)
dir_stats.append(
{
"name": f"{directory.path.name}/",
"percent_translated": directory.completion,
"files": [po_file.as_dict() for po_file in sorted(directory.files)],
}
)
print(
json.dumps(
dir_stats,
@ -166,198 +110,16 @@ def build_ignore_matcher(path: Path, exclude: List[str]) -> Callable[[str], bool
return lambda file_path: any(r.match(file_path) for r in rules)
def exec_potodo(
path: Path,
exclude: List[str],
hide_reserved: bool,
counts: bool,
json_format: bool,
select: Callable[[PoFileStats], bool],
show_reservation_dates: bool,
no_cache: bool,
is_interactive: bool,
matching_files: bool,
api_url: str,
) -> 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 hide_reserved: Will not show the reserved files
:param counts: Render list with counts not percentage
:param json_format: Format output as JSON.
: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
:param api_url: API URL for reservation tickets on Gitea or GitHub
"""
ignore_matches = build_ignore_matcher(path, exclude)
if is_interactive:
from potodo.interactive import interactive_output
interactive_output(path, ignore_matches)
else:
non_interactive_output(
path,
hide_reserved,
counts,
json_format,
select,
show_reservation_dates,
no_cache,
is_interactive,
matching_files,
ignore_matches,
api_url,
)
def main() -> None:
parser = argparse.ArgumentParser(
prog="potodo",
description="List and prettify the po files left to translate.",
)
args = parse_args()
parser.add_argument(
"-p",
"--path",
help="execute Potodo in path",
metavar="path",
)
if args.logging_level:
setup_logging(args.logging_level)
parser.add_argument(
"-e",
"--exclude",
nargs="+",
default=[],
help="gitignore-style patterns to exclude from search.",
metavar="path",
)
logging.info("Logging activated.")
logging.debug("Executing potodo with args %s", args)
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(
"-u",
"--api-url",
help=(
"API URL to retrieve reservation tickets (https://api.github.com/repos/ORGANISATION/REPOSITORY/issues?state=open or https://git.afpy.org/api/v1/repos/ORGANISATION/REPOSITORY/issues?state=open&type=issues)"
),
)
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 = parser.parse_args()
check_args(args)
ignore_matches = build_ignore_matcher(args.path, args.exclude)
def select(po_file: PoFileStats) -> bool:
"""Return True if the po_file should be displayed, False otherwise."""
@ -378,25 +140,22 @@ def main() -> None:
if args.only_reserved and not po_file.reserved_by:
return False
if ignore_matches(str(po_file.path)):
return False
return True
if args.logging_level:
setup_logging(args.logging_level)
if args.is_interactive:
from potodo.interactive import interactive_output
logging.info("Logging activated.")
logging.debug("Executing potodo with args %s", args)
interactive_output(args.path, ignore_matches)
return
# Launch the processing itself
exec_potodo(
args.path,
args.exclude,
args.hide_reserved,
args.counts,
args.json_format,
select,
args.show_reservation_dates,
args.no_cache,
args.is_interactive,
args.matching_files,
args.api_url,
)
po_project = scan_path(args.path, args.no_cache, args.hide_reserved, args.api_url)
po_project.filter(select)
if args.matching_files:
print_matching_files(po_project)
elif args.json_format:
print_po_project_as_json(po_project)
else:
print_po_project(po_project, args.counts, args.show_reservation_dates)

View File

@ -2,32 +2,24 @@ from pathlib import Path
import pytest
from potodo.potodo import main
@pytest.fixture
def repo_dir():
@pytest.fixture(name="repo_dir")
def _repo_dir():
return Path(__file__).resolve().parent / "fixtures" / "repository"
@pytest.fixture
def base_config(repo_dir):
def select(po_file) -> bool:
"""Return True if the po_file should be displayed, False otherwise."""
return not (
po_file.percent_translated == 100
or po_file.percent_translated < 0
or po_file.percent_translated > 100
def run_potodo(repo_dir, capsys, monkeypatch):
def run_it(argv):
monkeypatch.setattr(
"sys.argv", ["potodo", "--no-cache", "-p", str(repo_dir)] + argv
)
try:
main()
except SystemExit:
pass
return capsys.readouterr()
return {
"path": repo_dir,
"exclude": ["excluded/", "excluded.po"],
"hide_reserved": False,
"counts": False,
"json_format": False,
"select": select,
"show_reservation_dates": False,
"no_cache": True,
"is_interactive": False,
"matching_files": False,
"api_url": "",
}
return run_it

View File

@ -1,8 +1,13 @@
from potodo.potodo import exec_potodo
from pathlib import Path
from potodo.potodo import main
REPO_DIR = Path(__file__).resolve().parent / "fixtures" / "repository"
def test_no_exclude(capsys, base_config):
exec_potodo(**base_config)
def test_no_exclude(capsys, monkeypatch):
monkeypatch.setattr("sys.argv", ["potodo", "-p", str(REPO_DIR)])
main()
out, err = capsys.readouterr()
assert not err
assert "file1" in out
@ -10,9 +15,11 @@ def test_no_exclude(capsys, base_config):
assert "file3" in out
def test_exclude_file(capsys, base_config):
base_config["exclude"] = ["file*"]
exec_potodo(**base_config)
def test_exclude_file(capsys, monkeypatch):
monkeypatch.setattr(
"sys.argv", ["potodo", "-p", str(REPO_DIR), "--exclude", "file*"]
)
main()
out, err = capsys.readouterr()
assert not err
assert "file1" not in out
@ -21,9 +28,11 @@ def test_exclude_file(capsys, base_config):
assert "excluded" in out # The only one not being named file
def test_exclude_directory(capsys, base_config):
base_config["exclude"] = ["excluded/*"]
exec_potodo(**base_config)
def test_exclude_directory(capsys, monkeypatch):
monkeypatch.setattr(
"sys.argv", ["potodo", "-p", str(REPO_DIR), "--exclude", "excluded/*"]
)
main()
out, err = capsys.readouterr()
assert not err
assert "file1" in out
@ -33,9 +42,11 @@ def test_exclude_directory(capsys, base_config):
assert "excluded/" not in out
def test_exclude_single_file(capsys, base_config):
base_config["exclude"] = ["file2.po"]
exec_potodo(**base_config)
def test_exclude_single_file(capsys, monkeypatch):
monkeypatch.setattr(
"sys.argv", ["potodo", "-p", str(REPO_DIR), "--exclude", "file2.po"]
)
main()
out, err = capsys.readouterr()
assert not err
assert "file1" in out

View File

@ -1,11 +1,8 @@
import json
from potodo.potodo import exec_potodo
def test_txt_output(capsys, base_config):
exec_potodo(**base_config)
captured = capsys.readouterr()
def test_txt_output(run_potodo):
captured = run_potodo(["--exclude", "excluded/", "excluded.po"])
assert "file1.po" in captured.out
assert "file2.po" in captured.out
@ -16,10 +13,10 @@ def test_txt_output(capsys, base_config):
assert "excluded" not in captured.out
def test_output(capsys, base_config, repo_dir):
base_config["json_format"] = True
exec_potodo(**base_config)
output = json.loads(capsys.readouterr().out)
def test_output(run_potodo, repo_dir):
output = json.loads(
run_potodo(["--json", "--exclude", "excluded/", "excluded.po"]).out
)
expected_folder = {
"name": "folder/",

View File

@ -1,78 +1,41 @@
import sys
from subprocess import check_output, CalledProcessError
def test_potodo_help(run_potodo):
output = run_potodo(["--help"]).out
output_short = run_potodo(["-h"]).out
assert output == output_short
assert "-h, --help show this help message and exit" in output
class TestPotodoArgsErrors:
def test_potodo_help(self):
output = check_output([sys.executable, "-m", "potodo", "--help"]).decode(
"utf-8"
)
output_short = check_output([sys.executable, "-m", "potodo", "-h"]).decode(
"utf-8"
)
assert output == output_short
assert "-h, --help show this help message and exit" in output
# TODO: Find a better way of testing help output
def test_potodo_above_below_conflict(run_potodo):
output = run_potodo(["--above", "50", "--below", "40"]).err
output_short = run_potodo(["-a", "50", "-b", "40"]).err
assert (
output
== output_short
== "Potodo: 'below' value must be greater than 'above' value.\n"
)
def test_potodo_above_below_conflict(self):
try:
check_output(
[sys.executable, "-m", "potodo", "--above", "50", "--below", "40"]
).decode("utf-8")
except CalledProcessError as e:
output = e.output
try:
check_output(
[sys.executable, "-m", "potodo", "-a", "50", "-b", "40"]
).decode("utf-8")
except CalledProcessError as e:
output_short = e.output
assert output == output_short
assert output == b"Potodo: 'below' value must be greater than 'above' value.\n"
def test_potodo_json_interactive_conflict(self):
try:
check_output(
[sys.executable, "-m", "potodo", "--json", "--interactive"]
).decode("utf-8")
except CalledProcessError as e:
output = e.output
try:
check_output([sys.executable, "-m", "potodo", "-j", "-i"]).decode("utf-8")
except CalledProcessError as e:
output_short = e.output
assert output == output_short
assert (
output
== b"Potodo: Json format and interactive modes cannot be activated at the same time.\n"
)
def test_potodo_json_interactive_conflict(run_potodo):
output = run_potodo(["--json", "--interactive"]).err
output_short = run_potodo(["-j", "-i"]).err
assert (
output
== output_short
== "Potodo: Json format and interactive modes cannot be activated at the same time.\n"
)
def test_potodo_exclude_and_only_fuzzy_conflict(self):
try:
check_output(
[sys.executable, "-m", "potodo", "--exclude-fuzzy", "--only-fuzzy"]
).decode("utf-8")
except CalledProcessError as e:
output = e.output
assert (
output
== b"Potodo: Cannot pass --exclude-fuzzy and --only-fuzzy at the same time.\n"
)
def test_potodo_exclude_and_only_reserved_conflict(self):
try:
check_output(
[
sys.executable,
"-m",
"potodo",
"--exclude-reserved",
"--only-reserved",
]
).decode("utf-8")
except CalledProcessError as e:
output = e.output
assert (
output
== b"Potodo: Cannot pass --exclude-reserved and --only-reserved at the same time.\n"
)
def test_potodo_exclude_and_only_fuzzy_conflict(run_potodo):
output = run_potodo(["--exclude-fuzzy", "--only-fuzzy"]).err
assert (
output
== "Potodo: Cannot pass --exclude-fuzzy and --only-fuzzy at the same time.\n"
)
def test_potodo_exclude_and_only_reserved_conflict(run_potodo):
output = run_potodo(["--exclude-reserved", "--only-reserved"]).err
assert (
output
== "Potodo: Cannot pass --exclude-reserved and --only-reserved at the same time.\n"
)

View File

@ -1,187 +1,103 @@
import sys
from subprocess import check_output
from pathlib import Path
HERE = Path(__file__).parent.resolve()
FIXTURES = HERE / "fixtures"
def test_potodo_no_args(run_potodo):
output = run_potodo([]).out
assert "# excluded (50.00% done)" in output
assert "# folder (33.33% done)" in output
assert "- excluded.po 1 / 2 ( 50.0% translated)" in output
assert "- file3.po 0 / 1 ( 0.0% translated)" in output
assert "# repository (25.00% done)" in output
assert (
"- file1.po 1 / 3 ( 33.0% translated), 1 fuzzy"
in output
)
class TestPotodoCLI:
def test_potodo_no_args(self):
output = check_output(
[sys.executable, "-m", "potodo", "-p", str(FIXTURES)], encoding="UTF-8"
)
assert "# excluded (50.00% done)" in output
assert "# folder (33.33% done)" in output
assert (
"- excluded.po 1 / 2 ( 50.0% translated)" in output
)
assert (
"- file3.po 0 / 1 ( 0.0% translated)" in output
)
assert "# repository (25.00% done)" in output
assert (
"- file1.po 1 / 3 ( 33.0% translated), 1 fuzzy"
in output
)
def test_potodo_exclude(run_potodo):
output = run_potodo(["--exclude", "excluded/", "excluded.po"]).out
output_short = run_potodo(["-e", "excluded/", "excluded.po"]).out
assert output == output_short
assert "# excluded (50.00% done)" not in output
assert (
"- excluded.po 1 / 2 ( 50.0% translated)" not in output
)
assert "# repository (25.00% done)" in output
assert (
"- file1.po 1 / 3 ( 33.0% translated), 1 fuzzy"
in output
)
def test_potodo_exclude(self, base_config):
output = check_output(
[
sys.executable,
"-m",
"potodo",
"--exclude",
base_config["exclude"][0],
base_config["exclude"][1],
"-p",
str(FIXTURES),
],
encoding="UTF-8",
)
output_short = check_output(
[
sys.executable,
"-m",
"potodo",
"-e",
base_config["exclude"][0],
base_config["exclude"][1],
"-p",
str(FIXTURES),
],
encoding="UTF-8",
)
assert output == output_short
assert "# excluded (50.00% done)" not in output
assert (
"- excluded.po 1 / 2 ( 50.0% translated)"
not in output
)
assert "# repository (25.00% done)" in output
assert (
"- file1.po 1 / 3 ( 33.0% translated), 1 fuzzy"
in output
)
def test_potodo_above(self):
output = check_output(
[sys.executable, "-m", "potodo", "--above", "40", "-p", str(FIXTURES)],
encoding="UTF-8",
)
output_short = check_output(
[sys.executable, "-m", "potodo", "-a", "40"]
).decode("utf-8")
assert output == output_short
assert (
"- file1.po 1 / 3 ( 33.0% translated), 1 fuzzy"
not in output
)
assert (
"- excluded.po 1 / 2 ( 50.0% translated)" in output
)
def test_potodo_above(run_potodo):
output = run_potodo(["--above", "40"]).out
output_short = run_potodo(["-a", "40"]).out
assert output == output_short
assert (
"- file1.po 1 / 3 ( 33.0% translated), 1 fuzzy"
not in output
)
assert "- excluded.po 1 / 2 ( 50.0% translated)" in output
def test_potodo_below(self):
output = check_output(
[sys.executable, "-m", "potodo", "--below", "40", "-p", str(FIXTURES)],
encoding="UTF-8",
)
output_short = check_output(
[sys.executable, "-m", "potodo", "-b", "40", "-p", str(FIXTURES)],
encoding="UTF-8",
)
assert output == output_short
assert (
"- file1.po 1 / 3 ( 33.0% translated), 1 fuzzy"
in output
)
assert (
"- excluded.po 1 / 2 ( 50.0% translated)"
not in output
)
def test_potodo_below(run_potodo):
output = run_potodo(["--below", "40"]).out
output_short = run_potodo(["-b", "40"]).out
assert output == output_short
assert (
"- file1.po 1 / 3 ( 33.0% translated), 1 fuzzy"
in output
)
assert (
"- excluded.po 1 / 2 ( 50.0% translated)" not in output
)
def test_potodo_onlyfuzzy(self):
output = check_output(
[sys.executable, "-m", "potodo", "--only-fuzzy", "-p", str(FIXTURES)],
encoding="UTF-8",
)
output_short = check_output(
[sys.executable, "-m", "potodo", "-f", "-p", str(FIXTURES)],
encoding="UTF-8",
)
assert output == output_short
assert (
"- file1.po 1 / 3 ( 33.0% translated), 1 fuzzy"
in output
)
assert (
"- excluded.po 1 / 2 ( 50.0% translated)"
not in output
)
def test_potodo_counts(self):
output = check_output(
[sys.executable, "-m", "potodo", "--counts", "-p", str(FIXTURES)],
encoding="UTF-8",
)
output_short = check_output(
[sys.executable, "-m", "potodo", "-c", "-p", str(FIXTURES)],
encoding="UTF-8",
)
assert output == output_short
assert (
"- excluded.po 1 / 2 ( 50.0% translated)"
not in output
)
assert "- file4.po 1 to do" in output
assert "# repository (25.00% done)" in output
assert "- file1.po 2 to do, 1 fuzzy." in output
def test_potodo_onlyfuzzy(run_potodo):
output = run_potodo(["--only-fuzzy"]).out
output_short = run_potodo(["-f"]).out
assert output == output_short
assert (
"- file1.po 1 / 3 ( 33.0% translated), 1 fuzzy"
in output
)
assert (
"- excluded.po 1 / 2 ( 50.0% translated)" not in output
)
def test_potodo_exclude_fuzzy(self):
output = check_output(
[sys.executable, "-m", "potodo", "--exclude-fuzzy", "-p", str(FIXTURES)],
encoding="UTF-8",
)
assert (
"- excluded.po 1 / 2 ( 50.0% translated)" in output
)
assert "- file1.po 2 to do, 1 fuzzy." not in output
def test_potodo_matching_files_solo(self):
output = check_output(
[sys.executable, "-m", "potodo", "--matching-files", "-p", str(FIXTURES)],
encoding="UTF-8",
)
output_short = check_output(
[sys.executable, "-m", "potodo", "-l", "-p", str(FIXTURES)],
encoding="UTF-8",
)
assert output == output_short
assert "excluded/file4.po" in output
assert "folder/excluded.po" in output
assert "folder/file3.po" in output
assert "file1.po" in output
assert "file2.po" in output
def test_potodo_counts(run_potodo):
output = run_potodo(["--counts"]).out
output_short = run_potodo(["-c"]).out
assert output == output_short
assert (
"- excluded.po 1 / 2 ( 50.0% translated)" not in output
)
assert "- file4.po 1 to do" in output
assert "# repository (25.00% done)" in output
assert "- file1.po 2 to do, 1 fuzzy." in output
def test_potodo_matching_files_fuzzy(self):
output = check_output(
[
sys.executable,
"-m",
"potodo",
"--matching-files",
"--only-fuzzy",
"-p",
str(FIXTURES),
],
encoding="UTF-8",
)
output_short = check_output(
[sys.executable, "-m", "potodo", "-l", "-f"]
).decode("utf-8")
assert output == output_short
assert "file1.po" in output
# TODO: Test hide_reserved, offline options, only_reserved, exclude_reserved, show_reservation_dates
# TODO: Test verbose output levels
def test_potodo_exclude_fuzzy(run_potodo):
output = run_potodo(["--exclude-fuzzy"]).out
assert "- excluded.po 1 / 2 ( 50.0% translated)" in output
assert "- file1.po 2 to do, 1 fuzzy." not in output
def test_potodo_matching_files_solo(run_potodo):
output = run_potodo(["--matching-files"]).out
output_short = run_potodo(["-l"]).out
assert output == output_short
assert "excluded/file4.po" in output
assert "folder/excluded.po" in output
assert "folder/file3.po" in output
assert "file1.po" in output
assert "file2.po" in output
def test_potodo_matching_files_fuzzy(run_potodo):
output = run_potodo(["--matching-files", "--only-fuzzy"]).out
output_short = run_potodo(["-l", "-f"]).out
assert output == output_short
assert "file1.po" in output
# TODO: Test hide_reserved, offline options, only_reserved, exclude_reserved, show_reservation_dates
# TODO: Test verbose output levels