Skip to content

Commit 36f7b34

Browse files
authored
Merge pull request jxmorris12#129 from mdevolde/logger
feat: added loggers and logs, added logging conf for CL, moving error messages in var before raise
2 parents bf30b52 + 5c78b19 commit 36f7b34

File tree

12 files changed

+258
-139
lines changed

12 files changed

+258
-139
lines changed

MANIFEST.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
include pyproject.toml
22
include README.md
33
include LICENSE
4-
recursive-include language_tool_python *.py
4+
recursive-include language_tool_python *.py *.toml
55
include language_tool_python/py.typed
66

77
prune .github/

language_tool_python/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
"exceptions",
1010
]
1111

12+
import logging
13+
1214
from . import exceptions, utils
1315
from .language_tag import LanguageTag
1416
from .match import Match
1517
from .server import LanguageTool, LanguageToolPublicAPI
18+
19+
logger = logging.getLogger(__name__)
20+
logger.addHandler(logging.NullHandler())

language_tool_python/__main__.py

Lines changed: 75 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
"""LanguageTool command line."""
22

33
import argparse
4+
import importlib.resources
45
import locale
6+
import logging
57
import re
68
import sys
9+
import traceback
710
from importlib.metadata import PackageNotFoundError, version
11+
from logging.config import dictConfig
12+
from pathlib import Path
813
from typing import Any, Optional, Set, Union
914

1015
import toml
@@ -15,10 +20,23 @@
1520
try:
1621
__version__ = version("language_tool_python")
1722
except PackageNotFoundError: # If the package is not installed in the environment, read the version from pyproject.toml
18-
with open("pyproject.toml", "rb") as f:
23+
project_root = Path(__file__).resolve().parent.parent
24+
pyproject = project_root / "pyproject.toml"
25+
with open(pyproject, "rb") as f:
1926
__version__ = toml.loads(f.read().decode("utf-8"))["project"]["version"]
2027

2128

29+
logger = logging.getLogger(__name__)
30+
with (
31+
importlib.resources.as_file(
32+
importlib.resources.files("language_tool_python").joinpath("logging.toml")
33+
) as config_path,
34+
open(config_path, "rb") as f,
35+
):
36+
log_config = toml.loads(f.read().decode("utf-8"))
37+
dictConfig(log_config)
38+
39+
2240
def parse_args() -> argparse.Namespace:
2341
"""
2442
Parse command line arguments.
@@ -101,6 +119,7 @@ def parse_args() -> argparse.Namespace:
101119
help="hostname of the remote LanguageTool server",
102120
)
103121
parser.add_argument("--remote-port", help="port of the remote LanguageTool server")
122+
parser.add_argument("--verbose", action="store_true", help="enable verbose output")
104123

105124
args = parser.parse_args()
106125

@@ -120,12 +139,11 @@ class RulesAction(argparse.Action):
120139
This action is used to modify the set of rules stored in the argparse
121140
namespace when the action is triggered. It updates the attribute specified
122141
by 'self.dest' with the provided values.
123-
124-
.. attribute:: dest
125-
:type: str
126-
The destination attribute to update.
127142
"""
128143

