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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,14 +99,15 @@ Options:
--results <file> Write validation results to file (TRX or JUnit format)
--log <file> Write output to log file
--lint Lint requirements files for structural issues
--depth <depth> Default markdown header depth for all reports (default: 1)
--requirements <pattern> Requirements files glob pattern
--report <file> Export requirements to markdown file
--report-depth <depth> Markdown header depth for requirements report (default: 1)
--report-depth <depth> Markdown header depth for requirements report (overrides --depth)
--tests <pattern> Test result files glob pattern (TRX or JUnit)
--matrix <file> Export trace matrix to markdown file
--matrix-depth <depth> Markdown header depth for trace matrix (default: 1)
--matrix-depth <depth> Markdown header depth for trace matrix (overrides --depth)
--justifications <file> Export requirement justifications to markdown file
--justifications-depth <depth> Markdown header depth for justifications (default: 1)
--justifications-depth <depth> Markdown header depth for justifications (overrides --depth)
--filter <tags> Comma-separated list of tags to filter requirements
--enforce Fail if requirements are not fully tested
```
Expand Down Expand Up @@ -464,14 +465,19 @@ in case of physical storage theft or unauthorized access to storage media.

### Configuring Header Depth

Use the `--justifications-depth` option to control the markdown header depth (default: 1):
Use the `--depth` option to set the default markdown header depth for all reports (default: 1). Individual
report depths can be overridden with `--report-depth`, `--matrix-depth`, or `--justifications-depth`:

```bash
reqstream --requirements "**/*.yaml" --justifications justifications.md --justifications-depth 2
reqstream --requirements "**/*.yaml" --justifications justifications.md --depth 2
```

This adjusts the header levels in the output, useful when embedding the justifications document in larger
documentation structures.
```bash
reqstream --requirements "**/*.yaml" --justifications justifications.md --depth 2 --justifications-depth 3
```

This adjusts the header levels in the output, useful when embedding the reports in larger documentation
structures.

## Development

Expand Down
11 changes: 8 additions & 3 deletions docs/design/reqstream/cli/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ when the enclosing `using` block in `Program.Main` exits.
| `RequirementsFiles` | `List<string>` | `--requirements` | Expanded list of requirement file paths |
| `TestFiles` | `List<string>` | `--tests` | Expanded list of test-result file paths |
| `RequirementsReport` | `string?` | `--report` | Destination path for requirements report |
| `ReportDepth` | `int` | `--report-depth` | Heading depth for requirements report |
| `Depth` | `int` | `--depth` | Default heading depth for all reports (default: 1) |
| `ReportDepth` | `int` | `--report-depth` | Heading depth for requirements report (overrides `Depth`) |
| `Matrix` | `string?` | `--matrix` | Destination path for trace matrix report |
| `MatrixDepth` | `int` | `--matrix-depth` | Heading depth for trace matrix report |
| `MatrixDepth` | `int` | `--matrix-depth` | Heading depth for trace matrix report (overrides `Depth`) |
| `JustificationsFile` | `string?` | `--justifications` | Destination path for justifications report |
| `JustificationsDepth` | `int` | `--justifications-depth` | Heading depth for justifications report |
| `JustificationsDepth` | `int` | `--justifications-depth` | Heading depth for justifications (overrides `Depth`) |
| `ExitCode` | `int` | — | Computed: `_hasErrors ? 1 : 0` |

## Methods
Expand All @@ -55,6 +56,10 @@ arguments merge into the same set. `--requirements` and `--tests` values are pas
`ExpandGlobPattern` and appended to the respective file lists. If `--log` is specified, the named
file is opened for writing and assigned to `_logWriter` before the method returns.

`--depth` sets the default heading depth (`Depth`). The per-report depth arguments
(`--report-depth`, `--matrix-depth`, `--justifications-depth`) override this default if
specified; otherwise each report inherits the value of `Depth`.

### `ExpandGlobPattern(pattern)`

`ExpandGlobPattern` resolves a single pattern (which may contain `*` or `**` wildcards) to a list
Expand Down
1 change: 1 addition & 0 deletions docs/reqstream/reqstream/cli/cli.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ sections:
- ReqStream-Requirements-TestGlobPatterns
- ReqStream-Command-ReportDepth
- ReqStream-Command-MatrixDepth
- ReqStream-Command-Depth
- ReqStream-Command-TagFilter

- id: ReqStream-Cli-Output
Expand Down
13 changes: 13 additions & 0 deletions docs/reqstream/reqstream/cli/context.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,19 @@ sections:
tests:
- Context_Create_MatrixDepth_SetsMatrixDepthProperty

- id: ReqStream-Command-Depth
title: The tool shall support a default markdown header depth that applies to all reports.
justification: |
A single default depth argument simplifies configuration when all reports are embedded
at the same level in a larger document, while still allowing per-report overrides.
tags:
- cli
tests:
- Context_Create_Depth_SetsAllDepths
- Context_Create_SpecificDepthOverridesDefaultDepth
- Context_Create_MissingDepth_ThrowsException
- Context_Create_InvalidDepth_ThrowsException

- id: ReqStream-Command-TagFilter
title: The tool shall support filtering requirements output by tags.
justification: |
Expand Down
35 changes: 18 additions & 17 deletions docs/user_guide/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -530,15 +530,16 @@ ReqStream supports the following command-line options:
| `--results <file>` | Write validation test results to a file (TRX or JUnit format, use .trx or .xml extension) |
| `--lint` | Lint requirements files for structural issues |
| `--log <file>` | Write output to specified log file |
| `--depth <depth>` | Default starting header depth for all reports (default: 1) |
| `--requirements <pattern>` | Glob pattern for requirements YAML files |
| `--report <file>` | Export requirements to markdown file |
| `--report-depth <depth>` | Starting header depth for requirements report (default: 1) |
| `--report-depth <depth>` | Starting header depth for requirements report (overrides `--depth`) |
| `--filter <tags>` | Comma-separated list of tags to filter requirements by |
| `--tests <pattern>` | Glob pattern for test result files (TRX or JUnit format) |
| `--matrix <file>` | Export trace matrix to markdown file |
| `--matrix-depth <depth>` | Starting header depth for trace matrix (default: 1) |
| `--matrix-depth <depth>` | Starting header depth for trace matrix (overrides `--depth`) |
| `--justifications <file>` | Export justifications to markdown file |
| `--justifications-depth <depth>` | Starting header depth for justifications (default: 1) |
| `--justifications-depth <depth>` | Starting header depth for justifications (overrides `--depth`) |
| `--enforce` | Fail if requirements are not fully tested |

## Examples
Expand Down Expand Up @@ -680,10 +681,9 @@ reqstream --requirements "docs/**/*.yaml" \
```bash
reqstream --requirements "docs/**/*.yaml" \
--report requirements.md \
--report-depth 2 \
--tests "test-results/**/*.trx" \
--matrix matrix.md \
--matrix-depth 1
--depth 2
```

**Silent mode for CI/CD:**
Expand Down Expand Up @@ -890,25 +890,25 @@ reqstream --requirements "docs/**/*.yaml" \

**Control header depth:**

The `--report-depth`, `--matrix-depth`, and `--justifications-depth` options control the starting markdown header
level:
Use `--depth` to set a single default header depth for all reports, or use `--report-depth`,
`--matrix-depth`, and `--justifications-depth` to override the depth for individual reports:

```bash
# Start requirements with ## (level 2) instead of # (level 1)
# Set all reports to start at ## (level 2)
reqstream --requirements "docs/**/*.yaml" \
--tests "test-results/**/*.trx" \
--report requirements.md \
--report-depth 2
--matrix matrix.md \
--justifications justifications.md \
--depth 2

