argparse_help.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. # -*- coding: utf-8 -*-
  2. # manpage/argparse_help.py
  3. # Part of ‘manpage’, a Python library for making Unix manual documents.
  4. #
  5. # Copyright © 2016 Ben Finney <ben+python@benfinney.id.au>
  6. #
  7. # This is free software: see the grant of license at end of this file.
  8. """ ArgumentParser help integration to build Unix “man page” documents. """
  9. import argparse
  10. import textwrap
  11. from . import document
  12. class CommandManPageMaker(document.ManPageMaker):
  13. """ Maker for a command manual page document.
  14. Data attributes:
  15. * `metadata`: A `document.MetaData` instance specifying the
  16. document metadata for the manual page document.
  17. * `parser`: The `argparse.ArgumentParser` instance for the
  18. command to be documented.
  19. * `seealso`: A collection of `document.Reference` instances.
  20. If not ``None``, this is used to populate the “SEE ALSO”
  21. section of the document.
  22. """
  23. document_class = document.CommandDocument
  24. def __init__(self, metadata):
  25. super().__init__(metadata)
  26. self.parser = None
  27. def set_parser(self, parser):
  28. """ Set the parser to use for generating help.
  29. :param parser: The instance of `argparse.ArgumentParser`
  30. from which to derive help text for the command.
  31. :return: None.
  32. """
  33. parser.formatter_class = ManPageHelpFormatter
  34. parser.prog = self.metadata.name
  35. self.parser = parser
  36. def make_manpage(self):
  37. """ Make a manual page document from the known metadata. """
  38. manpage = super().make_manpage()
  39. manpage.content_sections.update({
  40. "OPTIONS": self.make_options_section(),
  41. })
  42. return manpage
  43. def make_synopsis_section(self, text=None):
  44. """ Make the “SYNOPSIS” section of the document. """
  45. if text is None:
  46. text = " ".join(
  47. line.strip()
  48. for line in self.parser.format_usage().splitlines())
  49. section = super().make_synopsis_section(text)
  50. return section
  51. def make_description_section(self, text=None):
  52. """ Make the “DESCRIPTION” section of the document. """
  53. if text is None:
  54. text = self.parser.description
  55. section = super().make_description_section(text)
  56. return section
  57. def make_options_section(self):
  58. """ Make the “OPTIONS” section of the document. """
  59. section = document.DocumentSection("OPTIONS")
  60. options_markup = self.parser.formatter_class.format_options(
  61. self.parser)
  62. section.body = options_markup
  63. return section
  64. def make_distribution_section(self, distribution):
  65. """ Make the “DISTRIBUTION” section of the document. """
  66. section = document.DocumentSection("DISTRIBUTION")
  67. name_markup = document.GroffMarkup.escapetext(
  68. self.metadata.name, hyphen=document.GroffMarkup.glyph.minus)
  69. distribution_markup = document.GroffMarkup.escapetext(
  70. distribution.get_name())
  71. homepage = distribution.get_url()
  72. homepage_markup = document.GroffMarkup.escapetext(homepage)
  73. section.body = textwrap.dedent("""\
  74. The command
  75. {macro.bold} {name}
  76. is part of the Python distribution
  77. {glyph.dquote_left}{dist}{glyph.dquote_right}.
  78. The home page for {dist} is at
  79. {macro.url_begin} {homepage}
  80. {macro.url_end} .
  81. """).format(
  82. macro=document.GroffMarkup.macro,
  83. glyph=document.GroffMarkup.glyph,
  84. name=name_markup,
  85. dist=distribution_markup, homepage=homepage_markup)
  86. return section
  87. class ManPageHelpFormatter(argparse.RawTextHelpFormatter):
  88. """ ArgumentParser help formatter to generate a Unix manual page. """
  89. def __init__(
  90. self, prog,
  91. indent_increment=2,
  92. max_help_position=8,
  93. width=float("inf"),
  94. ):
  95. prog_markup = self._format_literal_text(prog)
  96. super().__init__(
  97. prog_markup, indent_increment, max_help_position, width)
  98. def _indent(self):
  99. pass
  100. def _dedent(self):
  101. pass
  102. def _split_lines(self, text, width):
  103. lines = [text]
  104. return lines
  105. @staticmethod
  106. def _format_literal_text(text):
  107. """ Convert `text` to Groff markup for literal command text. """
  108. text_markup = document.GroffMarkup.escapetext(
  109. text, hyphen=document.GroffMarkup.glyph.minus)
  110. result = "{font.bold}{text}{font.previous}".format(
  111. font=document.GroffMarkup.font, text=text_markup)
  112. return result
  113. @staticmethod
  114. def _format_variable_text(text):
  115. """ Convert `text` to Groff markup for variable parameter text. """
  116. text_markup = document.GroffMarkup.escapetext(
  117. text, hyphen=document.GroffMarkup.glyph.minus)
  118. result = "{font.italic}{text}{font.previous}".format(
  119. font=document.GroffMarkup.font, text=text_markup)
  120. return result
  121. @staticmethod
  122. def _format_option_text(text):
  123. """ Convert `text` to Groff markup for a command option. """
  124. result = document.GroffMarkup.escapetext(
  125. text, hyphen=document.GroffMarkup.glyph.minus)
  126. return result
  127. @staticmethod
  128. def format_options(parser):
  129. """ Format the detailed options help.
  130. :param parser: The `ArgumentParser` instance to document.
  131. :return: The text of the detailed options help.
  132. """
  133. formatter = parser._get_formatter()
  134. for action_group in parser._action_groups:
  135. formatter.start_section(action_group.title)
  136. formatter.add_arguments(action_group._group_actions)
  137. formatter.end_section()
  138. formatter.add_text(parser.epilog)
  139. return formatter.format_help()
  140. def start_section(self, heading):
  141. """ Start a new subsection of the detailed actions help.
  142. :param heading: Text of the section heading.
  143. :return: None.
  144. """
  145. heading_markup = textwrap.dedent("""\
  146. {control.empty}
  147. {macro.subsection} {title}""").format(
  148. control=document.GroffMarkup.control,
  149. macro=document.GroffMarkup.macro,
  150. title=heading.title())
  151. section = self._Section(self, self._current_section, heading_markup)
  152. self._add_item(section.format_help, [])
  153. self._current_section = section
  154. def _format_usage(self, usage, actions, groups, prefix):
  155. """ Make text for the usage message.
  156. :param usage: Text to use for the message. If not
  157. specified, construct the message from the remaining
  158. arguments.
  159. :param actions: Collection of `Action` instances to
  160. document.
  161. :param groups: Collection of `_ArgumentGroup` instances
  162. grouping the actions.
  163. :param prefix: Text to prefix the usage message.
  164. :return: Text of the usage message.
  165. """
  166. prefix = ""
  167. result = super()._format_usage(usage, actions, groups, prefix)
  168. return result
  169. @classmethod
  170. def _action_with_literal_text_markup(cls, action):
  171. """ Make a copy of `action`, with literal text formatted.
  172. :param action: The `Action` instance to document.
  173. :return: An `Action` instance copied from `action`, with
  174. literal text marked up for help output.
  175. """
  176. action_with_markup = argparse.Action(
  177. list(action.option_strings),
  178. action.dest,
  179. nargs=action.nargs,
  180. const=action.const,
  181. default=action.default,
  182. type=action.type,
  183. choices=action.choices,
  184. required=action.required,
  185. help=action.help,
  186. metavar=action.metavar)
  187. action_with_markup.option_strings = [
  188. cls._format_literal_text(item)
  189. for item in action.option_strings]
  190. if action.choices is not None:
  191. action_with_markup.choices = [
  192. cls._format_literal_text(item)
  193. for item in action.choices]
  194. return action_with_markup
  195. def _metavar_formatter(self, action, default_metavar):
  196. """ Provide a formatter function of the action meta-variable.
  197. :param action: The `Action` instance to document.
  198. :param default_metavar: Default “meta variable”
  199. (replaceable parameter) name in the arguments.
  200. :return: A function which takes a parameter `tuple_size`,
  201. a positive integer; and returns a value for formatting.
  202. The formatter function returns either the marked-up
  203. meta-variable text (if `tuple_size` is 1), or a tuple of
  204. `tuple_size` copies of the marked-up text.
  205. This method is used in the base `argparse.ArgumentParser`
  206. for some formatting operations.
  207. """
  208. action_with_markup = self._action_with_literal_text_markup(action)
  209. if action.metavar is not None:
  210. action_with_markup.metavar = self._format_variable_text(
  211. action.metavar)
  212. default_metavar_with_markup = self._format_variable_text(
  213. default_metavar)
  214. result = super()._metavar_formatter(
  215. action_with_markup, default_metavar_with_markup)
  216. return result
  217. def _format_actions_usage(self, actions, groups):
  218. """ Make text documenting `actions` in the usage message.
  219. :param actions: Collection of `Action` instances to
  220. document.
  221. :param groups: Collection of `_ArgumentGroup` instances
  222. grouping the actions.
  223. :return: Text documenting the `actions` for the usage
  224. message.
  225. """
  226. marked_up_actions = [
  227. self._action_with_literal_text_markup(action)
  228. for action in actions]
  229. result = super()._format_actions_usage(marked_up_actions, groups)
  230. return result
  231. def _format_action_invocation(self, action):
  232. """ Make text detailing the invocation of `action`.
  233. :param action: The `Action` instance to document.
  234. :return: The text documenting the invocation of `action`.
  235. """
  236. default_metavar = action.dest.upper()
  237. action_with_markup = self._action_with_literal_text_markup(action)
  238. args_markup = self._format_args(action_with_markup, default_metavar)
  239. parts = []
  240. if not action_with_markup.option_strings:
  241. # Action is a positional parameter.
  242. metavar_markup = self._metavar_formatter(
  243. action, default_metavar)(1)[0]
  244. parts.append(metavar_markup)
  245. else:
  246. # Action is a non-positional option.
  247. if action_with_markup.nargs == 0:
  248. option_template = "{option}"
  249. else:
  250. option_template = "{option} {args}"
  251. options_markup = ",\n".join(
  252. option_template.format(
  253. option=option_markup, args=args_markup)
  254. for option_markup in action_with_markup.option_strings)
  255. parts.append(options_markup)
  256. result = "\n" + self._join_parts(parts)
  257. return result
  258. # This is free software: you may copy, modify, and/or distribute this work
  259. # under the terms of the GNU General Public License as published by the
  260. # Free Software Foundation; version 3 of that license or any later version.
  261. #
  262. # No warranty expressed or implied. See the file ‘LICENSE.GPL-3’ for details,
  263. # or view it online at <URL:https://www.gnu.org/licenses/gpl-3.0.html>.
  264. # Local variables:
  265. # coding: utf-8
  266. # mode: python
  267. # End:
  268. # vim: fileencoding=utf-8 filetype=python :