Commit | Line | Data |
---|---|---|
410f6138 CH |
1 | # The software in this package is distributed under the GNU General |
2 | # Public License version 2 (with a special exception described below). | |
3 | # | |
4 | # A copy of GNU General Public License (GPL) is included in this distribution, | |
5 | # in the file COPYING.GPL. | |
6 | # | |
7 | # As a special exception, if other files instantiate templates or use macros | |
8 | # or inline functions from this file, or you compile this file and link it | |
9 | # with other works to produce a work based on this file, this file | |
10 | # does not by itself cause the resulting work to be covered | |
11 | # by the GNU General Public License. | |
12 | # | |
13 | # However the source code for this file must still be made available | |
14 | # in accordance with section (3) of the GNU General Public License. | |
15 | # | |
16 | # This exception does not invalidate any other reasons why a work based | |
17 | # on this file might be covered by the GNU General Public License. | |
18 | ||
19 | """ | |
20 | argparse_helpers: Some convenience helpers for argparse | |
21 | ||
df036fbe CH |
22 | Featuring: |
23 | - NonExitingParser: a subclass of :py:class:`argparse.ArgumentParser` | |
24 | that does not do *sys.exit(2)* on parse error but instead raises a | |
25 | :py:class:`ArgParserWantsExit` exception. | |
26 | - function existing_file/dir[_or_empty] which can be used as a type in calls to | |
27 | :py:func:`argparse.ArgumentParser.add_argument` | |
410f6138 CH |
28 | |
29 | .. codeauthor:: Intra2net | |
30 | """ | |
31 | ||
32 | from argparse import ArgumentParser, ArgumentTypeError | |
f02ce4ca | 33 | from os.path import isfile, isdir |
410f6138 CH |
34 | |
35 | ||
36 | class ArgParserWantsExit(Exception): | |
37 | """ | |
38 | Exception raised from NonExitingParser instead of calling sys.exit(2). | |
39 | """ | |
40 | ||
41 | def __init__(self, message): | |
42 | super(ArgParserWantsExit, self).__init__("Error parsing args: " + | |
43 | message) | |
44 | ||
45 | ||
46 | class NonExitingParser(ArgumentParser): | |
47 | """ArgumentParser that does not call sys.exit(2) on parse failure. | |
48 | ||
f02ce4ca | 49 | Calling `sys.exit` also just raises a SystemExit exception. But that is not |
410f6138 CH |
50 | a subclass of Exception and not as explicit and specific as this one. |
51 | ||
52 | Convenient e.g. for global try-except blocks e.g. in a daemon:: | |
53 | ||
54 | def main(): | |
55 | log = None | |
56 | try: | |
57 | log = create_log() | |
58 | parser = NonExitingParser() | |
59 | # ... add args ... | |
60 | args = parser.parse_args() | |
61 | # ... rest of your program ... | |
62 | except Exception as exc: | |
63 | log.error('uncaught exception: exc') # will always show in log | |
64 | ||
65 | """ | |
66 | def error(self, message): | |
67 | """Called when error occurred parsing args. Raise error, not exit.""" | |
68 | raise ArgParserWantsExit(message) | |
69 | ||
70 | ||
71 | def existing_file(filename): | |
72 | """ | |
73 | Function that raises ArgumentTypeError if argument is not an existing file. | |
74 | ||
75 | Returns filename if it is valid. Can be used as `type` argument in | |
76 | :py:meth:`argparse.ArgumentParser.add_argument` | |
77 | ||
78 | If you want to open the file, you can as well use the python built-in | |
79 | :py:class:`argparse.FileType` instead. | |
80 | """ | |
81 | if isfile(filename): | |
82 | return filename | |
83 | raise ArgumentTypeError('{} is not an existing file'.format(filename)) | |
752bf4ea CH |
84 | |
85 | ||
86 | def existing_file_or_empty(filename=''): | |
87 | """ | |
88 | Like :py:func:`existing_file` but accepts empty filename (returns '' then). | |
89 | """ | |
90 | if not filename.strip(): | |
91 | return '' | |
92 | return existing_file(filename) | |
f02ce4ca CH |
93 | |
94 | ||
95 | def existing_dir(path): | |
96 | """ | |
97 | Function that raises ArgumentTypeError if argument is not an existing directory. | |
98 | ||
99 | .. seealso:: :py:func:`existing_file`, :py:func:`existing_dir_or_empty` | |
100 | """ | |
101 | if isdir(path): | |
102 | return path | |
103 | raise ArgumentTypeError('{} is not an existing directory'.format(path)) | |
104 | ||
105 | ||
106 | def existing_dir_or_empty(path=''): | |
107 | """ | |
108 | Like :py:func:`existing_dir` but accepts empty path (returns '' then). | |
109 | """ | |
110 | if not path.strip(): | |
111 | return '' | |
112 | return existing_dir(path) |