From 35c4b47850097f801e9e40cd577dc5ea15b9fa53 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:29:24 +0000 Subject: [PATCH 01/35] chore: establish a basic structure for document generation --- .github/workflows/continuous-integration.yml | 4 +++ .github/workflows/wc-document-generation.yml | 27 ++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 .github/workflows/wc-document-generation.yml diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 5f9f7f84..8c19c378 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -25,3 +25,7 @@ jobs: id-token: write packages: write pull-requests: write + document-generation: + uses: ./.github/workflows/wc-document-generation.yml + permissions: + contents: read diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml new file mode 100644 index 00000000..68255e09 --- /dev/null +++ b/.github/workflows/wc-document-generation.yml @@ -0,0 +1,27 @@ +--- +name: Document Generation + +on: + workflow_call: + inputs: + flavor: + required: true + type: string + +permissions: + contents: read + +jobs: + generate-documentation: + runs-on: ubuntu-latest + steps: + - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 + with: + disable-sudo-and-containers: true + egress-policy: audit + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + persist-credentials: false + - run: | + set -Eeuo pipefail + python -m pip install gherkin-official https://sbdl.dev/sbdl-package.tar.gz From 971710ed9d5630fc935db3a5a95a2e7dfd85522c Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Mon, 23 Jun 2025 12:57:45 +0000 Subject: [PATCH 02/35] chore: playing around with location and granularity --- .github/workflows/continuous-integration.yml | 4 ---- .github/workflows/wc-build-push-test.yml | 5 +++++ .github/workflows/wc-document-generation.yml | 4 ---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 8c19c378..5f9f7f84 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -25,7 +25,3 @@ jobs: id-token: write packages: write pull-requests: write - document-generation: - uses: ./.github/workflows/wc-document-generation.yml - permissions: - contents: read diff --git a/.github/workflows/wc-build-push-test.yml b/.github/workflows/wc-build-push-test.yml index d247d218..a79c6c4f 100644 --- a/.github/workflows/wc-build-push-test.yml +++ b/.github/workflows/wc-build-push-test.yml @@ -88,3 +88,8 @@ jobs: - uses: EnricoMi/publish-unit-test-result-action@3a74b2957438d0b6e2e61d67b05318aa25c9e6c6 # v2.20.0 with: files: test-report-*.xml + + generate-documents: + uses: ./.github/workflows/wc-document-generation.yml + permissions: + contents: read diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index 68255e09..3c804ece 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -3,10 +3,6 @@ name: Document Generation on: workflow_call: - inputs: - flavor: - required: true - type: string permissions: contents: read From 4781fa6bc5d04b894167871e2c591f72a9a80691 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Tue, 24 Jun 2025 05:31:18 +0000 Subject: [PATCH 03/35] chore: add gherkin extraction --- .github/workflows/wc-document-generation.yml | 4 ++ docs/support/gherkin-to-csv.py | 75 ++++++++++++++++++++ test/cpp/features/compilation.feature | 38 ++++++++-- 3 files changed, 111 insertions(+), 6 deletions(-) create mode 100755 docs/support/gherkin-to-csv.py diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index 3c804ece..fb57e51a 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -21,3 +21,7 @@ jobs: - run: | set -Eeuo pipefail python -m pip install gherkin-official https://sbdl.dev/sbdl-package.tar.gz + - run: | + set -Eeuo pipefail + python docs/support/gherkin-to-csv.py test/cpp/features/*.feature + python -m csv-to-sbdl --identifier 0 --description 1 --skipheader rules.csv diff --git a/docs/support/gherkin-to-csv.py b/docs/support/gherkin-to-csv.py new file mode 100755 index 00000000..d2e71da7 --- /dev/null +++ b/docs/support/gherkin-to-csv.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +import argparse +import csv +import os +import sys +from dataclasses import dataclass, asdict, fields +from typing import List +from gherkin.parser import Parser +from gherkin.token_scanner import TokenScanner + +@dataclass +class Rule: + """A class representing a rule extracted from a Gherkin feature file.""" + identifier: str + description: str + + @classmethod + def field_names(cls) -> List[str]: + return [f.name for f in fields(cls)] + +def extract_rules_from_feature(file_path): + """Parse a Gherkin feature file and extract the rules.""" + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + + parser = Parser() + try: + feature_document = parser.parse(TokenScanner(content)) + rules = [] + + if 'feature' in feature_document: + feature = feature_document['feature'] + + if 'children' in feature: + for child in feature['children']: + if 'rule' in child: + rule = child['rule'] + rule_id = rule.get('name', '').strip() + description = rule.get('description', '').strip() + rules.append(Rule( + identifier=rule_id, + description=description + )) + + return rules + except Exception as e: + print(f"Error parsing {file_path}: {e}", file=sys.stderr) + return [] + +def main(): + parser = argparse.ArgumentParser(description='Extract rules from Gherkin feature files and save to CSV') + parser.add_argument('feature_files', nargs='+', help='Paths to feature files') + parser.add_argument('--output', '-o', default='rules.csv', help='Output CSV file path') + + args = parser.parse_args() + all_rules = [] + + for feature_path in args.feature_files: + if os.path.isfile(feature_path): + print(f"Processing {feature_path}") + rules = extract_rules_from_feature(feature_path) + all_rules.extend(rules) + else: + print(f"File not found: {feature_path}", file=sys.stderr) + + with open(args.output, 'w', newline='', encoding='utf-8') as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=Rule.field_names()) + writer.writeheader() + writer.writerows([asdict(rule) for rule in all_rules]) + + print(f"Extracted {len(all_rules)} rules to {args.output}") + +if __name__ == '__main__': + main() diff --git a/test/cpp/features/compilation.feature b/test/cpp/features/compilation.feature index b2128ef8..99d367a9 100644 --- a/test/cpp/features/compilation.feature +++ b/test/cpp/features/compilation.feature @@ -1,10 +1,11 @@ -Feature: Compile source code into working software +Feature: amp-devcontainer::compilation As a developer To generate working software Source code needs to be compiled successfully - Scenario: Compile valid source code into working software targeting the host architecture + Rule: amp-devcontainer::compilation.host-target + amp-devcontainer *SHALL* be able to compile valid source code into a working executable targeting the container host architecture. Compiling valid source code into working software, able to run on the host architecture, can be necessary in several scenarios; for example when: @@ -13,7 +14,32 @@ Feature: Compile source code into working software - running tests on the host - building plug-ins, extensions, code generators, or other additional tools that need to run on the host - Given build configuration "gcc" is selected - And build preset "gcc" is selected - When the selected target is built - Then the output should contain "Build finished with exit code 0" + @flavor:cpp + Scenario: Compile valid source code into working software targeting the host architecture + Given build configuration "gcc" is selected + And build preset "gcc" is selected + When the selected target is built + Then the output should contain "Build finished with exit code 0" + + Rule: amp-devcontainer::compilation.arm-target + amp-devcontainer *SHALL* be able to compile valid source-code into a working ELF executable targeting the ARM Cortex architecture. + + Compiling valid source-code into working ELF executables, able to run on the ARM Cortex architecture, + is a primary function for amp-devcontainer. It enables building firmware for micro-controllers based + on the ARM Cortex architecture. + + Rule: amp-devcontainer::compilation.windows-target + amp-devcontainer *SHALL* be able to compile valid source-code into a working executable targeting the Microsoft® Windows operating system. + + Compiling valid source-code into working executables, able to run on the Microsoft® Windows operating system, can be necessary in several scenarios e.g. + + - Cross platform code is written and needs to be compiled and deployed + - Executables needs to be deployed outside of container context to a host running the Microsoft® Windows operating system + + Rule: amp-devcontainer::compilation.cache + amp-devcontainer *SHOULD* be able to cache the results of a compilation to speed-up subsequent compilations. + + Maintaining a compilation cache can be useful in both local and ci development scenarios. A compilation cache can provide benefits like + + - Reduce developer waiting time and context switches, [maintaining flow-state](https://azure.microsoft.com/en-us/blog/quantifying-the-impact-of-developer-experience/) + - Reduce CPU usage at the cost of more storage usage, potentially reducing energy consumption and cost for metered ci-systems From ca61782fb054ad167d203841c1fdd15fd67c7ab6 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Wed, 24 Sep 2025 14:14:02 +0000 Subject: [PATCH 04/35] docs: generate requirements document --- .github/workflows/wc-document-generation.yml | 18 ++++++-- docs/templates/requirements.template.md | 45 ++++++++++++++++++++ test/cpp/features/compilation.feature | 30 ++++++------- 3 files changed, 74 insertions(+), 19 deletions(-) create mode 100644 docs/templates/requirements.template.md diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index fb57e51a..b62f53d3 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -8,7 +8,7 @@ permissions: contents: read jobs: - generate-documentation: + generate-requirements: runs-on: ubuntu-latest steps: - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 @@ -18,10 +18,20 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - - run: | + - name: Install dependencies + run: | set -Eeuo pipefail - python -m pip install gherkin-official https://sbdl.dev/sbdl-package.tar.gz - - run: | + sudo apt-get install --update --no-install-recommends -y pandoc plantuml texlive texlive-fonts-recommended + python -m pip install gherkin-official pytm sbdl + - name: Generate requirements document + run: | set -Eeuo pipefail python docs/support/gherkin-to-csv.py test/cpp/features/*.feature python -m csv-to-sbdl --identifier 0 --description 1 --skipheader rules.csv + sbdl -m template-fill --template docs/templates/requirements.template.md output.sbdl | pandoc -f gfm -o requirements.pdf + - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + if: always() + with: + name: documents + path: requirements.pdf + retention-days: 2 diff --git a/docs/templates/requirements.template.md b/docs/templates/requirements.template.md new file mode 100644 index 00000000..589a38b0 --- /dev/null +++ b/docs/templates/requirements.template.md @@ -0,0 +1,45 @@ +# amp-devcontainer requirement specification + +## Introduction + +### Purpose + +This document describes the software system requirements for amp-devcontainer. + +### Definitions of key words + +The key words *MUST*, *MUST NOT*, *REQUIRED*, *SHALL*, *SHALL NOT*, *SHOULD*, *SHOULD NOT*, *RECOMMENDED*, *MAY*, and *OPTIONAL* in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). + +### Abstract + +amp-devcontainer is a set of [devcontainers](https://containers.dev/) tailored towards modern, embedded, software development. + +The containers may be used both for local development and continuous integration (ci). + +### Terminology and Abbreviations + +| Terminology and Abbreviations | Description/Definition | +|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------| +| ARM | A family of RISC architectures for computer processors and micro controllers, formerly an acronym for Advanced RISC Machines and originally Acorn RISC Machine | +| Continuous Integration (ci) | The practice of continuously merging developers work to a shared code-base; ideally including automation for build, test and deployment | +| ELF | Executable and Linkable Format, formerly named Extensible Linking Format | +| RISC | Reduced Instruction Set Computer | + +## Requirements + +{%- macro reencode(text) -%} +{{ text.encode('utf-8').decode('unicode_escape') }} +{%- endmacro -%} + +{%- macro sbdl_id_to_header(text) -%} +{{ text.replace('_', ' ') }} +{%- endmacro -%} + +{% for req_id, requirement in sbdl.items() %} +{% if requirement.type == 'requirement' %} +### {{ reencode(sbdl_id_to_header(req_id)) }} + +{{ reencode(requirement.description) }} + +{% endif %} +{% endfor %} diff --git a/test/cpp/features/compilation.feature b/test/cpp/features/compilation.feature index 99d367a9..d2bce27e 100644 --- a/test/cpp/features/compilation.feature +++ b/test/cpp/features/compilation.feature @@ -1,34 +1,34 @@ -Feature: amp-devcontainer::compilation +Feature: Compilation - As a developer - To generate working software - Source code needs to be compiled successfully + As a software developer + To generate a working product + Source code needs to be compiled into working software - Rule: amp-devcontainer::compilation.host-target - amp-devcontainer *SHALL* be able to compile valid source code into a working executable targeting the container host architecture. + Rule: Compile for container host architecture and operating system + amp-devcontainer *SHALL* be able to compile valid source code into a working executable targeting the container host architecture and operating system. - Compiling valid source code into working software, able to run on the host architecture, + Compiling valid source code into working software, able to run on the container host architecture and operating system, can be necessary in several scenarios; for example when: - - the host is the deployment target - - running tests on the host - - building plug-ins, extensions, code generators, or other additional tools that need to run on the host + - the container host is the deployment target + - running tests on the container host + - building plug-ins, extensions, code generators, or other additional tools that need to run on the container host @flavor:cpp - Scenario: Compile valid source code into working software targeting the host architecture + Scenario: Compile valid source code into working software targeting the container host architecture Given build configuration "gcc" is selected And build preset "gcc" is selected When the selected target is built Then the output should contain "Build finished with exit code 0" - Rule: amp-devcontainer::compilation.arm-target + Rule: Compile for ARM Cortex target architecture amp-devcontainer *SHALL* be able to compile valid source-code into a working ELF executable targeting the ARM Cortex architecture. Compiling valid source-code into working ELF executables, able to run on the ARM Cortex architecture, is a primary function for amp-devcontainer. It enables building firmware for micro-controllers based on the ARM Cortex architecture. - Rule: amp-devcontainer::compilation.windows-target + Rule: Compile for Microsoft® Windows operating system amp-devcontainer *SHALL* be able to compile valid source-code into a working executable targeting the Microsoft® Windows operating system. Compiling valid source-code into working executables, able to run on the Microsoft® Windows operating system, can be necessary in several scenarios e.g. @@ -36,10 +36,10 @@ Feature: amp-devcontainer::compilation - Cross platform code is written and needs to be compiled and deployed - Executables needs to be deployed outside of container context to a host running the Microsoft® Windows operating system - Rule: amp-devcontainer::compilation.cache + Rule: Compilation cache amp-devcontainer *SHOULD* be able to cache the results of a compilation to speed-up subsequent compilations. - Maintaining a compilation cache can be useful in both local and ci development scenarios. A compilation cache can provide benefits like + Maintaining a compilation cache can be useful in both local and ci development scenarios. A compilation cache can provide benefits like: - Reduce developer waiting time and context switches, [maintaining flow-state](https://azure.microsoft.com/en-us/blog/quantifying-the-impact-of-developer-experience/) - Reduce CPU usage at the cost of more storage usage, potentially reducing energy consumption and cost for metered ci-systems From e3cc6600c0165139633d002a98df27cbf020ad99 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Wed, 24 Sep 2025 14:15:28 +0000 Subject: [PATCH 05/35] fix: don't use sudo in workflows --- .github/workflows/wc-document-generation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index b62f53d3..f14707f2 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -21,7 +21,7 @@ jobs: - name: Install dependencies run: | set -Eeuo pipefail - sudo apt-get install --update --no-install-recommends -y pandoc plantuml texlive texlive-fonts-recommended + apt-get install --update --no-install-recommends -y pandoc plantuml texlive texlive-fonts-recommended python -m pip install gherkin-official pytm sbdl - name: Generate requirements document run: | From 176a0f06d232b4c44767cee6790d13b38ee48c71 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Wed, 24 Sep 2025 14:18:43 +0000 Subject: [PATCH 06/35] fix: allow sudo in workflow that needs it --- .github/workflows/wc-document-generation.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index f14707f2..e93f80d7 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -13,7 +13,6 @@ jobs: steps: - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 with: - disable-sudo-and-containers: true egress-policy: audit - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: @@ -21,7 +20,7 @@ jobs: - name: Install dependencies run: | set -Eeuo pipefail - apt-get install --update --no-install-recommends -y pandoc plantuml texlive texlive-fonts-recommended + sudo apt-get install --update --no-install-recommends -y pandoc plantuml texlive texlive-fonts-recommended python -m pip install gherkin-official pytm sbdl - name: Generate requirements document run: | From ada3cacde5763aa892a4b4ae776492c7dbe41f29 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Wed, 24 Sep 2025 14:28:43 +0000 Subject: [PATCH 07/35] ci: install missing package --- .github/workflows/wc-document-generation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index e93f80d7..a6717712 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -8,7 +8,7 @@ permissions: contents: read jobs: - generate-requirements: + generate-documents: runs-on: ubuntu-latest steps: - uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1 @@ -20,7 +20,7 @@ jobs: - name: Install dependencies run: | set -Eeuo pipefail - sudo apt-get install --update --no-install-recommends -y pandoc plantuml texlive texlive-fonts-recommended + sudo apt-get install --update --no-install-recommends -y lmodern pandoc plantuml texlive texlive-fonts-recommended python -m pip install gherkin-official pytm sbdl - name: Generate requirements document run: | From c8df85bff350c250555ca4a7a50e577f9aac1b85 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 25 Sep 2025 07:09:36 +0200 Subject: [PATCH 08/35] Apply suggestions from code review Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Signed-off-by: Ron <45816308+rjaegers@users.noreply.github.com> --- docs/templates/requirements.template.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/templates/requirements.template.md b/docs/templates/requirements.template.md index 589a38b0..3b941f2a 100644 --- a/docs/templates/requirements.template.md +++ b/docs/templates/requirements.template.md @@ -18,12 +18,12 @@ The containers may be used both for local development and continuous integration ### Terminology and Abbreviations -| Terminology and Abbreviations | Description/Definition | -|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------| +| Terminology and Abbreviations | Description/Definition | +|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| | ARM | A family of RISC architectures for computer processors and micro controllers, formerly an acronym for Advanced RISC Machines and originally Acorn RISC Machine | -| Continuous Integration (ci) | The practice of continuously merging developers work to a shared code-base; ideally including automation for build, test and deployment | -| ELF | Executable and Linkable Format, formerly named Extensible Linking Format | -| RISC | Reduced Instruction Set Computer | +| Continuous Integration (ci) | The practice of continuously merging developers work to a shared code-base; ideally including automation for build, test and deployment | +| ELF | Executable and Linkable Format, formerly named Extensible Linking Format | +| RISC | Reduced Instruction Set Computer | ## Requirements From ac7263d7894e1f8f41e5d4e3753b0d9ae2d6535d Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 25 Sep 2025 10:43:19 +0000 Subject: [PATCH 09/35] docs: generate nicer PDF --- .github/workflows/wc-document-generation.yml | 9 +++++---- docs/support/gherkin-to-csv.py | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index a6717712..878e5f83 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -20,14 +20,15 @@ jobs: - name: Install dependencies run: | set -Eeuo pipefail - sudo apt-get install --update --no-install-recommends -y lmodern pandoc plantuml texlive texlive-fonts-recommended - python -m pip install gherkin-official pytm sbdl + sudo apt-get update && sudo apt-get install --no-install-recommends -y plantuml + python -m pip install gherkin-official sbdl - name: Generate requirements document run: | set -Eeuo pipefail python docs/support/gherkin-to-csv.py test/cpp/features/*.feature - python -m csv-to-sbdl --identifier 0 --description 1 --skipheader rules.csv - sbdl -m template-fill --template docs/templates/requirements.template.md output.sbdl | pandoc -f gfm -o requirements.pdf + python -m csv-to-sbdl --identifier 0 --description 1 --skipheader rules.csv -o - | sbdl -m template-fill --template docs/templates/requirements.template.md output.sbdl > requirements.md + - uses: docker://pandoc/latex:3.7@sha256:ea5a6702bd47aea68dc1da18ff7e9891a76097d10ae88ea03d343113114dcbbe + args: --output requirements.pdf requirements.md - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: always() with: diff --git a/docs/support/gherkin-to-csv.py b/docs/support/gherkin-to-csv.py index d2e71da7..5144c632 100755 --- a/docs/support/gherkin-to-csv.py +++ b/docs/support/gherkin-to-csv.py @@ -21,11 +21,11 @@ def field_names(cls) -> List[str]: def extract_rules_from_feature(file_path): """Parse a Gherkin feature file and extract the rules.""" - with open(file_path, 'r', encoding='utf-8') as file: - content = file.read() - - parser = Parser() try: + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + + parser = Parser() feature_document = parser.parse(TokenScanner(content)) rules = [] From 9c37fe18450ce65b7fdc5dffe198c428cdaf18cb Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 25 Sep 2025 10:49:05 +0000 Subject: [PATCH 10/35] chore: correct workflow syntax --- .github/workflows/wc-document-generation.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index 878e5f83..28c04808 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -28,7 +28,8 @@ jobs: python docs/support/gherkin-to-csv.py test/cpp/features/*.feature python -m csv-to-sbdl --identifier 0 --description 1 --skipheader rules.csv -o - | sbdl -m template-fill --template docs/templates/requirements.template.md output.sbdl > requirements.md - uses: docker://pandoc/latex:3.7@sha256:ea5a6702bd47aea68dc1da18ff7e9891a76097d10ae88ea03d343113114dcbbe - args: --output requirements.pdf requirements.md + with: + args: --output requirements.pdf requirements.md - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: always() with: From becc9918a5328342a78b5c5c656c4966c8e89e1b Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:07:24 +0000 Subject: [PATCH 11/35] chore: minor refactor to workflow --- .github/workflows/wc-document-generation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index 28c04808..e56f0b95 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -26,12 +26,12 @@ jobs: run: | set -Eeuo pipefail python docs/support/gherkin-to-csv.py test/cpp/features/*.feature - python -m csv-to-sbdl --identifier 0 --description 1 --skipheader rules.csv -o - | sbdl -m template-fill --template docs/templates/requirements.template.md output.sbdl > requirements.md + python -m csv-to-sbdl --identifier 0 --description 1 --skipheader rules.csv + sbdl -m template-fill --template docs/templates/requirements.template.md output.sbdl > requirements.md - uses: docker://pandoc/latex:3.7@sha256:ea5a6702bd47aea68dc1da18ff7e9891a76097d10ae88ea03d343113114dcbbe with: args: --output requirements.pdf requirements.md - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - if: always() with: name: documents path: requirements.pdf From a11b9678f687b6a2305ece72cc87b22cc49e246e Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 25 Sep 2025 11:36:31 +0000 Subject: [PATCH 12/35] chore: pin pip dependencies --- .github/workflows/wc-document-generation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index e56f0b95..d0dab448 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -21,7 +21,7 @@ jobs: run: | set -Eeuo pipefail sudo apt-get update && sudo apt-get install --no-install-recommends -y plantuml - python -m pip install gherkin-official sbdl + python -m pip install gherkin-official==35.1.0 sbdl==1.16.4 - name: Generate requirements document run: | set -Eeuo pipefail From 8caaba23f953f9b952983a0da1b8fe75d574aaf9 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 25 Sep 2025 12:11:52 +0000 Subject: [PATCH 13/35] docs: make the pdf more appealing --- .github/workflows/wc-document-generation.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index d0dab448..4241b822 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -28,9 +28,9 @@ jobs: python docs/support/gherkin-to-csv.py test/cpp/features/*.feature python -m csv-to-sbdl --identifier 0 --description 1 --skipheader rules.csv sbdl -m template-fill --template docs/templates/requirements.template.md output.sbdl > requirements.md - - uses: docker://pandoc/latex:3.7@sha256:ea5a6702bd47aea68dc1da18ff7e9891a76097d10ae88ea03d343113114dcbbe + - uses: docker://pandoc/extra:3.7.0@sha256:a703d335fa237f8fc3303329d87e2555dca5187930da38bfa9010fa4e690933a with: - args: --output requirements.pdf requirements.md + args: --template eisvogel --output requirements.pdf requirements.md - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: documents From e51eaef314c687f746cf8c675007d2641f8f7b84 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 25 Sep 2025 17:10:39 +0000 Subject: [PATCH 14/35] chore: refactor gherkin to sbdl workflow --- .github/workflows/wc-document-generation.yml | 5 +- docs/support/gherkin-to-csv.py | 75 -------- docs/support/gherkin-to-sbdl.py | 170 ++++++++++++++++++ ...rements.template.md => requirements.md.j2} | 0 4 files changed, 172 insertions(+), 78 deletions(-) delete mode 100755 docs/support/gherkin-to-csv.py create mode 100755 docs/support/gherkin-to-sbdl.py rename docs/templates/{requirements.template.md => requirements.md.j2} (100%) diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index 4241b822..8ff064ed 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -25,9 +25,8 @@ jobs: - name: Generate requirements document run: | set -Eeuo pipefail - python docs/support/gherkin-to-csv.py test/cpp/features/*.feature - python -m csv-to-sbdl --identifier 0 --description 1 --skipheader rules.csv - sbdl -m template-fill --template docs/templates/requirements.template.md output.sbdl > requirements.md + python docs/support/gherkin-to-sbdl.py test/cpp/features/*.feature + sbdl -m template-fill --template docs/templates/requirements.md.j2 output.sbdl > requirements.md - uses: docker://pandoc/extra:3.7.0@sha256:a703d335fa237f8fc3303329d87e2555dca5187930da38bfa9010fa4e690933a with: args: --template eisvogel --output requirements.pdf requirements.md diff --git a/docs/support/gherkin-to-csv.py b/docs/support/gherkin-to-csv.py deleted file mode 100755 index 5144c632..00000000 --- a/docs/support/gherkin-to-csv.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import csv -import os -import sys -from dataclasses import dataclass, asdict, fields -from typing import List -from gherkin.parser import Parser -from gherkin.token_scanner import TokenScanner - -@dataclass -class Rule: - """A class representing a rule extracted from a Gherkin feature file.""" - identifier: str - description: str - - @classmethod - def field_names(cls) -> List[str]: - return [f.name for f in fields(cls)] - -def extract_rules_from_feature(file_path): - """Parse a Gherkin feature file and extract the rules.""" - try: - with open(file_path, 'r', encoding='utf-8') as file: - content = file.read() - - parser = Parser() - feature_document = parser.parse(TokenScanner(content)) - rules = [] - - if 'feature' in feature_document: - feature = feature_document['feature'] - - if 'children' in feature: - for child in feature['children']: - if 'rule' in child: - rule = child['rule'] - rule_id = rule.get('name', '').strip() - description = rule.get('description', '').strip() - rules.append(Rule( - identifier=rule_id, - description=description - )) - - return rules - except Exception as e: - print(f"Error parsing {file_path}: {e}", file=sys.stderr) - return [] - -def main(): - parser = argparse.ArgumentParser(description='Extract rules from Gherkin feature files and save to CSV') - parser.add_argument('feature_files', nargs='+', help='Paths to feature files') - parser.add_argument('--output', '-o', default='rules.csv', help='Output CSV file path') - - args = parser.parse_args() - all_rules = [] - - for feature_path in args.feature_files: - if os.path.isfile(feature_path): - print(f"Processing {feature_path}") - rules = extract_rules_from_feature(feature_path) - all_rules.extend(rules) - else: - print(f"File not found: {feature_path}", file=sys.stderr) - - with open(args.output, 'w', newline='', encoding='utf-8') as csvfile: - writer = csv.DictWriter(csvfile, fieldnames=Rule.field_names()) - writer.writeheader() - writer.writerows([asdict(rule) for rule in all_rules]) - - print(f"Extracted {len(all_rules)} rules to {args.output}") - -if __name__ == '__main__': - main() diff --git a/docs/support/gherkin-to-sbdl.py b/docs/support/gherkin-to-sbdl.py new file mode 100755 index 00000000..7902f754 --- /dev/null +++ b/docs/support/gherkin-to-sbdl.py @@ -0,0 +1,170 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys +from dataclasses import dataclass +from typing import List, Dict +from gherkin.parser import Parser +from gherkin.token_scanner import TokenScanner + +@dataclass +class SBDLRequirement: + """A class representing an SBDL requirement.""" + identifier: str + description: str + parent: str = "" + +def make_sbdl_identifier(name: str) -> str: + """Convert a name to a valid SBDL identifier.""" + # Replace spaces and special characters with underscores + identifier = name.replace(" ", "_").replace("-", "_").replace("®", "_") + # Remove or replace other special characters + identifier = "".join(c if c.isalnum() or c == "_" else "_" for c in identifier) + # Remove multiple consecutive underscores + while "__" in identifier: + identifier = identifier.replace("__", "_") + # Remove leading/trailing underscores + identifier = identifier.strip("_") + return identifier + +def clean_description_text(text: str) -> str: + """Clean up description text by removing excessive indentation and normalizing whitespace.""" + if not text: + return text + + lines = text.split('\n') + + # Find the minimum indentation (excluding empty lines) + non_empty_lines = [line for line in lines if line.strip()] + if not non_empty_lines: + return text.strip() + + min_indent = min(len(line) - len(line.lstrip()) for line in non_empty_lines) + + # Remove the common indentation from all lines + cleaned_lines = [] + for line in lines: + if line.strip(): # Non-empty line + cleaned_lines.append(line[min_indent:] if len(line) >= min_indent else line) + else: # Empty line + cleaned_lines.append('') + + # Join lines and clean up excessive whitespace + return '\n'.join(cleaned_lines).strip() + +def extract_requirements_from_feature(file_path: str) -> Dict[str, List[SBDLRequirement]]: + """Parse a Gherkin feature file and extract requirements in SBDL format.""" + try: + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + + parser = Parser() + feature_document = parser.parse(TokenScanner(content)) + features = {} + + if 'feature' in feature_document: + feature = feature_document['feature'] + + # Extract feature information + feature_name = feature.get('name', '').strip() + feature_description = clean_description_text(feature.get('description', '')) + + if not feature_name: + print(f"Warning: Feature in {file_path} has no name", file=sys.stderr) + return features + + feature_id = make_sbdl_identifier(feature_name) + print(f"Extracted Feature: {feature_name}") + print(f"Feature ID: {feature_id}") + print(f"Description: {feature_description}\n") + + # Create feature requirement + feature_requirement = SBDLRequirement( + identifier=feature_id, + description=feature_description + ) + + rules = [] + + # Extract rules under this feature + if 'children' in feature: + for child in feature['children']: + if 'rule' in child: + rule = child['rule'] + rule_name = rule.get('name', '').strip() + rule_description = clean_description_text(rule.get('description', '')) + + if not rule_name: + continue + + rule_id = make_sbdl_identifier(rule_name) + print(f" Extracted Rule: {rule_name}") + print(f" Rule ID: {rule_id}") + print(f" Description: {rule_description}\n") + + rule_requirement = SBDLRequirement( + identifier=rule_id, + description=rule_description, + parent=feature_id + ) + rules.append(rule_requirement) + + features[feature_id] = { + 'feature': feature_requirement, + 'rules': rules + } + + return features + except Exception as e: + print(f"Error parsing {file_path}: {e}", file=sys.stderr) + return {} + +def write_sbdl_output(all_features: Dict[str, Dict], output_file: str): + """Write SBDL format output.""" + with open(output_file, 'w', encoding='utf-8') as f: + f.write("#!sbdl\n") + + for feature_id, feature_data in all_features.items(): + feature_req = feature_data['feature'] + rules = feature_data['rules'] + + # Write feature requirement + f.write(f"{feature_req.identifier} is requirement {{ ") + f.write(f'description is "{feature_req.description.replace(chr(10), "\\n")}" ') + f.write("}\n") + + # Write child rule requirements + for rule in rules: + f.write(f"{rule.identifier} is requirement {{ ") + f.write(f'description is "{rule.description.replace(chr(10), "\\n")}" ') + f.write(f'parent is {rule.parent} ') + f.write("}\n") + +def main(): + parser = argparse.ArgumentParser(description='Extract requirements from Gherkin feature files and generate SBDL') + parser.add_argument('feature_files', nargs='+', help='Paths to feature files') + parser.add_argument('--output', '-o', default='output.sbdl', help='Output SBDL file path') + + args = parser.parse_args() + all_features = {} + total_requirements = 0 + + for feature_path in args.feature_files: + if os.path.isfile(feature_path): + print(f"Processing {feature_path}") + features = extract_requirements_from_feature(feature_path) + all_features.update(features) + + # Count requirements + for feature_data in features.values(): + total_requirements += 1 # Feature requirement + total_requirements += len(feature_data['rules']) # Rule requirements + else: + print(f"File not found: {feature_path}", file=sys.stderr) + + write_sbdl_output(all_features, args.output) + print(f"Extracted {total_requirements} requirements to {args.output}") + +if __name__ == '__main__': + main() diff --git a/docs/templates/requirements.template.md b/docs/templates/requirements.md.j2 similarity index 100% rename from docs/templates/requirements.template.md rename to docs/templates/requirements.md.j2 From 34621e053a1071aa448191b4347c48d780e47985 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 25 Sep 2025 17:41:57 +0000 Subject: [PATCH 15/35] chore: refactor requirement structure Allow for nested requirements, the top level being specified by the Feature name and description and child requirements being specified as Rules with their description. --- docs/support/gherkin-to-sbdl.py | 57 ++++++++++++++++++------------- docs/templates/requirements.md.j2 | 19 ++++++++--- 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/docs/support/gherkin-to-sbdl.py b/docs/support/gherkin-to-sbdl.py index 7902f754..0499aec5 100755 --- a/docs/support/gherkin-to-sbdl.py +++ b/docs/support/gherkin-to-sbdl.py @@ -7,6 +7,7 @@ from typing import List, Dict from gherkin.parser import Parser from gherkin.token_scanner import TokenScanner +import sbdl @dataclass class SBDLRequirement: @@ -16,16 +17,21 @@ class SBDLRequirement: parent: str = "" def make_sbdl_identifier(name: str) -> str: - """Convert a name to a valid SBDL identifier.""" - # Replace spaces and special characters with underscores - identifier = name.replace(" ", "_").replace("-", "_").replace("®", "_") - # Remove or replace other special characters - identifier = "".join(c if c.isalnum() or c == "_" else "_" for c in identifier) + """Convert a name to a valid SBDL identifier using SBDL's built-in functions.""" + # Replace spaces and dashes with underscores first, then use SBDL's sanitization + identifier = name.replace(" ", "_").replace("-", "_") + + # Use SBDL's built-in identifier sanitization + identifier = sbdl.SBDL_Parser.sanitize_identifier(identifier) + + # Additional cleanup for readability # Remove multiple consecutive underscores while "__" in identifier: identifier = identifier.replace("__", "_") + # Remove leading/trailing underscores identifier = identifier.strip("_") + return identifier def clean_description_text(text: str) -> str: @@ -75,10 +81,6 @@ def extract_requirements_from_feature(file_path: str) -> Dict[str, List[SBDLRequ return features feature_id = make_sbdl_identifier(feature_name) - print(f"Extracted Feature: {feature_name}") - print(f"Feature ID: {feature_id}") - print(f"Description: {feature_description}\n") - # Create feature requirement feature_requirement = SBDLRequirement( identifier=feature_id, @@ -99,10 +101,6 @@ def extract_requirements_from_feature(file_path: str) -> Dict[str, List[SBDLRequ continue rule_id = make_sbdl_identifier(rule_name) - print(f" Extracted Rule: {rule_name}") - print(f" Rule ID: {rule_id}") - print(f" Description: {rule_description}\n") - rule_requirement = SBDLRequirement( identifier=rule_id, description=rule_description, @@ -121,25 +119,38 @@ def extract_requirements_from_feature(file_path: str) -> Dict[str, List[SBDLRequ return {} def write_sbdl_output(all_features: Dict[str, Dict], output_file: str): - """Write SBDL format output.""" - with open(output_file, 'w', encoding='utf-8') as f: + """Write SBDL format output using SBDL Python interface.""" + # Get SBDL syntax tokens and types + tokens = sbdl.SBDL_Parser.Tokens + types = sbdl.SBDL_Parser.Types + attrs = sbdl.SBDL_Parser.Attributes + + # Open output file using SBDL's file handler + with sbdl.open_output_file(output_file) as f: + # Write SBDL file header f.write("#!sbdl\n") for feature_id, feature_data in all_features.items(): feature_req = feature_data['feature'] rules = feature_data['rules'] - # Write feature requirement - f.write(f"{feature_req.identifier} is requirement {{ ") - f.write(f'description is "{feature_req.description.replace(chr(10), "\\n")}" ') - f.write("}\n") + # Write feature requirement using proper SBDL syntax + escaped_desc = sbdl.SBDL_Parser.sanitize(feature_req.description) + f.write(f"{feature_req.identifier} {tokens.declaration} {types.requirement} ") + f.write(f"{tokens.declaration_group_delimeters[0]} ") + f.write(f"{attrs.description}{tokens.declaration_attribute_assign}") + f.write(f"{tokens.declaration_attribute_delimeter}{escaped_desc}{tokens.declaration_attribute_delimeter} ") + f.write(f"{tokens.declaration_group_delimeters[1]}\n") # Write child rule requirements for rule in rules: - f.write(f"{rule.identifier} is requirement {{ ") - f.write(f'description is "{rule.description.replace(chr(10), "\\n")}" ') - f.write(f'parent is {rule.parent} ') - f.write("}\n") + escaped_desc = sbdl.SBDL_Parser.sanitize(rule.description) + f.write(f"{rule.identifier} {tokens.declaration} {types.requirement} ") + f.write(f"{tokens.declaration_group_delimeters[0]} ") + f.write(f"{attrs.description}{tokens.declaration_attribute_assign}") + f.write(f"{tokens.declaration_attribute_delimeter}{escaped_desc}{tokens.declaration_attribute_delimeter} ") + f.write(f"{attrs.parent}{tokens.declaration_attribute_assign}{rule.parent} ") + f.write(f"{tokens.declaration_group_delimeters[1]}\n") def main(): parser = argparse.ArgumentParser(description='Extract requirements from Gherkin feature files and generate SBDL') diff --git a/docs/templates/requirements.md.j2 b/docs/templates/requirements.md.j2 index 3b941f2a..f2a6f478 100644 --- a/docs/templates/requirements.md.j2 +++ b/docs/templates/requirements.md.j2 @@ -35,11 +35,22 @@ The containers may be used both for local development and continuous integration {{ text.replace('_', ' ') }} {%- endmacro -%} -{% for req_id, requirement in sbdl.items() %} -{% if requirement.type == 'requirement' %} +{%- for req_id, requirement in sbdl.items() %} +{%- if requirement.type == 'requirement' and 'parent' not in requirement %} + ### {{ reencode(sbdl_id_to_header(req_id)) }} {{ reencode(requirement.description) }} -{% endif %} -{% endfor %} +{%- if 'child' in requirement %} +{%- for child in requirement.child %} +{%- set child_req = sbdl[child.identifier] %} + +#### {{ reencode(sbdl_id_to_header(child.identifier)) }} + +{{ reencode(child_req.description) }} + +{%- endfor %} +{%- endif %} +{%- endif %} +{%- endfor %} From de87a42485a8377313ead8123790fa041c4e7990 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 25 Sep 2025 17:43:31 +0000 Subject: [PATCH 16/35] chore: enable listings code highlighter --- .github/workflows/wc-document-generation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index 8ff064ed..2d2f4a43 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -29,7 +29,7 @@ jobs: sbdl -m template-fill --template docs/templates/requirements.md.j2 output.sbdl > requirements.md - uses: docker://pandoc/extra:3.7.0@sha256:a703d335fa237f8fc3303329d87e2555dca5187930da38bfa9010fa4e690933a with: - args: --template eisvogel --output requirements.pdf requirements.md + args: --template eisvogel --listings --output requirements.pdf requirements.md - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: documents From db8cafbad4a6134f93fa2d4e5e734b392aab0a54 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 25 Sep 2025 18:28:47 +0000 Subject: [PATCH 17/35] docs: add eisvogel front-matter --- docs/templates/requirements.md.j2 | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/templates/requirements.md.j2 b/docs/templates/requirements.md.j2 index f2a6f478..bbd6a1e9 100644 --- a/docs/templates/requirements.md.j2 +++ b/docs/templates/requirements.md.j2 @@ -1,4 +1,17 @@ -# amp-devcontainer requirement specification +--- +title: "Requirements specification for amp-devcontainer" +author: [Ron Jaegers] +date: "25-Sept-2025" +keywords: [Requirements, amp-devcontainer] +lang: "en" +titlepage: true +titlepage-color: "0B5ED7" +titlepage-text-color: "FFFFFF" +titlepage-rule-color: "FFFFFF" +titlepage-rule-height: 2 +... + +# Requirements specification for amp-devcontainer ## Introduction From 6e07d15924630838fa01208ec5d8a320fbc8d895 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:07:49 +0000 Subject: [PATCH 18/35] docs: update to front-matter config --- docs/templates/requirements.md.j2 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/templates/requirements.md.j2 b/docs/templates/requirements.md.j2 index bbd6a1e9..d4b61617 100644 --- a/docs/templates/requirements.md.j2 +++ b/docs/templates/requirements.md.j2 @@ -1,7 +1,8 @@ --- title: "Requirements specification for amp-devcontainer" -author: [Ron Jaegers] -date: "25-Sept-2025" +author: ["@rjaegers"] +block-headings: 4 +date: "Sept-2025" keywords: [Requirements, amp-devcontainer] lang: "en" titlepage: true From f2e6e60fa9c05cbd31264509467911dfae3456f5 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:52:23 +0000 Subject: [PATCH 19/35] docs: add more requirements --- test/cpp/features/compatibility.feature | 25 ++++++++++++++++++ test/cpp/features/debugging.feature | 22 ++++++++++++++++ test/cpp/features/maintainability.feature | 1 + test/cpp/features/security.feature | 26 +++++++++++++++++++ .../features/static-dynamic-analysis.feature | 7 +++-- 5 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 test/cpp/features/compatibility.feature create mode 100644 test/cpp/features/debugging.feature create mode 100644 test/cpp/features/maintainability.feature create mode 100644 test/cpp/features/security.feature diff --git a/test/cpp/features/compatibility.feature b/test/cpp/features/compatibility.feature new file mode 100644 index 00000000..71af0887 --- /dev/null +++ b/test/cpp/features/compatibility.feature @@ -0,0 +1,25 @@ +Feature: Compatibility + + As a software craftsperson + to ensure that my development environment works well with a variety of tools and systems + I want my development environment to be compatible with commonly used tools and systems + + Rule: Host architecture + amp-devcontainer *SHALL* be compatible with both the x86-64 (AMD64) *and* AArch64 (ARM64) host architectures. + + Supporting both x86-64 and AArch64 has several advantages: + + - Increasing useability on a wide range of host machines, from PC hardware using the x86-64 architecture to Apple Silicon using the AArch64 architecture + - Unlocking the power efficiency of the AArch64 architecture, potentially reducing energy consumption and cost for metered ci-systems + + Rule: Open Container Initiative (OCI) Image Specification + amp-devcontainer images *SHALL* be compatible with the [OCI image specification](https://github.com/opencontainers/image-spec/blob/main/spec.md) + + To guarantee compatibility with container runtimes and container- and image tooling; amp-devcontainer should be compatible with the OCI image specification. + + Rule: Integrated Development Environment + amp-devcontainer *SHALL* be compatible with [VS Code](https://code.visualstudio.com/) *and* [GitHub Codespaces](https://github.com/features/codespaces). + + It should be possible to use amp-devcontainer and all of its features in both VS Code and GitHub Codespaces with minimal effort. + Where minimal effort means: with the least amount of additional set-up, user intervention or configuration for all functionality that is provided by amp-devcontainer. + Features and functions should work "out-of-the-box" without being overly opinionated. diff --git a/test/cpp/features/debugging.feature b/test/cpp/features/debugging.feature new file mode 100644 index 00000000..33c0fea5 --- /dev/null +++ b/test/cpp/features/debugging.feature @@ -0,0 +1,22 @@ +Feature: Debugging + + As a software craftsperson + to efficiently identify and resolve issues in my code + I want to be able to debug my source code within the development environment + + Rule: Debugging support + amp-devcontainer *SHALL* provide debugging support for the primary programming languages used within the container. + + Providing debugging support within the development environment enhances the developer experience and productivity. + It allows developers to efficiently identify and resolve issues in their code by setting breakpoints, inspecting variables, and stepping through code execution. + This capability is essential for diagnosing complex problems, understanding code flow, and ensuring the correctness of software. + By having integrated debugging tools, developers can streamline their workflow and reduce the time spent on troubleshooting and fixing bugs. + + @flavor:cpp @fixme + Scenario: Debug a simple C++ program + Given the file "debugging/main.cpp" is opened in the editor + When a breakpoint is set on line 5 of "debugging/main.cpp" + And the debugger is started + Then the debugger should stop at line 5 of "debugging/main.cpp" + When the debugger is continued + Then the program output should be "Hello, Debugging!" diff --git a/test/cpp/features/maintainability.feature b/test/cpp/features/maintainability.feature new file mode 100644 index 00000000..c9eb8b06 --- /dev/null +++ b/test/cpp/features/maintainability.feature @@ -0,0 +1 @@ +Feature: Maintainability diff --git a/test/cpp/features/security.feature b/test/cpp/features/security.feature new file mode 100644 index 00000000..7268b2d3 --- /dev/null +++ b/test/cpp/features/security.feature @@ -0,0 +1,26 @@ +Feature: Security + + As a security engineer and security conscious developer + to have control over the security posture of my development environment + I want to have controls in place to identify and mitigate supply-chain vulnerabilities + + Rule: Build provenance + amp-devcontainer *SHALL* include build provenance as specified in [SLSA v1.0 Build L3](https://slsa.dev/spec/v1.0/levels). + + Including provenance gives confidence that the container images haven't been tampered with and can be securely traced back to its source code. + The primary purpose is to enable [verification](https://slsa.dev/spec/v1.0/verifying-artifacts) that the container image was built as expected. + Consumers have some way of knowing what the expected provenance should look like for a given container image and then compare each container image's actual provenance to those expectations. + Doing so prevents several classes of [supply chain threats](https://slsa.dev/spec/v1.0/threats). + + Rule: Signing + amp-devcontainer *SHALL* cryptographically sign its released container images. + + Cryptographically signing released container images provides integrity and authenticity guarantees. + It enables consumers to verify that the container image hasn't been tampered with and that it indeed originates from the expected publisher. + This helps mitigate several classes of [supply chain threats](https://slsa.dev/spec/v1.0/threats). + + Rule: Software Bill of Materials (SBOM) + amp-devcontainer *SHALL* provide a Software Bill of Materials (SBOM) for its released container images. + + Providing a Software Bill of Materials (SBOM) enables consumers to identify and manage security risks associated with the software components included in the container images. + It helps identify known vulnerabilities, license compliance issues, and potential supply chain risks. diff --git a/test/cpp/features/static-dynamic-analysis.feature b/test/cpp/features/static-dynamic-analysis.feature index 8feb2195..e42504c5 100644 --- a/test/cpp/features/static-dynamic-analysis.feature +++ b/test/cpp/features/static-dynamic-analysis.feature @@ -1,12 +1,11 @@ -Feature: Analyze source code using static and dynamic analysis +Feature: Static and dynamic analysis As a software craftsperson - To maintain consistent, high-quality and bug-free code + to maintain consistent, high-quality and bug-free code I want my source code to be statically and dynamically analyzed - @fixme + @flavor:cpp @fixme Scenario: Format source code according to a formatting style - Given the file "clang-tools/unformatted.cpp" is opened in the editor When the active document is formatted And the active document is saved From bbc02bf07ff92f5e501ee90fe87b430bd481e5d0 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:52:33 +0000 Subject: [PATCH 20/35] docs: add a toc --- docs/templates/requirements.md.j2 | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/templates/requirements.md.j2 b/docs/templates/requirements.md.j2 index d4b61617..94051cd4 100644 --- a/docs/templates/requirements.md.j2 +++ b/docs/templates/requirements.md.j2 @@ -10,6 +10,7 @@ titlepage-color: "0B5ED7" titlepage-text-color: "FFFFFF" titlepage-rule-color: "FFFFFF" titlepage-rule-height: 2 +toc: true ... # Requirements specification for amp-devcontainer From cdf9a70cda522f0d560c154c6e6253e1b6febaa6 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Mon, 29 Sep 2025 12:50:15 +0000 Subject: [PATCH 21/35] docs: extend requirements --- .github/workflows/wc-document-generation.yml | 8 ++-- ...software-requirements-specification.md.j2} | 9 +++-- test/cpp/features/compatibility.feature | 21 +++++++---- test/cpp/features/compilation.feature | 10 ++--- test/cpp/features/debugging.feature | 10 ++++- test/cpp/features/maintainability.feature | 37 +++++++++++++++++++ test/cpp/features/security.feature | 2 +- .../features/static-dynamic-analysis.feature | 35 ++++++++++++++++++ 8 files changed, 110 insertions(+), 22 deletions(-) rename docs/templates/{requirements.md.j2 => software-requirements-specification.md.j2} (81%) diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index 2d2f4a43..d67dd710 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -22,16 +22,16 @@ jobs: set -Eeuo pipefail sudo apt-get update && sudo apt-get install --no-install-recommends -y plantuml python -m pip install gherkin-official==35.1.0 sbdl==1.16.4 - - name: Generate requirements document + - name: Generate SRS document run: | set -Eeuo pipefail python docs/support/gherkin-to-sbdl.py test/cpp/features/*.feature - sbdl -m template-fill --template docs/templates/requirements.md.j2 output.sbdl > requirements.md + sbdl -m template-fill --template docs/templates/software-requirements-specification.md.j2 output.sbdl > software-requirements-specification.md - uses: docker://pandoc/extra:3.7.0@sha256:a703d335fa237f8fc3303329d87e2555dca5187930da38bfa9010fa4e690933a with: - args: --template eisvogel --listings --output requirements.pdf requirements.md + args: --template eisvogel --listings --output software-requirements-specification.pdf software-requirements-specification.md - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: documents - path: requirements.pdf + path: software-requirements-specification.pdf retention-days: 2 diff --git a/docs/templates/requirements.md.j2 b/docs/templates/software-requirements-specification.md.j2 similarity index 81% rename from docs/templates/requirements.md.j2 rename to docs/templates/software-requirements-specification.md.j2 index 94051cd4..2534d3d8 100644 --- a/docs/templates/requirements.md.j2 +++ b/docs/templates/software-requirements-specification.md.j2 @@ -1,9 +1,9 @@ --- -title: "Requirements specification for amp-devcontainer" +title: "Software requirements specification for amp-devcontainer" author: ["@rjaegers"] block-headings: 4 date: "Sept-2025" -keywords: [Requirements, amp-devcontainer] +keywords: [Software, Requirements, SRS, amp-devcontainer] lang: "en" titlepage: true titlepage-color: "0B5ED7" @@ -13,7 +13,7 @@ titlepage-rule-height: 2 toc: true ... -# Requirements specification for amp-devcontainer +# Software requirements specification for amp-devcontainer ## Introduction @@ -36,7 +36,8 @@ The containers may be used both for local development and continuous integration | Terminology and Abbreviations | Description/Definition | |-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| | ARM | A family of RISC architectures for computer processors and micro controllers, formerly an acronym for Advanced RISC Machines and originally Acorn RISC Machine | -| Continuous Integration (ci) | The practice of continuously merging developers work to a shared code-base; ideally including automation for build, test and deployment | +| Continuous Delivery (cd) | Extends ci by automating the release of validated code, extending test automation to ensure that code is production-ready | +| Continuous Integration (ci) | The practice of continuously integrating developers' work to a shared code-base; including automation for build and test | | ELF | Executable and Linkable Format, formerly named Extensible Linking Format | | RISC | Reduced Instruction Set Computer | diff --git a/test/cpp/features/compatibility.feature b/test/cpp/features/compatibility.feature index 71af0887..01d41869 100644 --- a/test/cpp/features/compatibility.feature +++ b/test/cpp/features/compatibility.feature @@ -4,6 +4,11 @@ Feature: Compatibility to ensure that my development environment works well with a variety of tools and systems I want my development environment to be compatible with commonly used tools and systems + Rule: Open Container Initiative (OCI) Image Specification + amp-devcontainer images *SHALL* be compatible with the [OCI image specification](https://github.com/opencontainers/image-spec/blob/main/spec.md) + + To guarantee compatibility with container runtimes and container- and image tooling; amp-devcontainer should be compatible with the OCI image specification. + Rule: Host architecture amp-devcontainer *SHALL* be compatible with both the x86-64 (AMD64) *and* AArch64 (ARM64) host architectures. @@ -12,14 +17,16 @@ Feature: Compatibility - Increasing useability on a wide range of host machines, from PC hardware using the x86-64 architecture to Apple Silicon using the AArch64 architecture - Unlocking the power efficiency of the AArch64 architecture, potentially reducing energy consumption and cost for metered ci-systems - Rule: Open Container Initiative (OCI) Image Specification - amp-devcontainer images *SHALL* be compatible with the [OCI image specification](https://github.com/opencontainers/image-spec/blob/main/spec.md) - - To guarantee compatibility with container runtimes and container- and image tooling; amp-devcontainer should be compatible with the OCI image specification. - - Rule: Integrated Development Environment - amp-devcontainer *SHALL* be compatible with [VS Code](https://code.visualstudio.com/) *and* [GitHub Codespaces](https://github.com/features/codespaces). + Rule: Integrated Development Environment (IDE) + amp-devcontainer *SHOULD* be compatible with [VS Code](https://code.visualstudio.com/) *and* [GitHub Codespaces](https://github.com/features/codespaces). It should be possible to use amp-devcontainer and all of its features in both VS Code and GitHub Codespaces with minimal effort. Where minimal effort means: with the least amount of additional set-up, user intervention or configuration for all functionality that is provided by amp-devcontainer. Features and functions should work "out-of-the-box" without being overly opinionated. + + Rule: GitHub Actions + amp-devcontainer *SHOULD* support seamless integration with [GitHub Actions](https://github.com/features/actions) without additional configuration. + + Seamless integration with GitHub Actions allows users to easily incorporate amp-devcontainer into their ci/cd workflows. + This integration helps automate the build, test, and deployment processes, improving efficiency and reducing manual errors. + By minimizing the need for additional configuration, users can quickly set up and start using amp-devcontainer in their GitHub Actions workflows, enhancing their overall development experience. diff --git a/test/cpp/features/compilation.feature b/test/cpp/features/compilation.feature index d2bce27e..728177d0 100644 --- a/test/cpp/features/compilation.feature +++ b/test/cpp/features/compilation.feature @@ -1,8 +1,8 @@ Feature: Compilation As a software developer - To generate a working product - Source code needs to be compiled into working software + to generate a working product, when using compiled languages + source code needs to be compiled into working software Rule: Compile for container host architecture and operating system amp-devcontainer *SHALL* be able to compile valid source code into a working executable targeting the container host architecture and operating system. @@ -22,14 +22,14 @@ Feature: Compilation Then the output should contain "Build finished with exit code 0" Rule: Compile for ARM Cortex target architecture - amp-devcontainer *SHALL* be able to compile valid source-code into a working ELF executable targeting the ARM Cortex architecture. + amp-devcontainer *SHOULD* be able to compile valid source-code into a working ELF executable targeting the ARM Cortex architecture. Compiling valid source-code into working ELF executables, able to run on the ARM Cortex architecture, is a primary function for amp-devcontainer. It enables building firmware for micro-controllers based on the ARM Cortex architecture. Rule: Compile for Microsoft® Windows operating system - amp-devcontainer *SHALL* be able to compile valid source-code into a working executable targeting the Microsoft® Windows operating system. + amp-devcontainer *SHOULD* be able to compile valid source-code into a working executable targeting the Microsoft® Windows operating system. Compiling valid source-code into working executables, able to run on the Microsoft® Windows operating system, can be necessary in several scenarios e.g. @@ -37,7 +37,7 @@ Feature: Compilation - Executables needs to be deployed outside of container context to a host running the Microsoft® Windows operating system Rule: Compilation cache - amp-devcontainer *SHOULD* be able to cache the results of a compilation to speed-up subsequent compilations. + amp-devcontainer *MAY* be able to cache the results of a compilation to speed-up subsequent compilations. Maintaining a compilation cache can be useful in both local and ci development scenarios. A compilation cache can provide benefits like: diff --git a/test/cpp/features/debugging.feature b/test/cpp/features/debugging.feature index 33c0fea5..e3b01754 100644 --- a/test/cpp/features/debugging.feature +++ b/test/cpp/features/debugging.feature @@ -5,7 +5,7 @@ Feature: Debugging I want to be able to debug my source code within the development environment Rule: Debugging support - amp-devcontainer *SHALL* provide debugging support for the primary programming languages used within the container. + amp-devcontainer *SHALL* provide debugging support for the primary programming language(s) used within the container. Providing debugging support within the development environment enhances the developer experience and productivity. It allows developers to efficiently identify and resolve issues in their code by setting breakpoints, inspecting variables, and stepping through code execution. @@ -20,3 +20,11 @@ Feature: Debugging Then the debugger should stop at line 5 of "debugging/main.cpp" When the debugger is continued Then the program output should be "Hello, Debugging!" + + Rule: Upload firmware to micro-controller + amp-devcontainer *MAY* provide tools to upload compiled firmware to a connected micro-controller. + + Providing tools to upload compiled firmware to a connected micro-controller enhances the development workflow for embedded systems. + It allows developers to quickly and easily transfer their compiled code to the target hardware for testing and debugging. + This capability is essential for validating the functionality of the firmware on the actual device, ensuring that it behaves as expected in real-world scenarios. + By having integrated tools for firmware upload, developers can streamline their workflow, reduce manual steps, and improve overall productivity when working with micro-controllers. diff --git a/test/cpp/features/maintainability.feature b/test/cpp/features/maintainability.feature index c9eb8b06..e1d3c8d4 100644 --- a/test/cpp/features/maintainability.feature +++ b/test/cpp/features/maintainability.feature @@ -1 +1,38 @@ Feature: Maintainability + + As a software craftsperson + to ensure that I have access to a stable and reliable development environment + I want my development environment to be maintainable over time. + + Rule: Tool and dependency updates + amp-devcontainer *SHOULD* contain up-to-date tools and dependencies. + + Keeping tools and dependencies up-to-date helps ensure that the development environment remains secure, stable, and compatible with the latest technologies. + It also helps prevent issues related to deprecated or unsupported software versions, reducing maintenance overhead and improving overall developer productivity. + Regular updates can also introduce new features and improvements that enhance the development experience. + + Rule: Automatic updates + amp-devcontainer *SHOULD* provide support for automatic updates when consumed as a dependency. + + Providing support for automatic updates when amp-devcontainer is consumed as a dependency helps ensure that users always have access to the latest features, bug fixes, and security patches. + This reduces the maintenance burden on users, as they do not need to manually track and apply updates. + Automatic updates can also help ensure compatibility with other dependencies and tools, improving the overall stability and reliability of the development environment. + + Rule: Architectural decisions + amp-devcontainer *SHOULD* document its architectural decisions. + + Documenting architectural decisions helps provide context and rationale for the design choices made in the development environment. + This information can be valuable for future maintainers, as it helps them understand the reasoning behind certain implementations and can guide them in making informed decisions when modifying or extending the environment. + Clear documentation of architectural decisions can also facilitate collaboration among team members and improve overall maintainability. + + Rule: Container image size + amp-devcontainer *SHOULD* aim to keep its container image size as small as possible without compromising functionality. + + Keeping the container image size small helps improve performance, reduce resource consumption, and minimize the time required to download and deploy the development environment. + A smaller image size can also help reduce storage costs and improve scalability, particularly in cloud-based environments. + By optimizing the container image size, amp-devcontainer can provide a more efficient and user-friendly experience for developers. + + + - The container image size is monitored. + - The compressed image size is kept below 1 GiB. + diff --git a/test/cpp/features/security.feature b/test/cpp/features/security.feature index 7268b2d3..4b61af2e 100644 --- a/test/cpp/features/security.feature +++ b/test/cpp/features/security.feature @@ -20,7 +20,7 @@ Feature: Security This helps mitigate several classes of [supply chain threats](https://slsa.dev/spec/v1.0/threats). Rule: Software Bill of Materials (SBOM) - amp-devcontainer *SHALL* provide a Software Bill of Materials (SBOM) for its released container images. + amp-devcontainer *SHOULD* provide a Software Bill of Materials (SBOM) for its released container images. Providing a Software Bill of Materials (SBOM) enables consumers to identify and manage security risks associated with the software components included in the container images. It helps identify known vulnerabilities, license compliance issues, and potential supply chain risks. diff --git a/test/cpp/features/static-dynamic-analysis.feature b/test/cpp/features/static-dynamic-analysis.feature index e42504c5..b2cf8728 100644 --- a/test/cpp/features/static-dynamic-analysis.feature +++ b/test/cpp/features/static-dynamic-analysis.feature @@ -4,9 +4,44 @@ Feature: Static and dynamic analysis to maintain consistent, high-quality and bug-free code I want my source code to be statically and dynamically analyzed + Rule: Code formatting + amp-devcontainer *MAY* provide code formatting tools for the primary programming language(s) used within the container. + + Providing code formatting tools helps maintain a consistent coding style across the codebase, improving readability and reducing friction during code reviews. + It also helps catch potential issues early by enforcing coding standards and best practices. + By integrating code formatting tools into the development environment, developers can easily format their code according to predefined rules, ensuring that the code adheres to the project's style guidelines. + @flavor:cpp @fixme Scenario: Format source code according to a formatting style Given the file "clang-tools/unformatted.cpp" is opened in the editor When the active document is formatted And the active document is saved Then the contents of "clang-tools/unformatted.cpp" should match the contents of "clang-tools/formatted.cpp" + + Rule: Static analysis + amp-devcontainer *MAY* provide static analysis tools for the primary programming language(s) used within the container. + + Providing static analysis tools helps identify potential issues in the code before it is executed, improving code quality and reducing the likelihood of runtime errors. + These tools can analyze the code for common pitfalls, coding standards violations, and potential bugs, providing developers with valuable feedback early in the development process. + By integrating static analysis tools into the development environment, developers can catch issues before they become more significant problems, streamlining the development workflow and improving overall code quality. + + Rule: Coverage analysis + amp-devcontainer *SHOULD* provide code coverage analysis tools for the primary programming language(s) used within the container. + + Providing code coverage analysis tools helps assess the effectiveness of the existing test suite by measuring how much of the code is exercised by the tests. + This information can help identify gaps in test coverage, ensuring that critical parts of the code are adequately tested. + By integrating code coverage analysis tools into the development environment, developers can improve their test suites, leading to higher code quality and increased confidence in the software's correctness. + + Rule: Mutation testing + amp-devcontainer *MAY* provide mutation testing tools for the primary programming language(s) used within the container. + + Providing mutation testing tools helps assess the effectiveness of the existing test suite by introducing small changes (mutations) to the code and checking if the tests can detect these changes. + This process helps identify gaps in test coverage and ensures that the tests are robust enough to catch potential issues in the code. + By integrating mutation testing tools into the development environment, developers can improve their test suites, leading to higher code quality and increased confidence in the software's correctness. + + Rule: Fuzz testing + amp-devcontainer *MAY* provide fuzz testing tools for the primary programming language(s) used within the container. + + Providing fuzz testing tools helps identify potential security vulnerabilities and robustness issues in the code by automatically generating and executing a large number of random inputs. + This process can uncover edge cases and unexpected behaviors that may not be covered by traditional testing methods. + By integrating fuzz testing tools into the development environment, developers can improve the overall security and reliability of their software, reducing the risk of vulnerabilities being exploited in production. From 9e239d16dae91d4528b63cdd426fe5fbdf8254c4 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Mon, 29 Sep 2025 18:44:23 +0000 Subject: [PATCH 22/35] chore: refactor gherkin to sbdl --- docs/support/gherkin-to-csv.py | 75 ---- docs/support/gherkin-to-sbdl.py | 343 ++++++++++-------- docs/support/gherkin_mapping_config.py | 106 ++++++ docs/templates/requirements.template.md | 45 --- .../software-requirements-specification.md.j2 | 19 +- 5 files changed, 311 insertions(+), 277 deletions(-) delete mode 100755 docs/support/gherkin-to-csv.py create mode 100644 docs/support/gherkin_mapping_config.py delete mode 100644 docs/templates/requirements.template.md diff --git a/docs/support/gherkin-to-csv.py b/docs/support/gherkin-to-csv.py deleted file mode 100755 index 5144c632..00000000 --- a/docs/support/gherkin-to-csv.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import csv -import os -import sys -from dataclasses import dataclass, asdict, fields -from typing import List -from gherkin.parser import Parser -from gherkin.token_scanner import TokenScanner - -@dataclass -class Rule: - """A class representing a rule extracted from a Gherkin feature file.""" - identifier: str - description: str - - @classmethod - def field_names(cls) -> List[str]: - return [f.name for f in fields(cls)] - -def extract_rules_from_feature(file_path): - """Parse a Gherkin feature file and extract the rules.""" - try: - with open(file_path, 'r', encoding='utf-8') as file: - content = file.read() - - parser = Parser() - feature_document = parser.parse(TokenScanner(content)) - rules = [] - - if 'feature' in feature_document: - feature = feature_document['feature'] - - if 'children' in feature: - for child in feature['children']: - if 'rule' in child: - rule = child['rule'] - rule_id = rule.get('name', '').strip() - description = rule.get('description', '').strip() - rules.append(Rule( - identifier=rule_id, - description=description - )) - - return rules - except Exception as e: - print(f"Error parsing {file_path}: {e}", file=sys.stderr) - return [] - -def main(): - parser = argparse.ArgumentParser(description='Extract rules from Gherkin feature files and save to CSV') - parser.add_argument('feature_files', nargs='+', help='Paths to feature files') - parser.add_argument('--output', '-o', default='rules.csv', help='Output CSV file path') - - args = parser.parse_args() - all_rules = [] - - for feature_path in args.feature_files: - if os.path.isfile(feature_path): - print(f"Processing {feature_path}") - rules = extract_rules_from_feature(feature_path) - all_rules.extend(rules) - else: - print(f"File not found: {feature_path}", file=sys.stderr) - - with open(args.output, 'w', newline='', encoding='utf-8') as csvfile: - writer = csv.DictWriter(csvfile, fieldnames=Rule.field_names()) - writer.writeheader() - writer.writerows([asdict(rule) for rule in all_rules]) - - print(f"Extracted {len(all_rules)} rules to {args.output}") - -if __name__ == '__main__': - main() diff --git a/docs/support/gherkin-to-sbdl.py b/docs/support/gherkin-to-sbdl.py index 0499aec5..b13fcdf7 100755 --- a/docs/support/gherkin-to-sbdl.py +++ b/docs/support/gherkin-to-sbdl.py @@ -1,181 +1,232 @@ #!/usr/bin/env python3 +""" +Generalized Gherkin to SBDL converter with configurable hierarchy mapping. +""" import argparse import os import sys -from dataclasses import dataclass -from typing import List, Dict +from dataclasses import dataclass, asdict +from typing import List, Dict, Optional from gherkin.parser import Parser from gherkin.token_scanner import TokenScanner import sbdl +from gherkin_mapping_config import ( + GherkinElementType, + SBDLElementType, + HierarchyMapping, + ConversionConfig, + FEATURE_RULE_CONFIG, + FEATURE_RULE_SCENARIO_CONFIG +) + @dataclass -class SBDLRequirement: - """A class representing an SBDL requirement.""" +class SBDLElement: + """A generalized SBDL element that can represent any type.""" identifier: str + element_type: SBDLElementType description: str - parent: str = "" + parent: Optional[str] = None + metadata: Dict = None -def make_sbdl_identifier(name: str) -> str: - """Convert a name to a valid SBDL identifier using SBDL's built-in functions.""" - # Replace spaces and dashes with underscores first, then use SBDL's sanitization - identifier = name.replace(" ", "_").replace("-", "_") - - # Use SBDL's built-in identifier sanitization - identifier = sbdl.SBDL_Parser.sanitize_identifier(identifier) - - # Additional cleanup for readability - # Remove multiple consecutive underscores - while "__" in identifier: - identifier = identifier.replace("__", "_") - - # Remove leading/trailing underscores - identifier = identifier.strip("_") - - return identifier + def __post_init__(self): + self.identifier = self._make_sbdl_identifier(self.identifier) + self.description = self._unindent_description_text(self.description) -def clean_description_text(text: str) -> str: - """Clean up description text by removing excessive indentation and normalizing whitespace.""" - if not text: - return text - - lines = text.split('\n') - - # Find the minimum indentation (excluding empty lines) - non_empty_lines = [line for line in lines if line.strip()] - if not non_empty_lines: - return text.strip() - - min_indent = min(len(line) - len(line.lstrip()) for line in non_empty_lines) - - # Remove the common indentation from all lines - cleaned_lines = [] - for line in lines: - if line.strip(): # Non-empty line - cleaned_lines.append(line[min_indent:] if len(line) >= min_indent else line) - else: # Empty line - cleaned_lines.append('') - - # Join lines and clean up excessive whitespace - return '\n'.join(cleaned_lines).strip() - -def extract_requirements_from_feature(file_path: str) -> Dict[str, List[SBDLRequirement]]: - """Parse a Gherkin feature file and extract requirements in SBDL format.""" - try: - with open(file_path, 'r', encoding='utf-8') as file: - content = file.read() - - parser = Parser() - feature_document = parser.parse(TokenScanner(content)) - features = {} - - if 'feature' in feature_document: - feature = feature_document['feature'] - - # Extract feature information - feature_name = feature.get('name', '').strip() - feature_description = clean_description_text(feature.get('description', '')) - - if not feature_name: - print(f"Warning: Feature in {file_path} has no name", file=sys.stderr) - return features - - feature_id = make_sbdl_identifier(feature_name) - # Create feature requirement - feature_requirement = SBDLRequirement( - identifier=feature_id, - description=feature_description + if self.metadata is None: + self.metadata = {} + + def _make_sbdl_identifier(self, name: str) -> str: + identifier = name.replace(" ", "_").replace("-", "_") + identifier = sbdl.SBDL_Parser.sanitize_identifier(identifier) + identifier = identifier.strip("_") + + return identifier + + def _unindent_description_text(self, text: str) -> str: + if not text: + return text + + lines = text.split('\n') + non_empty_lines = [line for line in lines if line.strip()] + if not non_empty_lines: + return text.strip() + + min_indent = min(len(line) - len(line.lstrip()) for line in non_empty_lines) + + cleaned_lines = [] + for line in lines: + if line.strip(): + cleaned_lines.append(line[min_indent:] if len(line) >= min_indent else line) + else: + cleaned_lines.append('') + + return '\n'.join(cleaned_lines).strip() + +class GherkinConverter: + """Converts Gherkin files to SBDL using configurable hierarchy mappings.""" + + def __init__(self, config: ConversionConfig): + self.config = config + + def extract_gherkin_element(self, element_data: Dict, element_type: GherkinElementType, + parent_id: Optional[str] = None) -> Optional[SBDLElement]: + """Extract a single Gherkin element and convert it to SBDL.""" + mapping = self.config.get_mapping_for_type(element_type) + + if not mapping: + return None + + name = element_data.get('name', '').strip() + if not name: + return None + + return SBDLElement( + identifier=name, + element_type=mapping.sbdl_type, + description=element_data.get('description', ''), + parent=parent_id, + metadata={ + 'gherkin_type': element_type.value, + 'original_name': name, + 'line': element_data.get('location', {}).get('line') + } + ) + + def extract_from_feature_file(self, file_path: str) -> List[SBDLElement]: + """Extract all configured elements from a Gherkin feature file.""" + try: + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + + parser = Parser() + feature_document = parser.parse(TokenScanner(content)) + elements = [] + + if 'feature' not in feature_document: + return elements + + feature_data = feature_document['feature'] + + # Extract feature + feature_element = self.extract_gherkin_element( + feature_data, GherkinElementType.FEATURE ) - - rules = [] - - # Extract rules under this feature - if 'children' in feature: - for child in feature['children']: - if 'rule' in child: - rule = child['rule'] - rule_name = rule.get('name', '').strip() - rule_description = clean_description_text(rule.get('description', '')) - - if not rule_name: - continue - - rule_id = make_sbdl_identifier(rule_name) - rule_requirement = SBDLRequirement( - identifier=rule_id, - description=rule_description, - parent=feature_id + if feature_element: + elements.append(feature_element) + print(f"Extracted Feature: {feature_element.identifier}") + + # Extract children (rules, scenarios, etc.) + if 'children' in feature_data: + for child in feature_data['children']: + child_elements = self._extract_child_elements( + child, feature_element.identifier if feature_element else None + ) + elements.extend(child_elements) + + return elements + + except Exception as e: + print(f"Error parsing {file_path}: {e}", file=sys.stderr) + return [] + + def _extract_child_elements(self, child_data: Dict, parent_id: Optional[str]) -> List[SBDLElement]: + """Recursively extract child elements from Gherkin data.""" + elements = [] + + # Check for rule + if 'rule' in child_data: + rule_element = self.extract_gherkin_element( + child_data['rule'], GherkinElementType.RULE, parent_id + ) + if rule_element: + elements.append(rule_element) + print(f" Extracted Rule: {rule_element.identifier}") + + # Extract scenarios under rule + if 'children' in child_data['rule']: + for rule_child in child_data['rule']['children']: + scenario_elements = self._extract_child_elements( + rule_child, rule_element.identifier ) - rules.append(rule_requirement) - - features[feature_id] = { - 'feature': feature_requirement, - 'rules': rules - } + elements.extend(scenario_elements) - return features - except Exception as e: - print(f"Error parsing {file_path}: {e}", file=sys.stderr) - return {} - -def write_sbdl_output(all_features: Dict[str, Dict], output_file: str): - """Write SBDL format output using SBDL Python interface.""" - # Get SBDL syntax tokens and types - tokens = sbdl.SBDL_Parser.Tokens - types = sbdl.SBDL_Parser.Types - attrs = sbdl.SBDL_Parser.Attributes - - # Open output file using SBDL's file handler - with sbdl.open_output_file(output_file) as f: - # Write SBDL file header - f.write("#!sbdl\n") - - for feature_id, feature_data in all_features.items(): - feature_req = feature_data['feature'] - rules = feature_data['rules'] - - # Write feature requirement using proper SBDL syntax - escaped_desc = sbdl.SBDL_Parser.sanitize(feature_req.description) - f.write(f"{feature_req.identifier} {tokens.declaration} {types.requirement} ") - f.write(f"{tokens.declaration_group_delimeters[0]} ") - f.write(f"{attrs.description}{tokens.declaration_attribute_assign}") - f.write(f"{tokens.declaration_attribute_delimeter}{escaped_desc}{tokens.declaration_attribute_delimeter} ") - f.write(f"{tokens.declaration_group_delimeters[1]}\n") - - # Write child rule requirements - for rule in rules: - escaped_desc = sbdl.SBDL_Parser.sanitize(rule.description) - f.write(f"{rule.identifier} {tokens.declaration} {types.requirement} ") + # Check for scenario + elif 'scenario' in child_data: + scenario_element = self.extract_gherkin_element( + child_data['scenario'], GherkinElementType.SCENARIO, parent_id + ) + if scenario_element: + elements.append(scenario_element) + print(f" Extracted Scenario: {scenario_element.identifier}") + + # Check for scenario outline + elif 'scenarioOutline' in child_data: + outline_element = self.extract_gherkin_element( + child_data['scenarioOutline'], GherkinElementType.SCENARIO_OUTLINE, parent_id + ) + if outline_element: + elements.append(outline_element) + print(f" Extracted Scenario Outline: {outline_element.identifier}") + + return elements + + def write_sbdl_output(self, elements: List[SBDLElement], output_file: str): + """Write elements to SBDL format.""" + # Get SBDL syntax tokens and types + tokens = sbdl.SBDL_Parser.Tokens + types = sbdl.SBDL_Parser.Types + attrs = sbdl.SBDL_Parser.Attributes + + with open(output_file, 'w', encoding='utf-8') as f: + f.write("#!sbdl\n") + + for element in elements: + escaped_desc = sbdl.SBDL_Parser.sanitize(element.description) + sbdl_type = element.element_type.value + + # Write element declaration + f.write(f"{element.identifier} {tokens.declaration} {sbdl_type} ") f.write(f"{tokens.declaration_group_delimeters[0]} ") f.write(f"{attrs.description}{tokens.declaration_attribute_assign}") f.write(f"{tokens.declaration_attribute_delimeter}{escaped_desc}{tokens.declaration_attribute_delimeter} ") - f.write(f"{attrs.parent}{tokens.declaration_attribute_assign}{rule.parent} ") + + # Add parent relationship if exists + if element.parent: + f.write(f"{attrs.parent}{tokens.declaration_attribute_assign}{element.parent} ") + f.write(f"{tokens.declaration_group_delimeters[1]}\n") def main(): - parser = argparse.ArgumentParser(description='Extract requirements from Gherkin feature files and generate SBDL') + configs = { + 'feature-rule': FEATURE_RULE_CONFIG, + 'feature-rule-scenario': FEATURE_RULE_SCENARIO_CONFIG + } + + parser = argparse.ArgumentParser(description='Configurable Gherkin to SBDL converter') parser.add_argument('feature_files', nargs='+', help='Paths to feature files') - parser.add_argument('--output', '-o', default='output.sbdl', help='Output SBDL file path') + parser.add_argument('--output', '-o', default='output.sbdl', help='Output SBDL file') + parser.add_argument('--config', choices=configs.keys(), + default=list(configs.keys())[0], help='Conversion configuration preset') args = parser.parse_args() - all_features = {} - total_requirements = 0 + config = configs[args.config] + converter = GherkinConverter(config) + # Process all feature files + all_elements = [] for feature_path in args.feature_files: if os.path.isfile(feature_path): print(f"Processing {feature_path}") - features = extract_requirements_from_feature(feature_path) - all_features.update(features) - - # Count requirements - for feature_data in features.values(): - total_requirements += 1 # Feature requirement - total_requirements += len(feature_data['rules']) # Rule requirements + elements = converter.extract_from_feature_file(feature_path) + all_elements.extend(elements) else: print(f"File not found: {feature_path}", file=sys.stderr) - write_sbdl_output(all_features, args.output) - print(f"Extracted {total_requirements} requirements to {args.output}") - + # Write SBDL output + converter.write_sbdl_output(all_elements, args.output) + print(f"Extracted {len(all_elements)} elements to {args.output}") + if __name__ == '__main__': main() diff --git a/docs/support/gherkin_mapping_config.py b/docs/support/gherkin_mapping_config.py new file mode 100644 index 00000000..113e5e59 --- /dev/null +++ b/docs/support/gherkin_mapping_config.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 + +""" +Configuration-driven Gherkin to SBDL mapping with flexible hierarchy support. +""" + +from dataclasses import dataclass +from typing import Dict, List, Optional, Union +from enum import Enum + +class GherkinElementType(Enum): + """Supported Gherkin element types for conversion.""" + FEATURE = "feature" + RULE = "rule" + SCENARIO = "scenario" + SCENARIO_OUTLINE = "scenario_outline" + EXAMPLE = "example" + BACKGROUND = "background" + +class SBDLElementType(Enum): + """Supported SBDL element types for mapping.""" + REQUIREMENT = "requirement" + ASPECT = "aspect" + USECASE = "usecase" + DEFINITION = "definition" + TEST = "test" + +@dataclass +class HierarchyMapping: + """Configuration for mapping Gherkin elements to SBDL with document hierarchy.""" + gherkin_type: GherkinElementType + sbdl_type: SBDLElementType + +@dataclass +class ConversionConfig: + """Complete configuration for Gherkin to SBDL conversion.""" + hierarchy_mappings: List[HierarchyMapping] + + def get_mapping_for_type(self, gherkin_type: GherkinElementType) -> Optional[HierarchyMapping]: + """Get the hierarchy mapping for a specific Gherkin element type.""" + for mapping in self.hierarchy_mappings: + if mapping.gherkin_type == gherkin_type: + return mapping + return None + +# Predefined configurations for different use cases + +# Current Configuration (Feature -> Rule mapping) +FEATURE_RULE_CONFIG = ConversionConfig( + hierarchy_mappings=[ + HierarchyMapping( + gherkin_type=GherkinElementType.FEATURE, + sbdl_type=SBDLElementType.REQUIREMENT + ), + HierarchyMapping( + gherkin_type=GherkinElementType.RULE, + sbdl_type=SBDLElementType.REQUIREMENT + ) + ] +) + +# Extended Configuration (Feature -> Rule -> Scenario mapping) +FEATURE_RULE_SCENARIO_CONFIG = ConversionConfig( + hierarchy_mappings=[ + HierarchyMapping( + gherkin_type=GherkinElementType.FEATURE, + sbdl_type=SBDLElementType.ASPECT + ), + HierarchyMapping( + gherkin_type=GherkinElementType.RULE, + sbdl_type=SBDLElementType.REQUIREMENT + ), + HierarchyMapping( + gherkin_type=GherkinElementType.SCENARIO, + sbdl_type=SBDLElementType.TEST + ) + ] +) + +# Flat Configuration (All as requirements at same level) +FLAT_CONFIG = ConversionConfig( + hierarchy_mappings=[ + HierarchyMapping( + gherkin_type=GherkinElementType.FEATURE, + sbdl_type=SBDLElementType.REQUIREMENT + ), + HierarchyMapping( + gherkin_type=GherkinElementType.RULE, + sbdl_type=SBDLElementType.REQUIREMENT + ) + ] +) + +# Use Case Focused Configuration +USECASE_CONFIG = ConversionConfig( + hierarchy_mappings=[ + HierarchyMapping( + gherkin_type=GherkinElementType.FEATURE, + sbdl_type=SBDLElementType.USECASE + ), + HierarchyMapping( + gherkin_type=GherkinElementType.SCENARIO, + sbdl_type=SBDLElementType.USECASE + ) + ] +) diff --git a/docs/templates/requirements.template.md b/docs/templates/requirements.template.md deleted file mode 100644 index 3b941f2a..00000000 --- a/docs/templates/requirements.template.md +++ /dev/null @@ -1,45 +0,0 @@ -# amp-devcontainer requirement specification - -## Introduction - -### Purpose - -This document describes the software system requirements for amp-devcontainer. - -### Definitions of key words - -The key words *MUST*, *MUST NOT*, *REQUIRED*, *SHALL*, *SHALL NOT*, *SHOULD*, *SHOULD NOT*, *RECOMMENDED*, *MAY*, and *OPTIONAL* in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). - -### Abstract - -amp-devcontainer is a set of [devcontainers](https://containers.dev/) tailored towards modern, embedded, software development. - -The containers may be used both for local development and continuous integration (ci). - -### Terminology and Abbreviations - -| Terminology and Abbreviations | Description/Definition | -|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ARM | A family of RISC architectures for computer processors and micro controllers, formerly an acronym for Advanced RISC Machines and originally Acorn RISC Machine | -| Continuous Integration (ci) | The practice of continuously merging developers work to a shared code-base; ideally including automation for build, test and deployment | -| ELF | Executable and Linkable Format, formerly named Extensible Linking Format | -| RISC | Reduced Instruction Set Computer | - -## Requirements - -{%- macro reencode(text) -%} -{{ text.encode('utf-8').decode('unicode_escape') }} -{%- endmacro -%} - -{%- macro sbdl_id_to_header(text) -%} -{{ text.replace('_', ' ') }} -{%- endmacro -%} - -{% for req_id, requirement in sbdl.items() %} -{% if requirement.type == 'requirement' %} -### {{ reencode(sbdl_id_to_header(req_id)) }} - -{{ reencode(requirement.description) }} - -{% endif %} -{% endfor %} diff --git a/docs/templates/software-requirements-specification.md.j2 b/docs/templates/software-requirements-specification.md.j2 index 2534d3d8..e0ead35f 100644 --- a/docs/templates/software-requirements-specification.md.j2 +++ b/docs/templates/software-requirements-specification.md.j2 @@ -1,7 +1,6 @@ --- title: "Software requirements specification for amp-devcontainer" author: ["@rjaegers"] -block-headings: 4 date: "Sept-2025" keywords: [Software, Requirements, SRS, amp-devcontainer] lang: "en" @@ -13,25 +12,23 @@ titlepage-rule-height: 2 toc: true ... -# Software requirements specification for amp-devcontainer +# Introduction -## Introduction - -### Purpose +## Purpose This document describes the software system requirements for amp-devcontainer. -### Definitions of key words +## Definitions of key words The key words *MUST*, *MUST NOT*, *REQUIRED*, *SHALL*, *SHALL NOT*, *SHOULD*, *SHOULD NOT*, *RECOMMENDED*, *MAY*, and *OPTIONAL* in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). -### Abstract +## Abstract amp-devcontainer is a set of [devcontainers](https://containers.dev/) tailored towards modern, embedded, software development. The containers may be used both for local development and continuous integration (ci). -### Terminology and Abbreviations +## Terminology and Abbreviations | Terminology and Abbreviations | Description/Definition | |-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -41,7 +38,7 @@ The containers may be used both for local development and continuous integration | ELF | Executable and Linkable Format, formerly named Extensible Linking Format | | RISC | Reduced Instruction Set Computer | -## Requirements +# Requirements {%- macro reencode(text) -%} {{ text.encode('utf-8').decode('unicode_escape') }} @@ -54,7 +51,7 @@ The containers may be used both for local development and continuous integration {%- for req_id, requirement in sbdl.items() %} {%- if requirement.type == 'requirement' and 'parent' not in requirement %} -### {{ reencode(sbdl_id_to_header(req_id)) }} +## {{ reencode(sbdl_id_to_header(req_id)) }} {{ reencode(requirement.description) }} @@ -62,7 +59,7 @@ The containers may be used both for local development and continuous integration {%- for child in requirement.child %} {%- set child_req = sbdl[child.identifier] %} -#### {{ reencode(sbdl_id_to_header(child.identifier)) }} +### {{ reencode(sbdl_id_to_header(child.identifier)) }} {{ reencode(child_req.description) }} From 8ef18fc8f240d4155568565967b3dea8d94ed167 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Wed, 1 Oct 2025 09:00:51 +0200 Subject: [PATCH 23/35] Update docs/templates/software-requirements-specification.md.j2 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ron <45816308+rjaegers@users.noreply.github.com> --- docs/templates/software-requirements-specification.md.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/templates/software-requirements-specification.md.j2 b/docs/templates/software-requirements-specification.md.j2 index e0ead35f..9620b973 100644 --- a/docs/templates/software-requirements-specification.md.j2 +++ b/docs/templates/software-requirements-specification.md.j2 @@ -1,7 +1,7 @@ --- title: "Software requirements specification for amp-devcontainer" author: ["@rjaegers"] -date: "Sept-2025" +date: "September-2025" keywords: [Software, Requirements, SRS, amp-devcontainer] lang: "en" titlepage: true From 5a918183682339930c877246cc60fcec40a0634b Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Wed, 1 Oct 2025 09:46:58 +0000 Subject: [PATCH 24/35] chore: refactor for testability --- docs/support/gherkin-to-sbdl.py | 209 +------------------------ docs/support/gherkin_sbdl_converter.py | 158 +++++++++++++++++++ 2 files changed, 166 insertions(+), 201 deletions(-) create mode 100644 docs/support/gherkin_sbdl_converter.py diff --git a/docs/support/gherkin-to-sbdl.py b/docs/support/gherkin-to-sbdl.py index b13fcdf7..8c6d505e 100755 --- a/docs/support/gherkin-to-sbdl.py +++ b/docs/support/gherkin-to-sbdl.py @@ -1,202 +1,11 @@ #!/usr/bin/env python3 -""" -Generalized Gherkin to SBDL converter with configurable hierarchy mapping. -""" import argparse import os import sys -from dataclasses import dataclass, asdict -from typing import List, Dict, Optional -from gherkin.parser import Parser -from gherkin.token_scanner import TokenScanner -import sbdl -from gherkin_mapping_config import ( - GherkinElementType, - SBDLElementType, - HierarchyMapping, - ConversionConfig, - FEATURE_RULE_CONFIG, - FEATURE_RULE_SCENARIO_CONFIG -) - -@dataclass -class SBDLElement: - """A generalized SBDL element that can represent any type.""" - identifier: str - element_type: SBDLElementType - description: str - parent: Optional[str] = None - metadata: Dict = None - - def __post_init__(self): - self.identifier = self._make_sbdl_identifier(self.identifier) - self.description = self._unindent_description_text(self.description) - - if self.metadata is None: - self.metadata = {} - - def _make_sbdl_identifier(self, name: str) -> str: - identifier = name.replace(" ", "_").replace("-", "_") - identifier = sbdl.SBDL_Parser.sanitize_identifier(identifier) - identifier = identifier.strip("_") - - return identifier - - def _unindent_description_text(self, text: str) -> str: - if not text: - return text - - lines = text.split('\n') - non_empty_lines = [line for line in lines if line.strip()] - if not non_empty_lines: - return text.strip() - - min_indent = min(len(line) - len(line.lstrip()) for line in non_empty_lines) - - cleaned_lines = [] - for line in lines: - if line.strip(): - cleaned_lines.append(line[min_indent:] if len(line) >= min_indent else line) - else: - cleaned_lines.append('') - - return '\n'.join(cleaned_lines).strip() - -class GherkinConverter: - """Converts Gherkin files to SBDL using configurable hierarchy mappings.""" - - def __init__(self, config: ConversionConfig): - self.config = config - - def extract_gherkin_element(self, element_data: Dict, element_type: GherkinElementType, - parent_id: Optional[str] = None) -> Optional[SBDLElement]: - """Extract a single Gherkin element and convert it to SBDL.""" - mapping = self.config.get_mapping_for_type(element_type) - - if not mapping: - return None - - name = element_data.get('name', '').strip() - if not name: - return None - - return SBDLElement( - identifier=name, - element_type=mapping.sbdl_type, - description=element_data.get('description', ''), - parent=parent_id, - metadata={ - 'gherkin_type': element_type.value, - 'original_name': name, - 'line': element_data.get('location', {}).get('line') - } - ) - - def extract_from_feature_file(self, file_path: str) -> List[SBDLElement]: - """Extract all configured elements from a Gherkin feature file.""" - try: - with open(file_path, 'r', encoding='utf-8') as file: - content = file.read() - - parser = Parser() - feature_document = parser.parse(TokenScanner(content)) - elements = [] - - if 'feature' not in feature_document: - return elements - - feature_data = feature_document['feature'] - - # Extract feature - feature_element = self.extract_gherkin_element( - feature_data, GherkinElementType.FEATURE - ) - if feature_element: - elements.append(feature_element) - print(f"Extracted Feature: {feature_element.identifier}") - - # Extract children (rules, scenarios, etc.) - if 'children' in feature_data: - for child in feature_data['children']: - child_elements = self._extract_child_elements( - child, feature_element.identifier if feature_element else None - ) - elements.extend(child_elements) - - return elements - - except Exception as e: - print(f"Error parsing {file_path}: {e}", file=sys.stderr) - return [] - - def _extract_child_elements(self, child_data: Dict, parent_id: Optional[str]) -> List[SBDLElement]: - """Recursively extract child elements from Gherkin data.""" - elements = [] - - # Check for rule - if 'rule' in child_data: - rule_element = self.extract_gherkin_element( - child_data['rule'], GherkinElementType.RULE, parent_id - ) - if rule_element: - elements.append(rule_element) - print(f" Extracted Rule: {rule_element.identifier}") - - # Extract scenarios under rule - if 'children' in child_data['rule']: - for rule_child in child_data['rule']['children']: - scenario_elements = self._extract_child_elements( - rule_child, rule_element.identifier - ) - elements.extend(scenario_elements) - - # Check for scenario - elif 'scenario' in child_data: - scenario_element = self.extract_gherkin_element( - child_data['scenario'], GherkinElementType.SCENARIO, parent_id - ) - if scenario_element: - elements.append(scenario_element) - print(f" Extracted Scenario: {scenario_element.identifier}") - - # Check for scenario outline - elif 'scenarioOutline' in child_data: - outline_element = self.extract_gherkin_element( - child_data['scenarioOutline'], GherkinElementType.SCENARIO_OUTLINE, parent_id - ) - if outline_element: - elements.append(outline_element) - print(f" Extracted Scenario Outline: {outline_element.identifier}") - - return elements - - def write_sbdl_output(self, elements: List[SBDLElement], output_file: str): - """Write elements to SBDL format.""" - # Get SBDL syntax tokens and types - tokens = sbdl.SBDL_Parser.Tokens - types = sbdl.SBDL_Parser.Types - attrs = sbdl.SBDL_Parser.Attributes - - with open(output_file, 'w', encoding='utf-8') as f: - f.write("#!sbdl\n") - - for element in elements: - escaped_desc = sbdl.SBDL_Parser.sanitize(element.description) - sbdl_type = element.element_type.value - - # Write element declaration - f.write(f"{element.identifier} {tokens.declaration} {sbdl_type} ") - f.write(f"{tokens.declaration_group_delimeters[0]} ") - f.write(f"{attrs.description}{tokens.declaration_attribute_assign}") - f.write(f"{tokens.declaration_attribute_delimeter}{escaped_desc}{tokens.declaration_attribute_delimeter} ") - - # Add parent relationship if exists - if element.parent: - f.write(f"{attrs.parent}{tokens.declaration_attribute_assign}{element.parent} ") - - f.write(f"{tokens.declaration_group_delimeters[1]}\n") +from gherkin_mapping_config import FEATURE_RULE_CONFIG, FEATURE_RULE_SCENARIO_CONFIG +from gherkin_sbdl_converter import GherkinConverter def main(): configs = { @@ -208,25 +17,23 @@ def main(): parser.add_argument('feature_files', nargs='+', help='Paths to feature files') parser.add_argument('--output', '-o', default='output.sbdl', help='Output SBDL file') parser.add_argument('--config', choices=configs.keys(), - default=list(configs.keys())[0], help='Conversion configuration preset') + default='feature-rule', help='Conversion configuration preset') args = parser.parse_args() config = configs[args.config] converter = GherkinConverter(config) + gherkin_elements = [] - # Process all feature files - all_elements = [] for feature_path in args.feature_files: if os.path.isfile(feature_path): print(f"Processing {feature_path}") elements = converter.extract_from_feature_file(feature_path) - all_elements.extend(elements) + gherkin_elements.extend(elements) else: print(f"File not found: {feature_path}", file=sys.stderr) - # Write SBDL output - converter.write_sbdl_output(all_elements, args.output) - print(f"Extracted {len(all_elements)} elements to {args.output}") - + converter.write_sbdl_output(gherkin_elements, args.output) + print(f"Extracted {len(gherkin_elements)} elements to {args.output}") + if __name__ == '__main__': main() diff --git a/docs/support/gherkin_sbdl_converter.py b/docs/support/gherkin_sbdl_converter.py new file mode 100644 index 00000000..6182a261 --- /dev/null +++ b/docs/support/gherkin_sbdl_converter.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 +"""Gherkin to SBDL converter with configurable hierarchy mappings.""" + +from dataclasses import dataclass +from typing import List, Dict, Optional +from textwrap import dedent + +from gherkin.parser import Parser +from gherkin.token_scanner import TokenScanner +import sbdl + +from gherkin_mapping_config import ( + ConversionConfig, + GherkinElementType, + SBDLElementType, +) + +@dataclass +class SBDLElement: + """A generalized SBDL element that can represent any type.""" + + identifier: str + element_type: SBDLElementType + description: str + parent: Optional[str] = None + metadata: Optional[Dict] = None + + def __post_init__(self): + self.identifier = self._make_sbdl_identifier(self.identifier) + self.description = dedent(self.description) + + if self.metadata is None: + self.metadata = {} + + def _make_sbdl_identifier(self, name: str) -> str: + identifier = name.replace(" ", "_").replace("-", "_") + identifier = sbdl.SBDL_Parser.sanitize_identifier(identifier) + identifier = identifier.strip("_") + + return identifier + +class GherkinConverter: + """Converts Gherkin files to SBDL using configurable hierarchy mappings.""" + + def __init__(self, config: ConversionConfig): + self.config = config + # Strategy map for element extraction: key in parsed Gherkin dict -> handler. + self._strategies = { + 'feature': self._handle_feature, + 'rule': self._handle_rule, + 'scenario': self._handle_scenario, + 'scenarioOutline': self._handle_scenario_outline, + } + + def extract_from_feature_file(self, file_path: str) -> List[SBDLElement]: + """Extract all configured elements from a Gherkin feature file.""" + try: + with open(file_path, 'r', encoding='utf-8') as file: + content = file.read() + + parser = Parser() + feature_document = parser.parse(TokenScanner(content)) + + if 'feature' not in feature_document: + return [] + + return self._dispatch(feature_document, None) + except Exception as e: + print(f"Error parsing {file_path}: {e}", file=sys.stderr) + return [] + + def write_sbdl_output(self, elements: List[SBDLElement], output_file: str): + tokens = sbdl.SBDL_Parser.Tokens + attrs = sbdl.SBDL_Parser.Attributes + + with open(output_file, 'w', encoding='utf-8') as f: + f.write("#!sbdl\n") + + for element in elements: + escaped_desc = sbdl.SBDL_Parser.sanitize(element.description) + sbdl_type = element.element_type.value + f.write(f"{element.identifier} {tokens.declaration} {sbdl_type} ") + f.write(f"{tokens.declaration_group_delimeters[0]} ") + f.write(f"{attrs.description}{tokens.declaration_attribute_assign}") + f.write( + f"{tokens.declaration_attribute_delimeter}{escaped_desc}{tokens.declaration_attribute_delimeter} " + ) + + if element.parent: + f.write(f"{attrs.parent}{tokens.declaration_attribute_assign}{element.parent} ") + + f.write(f"{tokens.declaration_group_delimeters[1]}\n") + + def _extract_gherkin_element(self, element_data: Dict, element_type: GherkinElementType, parent_id: Optional[str]) -> Optional[SBDLElement]: + mapping = self.config.get_mapping_for_type(element_type) + + if not mapping: + return None + + name = element_data.get('name', '').strip() + + if not name: + return None + + return SBDLElement( + identifier=name, + element_type=mapping.sbdl_type, + description=element_data.get('description', ''), + parent=parent_id, + metadata={ + 'gherkin_type': element_type.value, + 'original_name': name, + 'line': element_data.get('location', {}).get('line'), + }, + ) + + def _dispatch(self, node: Dict, parent_id: Optional[str]) -> List[SBDLElement]: + for key, handler in self._strategies.items(): + if key in node: + return handler(node[key], parent_id) + return [] + + def _handle_feature(self, feature_data: Dict, parent_id: Optional[str]) -> List[SBDLElement]: + elements: List[SBDLElement] = [] + feature_element = self._extract_gherkin_element(feature_data, GherkinElementType.FEATURE, parent_id) + feature_id = None + if feature_element: + elements.append(feature_element) + feature_id = feature_element.identifier + print(f"Extracted Feature: {feature_element.identifier}") + for child in feature_data.get('children', []): + elements.extend(self._dispatch(child, feature_id)) + return elements + + def _handle_rule(self, rule_data: Dict, parent_id: Optional[str]) -> List[SBDLElement]: + elements: List[SBDLElement] = [] + rule_element = self._extract_gherkin_element(rule_data, GherkinElementType.RULE, parent_id) + if not rule_element: + return elements + elements.append(rule_element) + print(f" Extracted Rule: {rule_element.identifier}") + for rule_child in rule_data.get('children', []): + elements.extend(self._dispatch(rule_child, rule_element.identifier)) + return elements + + def _handle_scenario(self, scenario_data: Dict, parent_id: Optional[str]) -> List[SBDLElement]: + scenario_element = self._extract_gherkin_element(scenario_data, GherkinElementType.SCENARIO, parent_id) + if not scenario_element: + return [] + print(f" Extracted Scenario: {scenario_element.identifier}") + return [scenario_element] + + def _handle_scenario_outline(self, outline_data: Dict, parent_id: Optional[str]) -> List[SBDLElement]: + outline_element = self._extract_gherkin_element(outline_data, GherkinElementType.SCENARIO_OUTLINE, parent_id) + if not outline_element: + return [] + print(f" Extracted Scenario Outline: {outline_element.identifier}") + return [outline_element] From 8ff7a45ba2fb09eb480b0354770efaca4cb358df Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Wed, 1 Oct 2025 09:59:31 +0000 Subject: [PATCH 25/35] chore: extend the urllib3 ignores with one month --- .github/linters/.trivyignore.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/linters/.trivyignore.yml b/.github/linters/.trivyignore.yml index b79a6b06..c6364809 100644 --- a/.github/linters/.trivyignore.yml +++ b/.github/linters/.trivyignore.yml @@ -6,10 +6,10 @@ vulnerabilities: - id: CVE-2025-50181 paths: - ".devcontainer/cpp/requirements.txt" - expired_at: 2025-10-01 + expired_at: 2025-11-01 statement: This vulnerable dependency comes in via the Conan package, work is in-progress on supporting a non-vulnerable version (https://github.com/conan-io/conan/issues/13948) - id: CVE-2025-50182 paths: - ".devcontainer/cpp/requirements.txt" - expired_at: 2025-10-01 + expired_at: 2025-11-01 statement: This vulnerable dependency comes in via the Conan package, work is in-progress on supporting a non-vulnerable version (https://github.com/conan-io/conan/issues/13948) From b0fd257101f6d789d4f32e3304b287440bb11614 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:35:28 +0000 Subject: [PATCH 26/35] chore: update linter settings We allow feature files without scenarios as we use the feature files as ground-truth for our requirements. As some requirements can be tested on a different level they have no associated scenario. --- .github/linters/.gherkin-lintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/linters/.gherkin-lintrc b/.github/linters/.gherkin-lintrc index 5b9953db..61ada6d9 100644 --- a/.github/linters/.gherkin-lintrc +++ b/.github/linters/.gherkin-lintrc @@ -7,7 +7,7 @@ "no-duplicate-tags": "on", "no-empty-background": "on", "no-empty-file": "on", - "no-files-without-scenarios": "on", + "no-files-without-scenarios": "off", "no-multiple-empty-lines": "on", "no-partially-commented-tag-lines": "on", "no-scenario-outlines-without-examples": "on", From c9113e3102746ada215383c95383a2b6e8393062 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 2 Oct 2025 06:50:04 +0000 Subject: [PATCH 27/35] chore: add missing import --- docs/support/gherkin_sbdl_converter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/support/gherkin_sbdl_converter.py b/docs/support/gherkin_sbdl_converter.py index 6182a261..b59ba12e 100644 --- a/docs/support/gherkin_sbdl_converter.py +++ b/docs/support/gherkin_sbdl_converter.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from typing import List, Dict, Optional from textwrap import dedent +import sys from gherkin.parser import Parser from gherkin.token_scanner import TokenScanner From 6b3eefbd2c1bbf5b91d953147d87913c4baac670 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 2 Oct 2025 06:51:11 +0000 Subject: [PATCH 28/35] chore: add *.pyc to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ef4f50ce..e815c6c8 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,6 @@ test-results/ .env *.profdata *.profraw +*.pyc **/playwright/.auth/user.json CMakeUserPresets.json From 59d76ba99bd18ad3f0ad53ceca15b77f38feb1c2 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 2 Oct 2025 06:54:01 +0000 Subject: [PATCH 29/35] chore: add documents to release upload --- .github/workflows/release-build.yml | 10 ++++++++++ .github/workflows/wc-document-generation.yml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index d6eebf7a..3462f92c 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -90,6 +90,16 @@ jobs: GH_TOKEN: ${{ github.token }} REPOSITORY_OWNER: ${{ github.repository_owner }} REPOSITORY_NAME: ${{ github.event.repository.name }} + - uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 + with: + pattern: documents + - name: Upload documents to release + run: | + set -Eeuo pipefail + gh release upload "${REF_NAME}" ./*.pdf + env: + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} - name: Update package details in release run: | set -Eeuo pipefail diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index c8a6cd69..c5ba78fb 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -33,5 +33,5 @@ jobs: - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: documents - path: software-requirements-specification.pdf + path: "*.pdf" retention-days: 2 From 446a5fc4d003e57de9816a1abf9e51929a34d095 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Thu, 2 Oct 2025 12:33:54 +0000 Subject: [PATCH 30/35] docs: minor cosmetic changes --- .github/workflows/wc-document-generation.yml | 2 +- .../software-requirements-specification.md.j2 | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/workflows/wc-document-generation.yml b/.github/workflows/wc-document-generation.yml index c5ba78fb..95ec7e24 100644 --- a/.github/workflows/wc-document-generation.yml +++ b/.github/workflows/wc-document-generation.yml @@ -29,7 +29,7 @@ jobs: sbdl -m template-fill --template docs/templates/software-requirements-specification.md.j2 output.sbdl > software-requirements-specification.md - uses: docker://pandoc/extra:3.7.0@sha256:a703d335fa237f8fc3303329d87e2555dca5187930da38bfa9010fa4e690933a with: - args: --template eisvogel --listings --output software-requirements-specification.pdf software-requirements-specification.md + args: --template eisvogel --listings --number-sections --output software-requirements-specification.pdf software-requirements-specification.md - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: documents diff --git a/docs/templates/software-requirements-specification.md.j2 b/docs/templates/software-requirements-specification.md.j2 index 9620b973..dda4cbeb 100644 --- a/docs/templates/software-requirements-specification.md.j2 +++ b/docs/templates/software-requirements-specification.md.j2 @@ -1,7 +1,8 @@ --- title: "Software requirements specification for amp-devcontainer" author: ["@rjaegers"] -date: "September-2025" +colorlinks: true +date: "October-2025" keywords: [Software, Requirements, SRS, amp-devcontainer] lang: "en" titlepage: true @@ -10,6 +11,7 @@ titlepage-text-color: "FFFFFF" titlepage-rule-color: "FFFFFF" titlepage-rule-height: 2 toc: true +toc-own-page: true ... # Introduction @@ -30,13 +32,13 @@ The containers may be used both for local development and continuous integration ## Terminology and Abbreviations -| Terminology and Abbreviations | Description/Definition | -|-------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| -| ARM | A family of RISC architectures for computer processors and micro controllers, formerly an acronym for Advanced RISC Machines and originally Acorn RISC Machine | -| Continuous Delivery (cd) | Extends ci by automating the release of validated code, extending test automation to ensure that code is production-ready | -| Continuous Integration (ci) | The practice of continuously integrating developers' work to a shared code-base; including automation for build and test | -| ELF | Executable and Linkable Format, formerly named Extensible Linking Format | -| RISC | Reduced Instruction Set Computer | +| Term | Description/Definition | +|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ARM | A family of RISC architectures for computer processors and micro controllers, formerly an acronym for Advanced RISC Machines and originally Acorn RISC Machine | +| Continuous Delivery (cd) | Extends ci by automating the release of validated code, extending test automation to ensure that code is production-ready | +| Continuous Integration (ci) | The practice of continuously integrating developers' work to a shared code-base; including automation for build and test | +| ELF | Executable and Linkable Format, formerly named Extensible Linking Format | +| RISC | Reduced Instruction Set Computer | # Requirements From 72534db1f1cbbdb31a861d9edd32b96289836597 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Sat, 4 Oct 2025 06:58:07 +0000 Subject: [PATCH 31/35] chore: processed review comments --- .mega-linter.yml | 2 +- test/cpp/features/compatibility.feature | 6 +++--- test/cpp/features/compilation.feature | 6 +++--- test/cpp/features/debugging.feature | 15 +++------------ test/cpp/features/maintainability.feature | 4 ++-- test/cpp/features/security.feature | 6 +++--- test/cpp/features/static-dynamic-analysis.feature | 6 +++--- 7 files changed, 18 insertions(+), 27 deletions(-) diff --git a/.mega-linter.yml b/.mega-linter.yml index 1326467a..700da4c1 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -8,7 +8,6 @@ ENABLE: - SPELL - YAML DISABLE_LINTERS: - - MARKDOWN_MARKDOWN_LINK_CHECK - REPOSITORY_DEVSKIM - REPOSITORY_DUSTILOCK - REPOSITORY_KICS @@ -18,6 +17,7 @@ DISABLE_LINTERS: SARIF_REPORTER: true PRINT_ALPACA: false SHOW_SKIPPED_LINTERS: false +SPELL_LYCHEE_FILE_EXTENSIONS: [".feature", ".json", ".md", "md.j2", ".yaml", ".yml"] FILTER_REGEX_EXCLUDE: (CHANGELOG.md|package-lock.json) # tasks.json is wrongfully matched against another schema, # and schemas for .vscode/[tasks.json|launch.json] are built diff --git a/test/cpp/features/compatibility.feature b/test/cpp/features/compatibility.feature index 01d41869..65cdebd8 100644 --- a/test/cpp/features/compatibility.feature +++ b/test/cpp/features/compatibility.feature @@ -1,8 +1,8 @@ Feature: Compatibility - As a software craftsperson - to ensure that my development environment works well with a variety of tools and systems - I want my development environment to be compatible with commonly used tools and systems + As a software craftsperson, + to ensure that my development environment works well with a variety of tools and systems, + I want my development environment to be compatible with commonly used tools and systems. Rule: Open Container Initiative (OCI) Image Specification amp-devcontainer images *SHALL* be compatible with the [OCI image specification](https://github.com/opencontainers/image-spec/blob/main/spec.md) diff --git a/test/cpp/features/compilation.feature b/test/cpp/features/compilation.feature index 728177d0..de0e3e3c 100644 --- a/test/cpp/features/compilation.feature +++ b/test/cpp/features/compilation.feature @@ -1,8 +1,8 @@ Feature: Compilation - As a software developer - to generate a working product, when using compiled languages - source code needs to be compiled into working software + As a software developer, + to generate a working product, when using compiled languages, + source code needs to be compiled into working software. Rule: Compile for container host architecture and operating system amp-devcontainer *SHALL* be able to compile valid source code into a working executable targeting the container host architecture and operating system. diff --git a/test/cpp/features/debugging.feature b/test/cpp/features/debugging.feature index e3b01754..58a64287 100644 --- a/test/cpp/features/debugging.feature +++ b/test/cpp/features/debugging.feature @@ -1,8 +1,8 @@ Feature: Debugging - As a software craftsperson - to efficiently identify and resolve issues in my code - I want to be able to debug my source code within the development environment + As a software craftsperson, + to efficiently identify and resolve issues in my code, + I want to be able to debug my source code within the development environment. Rule: Debugging support amp-devcontainer *SHALL* provide debugging support for the primary programming language(s) used within the container. @@ -12,15 +12,6 @@ Feature: Debugging This capability is essential for diagnosing complex problems, understanding code flow, and ensuring the correctness of software. By having integrated debugging tools, developers can streamline their workflow and reduce the time spent on troubleshooting and fixing bugs. - @flavor:cpp @fixme - Scenario: Debug a simple C++ program - Given the file "debugging/main.cpp" is opened in the editor - When a breakpoint is set on line 5 of "debugging/main.cpp" - And the debugger is started - Then the debugger should stop at line 5 of "debugging/main.cpp" - When the debugger is continued - Then the program output should be "Hello, Debugging!" - Rule: Upload firmware to micro-controller amp-devcontainer *MAY* provide tools to upload compiled firmware to a connected micro-controller. diff --git a/test/cpp/features/maintainability.feature b/test/cpp/features/maintainability.feature index e1d3c8d4..730ce325 100644 --- a/test/cpp/features/maintainability.feature +++ b/test/cpp/features/maintainability.feature @@ -1,7 +1,7 @@ Feature: Maintainability - As a software craftsperson - to ensure that I have access to a stable and reliable development environment + As a software craftsperson, + to ensure that I have access to a stable and reliable development environment, I want my development environment to be maintainable over time. Rule: Tool and dependency updates diff --git a/test/cpp/features/security.feature b/test/cpp/features/security.feature index 4b61af2e..061a0053 100644 --- a/test/cpp/features/security.feature +++ b/test/cpp/features/security.feature @@ -1,8 +1,8 @@ Feature: Security - As a security engineer and security conscious developer - to have control over the security posture of my development environment - I want to have controls in place to identify and mitigate supply-chain vulnerabilities + As a security engineer and security conscious developer, + to have control over the security posture of my development environment, + I want to have controls in place to identify and mitigate supply-chain vulnerabilities. Rule: Build provenance amp-devcontainer *SHALL* include build provenance as specified in [SLSA v1.0 Build L3](https://slsa.dev/spec/v1.0/levels). diff --git a/test/cpp/features/static-dynamic-analysis.feature b/test/cpp/features/static-dynamic-analysis.feature index b2cf8728..3254790a 100644 --- a/test/cpp/features/static-dynamic-analysis.feature +++ b/test/cpp/features/static-dynamic-analysis.feature @@ -1,8 +1,8 @@ Feature: Static and dynamic analysis - As a software craftsperson - to maintain consistent, high-quality and bug-free code - I want my source code to be statically and dynamically analyzed + As a software craftsperson, + to maintain consistent, high-quality and bug-free code, + I want my source code to be statically and dynamically analyzed. Rule: Code formatting amp-devcontainer *MAY* provide code formatting tools for the primary programming language(s) used within the container. From 76a665ed890a8c8014c3a8cc6a422401a964fdce Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Sat, 4 Oct 2025 07:07:13 +0000 Subject: [PATCH 32/35] chore: correct/extend lychee scope --- .mega-linter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.mega-linter.yml b/.mega-linter.yml index 700da4c1..e75b0729 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -17,7 +17,7 @@ DISABLE_LINTERS: SARIF_REPORTER: true PRINT_ALPACA: false SHOW_SKIPPED_LINTERS: false -SPELL_LYCHEE_FILE_EXTENSIONS: [".feature", ".json", ".md", "md.j2", ".yaml", ".yml"] +SPELL_LYCHEE_FILE_EXTENSIONS: [".feature", ".json", ".md", ".md.j2", ".txt", ".yaml", ".yml"] FILTER_REGEX_EXCLUDE: (CHANGELOG.md|package-lock.json) # tasks.json is wrongfully matched against another schema, # and schemas for .vscode/[tasks.json|launch.json] are built From 5fe9db4e109da87b1c340c073a66ba37f86313fc Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Sat, 4 Oct 2025 09:10:40 +0200 Subject: [PATCH 33/35] Update docs/support/gherkin_sbdl_converter.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ron <45816308+rjaegers@users.noreply.github.com> --- docs/support/gherkin_sbdl_converter.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/docs/support/gherkin_sbdl_converter.py b/docs/support/gherkin_sbdl_converter.py index b59ba12e..afc7aff1 100644 --- a/docs/support/gherkin_sbdl_converter.py +++ b/docs/support/gherkin_sbdl_converter.py @@ -34,12 +34,9 @@ def __post_init__(self): self.metadata = {} def _make_sbdl_identifier(self, name: str) -> str: - identifier = name.replace(" ", "_").replace("-", "_") - identifier = sbdl.SBDL_Parser.sanitize_identifier(identifier) - identifier = identifier.strip("_") - - return identifier - + return sbdl.SBDL_Parser.sanitize_identifier( + name.replace(" ", "_").replace("-", "_") + ).strip("_") class GherkinConverter: """Converts Gherkin files to SBDL using configurable hierarchy mappings.""" From fe3f8ecc59a27d125be006777c8b1987b4051e0f Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Sat, 4 Oct 2025 09:12:17 +0200 Subject: [PATCH 34/35] Update .mega-linter.yml Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Signed-off-by: Ron <45816308+rjaegers@users.noreply.github.com> --- .mega-linter.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.mega-linter.yml b/.mega-linter.yml index e75b0729..b2af1df8 100644 --- a/.mega-linter.yml +++ b/.mega-linter.yml @@ -17,7 +17,8 @@ DISABLE_LINTERS: SARIF_REPORTER: true PRINT_ALPACA: false SHOW_SKIPPED_LINTERS: false -SPELL_LYCHEE_FILE_EXTENSIONS: [".feature", ".json", ".md", ".md.j2", ".txt", ".yaml", ".yml"] +SPELL_LYCHEE_FILE_EXTENSIONS: + [".feature", ".json", ".md", ".md.j2", ".txt", ".yaml", ".yml"] FILTER_REGEX_EXCLUDE: (CHANGELOG.md|package-lock.json) # tasks.json is wrongfully matched against another schema, # and schemas for .vscode/[tasks.json|launch.json] are built From 893436e03eb80849ba64f3fc1ea132d2caf1ae99 Mon Sep 17 00:00:00 2001 From: Ron <45816308+rjaegers@users.noreply.github.com> Date: Sat, 4 Oct 2025 08:36:40 +0000 Subject: [PATCH 35/35] style: add newline between classes --- docs/support/gherkin_sbdl_converter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/support/gherkin_sbdl_converter.py b/docs/support/gherkin_sbdl_converter.py index afc7aff1..888a8c6d 100644 --- a/docs/support/gherkin_sbdl_converter.py +++ b/docs/support/gherkin_sbdl_converter.py @@ -37,6 +37,7 @@ def _make_sbdl_identifier(self, name: str) -> str: return sbdl.SBDL_Parser.sanitize_identifier( name.replace(" ", "_").replace("-", "_") ).strip("_") + class GherkinConverter: """Converts Gherkin files to SBDL using configurable hierarchy mappings."""