Skip to content
Merged
Changes from 14 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
0dceb5d
Begin script for generic easyblock documentation
Caylo Jul 6, 2015
8fbc168
Generic easyblock docs, continued
Caylo Jul 7, 2015
7cfc6a2
python star operator is gr8
Caylo Jul 7, 2015
31587c9
Separate function for making rst tables
Caylo Jul 7, 2015
94425d2
Merge branch 'generic-easyblock-docs-issue636' of github.com:Caylo/ea…
Caylo Jul 7, 2015
34c2853
Also use mk_rst_table in avail_easyconfig_params
Caylo Jul 7, 2015
e96df9a
added table of contents
Caylo Jul 7, 2015
05bd752
Fixed some remarks
Caylo Jul 8, 2015
c657025
Delete :q
Caylo Jul 8, 2015
cc8da01
added custom step functions to doc
Caylo Jul 8, 2015
09a9742
remove examples from framework
Caylo Jul 8, 2015
60f70a2
Merge branch 'generic-easyblock-docs-issue636' of github.com:Caylo/ea…
Caylo Jul 8, 2015
0299605
added internal references for parent classes
Caylo Jul 8, 2015
5baa702
added mark for inherited functions
Caylo Jul 8, 2015
1e35b42
added page title
Caylo Jul 8, 2015
09c21da
Small fixes
Caylo Jul 8, 2015
d05cd30
More fixes
Caylo Jul 9, 2015
5062e0a
Small rst_table unit test
Caylo Jul 9, 2015
c9cf03e
fix merge
Caylo Jul 9, 2015
bf49369
remove example files
Caylo Jul 9, 2015
fc8557e
Update on doc for generic easyblocks
Caylo Jul 10, 2015
e9b0ff1
Merge branch 'develop' into generic-easyblock-docs-issue636
boegel Jul 10, 2015
d452321
Merge pull request #3 from boegel/generic-easyblock-docs-issue636
Caylo Jul 10, 2015
41d8468
Merge branch 'develop' into generic-easyblock-docs-issue636
boegel Jul 13, 2015
de8b822
Merge pull request #4 from boegel/generic-easyblock-docs-issue636
Caylo Jul 13, 2015
7a36365
sorted imports
Caylo Jul 13, 2015
16a08db
Merge branch 'generic-easyblock-docs-issue636' of github.com:Caylo/ea…
Caylo Jul 13, 2015
9204337
Fixed remarks
Caylo Jul 13, 2015
8a4ec13
sort easyblocks
Caylo Jul 13, 2015
a48c80b
remove debug print
Caylo Jul 13, 2015
b48bf8c
forgot a ':'
Caylo Jul 13, 2015
307ae84
rst documentation for generic easyblocks unit test
Caylo Jul 14, 2015
a75787b
Merge branch 'generic-easyblock-docs-issue636' of github.com:Caylo/ea…
Caylo Jul 14, 2015
cb4bd0f
table of contents as list
Caylo Jul 15, 2015
3c394ae
remark above title
Caylo Jul 15, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 146 additions & 28 deletions easybuild/tools/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,25 +34,27 @@
@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
Copy link
Member

Choose a reason for hiding this comment

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

sort imported functions alphabetically

from easybuild.tools.filetools import read_file
Copy link
Member

Choose a reason for hiding this comment

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

sort alphabetically with other from ... import lines


Copy link
Member

Choose a reason for hiding this comment

The 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):
Copy link
Member

Choose a reason for hiding this comment

The 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,
Expand All @@ -65,29 +67,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)

# 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='-'))
titles = ["**Parameter name**", "**Description**", "**Default value**"]
values = [
['``' + name + '``' for name in grouped_params[grpname].keys()],
Copy link
Member

Choose a reason for hiding this comment

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

add comment at the end of this line: # parameter name (two spaces before # with same-line comments); similar below

[x[0] for x in grouped_params[grpname].values()],
[str(quote_str(x[1])) for x in grouped_params[grpname].values()]
]

# 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)
Expand Down Expand Up @@ -160,3 +147,134 @@ def avail_easyconfig_params(easyblock, output_format):
FORMAT_TXT: avail_easyconfig_params_txt,
}
return avail_easyconfig_params_functions[output_format](title, grouped_params)

