mmgen.share.Opts: improve opts parsing

This commit is contained in:
The MMGen Project 2020-04-14 13:42:08 +00:00
commit 05653ee47a
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
3 changed files with 100 additions and 104 deletions

View file

@ -167,6 +167,7 @@ class g(object):
'hidden_incog_input_params','in_fmt'
)
incompatible_opts = (
('help','longhelp'),
('bob','alice'),
('label','keep_label'),
('tx_id','info'),

View file

@ -53,8 +53,7 @@ def _show_hash_presets():
def opt_preproc_debug(po):
d = (
('Cmdline', ' '.join(sys.argv)),
('Short opts', po.short_opts),
('Long opts', po.long_opts),
('Opts', po.opts),
('Skipped opts', po.skipped_opts),
('User-selected opts', po.user_opts),
('Cmd args', po.cmd_args),
@ -184,7 +183,7 @@ def init(opts_data,add_opts=[],opt_filter=None,parse_only=False):
opts_data['text']['long_options'] = common_opts_data['text']
# po: user_opts cmd_args short_opts long_opts skipped_opts
# po: user_opts cmd_args opts skipped_opts
po = mmgen.share.Opts.parse_opts(opts_data,opt_filter=opt_filter,parse_only=parse_only)
if parse_only:
@ -194,10 +193,10 @@ def init(opts_data,add_opts=[],opt_filter=None,parse_only=False):
opt_preproc_debug(po)
# Copy parsed opts to opt, setting values to None if not set by user
for o in (
tuple(s.rstrip('=') for s in po.long_opts)
for o in set(
po.opts
+ po.skipped_opts
+ tuple(add_opts)
+ tuple(po.skipped_opts)
+ g.required_opts
+ g.common_opts ):
setattr(opt,o,po.user_opts[o] if o in po.user_opts else None)
@ -295,13 +294,14 @@ def init(opts_data,add_opts=[],opt_filter=None,parse_only=False):
# === end global var initialization === #
if opts_data['do_help']: # print help screen only after global vars are initialized
die_on_incompatible_opts(g.incompatible_opts)
# print help screen only after global vars are initialized:
if getattr(opt,'help',None) or getattr(opt,'longhelp',None):
if not 'code' in opts_data:
opts_data['code'] = {}
opts_data['code']['long_options'] = common_opts_data['code']
mmgen.share.Opts.print_help(opts_data,opt_filter) # exits
die_on_incompatible_opts(g.incompatible_opts)
mmgen.share.Opts.print_help(po,opts_data,opt_filter) # exits
check_or_create_dir(g.data_dir) # g.data_dir is finalized, so we can create it

View file

@ -20,122 +20,117 @@
Opts.py: Generic options parsing
"""
import sys,getopt
import collections
import sys,re
from collections import namedtuple
pat = re.compile(r'^-([a-zA-Z0-9-]), --([a-zA-Z0-9-]{2,64})(=| )(.+)')
def usage(opts_data):
print('USAGE: {} {}'.format(opts_data['prog_name'], opts_data['usage']))
sys.exit(2)
def print_help(opts_data,opt_filter):
def print_help(po,opts_data,opt_filter):
def parse_lines(text):
filtered = False
for line in text.strip().splitlines():
m = pat.match(line)
if m:
filtered = bool(opt_filter and m[1] not in opt_filter)
if not filtered:
yield fs.format( ('-'+m[1]+',','')[m[1]=='-'], m[2], m[4] )
elif not filtered:
yield line
opts_type,fs = ('options','{:<3} --{} {}') if 'help' in po.user_opts else ('long_options','{} --{} {}')
t = opts_data['text']
c = opts_data['code']
nl = '\n '
text = nl.join(parse_lines(t[opts_type]))
# header
pn = opts_data['prog_name']
out = ' {:<{p}} {}\n'.format(pn.upper()+':',t['desc'].strip(),p=len(pn)+1)
out += ' {:<{p}} {} {}\n'.format('USAGE:',pn,t['usage'].strip(),p=len(pn)+1)
out = (
' {:<{p}} {}'.format(pn.upper()+':',t['desc'].strip(),p=len(pn)+1)
+ nl + '{:<{p}} {} {}'.format('USAGE:',pn,t['usage'].strip(),p=len(pn)+1)
+ nl + opts_type.upper().replace('_',' ') + ':'
+ nl + (c[opts_type](text) if opts_type in c else text)
)
# options
if opts_data['do_help'] == 'longhelp':
hdr,ls,es = (' LONG OPTIONS:','',' ')
text = t['long_options'].strip()
code = c['long_options'] if 'long_options' in c else None
else:
hdr,ls,es = ('OPTIONS:',' ','')
text = t['options']
code = c['options'] if 'options' in c else None
ftext = code(text) if code else text
out += '{ls}{}\n{ls}{es}{}'.format(hdr,('\n'+ls).join(ftext.splitlines()),ls=ls,es=es)
# notes
if opts_data['do_help'] == 'help' and 'notes' in t:
ftext = c['notes'](t['notes']) if 'notes' in c else t['notes']
out += '\n ' + '\n '.join(ftext.rstrip().splitlines())
if opts_type == 'options' and 'notes' in t:
ntext = c['notes'](t['notes']) if 'notes' in c else t['notes']
out += nl + nl.join(ntext.rstrip().splitlines())
print(out)
sys.exit(0)
def process_opts(opts_data,short_opts,long_opts):
def process_uopts(opts_data,short_opts,long_opts):
import os
import os,getopt
opts_data['prog_name'] = os.path.basename(sys.argv[0])
long_opts = [i.replace('_','-') for i in long_opts]
so_str = short_opts.replace('-:','').replace('-','')
try: cl_opts,args = getopt.getopt(sys.argv[1:], so_str, long_opts)
except getopt.GetoptError as err:
print(str(err))
sys.exit(2)
try:
cl_uopts,uargs = getopt.getopt(sys.argv[1:],''.join(short_opts),long_opts)
except getopt.GetoptError as e:
print(e.args[0])
sys.exit(1)
sopts_list = ':_'.join(['_'.join(list(i)) for i in short_opts.split(':')]).split('_')
opts = {}
opts_data['do_help'] = False
def get_uopts():
for uopt,uparm in cl_uopts:
if uopt.startswith('--'):
lo = uopt[2:]
if lo in long_opts:
yield (lo.replace('-','_'), True)
else: # lo+'=' in long_opts
yield (lo.replace('-','_'), uparm)
else: # uopt.startswith('-')
so = uopt[1]
if so in short_opts:
yield (long_opts[short_opts.index(so)].replace('-','_'), True)
else: # so+':' in short_opts
yield (long_opts[short_opts.index(so+':')][:-1].replace('-','_'), uparm)
for opt,arg in cl_opts:
if opt in ('-h','--help'):
opts_data['do_help'] = 'help'
elif opt == '--longhelp':
opts_data['do_help'] = 'longhelp'
elif opt[:2] == '--' and opt[2:] in long_opts:
opts[opt[2:].replace('-','_')] = True
elif opt[:2] == '--' and opt[2:]+'=' in long_opts:
opts[opt[2:].replace('-','_')] = arg
elif opt[1] != '-' and opt[1] in sopts_list:
opts[long_opts[sopts_list.index(opt[1:])].replace('-','_')] = True
elif opt[1] != '-' and opt[1:]+':' in sopts_list:
opts[long_opts[sopts_list.index(
opt[1:]+':')][:-1].replace('-','_')] = arg
else: assert False, 'Invalid option'
uopts = dict(get_uopts())
if 'sets' in opts_data:
for o_in,v_in,o_out,v_out in opts_data['sets']:
if o_in in opts:
v = opts[o_in]
if (v and v_in == bool) or v == v_in:
if o_out in opts and opts[o_out] != v_out:
for a_opt,a_val,b_opt,b_val in opts_data['sets']:
if a_opt in uopts:
u_val = uopts[a_opt]
if (u_val and a_val == bool) or u_val == a_val:
if b_opt in uopts and uopts[b_opt] != b_val:
sys.stderr.write(
'Option conflict:\n --{}={}, with\n --{}={}\n'.format(
o_out.replace('_','-'),opts[o_out],
o_in.replace('_','-'),opts[o_in]))
'Option conflict:'
+ '\n --{}={}, with'.format(b_opt.replace('_','-'),uopts[b_opt])
+ '\n --{}={}\n'.format(a_opt.replace('_','-'),uopts[a_opt]) )
sys.exit(1)
else:
opts[o_out] = v_out
uopts[b_opt] = b_val
return opts,args
return uopts,uargs
def parse_opts(opts_data,opt_filter=None,parse_only=False):
import re
pat = r'^-([a-zA-Z0-9-]), --([a-zA-Z0-9-]{2,64})(=| )(.+)'
od_all = []
for k in ('options','long_options'):
if k not in opts_data['text']: continue
od,skip = [],True
for l in opts_data['text'][k].strip().splitlines():
m = re.match(pat,l)
short_opts,long_opts,skipped_opts = [],[],[]
def parse_lines(opts_type):
for line in opts_data['text'][opts_type].strip().splitlines():
m = pat.match(line)
if m:
skip = bool(opt_filter) and m.group(1) not in opt_filter
app = (['',''],[':','='])[m.group(3) == '=']
od.append(list(m.groups()) + app + [skip])
else:
if not skip: od[-1][3] += '\n' + l
if bool(opt_filter and m[1] not in opt_filter):
skipped_opts.append(m[2])
else:
if opts_type == 'options':
short_opts.append(m[1] + ('',':')[m[3] == '='])
long_opts.append(m[2] + ('','=')[m[3] == '='])
if not parse_only:
opts_data['text'][k] = '\n'.join(
['{:<3} --{} {}'.format(
('-'+d[0]+',','')[d[0]=='-'],d[1],d[3]) for d in od if d[6] == False]
)
od_all += od
for opts_type in ('options','long_options'):
parse_lines(opts_type)
short_opts = ''.join([d[0]+d[4] for d in od_all if d[6] == False])
long_opts = [d[1].replace('-','_')+d[5] for d in od_all if d[6] == False]
skipped_opts = [d[1].replace('-','_') for d in od_all if d[6] == True]
uopts,uargs = process_uopts(opts_data,short_opts,long_opts)
opts,args = process_opts(opts_data,short_opts,long_opts)
from collections import namedtuple
ret = namedtuple('parsed_cmd_opts',['user_opts','cmd_args','short_opts','long_opts','skipped_opts'])
return ret(opts,args,short_opts,long_opts,skipped_opts)
po = namedtuple('parsed_cmd_opts',['user_opts','cmd_args','opts','skipped_opts'])
return po(
uopts, # dict
uargs, # list, callers can pop
tuple(o.replace('-','_').rstrip('=') for o in long_opts),
tuple(o.replace('-','_') for o in skipped_opts),
)