Merge branch 'api-doc-improvements'
[pyi2ncommon] / src / argparse_helpers.py
CommitLineData
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"""
20argparse_helpers: Some convenience helpers for argparse
21
df036fbe
CH
22Featuring:
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
32from argparse import ArgumentParser, ArgumentTypeError
f02ce4ca 33from os.path import isfile, isdir
410f6138
CH
34
35
36class 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
46class 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
71def 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
86def 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
95def 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
106def 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)