helpparser.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. import sys
  2. from subprocess import Popen, PIPE
  3. import re
  4. from collections import namedtuple
  5. from itertools import chain
  6. import argparse
  7. from vim_pandoc.utils import ensure_string
  8. PandocOption = namedtuple('PandocOption', ['names', 'arg', 'optional_arg'])
  9. class PandocInfo(object):
  10. def __init__(self, pandoc='pandoc'):
  11. if type(pandoc) == bytes:
  12. pandoc = pandoc.decode()
  13. self.pandoc = pandoc
  14. self.update()
  15. def __raw_output(self, cmd, pattern=None):
  16. data = ensure_string(Popen([self.pandoc, cmd], stdout=PIPE).communicate()[0])
  17. if pattern:
  18. return re.search(pattern, data, re.DOTALL)
  19. else:
  20. return data
  21. def update(self):
  22. self.version = self.get_version()
  23. self.options = self.get_options()
  24. self.extensions = self.get_extensions()
  25. self.input_formats = self.get_input_formats()
  26. self.output_formats = self.get_output_formats()
  27. def get_version(self):
  28. versionPattern = 'pandoc(\.exe)? (?P<version>\d+\.\d+)'
  29. return self.__raw_output('--version', pattern=versionPattern).group('version')
  30. def get_options(self):
  31. # first line describes pandoc usage
  32. data = self.__raw_output('--help').splitlines()[1:]
  33. data = [l.strip() for l in data]
  34. # options from --trace onwards are not meaningful for us
  35. try:
  36. cutoff = data.index('--trace')
  37. except ValueError:
  38. cutoff = -1
  39. data = data[:cutoff]
  40. options = []
  41. for line in data:
  42. # TODO: simplify if possible
  43. if re.search(',', line): # multiple variant options
  44. if re.search('(?<![a-z])(?<!-)-(?!-)', line):
  45. if re.search('\[', line):
  46. optional = True
  47. else:
  48. optional = False
  49. opts = re.findall("-+([a-zA-Z-]+)[\[ =]", line)
  50. if opts:
  51. options.append(PandocOption(opts, True, optional))
  52. else:
  53. opts = re.findall('--([a-z-]+)', line)
  54. if opts:
  55. options.append(PandocOption(opts, False, False))
  56. else:
  57. if re.search('=', line): # take arguments
  58. if re.search('\[=', line): # arguments are optional
  59. optional = re.findall('--([a-z-]+)\[=', line)
  60. if optional:
  61. options.append(PandocOption(optional, True, True))
  62. else:
  63. optarg_opts = re.findall('-+([a-zA-Z-]+)[ =][A-Za-z]+', line)
  64. if optarg_opts:
  65. options.append(PandocOption(optarg_opts, True, False))
  66. else: # flags
  67. flag_opts = re.findall('-+([a-z-]+(?![=]))', line)
  68. if flag_opts:
  69. options.append(PandocOption(flag_opts, False, False))
  70. return options
  71. def get_options_list(self):
  72. return list(chain.from_iterable([v.names for v in self.options]))
  73. def get_extensions(self):
  74. data = self.__raw_output('--list-extensions')
  75. data = re.sub('(^ |^)[\+-]', '', data, flags=re.MULTILINE)
  76. return data.splitlines()
  77. def get_input_formats(self):
  78. data = self.__raw_output('--list-input-formats')
  79. return data.splitlines()
  80. def get_output_formats(self):
  81. data = self.__raw_output('--list-output-formats')
  82. return data.splitlines()
  83. def is_valid_output_format(self, identifier):
  84. if not identifier.startswith("markdown") and identifier in self.output_formats:
  85. return True
  86. elif identifier.startswith("markdown"):
  87. return re.match(identifier+"(([+-]("+"|".join(self.extensions)+"))+)?$", identifier)
  88. def build_argument_parser(self):
  89. def wrap_flag(flag):
  90. if len(flag) == 1:
  91. return "-" + flag
  92. else:
  93. return "--" + flag
  94. parser = argparse.ArgumentParser()
  95. parser.add_argument('output_format')
  96. for opt in self.options:
  97. flags = [wrap_flag(f) for f in opt.names]
  98. extra = {}
  99. extra['action'] = 'store_true' if not opt.arg else 'store'
  100. # some options can be given several times
  101. repeatable_options = ['bibliography', 'css', 'defaults', 'metadata-file', 'syntax-definition']
  102. if any(map(lambda x: x.isupper() and x != 'T' or x in repeatable_options, opt.names)):
  103. extra['action'] = 'append'
  104. if opt.arg:
  105. extra['nargs'] = '?' if opt.optional_arg else 1
  106. parser.add_argument(*flags, **extra)
  107. return parser