From 3eeaf970071c7d06087abc954c92f07bce540356 Mon Sep 17 00:00:00 2001 From: Aleix Pol i Gonzalez Date: Fri, 16 Oct 2026 15:01:34 +0200 Subject: [PATCH 1/2] Add support for composing strings When composing onto a string allow prepending and appending onto them. It uses the same operators we use for lists (<), (=) and (>). --- src/buildstream/node.pxd | 1 + src/buildstream/node.pyx | 23 +++++++++++++++++++++++ tests/internals/yaml.py | 14 ++++++++++++++ tests/internals/yaml/stringappend.yaml | 3 +++ 4 files changed, 41 insertions(+) create mode 100644 tests/internals/yaml/stringappend.yaml diff --git a/src/buildstream/node.pxd b/src/buildstream/node.pxd index d1cc94547..56531167e 100644 --- a/src/buildstream/node.pxd +++ b/src/buildstream/node.pxd @@ -65,6 +65,7 @@ cdef class MappingNode(Node): # Protected Methods cdef void _compose_on_composite_dict(self, MappingNode target) cdef void _compose_on_list(self, SequenceNode target) + cdef void _compose_on_string(self, ScalarNode target) # Private Methods cdef void __composite(self, MappingNode target, list path) except * diff --git a/src/buildstream/node.pyx b/src/buildstream/node.pyx index 761e7b775..b71ad6909 100644 --- a/src/buildstream/node.pyx +++ b/src/buildstream/node.pyx @@ -977,6 +977,9 @@ cdef class MappingNode(Node): if type(target_value) is SequenceNode: # Composite list composes into a list self._compose_on_list(target_value) + elif isinstance(( target_value).value, str): + # Composite list composes into a string + self._compose_on_string(target_value) elif target_value._is_composite_list(): # Composite list merges into composite list self._compose_on_composite_dict(target_value) @@ -1016,6 +1019,26 @@ cdef class MappingNode(Node): if suffix is not None: target.value.extend(suffix.value) + # _compose_on_string(target) + # + # Compose the current node on the given string. + # + # Args: + # target (.SequenceNode): sequence on which to compose the current composite dict + # + cdef void _compose_on_string(self, ScalarNode target): + cdef ScalarNode clobber = self.value.get("(=)") + cdef ScalarNode prefix = self.value.get("(<)") + cdef ScalarNode suffix = self.value.get("(>)") + + if clobber is not None: + target.value.clear() + target.value.extend(clobber.value) + if prefix is not None: + target.value = prefix.value + target.value + if suffix is not None: + target.value += suffix.value + # _compose_on_composite_dict(target) # # Compose the current node on the given composite dict. diff --git a/tests/internals/yaml.py b/tests/internals/yaml.py index 6e38adb70..fa2b50e2c 100644 --- a/tests/internals/yaml.py +++ b/tests/internals/yaml.py @@ -184,6 +184,20 @@ def test_composite_preserve_originals(datafiles): assert orig_extra.get_str("old") == "new" +@pytest.mark.datafiles(os.path.join(DATA_DIR)) +def test_composite_strings(datafiles): + + filename = os.path.join(datafiles, "basics.yaml") + overlayfile = os.path.join(datafiles, "stringappend.yaml") + + base = _yaml.load(filename, shortname=None) + overlay = _yaml.load(overlayfile, shortname=None) + base_copy = base.clone() + overlay._composite(base_copy) + + assert base_copy.get_str("description") == ("incoming potato(The vehicle of choice for rainbow travel)potato") + + # Tests for list composition # # Each test composits a filename on top of basics.yaml, and tests diff --git a/tests/internals/yaml/stringappend.yaml b/tests/internals/yaml/stringappend.yaml new file mode 100644 index 000000000..a7f714517 --- /dev/null +++ b/tests/internals/yaml/stringappend.yaml @@ -0,0 +1,3 @@ +description: + (>): ")potato" + (<): "incoming potato(" From 915952375367444b1f5b589a3d361c16d57eddae Mon Sep 17 00:00:00 2001 From: Aleix Pol i Gonzalez Date: Thu, 22 Oct 2026 03:49:56 +0200 Subject: [PATCH 2/2] Document that append/prepend/overwrite are not just for lists They are for strings too now! --- doc/source/format_intro.rst | 48 +++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/doc/source/format_intro.rst b/doc/source/format_intro.rst index 9c19225d4..c73e4f316 100644 --- a/doc/source/format_intro.rst +++ b/doc/source/format_intro.rst @@ -245,13 +245,18 @@ author to assert some invalid configurations. .. _format_directives_list_prepend: -(<) List Prepend +(<) Prepend ~~~~~~~~~~~~~~~~ -Indicates that the list should be prepended to the target list, -instead of the default behavior which is to replace the target list. +Indicates that the value should be prepended to the target value, +instead of the default behaviour which is to replace the target value. + +When applying to a list, it will insert the new list items before the target list items. +When applying to a string, it will prefix the new string onto the target string. **Example:** +For lists: + .. code:: yaml config: @@ -264,10 +269,24 @@ instead of the default behavior which is to replace the target list. .. _format_directives_list_append: -(>) List Append +For strings: + +.. code:: yaml + + string-property: + (<): "start" + +.. _format_directives_string_append: + +(>) Append ~~~~~~~~~~~~~~~ -Indicates that the list should be appended to the target list, instead -of the default behavior which is to replace the target list. +Indicates that the values should be appended to its target, instead +of the default behavior which is to replace it. + + +When applying to a list, it will place the new list items at the end of the list. +When applying to a string, it will place the string value as a postfix. + **Example:** @@ -285,21 +304,20 @@ of the default behavior which is to replace the target list. .. _format_directives_list_overwrite: -(=) List Overwrite +(=) Overwrite ~~~~~~~~~~~~~~~~~~ -Indicates that the list should be overwritten completely. +Indicates that the value should be overwritten completely. -This exists mostly for completeness, and we recommend using literal -lists most of the time instead of list overwrite directives when the -intent is to overwrite a list. +This exists mostly for completeness, and we recommend using literals +instead of the overwrite directive when the intent is to overwrite. -This has the same behavior as a literal list, except that an +This has the same behavior as a literal, except that an error will be triggered in the case that there is no underlying -list to overwrite; whereas a literal list will simply create a new -list. +property to overwrite; whereas a literal entry will simply create a new +property. The added error protection can be useful when intentionally -overwriting a list in an element's *public data*, which is mostly +overwriting a value in an element's *public data*, which is mostly free form and not validated.