# Start trace matrix sections with ### (level 3)
# Set default depth to 2 but override trace matrix to level 3
reqstream --requirements "docs/**/*.yaml" \
--tests "test-results/**/*.trx" \
--report requirements.md \
--matrix matrix.md \
--depth 2 \
--matrix-depth 3

# Start justifications sections with ## (level 2)
reqstream --requirements "docs/**/*.yaml" \
--justifications justifications.md \
--justifications-depth 2
```

This is useful when embedding generated reports into larger documents.
Expand Down Expand Up @@ -1393,8 +1393,9 @@ without `--tests`, the tool will report an error asking you to specify test resu

**Q: Can I customize the markdown format of reports?**

A: Currently, ReqStream uses a fixed markdown format. You can control the header depth with `--report-depth` and
`--matrix-depth`, but other formatting is not customizable.
A: Currently, ReqStream uses a fixed markdown format. You can control the header depth with `--depth`
(applies to all reports) or `--report-depth`, `--matrix-depth`, and `--justifications-depth` to override
individual reports, but other formatting is not customizable.

**Q: Can I export to formats other than markdown?**

Expand Down
43 changes: 34 additions & 9 deletions src/DemaConsulting.ReqStream/Cli/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@
/// </summary>
public List<string> TestFiles { get; private init; } = [];

/// <summary>
/// Gets the default markdown header depth for all reports.
/// </summary>
public int Depth { get; private init; } = 1;

/// <summary>
/// Gets the requirements report output file path.
/// </summary>
Expand Down Expand Up @@ -137,7 +142,7 @@
/// <param name="args">Command-line arguments.</param>
/// <returns>A new Context instance.</returns>
/// <exception cref="ArgumentException">Thrown when arguments are invalid.</exception>
public static Context Create(string[] args)

Check warning on line 145 in src/DemaConsulting.ReqStream/Cli/Context.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Refactor this method to reduce its Cognitive Complexity from 61 to the 15 allowed.

Check warning on line 145 in src/DemaConsulting.ReqStream/Cli/Context.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Refactor this method to reduce its Cognitive Complexity from 61 to the 15 allowed.

Check warning on line 145 in src/DemaConsulting.ReqStream/Cli/Context.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Refactor this method to reduce its Cognitive Complexity from 61 to the 15 allowed.
{
// Validate input
ArgumentNullException.ThrowIfNull(args);
Expand All @@ -157,13 +162,14 @@

// Initialize optional parameters
string? requirementsReport = null;
var reportDepth = 1;
int? reportDepth = null;
string? matrix = null;
var matrixDepth = 1;
int? matrixDepth = null;
string? justificationsFile = null;
var justificationsDepth = 1;
int? justificationsDepth = null;
string? logFile = null;
string? resultsFile = null;
var depth = 1;

// Parse command-line arguments
int i = 0;
Expand Down Expand Up @@ -197,6 +203,21 @@
lint = true;
break;

case "--depth":
// Ensure argument has a value
if (i >= args.Length)
{
throw new ArgumentException($"{arg} requires a depth argument", nameof(args));
}

// Parse and validate depth value
if (!int.TryParse(args[i++], out depth) || depth < 1)
{
throw new ArgumentException($"{arg} requires a positive integer", nameof(args));
}

break;

case "--result":
case "--results":
// Ensure argument has a value
Expand Down Expand Up @@ -277,11 +298,12 @@
}

// Parse and validate depth value
if (!int.TryParse(args[i++], out reportDepth) || reportDepth < 1)
if (!int.TryParse(args[i++], out var parsedReportDepth) || parsedReportDepth < 1)
{
throw new ArgumentException($"{arg} requires a positive integer", nameof(args));
}

reportDepth = parsedReportDepth;
break;

case "--matrix":
Expand All @@ -302,11 +324,12 @@
}

// Parse and validate depth value
if (!int.TryParse(args[i++], out matrixDepth) || matrixDepth < 1)
if (!int.TryParse(args[i++], out var parsedMatrixDepth) || parsedMatrixDepth < 1)
{
throw new ArgumentException($"{arg} requires a positive integer", nameof(args));
}

matrixDepth = parsedMatrixDepth;
break;

case "--justifications":
Expand All @@ -327,11 +350,12 @@
}

// Parse and validate depth value
if (!int.TryParse(args[i++], out justificationsDepth) || justificationsDepth < 1)
if (!int.TryParse(args[i++], out var parsedJustificationsDepth) || parsedJustificationsDepth < 1)
{
throw new ArgumentException($"{arg} requires a positive integer", nameof(args));
}

justificationsDepth = parsedJustificationsDepth;
break;

default:
Expand All @@ -353,11 +377,12 @@
RequirementsFiles = requirementsFiles,
TestFiles = testFiles,
RequirementsReport = requirementsReport,
ReportDepth = reportDepth,
Depth = depth,
ReportDepth = reportDepth ?? depth,
Matrix = matrix,
MatrixDepth = matrixDepth,
MatrixDepth = matrixDepth ?? depth,
JustificationsFile = justificationsFile,
JustificationsDepth = justificationsDepth
JustificationsDepth = justificationsDepth ?? depth
};

// Open log file if specified
Expand Down
7 changes: 4 additions & 3 deletions src/DemaConsulting.ReqStream/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,16 +162,17 @@ private static void PrintHelp(Context context)
context.WriteLine(" --results <file> Write validation results to file (TRX or JUnit format)");
context.WriteLine(" --lint Lint requirements files for issues");
context.WriteLine(" --log <file> Write output to log file");
context.WriteLine(" --depth <depth> Default markdown header depth for all reports (default: 1)");
context.WriteLine(" --requirements <pattern> Requirements files glob pattern");
context.WriteLine(" --report <file> Export requirements to markdown file");
context.WriteLine(" --report-depth <depth> Markdown header depth for requirements report (default: 1)");
context.WriteLine(" --report-depth <depth> Markdown header depth for requirements report (overrides --depth)");
context.WriteLine(" --filter <tags> Filter requirements by comma-separated tags");
context.WriteLine(" --justifications <file> Export justifications to markdown file");
context.WriteLine(" --justifications-depth <depth>");
context.WriteLine(" Markdown header depth for justifications (default: 1)");
context.WriteLine(" Markdown header depth for justifications (overrides --depth)");
context.WriteLine(" --tests <pattern> Test result files glob pattern (TRX or JUnit)");
context.WriteLine(" --matrix <file> Export trace matrix to markdown file");
context.WriteLine(" --matrix-depth <depth> Markdown header depth for trace matrix (default: 1)");
context.WriteLine(" --matrix-depth <depth> Markdown header depth for trace matrix (overrides --depth)");
context.WriteLine(" --enforce Fail if requirements are not fully tested");
}

Expand Down
2 changes: 1 addition & 1 deletion src/DemaConsulting.ReqStream/SelfTest/Validation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
/// <param name="context">The context for output.</param>
private static void PrintValidationHeader(Context context)
{
context.WriteLine("# DEMA Consulting ReqStream");
context.WriteLine($"{new string('#', context.Depth)} DEMA Consulting ReqStream");
context.WriteLine("");
context.WriteLine("| Information | Value |");
context.WriteLine("| :------------------ | :------------------------------------------------- |");
Expand Down Expand Up @@ -130,7 +130,7 @@
{
// Run the program with requirements file (using relative pattern)
int exitCode;
using (var testContext = Context.Create(["--silent", "--log", "requirements-test.log", "--requirements", "*.yaml"]))

Check warning on line 133 in src/DemaConsulting.ReqStream/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal '--requirements' 9 times.

Check warning on line 133 in src/DemaConsulting.ReqStream/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal '--log' 6 times.

Check warning on line 133 in src/DemaConsulting.ReqStream/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build windows-latest

Define a constant instead of using this literal '--silent' 8 times.

Check warning on line 133 in src/DemaConsulting.ReqStream/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal '--requirements' 9 times.

Check warning on line 133 in src/DemaConsulting.ReqStream/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal '--log' 6 times.

Check warning on line 133 in src/DemaConsulting.ReqStream/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build ubuntu-latest

Define a constant instead of using this literal '--silent' 8 times.

Check warning on line 133 in src/DemaConsulting.ReqStream/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal '--requirements' 9 times.

Check warning on line 133 in src/DemaConsulting.ReqStream/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal '--log' 6 times.

Check warning on line 133 in src/DemaConsulting.ReqStream/SelfTest/Validation.cs

View workflow job for this annotation

GitHub Actions / Build / Build macos-latest

Define a constant instead of using this literal '--silent' 8 times.
{
Program.Run(testContext);
exitCode = testContext.ExitCode;
Expand Down
54 changes: 54 additions & 0 deletions test/DemaConsulting.ReqStream.Tests/Cli/ContextTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public void Context_Create_NoArguments_ReturnsDefaultContext()
Assert.IsNull(context.ResultsFile);
Assert.IsFalse(context.Enforce);
Assert.IsNull(context.RequirementsReport);
Assert.AreEqual(1, context.Depth);
Assert.AreEqual(1, context.ReportDepth);
Assert.IsNull(context.Matrix);
Assert.AreEqual(1, context.MatrixDepth);
Expand Down Expand Up @@ -745,4 +746,57 @@ public void Context_Create_InvalidJustificationsDepth_ThrowsException()
var ex2 = Assert.ThrowsExactly<ArgumentException>(() => Context.Create(["--justifications-depth", "0"]));
Assert.Contains("--justifications-depth requires a positive integer", ex2.Message);
}

/// <summary>
/// Test creating a context with depth flag sets all report depths.
/// </summary>
[TestMethod]
public void Context_Create_Depth_SetsAllDepths()
{
using var context = Context.Create(["--depth", "2"]);

Assert.AreEqual(2, context.Depth);
Assert.AreEqual(2, context.ReportDepth);
Assert.AreEqual(2, context.MatrixDepth);
Assert.AreEqual(2, context.JustificationsDepth);
Assert.AreEqual(0, context.ExitCode);
}

/// <summary>
/// Test that specific depth flags override the default depth.
/// </summary>
[TestMethod]
public void Context_Create_SpecificDepthOverridesDefaultDepth()
{
using var context = Context.Create(["--depth", "2", "--report-depth", "3"]);

Assert.AreEqual(2, context.Depth);
Assert.AreEqual(3, context.ReportDepth);
Assert.AreEqual(2, context.MatrixDepth);
Assert.AreEqual(2, context.JustificationsDepth);
Assert.AreEqual(0, context.ExitCode);
}

/// <summary>
/// Test creating a context with missing depth argument.
/// </summary>
[TestMethod]
public void Context_Create_MissingDepth_ThrowsException()
{
var ex = Assert.ThrowsExactly<ArgumentException>(() => Context.Create(["--depth"]));
Assert.Contains("--depth requires a depth argument", ex.Message);
}

/// <summary>
/// Test creating a context with invalid depth.
/// </summary>
[TestMethod]
public void Context_Create_InvalidDepth_ThrowsException()
{
var ex1 = Assert.ThrowsExactly<ArgumentException>(() => Context.Create(["--depth", "invalid"]));
Assert.Contains("--depth requires a positive integer", ex1.Message);

var ex2 = Assert.ThrowsExactly<ArgumentException>(() => Context.Create(["--depth", "0"]));
Assert.Contains("--depth requires a positive integer", ex2.Message);
}
}
Loading