I am all about trying to make development easier and reduce mundane tasks, especially dealing with commandline args and getopt, OptParse, argparse sorry just not a fan. Argparse-cli is really cool if you want low level control. I just got tired of writing tons of conditionals, labels, limited type handling, and having to do all of it over and over for every commandline project so help end the madness I wrote PyOpts.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 | """
PyOpts.py
Author: AJ Mayorga
A highlevel quick wrapper for simplifying commandline options/arg handling
FEATURES:
- Generates shorts, longs and help/usage automatically
- Allows for specification of required options/args or groups there of
in addition to exclusionary opt/args or groups.
- Configures default values and selects/"choices"
- Returns a simple dictionary with key of supplied key name and value of configured type
DETAILS AND SUCH:
By passing a dict of args that are normally present & you will use in your code anyway.
The arg dict is given a key name & initialize each with its own config dict consisting of
the following keys and values.
PROJECT KEYS:
About is an about section that will be used as a header for
the generated usage called by -h/--help
ExitOnErr if True when and error or exception is found in processing the opt/args
an error message will be generated (if Quiet=False) followed by sys.exit()
Quiet toggles error/exception message output
Minimum the minimum number of required opt/args. This is for trivial commandline
requirements. 0 indicates no min required.
All other provided keys are considered option keys the provided names for which will be used
to derive shorts '-p' and longs '--port'. Each option key's value is initialized with a
configuration dict.
CONFIG DICT:
GID group identifier for a vars used by REQ Can be alpha/num value
REQ indicates variable requirement when not present False is default
- True indicates an independent value must be supplied, False not necessary
- When a list of GIDs is supplied the var requirment is True and
all group members without a '-' prefix must have asigned values.
A GID prefixed with a '-' indicates an exclusionary var meaning if
if provided with a current var an error will occur (think xor)
VALUE serves as default value and/or type cast reference for final value
for all variables that are int, strings, bools or float.
- When a list is provided it serves as a choice selection
where the provided value must match one listed in the list.
HELP a short descriptive line that is returned along with an error message
if an error is discovered related to the var. Also used to construct
--help/-h usage output
if all opts/args are received without error the config portion of of the ARGS dict
is overwritten with the user supplied value cast to the type of the supplied 'VALUE'
from the orig config.
opts/args supplied by the user that are not configured are ignored.
a list of mock debug values can be supplied (see SA below) for debugging providing
this list overrides reading sys.argv[1:]
#####################################################################################################
"""
import sys, os, string
class PyOptsException(Exception):
def __init__(self, value):
self.parameter = value
def __str__(self):
return repr(self.parameter)
class PyOpts(PyOptsException):
def __init__(self):
self.SA = None
self.about = ''
self.uhead = ''
self.ExitOnErr = False
self.Quiet = False
self.help = False
self.usage = []
self.Temp = []
self.Lost = {}
self.Groups = {}
self.OUT = {}
self.Found = []
self.Minimum = 0
self.Configs = ['About','Quiet','ExitOnErr','Minimum','Missing']
self.ErrorMsgs = ['Missing Member: %s of Option Group %s',
'Invalid Arguement: %s For Option %s Valid Args Are: %s',
'Duplicate Long Name Found Check Key Name In Init Dict',
'No Members Assigned To Required Group: ',
'Error Missing Required Parameter : %s %s',
'%s Options/Args Less Than Required Minimum %s',
'Exclusion Error Option %s Cannot Not Be Used With %s' ]
def OnErr(self, ErrorNum, args):
if not self.Quiet:
print self.ErrorMsgs[ErrorNum] % args
if self.ExitOnErr:
sys.exit(1)
return False
def SL(self, v, z):
d = self.Temp
if z+v in d:
if z == '-':
short = (z+[x for x in string.letters if not z+x in d][0],
z+v.upper())[not z+v.upper() in d]
self.Temp.append(short)
return short
raise PyOptsException(self.ErrorMsgs[2])
long = z+v
self.Temp.append(long)
return long
def CastAs(self, var, varType):
try:
if isinstance(varType, bool): return bool(var)
elif isinstance(varType, str): return str(var)
elif isinstance(varType, int): return int(var)
elif isinstance(varType, float):return float(var)
elif isinstance(varType, long): return long(var)
elif isinstance(varType, int): return int(var)
except:
return False
def GetValue(self, opt,cV,aV):
val = self.CastAs(aV, cV)
if not isinstance(cV, list):
return val
for x in cV:
y = self.GetValue(opt,x, aV)
if repr(y) == repr(x):
return y
choices = '|'.join([str(x) for x in cV])
self.OnErr(1,(repr(aV),opt,choices))
def LookUp(self, d, key):
for k,v in d.iteritems():
if k in self.Configs: continue
if k == key:
return v
elif isinstance(d[k], dict):
r = self.LookUp(d[k], key)
if r: return r
return None
def DependencyCheck(self, Opts):
for gid, members in self.Groups.iteritems():
missing = [x for x in members if x not in self.Found]
self.Groups[gid]['Missing'] = missing
for opt, conf in Opts.iteritems():
if opt in self.Configs:
continue
exclude = False
if isinstance(conf['REQ'], list):
for gid in conf['REQ']:
if isinstance(gid, int):
if gid < 0:
exclude=True
gid = abs(gid)
else:
if gid[0] == '-':
exclude=True
gid = gid.replace('-','')
gid = repr(gid)
if self.Groups.has_key(gid):
groupOpts = repr([x for x in self.Groups[gid].itervalues() if x != []])
groupOpts = groupOpts.replace("'", "")
if self.Groups[gid] and exclude:
opt = repr(self.LookUp(self.Groups, opt)).replace("'","")
self.OnErr(6, (opt,groupOpts))
if self.Groups[gid]['Missing'] != []:
mia = ''
for member in self.Groups[gid]['Missing']:
mia += repr(self.Groups[gid][member]).replace("'","")
return mia,groupOpts
else:
print self.Groups
raise PyOptsException(self.ErrorMsgs[3]+gid)
return False
def OP(self, Opts, SA=False):
SA = (SA,sys.argv[1:])[SA==False]
self.about = Opts.get('About', '')
self.ExitOnErr = Opts.get('ExitOnErr', True)
self.Quiet = Opts.get('Quiet', False)
self.Minimum = Opts.get('Minimum', 0)
for opt, conf in Opts.iteritems():
if opt in self.Configs:
continue
conf['GID'] = gid = conf.get('GID', None)
conf['REQ'] = req = conf.get('REQ', False)
conf['HELP'] = help = conf.get('HELP', '---')
conf['VALUE'] = value = conf.get('VALUE', '')
conf['VALUE'] = value = (value,'True/False')[isinstance(value,bool)]
short, long = (self.SL(opt[:1],'-'), self.SL(opt,'--'))
self.usage.extend([' '+short+' '*2,long+' '*(15-len(long)),
str(value)+' '*(20-len(str(value))),help,'\n'])
if gid != None:
gid = repr(gid)
if not self.Groups.has_key(gid):
self.Groups[gid]={}
self.Groups[gid][opt] = [short,long]
if not short in SA and not long in SA and req:
if gid:
self.Lost[gid] = opt
if req == True:
self.OnErr(4,(opt,help))
for x in range(len(SA)):
if SA[x] == '-h' or SA[x] == '--help':
self.help = True
continue
if SA[x] == long or SA[x] == short:
self.OUT[opt] = self.GetValue(opt, value, SA[x+1])
self.Found.append(opt)
if len(self.Found) < self.Minimum:
self.OnErr(5, (len(self.Found),self.Minimum))
mia = self.DependencyCheck(Opts)
if mia:
self.OnErr(0,mia)
if self.help:
print self.about+self.uhead+'\n'+''.join(self.usage)
sys.exit(1)
return self.OUT
#------------------------------------------------------------------------------#
# DEMO PROJECT STARTS HERE
from PyOpts import PyOpts
__author__ = "AJ Mayorga"
__version__ = "1.0.1"
__status__ = "Demo"
ABOUT="""
################################################################################
project: myOpts.py
version : """+__version__+"""
author : """+__author__+"""
status : """+__status__+"""
This is a Demo Program To Illustrate Handling Of Options/Arguments
At The Commandline
################################################################################
"""
#For Debugging Play With These To Override sys.argv[1:] to see how things are handled
SA = ['myOpts.py','--to','Mary','--fromname','boomers',
'--body','blah','--fromaddress', 'Billy@yuckbutt.com',
'--subject', 'Scooters', '--threads',43, '--template','Monkey','-h']
class OPTIONS_DEMO:
def __init__(self):
#Simple Option/Args
self.ARGS = {}
self.ARGS['About'] = ABOUT
self.ARGS['Minimum'] = 4
self.ARGS['to'] = {'VALUE': "", 'HELP':"Email List or Single Address"}
self.ARGS['fromaddress'] = {'VALUE': "", 'HELP':"Email Address of Sender e.g.WB@blah.com"}
self.ARGS['subject'] = {'VALUE': "", 'HELP':"Subject of Email"}
self.ARGS['body'] = {'VALUE': "", 'HELP':"Body/Content of Email (File or String)"}
"""
#Advanced Option/Args
PROTOS = ["HTTP","HTTPS","FTP"]
self.ARGS = {}
self.ARGS['About'] = ABOUT
self.ARGS['ExitOnErr'] = True
self.ARGS['Quiet'] = False
self.ARGS['Minimum'] = 0
self.ARGS['newtemplate'] = {'GID':0, 'REQ':[-1], 'VALUE': False, 'HELP':"Save Resulting Email As New Template"}
self.ARGS['template'] = {'GID':1, 'REQ':[-0], 'VALUE': False, 'HELP':"Email Template To Use"}
self.ARGS['to'] = {'GID':2, 'REQ':True, 'VALUE': "", 'HELP':"Email List or Single Address"}
self.ARGS['fromname'] = {'GID':3, 'REQ':[2], 'VALUE': "", 'HELP':"Name of Sender e.g. Will J Buck CEO"}
self.ARGS['fromaddress'] = {'GID':4, 'REQ':[3], 'VALUE': "", 'HELP':"Email Address of Sender e.g. WB@blah.com"}
self.ARGS['subject'] = {'GID':5, 'REQ':[4], 'VALUE': "", 'HELP':"Subject of Email"}
self.ARGS['body'] = {'GID':6, 'REQ':[5], 'VALUE': "", 'HELP':"Body/Content of Email (File or String)"}
self.ARGS['attachments'] = { 'REQ':False, 'VALUE': "", 'HELP':"Comma Separated List of Attachment Files"}
self.ARGS['threads'] = { 'REQ':False, 'VALUE': 1, 'HELP':"Number of Sending Threads"}
self.ARGS['signed'] = { 'REQ':False, 'VALUE': False, 'HELP':"Digitally Sign On Each Sent Email"}
self.ARGS['readreceipt'] = { 'REQ':False, 'VALUE': False, 'HELP':"Require Read Receipts"}
self.ARGS['linkprotocol'] = { 'REQ':False, 'VALUE': PROTOS,'HELP':"Format Embedded Links To Protocol"}
"""
self.ARGS = PyOpts().OP(self.ARGS,SA)
print self.ARGS
if __name__ == '__main__':
OD = OPTIONS_DEMO()
|
while optparse may show its age, argparse and argparse-cli would serve you better. Take a look at them, before reinventing the wheel :P
Thx Alan, I did look at those and must say argparse-cli looks pretty cool tons of capability but it lacks a high level implementation for basic options/args needs Im all about granularity and control but sometimes you just dont want to type all those parser.add_options and have to do it for every commandline project BLEH!, And argparse is not in standard Python yet so Guessing what I need to do is actually make a high level wrapper for optparse/argparse, hmmmmm so many ideas so little time.
Revised and even more simple uses common variables already found for the most part in your code to handle basic options handling, when you dont need all the bells and whistles of argparse and just want to get on coding. Provided values in the ARGS dict are used to determine type and default. after myOpts does the checking and processing it outputs a very sane easy dict that overwrites the orig so your program can just refer to class args by calling self.ARGS['argname'] = value with correct type from commandline, Easy : )
You may want to have at look at this recipe http://code.activestate.com/recipes/576935/ for a higher level interface to argparse.
@Alia thanks looked at it but still doesnt seem as intuitive or streamlined as what Ive done here. with what ive done you never have to declare or instantiate anything that you wont use later. Granted pyopts doesnt give you as much granularity as argparse-cli but for fast option handling and a real descent set of flexibilities it comes pretty close in a small package.