Copy link
Member

Choose a reason for hiding this comment

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

two blank lines between top-level functions

def generic_easyblocks(path_to_examples, common_params={}, doc_functions=[]):
Copy link
Member

Choose a reason for hiding this comment

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

rename function name to make it clear this yields something in rst format

"""
Compose overview of all generic easyblocks
Copy link
Member

Choose a reason for hiding this comment

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

mention rst

"""
modules = import_available_modules('easybuild.easyblocks.generic')
Copy link
Member

Choose a reason for hiding this comment

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

the package name easybuild.easyblocks.generic should become a parameter of this function, so we can generate an overview of software-specific (i.e. non-generic) easyblocks too

maybe rename function to gen_easyblocks_overview_rst?

Copy link
Member

Choose a reason for hiding this comment

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

or, maybe just add a generic=True named argument, so you can include a matching title

docs = []
all_blocks = []

# get all blocks
for m in modules:
Copy link
Member

Choose a reason for hiding this comment

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

no single-letter variable names outside of list comprehensions, use mod or module (make sure module is not a keyword in Python)

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('easybuild.easyblocks.generic') and eb_class not in all_blocks:
all_blocks.append(eb_class)

for eb_class in all_blocks:
Copy link
Member

Choose a reason for hiding this comment

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

sort all_blocks here using sorted(all_blocks, key=...)

docs.append(doc_easyblock(eb_class, path_to_examples, common_params, doc_functions, all_blocks))

toc = ['.. contents:: Available generic easyblocks', ' :depth: 1', '']
Copy link
Member

Choose a reason for hiding this comment

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

drop the Available generic easyblocks, we should have a page title in place instead (above the .. contents::)


return toc + sorted(docs)

Copy link
Member

Choose a reason for hiding this comment

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

insert extra blank line

def doc_easyblock(eb_class, path_to_examples, common_params, doc_functions, all_blocks):
Copy link
Member

Choose a reason for hiding this comment

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

rename to gen_easyblock_doc_section_rst

"""
Compose overview of one easyblock given class object of the easyblock in rst format
Copy link
Member

Choose a reason for hiding this comment

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

mention rst

"""
classname = eb_class.__name__

lines = [
'``' + classname + '``',
'=' * (len(classname)+4),
'',
]

bases = []
for b in eb_class.__bases__:
base = b.__name__ + '_' if b in all_blocks else b.__name__
Copy link
Member

Choose a reason for hiding this comment

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

make these references to other sections, via:

.. section_label:
:ref:`section_label`

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],
[val[1] for val in ex_opt.values()],
['``' + str(quote_str(val[0])) + '``' for val in ex_opt.values()]
]

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]:
Copy link
Member

Choose a reason for hiding this comment

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

use mk_rst_table here too?

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)'
Copy link
Member

Choose a reason for hiding this comment

The 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'
Copy link
Member

Choose a reason for hiding this comment

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

add in <name> easyblock

lines.extend([title, '-' * len(title)] + custom)
lines.append('')

# Add example if available
if classname + '.eb' in os.listdir(os.path.join(path_to_examples)):
Copy link
Member

Choose a reason for hiding this comment

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

use os.path.exists(os.path.join(path_to_examples, '%s.eb' % classname)) instead?

lines.extend(['', 'Example', '-' * 8, '', '::', ''])
Copy link
Member

Choose a reason for hiding this comment

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

make section title Example for <name> easyblock

Copy link
Member

Choose a reason for hiding this comment

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

and don't hardcode 8, put the section title in a variable and use len

for line in read_file(os.path.join(path_to_examples, classname+'.eb')).split('\n'):
Copy link
Member

Choose a reason for hiding this comment

The 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 Example section instead?

Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

With if classname + '.eb' in os.listdir(os.path.join(path_to_examples)): it won't crash, or am i overlooking something? I tested it with only a few examples as well, it should work

Copy link
Member

Choose a reason for hiding this comment

The 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

Copy link
Member

Choose a reason for hiding this comment

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

drop emtpy line