-
Notifications
You must be signed in to change notification settings - Fork 219
Generate docs for generic easyblocks (REVIEW) #1317
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
Changes from 25 commits
0dceb5d
8fbc168
7cfc6a2
31587c9
94425d2
34c2853
e96df9a
05bd752
c657025
cc8da01
09a9742
60f70a2
0299605
5baa702
1e35b42
09c21da
d05cd30
5062e0a
c9cf03e
bf49369
fc8557e
e9b0ff1
d452321
41d8468
de8b822
7a36365
16a08db
9204337
8a4ec13
a48c80b
b48bf8c
307ae84
a75787b
cb4bd0f
3c394ae
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 |
|---|---|---|
|
|
@@ -34,25 +34,29 @@ | |
| @author: Ward Poelmans (Ghent University) | ||
| """ | ||
| import copy | ||
| import inspect | ||
| import os | ||
|
|
||
| from easybuild.framework.easyconfig.default import DEFAULT_CONFIG, HIDDEN, sorted_categories | ||
| from easybuild.framework.easyconfig.easyconfig import get_easyblock_class | ||
| from easybuild.tools.ordereddict import OrderedDict | ||
| from easybuild.tools.utilities import quote_str | ||
| from easybuild.tools.utilities import quote_str, import_available_modules | ||
| from easybuild.tools.filetools import read_file | ||
|
Member
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. sort alphabetically with other |
||
|
|
||
|
Member
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. two blank lines here please |
||
|
|
||
| FORMAT_RST = 'rst' | ||
| FORMAT_TXT = 'txt' | ||
|
|
||
|
|
||
| def det_col_width(entries, title): | ||
|
Member
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. keep two blank lines between imports, constants, and top-level functions (so add one above here) |
||
| """Determine column width based on column title and list of entries.""" | ||
| return max(map(len, entries + [title])) | ||
|
|
||
|
|
||
| def avail_easyconfig_params_rst(title, grouped_params): | ||
| """ | ||
| Compose overview of available easyconfig parameters, in RST format. | ||
| """ | ||
| def det_col_width(entries, title): | ||
| """Determine column width based on column title and list of entries.""" | ||
| return max(map(len, entries + [title])) | ||
|
|
||
| # main title | ||
| lines = [ | ||
| title, | ||
|
|
@@ -65,29 +69,14 @@ def det_col_width(entries, title): | |
| lines.append("%s parameters" % grpname) | ||
| lines.extend(['-' * len(lines[-1]), '']) | ||
|
|
||
| name_title = "**Parameter name**" | ||
| descr_title = "**Description**" | ||
| dflt_title = "**Default value**" | ||
|
|
||
| # figure out column widths | ||
| nw = det_col_width(grouped_params[grpname].keys(), name_title) + 4 # +4 for raw format ("``foo``") | ||
| dw = det_col_width([x[0] for x in grouped_params[grpname].values()], descr_title) | ||
| dfw = det_col_width([str(quote_str(x[1])) for x in grouped_params[grpname].values()], dflt_title) | ||
| titles = ["**Parameter name**", "**Description**", "**Default value**"] | ||
| values = [ | ||
| ['``' + name + '``' for name in grouped_params[grpname].keys()], # parameter name | ||
| [x[0] for x in grouped_params[grpname].values()], # description | ||
| [str(quote_str(x[1])) for x in grouped_params[grpname].values()] #default value | ||
|
Member
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. space after |
||
| ] | ||
|
|
||
| # 3 columns (name, description, default value), left-aligned, {c} is fill char | ||
| line_tmpl = "{0:{c}<%s} {1:{c}<%s} {2:{c}<%s}" % (nw, dw, dfw) | ||
| table_line = line_tmpl.format('', '', '', c='=', nw=nw, dw=dw, dfw=dfw) | ||
|
|
||
| # table header | ||
| lines.append(table_line) | ||
| lines.append(line_tmpl.format(name_title, descr_title, dflt_title, c=' ')) | ||
| lines.append(line_tmpl.format('', '', '', c='-')) | ||
|
|
||
| # table rows by parameter | ||
| for name, (descr, dflt) in sorted(grouped_params[grpname].items()): | ||
| rawname = '``%s``' % name | ||
| lines.append(line_tmpl.format(rawname, descr, str(quote_str(dflt)), c=' ')) | ||
| lines.append(table_line) | ||
| lines.extend(mk_rst_table(titles, values)) | ||
| lines.append('') | ||
|
|
||
| return '\n'.join(lines) | ||
|
|
@@ -160,3 +149,139 @@ def avail_easyconfig_params(easyblock, output_format): | |
| FORMAT_TXT: avail_easyconfig_params_txt, | ||
| } | ||
| return avail_easyconfig_params_functions[output_format](title, grouped_params) | ||
|
|
||
|
Member
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. two blank lines between top-level functions |
||
|
|
||
| def gen_easyblocks_overview_rst(package_name, path_to_examples, common_params={}, doc_functions=[]): | ||
| """ | ||
| Compose overview of all easyblocks in the given package in rst format | ||
| """ | ||
| modules = import_available_modules(package_name) | ||
| docs = [] | ||
| all_blocks = [] | ||
|
|
||
| # get all blocks | ||
| for m in modules: | ||
|
Member
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. no single-letter variable names outside of list comprehensions, use |
||
| for name,obj in inspect.getmembers(m, inspect.isclass): | ||
| eb_class = getattr(m, name) | ||
| # skip imported classes that are not easyblocks | ||
| if eb_class.__module__.startswith(package_name) and eb_class not in all_blocks: | ||
| all_blocks.append(eb_class) | ||
|
|
||
| for eb_class in all_blocks: | ||
|
Member
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. sort |
||
| docs.append(gen_easyblock_doc_section_rst(eb_class, path_to_examples, common_params, doc_functions, all_blocks)) | ||
|
|
||
| title = 'Overview of generic easyblocks' | ||
|
|
||
| heading = ['=' * len(title), title, '=' * len(title), '', '.. contents::', ' :depth: 2', ''] | ||
|
Member
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. define this list over multiple lines, to make the indent clear: heading = [
'=' * len(title),
title,
'=' * len(title),
'',
'.. contents::',
' :depth: 2',
'',
]
Member
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. maybe also use The title in the contents block looks weird, I think
Member
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. |
||
| return heading + sorted(docs) | ||
|
Member
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. don't sort |
||
|
|
||
|
Member
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. insert extra blank line |
||
| def gen_easyblock_doc_section_rst(eb_class, path_to_examples, common_params, doc_functions, all_blocks): | ||
| """ | ||
| Compose overview of one easyblock given class object of the easyblock in rst format | ||
|
Member
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. mention rst |
||
| """ | ||
| classname = eb_class.__name__ | ||
|
|
||
| lines = [ | ||
| '.. _' + classname + ':', | ||
| '', | ||
| '``' + classname + '``', | ||
| '=' * (len(classname)+4), | ||
| '', | ||
| ] | ||
|
|
||
| bases = [] | ||
| for b in eb_class.__bases__: | ||
| base = ':ref:`' + b.__name__ +'`' if b in all_blocks else b.__name__ | ||
| bases.append(base) | ||
|
|
||
| derived = '(derives from ' + ', '.join(bases) + ')' | ||
| lines.extend([derived, '']) | ||
|
|
||
| # Description (docstring) | ||
| lines.extend([eb_class.__doc__.strip(), '']) | ||
|
|
||
| # Add extra options, if any | ||
| if eb_class.extra_options(): | ||
| extra_parameters = 'Extra easyconfig parameters specific to ``' + classname + '`` easyblock' | ||
| lines.extend([extra_parameters, '-' * len(extra_parameters), '']) | ||
| ex_opt = eb_class.extra_options() | ||
|
|
||
| titles = ['easyconfig parameter', 'description', 'default value'] | ||
| values = [ | ||
| ['``' + key + '``' for key in ex_opt], # parameter name | ||
| [val[1] for val in ex_opt.values()], # description | ||
| ['``' + str(quote_str(val[0])) + '``' for val in ex_opt.values()] # default value | ||
| ] | ||
|
|
||
| lines.extend(mk_rst_table(titles, values)) | ||
|
|
||
| # Add commonly used parameters | ||
| if classname in common_params: | ||
| commonly_used = 'Commonly used easyconfig parameters with ``' + classname + '`` easyblock' | ||
| lines.extend([commonly_used, '-' * len(commonly_used)]) | ||
|
|
||
| for opt in common_params[classname]: | ||
|
Member
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. use |
||
| param = '* ``' + opt + '`` - ' + DEFAULT_CONFIG[opt][1] | ||
| lines.append(param) | ||
| lines.append('') | ||
|
|
||
| # Add docstring for custom steps | ||
| custom = [] | ||
| inh = '' | ||
| for func in doc_functions: | ||
| if func in eb_class.__dict__: | ||
| f = eb_class.__dict__[func] | ||
| elif func in eb_class.__bases__[0].__dict__: | ||
| f = eb_class.__bases__[0].__dict__[func] | ||
| inh = ' (inherited)' | ||
|
Member
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. hmm, not sure about this, my preference would be to not included these inherited methods and only mention the ones custom to the easyblock being documented in this section |
||
|
|
||
| if f.__doc__: | ||
| custom.append('* ``' + func + '`` - ' + f.__doc__.strip() + inh) | ||
|
|
||
| if custom: | ||
| title = 'Customised steps in ``' + classname + '`` easyblock' | ||
| lines.extend([title, '-' * len(title)] + custom) | ||
| lines.append('') | ||
|
|
||
| # Add example if available | ||
| if os.path.exists(os.path.join(path_to_examples, '%s.eb' % classname)): | ||
| title = 'Example for ``' + classname + '`` easyblock' | ||
| lines.extend(['', title, '-' * len(title), '', '::', '']) | ||
| for line in read_file(os.path.join(path_to_examples, classname+'.eb')).split('\n'): | ||
|
Member
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. what if no example is available? this will make it crash hard, it should just skip the
Member
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. we maybe only include examples for some easyblocks, especially with non-generic ones
Contributor
Author
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. With
Member
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. ah, yes, missed that |
||
| lines.append(' ' + line.strip()) | ||
| lines.append('') # empty line after literal block | ||
|
|
||
| return '\n'.join(lines) | ||
|
|
||
|
|
||
| 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) | ||
| table = [] | ||
| col_widths = [] | ||
| tmpl = [] | ||
| line= [] | ||
|
|
||
| # figure out column widths | ||
| for i in range(0, num_col): | ||
| col_widths.append(det_col_width(values[i], titles[i])) | ||
|
|
||
| # make line template | ||
| tmpl.append('{' + str(i) + ':{c}<' + str(col_widths[i]) + '}') | ||
| line.append('') # needed for table line | ||
|
|
||
| line_tmpl = ' '.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 i in range(0, len(values[0])): | ||
| table.append(line_tmpl.format(*[v[i] for v in values], c=' ')) | ||
|
|
||
| table.extend([table_line, '']) | ||
|
|
||
| return table | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| # # | ||
| # Copyright 2012-2015 Ghent University | ||
| # | ||
| # This file is part of EasyBuild, | ||
| # 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/easybuild | ||
| # | ||
| # EasyBuild 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. | ||
| # | ||
| # EasyBuild 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 docs.py. | ||
| """ | ||
|
|
||
| from test.framework.utilities import EnhancedTestCase, init_config | ||
| from unittest import TestLoader, main | ||
| from easybuild.tools.docs import mk_rst_table | ||
|
|
||
| class DocsTest(EnhancedTestCase): | ||
|
|
||
| def test_rst_table(self): | ||
| """ Test mk_rst_table function """ | ||
| entries_1 = [['one', 'two', 'three']] | ||
|
Member
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. what's with all the |
||
| t1 = 'This title is long' | ||
|
Member
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. no it's not :P |
||
| titles_1 = [t1] | ||
|
|
||
| # small table | ||
| table_1 = '\n'.join(mk_rst_table(titles_1, entries_1)) | ||
| check_1 = '\n'.join([ | ||
| '=' * len(t1), | ||
| t1, | ||
| '=' * len(t1), | ||
| 'one' + ' ' * (len(t1) - 3), | ||
| 'two' + ' ' * (len(t1) -3), | ||
| 'three' + ' ' * (len(t1) - 5), | ||
| '=' * len(t1), | ||
| '', | ||
| ]) | ||
|
|
||
| self.assertEqual(table_1, check_1) | ||
|
|
||
|
|
||
| def suite(): | ||
| """ returns all test cases in this module """ | ||
| return TestLoader().loadTestsFromTestCase(DocsTest) | ||
|
|
||
| if __name__ == '__main__': | ||
| # also check the setUp for debug | ||
| # logToScreen(enable=True) | ||
| # setLogLevelDebug() | ||
| 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.
sort imported functions alphabetically