summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTzafrir Cohen <tzafrir@cohens.org.il>2018-02-27 19:37:27 +0200
committerTzafrir Cohen <tzafrir@cohens.org.il>2018-02-27 19:47:07 +0200
commit4d16eec8207908d94df26511a8a617c8f7cf7234 (patch)
treef7496ce23c23c5de8ea460189b154bf667bf592c
Initial commit
-rw-r--r--Makefile9
-rwxr-xr-xargparse-html.py186
2 files changed, 195 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..53c08fa
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,9 @@
+script = argparse-html.py
+
+all: README README.html
+
+README: $(script)
+ python $(script) --help >$@
+
+README.html: $(script)
+ python $(script) --html-usage --html-usage-raw >$@
diff --git a/argparse-html.py b/argparse-html.py
new file mode 100755
index 0000000..53082a7
--- /dev/null
+++ b/argparse-html.py
@@ -0,0 +1,186 @@
+""" Argparse usage to HTML output
+
+A class and an example script that uses it
+
+Copyright 2018, Tzafrir Cohen <tzafrir@cohens.org.il>
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+"""
+
+from __future__ import print_function
+import argparse
+import re
+
+# Used by the example script and not by the class itself.
+import sys
+import time
+
+
+class HTMLFormatter(argparse.RawTextHelpFormatter):
+ """ Format argparse help as an HTML snippet.
+
+ Need to inherit from RawTextHelpFormatter rather than HelpFormatter
+ in order to keep description / epilog text split to lines (paragraps).
+ """
+ def _format_action(self, action):
+ """ Wrap an action line in a LI tag """
+ return "<li>{}</li>".format(
+ super(HTMLFormatter, self)._format_action(action))
+
+ def _format_usage(self, usage, actions, groups, prefix):
+ """ Wrap usage text in a P tag """
+ return "<p>{}</p>".format(
+ super(HTMLFormatter, self)._format_usage(usage, actions,
+ groups, prefix))
+
+ def _format_text(self, text):
+ """ Format a single section. Split to paragraphs """
+ text = super(HTMLFormatter, self)._format_text(text)
+ text = text.strip()
+ paragrapgs = re.split(r'^\s*$\n', text, flags=re.M)
+ html_pars = []
+ for p in paragrapgs:
+ if p in ['</ul>']:
+ html_pars.append(p)
+ else:
+ html_pars.append("<p>{}</p>".format(p))
+ return "\n".join(html_pars)
+
+ def start_section(self, heading):
+ """ A title and beginning of group UL.
+
+ FIXME: needs extra cleanup of the ':' after the <ul>.
+ """
+ text = "<p>{}</p>\n<ul>".format(heading)
+ super(HTMLFormatter, self).start_section(text)
+
+ def end_section(self):
+ """ Close list of items. FIXME: leaves an extra </ul> """
+ super(HTMLFormatter, self).end_section()
+ self.add_text('</ul>')
+
+
+def parse_cmd_line(args=sys.argv[1:]):
+ """ Parse command-line arguments and returns configuration """
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawTextHelpFormatter)
+ parser.description = """
+ This script demonstrates how to automatically convert argparse
+ output to HTML. See also the class HTMLFormatter.
+
+ It's a hack. It does not produce a very clean HTML (there's an
+ extra '</ul>, for instance). But it's good enough for me. An empty
+ (or spaces-only) line is considered as a paragraph separator.
+
+ I needed to produce HTML as a single line and thus this is the
+ default output. This is not part of the class and can be easily
+ changed in the wrapper function html_usage().
+
+ """
+
+ parser.epilog = """
+ Note that we use RawTextHelpFormatter as the parser, rather than
+ HelpFormatter itself. Otherwise splitting to paragarphs with empty
+ lines won't show up in the standard --help output.
+
+ If you have no problem with that, you can remove the formatter_class
+ parameter above.
+ """
+
+ parser.add_argument("-a", "--arg-a", action="store", type=float,
+ default=1.1,
+ help="Set value of A. Default: %(default)s.")
+ parser.add_argument("-b", "--param-b", action="store", type=int,
+ default=2,
+ help="Set value of B. Default: %(default)s.")
+ parser.add_argument("-v", "--verbose", action="store_true",
+ help="Be verbose")
+ opt_sh = parser.add_argument_group("For Interactive Use")
+ opt_sh.add_argument("-d", "--no-act", action="store_true",
+ help="Do nothing. Just print what is to be run")
+ opt_sh.add_argument("-c", "--config", action="store_true",
+ help="Do nothing. Just show configuration")
+ opt_sh.add_argument("--html-usage", action="store_true",
+ help="Do nothing. Format help as HTML (a single line)")
+ opt_sh.add_argument("--html-usage-raw", action="store_true",
+ help="If html-usage: don't squash HTML to a single line")
+ cfg = parser.parse_args(args)
+
+ if cfg.html_usage:
+ html_usage(parser, cfg)
+ sys.exit(0)
+
+ # example sanity test:
+ for key in ['arg_a', 'param_b']:
+ val = getattr(cfg, key)
+ if val >= 100 or val < 0:
+ msg = "E: Invalid value for {}: must be between 0 and 10".format(key)
+ parser.error(msg)
+
+ return cfg
+
+
+def html_usage(parser, cfg):
+ """ Print usage text as an HTML snippet.
+
+ If not html_usage_raw was not set in the configuation: produce a
+ single line.
+
+ Replacing the formatter class for an existing object is not
+ documented anywhere, but seems to work just fine.
+ """
+ parser.formatter_class = HTMLFormatter
+ help_text = parser.format_help()
+ help_text = re.sub('<ul>:', '<ul>', help_text)
+ if not cfg.html_usage_raw:
+ help_text = re.sub('\n', '', help_text)
+ help_text = re.sub(r'\s+', ' ', help_text)
+ print(help_text)
+
+
+def show_config(cfg):
+ """ Report configuration. Just demo code """
+ print("A: {}, B: {}".format(cfg.arg_a, cfg.param_b))
+
+
+def do_work(cfg):
+ """ The hard work. More demo code. """
+ if cfg.no_act:
+ print("Would do work for A: {}, B: {}".format(cfg.arg_a, cfg.param_b))
+ else:
+ if cfg.verbose:
+ print("Now working hard with A: {}, B: {}".format(cfg.arg_a,
+ cfg.param_b))
+ time.sleep(cfg.arg_a * cfg.param_b)
+ if cfg.verbose:
+ print("Work is done")
+
+
+def main():
+ """ Main function """
+ cfg = parse_cmd_line()
+ if cfg.status:
+ show_config(cfg)
+ else:
+ do_work(cfg)
+
+
+if __name__ == '__main__':
+ main()