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/__init__.py +4 -0
- pdffiller/__main__.py +6 -0
- pdffiller/_version.py +6 -0
- pdffiller/cli/__init__.py +0 -0
- pdffiller/cli/args.py +28 -0
- pdffiller/cli/boolean_action.py +35 -0
- pdffiller/cli/cli.py +260 -0
- pdffiller/cli/command.py +291 -0
- pdffiller/cli/commands/__init__.py +0 -0
- pdffiller/cli/commands/dump_data_fields.py +75 -0
- pdffiller/cli/commands/fill_form.py +142 -0
- pdffiller/cli/exit_codes.py +10 -0
- pdffiller/cli/formatters.py +16 -0
- pdffiller/cli/once_argument.py +19 -0
- pdffiller/cli/smart_formatter.py +10 -0
- pdffiller/const.py +22 -0
- pdffiller/exceptions.py +59 -0
- pdffiller/io/__init__.py +0 -0
- pdffiller/io/colors.py +52 -0
- pdffiller/io/output.py +335 -0
- pdffiller/pdf.py +488 -0
- pdffiller/py.typed.py +0 -0
- pdffiller/typing.py +59 -0
- pdffiller/utils.py +36 -0
- pdffiller/widgets/__init__.py +0 -0
- pdffiller/widgets/base.py +107 -0
- pdffiller/widgets/checkbox.py +52 -0
- pdffiller/widgets/radio.py +37 -0
- pdffiller/widgets/text.py +82 -0
- python_pdffiller-1.0.0.dist-info/METADATA +138 -0
- python_pdffiller-1.0.0.dist-info/RECORD +36 -0
- python_pdffiller-1.0.0.dist-info/WHEEL +5 -0
- python_pdffiller-1.0.0.dist-info/entry_points.txt +2 -0
- python_pdffiller-1.0.0.dist-info/licenses/AUTHORS.rst +7 -0
- python_pdffiller-1.0.0.dist-info/licenses/COPYING +22 -0
- python_pdffiller-1.0.0.dist-info/top_level.txt +1 -0
pdffiller/__init__.py
ADDED
pdffiller/__main__.py
ADDED
pdffiller/_version.py
ADDED
|
File without changes
|
pdffiller/cli/args.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from pdffiller.cli.command import BaseCommand
|
|
4
|
+
from pdffiller.typing import Any, Optional, Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def add_global_arguments(
|
|
8
|
+
parser: Union[argparse.ArgumentParser, Any],
|
|
9
|
+
add_help: bool = True,
|
|
10
|
+
main_parser: Optional[argparse.ArgumentParser] = None,
|
|
11
|
+
) -> None:
|
|
12
|
+
"""Add global parsers command-line options."""
|
|
13
|
+
BaseCommand.init_log_file(parser)
|
|
14
|
+
BaseCommand.init_log_levels(parser)
|
|
15
|
+
|
|
16
|
+
if hasattr(parser, "_command"):
|
|
17
|
+
getattr(parser, "_command").init_formatters(parser)
|
|
18
|
+
elif main_parser and hasattr(main_parser, "_command"):
|
|
19
|
+
getattr(main_parser, "_command").init_formatters(parser)
|
|
20
|
+
if add_help:
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
"-h",
|
|
23
|
+
"--help",
|
|
24
|
+
dest="help",
|
|
25
|
+
action="store_true",
|
|
26
|
+
default=False,
|
|
27
|
+
help="show this help message and exit",
|
|
28
|
+
)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from pdffiller.typing import Any, List, Optional, Sequence, Union
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BooleanAction(argparse.Action):
|
|
7
|
+
"""Handle --no-xxx --xxx command-line options"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, option_strings: Sequence[str], dest: str, **kwargs: Any) -> None:
|
|
10
|
+
_option_strings: List[str] = []
|
|
11
|
+
for option_string in option_strings:
|
|
12
|
+
_option_strings.append(option_string)
|
|
13
|
+
|
|
14
|
+
if option_string.startswith("--"):
|
|
15
|
+
option_string = "--no-" + option_string[2:]
|
|
16
|
+
_option_strings.append(option_string)
|
|
17
|
+
|
|
18
|
+
if kwargs.get("help") is not None and kwargs.get("default") is not None:
|
|
19
|
+
kwargs["help"] += f" (default: {kwargs['default']})"
|
|
20
|
+
|
|
21
|
+
super().__init__(option_strings=_option_strings, dest=dest, nargs=0, **kwargs)
|
|
22
|
+
|
|
23
|
+
def __call__(
|
|
24
|
+
self,
|
|
25
|
+
parser: argparse.ArgumentParser,
|
|
26
|
+
namespace: argparse.Namespace,
|
|
27
|
+
values: Optional[Union[str, Sequence[str]]] = None,
|
|
28
|
+
option_string: Optional[str] = None,
|
|
29
|
+
) -> None:
|
|
30
|
+
if option_string and option_string in self.option_strings:
|
|
31
|
+
setattr(namespace, self.dest, not option_string.startswith("--no-"))
|
|
32
|
+
|
|
33
|
+
def format_usage(self) -> str:
|
|
34
|
+
"""Get usage message"""
|
|
35
|
+
return " | ".join(self.option_strings)
|
pdffiller/cli/cli.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import os
|
|
3
|
+
import pkgutil
|
|
4
|
+
import signal
|
|
5
|
+
import sys
|
|
6
|
+
import textwrap
|
|
7
|
+
import traceback
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
from difflib import get_close_matches
|
|
10
|
+
from inspect import getmembers
|
|
11
|
+
|
|
12
|
+
from pdffiller import __version__ as pdffiller_version
|
|
13
|
+
from pdffiller.cli.command import PdfFillerSubCommand
|
|
14
|
+
from pdffiller.cli.exit_codes import (
|
|
15
|
+
ERROR_COMMAND_NAME,
|
|
16
|
+
ERROR_ENCOUNTERED,
|
|
17
|
+
ERROR_GENERAL,
|
|
18
|
+
ERROR_SIGTERM,
|
|
19
|
+
ERROR_SUBCOMMAND_NAME,
|
|
20
|
+
ERROR_UNEXPECTED,
|
|
21
|
+
SUCCESS,
|
|
22
|
+
USER_CTRL_BREAK,
|
|
23
|
+
USER_CTRL_C,
|
|
24
|
+
)
|
|
25
|
+
from pdffiller.exceptions import (
|
|
26
|
+
AbortExecution,
|
|
27
|
+
CommandLineError,
|
|
28
|
+
InvalidCommandNameException,
|
|
29
|
+
InvalidSubCommandNameException,
|
|
30
|
+
PdfFillerException,
|
|
31
|
+
)
|
|
32
|
+
from pdffiller.io.colors import init_colorama
|
|
33
|
+
from pdffiller.io.output import cli_out_write, Color, LEVEL_TRACE, PdfFillerOutput
|
|
34
|
+
from pdffiller.typing import Any, Dict, ExitCode, List, Optional
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Cli:
|
|
38
|
+
"""A single command of the PdfFiller application, with all the first level commands.
|
|
39
|
+
Manages the parsing of parameters and delegates functionality to the appropriate
|
|
40
|
+
command. It can also show the help of the tool.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self) -> None:
|
|
44
|
+
self._groups: Dict[str, List[str]] = defaultdict(list)
|
|
45
|
+
self._commands: Dict[str, Any] = {}
|
|
46
|
+
init_colorama(sys.stderr)
|
|
47
|
+
|
|
48
|
+
def _add_commands(self) -> None:
|
|
49
|
+
pdffiller_commands_path = os.path.join(
|
|
50
|
+
os.path.dirname(os.path.abspath(__file__)), "commands"
|
|
51
|
+
)
|
|
52
|
+
for module in pkgutil.iter_modules([pdffiller_commands_path]):
|
|
53
|
+
module_name = module[1]
|
|
54
|
+
self._add_command(f"pdffiller.cli.commands.{module_name}", module_name)
|
|
55
|
+
|
|
56
|
+
def _add_command(
|
|
57
|
+
self, import_path: str, method_name: str, package: Optional[str] = None
|
|
58
|
+
) -> None:
|
|
59
|
+
try:
|
|
60
|
+
imported_module = importlib.import_module(import_path)
|
|
61
|
+
cmd_mapping = getattr(imported_module, "__MAPPING__", {})
|
|
62
|
+
cb_name = cmd_mapping.get(method_name, method_name)
|
|
63
|
+
command_wrapper = getattr(imported_module, cb_name)
|
|
64
|
+
if command_wrapper.doc:
|
|
65
|
+
name = f"{package}:{method_name}" if package else method_name
|
|
66
|
+
self._commands[name] = command_wrapper
|
|
67
|
+
self._groups[command_wrapper.group].append(name)
|
|
68
|
+
for name, value in getmembers(imported_module):
|
|
69
|
+
if isinstance(value, PdfFillerSubCommand):
|
|
70
|
+
if name.startswith(f"{cb_name}_"):
|
|
71
|
+
command_wrapper.add_subcommand(value)
|
|
72
|
+
elif name[0] != "_":
|
|
73
|
+
raise PdfFillerException(
|
|
74
|
+
"The name for the subcommand method should "
|
|
75
|
+
"begin with the main command name + '_'. "
|
|
76
|
+
f"i.e. {cb_name}_<subcommand_name>"
|
|
77
|
+
)
|
|
78
|
+
except AttributeError as exc:
|
|
79
|
+
raise PdfFillerException(
|
|
80
|
+
f"There is no {method_name} method defined in {import_path}"
|
|
81
|
+
) from exc
|
|
82
|
+
|
|
83
|
+
def _print_similar(self, command: str) -> None:
|
|
84
|
+
"""Looks for similar commands and prints them if found."""
|
|
85
|
+
output = PdfFillerOutput()
|
|
86
|
+
matches = get_close_matches(
|
|
87
|
+
word=command, possibilities=self._commands.keys(), n=5, cutoff=0.75
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
if len(matches) == 0:
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
if len(matches) > 1:
|
|
94
|
+
output.info("The most similar commands are")
|
|
95
|
+
else:
|
|
96
|
+
output.info("The most similar command is")
|
|
97
|
+
|
|
98
|
+
for match in matches:
|
|
99
|
+
output.info(f" {match}")
|
|
100
|
+
|
|
101
|
+
output.writeln("")
|
|
102
|
+
|
|
103
|
+
def _output_help_cli(self) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Prints a summary of all commands.
|
|
106
|
+
"""
|
|
107
|
+
max_len = max((len(c) for c in self._commands)) + 4
|
|
108
|
+
line_format = f"{{: <{max_len}}}"
|
|
109
|
+
|
|
110
|
+
nb_groups = len(self._groups.items())
|
|
111
|
+
for group_name, comm_names in sorted(self._groups.items()):
|
|
112
|
+
if nb_groups != 1 or group_name:
|
|
113
|
+
cli_out_write("\n" + group_name + " commands", Color.YELLOW)
|
|
114
|
+
else:
|
|
115
|
+
cli_out_write("\n")
|
|
116
|
+
for name in comm_names:
|
|
117
|
+
# future-proof way to ensure tabular formatting
|
|
118
|
+
cli_out_write(line_format.format(name), Color.GREEN, endline="")
|
|
119
|
+
|
|
120
|
+
# Help will be all the lines up to the first empty one
|
|
121
|
+
docstring_lines = self._commands[name].doc.split("\n")
|
|
122
|
+
start = False
|
|
123
|
+
data: List[str] = []
|
|
124
|
+
for line in docstring_lines:
|
|
125
|
+
line = line.strip()
|
|
126
|
+
if not line:
|
|
127
|
+
if start:
|
|
128
|
+
break
|
|
129
|
+
start = True
|
|
130
|
+
continue
|
|
131
|
+
data.append(line)
|
|
132
|
+
|
|
133
|
+
txt = textwrap.fill(" ".join(data), 80, subsequent_indent=" " * (max_len + 2))
|
|
134
|
+
cli_out_write(txt)
|
|
135
|
+
|
|
136
|
+
cli_out_write("")
|
|
137
|
+
cli_out_write('Type "pdffiller <command> -h" for help', Color.BLUE)
|
|
138
|
+
|
|
139
|
+
def run(self, *args: Any) -> ExitCode:
|
|
140
|
+
"""Entry point for executing commands, dispatcher to class
|
|
141
|
+
methods
|
|
142
|
+
"""
|
|
143
|
+
output = PdfFillerOutput()
|
|
144
|
+
self._add_commands()
|
|
145
|
+
try:
|
|
146
|
+
command_argument = args[0][0]
|
|
147
|
+
except IndexError: # No parameters
|
|
148
|
+
self._output_help_cli()
|
|
149
|
+
raise InvalidCommandNameException(None) # pylint: disable=raise-missing-from
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
command = self._commands[command_argument]
|
|
153
|
+
except KeyError: # No parameters
|
|
154
|
+
if command_argument in ["--version"]:
|
|
155
|
+
cli_out_write(
|
|
156
|
+
f"pdffiller {pdffiller_version}",
|
|
157
|
+
fore=Color.BRIGHT_GREEN,
|
|
158
|
+
)
|
|
159
|
+
raise AbortExecution(0) # pylint: disable=raise-missing-from
|
|
160
|
+
|
|
161
|
+
if command_argument in ["-h", "--help"]:
|
|
162
|
+
self._output_help_cli()
|
|
163
|
+
raise AbortExecution(0) # pylint: disable=raise-missing-from
|
|
164
|
+
|
|
165
|
+
output.info(
|
|
166
|
+
f"'{command_argument}' is not a pdffiller command. " "See 'pdffiller --help'."
|
|
167
|
+
)
|
|
168
|
+
output.info("")
|
|
169
|
+
self._print_similar(command_argument)
|
|
170
|
+
raise InvalidCommandNameException( # pylint: disable=raise-missing-from
|
|
171
|
+
command_argument
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
command.run(args[0][1:])
|
|
176
|
+
except Exception as exc:
|
|
177
|
+
if not isinstance(exc, AbortExecution) and PdfFillerOutput.level_allowed(LEVEL_TRACE):
|
|
178
|
+
output.trace("\n".join(traceback.format_exception(*sys.exc_info())))
|
|
179
|
+
raise exc
|
|
180
|
+
return 0
|
|
181
|
+
|
|
182
|
+
@staticmethod
|
|
183
|
+
def exception_exit_error(exception: Optional[BaseException]) -> ExitCode:
|
|
184
|
+
"""Convert exception into an exit code"""
|
|
185
|
+
output = PdfFillerOutput()
|
|
186
|
+
if exception is None:
|
|
187
|
+
return SUCCESS
|
|
188
|
+
if isinstance(exception, AbortExecution):
|
|
189
|
+
return SUCCESS if exception.exitcode == 0 else ERROR_ENCOUNTERED
|
|
190
|
+
if isinstance(exception, InvalidCommandNameException):
|
|
191
|
+
return ERROR_COMMAND_NAME
|
|
192
|
+
if isinstance(exception, InvalidSubCommandNameException):
|
|
193
|
+
return ERROR_SUBCOMMAND_NAME
|
|
194
|
+
if isinstance(exception, CommandLineError):
|
|
195
|
+
output.error(exception.message)
|
|
196
|
+
return ERROR_UNEXPECTED
|
|
197
|
+
if isinstance(exception, PdfFillerException):
|
|
198
|
+
output.error(exception.message)
|
|
199
|
+
return ERROR_GENERAL
|
|
200
|
+
if isinstance(exception, SystemExit):
|
|
201
|
+
if exception.code != 0:
|
|
202
|
+
output.error(f"Exiting with code: {exception.code}")
|
|
203
|
+
return ERROR_ENCOUNTERED
|
|
204
|
+
|
|
205
|
+
assert isinstance(exception, Exception)
|
|
206
|
+
output.error(traceback.format_exc())
|
|
207
|
+
try:
|
|
208
|
+
msg = str(exception)
|
|
209
|
+
except Exception: # pylint: disable=broad-except
|
|
210
|
+
msg = repr(exception)
|
|
211
|
+
output.error(msg)
|
|
212
|
+
return ERROR_UNEXPECTED
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def main(args: Optional[List[str]] = None) -> ExitCode:
|
|
216
|
+
"""main entry point of the PdfFiller application, using a Command to
|
|
217
|
+
parse parameters.
|
|
218
|
+
|
|
219
|
+
:parameters:
|
|
220
|
+
:param args: Optional command-line arguments, else `sys.argv` will
|
|
221
|
+
be used by default
|
|
222
|
+
|
|
223
|
+
:return: One of the following exit code:
|
|
224
|
+
|
|
225
|
+
* 0: Success (done)
|
|
226
|
+
* 1: General PdfFillerException error (done)
|
|
227
|
+
* 2: Ctrl+C
|
|
228
|
+
* 3: Ctrl+Break
|
|
229
|
+
* 4: SIGTERM
|
|
230
|
+
* 5: Invalid configuration (done)
|
|
231
|
+
* 6: Unexpected error
|
|
232
|
+
* 7: Invalid command name
|
|
233
|
+
* 8: Invalid sub-command name
|
|
234
|
+
"""
|
|
235
|
+
|
|
236
|
+
def ctrl_c_handler(_: Any, __: Any) -> None:
|
|
237
|
+
print("You pressed Ctrl+C!")
|
|
238
|
+
sys.exit(USER_CTRL_C)
|
|
239
|
+
|
|
240
|
+
def sigterm_handler(_: Any, __: Any) -> None:
|
|
241
|
+
print("Received SIGTERM!")
|
|
242
|
+
sys.exit(ERROR_SIGTERM)
|
|
243
|
+
|
|
244
|
+
def ctrl_break_handler(_: Any, __: Any) -> None:
|
|
245
|
+
print("You pressed Ctrl+Break!")
|
|
246
|
+
sys.exit(USER_CTRL_BREAK)
|
|
247
|
+
|
|
248
|
+
signal.signal(signal.SIGINT, ctrl_c_handler)
|
|
249
|
+
signal.signal(signal.SIGTERM, sigterm_handler)
|
|
250
|
+
|
|
251
|
+
if sys.platform == "win32":
|
|
252
|
+
signal.signal(signal.SIGBREAK, ctrl_break_handler)
|
|
253
|
+
|
|
254
|
+
cli = Cli()
|
|
255
|
+
error: ExitCode = SUCCESS
|
|
256
|
+
try:
|
|
257
|
+
cli.run(args if args is not None else sys.argv[1:])
|
|
258
|
+
except BaseException as exc: # pylint: disable=broad-except
|
|
259
|
+
error = cli.exception_exit_error(exc)
|
|
260
|
+
return error
|
pdffiller/cli/command.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
|
|
3
|
+
from pdffiller.cli.once_argument import OnceArgument
|
|
4
|
+
from pdffiller.cli.smart_formatter import SmartFormatter
|
|
5
|
+
from pdffiller.exceptions import (
|
|
6
|
+
AbortExecution,
|
|
7
|
+
InvalidSubCommandNameException,
|
|
8
|
+
PdfFillerException,
|
|
9
|
+
)
|
|
10
|
+
from pdffiller.io.output import PdfFillerOutput
|
|
11
|
+
from pdffiller.typing import (
|
|
12
|
+
Any,
|
|
13
|
+
Callable,
|
|
14
|
+
Dict,
|
|
15
|
+
List,
|
|
16
|
+
Optional,
|
|
17
|
+
Sequence,
|
|
18
|
+
SubParserType,
|
|
19
|
+
Union,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PdfFillerArgumentParser(argparse.ArgumentParser):
|
|
24
|
+
"""PdfFiller argument parser to support configuration file"""
|
|
25
|
+
|
|
26
|
+
_command: "PdfFillerCommand"
|
|
27
|
+
|
|
28
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
29
|
+
super().__init__(*args, **kwargs)
|
|
30
|
+
|
|
31
|
+
def parse_args( # type: ignore[override] # pylint: disable=arguments-differ
|
|
32
|
+
self, args: Optional[Sequence[str]] = None
|
|
33
|
+
) -> argparse.Namespace:
|
|
34
|
+
options = super().parse_args(args)
|
|
35
|
+
|
|
36
|
+
PdfFillerOutput.define_log_level(options.verbosity)
|
|
37
|
+
PdfFillerOutput.define_log_output(options.log_file)
|
|
38
|
+
output = PdfFillerOutput()
|
|
39
|
+
|
|
40
|
+
if "help" in options and options.help:
|
|
41
|
+
self.print_help(output.stream)
|
|
42
|
+
raise AbortExecution(0)
|
|
43
|
+
|
|
44
|
+
return options
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
CommandCallback = Callable[[PdfFillerArgumentParser, Any], Any]
|
|
48
|
+
"""Command callback function.
|
|
49
|
+
:param PdfFillerArgumentParser: Current parser object
|
|
50
|
+
:param Any: Command-line arguments as a list
|
|
51
|
+
:return: Depend on each command.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
SubCommandCallback = Callable[[PdfFillerArgumentParser, argparse.ArgumentParser, Any], Any]
|
|
55
|
+
"""Sub-command callback function.
|
|
56
|
+
:param PdfFillerArgumentParser: Current parser object
|
|
57
|
+
:param argparse.ArgumentParser: The command sub-parser object
|
|
58
|
+
:param Any: Command-line arguments as a list
|
|
59
|
+
:return: Depend on each command.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
FormatterCallback = Callable[[Any], None]
|
|
63
|
+
"""Formatter callback function.
|
|
64
|
+
:param Dict: Output format keyword with its callback function function
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class BaseCommand:
|
|
69
|
+
"""Base PdfFiller command"""
|
|
70
|
+
|
|
71
|
+
def __init__(
|
|
72
|
+
self,
|
|
73
|
+
name: str,
|
|
74
|
+
callback: Union[CommandCallback, SubCommandCallback],
|
|
75
|
+
formatters: Optional[Dict[str, FormatterCallback]] = None,
|
|
76
|
+
) -> None:
|
|
77
|
+
self.formatters: Dict[str, FormatterCallback] = {"text": lambda x: None}
|
|
78
|
+
self.callback = callback
|
|
79
|
+
self.callback_name: str = name
|
|
80
|
+
if formatters:
|
|
81
|
+
for kind, action in formatters.items():
|
|
82
|
+
if callable(action):
|
|
83
|
+
self.formatters[kind] = action
|
|
84
|
+
else:
|
|
85
|
+
raise PdfFillerException(
|
|
86
|
+
f"Invalid formatter for {kind}. The formatter must be" "a valid function"
|
|
87
|
+
)
|
|
88
|
+
if callback.__doc__:
|
|
89
|
+
self.callback_doc = callback.__doc__
|
|
90
|
+
else:
|
|
91
|
+
raise PdfFillerException(
|
|
92
|
+
f"No documentation string defined for command: '{self.callback_name}'."
|
|
93
|
+
" PdfFiller commands should provide a documentation string explaining "
|
|
94
|
+
"its use briefly."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def init_log_levels(parser: argparse.ArgumentParser) -> None:
|
|
99
|
+
"""Add verbosity command-line option"""
|
|
100
|
+
parser.add_argument(
|
|
101
|
+
"-V",
|
|
102
|
+
"--verbosity",
|
|
103
|
+
default="status",
|
|
104
|
+
metavar="LEVEL",
|
|
105
|
+
nargs="?",
|
|
106
|
+
type=str,
|
|
107
|
+
help="Level of detail of the output. Valid options from less verbose "
|
|
108
|
+
"to more verbose: -Vquiet, -Verror, -Vwarning, -Vnotice, -Vstatus, "
|
|
109
|
+
"-V or -Vverbose, -VV or -Vdebug, -VVV or -vtrace",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def init_log_file(parser: argparse.ArgumentParser) -> None:
|
|
114
|
+
"""Add output log file command-line option"""
|
|
115
|
+
parser.add_argument(
|
|
116
|
+
"-L",
|
|
117
|
+
"--log-file",
|
|
118
|
+
metavar="PATH",
|
|
119
|
+
type=str,
|
|
120
|
+
help="Send output to PATH instead of stderr.",
|
|
121
|
+
action=OnceArgument,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def _help_formatters(self) -> List[str]:
|
|
126
|
+
"""
|
|
127
|
+
Formatters that are shown as available in help, 'text' formatter
|
|
128
|
+
should not appear
|
|
129
|
+
"""
|
|
130
|
+
return [formatter for formatter in self.formatters if formatter != "text"]
|
|
131
|
+
|
|
132
|
+
def init_formatters(self, parser: argparse.ArgumentParser) -> None:
|
|
133
|
+
"""Add formatters command-line options."""
|
|
134
|
+
formatters = self._help_formatters
|
|
135
|
+
if formatters:
|
|
136
|
+
parser.add_argument(
|
|
137
|
+
"-f",
|
|
138
|
+
"--format",
|
|
139
|
+
metavar="NAME",
|
|
140
|
+
action=OnceArgument,
|
|
141
|
+
help=f"Select the output format: {', '.join(formatters)}",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def name(self) -> str:
|
|
146
|
+
"""Get action name"""
|
|
147
|
+
return self.callback_name
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def method(self) -> Union[CommandCallback, SubCommandCallback]:
|
|
151
|
+
"""Get action method"""
|
|
152
|
+
return self.callback
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def doc(self) -> str:
|
|
156
|
+
"""Get action help message"""
|
|
157
|
+
return self.callback_doc
|
|
158
|
+
|
|
159
|
+
def _format(self, parser: argparse.ArgumentParser, info: Dict[str, Any], *args: Any) -> None:
|
|
160
|
+
parser_args, _ = parser.parse_known_args(*args)
|
|
161
|
+
|
|
162
|
+
default_format = "text"
|
|
163
|
+
try:
|
|
164
|
+
formatarg = parser_args.format or default_format
|
|
165
|
+
except AttributeError:
|
|
166
|
+
formatarg = default_format
|
|
167
|
+
|
|
168
|
+
try:
|
|
169
|
+
formatter = self.formatters[formatarg]
|
|
170
|
+
except KeyError as exc:
|
|
171
|
+
raise PdfFillerException(
|
|
172
|
+
f"{formatarg} is not a known format. Supported formatters are: "
|
|
173
|
+
f"{', '.join(self._help_formatters)}"
|
|
174
|
+
) from exc
|
|
175
|
+
|
|
176
|
+
formatter(info)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class PdfFillerCommand(BaseCommand):
|
|
180
|
+
"""Main PdfFiller command object"""
|
|
181
|
+
|
|
182
|
+
def __init__(
|
|
183
|
+
self,
|
|
184
|
+
cb: CommandCallback,
|
|
185
|
+
group: Optional[str] = None,
|
|
186
|
+
formatters: Optional[Dict[str, FormatterCallback]] = None,
|
|
187
|
+
callback_name: Optional[str] = None,
|
|
188
|
+
) -> None:
|
|
189
|
+
if not callback_name:
|
|
190
|
+
callback_name = cb.__name__.replace("_", "-")
|
|
191
|
+
super().__init__(callback_name, cb, formatters=formatters)
|
|
192
|
+
self.subcommands: Dict[str, "PdfFillerSubCommand"] = {}
|
|
193
|
+
self.group_name = group # or "Other"
|
|
194
|
+
|
|
195
|
+
def add_subcommand(self, subcommand: "PdfFillerSubCommand") -> None:
|
|
196
|
+
"""Register new sub-command"""
|
|
197
|
+
subcommand.set_name(self.callback_name)
|
|
198
|
+
self.subcommands[subcommand.callback_name] = subcommand
|
|
199
|
+
|
|
200
|
+
def _docs(self) -> PdfFillerArgumentParser:
|
|
201
|
+
parser = PdfFillerArgumentParser(
|
|
202
|
+
description=self.callback_doc,
|
|
203
|
+
prog=f"pdffiller {self.callback_name}",
|
|
204
|
+
formatter_class=SmartFormatter,
|
|
205
|
+
add_help=False,
|
|
206
|
+
)
|
|
207
|
+
return parser
|
|
208
|
+
|
|
209
|
+
def run(self, *args: Any) -> None:
|
|
210
|
+
"""Parse and execute requested command"""
|
|
211
|
+
parser = PdfFillerArgumentParser(
|
|
212
|
+
description=self.callback_doc,
|
|
213
|
+
prog=f"pdffiller {self.callback_name}",
|
|
214
|
+
formatter_class=SmartFormatter,
|
|
215
|
+
add_help=False,
|
|
216
|
+
)
|
|
217
|
+
# pylint: disable=protected-access
|
|
218
|
+
parser._command = self
|
|
219
|
+
info = self.callback(parser, *args)
|
|
220
|
+
|
|
221
|
+
if not self.subcommands:
|
|
222
|
+
self._format(parser, info, *args)
|
|
223
|
+
else:
|
|
224
|
+
subcommand_parser: SubParserType = parser.add_subparsers(
|
|
225
|
+
dest="subcommand",
|
|
226
|
+
)
|
|
227
|
+
subcommand_parser.required = True
|
|
228
|
+
try:
|
|
229
|
+
sub = self.subcommands[args[0][0]]
|
|
230
|
+
except (KeyError, IndexError): # display help
|
|
231
|
+
for sub in self.subcommands.values():
|
|
232
|
+
sub.set_parser(subcommand_parser)
|
|
233
|
+
parser.print_help()
|
|
234
|
+
raise InvalidSubCommandNameException( # pylint: disable=raise-missing-from
|
|
235
|
+
args[0][0] if len(args[0]) else ""
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
sub.set_parser(subcommand_parser)
|
|
239
|
+
sub.run(parser, *args)
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def group(self) -> Optional[str]:
|
|
243
|
+
"""Gets group name."""
|
|
244
|
+
return self.group_name
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class PdfFillerSubCommand(BaseCommand):
|
|
248
|
+
"""PdfFiller sub-command"""
|
|
249
|
+
|
|
250
|
+
parser: argparse.ArgumentParser
|
|
251
|
+
|
|
252
|
+
def __init__(
|
|
253
|
+
self,
|
|
254
|
+
cb: SubCommandCallback,
|
|
255
|
+
formatters: Optional[Dict[str, FormatterCallback]] = None,
|
|
256
|
+
) -> None:
|
|
257
|
+
super().__init__("", cb, formatters=formatters)
|
|
258
|
+
self.subcommand_name = cb.__name__.replace("_", "-")
|
|
259
|
+
|
|
260
|
+
def run(self, parent_parser: PdfFillerArgumentParser, *args: object) -> None:
|
|
261
|
+
"""Execute the sub-command"""
|
|
262
|
+
setattr(self.parser, "_command", self)
|
|
263
|
+
info = self.callback(parent_parser, self.parser, *args)
|
|
264
|
+
self._format(parent_parser, info, *args)
|
|
265
|
+
|
|
266
|
+
def set_name(self, parent_name: str) -> None:
|
|
267
|
+
"""Set sub-command name"""
|
|
268
|
+
self.callback_name = self.subcommand_name.replace(f"{parent_name}-", "", 1)
|
|
269
|
+
|
|
270
|
+
def set_parser(self, subcommand_parser: SubParserType) -> None:
|
|
271
|
+
"""Set the associated parser"""
|
|
272
|
+
self.parser = subcommand_parser.add_parser(
|
|
273
|
+
self.callback_name, help=self.callback_doc, add_help=True
|
|
274
|
+
)
|
|
275
|
+
self.parser.description = self.callback_doc
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def pdffiller_command(
|
|
279
|
+
group: Optional[str],
|
|
280
|
+
formatters: Optional[Dict[str, FormatterCallback]] = None,
|
|
281
|
+
name: Optional[str] = None,
|
|
282
|
+
) -> Callable[[CommandCallback], PdfFillerCommand]:
|
|
283
|
+
"""Register a PdfFiller command"""
|
|
284
|
+
return lambda f: PdfFillerCommand(f, group, formatters=formatters, callback_name=name)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def pdffiller_subcommand(
|
|
288
|
+
formatters: Optional[Dict[str, FormatterCallback]] = None
|
|
289
|
+
) -> Callable[[SubCommandCallback], PdfFillerSubCommand]:
|
|
290
|
+
"""Register a PdfFiller sub-command"""
|
|
291
|
+
return lambda f: PdfFillerSubCommand(f, formatters=formatters)
|
|
File without changes
|