Skip to content

Commit 860e600

Browse files
authored
Merge pull request #4152 from APN-Pucky/develop
add support for --list-software --output-format=json
2 parents d977648 + 4288f5b commit 860e600

File tree

3 files changed

+212
-2
lines changed

3 files changed

+212
-2
lines changed

easybuild/tools/docs.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"""
3939
import copy
4040
import inspect
41+
import json
4142
import os
4243
from easybuild.tools import LooseVersion
4344

@@ -72,6 +73,7 @@
7273
DETAILED = 'detailed'
7374
SIMPLE = 'simple'
7475

76+
FORMAT_JSON = 'json'
7577
FORMAT_MD = 'md'
7678
FORMAT_RST = 'rst'
7779
FORMAT_TXT = 'txt'
@@ -115,6 +117,11 @@ def avail_cfgfile_constants(go_cfg_constants, output_format=FORMAT_TXT):
115117
return generate_doc('avail_cfgfile_constants_%s' % output_format, [go_cfg_constants])
116118

117119

120+
def avail_cfgfile_constants_json(go_cfg_constants):
121+
"""Generate documentation on constants for configuration files in json format"""
122+
raise NotImplementedError("JSON output format not supported for avail_cfgfile_constants_json")
123+
124+
118125
def avail_cfgfile_constants_txt(go_cfg_constants):
119126
"""Generate documentation on constants for configuration files in txt format"""
120127
doc = [
@@ -184,6 +191,11 @@ def avail_easyconfig_constants(output_format=FORMAT_TXT):
184191
return generate_doc('avail_easyconfig_constants_%s' % output_format, [])
185192

186193

194+
def avail_easyconfig_constants_json():
195+
"""Generate easyconfig constant documentation in json format"""
196+
raise NotImplementedError("JSON output format not supported for avail_easyconfig_constants_json")
197+
198+
187199
def avail_easyconfig_constants_txt():
188200
"""Generate easyconfig constant documentation in txt format"""
189201
doc = ["Constants that can be used in easyconfigs"]
@@ -242,6 +254,11 @@ def avail_easyconfig_licenses(output_format=FORMAT_TXT):
242254
return generate_doc('avail_easyconfig_licenses_%s' % output_format, [])
243255

244256

257+
def avail_easyconfig_licenses_json():
258+
"""Generate easyconfig license documentation in json format"""
259+
raise NotImplementedError("JSON output format not supported for avail_easyconfig_licenses_json")
260+
261+
245262
def avail_easyconfig_licenses_txt():
246263
"""Generate easyconfig license documentation in txt format"""
247264
doc = ["License constants that can be used in easyconfigs"]
@@ -354,6 +371,13 @@ def avail_easyconfig_params_rst(title, grouped_params):
354371
return '\n'.join(doc)
355372

356373

374+
def avail_easyconfig_params_json():
375+
"""
376+
Compose overview of available easyconfig parameters, in json format.
377+
"""
378+
raise NotImplementedError("JSON output format not supported for avail_easyconfig_params_json")
379+
380+
357381
def avail_easyconfig_params_txt(title, grouped_params):
358382
"""
359383
Compose overview of available easyconfig parameters, in plain text format.
@@ -426,6 +450,11 @@ def avail_easyconfig_templates(output_format=FORMAT_TXT):
426450
return generate_doc('avail_easyconfig_templates_%s' % output_format, [])
427451

428452

