|
11 | 11 | from concurrent.futures import ThreadPoolExecutor, as_completed |
12 | 12 |
|
13 | 13 | # Local imports |
14 | | -from .logging_utils import get_logger |
| 14 | +from .logging_utils import get_logger, set_root_level |
15 | 15 | from .ocr import ocr_pdf |
16 | 16 | from .errors import PipelineError |
17 | 17 | from .types import OcrResult |
|
27 | 27 | """ |
28 | 28 | logger = get_logger(__name__) |
29 | 29 |
|
| 30 | +LOG_LEVELS: dict[str, int] = { |
| 31 | + "DEBUG": logging.DEBUG, |
| 32 | + "INFO": logging.INFO, |
| 33 | + "WARNING": logging.WARNING, |
| 34 | + "ERROR": logging.ERROR, |
| 35 | +} |
30 | 36 |
|
31 | 37 | """ |
32 | 38 | Command-line interface entrypoint. |
@@ -76,20 +82,32 @@ def main() -> None: |
76 | 82 | default=False, |
77 | 83 | help="suppress informational output; only warnings and errors are shown", |
78 | 84 | ) |
| 85 | + parser.add_argument( |
| 86 | + "--log-level", |
| 87 | + choices=list(LOG_LEVELS.keys()), |
| 88 | + help="set root log level", |
| 89 | + ) |
79 | 90 | # ------------------------------------------------------------------ |
80 | 91 | # Apply logging level according to CLI flags |
81 | 92 | # ------------------------------------------------------------------ |
82 | 93 | args = parser.parse_args() |
83 | 94 |
|
84 | | - root_logger = logging.getLogger() |
| 95 | + # Determine flags for logging, guard against mocks in tests |
| 96 | + log_level = args.log_level if args.log_level in LOG_LEVELS else None |
| 97 | + verbose = args.verbose if isinstance(args.verbose, bool) else False |
| 98 | + quiet = args.quiet if isinstance(args.quiet, bool) else False |
85 | 99 |
|
86 | | - if args.verbose and getattr(args, "quiet", False): |
| 100 | + # Apply logging level according to CLI flags (--log-level supersedes verbose/quiet) |
| 101 | + if verbose and quiet: |
87 | 102 | parser.error("--verbose and --quiet are mutually exclusive") |
88 | | - |
89 | | - if getattr(args, "quiet", False): |
90 | | - root_logger.setLevel(logging.WARNING) |
91 | | - elif args.verbose: |
92 | | - root_logger.setLevel(logging.DEBUG) |
| 103 | + if log_level and (verbose or quiet): |
| 104 | + parser.error("--log-level cannot be used with --verbose/--quiet") |
| 105 | + if log_level: |
| 106 | + set_root_level(LOG_LEVELS[log_level]) |
| 107 | + elif quiet: |
| 108 | + set_root_level(logging.WARNING) |
| 109 | + elif verbose: |
| 110 | + set_root_level(logging.DEBUG) |
93 | 111 | logger.debug("Verbose flag enabled – root log‑level set to DEBUG") |
94 | 112 |
|
95 | 113 | try: |
@@ -130,9 +148,18 @@ def main() -> None: |
130 | 148 | for pdf_path in args.pdfs: |
131 | 149 | results.append(completed[pdf_path]) |
132 | 150 |
|
133 | | - print( |
134 | | - json.dumps(results, ensure_ascii=False, indent=2 if args.verbose else None) |
135 | | - ) |
| 151 | + # Emit JSON to stdout. |
| 152 | + # If the downstream pipe closes early (e.g. `| head`), writing to |
| 153 | + # stdout raises BrokenPipeError. Treat that as a normal termination |
| 154 | + # and exit silently. |
| 155 | + import contextlib |
| 156 | + |
| 157 | + with contextlib.suppress(BrokenPipeError): |
| 158 | + print( |
| 159 | + json.dumps( |
| 160 | + results, ensure_ascii=False, indent=2 if args.verbose else None |
| 161 | + ) |
| 162 | + ) |
136 | 163 |
|
137 | 164 | except PipelineError as exc: |
138 | 165 | logger.error(str(exc)) |
|
0 commit comments