Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ For new fields the following can be defined:
If specified, these will be evaluated in order for any need that does not explicitly set the field, with the first match setting the field value.
- ``default``: A default value for the field (optional).
If specified, this value will be used for any need that does not explicitly set the field and does not match any predicates.
- ``parse_variants``: If set to ``True``, the field will support :ref:`variant options <needs_variant_support>`.
- ``parse_variants``: If set to ``True``, the field will support :ref:`variant options <needs_variant_support>` and :ref:`variant data references <needs_variant_data_references>`.
Default: ``False``.
- ``parse_dynamic_functions``: If set to ``True``, the field will support :ref:`dynamic_functions`.
Default: the value of :ref:`needs_parse_dynamic_functions` (``True``).
Expand Down Expand Up @@ -378,7 +378,7 @@ Each configured link can define:
If specified, these will be evaluated in order for any need that does not explicitly set the field, with the first match setting the field value.
- ``default`` (optional): A default value for the field.
If specified, this value will be used for any need that does not explicitly set the field and does not match any predicates.
- ``parse_variants``: If set to ``True``, the field will support :ref:`variant options <needs_variant_support>`.
- ``parse_variants``: If set to ``True``, the field will support :ref:`variant options <needs_variant_support>` and :ref:`variant data references <needs_variant_data_references>`.
Default: ``False``.
- ``parse_dynamic_functions``: If set to ``True``, the field will support :ref:`dynamic_functions`.
Default: the value of :ref:`needs_parse_dynamic_functions` (``True``).
Expand Down Expand Up @@ -618,6 +618,9 @@ The data is then available in filter expressions via the ``var`` namespace:

Accessing a missing key raises an ``AttributeError``, which helps catch typos in filter expressions.

The ``var`` namespace can also be referenced directly within need field values using the
:ref:`<{ ... }> syntax <needs_variant_data_references>`.

Default: ``{}``

.. seealso::
Expand Down Expand Up @@ -2905,7 +2908,7 @@ Each configured link should define:
If specified, these will be evaluated in order for any need that does not explicitly set the field, with the first match setting the field value.
- ``default`` (optional): A default value for the field.
If specified, this value will be used for any need that does not explicitly set the field and does not match any predicates.
- ``parse_variants``: If set to ``True``, the field will support :ref:`variant options <needs_variant_support>`.
- ``parse_variants``: If set to ``True``, the field will support :ref:`variant options <needs_variant_support>` and :ref:`variant data references <needs_variant_data_references>`.
Default: ``False``.
- ``parse_dynamic_functions``: If set to ``True``, the field will support :ref:`dynamic_functions`.
Default: the value of :ref:`needs_parse_dynamic_functions` (``True``).
Expand Down
81 changes: 81 additions & 0 deletions docs/dynamic_functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,84 @@ Below is an implementation of variants for need options:
:collapse:

Variants for need options in action

.. _needs_variant_data_references:

Variant data references
=======================

.. versionadded:: 8.2.0

In addition to :ref:`variant functions <needs_variant_support>` (``<<...>>``),
you can inject values directly from :ref:`needs_variant_data` into a need field
using the ``<{ ... }>`` syntax.
The referenced value is looked up and substituted into the field value.

.. important::

This feature uses the same enablement as variant functions:
you must set the :ref:`needs_fields` or :ref:`needs_links` configuration
parameter's ``parse_variants`` option to ``True`` for the specific field.

Rules for variant data references
---------------------------------

* References must be wrapped in ``<{`` and ``}>`` symbols, like ``<{ var.cpu }>``.
* The reference must be a dotted path rooted at the ``var`` namespace, which
exposes the :ref:`needs_variant_data`,
via key lookup via dot notation (``var.build.optimization``).
* Accessing a missing key, or a path that does not resolve to a leaf value will emit a warning.
* The resolved value is type-validated against the schema of the field
(or, for array fields, against the schema of the array items).
A warning is emitted if the type does not match.
* References can be used for scalar, string, array, and link fields.
* For string fields, a reference can be embedded within surrounding text, and the
parts are joined with spaces.
For non-string scalar fields, the reference must make up the entire value, so
that the resolved value keeps its native type (e.g. an integer).

Use Cases
---------

In your ``conf.py``, enable ``parse_variants`` for the fields you want to use
references in, and define the :ref:`needs_variant_data`:

