Opts.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. #!/usr/bin/env python3
  2. #
  3. # Opts.py, an options parsing library for Python.
  4. # Copyright (C)2013-2018 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. Opts.py: Generic options parsing
  20. """
  21. import sys,getopt
  22. import collections
  23. def usage(opts_data):
  24. print('USAGE: {} {}'.format(opts_data['prog_name'], opts_data['usage']))
  25. sys.exit(2)
  26. def print_help_and_exit(opts_data,longhelp=False):
  27. pn = opts_data['prog_name']
  28. pn_len = str(len(pn)+2)
  29. out = ' {:<{p}} {}\n'.format(pn.upper()+':',opts_data['desc'].strip(),p=pn_len)
  30. out += ' {:<{p}} {} {}\n'.format('USAGE:',pn,opts_data['usage'].strip(),p=pn_len)
  31. o = opts_data[('options','long_options')[longhelp]].strip()
  32. if 'options_fmt_args' in opts_data:
  33. o = o.format(**opts_data['options_fmt_args']())
  34. hdr = ('OPTIONS:',' LONG OPTIONS:')[longhelp]
  35. ls = (' ','')[longhelp]
  36. es = ('',' ')[longhelp]
  37. out += '{ls}{}\n{ls}{es}{}'.format(hdr,('\n'+ls).join(o.splitlines()),ls=ls,es=es)
  38. if 'notes' in opts_data and not longhelp:
  39. n = opts_data['notes']
  40. if isinstance(n, collections.Callable): n = n()
  41. out += '\n ' + '\n '.join(n.rstrip().splitlines())
  42. print(out)
  43. sys.exit(0)
  44. def process_opts(argv,opts_data,short_opts,long_opts,skip_help=False):
  45. import os
  46. opts_data['prog_name'] = os.path.basename(sys.argv[0])
  47. long_opts = [i.replace('_','-') for i in long_opts]
  48. so_str = short_opts.replace('-:','').replace('-','')
  49. try: cl_opts,args = getopt.getopt(argv[1:], so_str, long_opts)
  50. except getopt.GetoptError as err:
  51. print(str(err))
  52. sys.exit(2)
  53. sopts_list = ':_'.join(['_'.join(list(i)) for i in short_opts.split(':')]).split('_')
  54. opts,skipped_help = {},False
  55. for opt,arg in cl_opts:
  56. if opt in ('-h','--help'):
  57. if not skip_help: print_help_and_exit(opts_data)
  58. skipped_help = True
  59. elif opt == '--longhelp':
  60. if not skip_help: print_help_and_exit(opts_data,longhelp=True)
  61. skipped_help = True
  62. elif opt[:2] == '--' and opt[2:] in long_opts:
  63. opts[opt[2:].replace('-','_')] = True
  64. elif opt[:2] == '--' and opt[2:]+'=' in long_opts:
  65. opts[opt[2:].replace('-','_')] = arg
  66. elif opt[1] != '-' and opt[1] in sopts_list:
  67. opts[long_opts[sopts_list.index(opt[1:])].replace('-','_')] = True
  68. elif opt[1] != '-' and opt[1:]+':' in sopts_list:
  69. opts[long_opts[sopts_list.index(
  70. opt[1:]+':')][:-1].replace('-','_')] = arg
  71. else: assert False, 'Invalid option'
  72. if 'sets' in opts_data:
  73. for o_in,v_in,o_out,v_out in opts_data['sets']:
  74. if o_in in opts:
  75. v = opts[o_in]
  76. if (v and v_in == bool) or v == v_in:
  77. if o_out in opts and opts[o_out] != v_out:
  78. sys.stderr.write(
  79. 'Option conflict:\n --{}={}, with\n --{}={}\n'.format(
  80. o_out.replace('_','-'),opts[o_out],
  81. o_in.replace('_','-'),opts[o_in]))
  82. sys.exit(1)
  83. else:
  84. opts[o_out] = v_out
  85. return opts,args,skipped_help
  86. def parse_opts(argv,opts_data,opt_filter=None,skip_help=False):
  87. import re
  88. pat = r'^-([a-zA-Z0-9-]), --([a-zA-Z0-9-]{2,64})(=| )(.+)'
  89. od_all = []
  90. for k in ['options'] + ([],['long_options'])['long_options' in opts_data]:
  91. od,skip = [],True
  92. for l in opts_data[k].strip().splitlines():
  93. m = re.match(pat,l)
  94. if m:
  95. skip = (False,True)[bool(opt_filter) and m.group(1) not in opt_filter]
  96. app = (['',''],[':','='])[m.group(3) == '=']
  97. od.append(list(m.groups()) + app + [skip])
  98. else:
  99. if not skip: od[-1][3] += '\n' + l
  100. opts_data[k] = '\n'.join(
  101. ['{:<3} --{} {}'.format(
  102. ('-'+d[0]+',','')[d[0]=='-'],d[1],d[3]) for d in od if d[6] == False]
  103. )
  104. od_all += od
  105. short_opts = ''.join([d[0]+d[4] for d in od_all if d[6] == False])
  106. long_opts = [d[1].replace('-','_')+d[5] for d in od_all if d[6] == False]
  107. skipped_opts = [d[1].replace('-','_') for d in od_all if d[6] == True]
  108. opts,args,skipped_help = process_opts(argv,opts_data,short_opts,long_opts,skip_help=skip_help)
  109. return opts,args,short_opts,long_opts,skipped_opts,skipped_help