diff --git a/cchecker.py b/cchecker.py index 4e5ed5d4..1327a3b2 100755 --- a/cchecker.py +++ b/cchecker.py @@ -22,21 +22,25 @@ def _print_checker_name_header(checker_str): def parse_options(opts): """ - Helper function to parse possible options. Splits option after the first - colon to split into key/value pairs. + Helper function to parse possible options. Splits option into key/value + pairs and optionally a value for the checker option. The separator + is a colon. :param opts: Iterable of strings with options :rtype: dict - :return: Dictionary with keys as checker type (i.e. "cf", "acdd") + :return: Dictionary with keys as checker type (i.e. "cf", "acdd"). + Each value is a dictionary where keys are checker options and values + are checker option values or None if not provided. """ - options_dict = defaultdict(set) + options_dict = defaultdict(dict) for opt_str in opts: try: - checker_type, checker_opt = opt_str.split(":", 1) + checker_type, checker_opt, *checker_val = opt_str.split(":", 2) + checker_val = checker_val[0] if checker_val else None except ValueError: warnings.warn(f"Could not split option {opt_str}, ignoring", stacklevel=2) else: - options_dict[checker_type].add(checker_opt) + options_dict[checker_type][checker_opt] = checker_val return options_dict @@ -174,8 +178,9 @@ def main(): checkers. Multiple options can be specified via multiple invocations of this switch. Options should be prefixed with a the - checker name followed by the option, e.g. - ':' + checker name followed by the option, + potentially followed by a value, e.g. + ':[:]' Available options: 'cf:enable_appendix_a_checks' - Allow check @@ -235,7 +240,7 @@ def main(): print(f"IOOS compliance checker version {__version__}") sys.exit(0) - options_dict = parse_options(args.option) if args.option else defaultdict(set) + options_dict = parse_options(args.option) if args.option else defaultdict(dict) if args.describe_checks: error_stat = 0 diff --git a/compliance_checker/base.py b/compliance_checker/base.py index 4cf5e751..0f276754 100644 --- a/compliance_checker/base.py +++ b/compliance_checker/base.py @@ -157,7 +157,7 @@ def setup(self, ds): def __init__(self, options=None): self._defined_results = defaultdict(lambda: defaultdict(dict)) if options is None: - self.options = set() + self.options = {} else: self.options = options diff --git a/compliance_checker/suite.py b/compliance_checker/suite.py index a548aae7..6b534f5f 100644 --- a/compliance_checker/suite.py +++ b/compliance_checker/suite.py @@ -401,7 +401,7 @@ def run_all(self, ds, checker_names, include_checks=None, skip_checks=None): # use some kind of checker object with checker type and # version baked in checker_type_name = checker_name.split(":")[0] - checker_opts = self.options.get(checker_type_name, set()) + checker_opts = self.options.get(checker_type_name, {}) # instantiate a Checker object try: diff --git a/compliance_checker/tests/test_cf.py b/compliance_checker/tests/test_cf.py index 3db25d2d..04ca15b2 100644 --- a/compliance_checker/tests/test_cf.py +++ b/compliance_checker/tests/test_cf.py @@ -240,7 +240,7 @@ def test_appendix_a(self): dataset = self.load_dataset(STATIC_FILES["bad_data_type"]) # Ordinarily, options would be specified in the checker constructor, but # we set them manually here so we don't have to monkey patch `setUp` - self.cf.options = {"enable_appendix_a_checks"} + self.cf.options = {"enable_appendix_a_checks": None} new_check = copy.deepcopy(self.cf) self.cf.setup(dataset) aa_results = self.cf.check_appendix_a(dataset) diff --git a/compliance_checker/tests/test_cli.py b/compliance_checker/tests/test_cli.py index 2788ec25..670e5b27 100644 --- a/compliance_checker/tests/test_cli.py +++ b/compliance_checker/tests/test_cli.py @@ -7,9 +7,12 @@ import json import os import platform +import shutil import subprocess import sys from argparse import Namespace +from collections import defaultdict +from importlib.machinery import SourceFileLoader import pytest @@ -268,3 +271,23 @@ def test_nczarr_pass_through(self, zarr_url): output_format="text", ) assert not errors + + +def test_parse_options(): + """Test the option parser of cchecker.py""" + # Load cchecker.py + cchecker = SourceFileLoader("cchecker", shutil.which("cchecker.py")).load_module() + # Simple test checker_type:checker_opt + opt_dict = cchecker.parse_options(["cf:enable_appendix_a_checks"]) + assert opt_dict == defaultdict(dict, {"cf": {"enable_appendix_a_checks": None}}) + # Test case checker_type:checker_opt:checker_val + opt_dict = cchecker.parse_options( + ["type:opt:val", "type:opt2:val:2", "cf:enable_appendix_a_checks"], + ) + assert opt_dict == defaultdict( + dict, + { + "type": {"opt": "val", "opt2": "val:2"}, + "cf": {"enable_appendix_a_checks": None}, + }, + )