Skip to content

Commit 84e8725

Browse files
committed
add support for MarkDown ('md') output format for --list-software
1 parent 19f2a1e commit 84e8725

File tree

2 files changed

+242
-100
lines changed

2 files changed

+242
-100
lines changed

easybuild/tools/docs.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,98 @@ def list_software(output_format=FORMAT_TXT, detailed=False, only_installed=False
634634
return generate_doc('list_software_%s' % output_format, [software, detailed])
635635

636636

637+
def list_software_md(software, detailed=True):
638+
"""
639+
Return overview of supported software in MarkDown format
640+
641+
:param software: software information (structured like list_software does)
642+
:param detailed: whether or not to return detailed information (incl. version, versionsuffix, toolchain info)
643+
:return: multi-line string presenting requested info
644+
"""
645+
646+
lines = [
647+
"# List of supported software",
648+
'',
649+
"EasyBuild supports %d different software packages (incl. toolchains, bundles):" % len(software),
650+
'',
651+
]
652+
653+
# links to per-letter tables
654+
letter_refs = ''
655+
key_letters = nub(sorted(k[0].lower() for k in software.keys()))
656+
letter_links = ' - '.join(['<a href="#' + x + '">' + x + '</a>' for x in ascii_lowercase if x in key_letters])
657+
lines.extend([letter_links, ''])
658+
659+
letter = None
660+
sorted_keys = sorted(software.keys(), key=lambda x: x.lower())
661+
for key in sorted_keys:
662+
663+
# start a new subsection for each letter
664+
if key[0].lower() != letter:
665+
666+
# subsection for new letter
667+
letter = key[0].lower()
668+
lines.extend([
669+
'',
670+
'<a anchor="%s"/>' % letter,
671+
"### *%s*" % letter.upper(),
672+
'',
673+
])
674+
675+
if detailed:
676+
# quick links per software package
677+
lines.extend([
678+
'',
679+
' - '.join('<a href="#%s">%s</a>' % (k.lower(), k) for k in sorted_keys if k[0].lower() == letter),
680+
'',
681+
])
682+
683+
# append software to list, including version(suffix) & toolchain info if detailed info is requested
684+
if detailed:
685+
table_titles = ['version', 'toolchain']
686+
table_values = [[], []]
687+
688+
# first determine unique pairs of version/versionsuffix
689+
# we can't use LooseVersion yet here, since nub uses set and LooseVersion instances are not hashable
690+
pairs = nub((x['version'], x['versionsuffix']) for x in software[key])
691+
692+
# check whether any non-empty versionsuffixes are in play
693+
with_vsuff = any(vs for (_, vs) in pairs)
694+
if with_vsuff:
695+
table_titles.insert(1, 'versionsuffix')
696+
table_values.insert(1, [])
697+
698+
# sort pairs by version (and then by versionsuffix);
699+
# we sort by LooseVersion to obtain chronological version ordering,
700+
# but we also need to retain original string version for filtering-by-version done below
701+
sorted_pairs = sort_looseversions((LooseVersion(v), vs, v) for v, vs in pairs)
702+
703+
for _, vsuff, ver in sorted_pairs:
704+
table_values[0].append('``%s``' % ver)
705+
if with_vsuff:
706+
if vsuff:
707+
table_values[1].append('``%s``' % vsuff)
708+
else:
709+
table_values[1].append('')
710+
tcs = [x['toolchain'] for x in software[key] if x['version'] == ver and x['versionsuffix'] == vsuff]
711+
table_values[-1].append(', '.join('``%s``' % tc for tc in sorted(nub(tcs))))
712+
713+
lines.extend([
714+
'',
715+
'<a anchor="%s"/>' % key.lower(),
716+
'### *%s*' % key,
717+
'',
718+
' '.join(software[key][-1]['description'].split('\n')).lstrip(' '),
719+
'',
720+
"*homepage*: %s" % software[key][-1]['homepage'],
721+
'',
722+
] + md_title_and_table(None, table_titles, table_values))
723+
else:
724+
lines.append("* %s" % key)
725+
726+
return '\n'.join(lines)
727+
728+
637729
def list_software_rst(software, detailed=False):
638730
"""
639731
Return overview of supported software in RST format

test/framework/docs.py

Lines changed: 150 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,149 @@
263263
- EB_toytoy (easybuild.easyblocks.toytoy @ %(topdir)s/t/toytoy.py)
264264
- Toy_Extension (easybuild.easyblocks.generic.toy_extension @ %(topdir)s/generic/toy_extension.py)"""
265265

266+
LIST_SOFTWARE_SIMPLE_TXT = """
267+
* GCC
268+
* gzip"""
269+
270+
GCC_DESCR = "The GNU Compiler Collection includes front ends for C, C++, Objective-C, Fortran, Java, and Ada, "
271+
GCC_DESCR += "as well as libraries for these languages (libstdc++, libgcj,...)."
272+
GZIP_DESCR = "gzip (GNU zip) is a popular data compression program as a replacement for compress"
273+
274+
LIST_SOFTWARE_DETAILED_TXT = """
275+
* GCC
276+
277+
%(gcc_descr)s
278+
279+
homepage: http://gcc.gnu.org/
280+
281+
* GCC v4.6.3: system
282+
283+
* gzip
284+
285+
%(gzip_descr)s
286+
287+
homepage: http://www.gzip.org/
288+
289+
* gzip v1.4: GCC/4.6.3, system
290+
* gzip v1.5: foss/2018a, intel/2018a
291+
""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}
292+
293+
LIST_SOFTWARE_SIMPLE_RST = """List of supported software
294+
==========================
295+
296+
EasyBuild |version| supports 2 different software packages (incl. toolchains, bundles):
297+
298+
:ref:`list_software_letter_g`
299+
300+
301+
.. _list_software_letter_g:
302+
303+
*G*
304+
---
305+
306+
* GCC
307+
* gzip"""
308+
309+
LIST_SOFTWARE_DETAILED_RST = """List of supported software
310+
==========================
311+
312+
EasyBuild |version| supports 2 different software packages (incl. toolchains, bundles):
313+
314+
:ref:`list_software_letter_g`
315+
316+
317+
.. _list_software_letter_g:
318+
319+
*G*
320+
---
321+
322+
323+
:ref:`list_software_GCC_205` - :ref:`list_software_gzip_442`
324+
325+
326+
.. _list_software_GCC_205:
327+
328+
*GCC*
329+
+++++
330+
331+
%(gcc_descr)s
332+
333+
*homepage*: http://gcc.gnu.org/
334+
335+
========= ==========
336+
version toolchain
337+
========= ==========
338+
``4.6.3`` ``system``
339+
========= ==========
340+
341+
342+
.. _list_software_gzip_442:
343+
344+
*gzip*
345+
++++++
346+
347+
%(gzip_descr)s
348+
349+
*homepage*: http://www.gzip.org/
350+
351+
======= ===============================
352+
version toolchain
353+
======= ===============================
354+
``1.4`` ``GCC/4.6.3``, ``system``
355+
``1.5`` ``foss/2018a``, ``intel/2018a``
356+
======= ===============================
357+
""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}
358+
359+
LIST_SOFTWARE_SIMPLE_MD = """# List of supported software
360+
361+
EasyBuild supports 2 different software packages (incl. toolchains, bundles):
362+
363+
<a href="#g">g</a>
364+
365+
366+
<a anchor="g"/>
367+
### *G*
368+
369+
* GCC
370+
* gzip"""
371+
372+
LIST_SOFTWARE_DETAILED_MD = """# List of supported software
373+
374+
EasyBuild supports 2 different software packages (incl. toolchains, bundles):
375+
376+
<a href="#g">g</a>
377+
378+
379+
<a anchor="g"/>
380+
### *G*
381+
382+
383+
<a href="#gcc">GCC</a> - <a href="#gzip">gzip</a>
384+
385+
386+
<a anchor="gcc"/>
387+
### *GCC*
388+
389+
%(gcc_descr)s
390+
391+
*homepage*: http://gcc.gnu.org/
392+
393+
version |toolchain
394+
---------|----------
395+
``4.6.3``|``system``
396+
397+
<a anchor="gzip"/>
398+
### *gzip*
399+
400+
%(gzip_descr)s
401+
402+
*homepage*: http://www.gzip.org/
403+
404+
version|toolchain
405+
-------|-------------------------------
406+
``1.4``|``GCC/4.6.3``, ``system``
407+
``1.5``|``foss/2018a``, ``intel/2018a``""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}
408+
266409

267410
class DocsTest(EnhancedTestCase):
268411

@@ -376,107 +519,14 @@ def test_list_software(self):
376519
}
377520
init_config(build_options=build_options)
378521

379-
expected = '\n'.join([
380-
'',
381-
'* GCC',
382-
'* gzip',
383-
])
384-
self.assertEqual(list_software(output_format='txt'), expected)
385-
386-
expected = re.compile('\n'.join([
387-
r'',
388-
r'\* GCC',
389-
r'',
390-
r"The GNU Compiler Collection .*",
391-
r'',
392-
r'homepage: http://gcc.gnu.org/',
393-
r'',
394-
r' \* GCC v4.6.3: system',
395-
r'',
396-
r'\* gzip',
397-
r'',
398-
r"gzip \(GNU zip\) is .*",
399-
r'',
400-
r'homepage: http://www.gzip.org/',
401-
r'',
402-
r" \* gzip v1.4: GCC/4.6.3, system",
403-
r" \* gzip v1.5: foss/2018a, intel/2018a",
404-
'',
405-
]))
406-
txt = list_software(output_format='txt', detailed=True)
407-
self.assertTrue(expected.match(txt), "Pattern '%s' found in: %s" % (expected.pattern, txt))
522+
self.assertEqual(list_software(output_format='txt'), LIST_SOFTWARE_SIMPLE_TXT)
523+
self.assertEqual(list_software(output_format='txt', detailed=True), LIST_SOFTWARE_DETAILED_TXT)
408524

409-
expected = '\n'.join([
410-
"List of supported software",
411-
"==========================",
412-
'',
413-
"EasyBuild |version| supports 2 different software packages (incl. toolchains, bundles):",
414-
'',
415-
':ref:`list_software_letter_g`',
416-
'',
417-
'',
418-
'.. _list_software_letter_g:',
419-
'',
420-
'*G*',
421-
'---',
422-
'',
423-
'* GCC',
424-
'* gzip',
425-
])
426-
self.assertEqual(list_software(output_format='rst'), expected)
427-
428-
expected = re.compile('\n'.join([
429-
r"List of supported software",
430-
r"==========================",
431-
r'',
432-
r"EasyBuild \|version\| supports 2 different software packages \(incl. toolchains, bundles\):",
433-
r'',
434-
r':ref:`list_software_letter_g`',
435-
r'',
436-
r'',
437-
r'.. _list_software_letter_g:',
438-
r'',
439-
r'\*G\*',
440-
r'---',
441-
r'',
442-
r'',
443-
r':ref:`list_software_GCC_205` - :ref:`list_software_gzip_442`',
444-
r'',
445-
r'',
446-
r'\.\. _list_software_GCC_205:',
447-
r'',
448-
r'\*GCC\*',
449-
r'\+\+\+\+\+',
450-
r'',
451-
r'The GNU Compiler Collection .*',
452-
r'',
453-
r'\*homepage\*: http://gcc.gnu.org/',
454-
r'',
455-
r'========= ==========',
456-
r'version toolchain ',
457-
r'========= ==========',
458-
r'``4.6.3`` ``system``',
459-
r'========= ==========',
460-
r'',
461-
r'',
462-
r'\.\. _list_software_gzip_442:',
463-
r'',
464-
r'\*gzip\*',
465-
r'\+\+\+\+\+\+',
466-
r'',
467-
r'gzip \(GNU zip\) is a popular .*',
468-
r'',
469-
r'\*homepage\*: http://www.gzip.org/',
470-
r'',
471-
r'======= ===============================',
472-
r'version toolchain ',
473-
r'======= ===============================',
474-
r'``1.4`` ``GCC/4.6.3``, ``system`` ',
475-
r'``1.5`` ``foss/2018a``, ``intel/2018a``',
476-
r'======= ===============================',
477-
]))
478-
txt = list_software(output_format='rst', detailed=True)
479-
self.assertTrue(expected.match(txt), "Pattern '%s' found in: %s" % (expected.pattern, txt))
525+
self.assertEqual(list_software(output_format='rst'), LIST_SOFTWARE_SIMPLE_RST)
526+
self.assertEqual(list_software(output_format='rst', detailed=True), LIST_SOFTWARE_DETAILED_RST)
527+
528+
self.assertEqual(list_software(output_format='md'), LIST_SOFTWARE_SIMPLE_MD)
529+
self.assertEqual(list_software(output_format='md', detailed=True), LIST_SOFTWARE_DETAILED_MD)
480530

481531
# GCC/4.6.3 is installed, no gzip module installed
482532
txt = list_software(output_format='txt', detailed=True, only_installed=True)

0 commit comments

Comments
 (0)