-
Notifications
You must be signed in to change notification settings - Fork 51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added output format options for --help #180
Changes from 9 commits
c907b67
061649b
5ec0d20
799b0d7
3bcb2bb
e601d57
2b9b9ec
9869871
11d5841
e164783
47f856a
8a13d7d
774667e
2671948
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# # | ||
# Copyright 2015-2015 Ghent University | ||
# | ||
# This file is part of vsc-base, | ||
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), | ||
# with support of Ghent University (http://ugent.be/hpc), | ||
# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), | ||
# the Hercules foundation (http://www.herculesstichting.be/in_English) | ||
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). | ||
# | ||
# http://github.com/hpcugent/vsc-base | ||
# | ||
# vsc-base is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU Library General Public License as | ||
# published by the Free Software Foundation, either version 2 of | ||
# the License, or (at your option) any later version. | ||
# | ||
# vsc-base is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU Library General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU Library General Public License | ||
# along with vsc-base. If not, see <http://www.gnu.org/licenses/>. | ||
# # | ||
""" | ||
Functions for generating rst documentation | ||
|
||
@author: Caroline De Brouwer (Ghent University) | ||
""" | ||
|
||
INDENT_4SPACES = ' ' * 4 | ||
|
||
|
||
class LengthNotEqualException(ValueError): | ||
pass | ||
|
||
|
||
def mk_rst_table(titles, columns): | ||
""" | ||
Returns an rst table with given titles and columns (a nested list of string columns for each column) | ||
""" | ||
title_cnt, col_cnt = len(titles), len(columns) | ||
if title_cnt != col_cnt: | ||
msg = "Number of titles/columns should be equal, found %d titles and %d columns" % (title_cnt, col_cnt) | ||
raise LengthNotEqualException, msg | ||
table = [] | ||
col_widths = [] | ||
tmpl = [] | ||
line= [] | ||
|
||
# figure out column widths | ||
for i, title in enumerate(titles): | ||
width = max(map(len, columns[i] + [title])) | ||
|
||
# make line template | ||
tmpl.append('{%s:{c}<%s}' % (i, width)) | ||
|
||
line = [''] * col_cnt | ||
line_tmpl = INDENT_4SPACES.join(tmpl) | ||
table_line = line_tmpl.format(*line, c='=') | ||
|
||
table.append(table_line) | ||
table.append(line_tmpl.format(*titles, c=' ')) | ||
table.append(table_line) | ||
|
||
for row in map(list, zip(*columns)): | ||
table.append(line_tmpl.format(*row, c=' ')) | ||
|
||
table.extend([table_line, '']) | ||
|
||
return table |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
# | ||
# | ||
# # | ||
# Copyright 2011-2014 Ghent University | ||
# | ||
# This file is part of vsc-base, | ||
|
@@ -46,11 +45,15 @@ | |
from optparse import SUPPRESS_HELP as nohelp # supported in optparse of python v2.4 | ||
from optparse import _ as _gettext # this is gettext normally | ||
from vsc.utils.dateandtime import date_parser, datetime_parser | ||
from vsc.utils.docs import mk_rst_table | ||
from vsc.utils.fancylogger import getLogger, setLogLevel, getDetailsLogLevels | ||
from vsc.utils.missing import shell_quote, nub | ||
from vsc.utils.optcomplete import autocomplete, CompleterOption | ||
|
||
|
||
HELP_OUTPUTOPTIONS = ['', 'rst', 'short', 'config',] | ||
|
||
|
||
def set_columns(cols=None): | ||
"""Set os.environ COLUMNS variable | ||
- only if it is not set already | ||
|
@@ -162,9 +165,9 @@ class ExtOption(CompleterOption): | |
DISABLE = 'disable' # inverse action | ||
|
||
EXTOPTION_EXTRA_OPTIONS = ('date', 'datetime', 'regex', 'add', 'add_first', 'add_flex',) | ||
EXTOPTION_STORE_OR = ('store_or_None',) # callback type | ||
EXTOPTION_STORE_OR = ('store_or_None', 'help') # callback type | ||
EXTOPTION_LOG = ('store_debuglog', 'store_infolog', 'store_warninglog',) | ||
EXTOPTION_HELP = ('shorthelp', 'confighelp',) | ||
EXTOPTION_HELP = ('shorthelp', 'confighelp', 'help') | ||
|
||
ACTIONS = Option.ACTIONS + EXTOPTION_EXTRA_OPTIONS + EXTOPTION_STORE_OR + EXTOPTION_LOG + EXTOPTION_HELP | ||
STORE_ACTIONS = Option.STORE_ACTIONS + EXTOPTION_EXTRA_OPTIONS + EXTOPTION_LOG + ('store_or_None',) | ||
|
@@ -211,9 +214,10 @@ def store_or(option, opt_str, value, parser, *args, **kwargs): | |
self.callback = store_or | ||
self.callback_kwargs = { | ||
'orig_default': copy.deepcopy(self.default), | ||
} | ||
} | ||
self.action = 'callback' # act as callback | ||
if self.store_or == 'store_or_None': | ||
|
||
if self.store_or in self.EXTOPTION_STORE_OR: | ||
self.default = None | ||
else: | ||
self.log.raiseException("_set_attrs: unknown store_or %s" % self.store_or, exception=ValueError) | ||
|
@@ -222,7 +226,16 @@ def take_action(self, action, dest, opt, value, values, parser): | |
"""Extended take_action""" | ||
orig_action = action # keep copy | ||
|
||
if action == 'shorthelp': | ||
# dest is None for actions like shorthelp and confighelp | ||
if dest and getattr(parser._long_opt.get('--' + dest, ''), 'store_or', '') == 'help': | ||
Option.take_action(self, action, dest, opt, value, values, parser) | ||
fn = getattr(parser, 'print_%shelp' % values.help) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs a default or check that the method/attribute exists There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. checking can be done with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs a default like fn = getattr(parser, 'print_%shelp' % values.help, None)
if fn is None:
... some error
else:
fn()
parser.exit() There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Caylo: you forgot the |
||
if fn is None: | ||
self.log.raiseException("Unknown option for help: %s" % value.help, exception=ValueError) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/Unknown option/Unsupported output format/ |
||
else: | ||
fn() | ||
parser.exit() | ||
elif action == 'shorthelp': | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. open an issue to convert There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. see #181 |
||
parser.print_shorthelp() | ||
parser.exit() | ||
elif action == 'confighelp': | ||
|
@@ -567,8 +580,7 @@ def print_shorthelp(self, fh=None): | |
|
||
self.print_help(fh) | ||
|
||
def print_help(self, fh=None): | ||
"""Intercept print to file to print to string and remove the ENABLE/DISABLE options from help""" | ||
def check_help(self, fh): | ||
if self.help_to_string: | ||
self.help_to_file = StringIO.StringIO() | ||
if fh is None: | ||
|
@@ -583,9 +595,53 @@ def _is_enable_disable(x): | |
for opt in self._get_all_options(): | ||
# remove all long_opts with ENABLE/DISABLE naming | ||
opt._long_opts = [x for x in opt._long_opts if not _is_enable_disable(x)] | ||
return fh | ||
|
||
def print_help(self, fh=None): | ||
"""Intercept print to file to print to string and remove the ENABLE/DISABLE options from help""" | ||
fh = self.check_help(fh) | ||
OptionParser.print_help(self, fh) | ||
|
||
def print_rsthelp(self, fh=None): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just drop the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nvm |
||
""" Print help in rst format """ | ||
fh = self.check_help(fh) | ||
result = [] | ||
if self.usage: | ||
result.append("Usage: ``%s``" % self.get_usage().replace("Usage: ", '').strip()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. dedicated |
||
result.append('') | ||
if self.description: | ||
result.append(self.description) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. description section? |
||
result.append('') | ||
|
||
result.append(self.format_option_rsthelp()) | ||
|
||
rsthelptxt = '\n'.join(result) | ||
if fh is None: | ||
fh = sys.stdout | ||
fh.write(rsthelptxt) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nvm |
||
|
||
def format_option_rsthelp(self, formatter=None): | ||
""" Formatting for help in rst format """ | ||
if not formatter: | ||
formatter = self.formatter | ||
formatter.store_option_strings(self) | ||
|
||
res = [] | ||
titles = ["Option", "Help"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. replace with titles = ["Option flag", "Option description"] |
||
|
||
all_opts = [("Options", self.option_list)] + [(group.title, group.option_list) for group in self.option_groups] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. self.option_list only contains |
||
for title, opts in all_opts: | ||
values = [] | ||
res.extend([title, '-' * len(title)]) | ||
for opt in opts: | ||
if not opt.help is nohelp: | ||
values.append(['``%s``' % formatter.option_strings[opt], formatter.expand_default(opt)]) | ||
|
||
res.extend(mk_rst_table(titles, map(list, zip(*values)))) | ||
res.append('') | ||
|
||
return '\n'.join(res) | ||
|
||
def print_confighelp(self, fh=None): | ||
"""Print help as a configfile.""" | ||
|
||
|
@@ -634,6 +690,9 @@ def _add_help_option(self): | |
self.add_option("-%s" % self.longhelp[0], | ||
self.longhelp[1], # *self.longhelp[1:], syntax error in Python 2.4 | ||
action="help", | ||
type="choice", | ||
choices=HELP_OUTPUTOPTIONS, | ||
default='', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this also needs a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this needed to become a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add a choice with possible suppoted help formats There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add this: metavar='OUTPUT_FORMAT', |
||
help=_gettext("show full help message and exit")) | ||
self.add_option("--confighelp", | ||
action="confighelp", | ||
|
@@ -792,6 +851,7 @@ def __init__(self, **kwargs): | |
'usage': kwargs.get('usage', self.USAGE), | ||
'version': self.VERSION, | ||
}) | ||
|
||
self.parser = self.PARSER(**kwargs) | ||
self.parser.allow_interspersed_args = self.INTERSPERSED | ||
|
||
|
@@ -1423,6 +1483,10 @@ def generate_cmd_line(self, ignore=None, add_default=None): | |
opt_dests.sort() | ||
|
||
for opt_dest in opt_dests: | ||
# help is store_or_None, but is not a processed option, so skip it | ||
if opt_dest in ExtOption.EXTOPTION_HELP: | ||
continue | ||
|
||
opt_value = self.options.__dict__[opt_dest] | ||
# this is the action as parsed by the class, not the actual action set in option | ||
# (eg action store_or_None is shown here as store_or_None, not as callback) | ||
|
@@ -1453,7 +1517,7 @@ def generate_cmd_line(self, ignore=None, add_default=None): | |
(opt_name, opt_value)) | ||
continue | ||
|
||
if action in ('store_or_None',): | ||
if action in ExtOption.EXTOPTION_STORE_OR: | ||
if opt_value == default: | ||
self.log.debug("generate_cmd_line %s adding %s (value is default value %s)" % | ||
(action, opt_name, opt_value)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
## | ||
# Copyright 2015-2015 Ghent University | ||
# | ||
# This file is part of vsc-base, | ||
# originally created by the HPC team of Ghent University (http://ugent.be/hpc/en), | ||
# with support of Ghent University (http://ugent.be/hpc), | ||
# the Flemish Supercomputer Centre (VSC) (https://vscentrum.be/nl/en), | ||
# the Hercules foundation (http://www.herculesstichting.be/in_English) | ||
# and the Department of Economy, Science and Innovation (EWI) (http://www.ewi-vlaanderen.be/en). | ||
# | ||
# http://github.com/hpcugent/vsc-base | ||
# | ||
# vsc-base is free software: you can redistribute it and/or modify | ||
# it under the terms of the GNU General Public License as published by | ||
# the Free Software Foundation v2. | ||
# | ||
# vsc-base is distributed in the hope that it will be useful, | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
# GNU General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU General Public License | ||
# along with EasyBuild. If not, see <http://www.gnu.org/licenses/>. | ||
## | ||
""" | ||
Unit tests for the docs module. | ||
|
||
@author: Kenneth Hoste (Ghent University) | ||
@author: Caroline De Brouwer (Ghent University) | ||
""" | ||
import os | ||
from unittest import TestLoader, TestCase, main | ||
from vsc.utils.testing import EnhancedTestCase | ||
|
||
from vsc.utils.docs import mk_rst_table | ||
|
||
|
||
class DocsTest(EnhancedTestCase): | ||
"""Tests for docs functions.""" | ||
|
||
def test_mk_rst_table(self): | ||
"""Test mk_rst_table function.""" | ||
entries = [['one', 'two', 'three']] | ||
t = 'This title is longer than the entries in the column' | ||
titles = [t] | ||
|
||
# small table | ||
table = mk_rst_table(titles, entries) | ||
check = [ | ||
'=' * len(t), | ||
t, | ||
'=' * len(t), | ||
'one' + ' ' * (len(t) - 3), | ||
'two' + ' ' * (len(t) -3), | ||
'three' + ' ' * (len(t) - 5), | ||
'=' * len(t), | ||
'', | ||
] | ||
|
||
self.assertEqual(table, check) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there a rst parser / validation tool for python? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://pypi.python.org/pypi/docutils probably has, but that may be overkill here? a hard check is fine here imho There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok for now. but good to know we could use such a tool in more complex tests if ever needed |
||
def suite(): | ||
""" returns all the testcases in this module """ | ||
return TestLoader().loadTestsFromTestCase(DocsTest) | ||
|
||
if __name__ == '__main__': | ||
main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename + drop trailing
,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or better:
HELP_OUTPUT_FORMATS