Compare commits

...

10 Commits

8 changed files with 407 additions and 638 deletions

View File

@ -1,20 +1,173 @@
import argparse
import logging import logging
import os import os
import sys
from argparse import Namespace from argparse import Namespace
from pathlib import Path 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: def check_args(args: Namespace) -> None:
# If below is lower than above, raise an error # If below is lower than above, raise an error
if args.below < args.above: if args.below < args.above:
print("Potodo: 'below' value must be greater than 'above' value.") print(
exit(1) "Potodo: 'below' value must be greater than 'above' value.", file=sys.stderr
)
sys.exit(1)
if args.json_format and args.is_interactive: if args.json_format and args.is_interactive:
print( 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: if args.is_interactive:
try: try:
@ -23,20 +176,24 @@ def check_args(args: Namespace) -> None:
import platform import platform
print( print(
'Potodo: "{}" is not supported for interactive mode'.format( f'Potodo: "{platform.system()}" is not supported for interactive mode',
platform.system() file=sys.stderr,
)
) )
sys.exit(1)
if args.exclude_fuzzy and args.only_fuzzy: if args.exclude_fuzzy and args.only_fuzzy:
print("Potodo: Cannot pass --exclude-fuzzy and --only-fuzzy at the same time.") print(
exit(1) "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: if args.exclude_reserved and args.only_reserved:
print( 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 no path is specified, use current directory
if not args.path: if not args.path:

View File

@ -4,7 +4,7 @@ import os
import pickle import pickle
from pathlib import Path from pathlib import Path
from tempfile import NamedTemporaryFile 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 import polib
@ -134,22 +134,16 @@ class PoDirectoryStats:
class PoProjectStats: class PoProjectStats:
"""Represents a hierarchy of `.po` files.""" """Represents the root of the hierarchy of `.po` files."""
def __init__( def __init__(self, path: Path):
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.
"""
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 # self.files can be persisted on disk
# using `.write_cache()` and `.read_cache() # 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 @property
def translated(self) -> int: def translated(self) -> int:
@ -166,41 +160,21 @@ class PoProjectStats:
"""Return % of completion of this project.""" """Return % of completion of this project."""
return 100 * self.translated / self.entries return 100 * self.translated / self.entries
@staticmethod def rescan(self) -> None:
def allow_all(path: str) -> bool: """Scan disk to search for po files.
"""Default filtering function: allow all files."""
return True
def find_all_files(self) -> List[Path]: This is the only function that hit the disk.
"""Get all the files matching `**/*.po`.
File can be filtered using `self.filter_function`, see __init__.
""" """
return [ for path in list(self.path.rglob("*.po")):
file for file in self.path.rglob("*.po") if self.filter_function(str(file)) if path not in self.files:
] self.files.append(PoFileStats(path))
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]
def stats_by_directory(self) -> List[PoDirectoryStats]: def stats_by_directory(self) -> List[PoDirectoryStats]:
return [ return [
PoDirectoryStats( PoDirectoryStats(directory, list(po_files))
directory, [self.stats_for_file(po_file) for po_file in 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( def read_cache(
@ -222,9 +196,9 @@ class PoProjectStats:
if data.get("version") != VERSION: if data.get("version") != VERSION:
logging.info("Found old cache, ignored it.") logging.info("Found old cache, ignored it.")
return return
for path, stats in cast(Dict[Path, PoFileStats], data["data"]).items(): for po_file in cast(List[PoFileStats], data["data"]):
if os.path.getmtime(path.resolve()) == stats.mtime: if os.path.getmtime(po_file.path.resolve()) == po_file.mtime:
self.files[path] = stats self.files.append(po_file)
def write_cache(self, cache_path: Path = Path(".potodo/cache.pickle")) -> None: def write_cache(self, cache_path: Path = Path(".potodo/cache.pickle")) -> None:
"""Persists all PoFileStats to disk.""" """Persists all PoFileStats to disk."""

View File

@ -1,13 +1,11 @@
import argparse
import json import json
import logging import logging
from pathlib import Path 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 gitignore_parser import rule_from_pattern
from potodo import __version__ from potodo.arguments_handling import parse_args
from potodo.arguments_handling import check_args
from potodo.forge_api import get_issue_reservations from potodo.forge_api import get_issue_reservations
from potodo.json import json_dateconv from potodo.json import json_dateconv
from potodo.logging import setup_logging from potodo.logging import setup_logging
@ -18,11 +16,10 @@ def scan_path(
path: Path, path: Path,
no_cache: bool, no_cache: bool,
hide_reserved: bool, hide_reserved: bool,
ignore_matches: Callable[[str], bool],
api_url: str, api_url: str,
) -> PoProjectStats: ) -> PoProjectStats:
logging.debug("Finding po files in %s", path) 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" cache_path = path.resolve() / ".potodo" / "cache.pickle"
if no_cache: if no_cache:
@ -30,12 +27,14 @@ def scan_path(
else: else:
po_project.read_cache(cache_path) po_project.read_cache(cache_path)
po_project.rescan()
if not no_cache: if not no_cache:
po_project.write_cache(cache_path) po_project.write_cache(cache_path)
if api_url and not hide_reserved: if api_url and not hide_reserved:
issue_reservations = get_issue_reservations(api_url) 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( reserved_by, reservation_date = issue_reservations.get(
po_file_stats.filename_dir.lower(), (None, None) po_file_stats.filename_dir.lower(), (None, None)
) )
@ -46,100 +45,45 @@ def scan_path(
return po_project return po_project
def non_interactive_output( def print_matching_files(po_project: PoProjectStats) -> None:
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:
for directory in sorted(po_project.stats_by_directory()): for directory in sorted(po_project.stats_by_directory()):
for po_file in sorted(directory.files): for po_file in sorted(directory.files):
if select(po_file): print(po_file.path)
print(po_file.path)
def print_po_project( def print_po_project(
po_project: PoProjectStats, po_project: PoProjectStats, counts: bool, show_reservation_dates: bool
counts: bool,
select: Callable[[PoFileStats], bool],
show_reservation_dates: bool,
) -> None: ) -> None:
for directory in sorted(po_project.stats_by_directory()): 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): for po_file in sorted(directory.files):
if select(po_file): line = f"- {po_file.filename:<30} "
line = f"- {po_file.filename:<30} " if counts:
if counts: line += f"{po_file.missing:3d} to do"
line += f"{po_file.missing:3d} to do" else:
else: line += f"{po_file.translated_nb:3d} / {po_file.entries:3d}"
line += ( line += f" ({po_file.percent_translated:5.1f}% translated)"
f"{po_file.translated_nb:3d} / {po_file.entries:3d}" if po_file.fuzzy_nb:
f" ({po_file.percent_translated:5.1f}% translated)" line += f", {po_file.fuzzy_nb} fuzzy"
) if po_file.reserved_by is not None:
if po_file.fuzzy_nb: line += ", " + po_file.reservation_str(show_reservation_dates)
line += f", {po_file.fuzzy_nb} fuzzy" print(line + ".")
if po_file.reserved_by is not None:
line += ", " + po_file.reservation_str(show_reservation_dates)
print(line + ".")
if po_project.entries != 0: if po_project.entries != 0:
print(f"\n\n# TOTAL ({po_project.completion:.2f}% done)\n") print(f"\n\n# TOTAL ({po_project.completion:.2f}% done)\n")
def print_po_project_as_json( def print_po_project_as_json(po_project: PoProjectStats) -> None:
po_project: PoProjectStats, dir_stats: List[Dict[str, Any]] = []
select: Callable[[PoFileStats], bool],
) -> None:
dir_stats: List[Any] = []
for directory in sorted(po_project.stats_by_directory()): for directory in sorted(po_project.stats_by_directory()):
buffer: List[Any] = [] dir_stats.append(
for po_file in sorted(directory.files): {
if not select(po_file): "name": f"{directory.path.name}/",
continue "percent_translated": directory.completion,
buffer.append(po_file.as_dict()) "files": [po_file.as_dict() for po_file in sorted(directory.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 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,
}
)
print( print(
json.dumps( json.dumps(
dir_stats, 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) 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: def main() -> None:
parser = argparse.ArgumentParser( args = parse_args()
prog="potodo",
description="List and prettify the po files left to translate.",
)
parser.add_argument( if args.logging_level:
"-p", setup_logging(args.logging_level)
"--path",
help="execute Potodo in path",
metavar="path",
)
parser.add_argument( logging.info("Logging activated.")
"-e", logging.debug("Executing potodo with args %s", args)
"--exclude",
nargs="+",
default=[],
help="gitignore-style patterns to exclude from search.",
metavar="path",
)
parser.add_argument( ignore_matches = build_ignore_matcher(args.path, args.exclude)
"-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)
def select(po_file: PoFileStats) -> bool: def select(po_file: PoFileStats) -> bool:
"""Return True if the po_file should be displayed, False otherwise.""" """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: if args.only_reserved and not po_file.reserved_by:
return False return False
if ignore_matches(str(po_file.path)):
return False
return True return True
if args.logging_level: if args.is_interactive:
setup_logging(args.logging_level) from potodo.interactive import interactive_output
logging.info("Logging activated.") interactive_output(args.path, ignore_matches)
logging.debug("Executing potodo with args %s", args) return
# Launch the processing itself po_project = scan_path(args.path, args.no_cache, args.hide_reserved, args.api_url)
exec_potodo( po_project.filter(select)
args.path, if args.matching_files:
args.exclude, print_matching_files(po_project)
args.hide_reserved, elif args.json_format:
args.counts, print_po_project_as_json(po_project)
args.json_format, else:
select, print_po_project(po_project, args.counts, args.show_reservation_dates)
args.show_reservation_dates,
args.no_cache,
args.is_interactive,
args.matching_files,
args.api_url,
)

View File

@ -2,32 +2,24 @@ from pathlib import Path
import pytest 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" return Path(__file__).resolve().parent / "fixtures" / "repository"
@pytest.fixture @pytest.fixture
def base_config(repo_dir): def run_potodo(repo_dir, capsys, monkeypatch):
def select(po_file) -> bool: def run_it(argv):
"""Return True if the po_file should be displayed, False otherwise.""" monkeypatch.setattr(
return not ( "sys.argv", ["potodo", "--no-cache", "-p", str(repo_dir)] + argv
po_file.percent_translated == 100
or po_file.percent_translated < 0
or po_file.percent_translated > 100
) )
try:
main()
except SystemExit:
pass
return capsys.readouterr()
return { return run_it
"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": "",
}

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): def test_no_exclude(capsys, monkeypatch):
exec_potodo(**base_config) monkeypatch.setattr("sys.argv", ["potodo", "-p", str(REPO_DIR)])
main()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert not err assert not err
assert "file1" in out assert "file1" in out
@ -10,9 +15,11 @@ def test_no_exclude(capsys, base_config):
assert "file3" in out assert "file3" in out
def test_exclude_file(capsys, base_config): def test_exclude_file(capsys, monkeypatch):
base_config["exclude"] = ["file*"] monkeypatch.setattr(
exec_potodo(**base_config) "sys.argv", ["potodo", "-p", str(REPO_DIR), "--exclude", "file*"]
)
main()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert not err assert not err
assert "file1" not in out 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 assert "excluded" in out # The only one not being named file
def test_exclude_directory(capsys, base_config): def test_exclude_directory(capsys, monkeypatch):
base_config["exclude"] = ["excluded/*"] monkeypatch.setattr(
exec_potodo(**base_config) "sys.argv", ["potodo", "-p", str(REPO_DIR), "--exclude", "excluded/*"]
)
main()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert not err assert not err
assert "file1" in out assert "file1" in out
@ -33,9 +42,11 @@ def test_exclude_directory(capsys, base_config):
assert "excluded/" not in out assert "excluded/" not in out
def test_exclude_single_file(capsys, base_config): def test_exclude_single_file(capsys, monkeypatch):
base_config["exclude"] = ["file2.po"] monkeypatch.setattr(
exec_potodo(**base_config) "sys.argv", ["potodo", "-p", str(REPO_DIR), "--exclude", "file2.po"]
)
main()
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert not err assert not err
assert "file1" in out assert "file1" in out

View File

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

View File

@ -1,78 +1,41 @@
import sys def test_potodo_help(run_potodo):
from subprocess import check_output, CalledProcessError 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_above_below_conflict(run_potodo):
def test_potodo_help(self): output = run_potodo(["--above", "50", "--below", "40"]).err
output = check_output([sys.executable, "-m", "potodo", "--help"]).decode( output_short = run_potodo(["-a", "50", "-b", "40"]).err
"utf-8" assert (
) output
output_short = check_output([sys.executable, "-m", "potodo", "-h"]).decode( == output_short
"utf-8" == "Potodo: 'below' value must be greater than 'above' value.\n"
) )
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(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): def test_potodo_json_interactive_conflict(run_potodo):
try: output = run_potodo(["--json", "--interactive"]).err
check_output( output_short = run_potodo(["-j", "-i"]).err
[sys.executable, "-m", "potodo", "--json", "--interactive"] assert (
).decode("utf-8") output
except CalledProcessError as e: == output_short
output = e.output == "Potodo: Json format and interactive modes cannot be activated at the same time.\n"
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_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): def test_potodo_exclude_and_only_fuzzy_conflict(run_potodo):
try: output = run_potodo(["--exclude-fuzzy", "--only-fuzzy"]).err
check_output( assert (
[ output
sys.executable, == "Potodo: Cannot pass --exclude-fuzzy and --only-fuzzy at the same time.\n"
"-m", )
"potodo",
"--exclude-reserved",
"--only-reserved", def test_potodo_exclude_and_only_reserved_conflict(run_potodo):
] output = run_potodo(["--exclude-reserved", "--only-reserved"]).err
).decode("utf-8") assert (
except CalledProcessError as e: output
output = e.output == "Potodo: Cannot pass --exclude-reserved and --only-reserved at the same time.\n"
assert ( )
output
== b"Potodo: Cannot pass --exclude-reserved and --only-reserved at the same time.\n"
)

View File

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