Skip to content
Open
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
15 changes: 10 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- `vspec compose` command: bundles a vspec model and all its dependencies (includes, overlays, units, quantities) into a self-contained snapshot folder. Validates the full expanded model before writing, then serialises the raw authored content faithfully — preserving `instances`, instance-level overrides, and custom struct types. See [compose.md](docs/compose.md).
- `vspec diff` command: compares two compose snapshots and reports every change (ADDED, REMOVED, MODIFIED) as structured JSON. Detects renames via the `fka` field and cascades them to child nodes automatically. See [diff.md](docs/diff.md).

### Changed

- **Breaking**: Unit descriptions (`unit` field) must now be globally unique across all quantities. Previously accepted duplicate unit descriptions in `units.yaml` will now fail validation with a clear error message identifying the conflicting unit keys.
Expand Down Expand Up @@ -40,7 +45,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Major restructure of repository structure and CLI

The vss-tools repository content structure and CLI has changed significantly
For more information see [vspec documentation](docs/vspec.md)
For more information see [vspec documentation](docs/export.md)

### Change in UUID handling.

Expand Down Expand Up @@ -106,7 +111,7 @@ In VSS-Tools 4.0 structs are supported in the following exporters:
Other exporters do not support structs.

It is possible to use specify muliple type files with `--types`, and to use types in combination with overlays.
For more information see [vspec documentation](docs/vspec.md)
For more information see [vspec documentation](docs/export.md)

### Change in UUID handling.

Expand All @@ -133,7 +138,7 @@ From now on, if new units are needed for the VSS catalog they shall be added to

Support for defining signals with struct type added.
For VSS 3.1 as experimental feature only supported by JSON exporter.
For more information see [vspec documentation](docs/vspec.md)
For more information see [vspec documentation](docs/export.md)

### Change in UUID handling.

Expand All @@ -155,7 +160,7 @@ The background is that they have been broken for a long period and no one has vo

### Support for specifying unit files

