Skip to content
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

Merged
merged 14 commits into from
Aug 7, 2015
66 changes: 66 additions & 0 deletions lib/vsc/utils/docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# #
# 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/>.
# #
"""
A class that has some support functions for generating rst documentation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not a class, just remove the whole A class that has some support part


@author: Caroline De Brouwer (Ghent University)
"""

def det_col_width(entries, title):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if this still makes sense, since it's only used in one place anymore?

"""Determine column width based on column title and list of entries."""
return max(map(len, entries + [title]))

def mk_rst_table(titles, values):
"""
Returns an rst table with given titles and values (a nested list of string values for each column)
"""
num_col = len(titles)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move it up and use it in the comparison

table = []
col_widths = []
tmpl = []
line= []

# figure out column widths
for i in range(0, num_col):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use enumrate

for i, title i enumerate(titles):

col_widths.append(det_col_width(values[i], titles[i]))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is len(values) >= len(titles)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe collapse them together into one variable named columns, where the first element/row specifies the row titles


# make line template
tmpl.append('{' + str(i) + ':{c}<' + str(col_widths[i]) + '}')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string templating, not +

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the col_widths[i] is the result of the det_col_width(values[i], titles[i]) in the line above, use temp variable to store it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or use col_widths[-1]?

line.append('') # needed for table line
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so at the end of this loop, line is

line = [''] * num_col


line_tmpl = ' '.join(tmpl)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make clear how many space the ' ' is like ' '*4; better eyt, make h eindentation a constant

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1, introduce something like INDENT_4SPACES

table_line = line_tmpl.format(*line, c="=")

table.append(table_line)
table.append(line_tmpl.format(*titles, c=' '))
table.append(table_line)

for i in range(0, len(values[0])):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enumerate

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, no enumerate, but if you can trasnpose values, this get less complicated

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transpose with

map(list, zip(*A))

table.append(line_tmpl.format(*[v[i] for v in values], c=' '))

table.extend([table_line, ''])

return table
86 changes: 75 additions & 11 deletions lib/vsc/utils/generaloption.py
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,
Expand Down Expand Up @@ -46,6 +45,7 @@
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
Expand Down Expand Up @@ -162,9 +162,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', 'formathelp') # callback type
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rename all formathelp to help

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

and/or this (not sure)?

EXTOPTION_STORE_OR = ('store_or_None', ) + EXTOPTION_HELP

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',)
Expand Down Expand Up @@ -211,9 +211,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 ('store_or_None', 'formathelp'):
self.default = None
else:
self.log.raiseException("_set_attrs: unknown store_or %s" % self.store_or, exception=ValueError)
Expand All @@ -222,7 +223,13 @@ 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', '') == 'formathelp':
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

magic string formathelp, add a constant for this (or for help)

Option.take_action(self, action, dest, opt, value, values, parser)
fn = getattr(parser, 'print_%shelp' % values.help)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs a default or check that the method/attribute exists

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checking can be done with hasattr

Copy link
Member

Choose a reason for hiding this comment

The 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()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Caylo: you forgot the , None bit on this line

fn()
parser.exit()
elif action == 'shorthelp':
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

open an issue to convert shorthelp and confighelp in aliases of resp help=short and help=config (it's not straightforward, so a bit out of the scope of this PR)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see #181

parser.print_shorthelp()
parser.exit()
elif action == 'confighelp':
Expand Down Expand Up @@ -567,8 +574,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:
Expand All @@ -583,9 +589,63 @@ 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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just drop the fh all together, we're not using it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm

""" Print help in rst format """
fh = self.check_help(fh)
result = []
title = "Easybuild help"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, has nothing to do with EB

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so make this an argument to this method, maybe with some sensible default

result.extend([title, "=" * len(title)])
if self.usage:
result.append("Usage: ``%s``" % self.get_usage().replace("Usage: ", '').strip())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dedicated Usage section?

result.append('')
if self.description:
result.append(self.description)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

description section?

result.append('')

result.append(self.format_option_rsthelp())

print "\n".join(result)

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 = []
opt_title = "Options"
res.extend([opt_title, '-' * len(opt_title)])
titles = ["Option", "Help"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace with

titles = ["Option flag", "Option description"]

values = [[],[]]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build the regular matrix, pass the transpose to mk_rst_table

for opt in self.option_list:
if not opt.help is nohelp:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why wouldn't the help be a valid documented option?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nohelp is SUPPRESS_HELP, it's not meant to be printed

values[0].append('``%s``' % formatter.option_strings[opt])
values[1].append(formatter.expand_default(opt))

res.extend(mk_rst_table(titles, values))
res.append('')

for group in self.option_groups:
res.extend([group.title, '-' * len(group.title)])
values[0] = []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

values = [[],[]]

values[1] = []
for opt in group.option_list:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks the same as above, make an (internal) function for it

if not opt.help is nohelp:
values[0].append('``%s``' % formatter.option_strings[opt])
values[1].append(formatter.expand_default(opt))

res.extend(mk_rst_table(titles, values))
res.append('')

return '\n'.join(res)

def print_confighelp(self, fh=None):
"""Print help as a configfile."""

Expand Down Expand Up @@ -633,7 +693,8 @@ def _add_help_option(self):
help=_gettext("show short help message and exit"))
self.add_option("-%s" % self.longhelp[0],
self.longhelp[1], # *self.longhelp[1:], syntax error in Python 2.4
action="help",
action="formathelp",
default='',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this also needs a choice to limit the supproted help formats (and unittests)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this needed to become a choice thingie?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a choice with possible suppoted help formats

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HELP_OUTPUT_FORMATS[0]?

Copy link
Member

Choose a reason for hiding this comment

The 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",
Expand Down Expand Up @@ -792,6 +853,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

Expand Down Expand Up @@ -1423,6 +1485,8 @@ 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)
Expand Down Expand Up @@ -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 ('store_or_None', 'formathelp'):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use EXTOPTION_STORE_OR (but in principle you can't reach here)

if opt_value == default:
self.log.debug("generate_cmd_line %s adding %s (value is default value %s)" %
(action, opt_name, opt_value))
Expand Down