From 05653ee47a974b23245c16db2ae411d2bd0e6608 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Tue, 14 Apr 2020 13:42:08 +0000 Subject: [PATCH] mmgen.share.Opts: improve opts parsing --- mmgen/globalvars.py | 1 + mmgen/opts.py | 20 +++--- mmgen/share/Opts.py | 169 +++++++++++++++++++++----------------------- 3 files changed, 93 insertions(+), 97 deletions(-) diff --git a/mmgen/globalvars.py b/mmgen/globalvars.py index 3cea849f..8b28293e 100755 --- a/mmgen/globalvars.py +++ b/mmgen/globalvars.py @@ -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'), diff --git a/mmgen/opts.py b/mmgen/opts.py index 50332c73..95fac7b8 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -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 diff --git a/mmgen/share/Opts.py b/mmgen/share/Opts.py index 3fdcf98c..6fd8f392 100755 --- a/mmgen/share/Opts.py +++ b/mmgen/share/Opts.py @@ -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), + )