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
37 changes: 27 additions & 10 deletions docs/internals/extensions/metamodel.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(metamodel)=
# score_metamodel

The `score_metamodel` extension is a core extension/component of the Docs-As-Code.
The `score_metamodel` extension is a core extension/component of the Docs-As-Code.
It provides metamodel definitions, validation checks, and project layout management for Sphinx documentation.

## Overview
Expand Down Expand Up @@ -33,7 +33,7 @@ The extension implements a multi-tier checking system:
- Require access to the complete needs graph
- Examples: Link validation, dependency checking, cross-reference verification

This extension comes with Docs-As-Code.
This extension comes with Docs-As-Code.
Add `score_metamodel` to your extensions in `conf.py`:

```python
Expand All @@ -44,6 +44,23 @@ extensions = [
]
```

## need types

Each type of needs is defined in the `needs_types` section of the `metamodel.yaml` file. Each need type has attributes, links, tags, and other properties that define its structure and behavior within the documentation system.

Each need type is introduced via `<type-name>:` followed by its properties indented under it.

Properties:
- **title**: The title of the need type.
- **prefix**: A unique prefix used to identify the need type. Default is the type name followed by `__`.
- **mandatory_options**: A list of mandatory options that must be provided for the need type.
`id` is worth mentioning as it is automatically included and must be unique. Default is the prefix followed by `[a-z0-9_]`.
- **optional_options**: A list of optional options that can be provided for the need type.
- **mandatory_links**: A list of mandatory links to other need types that must be included.
- **optional_links**: A list of optional links to other need types that can be included.
- **tags**: A list of tags associated with the need type.
- **parts**: The number of parts (separated by `__`) within the need ID.

## Creating New Validation Checks

The extension automatically discovers checks from the `checks/` directory and the metamodel.yaml config. There are several types of checks you can implement:
Expand Down Expand Up @@ -73,23 +90,23 @@ needs_types:
```

### 2. Generic Graph Checks (Configuration-Based)
Generic graph checks are defined in the metamodel.yaml under `graph_checks`.
Generic graph checks are defined in the metamodel.yaml under `graph_checks`.
These checks all follow the same structure:

```yaml
<name of the check>:
needs:
include: <need1>, <need2> #list of your needs
include: <need1>, <need2> #list of your needs
condition: <your condition(s) that need to be fulfilled>
check:
<link attribute to check>: <condition to be checked in each need inside the link attribute>
explanation: <A short sentence that explains what is required to be adhered to. This will be
explanation: <A short sentence that explains what is required to be adhered to. This will be
< part of the error message if the check fails>
```

> *Note:* You can also use multiple conditions or negate conditions in either the needs or check part.

A complete example might look like so:
A complete example might look like so:

```yaml
graph_checks:
Expand All @@ -101,14 +118,14 @@ graph_checks:
- safety != QM
- status == valid
check:
implements:
implements:
and:
- safety != QM
- status == valid
explanation: An safety architecture element can only link other safety architecture elements.
```

What does this check do?
What does this check do?
This check will go through each of the needs mentioned in 'include' that match the condition, and then for every single one of them check the needs that are linked inside the 'implements' attribute. Go inside those needs and check if they also fulfill the condition described.
If one of them does not fulfill the condition the check fails and will let you know with a warning that it did so.

Expand All @@ -126,7 +143,7 @@ prohibited_words_checks:
- < word to forbid >
```

An example might look like this:
An example might look like this:
```yaml
prohibited_words_checks:
content_check:
Expand Down Expand Up @@ -201,7 +218,7 @@ score_metamodel/
├── __init__.py
├── rst
│ ├── attributes
│ │ └── ...
│ │ └── ...
│ ├── conf.py
│ ├── graph
│ │ └── test_metamodel_graph.rst
Expand Down
64 changes: 25 additions & 39 deletions src/extensions/score_metamodel/checks/check_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,28 +46,18 @@ def _validate_value_pattern(
pattern: str,
need: NeedsInfoType,
field: str,
log: CheckLogger,
as_info: bool = False,
) -> None:
):
"""Check if a value matches the given pattern and log the result.

If ``as_info`` is True, mismatches are reported as info (non-failing)
messages, otherwise as warnings.
Returns true if the value matches the pattern, False otherwise.
"""
try:
if not re.match(pattern, value):
log.warning_for_option(
need,
field,
f"does not follow pattern `{pattern}`.",
is_new_check=as_info,
)
except TypeError:
log.warning_for_option(
need,
field,
f"pattern `{pattern}` is not a valid regex pattern.",
)
return re.match(pattern, value) is not None
except TypeError as e:
raise TypeError(
f"Error in metamodel.yaml at {need['type']}->{field}: "
f"pattern `{pattern}` is not a valid regex pattern."
) from e


def validate_fields(
Expand Down Expand Up @@ -102,23 +92,21 @@ def remove_prefix(word: str, prefixes: list[str]) -> str:
log.warning_for_need(
need, f"is missing required {field_type}: `{field}`."
)
continue # Skip empty optional fields
# Try except used to add more context to Error without passing variables
# just for that to function
try:
values = _normalize_values(raw_value)
except ValueError as err:
raise ValueError(
f"An Attribute inside need {need['id']} is "
"not of type str. Only Strings are allowed"
) from err
# The filter ensures that the function is only called when needed.
continue # Nothing to validate if not present

values = _normalize_values(raw_value)

for value in values:
if allowed_prefixes:
value = remove_prefix(value, allowed_prefixes)
_validate_value_pattern(
value, pattern, need, field, log, as_info=optional_link_as_info
)
if not _validate_value_pattern(value, pattern, need, field):
msg = f"does not follow pattern `{pattern}`."
log.warning_for_option(
need,
field,
msg,
is_new_check=optional_link_as_info,
)


# req-Id: tool_req__docs_req_attr_reqtype
Expand All @@ -137,19 +125,17 @@ def check_options(
Checks that required and optional options and links are present
and follow their defined patterns.
"""
production_needs_types = app.config.needs_types

need_options = get_need_type(production_needs_types, need["type"])
need_type = get_need_type(app.config.needs_types, need["type"])

# If undefined this is an empty list
allowed_prefixes = app.config.allowed_external_prefixes

# Validate Options and Links
field_validations = [
("option", need_options["mandatory_options"], True),
("option", need_options["optional_options"], False),
("link", need_options["mandatory_links"], True),
("link", need_options["optional_links"], False),
("option", need_type["mandatory_options"], True),
("option", need_type["optional_options"], False),
("link", need_type["mandatory_links"], True),
("link", need_type["optional_links"], False),
]

for field_type, field_values, is_required in field_validations:
Expand Down
Loading