diff --git a/CHANGES b/CHANGES index f5890f6c..14856fed 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,16 @@ +2.3.3: +Bug Fixes: +* Subtraction Collector math had no effect when the RHS was a list of scalar + values (because LHS was a list of NodeCoords, so comparison was always + false). Also reduced O(3N) to O(2N) during Collector subtraction. + +Enhancements: +* The console logger's debug method now includes the type of each element in a + list while it is being dumped. + 2.3.2: Bug Fixes: -* Subtraction collector math crashed when the RHS result was non-iterable. +* Subtraction Collector math crashed when the RHS result was non-iterable. 2.3.1: Bug Fixes: diff --git a/setup.py b/setup.py index d2fab542..743f156b 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="yamlpath", - version="2.3.2", + version="2.3.3", description="Read and change YAML/Compatible data using powerful, intuitive, command-line friendly syntax", long_description=long_description, long_description_content_type="text/markdown", diff --git a/tests/test_processor.py b/tests/test_processor.py index 681f2304..e637a78b 100644 --- a/tests/test_processor.py +++ b/tests/test_processor.py @@ -551,3 +551,39 @@ def test_get_singular_collectors(self, quiet_logger, yamlpath, results): assert unwrap_node_coords(node) == results[matchidx] matchidx += 1 assert len(results) == matchidx + + @pytest.mark.parametrize("yamlpath,results", [ + ("(/list1) + (/list2)", [[1, 2, 3, 4, 5, 6]]), + ("(/list1) - (/exclude)", [[1, 2]]), + ("(/list2) - (/exclude)", [[5, 6]]), + ("(/list1) + (/list2) - (/exclude)", [[1, 2, 5, 6]]), + ("((/list1) + (/list2)) - (/exclude)", [[1, 2, 5, 6]]), + ("(/list1) + ((/list2) - (/exclude))", [[1, 2, 3, 5, 6]]), + ("((/list1) - (/exclude)) + ((/list2) - (/exclude))", [[1, 2, 5, 6]]), + ("(((/list1) - (/exclude)) + ((/list2) - (/exclude)))[2]", [5]), + ]) + def test_scalar_collectors(self, quiet_logger, yamlpath, results): + yamldata = """--- + list1: + - 1 + - 2 + - 3 + list2: + - 4 + - 5 + - 6 + exclude: + - 3 + - 4 + """ + yaml = YAML() + processor = Processor(quiet_logger, yaml.load(yamldata)) + matchidx = 0 + # Note that Collectors deal with virtual DOMs, so mustexist must always + # be set True. Otherwise, ephemeral virtual nodes would be created and + # discarded. Is this desirable? Maybe, but not today. For now, using + # Collectors without setting mustexist=True will be undefined behavior. + for node in processor.get_nodes(yamlpath, mustexist=True): + assert unwrap_node_coords(node) == results[matchidx] + matchidx += 1 + assert len(results) == matchidx diff --git a/tests/test_wrappers_consoleprinter.py b/tests/test_wrappers_consoleprinter.py index 97e35637..4d0fd371 100644 --- a/tests/test_wrappers_consoleprinter.py +++ b/tests/test_wrappers_consoleprinter.py @@ -64,8 +64,8 @@ def test_debug_noisy(self, capsys): logger.debug(["test", anchoredval]) console = capsys.readouterr() assert "\n".join([ - "DEBUG: [0]=test", - "DEBUG: [1]=TestVal; &Anchor", + "DEBUG: [0]=test ", + "DEBUG: [1]=TestVal; &Anchor ", ]) + "\n" == console.out logger.debug({"ichi": 1, anchoredkey: anchoredval}) diff --git a/yamlpath/processor.py b/yamlpath/processor.py index 6b8be3e7..ba4e15e2 100644 --- a/yamlpath/processor.py +++ b/yamlpath/processor.py @@ -10,6 +10,7 @@ build_next_node, make_new_node, search_matches, + unwrap_node_coords, ) from yamlpath import YAMLPath from yamlpath.path import SearchTerms, CollectorTerms @@ -523,28 +524,26 @@ def _get_nodes_by_collector( data, peek_path, 0, parent, parentref): if (isinstance(node_coord, NodeCoords) and isinstance(node_coord.node, list)): - for coord in node_coord.node: + for coord_idx, coord in enumerate(node_coord.node): + if not isinstance(coord, NodeCoords): + coord = NodeCoords( + coord, node_coord.node, coord_idx) node_coords.append(coord) else: node_coords.append(node_coord) elif peek_attrs.operation == CollectorOperators.SUBTRACTION: - rem_node_coords = [] + rem_data = [] for node_coord in self._get_required_nodes( data, peek_path, 0, parent, parentref): - rem_node_coords.append(node_coord) - - # Removal requires looking only at the data element - rem_data = [] - for rem_node_coord in rem_node_coords: - just_data = rem_node_coord.node - if isinstance(just_data, list): - for just_element in just_data: - rem_data.append(just_element) + unwrapped_data = unwrap_node_coords(node_coord) + if isinstance(unwrapped_data, list): + for unwrapped_datum in unwrapped_data: + rem_data.append(unwrapped_datum) else: - rem_data.append(just_data) + rem_data.append(unwrapped_data) node_coords = [e for e in node_coords - if e.node not in rem_data] + if unwrap_node_coords(e) not in rem_data] else: raise YAMLPathException( "Adjoining Collectors without an operator has no" diff --git a/yamlpath/wrappers/consoleprinter.py b/yamlpath/wrappers/consoleprinter.py index e4601a25..41b3b75b 100644 --- a/yamlpath/wrappers/consoleprinter.py +++ b/yamlpath/wrappers/consoleprinter.py @@ -149,7 +149,7 @@ def debug(self, message): if hasattr(ele, "anchor") and ele.anchor.value is not None: attr = "; &" + ele.anchor.value eattr = (str(ele) + attr).replace("\n", "\nDEBUG: ") - print("DEBUG: [" + str(i) + "]=" + str(eattr)) + print("DEBUG: [{}]={} {}".format(i, eattr, type(ele))) elif isinstance(message, dict): for key, val in message.items(): key_anchor = (