diff options
author | Tzafrir Cohen <tzafrir@cohens.org.il> | 2018-02-27 19:37:27 +0200 |
---|---|---|
committer | Tzafrir Cohen <tzafrir@cohens.org.il> | 2018-02-27 19:47:07 +0200 |
commit | 4d16eec8207908d94df26511a8a617c8f7cf7234 (patch) | |
tree | f7496ce23c23c5de8ea460189b154bf667bf592c |
Initial commit
-rw-r--r-- | Makefile | 9 | ||||
-rwxr-xr-x | argparse-html.py | 186 |
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() |