From ee17657ad3290465e8255c8c9da7efbd5343f789 Mon Sep 17 00:00:00 2001 From: James Roy Date: Sat, 29 Mar 2025 13:03:26 +0800 Subject: [PATCH] edtlib: binding: Add a title keyword Add a 'title' keyword to the binding to provide a short description of the binding, while 'description' serves as the long description. Signed-off-by: James Roy --- doc/_scripts/gen_devicetree_rest.py | 9 +++- doc/build/dts/bindings-syntax.rst | 18 +++++++ doc/build/dts/bindings-upstream.rst | 2 + .../src/devicetree/edtlib.py | 52 +++++++++++++++---- .../tests/test-bindings/defaults.yaml | 2 + .../python-devicetree/tests/test_edtlib.py | 14 +++++ 6 files changed, 86 insertions(+), 11 deletions(-) diff --git a/doc/_scripts/gen_devicetree_rest.py b/doc/_scripts/gen_devicetree_rest.py index 9a28b591f29..908cfb0278b 100644 --- a/doc/_scripts/gen_devicetree_rest.py +++ b/doc/_scripts/gen_devicetree_rest.py @@ -580,7 +580,14 @@ def print_binding_page(binding, base_names, vnd_lookup, driver_sources,dup_compa {bus_help} ''', string_io) - print(to_code_block(binding.description.strip()), file=string_io) + + if binding.title: + description = ("\n\n" + .join([binding.title, binding.description]) + .strip()) + else: + description = binding.description.strip() + print(to_code_block(description), file=string_io) # Properties. print_block('''\ diff --git a/doc/build/dts/bindings-syntax.rst b/doc/build/dts/bindings-syntax.rst index def71687fb3..fea69ee5911 100644 --- a/doc/build/dts/bindings-syntax.rst +++ b/doc/build/dts/bindings-syntax.rst @@ -19,6 +19,16 @@ like this: .. code-block:: yaml + # When the description text is too long, this field can + # be used to improve readability, e.g.: + # + # title: Binding the device's hardware model. + # + # description | + # A piece of content with 20 lines. + # ... + title: Concise title for the long description [optional] + # A high level description of the device the binding applies to: description: | This is the Vendomatic company's foo-device. @@ -58,6 +68,14 @@ like this: These keys are explained in the following sections. +.. _dt-bindings-title: + +Title +***** + +Short description of the bound device, typically the hardware model. +(It's optional.) + .. _dt-bindings-description: Description diff --git a/doc/build/dts/bindings-upstream.rst b/doc/build/dts/bindings-upstream.rst index b33b7f92452..6a9cb666a42 100644 --- a/doc/build/dts/bindings-upstream.rst +++ b/doc/build/dts/bindings-upstream.rst @@ -83,6 +83,8 @@ style: .. code-block:: yaml + title: I'm sure you need a short title. + description: | My very long string goes here. diff --git a/scripts/dts/python-devicetree/src/devicetree/edtlib.py b/scripts/dts/python-devicetree/src/devicetree/edtlib.py index 3ab04c956e2..6c97c6f6d4c 100644 --- a/scripts/dts/python-devicetree/src/devicetree/edtlib.py +++ b/scripts/dts/python-devicetree/src/devicetree/edtlib.py @@ -114,8 +114,21 @@ class Binding: path: The absolute path to the file defining the binding. + title: + The free-form title of the binding (optional). + + When the content in the 'description:' is too long, the 'title:' can + be used as a heading for the extended description. Typically, it serves + as a description of the hardware model. For example: + + title: Nordic GPIO + + description: | + Descriptions and example nodes related to GPIO. + ... + description: - The free-form description of the binding, or None. + The free-form description of the binding. compatible: The compatible string the binding matches. @@ -173,7 +186,7 @@ class Binding: def __init__(self, path: Optional[str], fname2path: dict[str, str], raw: Any = None, require_compatible: bool = True, - require_description: bool = True): + require_description: bool = True, require_title: bool = False): """ Binding constructor. @@ -201,6 +214,12 @@ class Binding: "description:" line. If False, a missing "description:" is not an error. Either way, "description:" must be a string if it is present in the binding. + + require_title: + If True, it is an error if the binding does not contain a + "title:" line. If False, a missing "title:" is not an error. + Either way, "title:" must be a string if it is present in + the binding. """ self.path: Optional[str] = path self._fname2path: dict[str, str] = fname2path @@ -217,8 +236,8 @@ class Binding: self.raw: dict = self._merge_includes(raw, self.path) # Recursively initialize any child bindings. These don't - # require a 'compatible' or 'description' to be well defined, - # but they must be dicts. + # require a 'compatible', 'description' or 'title' to be well + # defined, but they must be dicts. if "child-binding" in raw: if not isinstance(raw["child-binding"], dict): _err(f"malformed 'child-binding:' in {self.path}, " @@ -232,7 +251,7 @@ class Binding: self.child_binding = None # Make sure this is a well defined object. - self._check(require_compatible, require_description) + self._check(require_compatible, require_description, require_title) # Initialize look up tables. self.prop2specs: dict[str, PropertySpec] = {} @@ -251,6 +270,11 @@ class Binding: basename = os.path.basename(self.path or "") return f"" + @property + def title(self) -> Optional[str]: + "See the class docstring" + return self.raw.get('title') + @property def description(self) -> Optional[str]: "See the class docstring" @@ -364,7 +388,8 @@ class Binding: return self._merge_includes(contents, path) - def _check(self, require_compatible: bool, require_description: bool): + def _check(self, require_compatible: bool, require_description: bool, + require_title: bool): # Does sanity checking on the binding. raw = self.raw @@ -378,6 +403,13 @@ class Binding: elif require_compatible: _err(f"missing 'compatible' in {self.path}") + if "title" in raw: + title = raw["title"] + if not isinstance(title, str) or not title: + _err(f"malformed or empty 'title' in {self.path}") + elif require_title: + _err(f"missing 'title' in {self.path}") + if "description" in raw: description = raw["description"] if not isinstance(description, str) or not description: @@ -387,8 +419,8 @@ class Binding: # Allowed top-level keys. The 'include' key should have been # removed by _load_raw() already. - ok_top = {"description", "compatible", "bus", "on-bus", - "properties", "child-binding"} + ok_top = {"title", "description", "compatible", "bus", + "on-bus", "properties", "child-binding"} # Descriptive errors for legacy bindings. legacy_errors = { @@ -398,7 +430,6 @@ class Binding: "parent": "use 'on-bus: ' instead", "parent-bus": "use 'on-bus: ' instead", "sub-node": "use 'child-binding' instead", - "title": "use 'description' instead", } for key in raw: @@ -3328,7 +3359,8 @@ _DEFAULT_PROP_BINDING: Binding = Binding( for name in _DEFAULT_PROP_TYPES }, }, - require_compatible=False, require_description=False, + require_compatible=False, + require_description=False, ) _DEFAULT_PROP_SPECS: dict[str, PropertySpec] = { diff --git a/scripts/dts/python-devicetree/tests/test-bindings/defaults.yaml b/scripts/dts/python-devicetree/tests/test-bindings/defaults.yaml index 4d3cfda3067..0a9b63a87ae 100644 --- a/scripts/dts/python-devicetree/tests/test-bindings/defaults.yaml +++ b/scripts/dts/python-devicetree/tests/test-bindings/defaults.yaml @@ -1,5 +1,7 @@ # SPDX-License-Identifier: BSD-3-Clause +title: Test binding + description: Property default value test compatible: "defaults" diff --git a/scripts/dts/python-devicetree/tests/test_edtlib.py b/scripts/dts/python-devicetree/tests/test_edtlib.py index 30fbc7c3fc3..8c826225c9b 100644 --- a/scripts/dts/python-devicetree/tests/test_edtlib.py +++ b/scripts/dts/python-devicetree/tests/test_edtlib.py @@ -531,6 +531,20 @@ def test_bus(): assert str(edt.get_node("/buses/foo-bus/node1/nested").binding_path) == \ hpath("test-bindings/device-on-foo-bus.yaml") +def test_binding_top_key(): + fname2path = {'include.yaml': 'test-bindings-include/include.yaml', + 'include-2.yaml': 'test-bindings-include/include-2.yaml'} + + with from_here(): + binding = edtlib.Binding("test-bindings/defaults.yaml", fname2path) + title = binding.title + description = binding.description + compatible = binding.compatible + + assert title == "Test binding" + assert description == "Property default value test" + assert compatible == "defaults" + def test_child_binding(): '''Test 'child-binding:' in bindings''' with from_here():