Add new parameter `-u` has been introduced, see [documentation](https://github.com/COVESA/vss-tools/blob/master/docs/vspec.md#handling-of-units).
Add new parameter `-u` has been introduced, see [documentation](https://github.com/COVESA/vss-tools/blob/master/docs/export.md#handling-of-units).
Use of default unit file deprecated.
At the same time a unit file has been added to [VSS](https://github.com/COVESA/vehicle_signal_specification/blob/master/spec/units.yaml),
allowing VSS tooling to control their own units rather than relying on units in VSS-tools.
Expand All @@ -169,4 +174,4 @@ file it will be used, only if not existing `config.yaml` in vss-tools will be us
### Overlay Support

Overlays introduced to allow customization of VSS. See [documentation](https://covesa.github.io/vehicle_signal_specification/rule_set/overlay/).
See [vss-tools documentation](https://github.com/COVESA/vss-tools/blob/master/docs/vspec.md) on how to include overlays when transforming VSS.
See [vss-tools documentation](https://github.com/COVESA/vss-tools/blob/master/docs/export.md) on how to include overlays when transforming VSS.
2 changes: 1 addition & 1 deletion FAQ.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
VSS standard catalog uses the units defined in [this file](https://github.com/COVESA/vehicle_signal_specification/blob/master/spec/units.yaml).
They are also described in the [VSS documentation](https://github.com/COVESA/vehicle_signal_specification/blob/master/docs-gen/content/rule_set/data_entry/data_units.md).

It is possible to replace the default units by the `--units/-u` parameter, see [vspec documentation](docs/vspec.md).
It is possible to replace the default units by the `--units/-u` parameter, see [vspec documentation](docs/export.md).

The syntax of the file with units shall follow this pattern:

Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,17 @@ vspec export --help
vspec export json --help
```

Please check [here](./docs/vspec.md) for generic info about exporters and their arguments
Please check [here](./docs/export.md) for generic info about exporters and their arguments
as well as [here](./docs/vspec_arch.md) for design decision, architecture and limitations.

vss-tools provides three top-level commands:

| Command | Description | Documentation |
|---|---|---|
| `vspec export` | Convert a vspec model to a target format (JSON, YAML, Protobuf, …) | [export.md](./docs/export.md) |
| `vspec compose` | Bundle a vspec model and all its dependencies into a self-contained snapshot folder | [compose.md](./docs/compose.md) |
| `vspec diff` | Compare two compose snapshots and report every change as structured JSON | [diff.md](./docs/diff.md) |

## Compatibility with VSS

The [COVESA VSS project repository](https://github.com/COVESA/vehicle_signal_specification) includes vss-tools as a submodule.
Expand Down
69 changes: 69 additions & 0 deletions docs/compose.md
Comment thread
erikbosch marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# compose

## What it does

A vspec model is typically spread across many files: a main `.vspec`, optional `#include` files, overlays, a units file, and a quantities file. `vspec compose` collapses all of that into a single **self-contained folder** (a _snapshot_) that any downstream `vspec export` command can consume without needing the original source tree.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe worth stating the differences compared to the "vspec export yaml" command, because they have some similarities, right? Like long term, do we need both?


```
vspec compose \
-u units.yaml \
-q quantities.yaml \
-s spec/Vehicle.vspec \
-l my_overlay.vspec \
--types types.vspec \
-o snapshot/
```

The output folder contains up to four files:

| File | Contains | Always written? |
|---|---|---|
| `model_snapshot.vspec` | The full model tree | Yes |
| `structs_snapshot.vspec` | Custom struct type definitions | Only when `--types` is given |
| `units_snapshot.yaml` | Resolved units | When `-u` is given or `units.yaml` exists next to the vspec |
| `quantities_snapshot.yaml` | Resolved quantities | When `-q` is given or `quantities.yaml` exists next to the vspec |

> Note: `vss-tools` automatically looks for `units.yaml` and `quantities.yaml` next to the vspec file when no `-u`/`-q` flags are given. If neither explicit paths nor default files can be found, the corresponding snapshot files are simply not written.


## How it works

```mermaid
flowchart TD
A[Source .vspec + overlays + units/quantities] --> B[Phase 1: validate\nget_trees expand=True]
B -->|invalid model| C[Abort with error]
B -->|valid| D[Phase 2: raw serialization\nload_vspec direct]
D --> E[snapshot/model_snapshot.vspec]
D --> F[snapshot/structs_snapshot.vspec]
D --> G[snapshot/units_snapshot.yaml]
D --> H[snapshot/quantities_snapshot.yaml]
```

**Phase 1 — Validation.** The full model is loaded with expansion enabled. All Pydantic type checks, unit validation, naming conventions, and structural rules run normally. If anything is wrong the command aborts with a clear error — the snapshot is never written.

**Phase 2 — Faithful serialization.** The raw authored YAML is read directly (without tree-building) and written as-is. This means:

- `instances` fields are preserved — the snapshot is not pre-expanded.
- Instance-level overrides (e.g. `Door.Row1.Left.IsOpen`) written in the source are kept exactly as authored.
- Only internal runtime fields (`delete`, `fqn`, `is_instance`) are stripped.
- No default values are injected — what you wrote is what you get.

## Using the snapshot

The primary use of such a snapshot could be a released artifact to complement the versioning and evolution of a domain data model.
Once the snapshot is taken, it becomes an inmutable reference.
Then, it becomes easier to report differences between two snapshopts.
See [diff command](./diff.md).

Because the snapshot is valid vspec, it feeds directly into any exporter:

```bash
vspec export json \
-u snapshot/units_snapshot.yaml \
-q snapshot/quantities_snapshot.yaml \
-s snapshot/model_snapshot.vspec \
--types snapshot/structs_snapshot.vspec \
-o output.json
```

This also means the snapshot round-trips cleanly: composing a snapshot of a snapshot produces identical output.
113 changes: 113 additions & 0 deletions docs/diff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# diff

## What it does

`vspec diff` compares two snapshots produced by `vspec compose` and reports every change as a structured JSON document. It is the foundation for tracking model evolution, generating changelogs, and feeding a spec registry.

```bash
vspec diff -p snapshot_v1/ -c snapshot_v2/
# or write to file:
vspec diff -p snapshot_v1/ -c snapshot_v2/ -o changes.json
```

## Output structure

```json
{
"previous": "snapshot_v1/",
"current": "snapshot_v2/",
"summary": { "ADDED": 2, "REMOVED": 1, "MODIFIED": 3 },
"changes": [ ... ]
}
```

Each entry in `changes` has:

| Field | Always present | Description |
|---|---|---|
| `type` | Yes | `ADDED`, `REMOVED`, or `MODIFIED` |
| `source` | Yes | `model`, `structs`, `units`, or `quantities` |
| `path` | Yes | FQN of the node in the **current** snapshot |
| `node_type` | Yes | e.g. `branch`, `sensor`, `actuator` |
| `message` | Yes | Human-readable description of the change |
| `previous_path` | MODIFIED renames only | FQN in the previous snapshot |
| `cascade` | MODIFIED renames only | `true` if this is a child of a renamed branch |
| `attribute_changes` | MODIFIED only | List of `{attribute, previous, current}` dicts |
| `attributes` | ADDED only | Full attribute dict of the new node |

## Change types

**ADDED** — a node exists in current but not in previous.

**REMOVED** — a node exists in previous but not in current.

**MODIFIED** — a node exists in both, but something changed. This covers:
- attribute value changes (datatype, unit, description, etc.)
- renames detected via the `fka` ("formerly known as") field

## Rename detection

Renames are detected automatically without requiring any extra flags.

```mermaid
flowchart LR
subgraph previous
A[A.Door\ntype: branch]
B[A.Door.IsOpen\ntype: actuator]
end
subgraph current
C["A.Portal\ntype: branch\nfka: A.Door"]
D[A.Portal.IsOpen\ntype: actuator]
end
A -- "fka match + same node_type" --> C
B -- "prefix substitution\ncascade: true" --> D
```

1. **Explicit rename** — if an added node has `fka: [old.path]` and the same `type` as the removed node, it is reported as `MODIFIED` with `previous_path`.
2. **Cascade** — children of a renamed branch are matched by FQN prefix substitution. Each cascaded child is independently checked for attribute changes too.

If `fka` is missing, or the node type doesn't match, the pair is reported as independent `REMOVED` + `ADDED`.

## Example output

```json
{
"type": "MODIFIED",
"source": "model",
"path": "A.Portal",
"previous_path": "A.Door",
"node_type": "branch",
"cascade": false,
"attribute_changes": [],
"message": "Branch 'A.Door' was renamed to 'A.Portal'."
},
{
"type": "MODIFIED",
"source": "model",
"path": "A.Portal.IsOpen",
"previous_path": "A.Door.IsOpen",
"node_type": "actuator",
"cascade": true,
"attribute_changes": [
{ "attribute": "datatype", "previous": "boolean", "current": "string" }
],
"message": "Actuator 'A.Door.IsOpen' was renamed to 'A.Portal.IsOpen' (cascaded from parent rename). Attribute 'datatype' changed from 'boolean' to 'string'."
}
```

## Typical workflow

```mermaid
flowchart LR
S[Source files\nvspec + overlays] --> C1[vspec compose\nv1.0]
S2[Updated source files] --> C2[vspec compose\nv2.0]
C1 --> SN1[snapshot_v1/]
C2 --> SN2[snapshot_v2/]
SN1 --> D[vspec diff]
SN2 --> D
D --> J[changes.json\nfed to registry / changelog]
```

## About braking changes
The `diff` command is intentionally reporting ANY change without dictating what constitudes a braking chage.
That distinction is to be handle elsewhere based on the diff report.
2 changes: 1 addition & 1 deletion docs/vspec.md → docs/export.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Instead instance information will be kept as additional information for the bran

### -e, --extended-attributes

See section on [overlays](vspec.md#handling-of-overlays-and-extensions) below
See section on [overlays](export.md#handling-of-overlays-and-extensions) below

### --strict-exceptions

Expand Down
6 changes: 3 additions & 3 deletions docs/samm.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,12 @@ Also, please keep in mind that since the CLI tool also provides functionality to

### Running this exporter in DEBUG or other mode

As per available functionality, provided by the [vspec](vspec.md), the DEFAULT mode of execution of this and other exporters is INFO.
As per available functionality, provided by the [vspec](export.md), the DEFAULT mode of execution of this and other exporters is INFO.

Other possible modes are: "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL".

In order to switch these when calling this exporter you can use the option: [--log-level](vspec.md#--log-level).
Also there is an option to redirect the console output i.e. logged information to a text file. To do so, you can use the [--log-file](vspec.md#--log-file) option.
In order to switch these when calling this exporter you can use the option: [--log-level](export.md#--log-level).
Also there is an option to redirect the console output i.e. logged information to a text file. To do so, you can use the [--log-file](export.md#--log-file) option.

A complete example, where you can call this exporter in DEBUG mode and store the logged information into a simple text file would be:

Expand Down
2 changes: 1 addition & 1 deletion noxfile.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import nox

PYTHON_VERSIONS = ["3.10", "3.13"]
PYTHON_VERSIONS = ["3.11", "3.13"]

nox.options.default_venv_backend = "uv"

Expand Down
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "vss-tools"
version = "7.0.0-dev0"
description='COVESA Vehicle Signal Specification tooling.'
readme = "README.md"
requires-python = ">=3.10"
requires-python = ">=3.11"
dependencies = [
"anytree>=2.12.1",
"click>=8.1.7",
Expand All @@ -18,6 +18,7 @@ dependencies = [
"pandas>=2.2.3",
"case-converter>=1.2.0",
"inflect>=7.5.0",
"ddmodl",
]
authors = [
{name="COVESA VSS", email="covesa-dev@covesa.global"}
Expand Down Expand Up @@ -45,6 +46,7 @@ line-length = 120
[tool.uv]
package = true


[tool.ruff.lint]
select = ["E", "F", "I"]

Expand Down
10 changes: 9 additions & 1 deletion src/vss_tools/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@
from vss_tools.lazy_group import LazyGroup


@click.group(context_settings={"auto_envvar_prefix": "vss_tools"}, invoke_without_command=True)
@click.group(
cls=LazyGroup,
lazy_subcommands={

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the lazy annotations here. Just make the commands "normal" ones by using @cli.command annotations for them

"compose": "vss_tools.compose:cli",
"diff": "vss_tools.diff_cmd:cli",
},
context_settings={"auto_envvar_prefix": "vss_tools"},
invoke_without_command=True,
)
@clo.log_level_opt
@clo.log_file_opt
@click.version_option()
Expand Down
Loading
Loading