Skip to content

Commit

Permalink
Merge pull request #180 from Caylo/rst-help
Browse files Browse the repository at this point in the history
Added output format options for --help
  • Loading branch information
boegel committed Aug 7, 2015
2 parents fe2593f + 2671948 commit eb9fe92
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 21 deletions.
72 changes: 72 additions & 0 deletions lib/vsc/utils/docs.py
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
87 changes: 77 additions & 10 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,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_OUTPUT_FORMATS = ['', 'rst', 'short', 'config']


def set_columns(cols=None):
"""Set os.environ COLUMNS variable
- only if it is not set already
Expand Down Expand Up @@ -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',)
Expand Down Expand Up @@ -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)
Expand All @@ -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, None)
if fn is None:
self.log.raiseException("Unsupported output format for help: %s" % value.help, exception=ValueError)
else:
fn()
parser.exit()
elif action == 'shorthelp':
parser.print_shorthelp()
parser.exit()
elif action == 'confighelp':
Expand Down Expand Up @@ -567,8 +580,8 @@ 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):
"""Checks filehandle for help functions"""
if self.help_to_string:
self.help_to_file = StringIO.StringIO()
if fh is None:
Expand All @@ -583,9 +596,54 @@ 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):
""" Print help in rst format """
fh = self.check_help(fh)
result = []
if self.usage:
title = "Usage"
result.extend([title, '-' * len(title), '', '``%s``' % self.get_usage().replace("Usage: ", '').strip(), ''])
if self.description:
title = "Description"
result.extend([title, '-' * len(title), '', self.description, ''])

result.append(self.format_option_rsthelp())

rsthelptxt = '\n'.join(result)
if fh is None:
fh = sys.stdout
fh.write(rsthelptxt)


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 flag", "Option description"]

all_opts = [("Help options", self.option_list)] + [(group.title, group.option_list) for group in self.option_groups]
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."""

Expand Down Expand Up @@ -634,6 +692,10 @@ 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_OUTPUT_FORMATS,
default=HELP_OUTPUT_FORMATS[0],
metavar='OUTPUT_FORMAT',
help=_gettext("show full help message and exit"))
self.add_option("--confighelp",
action="confighelp",
Expand Down Expand Up @@ -792,6 +854,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 +1486,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)
Expand Down Expand Up @@ -1453,7 +1520,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))
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def remove_bdist_rpm_source_file():

PACKAGE = {
'name': 'vsc-base',
'version': '2.2.3',
'version': '2.2.4',
'author': [sdw, jt, ag, kh],
'maintainer': [sdw, jt, ag, kh],
'packages': ['vsc', 'vsc.utils', 'vsc.install'],
Expand Down
67 changes: 67 additions & 0 deletions test/docs.py
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)

def suite():
""" returns all the testcases in this module """
return TestLoader().loadTestsFromTestCase(DocsTest)

if __name__ == '__main__':
main()
Loading

0 comments on commit eb9fe92

Please sign in to comment.