Skip to content

Commit 6142e93

Browse files
committed
Add option -o (or --output-format) to customize output format
1 parent e9d51fc commit 6142e93

File tree

5 files changed

+88
-64
lines changed

5 files changed

+88
-64
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
1717

1818
### Added
1919

20+
- Add option `-o` (or `--output-format`) to customize output format
2021
- Add official pre-commit hook
2122

2223
## Version 4.1.0 (2024-10-23)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Options:
6565
- `-W`, `--no-whitespace-eol`: do not check whitespace at end of lines inside strings
6666
- `-e`, `--extract`: display all translations and exit (all checks except compilation are disabled in this mode)
6767
- `-i`, `--ignore-errors`: display but ignore errors (always return 0)
68+
- `-o`, `--output-format`: output format
6869
- `-q`, `--quiet`: quiet mode: only display number of errors
6970
- `-v`, `--version`: display version and exit
7071

src/msgcheck/msgcheck.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
import shlex
4141
import sys
4242

43-
from msgcheck.po import PoCheck, PoReport
43+
from msgcheck.po import PoCheck, PoFileReport
4444

4545

4646
def msgcheck_parser() -> argparse.ArgumentParser:
@@ -138,6 +138,13 @@ def msgcheck_parser() -> argparse.ArgumentParser:
138138
action="store_true",
139139
help="display but ignore errors (always return 0)",
140140
)
141+
parser.add_argument(
142+
"-o",
143+
"--output-format",
144+
choices=["full", "oneline"],
145+
default="full",
146+
help="output format: full = complete output, oneline = one line output",
147+
)
141148
parser.add_argument(
142149
"-q",
143150
"--quiet",
@@ -167,7 +174,7 @@ def msgcheck_args(parser: argparse.ArgumentParser) -> argparse.Namespace:
167174
return parser.parse_args(shlex.split(os.getenv("MSGCHECK_OPTIONS") or "") + sys.argv[1:])
168175

169176

170-
def msgcheck_check_files(args: argparse.Namespace) -> list[tuple[str, list[PoReport]]]:
177+
def msgcheck_check_files(args: argparse.Namespace) -> list[PoFileReport]:
171178
"""Check files."""
172179
# create checker and set boolean options
173180
po_check = PoCheck()
@@ -195,30 +202,27 @@ def msgcheck_check_files(args: argparse.Namespace) -> list[tuple[str, list[PoRep
195202
return result
196203

197204

198-
def msgcheck_display_errors(
199-
args: argparse.Namespace,
200-
result: list[tuple[str, list[PoReport]]],
201-
) -> tuple[int, int, int]:
205+
def msgcheck_display_errors(args: argparse.Namespace, result: list[PoFileReport]) -> tuple[int, int, int]:
202206
"""Display error messages."""
203207
files_ok, files_with_errors, total_errors = 0, 0, 0
204-
for _, reports in result:
205-
if not reports:
208+
for report in result:
209+
if not report:
206210
files_ok += 1
207211
continue
208212
files_with_errors += 1
209-
total_errors += len(reports)
213+
total_errors += len(report)
210214
if not args.quiet:
211215
if args.only_misspelled:
212216
words = []
213-
for report in reports:
214-
words.extend(report.get_misspelled_words())
217+
for error in report:
218+
words.extend(error.get_misspelled_words())
215219
print("\n".join(sorted(set(words), key=lambda s: s.lower())))
216220
else:
217-
print("\n".join([str(report) for report in reports]))
221+
print("\n".join([error.to_string(fmt=args.output_format) for error in report]))
218222
return files_ok, files_with_errors, total_errors
219223

220224

221-
def msgcheck_display_result(args: argparse.Namespace, result: list[tuple[str, list[PoReport]]]) -> int:
225+
def msgcheck_display_result(args: argparse.Namespace, result: list[PoFileReport]) -> int:
222226
"""Display result and return the number of files with errors."""
223227
# display errors
224228
files_ok, files_with_errors, total_errors = msgcheck_display_errors(args, result)
@@ -229,19 +233,21 @@ def msgcheck_display_result(args: argparse.Namespace, result: list[tuple[str, li
229233
sys.exit(0)
230234

231235
# display files with number of errors
232-
if total_errors > 0 and not args.quiet:
233-
print("=" * 70)
234-
for filename, reports in result:
235-
errors = len(reports)
236-
if errors == 0:
237-
print(f"{filename}: OK")
238-
else:
239-
str_result = "almost good!" if errors <= 10 else "uh oh... try again!" # noqa: PLR2004
240-
print(f"{filename}: {errors} errors ({str_result})")
236+
if args.output_format == "full":
237+
if total_errors > 0 and not args.quiet:
238+
print("=" * 70)
239+
for report in result:
240+
errors = len(report)
241+
if errors == 0:
242+
print(f"{report.filename}: OK")
243+
else:
244+
str_result = "almost good!" if errors <= 10 else "uh oh... try again!" # noqa: PLR2004
245+
print(f"{report.filename}: {errors} errors ({str_result})")
241246

242247
# display total (if many files processed)
243-
str_errors = f", {files_with_errors} files with {total_errors} errors" if files_with_errors > 0 else ""
244-
print(f"TOTAL: {files_ok} files OK{str_errors}")
248+
if args.output_format == "full":
249+
str_errors = f", {files_with_errors} files with {total_errors} errors" if files_with_errors > 0 else ""
250+
print(f"TOTAL: {files_ok} files OK{str_errors}")
245251

246252
return files_with_errors
247253

src/msgcheck/po.py

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -72,18 +72,24 @@ def __init__( # noqa: PLR0913
7272
self.mstr = mstr
7373
self.fuzzy = fuzzy
7474

75-
def __repr__(self) -> str:
75+
def to_string(self, fmt: str = "full") -> str: # noqa: PLR0911
7676
"""Return PO report as string."""
7777
if self.idmsg == "extract":
7878
if isinstance(self.message, list):
7979
return ", ".join(self.message) + "\n---"
8080
return self.message + "\n---"
8181
if self.idmsg == "compile":
82+
if fmt == "oneline":
83+
if isinstance(self.message, list):
84+
return self.message[0]
85+
return self.message.split("\n")[0]
8286
return f"{'=' * 70}\n{self.message}"
8387
is_list = isinstance(self.message, list)
8488
count = f"({len(self.message)})" if is_list else ""
8589
str_fuzzy = "(fuzzy) " if self.fuzzy else ""
8690
str_msg = ", ".join(self.message) if is_list else self.message
91+
if fmt == "oneline":
92+
return f"{self.filename}:{self.line}: [{self.idmsg}{count}] {str_fuzzy}{str_msg}"
8793
msg = f"{'=' * 70}\n{self.filename}:{self.line}: [{self.idmsg}{count}] {str_fuzzy}{str_msg}"
8894
if self.mid:
8995
msg += "\n---\n" + self.mid
@@ -96,6 +102,14 @@ def get_misspelled_words(self) -> list[str]:
96102
return self.message if isinstance(self.message, list) else []
97103

98104

105+
class PoFileReport(list):
106+
"""A file report."""
107+
108+
def __init__(self, filename: str = "-") -> None:
109+
"""Initialize PO file report."""
110+
self.filename = filename
111+
112+
99113
class PoMessage:
100114
"""A message from a gettext file.
101115
@@ -615,40 +629,42 @@ def check_pofile(self, po_file: PoFile) -> list[PoReport]:
615629

616630
return reports
617631

618-
def check_file(self, filename: str) -> list[PoReport]:
619-
"""Check compilation and translations in a PO file.
620-
621-
Return a list of tuples: (filename, [PoReport, PoReport, ...]).
622-
"""
632+
def check_file(self, filename: str) -> PoFileReport :
633+
"""Check compilation and translations in a PO file."""
623634
po_file = PoFile(filename)
635+
report = PoFileReport(po_file.filename)
624636
# read the file
625637
try:
626638
po_file.read()
627639
except OSError as exc:
628-
return [PoReport(str(exc), "read", po_file.filename)]
640+
report.append(PoReport(str(exc), "read", po_file.filename))
641+
return report
629642
# compile the file (except if disabled)
630643
compile_rc = 0
631644
if self.checks["compile"]:
632645
compile_output, compile_rc = po_file.compile()
633646
if compile_rc != 0:
634647
# compilation failed
635-
return [PoReport(compile_output, "compile", po_file.filename)]
648+
report.append(PoReport(compile_output, "compile", po_file.filename))
649+
return report
636650
# compilation OK
637-
return self.check_pofile(po_file)
651+
report.extend(self.check_pofile(po_file))
652+
return report
638653

639-
def check_files(self, files: list[str]) -> list[tuple[str, list[PoReport]]]:
654+
def check_files(self, files: list[str]) -> list[PoFileReport]:
640655
"""Check compilation and translations in PO files.
641656
642657
Return a list of tuples: (filename, [PoReport, PoReport, ...]).
643658
"""
644-
result: list[tuple[str, list[PoReport]]] = []
659+
result: list[PoFileReport] = []
645660
for path in files:
646661
if Path(path).is_dir():
647662
for root, _, filenames in os.walk(path):
648-
for filename in filenames:
649-
if filename.endswith(".po"):
650-
path_po = Path(root) / filename
651-
result.append((str(path_po), self.check_file(str(path_po))))
663+
result.extend([
664+
self.check_file(str(Path(root) / filename))
665+
for filename in filenames
666+
if filename.endswith(".po")
667+
])
652668
else:
653-
result.append((path, self.check_file(path)))
669+
result.append(self.check_file(path))
654670
return result

tests/test_msgcheck.py

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -62,38 +62,38 @@ def test_extract() -> None:
6262
po_check.set_check("extract", True)
6363
result = po_check.check_files([local_path("fr.po")])
6464
assert len(result) == 1
65-
assert "fr.po" in result[0][0]
66-
assert len(result[0][1]) == 3
65+
assert "fr.po" in result[0].filename
66+
assert len(result[0]) == 3
6767

68-
report = result[0][1][0]
68+
report = result[0][0]
6969
assert report.message == "Ceci est un test.\n"
7070
assert report.idmsg == "extract"
7171
assert report.filename == "-"
7272
assert report.line == 0
7373
assert report.mid == ""
7474
assert report.mstr == ""
7575
assert report.fuzzy is False
76-
assert str(report) == "Ceci est un test.\n\n---"
76+
assert report.to_string() == "Ceci est un test.\n\n---"
7777

78-
report = result[0][1][1]
78+
report = result[0][1]
7979
assert report.message == "Test sur deux lignes.\nLigne 2."
8080
assert report.idmsg == "extract"
8181
assert report.filename == "-"
8282
assert report.line == 0
8383
assert report.mid == ""
8484
assert report.mstr == ""
8585
assert report.fuzzy is False
86-
assert str(report) == "Test sur deux lignes.\nLigne 2.\n---"
86+
assert report.to_string() == "Test sur deux lignes.\nLigne 2.\n---"
8787

88-
report = result[0][1][2]
88+
report = result[0][2]
8989
assert report.message == " erreur : %s" # noqa: RUF001
9090
assert report.idmsg == "extract"
9191
assert report.filename == "-"
9292
assert report.line == 0
9393
assert report.mid == ""
9494
assert report.mstr == ""
9595
assert report.fuzzy is False
96-
assert str(report) == " erreur : %s\n---" # noqa: RUF001
96+
assert report.to_string() == " erreur : %s\n---" # noqa: RUF001
9797

9898

9999
def test_checks() -> None:
@@ -105,24 +105,24 @@ def test_checks() -> None:
105105
assert len(result) == 2
106106

107107
# first file has no errors
108-
assert not result[0][1]
108+
assert not result[0]
109109

110110
# second file has 10 errors
111-
assert len(result[1][1]) == 9
111+
assert len(result[1]) == 9
112112

113113
# check first error
114-
report = result[1][1][0]
114+
report = result[1][0]
115115
assert report.message == "number of lines: 2 in string, 1 in translation"
116116
assert report.idmsg == "lines"
117117
assert "fr_errors.po" in report.filename
118118
assert report.line == 44
119119
assert report.mid == "Test 1 on two lines.\nLine 2."
120120
assert report.mstr == "Test 1 sur deux lignes."
121121
assert report.fuzzy is False
122-
assert "fr_errors.po:44: [lines] number of lines: 2 in string, 1 in translation" in str(report)
122+
assert "fr_errors.po:44: [lines] number of lines: 2 in string, 1 in translation" in report.to_string()
123123

124124
# check last error
125-
report = result[1][1][8]
125+
report = result[1][8]
126126
expected = "different whitespace at end of a line: 1 in string, 0 in translation"
127127
assert report.message == expected
128128
assert report.idmsg == "whitespace_eol"
@@ -133,8 +133,8 @@ def test_checks() -> None:
133133
assert report.fuzzy is False
134134

135135
# check number of errors by type
136-
errors = {}
137-
for report in result[1][1]:
136+
errors: dict[str, int] = {}
137+
for report in result[1]:
138138
errors[report.idmsg] = errors.get(report.idmsg, 0) + 1
139139
assert errors["lines"] == 2
140140
assert errors["punct"] == 1
@@ -152,7 +152,7 @@ def test_checks_fuzzy() -> None:
152152
assert len(result) == 1
153153

154154
# the file has 11 errors (with the fuzzy string)
155-
assert len(result[0][1]) == 10
155+
assert len(result[0]) == 10
156156

157157

158158
def test_checks_noqa() -> None:
@@ -165,7 +165,7 @@ def test_checks_noqa() -> None:
165165
assert len(result) == 1
166166

167167
# the file has 10 errors (including `noqa`-commented lines)
168-
assert len(result[0][1]) == 10
168+
assert len(result[0]) == 10
169169

170170

171171
def test_replace_fmt_c() -> None:
@@ -238,7 +238,7 @@ def test_spelling_id() -> None:
238238
assert len(result) == 1
239239

240240
# the file has 2 spelling errors: "Thsi" and "errro"
241-
report = result[0][1]
241+
report = result[0]
242242
assert len(report) == 3
243243
for i, word in enumerate(("Thsi", "testtwo", "errro")):
244244
assert report[i].idmsg == "spelling-id"
@@ -262,7 +262,7 @@ def test_spelling_id_multilpe_pwl() -> None:
262262
assert len(result) == 1
263263

264264
# the file has 2 spelling errors: "Thsi" and "errro"
265-
report = result[0][1]
265+
report = result[0]
266266
assert len(report) == 2
267267
for i, word in enumerate(("Thsi", "errro")):
268268
assert report[i].idmsg == "spelling-id"
@@ -283,7 +283,7 @@ def test_spelling_str() -> None:
283283
assert len(result) == 2
284284

285285
# first file has 3 spelling errors: "CecX", "aabbcc" and "xxyyzz"
286-
report = result[0][1]
286+
report = result[0]
287287
assert len(report) == 4
288288
for i, word in enumerate(("testtwo", "CecX", "aabbcc", "xxyyzz")):
289289
assert report[i].idmsg == "spelling-str"
@@ -293,7 +293,7 @@ def test_spelling_str() -> None:
293293
assert report[i].get_misspelled_words() == [word]
294294

295295
# second file has 1 error: dict/language "xyz" not found
296-
report = result[1][1]
296+
report = result[1]
297297
assert len(report) == 1
298298
assert report[0].idmsg == "dict"
299299

@@ -312,7 +312,7 @@ def test_spelling_str_multiple_pwl() -> None:
312312
assert len(result) == 2
313313

314314
# first file has 3 spelling errors: "CecX", "aabbcc" and "xxyyzz"
315-
report = result[0][1]
315+
report = result[0]
316316
assert len(report) == 3
317317
for i, word in enumerate(("CecX", "aabbcc", "xxyyzz")):
318318
assert report[i].idmsg == "spelling-str"
@@ -322,7 +322,7 @@ def test_spelling_str_multiple_pwl() -> None:
322322
assert report[i].get_misspelled_words() == [word]
323323

324324
# second file has 1 error: dict/language "xyz" not found
325-
report = result[1][1]
325+
report = result[1]
326326
assert len(report) == 1
327327
assert report[0].idmsg == "dict"
328328

@@ -416,4 +416,4 @@ def test_invalid_utf8() -> None:
416416
assert len(result) == 1
417417

418418
# the file has no errors
419-
assert len(result[0][1]) == 0
419+
assert len(result[0]) == 0

0 commit comments

Comments
 (0)