python-pdffiller 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pdffiller/io/output.py ADDED
@@ -0,0 +1,335 @@
1
+ import os
2
+ import sys
3
+ import traceback
4
+
5
+ from colorama import Fore, Style
6
+
7
+ from pdffiller import const
8
+ from pdffiller.exceptions import CommandLineError
9
+ from pdffiller.io.colors import color_enabled, is_terminal
10
+ from pdffiller.typing import Dict, Optional, Union
11
+
12
+ LEVEL_QUIET = 80 # -q
13
+ LEVEL_ERROR = 70 # Errors
14
+ LEVEL_WARNING = 60 # Warnings
15
+ LEVEL_NOTICE = 50 # Important messages to attract user attention.
16
+ LEVEL_STATUS = 40 # Default - The main interesting messages.
17
+ LEVEL_VERBOSE = 30 # -V Detailed informational messages.
18
+ LEVEL_DEBUG = 20 # -VV Closely related to internal implementation details
19
+ LEVEL_TRACE = 10 # -VVV Fine-grained messages with very low-level implementation details
20
+
21
+
22
+ class Color: # pylint: disable=too-few-public-methods
23
+ """Wrapper around colorama colors that are undefined in importing"""
24
+
25
+ RED = Fore.RED # @UndefinedVariable
26
+ WHITE = Fore.WHITE # @UndefinedVariable
27
+ CYAN = Fore.CYAN # @UndefinedVariable
28
+ GREEN = Fore.GREEN # @UndefinedVariable
29
+ MAGENTA = Fore.MAGENTA # @UndefinedVariable
30
+ BLUE = Fore.BLUE # @UndefinedVariable
31
+ YELLOW = Fore.YELLOW # @UndefinedVariable
32
+ BLACK = Fore.BLACK # @UndefinedVariable
33
+
34
+ BRIGHT_RED = Style.BRIGHT + Fore.RED # @UndefinedVariable
35
+ BRIGHT_BLUE = Style.BRIGHT + Fore.BLUE # @UndefinedVariable
36
+ BRIGHT_YELLOW = Style.BRIGHT + Fore.YELLOW # @UndefinedVariable
37
+ BRIGHT_GREEN = Style.BRIGHT + Fore.GREEN # @UndefinedVariable
38
+ BRIGHT_CYAN = Style.BRIGHT + Fore.CYAN # @UndefinedVariable
39
+ BRIGHT_WHITE = Style.BRIGHT + Fore.WHITE # @UndefinedVariable
40
+ BRIGHT_MAGENTA = Style.BRIGHT + Fore.MAGENTA # @UndefinedVariable
41
+
42
+
43
+ if os.getenv(const.ENV_PDFFILLER_COLOR_DARK):
44
+ Color.WHITE = Fore.BLACK
45
+ Color.CYAN = Fore.BLUE
46
+ Color.YELLOW = Fore.MAGENTA
47
+ Color.BRIGHT_WHITE = Fore.BLACK
48
+ Color.BRIGHT_CYAN = Fore.BLUE
49
+ Color.BRIGHT_YELLOW = Fore.MAGENTA
50
+ Color.BRIGHT_GREEN = Fore.GREEN
51
+
52
+
53
+ class PdfFillerOutput:
54
+ """Manage all PdfFiller output messages"""
55
+
56
+ # Singleton
57
+ _output_level: int = LEVEL_STATUS
58
+ _output_file: Optional[str] = None
59
+
60
+ def __init__(self, scope: str = "") -> None:
61
+ self.stream = sys.stderr
62
+ self._scope = scope
63
+ if self._output_file:
64
+ self.stream = open( # pylint: disable=consider-using-with
65
+ self._output_file, "wt", encoding="utf-8"
66
+ )
67
+ self._color: bool = color_enabled(self.stream)
68
+
69
+ @classmethod
70
+ def define_log_level(cls, level_name: Optional[str] = None) -> None:
71
+ """
72
+ Translates the verbosity level entered by a PdfFiller command.
73
+ If it's `None` (-V), it will be defaulted to `verbose` level.
74
+
75
+ :param level_name: `str` or `None`, where `None` is the same as `verbose`.
76
+ """
77
+ try:
78
+ level = {
79
+ "quiet": LEVEL_QUIET, # -Vquiet 80
80
+ "error": LEVEL_ERROR, # -Verror 70
81
+ "warning": LEVEL_WARNING, # -Vwaring 60
82
+ "notice": LEVEL_NOTICE, # -Vnotice 50
83
+ "status": LEVEL_STATUS, # -Vstatus 40
84
+ "info": LEVEL_STATUS, # -Vstatus 40
85
+ None: LEVEL_VERBOSE, # -V 30
86
+ "verbose": LEVEL_VERBOSE, # -Vverbose 30
87
+ "debug": LEVEL_DEBUG, # -Vdebug 20
88
+ "V": LEVEL_DEBUG, # -VV 20
89
+ "trace": LEVEL_TRACE, # -Vtrace 10
90
+ "VV": LEVEL_TRACE, # -VVV 10
91
+ }[level_name]
92
+ except KeyError:
93
+ # pylint: disable=raise-missing-from
94
+ raise CommandLineError(f"Invalid argument '-V{level_name}'")
95
+
96
+ cls._output_level = level
97
+
98
+ @classmethod
99
+ def define_log_output(cls, file_path: Optional[str] = None) -> None:
100
+ """
101
+ Translates the verbosity level entered by a PdfFiller command.
102
+ If it's `None` (-V), it will be defaulted to `verbose` level.
103
+
104
+ :param file_path: Optional path to output log file.
105
+ """
106
+ cls._output_file = file_path
107
+
108
+ @classmethod
109
+ def level_allowed(cls, level: int) -> bool:
110
+ """
111
+ Determines if a level can print associated message or not.
112
+ """
113
+ return cls._output_level <= level
114
+
115
+ @classmethod
116
+ def output_level(cls) -> int:
117
+ """Retrieve the current output level.
118
+
119
+ Returns:
120
+ int: The current output level
121
+ """
122
+ return cls._output_level
123
+
124
+ @property
125
+ def color(self) -> bool:
126
+ """
127
+ Determines if ANSI color is enabled or not.
128
+ """
129
+ return self._color
130
+
131
+ @property
132
+ def scope(self) -> str:
133
+ """
134
+ Retrieves the current scope
135
+ """
136
+ return self._scope
137
+
138
+ @scope.setter
139
+ def scope(self, out_scope: str) -> None:
140
+ """
141
+ Defines the current scope.
142
+ """
143
+ self._scope = out_scope
144
+
145
+ @property
146
+ def is_terminal(self) -> bool:
147
+ """
148
+ Determines if a stream is interactive.
149
+ """
150
+ return is_terminal(self.stream)
151
+
152
+ def writeln(
153
+ self, data: str, fore: Optional[str] = None, back: Optional[str] = None
154
+ ) -> "PdfFillerOutput":
155
+ """Writes a line including newline sequence"""
156
+ return self.write(data, fore, back, newline=True)
157
+
158
+ def write(
159
+ self,
160
+ data: str,
161
+ fore: Optional[str] = None,
162
+ back: Optional[str] = None,
163
+ newline: bool = False,
164
+ ) -> "PdfFillerOutput":
165
+ """Writes a message."""
166
+ if self._output_level > LEVEL_NOTICE:
167
+ return self
168
+ if self._color and (fore or back):
169
+ data = f"{fore or ''}{back or ''}{data}{Style.RESET_ALL}"
170
+
171
+ if newline:
172
+ data = f"{data}\n"
173
+ self.stream.write(data)
174
+ self.stream.flush()
175
+ return self
176
+
177
+ def rewrite_line(self, line: str) -> None:
178
+ """Abbreviates a line and display it."""
179
+ tmp_color = self._color
180
+ self._color = False
181
+ total_size = 70
182
+ limit_size = total_size // 2 - 3
183
+ if len(line) > total_size:
184
+ line = line[0:limit_size] + " ... " + line[-limit_size:]
185
+ self.write(f"\r{line}{' ' * (total_size - len(line))}")
186
+ self.stream.flush()
187
+ self._color = tmp_color
188
+
189
+ def _write_message(
190
+ self,
191
+ msg: Union[str, Dict[str, str]],
192
+ fore: Optional[str] = None,
193
+ back: Optional[str] = None,
194
+ ) -> None:
195
+ if isinstance(msg, dict):
196
+ # For traces we can receive a dict already, we try to transform then
197
+ # into more natural text
198
+ msg = ", ".join([f"{k}: {v}" for k, v in msg.items()])
199
+ msg = f"=> {msg}"
200
+
201
+ ret = ""
202
+ if self._scope:
203
+ if self._color:
204
+ ret = f"{fore or ''}{back or ''}{self.scope}:{Style.RESET_ALL} "
205
+ else:
206
+ ret = f"{self.scope}: "
207
+
208
+ if self._color:
209
+ ret += f"{fore or ''}{back or ''}{msg}{Style.RESET_ALL}"
210
+ else:
211
+ ret += f"{msg}"
212
+
213
+ self.stream.write(f"{ret}\n")
214
+ self.stream.flush()
215
+
216
+ def trace(self, msg: Union[str, Dict[str, str]]) -> "PdfFillerOutput":
217
+ """
218
+ Prints a trace message.
219
+ """
220
+ if self._output_level <= LEVEL_TRACE:
221
+ self._write_message(msg, fore=Color.BRIGHT_WHITE)
222
+ return self
223
+
224
+ def debug(self, msg: Union[str, Dict[str, str]]) -> "PdfFillerOutput":
225
+ """
226
+ Prints a debug message.
227
+ """
228
+ if self._output_level <= LEVEL_DEBUG:
229
+ self._write_message(msg)
230
+ return self
231
+
232
+ def verbose(
233
+ self,
234
+ msg: Union[str, Dict[str, str]],
235
+ fore: Optional[str] = None,
236
+ back: Optional[str] = None,
237
+ ) -> "PdfFillerOutput":
238
+ """
239
+ Prints a verbose message.
240
+ """
241
+ if self._output_level <= LEVEL_VERBOSE:
242
+ self._write_message(msg, fore=fore, back=back)
243
+ return self
244
+
245
+ def info(
246
+ self,
247
+ msg: Union[str, Dict[str, str]],
248
+ fore: Optional[str] = None,
249
+ back: Optional[str] = None,
250
+ ) -> "PdfFillerOutput":
251
+ """
252
+ Prints a status/informative message.
253
+ """
254
+ if self._output_level <= LEVEL_STATUS:
255
+ self._write_message(msg, fore=fore, back=back)
256
+ return self
257
+
258
+ def title(self, msg: Union[str, Dict[str, str]]) -> "PdfFillerOutput":
259
+ """
260
+ Prints a title.
261
+ """
262
+ if self._output_level <= LEVEL_NOTICE:
263
+ self._write_message(f"\n======== {msg} ========", fore=Color.BRIGHT_MAGENTA)
264
+ return self
265
+
266
+ def subtitle(self, msg: Union[str, Dict[str, str]]) -> "PdfFillerOutput":
267
+ """
268
+ Prints a subtitle.
269
+ """
270
+ if self._output_level <= LEVEL_NOTICE:
271
+ self._write_message(f"\n-------- {msg} --------", fore=Color.BRIGHT_MAGENTA)
272
+ return self
273
+
274
+ def highlight(self, msg: Union[str, Dict[str, str]]) -> "PdfFillerOutput":
275
+ """
276
+ Prints a highlighted message.
277
+ """
278
+ if self._output_level <= LEVEL_NOTICE:
279
+ self._write_message(msg, fore=Color.BRIGHT_MAGENTA)
280
+ return self
281
+
282
+ def success(self, msg: Union[str, Dict[str, str]]) -> "PdfFillerOutput":
283
+ """
284
+ Prints a success message.
285
+ """
286
+ if self._output_level <= LEVEL_NOTICE:
287
+ self._write_message(msg, fore=Color.BRIGHT_GREEN)
288
+ return self
289
+
290
+ def warning(self, msg: Union[str, Dict[str, str]]) -> "PdfFillerOutput":
291
+ """
292
+ Prints a warning message.
293
+ """
294
+ if self._output_level <= LEVEL_WARNING:
295
+ self._write_message(f"WARNING: {msg}", Color.YELLOW)
296
+ return self
297
+
298
+ def error(self, msg: Union[BaseException, str, Dict[str, str]]) -> "PdfFillerOutput":
299
+ """
300
+ Prints an error message.
301
+ """
302
+ if self._output_level <= LEVEL_ERROR:
303
+ if isinstance(msg, BaseException): # pragma: no cover
304
+ lines = traceback.format_exception(type(msg), value=msg, tb=msg.__traceback__)
305
+ exc_msg = ("\n".join(lines)).replace("\n", "\n ")
306
+ self._write_message(f"ERROR: {exc_msg}", Color.RED)
307
+ else:
308
+ self._write_message(f"ERROR: {msg}", Color.RED)
309
+ return self
310
+
311
+ def flush(self) -> None:
312
+ """
313
+ Flush associated stream.
314
+ """
315
+ self.stream.flush()
316
+
317
+
318
+ def cli_out_write(
319
+ data: Union[str, Dict[str, str]],
320
+ fore: Optional[str] = None,
321
+ back: Optional[str] = None,
322
+ endline: str = "\n",
323
+ indentation: int = 0,
324
+ ) -> None:
325
+ """
326
+ Output to be used by formatters to dump information to stdout
327
+ """
328
+ fore_ = fore or ""
329
+ back_ = back or ""
330
+ if (fore or back) and color_enabled(sys.stdout):
331
+ data = f"{' ' * indentation}{fore_}{back_}{data}{Style.RESET_ALL}{endline}"
332
+ else:
333
+ data = f"{' ' * indentation}{data}{endline}"
334
+
335
+ sys.stdout.write(data)