1# 2# Copyright (c) 2019 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com> 3# 4# This program is free software; you can redistribute it and/or 5# modify it under the terms of the GNU General Public License as 6# published by the Free Software Foundation; either version 2 of 7# the License, or (at your option) any later version. 8# 9# This program is distributed in the hope that it will be useful, 10# but WITHOUT ANY WARRANTY; without even the implied warranty of 11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12# GNU General Public License for more details. 13# 14 15import sys 16import os 17import re 18 19 20class Params: 21 def __init__(self): 22 self.excstk = False 23 24 25class Options: 26 def __init__(self, need_args, help_text, version_text): 27 for name in need_args: 28 if not re.fullmatch('(-[^-]|--[^=]+)', name): 29 raise Exception('invalid option name "%s"' % name) 30 31 self.need_args = need_args 32 self.help_text = help_text 33 self.version_text = version_text 34 35 36 def posixly_correct(self): # pylint: disable=no-self-use 37 return 'POSIXLY_CORRECT' in os.environ 38 39 40 def needs_arg(self, name): 41 return name in self.need_args 42 43 44 def fallback(self, name, params): 45 if name == '--excstk': 46 params.excstk = True 47 elif name == '--help' and self.help_text is not None: 48 sys.stdout.write(self.help_text) 49 sys.exit(0) 50 elif name == '--version' and self.version_text is not None: 51 sys.stdout.write(self.version_text) 52 sys.exit(0) 53 else: 54 suffix = ' (taking an argument?)' if self.needs_arg(name) else '' 55 suffix += ', try --help' if self.help_text is not None else '' 56 raise Exception('unknown option "%s"%s' % (name, suffix)) 57 58 59 def reader(self, args, skip_zero=True): 60 return Options.Reader(self, args, skip_zero) 61 62 63 class Reader: 64 def __init__(self, options, args, skip_zero): 65 self.options = options 66 self.args = args 67 self.skip_zero = skip_zero 68 69 70 def __iter__(self): 71 return Options.Reader.Iterator(self) 72 73 74 class Iterator: 75 def __init__(self, reader): 76 self.options = reader.options 77 self.args = reader.args 78 self.optind = int(reader.skip_zero) 79 self.chrind = 1 80 self.endopt = False 81 82 83 def __next__(self): 84 if self.chrind == 0: 85 self.optind += 1 86 self.chrind = 1 87 88 if self.optind == len(self.args): 89 raise StopIteration 90 91 arg = self.args[self.optind] 92 93 if self.endopt or arg == '-' or not arg.startswith('-'): 94 self.endopt = self.options.posixly_correct() 95 name = None 96 value = arg 97 elif arg == '--': 98 self.chrind = 0 99 self.endopt = True 100 return next(self) 101 elif not arg.startswith('--'): 102 name = '-' + arg[self.chrind] 103 self.chrind += 1 104 if self.chrind < len(arg): 105 if not self.options.needs_arg(name): 106 return (name, None) 107 value = arg[self.chrind:] 108 else: 109 value = None 110 elif '=' in arg and arg.index('=') >= 3: 111 name = arg.split('=', 1)[0] 112 if not self.options.needs_arg(name): 113 raise Exception('option "%s" does not take an argument' % name) 114 value = arg[len(name) + 1:] 115 else: 116 name = arg 117 value = None 118 119 if value is None and int(self.options.needs_arg(name)) > 0: 120 self.optind += 1 121 if self.optind == len(self.args): 122 raise Exception('option "%s" requires an argument' % name) 123 value = self.args[self.optind] 124 125 self.chrind = 0 126 return (name, value) 127 128 129def start(program_name, options, params, main_program): 130 parsed = Params() if params is None else params 131 132 try: 133 if sys.hexversion < 0x3050000: 134 raise Exception('python 3.5.0 or later required') 135 136 if params is None: 137 return main_program(options.reader(sys.argv), lambda name: options.fallback(name, parsed)) 138 139 nonopt = [] 140 141 for [name, value] in options.reader(sys.argv): 142 if name is None: 143 nonopt.append(value) 144 else: 145 options.parse(name, value, parsed) 146 147 return main_program(nonopt, parsed) 148 149 except Exception as ex: 150 if parsed.excstk: 151 raise 152 153 sys.stderr.write('%s: %s\n' % (sys.argv[0] if sys.argv[0] else program_name, str(ex))) 154 sys.exit(1) 155