453+
def avail_easyconfig_templates_json():
454+
""" Returns template documentation in json text format """
455+
raise NotImplementedError("JSON output format not supported for avail_easyconfig_templates")
456+
457+
429458
def avail_easyconfig_templates_txt():
430459
""" Returns template documentation in plain text format """
431460
# This has to reflect the methods/steps used in easyconfig _generate_template_values
@@ -640,6 +669,8 @@ def avail_classes_tree(classes, class_names, locations, detailed, format_strings
640669

641670

642671
def list_easyblocks(list_easyblocks=SIMPLE, output_format=FORMAT_TXT):
672+
if output_format == FORMAT_JSON:
673+
raise NotImplementedError("JSON output format not supported for list_easyblocks")
643674
format_strings = {
644675
FORMAT_MD: {
645676
'det_root_templ': "- **%s** (%s%s)",
@@ -1024,6 +1055,38 @@ def list_software_txt(software, detailed=False):
10241055
return '\n'.join(lines)
10251056

10261057

1058+
def list_software_json(software, detailed=False):
1059+
"""
1060+
Return overview of supported software in json
1061+
1062+
:param software: software information (strucuted like list_software does)
1063+
:param detailed: whether or not to return detailed information (incl. version, versionsuffix, toolchain info)
1064+
:return: multi-line string presenting requested info
1065+
"""
1066+
lines = ['[']
1067+
for key in sorted(software, key=lambda x: x.lower()):
1068+
for entry in software[key]:
1069+
if detailed:
1070+
# deep copy here to avoid modifying the original dict
1071+
entry = copy.deepcopy(entry)
1072+
entry['description'] = ' '.join(entry['description'].split('\n')).strip()
1073+
else:
1074+
entry = {}
1075+
entry['name'] = key
1076+
1077+
lines.append(json.dumps(entry, indent=4, sort_keys=True, separators=(',', ': ')) + ",")
1078+
if not detailed:
1079+
break
1080+
1081+
# remove trailing comma on last line
1082+
if len(lines) > 1:
1083+
lines[-1] = lines[-1].rstrip(',')
1084+
1085+
lines.append(']')
1086+
1087+
return '\n'.join(lines)
1088+
1089+
10271090
def list_toolchains(output_format=FORMAT_TXT):
10281091
"""Show list of known toolchains."""
10291092
_, all_tcs = search_toolchain('')
@@ -1173,6 +1236,11 @@ def list_toolchains_txt(tcs):
11731236
return '\n'.join(doc)
11741237

11751238

1239+
def list_toolchains_json(tcs):
1240+
""" Returns overview of all toolchains in json format """
1241+
raise NotImplementedError("JSON output not implemented yet for --list-toolchains")
1242+
1243+
11761244
def avail_toolchain_opts(name, output_format=FORMAT_TXT):
11771245
"""Show list of known options for given toolchain."""
11781246
tc_class, _ = search_toolchain(name)
@@ -1226,6 +1294,11 @@ def avail_toolchain_opts_rst(name, tc_dict):
12261294
return '\n'.join(doc)
12271295

12281296

1297+
def avail_toolchain_opts_json(name, tc_dict):
1298+
""" Returns overview of toolchain options in jsonformat """
1299+
raise NotImplementedError("JSON output not implemented yet for --avail-toolchain-opts")
1300+
1301+
12291302
def avail_toolchain_opts_txt(name, tc_dict):
12301303
""" Returns overview of toolchain options in txt format """
12311304
doc = ["Available options for %s toolchain:" % name]
@@ -1252,6 +1325,13 @@ def get_easyblock_classes(package_name):
12521325
return easyblocks
12531326

12541327

1328+
def gen_easyblocks_overview_json(package_name, path_to_examples, common_params=None, doc_functions=None):
1329+
"""
1330+
Compose overview of all easyblocks in the given package in json format
1331+
"""
1332+
raise NotImplementedError("JSON output not implemented yet for gen_easyblocks_overview")
1333+
1334+
12551335
def gen_easyblocks_overview_md(package_name, path_to_examples, common_params=None, doc_functions=None):
12561336
"""
12571337
Compose overview of all easyblocks in the given package in MarkDown format

easybuild/tools/options.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@
7979
from easybuild.tools.config import get_pretend_installpath, init, init_build_options, mk_full_default_path
8080
from easybuild.tools.config import BuildOptions, ConfigurationVariables
8181
from easybuild.tools.configobj import ConfigObj, ConfigObjError
82-
from easybuild.tools.docs import FORMAT_MD, FORMAT_RST, FORMAT_TXT
82+
from easybuild.tools.docs import FORMAT_JSON, FORMAT_MD, FORMAT_RST, FORMAT_TXT
8383
from easybuild.tools.docs import avail_cfgfile_constants, avail_easyconfig_constants, avail_easyconfig_licenses
8484
from easybuild.tools.docs import avail_toolchain_opts, avail_easyconfig_params, avail_easyconfig_templates
8585
from easybuild.tools.docs import list_easyblocks, list_toolchains
@@ -472,7 +472,8 @@ def override_options(self):
472472
'mpi-tests': ("Run MPI tests (when relevant)", None, 'store_true', True),
473473
'optarch': ("Set architecture optimization, overriding native architecture optimizations",
474474
None, 'store', None),
475-
'output-format': ("Set output format", 'choice', 'store', FORMAT_TXT, [FORMAT_MD, FORMAT_RST, FORMAT_TXT]),
475+
'output-format': ("Set output format", 'choice', 'store', FORMAT_TXT,
476+
[FORMAT_JSON, FORMAT_MD, FORMAT_RST, FORMAT_TXT]),
476477
'output-style': ("Control output style; auto implies using Rich if available to produce rich output, "
477478
"with fallback to basic colored output",
478479
'choice', 'store', OUTPUT_STYLE_AUTO, OUTPUT_STYLES),

test/framework/docs.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,104 @@
405405
``1.4``|``GCC/4.6.3``, ``system``
406406
``1.5``|``foss/2018a``, ``intel/2018a``""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}
407407

408+
LIST_SOFTWARE_SIMPLE_MD = """# List of supported software
409+
410+
EasyBuild supports 2 different software packages (incl. toolchains, bundles):
411+
412+
[g](#g)
413+
414+
415+
## G
416+
417+
* GCC
418+
* gzip"""
419+
420+
LIST_SOFTWARE_DETAILED_MD = """# List of supported software
421+
422+
EasyBuild supports 2 different software packages (incl. toolchains, bundles):
423+
424+
[g](#g)
425+
426+
427+
## G
428+
429+
430+
[GCC](#gcc) - [gzip](#gzip)
431+
432+
433+
### GCC
434+
435+
%(gcc_descr)s
436+
437+
*homepage*: <http://gcc.gnu.org/>
438+
439+
version |toolchain
440+
---------|----------
441+
``4.6.3``|``system``
442+
443+
### gzip
444+
445+
%(gzip_descr)s
446+
447+
*homepage*: <http://www.gzip.org/>
448+
449+
version|toolchain
450+
-------|-------------------------------
451+
``1.4``|``GCC/4.6.3``, ``system``
452+
``1.5``|``foss/2018a``, ``intel/2018a``""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}
453+
454+
LIST_SOFTWARE_SIMPLE_JSON = """[
455+
{
456+
"name": "GCC"
457+
},
458+
{
459+
"name": "gzip"
460+
}
461+
]"""
462+
463+
LIST_SOFTWARE_DETAILED_JSON = """[
464+
{
465+
"description": "%(gcc_descr)s",
466+
"homepage": "http://gcc.gnu.org/",
467+
"name": "GCC",
468+
"toolchain": "system",
469+
"version": "4.6.3",
470+
"versionsuffix": ""
471+
},
472+
{
473+
"description": "%(gzip_descr)s",
474+
"homepage": "http://www.gzip.org/",
475+
"name": "gzip",
476+
"toolchain": "GCC/4.6.3",
477+
"version": "1.4",
478+
"versionsuffix": ""
479+
},
480+
{
481+
"description": "%(gzip_descr)s",
482+
"homepage": "http://www.gzip.org/",
483+
"name": "gzip",
484+
"toolchain": "system",
485+
"version": "1.4",
486+
"versionsuffix": ""
487+
},
488+
{
489+
"description": "%(gzip_descr)s",
490+
"homepage": "http://www.gzip.org/",
491+
"name": "gzip",
492+
"toolchain": "foss/2018a",
493+
"version": "1.5",
494+
"versionsuffix": ""
495+
},
496+
{
497+
"description": "%(gzip_descr)s",
498+
"homepage": "http://www.gzip.org/",
499+
"name": "gzip",
500+
"toolchain": "intel/2018a",
501+
"version": "1.5",
502+
"versionsuffix": ""
503+
}
504+
]""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR}
505+
408506

409507
class DocsTest(EnhancedTestCase):
410508

@@ -541,6 +639,9 @@ def test_license_docs(self):
541639
regex = re.compile(r"^``GPLv3``\s*|The GNU General Public License", re.M)
542640
self.assertTrue(regex.search(lic_docs), "%s found in: %s" % (regex.pattern, lic_docs))
543641

642+
# expect NotImplementedError for JSON output
643+
self.assertRaises(NotImplementedError, avail_easyconfig_licenses, output_format='json')
644+
544645
def test_list_easyblocks(self):
545646
"""
546647
Tests for list_easyblocks function
@@ -569,6 +670,9 @@ def test_list_easyblocks(self):
569670
txt = list_easyblocks(list_easyblocks='detailed', output_format='md')
570671
self.assertEqual(txt, LIST_EASYBLOCKS_DETAILED_MD % {'topdir': topdir_easyblocks})
571672

673+
# expect NotImplementedError for JSON output
674+
self.assertRaises(NotImplementedError, list_easyblocks, output_format='json')
675+
572676
def test_list_software(self):
573677
"""Test list_software* functions."""
574678
build_options = {
@@ -587,6 +691,9 @@ def test_list_software(self):
587691
self.assertEqual(list_software(output_format='md'), LIST_SOFTWARE_SIMPLE_MD)
588692
self.assertEqual(list_software(output_format='md', detailed=True), LIST_SOFTWARE_DETAILED_MD)
589693

694+
self.assertEqual(list_software(output_format='json'), LIST_SOFTWARE_SIMPLE_JSON)
695+
self.assertEqual(list_software(output_format='json', detailed=True), LIST_SOFTWARE_DETAILED_JSON)
696+
590697
# GCC/4.6.3 is installed, no gzip module installed
591698
txt = list_software(output_format='txt', detailed=True, only_installed=True)
592699
self.assertTrue(re.search(r'^\* GCC', txt, re.M))
@@ -690,6 +797,10 @@ def test_list_toolchains(self):
690797
regex = re.compile(pattern, re.M)
691798
self.assertTrue(regex.search(txt_rst), "Pattern '%s' should be found in: %s" % (regex.pattern, txt_rst))
692799

800+
# expect NotImplementedError for json output format
801+
with self.assertRaises(NotImplementedError):
802+
list_toolchains(output_format='json')
803+
693804
def test_avail_cfgfile_constants(self):
694805
"""
695806
Test avail_cfgfile_constants to generate overview of constants that can be used in a configuration file.
@@ -734,6 +845,10 @@ def test_avail_cfgfile_constants(self):
734845
regex = re.compile(pattern, re.M)
735846
self.assertTrue(regex.search(txt_rst), "Pattern '%s' should be found in: %s" % (regex.pattern, txt_rst))
736847

848+
# expect NotImplementedError for json output format
849+
with self.assertRaises(NotImplementedError):
850+
avail_cfgfile_constants(option_parser.go_cfg_constants, output_format='json')
851+
737852
def test_avail_easyconfig_constants(self):
738853
"""
739854
Test avail_easyconfig_constants to generate overview of constants that can be used in easyconfig files.
@@ -777,6 +892,10 @@ def test_avail_easyconfig_constants(self):
777892
regex = re.compile(pattern, re.M)
778893
self.assertTrue(regex.search(txt_rst), "Pattern '%s' should be found in: %s" % (regex.pattern, txt_rst))
779894

895+
# expect NotImplementedError for json output format
896+
with self.assertRaises(NotImplementedError):
897+
avail_easyconfig_constants(output_format='json')
898+
780899
def test_avail_easyconfig_templates(self):
781900
"""
782901
Test avail_easyconfig_templates to generate overview of templates that can be used in easyconfig files.
@@ -827,6 +946,10 @@ def test_avail_easyconfig_templates(self):
827946
regex = re.compile(pattern, re.M)
828947
self.assertTrue(regex.search(txt_rst), "Pattern '%s' should be found in: %s" % (regex.pattern, txt_rst))
829948

949+
# expect NotImplementedError for json output format
950+
with self.assertRaises(NotImplementedError):
951+
avail_easyconfig_templates(output_format='json')
952+
830953
def test_avail_toolchain_opts(self):
831954
"""
832955
Test avail_toolchain_opts to generate overview of supported toolchain options.
@@ -911,6 +1034,12 @@ def test_avail_toolchain_opts(self):
9111034
regex = re.compile(pattern, re.M)
9121035
self.assertTrue(regex.search(txt_rst), "Pattern '%s' should be found in: %s" % (regex.pattern, txt_rst))
9131036

1037+
# expect NotImplementedError for json output format
1038+
with self.assertRaises(NotImplementedError):
1039+
avail_toolchain_opts('foss', output_format='json')
1040+
with self.assertRaises(NotImplementedError):
1041+
avail_toolchain_opts('intel', output_format='json')
1042+
9141043
def test_mk_table(self):
9151044
"""
9161045
Tests for mk_*_table functions.

0 commit comments

Comments
 (0)