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 <rruuaanng@outlook.com>
This commit is contained in:
James Roy 2025-03-29 13:03:26 +08:00 committed by Benjamin Cabé
parent b1775914d6
commit ee17657ad3
6 changed files with 86 additions and 11 deletions

View File

@ -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('''\

View File

@ -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

View File

@ -83,6 +83,8 @@ style:
.. code-block:: yaml
title: I'm sure you need a short title.
description: |
My very long string
goes here.

View File

@ -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"<Binding {basename}" + compat + ">"
@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: <bus>' instead",
"parent-bus": "use 'on-bus: <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] = {

View File

@ -1,5 +1,7 @@
# SPDX-License-Identifier: BSD-3-Clause
title: Test binding
description: Property default value test
compatible: "defaults"

View File

@ -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():