.. code-block:: python

needs_variant_data = {
"platform": "arm",
"build": {"opt_level": 2},
}

needs_fields = {
"arch": {
"schema": {"type": "string"},
"parse_variants": True,
},
"opt": {
"schema": {"type": "integer"},
"parse_variants": True,
},
}

In your ``.rst`` file, reference the variant data within field values:

.. code-block:: rst

.. req:: Example
:id: VD_001
:arch: <{ var.platform }>
:opt: <{ var.build.opt_level }>

In the example above, ``arch`` resolves to the string ``"arm"`` and ``opt``
resolves to the integer ``2``.

A reference can also be embedded within a larger string value:

.. code-block:: rst

.. req:: Example
:id: VD_002
:arch: platform is <{ var.platform }>

Here ``arch`` resolves to ``"platform is arm"``.
9 changes: 7 additions & 2 deletions sphinx_needs/api/need.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from sphinx_needs.nodes import Need
from sphinx_needs.roles.need_part import find_parts, update_need_with_parts
from sphinx_needs.utils import jinja_parse
from sphinx_needs.variant_data import VariantDataParsed
from sphinx_needs.variants import VariantFunctionParsed
from sphinx_needs.views import NeedsView

Expand Down Expand Up @@ -1002,14 +1003,18 @@ def _copy_links(
"""Implement 'copy' logic for links."""
if "links" not in links:
return # should not happen, but be defensive
copy_links: list[NeedLink | DynamicFunctionParsed | VariantFunctionParsed] = []
copy_links: list[
NeedLink | DynamicFunctionParsed | VariantFunctionParsed | VariantDataParsed
] = []
for link_field in schema.iter_link_fields():
if link_field.copy and link_field.name != "links":
other = links[link_field.name]
if isinstance(other, LinksLiteralValue | LinksFunctionArray):
copy_links.extend(other.value)
if any(
isinstance(li, DynamicFunctionParsed | VariantFunctionParsed)
isinstance(
li, DynamicFunctionParsed | VariantFunctionParsed | VariantDataParsed
)
for li in copy_links
):
if links["links"] is None:
Expand Down
46 changes: 46 additions & 0 deletions sphinx_needs/functions/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@
from sphinx_needs.need_item import NeedItem, NeedLink, NeedPartItem
from sphinx_needs.nodes import Need
from sphinx_needs.roles.need_func import NeedFunc
from sphinx_needs.variant_data import (
VariantDataError,
VariantDataParsed,
lookup_variant_data,
)
from sphinx_needs.variants import VariantFunctionParsed
from sphinx_needs.views import NeedsView

Expand Down Expand Up @@ -344,6 +349,27 @@ def resolve_functions(
resolved.extend(var_return)
else:
resolved.append(var_return)
elif isinstance(item, VariantDataParsed):
vd_return = _get_variant_data(item, needs_config.variant_data)
if not (
field_schema.type_check(vd_return)
or (
field_schema.type == "array"
and field_schema.type_check_item(vd_return)
)
):
raise ValueError(
f"variant data value {type(vd_return)} is not of type {field_schema.type!r}"
+ (
""
if field_schema.type != "array"
else f" or item type {field_schema.item_type!r}"
)
)
if isinstance(vd_return, list | tuple):
resolved.extend(vd_return)
else:
resolved.append(vd_return)
else:
resolved.append(item)

Expand Down Expand Up @@ -379,6 +405,26 @@ def _get_variant(
return variant.final_value


def _get_variant_data(
variant_data: VariantDataParsed,
variant_data_context: dict[str, Any],
) -> Any:
"""Resolve a variant data reference against the ``var`` namespace.

The reference is a constrained dotted ``var.*`` path (no arbitrary
expression evaluation); it is resolved by walking the variant data mapping.

:param variant_data: The parsed variant data reference.
:param variant_data_context: The resolved variant data mapping.
:returns: The resolved value.
:raises ValueError: if the reference is invalid or cannot be resolved.
"""
try:
return lookup_variant_data(variant_data_context, variant_data.expression)
except VariantDataError as exc:
raise ValueError(str(exc)) from exc


def check_and_get_content(
content: str,
need: NeedItem | NeedPartItem | None,
Expand Down
Loading
Loading