11"""LanguageTool command line."""
22
33import argparse
4+ import importlib .resources
45import locale
6+ import logging
57import re
68import sys
9+ import traceback
710from importlib .metadata import PackageNotFoundError , version
11+ from logging .config import dictConfig
12+ from pathlib import Path
813from typing import Any , Optional , Set , Union
914
1015import toml
1520try :
1621 __version__ = version ("language_tool_python" )
1722except 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+
2240def 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+
189222def 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
0 commit comments