From 871f955ca0317a3d5d1bcd2f5955b02d445ac62d Mon Sep 17 00:00:00 2001 From: Alexander Puck Neuwirth Date: Sun, 26 Nov 2023 22:01:02 +0100 Subject: [PATCH 01/10] Add formating as json --- easybuild/tools/docs.py | 30 ++++++++++++++++++++++++++++++ easybuild/tools/options.py | 4 ++-- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/easybuild/tools/docs.py b/easybuild/tools/docs.py index 4f4bfca99c..84666a6e10 100644 --- a/easybuild/tools/docs.py +++ b/easybuild/tools/docs.py @@ -39,6 +39,7 @@ import copy import inspect import os +import json from easybuild.tools import LooseVersion from easybuild.base import fancylogger @@ -75,6 +76,7 @@ FORMAT_MD = 'md' FORMAT_RST = 'rst' FORMAT_TXT = 'txt' +FORMAT_JSON = 'json' def generate_doc(name, params): @@ -1024,6 +1026,34 @@ def list_software_txt(software, detailed=False): return '\n'.join(lines) +def list_software_json(software, detailed=False): + """ + Return overview of supported software in json + + :param software: software information (strucuted like list_software does) + :param detailed: whether or not to return detailed information (incl. version, versionsuffix, toolchain info) + :return: multi-line string presenting requested info + """ + lines = ['['] + for key in sorted(software, key=lambda x: x.lower()): + for tmp in software[key]: + if detailed: + # deep copy here to avoid modifying the original dict + x = copy.deepcopy(tmp) + x['description'] = x['description'].split('\n')[0].strip() + else: + x = {} + x['name'] = key + + lines.append(json.dumps(x, indent=4) + ",") + if detailed: + break + # remove last comma + if len(lines) > 1: + lines[-1] = lines[-1][:-1] + return '\n'.join(lines) + '\n]' + + def list_toolchains(output_format=FORMAT_TXT): """Show list of known toolchains.""" _, all_tcs = search_toolchain('') diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index e69d96b2f6..2a7d990e01 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -79,7 +79,7 @@ from easybuild.tools.config import get_pretend_installpath, init, init_build_options, mk_full_default_path from easybuild.tools.config import BuildOptions, ConfigurationVariables from easybuild.tools.configobj import ConfigObj, ConfigObjError -from easybuild.tools.docs import FORMAT_MD, FORMAT_RST, FORMAT_TXT +from easybuild.tools.docs import FORMAT_MD, FORMAT_RST, FORMAT_TXT, FORMAT_JSON from easybuild.tools.docs import avail_cfgfile_constants, avail_easyconfig_constants, avail_easyconfig_licenses from easybuild.tools.docs import avail_toolchain_opts, avail_easyconfig_params, avail_easyconfig_templates from easybuild.tools.docs import list_easyblocks, list_toolchains @@ -469,7 +469,7 @@ def override_options(self): 'mpi-tests': ("Run MPI tests (when relevant)", None, 'store_true', True), 'optarch': ("Set architecture optimization, overriding native architecture optimizations", None, 'store', None), - 'output-format': ("Set output format", 'choice', 'store', FORMAT_TXT, [FORMAT_MD, FORMAT_RST, FORMAT_TXT]), + 'output-format': ("Set output format", 'choice', 'store', FORMAT_TXT, [FORMAT_MD, FORMAT_RST, FORMAT_TXT, FORMAT_JSON]), 'output-style': ("Control output style; auto implies using Rich if available to produce rich output, " "with fallback to basic colored output", 'choice', 'store', OUTPUT_STYLE_AUTO, OUTPUT_STYLES), From c3bde441c3710f3dbcd8ab873385795d3ff3179e Mon Sep 17 00:00:00 2001 From: Alexander Puck Neuwirth Date: Sun, 26 Nov 2023 22:26:14 +0100 Subject: [PATCH 02/10] Add json list software test --- easybuild/tools/docs.py | 2 +- easybuild/tools/options.py | 3 +- test/framework/docs.py | 88 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/easybuild/tools/docs.py b/easybuild/tools/docs.py index 84666a6e10..74684842dd 100644 --- a/easybuild/tools/docs.py +++ b/easybuild/tools/docs.py @@ -1040,7 +1040,7 @@ def list_software_json(software, detailed=False): if detailed: # deep copy here to avoid modifying the original dict x = copy.deepcopy(tmp) - x['description'] = x['description'].split('\n')[0].strip() + x['description'] = ' '.join(x['description'].split('\n')).strip() else: x = {} x['name'] = key diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 2a7d990e01..a1f9d756eb 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -469,7 +469,8 @@ def override_options(self): 'mpi-tests': ("Run MPI tests (when relevant)", None, 'store_true', True), 'optarch': ("Set architecture optimization, overriding native architecture optimizations", None, 'store', None), - 'output-format': ("Set output format", 'choice', 'store', FORMAT_TXT, [FORMAT_MD, FORMAT_RST, FORMAT_TXT, FORMAT_JSON]), + 'output-format': ("Set output format", 'choice', 'store', FORMAT_TXT, + [FORMAT_MD, FORMAT_RST, FORMAT_TXT, FORMAT_JSON]), 'output-style': ("Control output style; auto implies using Rich if available to produce rich output, " "with fallback to basic colored output", 'choice', 'store', OUTPUT_STYLE_AUTO, OUTPUT_STYLES), diff --git a/test/framework/docs.py b/test/framework/docs.py index 84d862bd3c..829369934a 100644 --- a/test/framework/docs.py +++ b/test/framework/docs.py @@ -405,6 +405,91 @@ ``1.4``|``GCC/4.6.3``, ``system`` ``1.5``|``foss/2018a``, ``intel/2018a``""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR} +LIST_SOFTWARE_SIMPLE_MD = """# List of supported software + +EasyBuild supports 2 different software packages (incl. toolchains, bundles): + +[g](#g) + + +## G + +* GCC +* gzip""" + + + +LIST_SOFTWARE_DETAILED_MD = """# List of supported software + +EasyBuild supports 2 different software packages (incl. toolchains, bundles): + +[g](#g) + + +## G + + +[GCC](#gcc) - [gzip](#gzip) + + +### GCC + +%(gcc_descr)s + +*homepage*: + +version |toolchain +---------|---------- +``4.6.3``|``system`` + +### gzip + +%(gzip_descr)s + +*homepage*: + +version|toolchain +-------|------------------------------- +``1.4``|``GCC/4.6.3``, ``system`` +``1.5``|``foss/2018a``, ``intel/2018a``""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR} + +LIST_SOFTWARE_SIMPLE_JSON = """[ +{ + "name": "GCC" +}, +{ + "name": "gzip" +}, +{ + "name": "gzip" +}, +{ + "name": "gzip" +}, +{ + "name": "gzip" +} +]""" + +LIST_SOFTWARE_DETAILED_JSON = """[ +{ + "toolchain": "system", + "description": "%(gcc_descr)s", + "homepage": "http://gcc.gnu.org/", + "version": "4.6.3", + "versionsuffix": "", + "name": "GCC" +}, +{ + "toolchain": "GCC/4.6.3", + "description": "%(gzip_descr)s", + "homepage": "http://www.gzip.org/", + "version": "1.4", + "versionsuffix": "", + "name": "gzip" +} +]""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR} + class DocsTest(EnhancedTestCase): @@ -587,6 +672,9 @@ def test_list_software(self): self.assertEqual(list_software(output_format='md'), LIST_SOFTWARE_SIMPLE_MD) self.assertEqual(list_software(output_format='md', detailed=True), LIST_SOFTWARE_DETAILED_MD) + self.assertEqual(list_software(output_format='json'), LIST_SOFTWARE_SIMPLE_JSON) + self.assertEqual(list_software(output_format='json', detailed=True), LIST_SOFTWARE_DETAILED_JSON) + # GCC/4.6.3 is installed, no gzip module installed txt = list_software(output_format='txt', detailed=True, only_installed=True) self.assertTrue(re.search(r'^\* GCC', txt, re.M)) From 1cc58f9971498c4b175b34937dc061568cea9900 Mon Sep 17 00:00:00 2001 From: Alexander Puck Neuwirth Date: Sun, 26 Nov 2023 22:27:50 +0100 Subject: [PATCH 03/10] Fix format... --- easybuild/tools/options.py | 2 +- test/framework/docs.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index a1f9d756eb..64c46b61b5 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -469,7 +469,7 @@ def override_options(self): 'mpi-tests': ("Run MPI tests (when relevant)", None, 'store_true', True), 'optarch': ("Set architecture optimization, overriding native architecture optimizations", None, 'store', None), - 'output-format': ("Set output format", 'choice', 'store', FORMAT_TXT, + 'output-format': ("Set output format", 'choice', 'store', FORMAT_TXT, [FORMAT_MD, FORMAT_RST, FORMAT_TXT, FORMAT_JSON]), 'output-style': ("Control output style; auto implies using Rich if available to produce rich output, " "with fallback to basic colored output", diff --git a/test/framework/docs.py b/test/framework/docs.py index 829369934a..5894796b05 100644 --- a/test/framework/docs.py +++ b/test/framework/docs.py @@ -417,8 +417,6 @@ * GCC * gzip""" - - LIST_SOFTWARE_DETAILED_MD = """# List of supported software EasyBuild supports 2 different software packages (incl. toolchains, bundles): From 9104b85d9efa6e1c10fbc32d52872f4a2617a770 Mon Sep 17 00:00:00 2001 From: Alexander Puck Neuwirth Date: Sun, 26 Nov 2023 22:35:50 +0100 Subject: [PATCH 04/10] Fix verbosity --- easybuild/tools/docs.py | 4 ++-- test/framework/docs.py | 33 ++++++++++++++++++++++++--------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/easybuild/tools/docs.py b/easybuild/tools/docs.py index 74684842dd..40f53f2ef1 100644 --- a/easybuild/tools/docs.py +++ b/easybuild/tools/docs.py @@ -1046,9 +1046,9 @@ def list_software_json(software, detailed=False): x['name'] = key lines.append(json.dumps(x, indent=4) + ",") - if detailed: + if not detailed: break - # remove last comma + # remove last line last comma if len(lines) > 1: lines[-1] = lines[-1][:-1] return '\n'.join(lines) + '\n]' diff --git a/test/framework/docs.py b/test/framework/docs.py index 5894796b05..b52e7e09ee 100644 --- a/test/framework/docs.py +++ b/test/framework/docs.py @@ -455,15 +455,6 @@ { "name": "GCC" }, -{ - "name": "gzip" -}, -{ - "name": "gzip" -}, -{ - "name": "gzip" -}, { "name": "gzip" } @@ -485,6 +476,30 @@ "version": "1.4", "versionsuffix": "", "name": "gzip" +}, +{ + "toolchain": "system", + "description": "%(gzip_descr)s", + "homepage": "http://www.gzip.org/", + "version": "1.4", + "versionsuffix": "", + "name": "gzip" +}, +{ + "toolchain": "foss/2018a", + "description": "%(gzip_descr)s", + "homepage": "http://www.gzip.org/", + "version": "1.5", + "versionsuffix": "", + "name": "gzip" +}, +{ + "toolchain": "intel/2018a", + "description": "%(gzip_descr)s", + "homepage": "http://www.gzip.org/", + "version": "1.5", + "versionsuffix": "", + "name": "gzip" } ]""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR} From 3538e93403c1adf70d9ab12f95537deec634e183 Mon Sep 17 00:00:00 2001 From: Alexander Puck Neuwirth Date: Sun, 26 Nov 2023 22:59:42 +0100 Subject: [PATCH 05/10] Add not implemented exceptions for other json cases --- easybuild/tools/docs.py | 43 +++++++++++++++++++++++++++++++++++++++++ test/framework/docs.py | 28 +++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/easybuild/tools/docs.py b/easybuild/tools/docs.py index 40f53f2ef1..0c710be74e 100644 --- a/easybuild/tools/docs.py +++ b/easybuild/tools/docs.py @@ -117,6 +117,11 @@ def avail_cfgfile_constants(go_cfg_constants, output_format=FORMAT_TXT): return generate_doc('avail_cfgfile_constants_%s' % output_format, [go_cfg_constants]) +def avail_cfgfile_constants_json(go_cfg_constants): + """Generate documentation on constants for configuration files in json format""" + raise NotImplementedError("JSON output format not supported for avail_cfgfile_constants_json") + + def avail_cfgfile_constants_txt(go_cfg_constants): """Generate documentation on constants for configuration files in txt format""" doc = [ @@ -186,6 +191,11 @@ def avail_easyconfig_constants(output_format=FORMAT_TXT): return generate_doc('avail_easyconfig_constants_%s' % output_format, []) +def avail_easyconfig_constants_json(): + """Generate easyconfig constant documentation in json format""" + raise NotImplementedError("JSON output format not supported for avail_easyconfig_constants_json") + + def avail_easyconfig_constants_txt(): """Generate easyconfig constant documentation in txt format""" doc = ["Constants that can be used in easyconfigs"] @@ -244,6 +254,11 @@ def avail_easyconfig_licenses(output_format=FORMAT_TXT): return generate_doc('avail_easyconfig_licenses_%s' % output_format, []) +def avail_easyconfig_licenses_json(): + """Generate easyconfig license documentation in json format""" + raise NotImplementedError("JSON output format not supported for avail_easyconfig_licenses_json") + + def avail_easyconfig_licenses_txt(): """Generate easyconfig license documentation in txt format""" doc = ["License constants that can be used in easyconfigs"] @@ -356,6 +371,13 @@ def avail_easyconfig_params_rst(title, grouped_params): return '\n'.join(doc) +def avail_easyconfig_params_json(): + """ + Compose overview of available easyconfig parameters, in json format. + """ + raise NotImplementedError("JSON output format not supported for avail_easyconfig_params_json") + + def avail_easyconfig_params_txt(title, grouped_params): """ Compose overview of available easyconfig parameters, in plain text format. @@ -428,6 +450,11 @@ def avail_easyconfig_templates(output_format=FORMAT_TXT): return generate_doc('avail_easyconfig_templates_%s' % output_format, []) +def avail_easyconfig_templates_json(): + """ Returns template documentation in json text format """ + raise NotImplementedError("JSON output format not supported for avail_easyconfig_templates") + + def avail_easyconfig_templates_txt(): """ Returns template documentation in plain text format """ # This has to reflect the methods/steps used in easyconfig _generate_template_values @@ -642,6 +669,8 @@ def avail_classes_tree(classes, class_names, locations, detailed, format_strings def list_easyblocks(list_easyblocks=SIMPLE, output_format=FORMAT_TXT): + if output_format == FORMAT_JSON: + raise NotImplementedError("JSON output format not supported for list_easyblocks") format_strings = { FORMAT_MD: { 'det_root_templ': "- **%s** (%s%s)", @@ -1202,6 +1231,10 @@ def list_toolchains_txt(tcs): return '\n'.join(doc) +def list_toolchains_json(tcs): + """ Returns overview of all toolchains in json format """ + raise NotImplementedError("JSON output not implemented yet for --list-toolchains") + def avail_toolchain_opts(name, output_format=FORMAT_TXT): """Show list of known options for given toolchain.""" @@ -1256,6 +1289,11 @@ def avail_toolchain_opts_rst(name, tc_dict): return '\n'.join(doc) +def avail_toolchain_opts_json(name, tc_dict): + """ Returns overview of toolchain options in jsonformat """ + raise NotImplementedError("JSON output not implemented yet for --avail-toolchain-opts") + + def avail_toolchain_opts_txt(name, tc_dict): """ Returns overview of toolchain options in txt format """ doc = ["Available options for %s toolchain:" % name] @@ -1281,6 +1319,11 @@ def get_easyblock_classes(package_name): return easyblocks +def gen_easyblocks_overview_json(package_name, path_to_examples, common_params=None, doc_functions=None): + """ + Compose overview of all easyblocks in the given package in json format + """ + raise NotImplementedError("JSON output not implemented yet for gen_easyblocks_overview") def gen_easyblocks_overview_md(package_name, path_to_examples, common_params=None, doc_functions=None): """ diff --git a/test/framework/docs.py b/test/framework/docs.py index b52e7e09ee..a271d63cfc 100644 --- a/test/framework/docs.py +++ b/test/framework/docs.py @@ -639,6 +639,9 @@ def test_license_docs(self): regex = re.compile(r"^``GPLv3``\s*|The GNU General Public License", re.M) self.assertTrue(regex.search(lic_docs), "%s found in: %s" % (regex.pattern, lic_docs)) + # expect NotImplementedError for JSON output + self.assertRaises(NotImplementedError, avail_easyconfig_licenses, output_format='json') + def test_list_easyblocks(self): """ Tests for list_easyblocks function @@ -667,6 +670,9 @@ def test_list_easyblocks(self): txt = list_easyblocks(list_easyblocks='detailed', output_format='md') self.assertEqual(txt, LIST_EASYBLOCKS_DETAILED_MD % {'topdir': topdir_easyblocks}) + # expect NotImplementedError for JSON output + self.assertRaises(NotImplementedError, list_easyblocks, output_format='json') + def test_list_software(self): """Test list_software* functions.""" build_options = { @@ -791,6 +797,10 @@ def test_list_toolchains(self): regex = re.compile(pattern, re.M) self.assertTrue(regex.search(txt_rst), "Pattern '%s' should be found in: %s" % (regex.pattern, txt_rst)) + # expect NotImplementedError for json output format + with self.assertRaises(NotImplementedError): + list_toolchains(output_format='json') + def test_avail_cfgfile_constants(self): """ Test avail_cfgfile_constants to generate overview of constants that can be used in a configuration file. @@ -835,6 +845,10 @@ def test_avail_cfgfile_constants(self): regex = re.compile(pattern, re.M) self.assertTrue(regex.search(txt_rst), "Pattern '%s' should be found in: %s" % (regex.pattern, txt_rst)) + # expect NotImplementedError for json output format + with self.assertRaises(NotImplementedError): + avail_cfgfile_constants(option_parser.go_cfg_constants, output_format='json') + def test_avail_easyconfig_constants(self): """ Test avail_easyconfig_constants to generate overview of constants that can be used in easyconfig files. @@ -878,6 +892,10 @@ def test_avail_easyconfig_constants(self): regex = re.compile(pattern, re.M) self.assertTrue(regex.search(txt_rst), "Pattern '%s' should be found in: %s" % (regex.pattern, txt_rst)) + # expect NotImplementedError for json output format + with self.assertRaises(NotImplementedError): + avail_easyconfig_constants(output_format='json') + def test_avail_easyconfig_templates(self): """ Test avail_easyconfig_templates to generate overview of templates that can be used in easyconfig files. @@ -928,6 +946,10 @@ def test_avail_easyconfig_templates(self): regex = re.compile(pattern, re.M) self.assertTrue(regex.search(txt_rst), "Pattern '%s' should be found in: %s" % (regex.pattern, txt_rst)) + # expect NotImplementedError for json output format + with self.assertRaises(NotImplementedError): + avail_easyconfig_templates(output_format='json') + def test_avail_toolchain_opts(self): """ Test avail_toolchain_opts to generate overview of supported toolchain options. @@ -1012,6 +1034,12 @@ def test_avail_toolchain_opts(self): regex = re.compile(pattern, re.M) self.assertTrue(regex.search(txt_rst), "Pattern '%s' should be found in: %s" % (regex.pattern, txt_rst)) + # expect NotImplementedError for json output format + with self.assertRaises(NotImplementedError): + avail_toolchain_opts('foss', output_format='json') + with self.assertRaises(NotImplementedError): + avail_toolchain_opts('intel', output_format='json') + def test_mk_table(self): """ Tests for mk_*_table functions. From 5967a66620ab57d3c212dc1e80f99d3e60aa280a Mon Sep 17 00:00:00 2001 From: Alexander Puck Neuwirth Date: Sun, 26 Nov 2023 23:01:35 +0100 Subject: [PATCH 06/10] Fix format again --- easybuild/tools/docs.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/easybuild/tools/docs.py b/easybuild/tools/docs.py index 0c710be74e..344123d0f7 100644 --- a/easybuild/tools/docs.py +++ b/easybuild/tools/docs.py @@ -1231,6 +1231,7 @@ def list_toolchains_txt(tcs): return '\n'.join(doc) + def list_toolchains_json(tcs): """ Returns overview of all toolchains in json format """ raise NotImplementedError("JSON output not implemented yet for --list-toolchains") @@ -1319,12 +1320,14 @@ def get_easyblock_classes(package_name): return easyblocks + def gen_easyblocks_overview_json(package_name, path_to_examples, common_params=None, doc_functions=None): """ Compose overview of all easyblocks in the given package in json format """ raise NotImplementedError("JSON output not implemented yet for gen_easyblocks_overview") + def gen_easyblocks_overview_md(package_name, path_to_examples, common_params=None, doc_functions=None): """ Compose overview of all easyblocks in the given package in MarkDown format From aaeaa62bb72b522d0769105b750ba62a5679547e Mon Sep 17 00:00:00 2001 From: Alexander Puck Neuwirth Date: Wed, 6 Dec 2023 11:53:37 +0100 Subject: [PATCH 07/10] Add trailing spaces --- test/framework/docs.py | 50 +++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/test/framework/docs.py b/test/framework/docs.py index a271d63cfc..4f878fc728 100644 --- a/test/framework/docs.py +++ b/test/framework/docs.py @@ -462,43 +462,43 @@ LIST_SOFTWARE_DETAILED_JSON = """[ { - "toolchain": "system", - "description": "%(gcc_descr)s", - "homepage": "http://gcc.gnu.org/", - "version": "4.6.3", - "versionsuffix": "", + "toolchain": "system", + "description": "%(gcc_descr)s", + "homepage": "http://gcc.gnu.org/", + "version": "4.6.3", + "versionsuffix": "", "name": "GCC" }, { - "toolchain": "GCC/4.6.3", - "description": "%(gzip_descr)s", - "homepage": "http://www.gzip.org/", - "version": "1.4", - "versionsuffix": "", + "toolchain": "GCC/4.6.3", + "description": "%(gzip_descr)s", + "homepage": "http://www.gzip.org/", + "version": "1.4", + "versionsuffix": "", "name": "gzip" }, { - "toolchain": "system", - "description": "%(gzip_descr)s", - "homepage": "http://www.gzip.org/", - "version": "1.4", - "versionsuffix": "", + "toolchain": "system", + "description": "%(gzip_descr)s", + "homepage": "http://www.gzip.org/", + "version": "1.4", + "versionsuffix": "", "name": "gzip" }, { - "toolchain": "foss/2018a", - "description": "%(gzip_descr)s", - "homepage": "http://www.gzip.org/", - "version": "1.5", - "versionsuffix": "", + "toolchain": "foss/2018a", + "description": "%(gzip_descr)s", + "homepage": "http://www.gzip.org/", + "version": "1.5", + "versionsuffix": "", "name": "gzip" }, { - "toolchain": "intel/2018a", - "description": "%(gzip_descr)s", - "homepage": "http://www.gzip.org/", - "version": "1.5", - "versionsuffix": "", + "toolchain": "intel/2018a", + "description": "%(gzip_descr)s", + "homepage": "http://www.gzip.org/", + "version": "1.5", + "versionsuffix": "", "name": "gzip" } ]""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR} From d2ccb1aa3364bf251b3a50cb973b98184e3977ed Mon Sep 17 00:00:00 2001 From: Alexander Puck Neuwirth Date: Wed, 6 Dec 2023 12:22:00 +0100 Subject: [PATCH 08/10] Enforce no space after comma from json.dumps (for older pythons) --- easybuild/tools/docs.py | 2 +- test/framework/docs.py | 50 ++++++++++++++++++++--------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/easybuild/tools/docs.py b/easybuild/tools/docs.py index 344123d0f7..1bbb4c2d59 100644 --- a/easybuild/tools/docs.py +++ b/easybuild/tools/docs.py @@ -1074,7 +1074,7 @@ def list_software_json(software, detailed=False): x = {} x['name'] = key - lines.append(json.dumps(x, indent=4) + ",") + lines.append(json.dumps(x, indent=4, separators=(',', ': ')) + ",") if not detailed: break # remove last line last comma diff --git a/test/framework/docs.py b/test/framework/docs.py index 4f878fc728..a271d63cfc 100644 --- a/test/framework/docs.py +++ b/test/framework/docs.py @@ -462,43 +462,43 @@ LIST_SOFTWARE_DETAILED_JSON = """[ { - "toolchain": "system", - "description": "%(gcc_descr)s", - "homepage": "http://gcc.gnu.org/", - "version": "4.6.3", - "versionsuffix": "", + "toolchain": "system", + "description": "%(gcc_descr)s", + "homepage": "http://gcc.gnu.org/", + "version": "4.6.3", + "versionsuffix": "", "name": "GCC" }, { - "toolchain": "GCC/4.6.3", - "description": "%(gzip_descr)s", - "homepage": "http://www.gzip.org/", - "version": "1.4", - "versionsuffix": "", + "toolchain": "GCC/4.6.3", + "description": "%(gzip_descr)s", + "homepage": "http://www.gzip.org/", + "version": "1.4", + "versionsuffix": "", "name": "gzip" }, { - "toolchain": "system", - "description": "%(gzip_descr)s", - "homepage": "http://www.gzip.org/", - "version": "1.4", - "versionsuffix": "", + "toolchain": "system", + "description": "%(gzip_descr)s", + "homepage": "http://www.gzip.org/", + "version": "1.4", + "versionsuffix": "", "name": "gzip" }, { - "toolchain": "foss/2018a", - "description": "%(gzip_descr)s", - "homepage": "http://www.gzip.org/", - "version": "1.5", - "versionsuffix": "", + "toolchain": "foss/2018a", + "description": "%(gzip_descr)s", + "homepage": "http://www.gzip.org/", + "version": "1.5", + "versionsuffix": "", "name": "gzip" }, { - "toolchain": "intel/2018a", - "description": "%(gzip_descr)s", - "homepage": "http://www.gzip.org/", - "version": "1.5", - "versionsuffix": "", + "toolchain": "intel/2018a", + "description": "%(gzip_descr)s", + "homepage": "http://www.gzip.org/", + "version": "1.5", + "versionsuffix": "", "name": "gzip" } ]""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR} From 5423a6fcedd1617ef5cd30883a8c0485abb8b62b Mon Sep 17 00:00:00 2001 From: Alexander Puck Neuwirth Date: Wed, 6 Dec 2023 13:38:52 +0100 Subject: [PATCH 09/10] Sort JSON keys for older python versions --- easybuild/tools/docs.py | 2 +- test/framework/docs.py | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/easybuild/tools/docs.py b/easybuild/tools/docs.py index 1bbb4c2d59..43f6038b26 100644 --- a/easybuild/tools/docs.py +++ b/easybuild/tools/docs.py @@ -1074,7 +1074,7 @@ def list_software_json(software, detailed=False): x = {} x['name'] = key - lines.append(json.dumps(x, indent=4, separators=(',', ': ')) + ",") + lines.append(json.dumps(x, indent=4, sort_keys=True, separators=(',', ': ')) + ",") if not detailed: break # remove last line last comma diff --git a/test/framework/docs.py b/test/framework/docs.py index a271d63cfc..70280892e4 100644 --- a/test/framework/docs.py +++ b/test/framework/docs.py @@ -462,44 +462,44 @@ LIST_SOFTWARE_DETAILED_JSON = """[ { - "toolchain": "system", "description": "%(gcc_descr)s", "homepage": "http://gcc.gnu.org/", + "name": "GCC", + "toolchain": "system", "version": "4.6.3", - "versionsuffix": "", - "name": "GCC" + "versionsuffix": "" }, { - "toolchain": "GCC/4.6.3", "description": "%(gzip_descr)s", "homepage": "http://www.gzip.org/", + "name": "gzip", + "toolchain": "GCC/4.6.3", "version": "1.4", - "versionsuffix": "", - "name": "gzip" + "versionsuffix": "" }, { - "toolchain": "system", "description": "%(gzip_descr)s", "homepage": "http://www.gzip.org/", + "name": "gzip", + "toolchain": "system", "version": "1.4", - "versionsuffix": "", - "name": "gzip" + "versionsuffix": "" }, { - "toolchain": "foss/2018a", "description": "%(gzip_descr)s", "homepage": "http://www.gzip.org/", + "name": "gzip", + "toolchain": "foss/2018a", "version": "1.5", - "versionsuffix": "", - "name": "gzip" + "versionsuffix": "" }, { - "toolchain": "intel/2018a", "description": "%(gzip_descr)s", "homepage": "http://www.gzip.org/", + "name": "gzip", + "toolchain": "intel/2018a", "version": "1.5", - "versionsuffix": "", - "name": "gzip" + "versionsuffix": "" } ]""" % {'gcc_descr': GCC_DESCR, 'gzip_descr': GZIP_DESCR} From 4288f5b1274fc4afe362ecbda73e0442520745a7 Mon Sep 17 00:00:00 2001 From: Kenneth Hoste Date: Fri, 29 Dec 2023 17:45:03 +0100 Subject: [PATCH 10/10] minor code style fixes in list_software_json --- easybuild/tools/docs.py | 26 +++++++++++++++----------- easybuild/tools/options.py | 4 ++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/easybuild/tools/docs.py b/easybuild/tools/docs.py index 43f6038b26..199cd22d78 100644 --- a/easybuild/tools/docs.py +++ b/easybuild/tools/docs.py @@ -38,8 +38,8 @@ """ import copy import inspect -import os import json +import os from easybuild.tools import LooseVersion from easybuild.base import fancylogger @@ -73,10 +73,10 @@ DETAILED = 'detailed' SIMPLE = 'simple' +FORMAT_JSON = 'json' FORMAT_MD = 'md' FORMAT_RST = 'rst' FORMAT_TXT = 'txt' -FORMAT_JSON = 'json' def generate_doc(name, params): @@ -1065,22 +1065,26 @@ def list_software_json(software, detailed=False): """ lines = ['['] for key in sorted(software, key=lambda x: x.lower()): - for tmp in software[key]: + for entry in software[key]: if detailed: # deep copy here to avoid modifying the original dict - x = copy.deepcopy(tmp) - x['description'] = ' '.join(x['description'].split('\n')).strip() + entry = copy.deepcopy(entry) + entry['description'] = ' '.join(entry['description'].split('\n')).strip() else: - x = {} - x['name'] = key + entry = {} + entry['name'] = key - lines.append(json.dumps(x, indent=4, sort_keys=True, separators=(',', ': ')) + ",") + lines.append(json.dumps(entry, indent=4, sort_keys=True, separators=(',', ': ')) + ",") if not detailed: break - # remove last line last comma + + # remove trailing comma on last line if len(lines) > 1: - lines[-1] = lines[-1][:-1] - return '\n'.join(lines) + '\n]' + lines[-1] = lines[-1].rstrip(',') + + lines.append(']') + + return '\n'.join(lines) def list_toolchains(output_format=FORMAT_TXT): diff --git a/easybuild/tools/options.py b/easybuild/tools/options.py index 64c46b61b5..fec9bb5da6 100644 --- a/easybuild/tools/options.py +++ b/easybuild/tools/options.py @@ -79,7 +79,7 @@ from easybuild.tools.config import get_pretend_installpath, init, init_build_options, mk_full_default_path from easybuild.tools.config import BuildOptions, ConfigurationVariables from easybuild.tools.configobj import ConfigObj, ConfigObjError -from easybuild.tools.docs import FORMAT_MD, FORMAT_RST, FORMAT_TXT, FORMAT_JSON +from easybuild.tools.docs import FORMAT_JSON, FORMAT_MD, FORMAT_RST, FORMAT_TXT from easybuild.tools.docs import avail_cfgfile_constants, avail_easyconfig_constants, avail_easyconfig_licenses from easybuild.tools.docs import avail_toolchain_opts, avail_easyconfig_params, avail_easyconfig_templates from easybuild.tools.docs import list_easyblocks, list_toolchains @@ -470,7 +470,7 @@ def override_options(self): 'optarch': ("Set architecture optimization, overriding native architecture optimizations", None, 'store', None), 'output-format': ("Set output format", 'choice', 'store', FORMAT_TXT, - [FORMAT_MD, FORMAT_RST, FORMAT_TXT, FORMAT_JSON]), + [FORMAT_JSON, FORMAT_MD, FORMAT_RST, FORMAT_TXT]), 'output-style': ("Control output style; auto implies using Rich if available to produce rich output, " "with fallback to basic colored output", 'choice', 'store', OUTPUT_STYLE_AUTO, OUTPUT_STYLES),