Fix errors in api doc creation
[pyi2ncommon] / src / argparse_helpers.py
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
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`
28
29 .. codeauthor:: Intra2net
30 """
31
32 from argparse import ArgumentParser, ArgumentTypeError
33 from os.path import isfile, isdir
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
49     Calling `sys.exit` also just raises a SystemExit exception. But that is not
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))
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)
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)