144+
dest: str
145+
"""The destination attribute to update."""
146+
129147
def __call__(
130148
self,
131149
parser: argparse.ArgumentParser,
@@ -186,6 +204,21 @@ def get_text(
186204
)
187205

188206

207+
def print_exception(exc: Exception, debug: bool) -> None:
208+
"""
209+
Print an exception message to stderr, optionally including a stack trace.
210+
211+
:param exc: The exception to print.
212+
:type exc: Exception
213+
:param debug: Whether to include a stack trace.
214+
:type debug: bool
215+
"""
216+
if debug:
217+
traceback.print_exc()
218+
else:
219+
print(exc, file=sys.stderr)
220+
221+
189222
def main() -> int:
190223
"""
191224
Main function to parse arguments, process files, and check text using LanguageTool.
@@ -195,6 +228,9 @@ def main() -> int:
195228
"""
196229
args = parse_args()
197230

231+
if args.verbose:
232+
logging.getLogger().setLevel(logging.DEBUG)
233+
198234
status = 0
199235

200236
for filename in args.files:
@@ -216,55 +252,53 @@ def main() -> int:
216252
remote_server = args.remote_host
217253
if args.remote_port is not None:
218254
remote_server += f":{args.remote_port}"
219-
lang_tool = LanguageTool(
255+
with LanguageTool(
220256
language=args.language,
221257
motherTongue=args.mother_tongue,
222258
remote_server=remote_server,
223-
)
224-
225-
try:
226-
text = get_text(filename, encoding, ignore=args.ignore_lines)
227-
except UnicodeError as exception:
228-
print(f"{filename}: {exception}", file=sys.stderr)
229-
continue
230-
231-
if not args.spell_check:
232-
lang_tool.disable_spellchecking()
259+
) as lang_tool:
260+
try:
261+
text = get_text(filename, encoding, ignore=args.ignore_lines)
262+
except (UnicodeError, FileNotFoundError) as exception:
263+
print_exception(exception, args.verbose)
264+
continue
233265

234-
lang_tool.disabled_rules.update(args.disable)
235-
lang_tool.enabled_rules.update(args.enable)
236-
lang_tool.enabled_rules_only = args.enabled_only
266+
if not args.spell_check:
267+
lang_tool.disable_spellchecking()
237268

238-
if args.picky:
239-
lang_tool.picky = True
269+
lang_tool.disabled_rules.update(args.disable)
270+
lang_tool.enabled_rules.update(args.enable)
271+
lang_tool.enabled_rules_only = args.enabled_only
240272

241-
try:
242-
if args.apply:
243-
print(lang_tool.correct(text))
244-
else:
245-
for match in lang_tool.check(text):
246-
rule_id = match.ruleId
273+
if args.picky:
274+
lang_tool.picky = True
247275

248-
replacement_text = ", ".join(
249-
f"'{word}'" for word in match.replacements
250-
).strip()
276+
try:
277+
if args.apply:
278+
print(lang_tool.correct(text))
279+
else:
280+
for match in lang_tool.check(text):
281+
rule_id = match.ruleId
251282

252-
message = match.message
283+
replacement_text = ", ".join(
284+
f"'{word}'" for word in match.replacements
285+
).strip()
253286

254-
# Messages that end with punctuation already include the
255-
# suggestion.
256-
if replacement_text and not message.endswith("?"):
257-
message += " Suggestions: " + replacement_text
287+
message = match.message
258288

259-
line, column = match.get_line_and_column(text)
289+
# Messages that end with punctuation already include the
290+
# suggestion.
291+
if replacement_text and not message.endswith("?"):
292+
message += " Suggestions: " + replacement_text
260293

261-
print(f"{filename}:{line}:{column}: {rule_id}: {message}")
294+
line, column = match.get_line_and_column(text)
262295

263-
status = 2
264-
except LanguageToolError as exception:
265-
print(f"{filename}: {exception}", file=sys.stderr)
266-
continue
296+
print(f"{filename}:{line}:{column}: {rule_id}: {message}")
267297

298+
status = 2
299+
except LanguageToolError as exception:
300+
print_exception(exception, args.verbose)
301+
continue
268302
return status
269303

270304

language_tool_python/config_file.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Module for configuring LanguageTool's local server."""
22

33
import atexit
4+
import logging
45
import os
56
import tempfile
67
from dataclasses import dataclass
@@ -9,6 +10,8 @@
910

1011
from .exceptions import PathError
1112

13+
logger = logging.getLogger(__name__)
14+
1215

1316
@dataclass(frozen=True)
1417
class OptionSpec:
@@ -94,9 +97,11 @@ def _path_validator(v: Any) -> None:
9497
"""
9598
p = Path(v)
9699
if not p.exists():
97-
raise PathError(f"path does not exist: {p}")
100+
err = f"path does not exist: {p}"
101+
raise PathError(err)
98102
if not p.is_file():
99-
raise PathError(f"path is not a file: {p}")
103+
err = f"path is not a file: {p}"
104+
raise PathError(err)
100105

101106

102107
CONFIG_SCHEMA: Dict[str, OptionSpec] = {
@@ -169,22 +174,27 @@ def _encode_config(config: Dict[str, Any]) -> Dict[str, str]:
169174
:raises TypeError: If a value's type does not match the expected type(s) defined
170175
in the CONFIG_SCHEMA specification.
171176
"""
177+
logger.debug("Encoding LanguageTool config with keys: %s", list(config.keys()))
172178
encoded: Dict[str, str] = {}
173179
for key, value in config.items():
174180
if _is_lang_key(key) and key.count("-") == 1: # lang-<code>
181+
logger.debug("Encoding language option %s=%r", key, value)
175182
encoded[key] = str(value)
176183
continue
177184
if _is_lang_key(key) and key.count("-") == 2: # lang-<code>-dictPath
185+
logger.debug("Encoding language dictPath %s=%r", key, value)
178186
_path_validator(value)
179187
encoded[key] = _path_encoder(value)
180188
continue
181189

182190
spec = CONFIG_SCHEMA.get(key)
183191
if spec is None:
184-
raise ValueError(f"unexpected key in config: {key}")
192+
err = f"unexpected key in config: {key}"
193+
raise ValueError(err)
185194

186195
if not isinstance(value, spec.py_types):
187-
raise TypeError(f"invalid type for {key}: {type(value).__name__}")
196+
err = f"invalid type for {key}: {type(value).__name__}"
197+
raise TypeError(err)
188198
if spec.validator is not None:
189199
spec.validator(value)
190200
encoded[key] = spec.encoder(value)
@@ -210,7 +220,8 @@ def __init__(self, config: Dict[str, Any]):
210220
Initialize the LanguageToolConfig object.
211221
"""
212222
if not config:
213-
raise ValueError("config cannot be empty")
223+
err = "config cannot be empty"
224+
raise ValueError(err)
214225

215226
self.config = _encode_config(config)
216227
self.path = self._create_temp_file()
@@ -232,6 +243,8 @@ def _create_temp_file(self) -> str:
232243
tmp_file.write(f"{key}={value}\n")
233244
temp_name = tmp_file.name
234245

246+
logger.debug("Created temporary LanguageTool config file at %s", temp_name)
247+
235248
# Remove file when program exits.
236249
atexit.register(lambda: os.unlink(temp_name))
237250

0 commit comments

Comments
 (0)