diff --git a/README.md b/README.md index 89f2a30..2530a2e 100644 --- a/README.md +++ b/README.md @@ -99,14 +99,15 @@ Options: --results Write validation results to file (TRX or JUnit format) --log Write output to log file --lint Lint requirements files for structural issues + --depth Default markdown header depth for all reports (default: 1) --requirements Requirements files glob pattern --report Export requirements to markdown file - --report-depth Markdown header depth for requirements report (default: 1) + --report-depth Markdown header depth for requirements report (overrides --depth) --tests Test result files glob pattern (TRX or JUnit) --matrix Export trace matrix to markdown file - --matrix-depth Markdown header depth for trace matrix (default: 1) + --matrix-depth Markdown header depth for trace matrix (overrides --depth) --justifications Export requirement justifications to markdown file - --justifications-depth Markdown header depth for justifications (default: 1) + --justifications-depth Markdown header depth for justifications (overrides --depth) --filter Comma-separated list of tags to filter requirements --enforce Fail if requirements are not fully tested ``` @@ -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 diff --git a/docs/design/reqstream/cli/context.md b/docs/design/reqstream/cli/context.md index d1a1470..4d710a9 100644 --- a/docs/design/reqstream/cli/context.md +++ b/docs/design/reqstream/cli/context.md @@ -32,11 +32,12 @@ when the enclosing `using` block in `Program.Main` exits. | `RequirementsFiles` | `List` | `--requirements` | Expanded list of requirement file paths | | `TestFiles` | `List` | `--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 @@ -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 diff --git a/docs/reqstream/reqstream/cli/cli.yaml b/docs/reqstream/reqstream/cli/cli.yaml index edf0c48..9e6213a 100644 --- a/docs/reqstream/reqstream/cli/cli.yaml +++ b/docs/reqstream/reqstream/cli/cli.yaml @@ -29,6 +29,7 @@ sections: - ReqStream-Requirements-TestGlobPatterns - ReqStream-Command-ReportDepth - ReqStream-Command-MatrixDepth + - ReqStream-Command-Depth - ReqStream-Command-TagFilter - id: ReqStream-Cli-Output diff --git a/docs/reqstream/reqstream/cli/context.yaml b/docs/reqstream/reqstream/cli/context.yaml index 5cac9bd..ef7b329 100644 --- a/docs/reqstream/reqstream/cli/context.yaml +++ b/docs/reqstream/reqstream/cli/context.yaml @@ -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: | diff --git a/docs/user_guide/introduction.md b/docs/user_guide/introduction.md index 24e9453..973c2f0 100644 --- a/docs/user_guide/introduction.md +++ b/docs/user_guide/introduction.md @@ -530,15 +530,16 @@ ReqStream supports the following command-line options: | `--results ` | Write validation test results to a file (TRX or JUnit format, use .trx or .xml extension) | | `--lint` | Lint requirements files for structural issues | | `--log ` | Write output to specified log file | +| `--depth ` | Default starting header depth for all reports (default: 1) | | `--requirements ` | Glob pattern for requirements YAML files | | `--report ` | Export requirements to markdown file | -| `--report-depth ` | Starting header depth for requirements report (default: 1) | +| `--report-depth ` | Starting header depth for requirements report (overrides `--depth`) | | `--filter ` | Comma-separated list of tags to filter requirements by | | `--tests ` | Glob pattern for test result files (TRX or JUnit format) | | `--matrix ` | Export trace matrix to markdown file | -| `--matrix-depth ` | Starting header depth for trace matrix (default: 1) | +| `--matrix-depth ` | Starting header depth for trace matrix (overrides `--depth`) | | `--justifications ` | Export justifications to markdown file | -| `--justifications-depth ` | Starting header depth for justifications (default: 1) | +| `--justifications-depth ` | Starting header depth for justifications (overrides `--depth`) | | `--enforce` | Fail if requirements are not fully tested | ## Examples @@ -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:** @@ -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. @@ -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?** diff --git a/src/DemaConsulting.ReqStream/Cli/Context.cs b/src/DemaConsulting.ReqStream/Cli/Context.cs index 7e1d6ef..a71d87e 100644 --- a/src/DemaConsulting.ReqStream/Cli/Context.cs +++ b/src/DemaConsulting.ReqStream/Cli/Context.cs @@ -89,6 +89,11 @@ public sealed class Context : IDisposable /// public List TestFiles { get; private init; } = []; + /// + /// Gets the default markdown header depth for all reports. + /// + public int Depth { get; private init; } = 1; + /// /// Gets the requirements report output file path. /// @@ -157,13 +162,14 @@ public static Context Create(string[] args) // 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; @@ -197,6 +203,21 @@ public static Context Create(string[] args) 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 @@ -277,11 +298,12 @@ public static Context Create(string[] args) } // 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": @@ -302,11 +324,12 @@ public static Context Create(string[] args) } // 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": @@ -327,11 +350,12 @@ public static Context Create(string[] args) } // 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: @@ -353,11 +377,12 @@ public static Context Create(string[] args) 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 diff --git a/src/DemaConsulting.ReqStream/Program.cs b/src/DemaConsulting.ReqStream/Program.cs index c1f5331..fbe8374 100644 --- a/src/DemaConsulting.ReqStream/Program.cs +++ b/src/DemaConsulting.ReqStream/Program.cs @@ -162,16 +162,17 @@ private static void PrintHelp(Context context) context.WriteLine(" --results Write validation results to file (TRX or JUnit format)"); context.WriteLine(" --lint Lint requirements files for issues"); context.WriteLine(" --log Write output to log file"); + context.WriteLine(" --depth Default markdown header depth for all reports (default: 1)"); context.WriteLine(" --requirements Requirements files glob pattern"); context.WriteLine(" --report Export requirements to markdown file"); - context.WriteLine(" --report-depth Markdown header depth for requirements report (default: 1)"); + context.WriteLine(" --report-depth Markdown header depth for requirements report (overrides --depth)"); context.WriteLine(" --filter Filter requirements by comma-separated tags"); context.WriteLine(" --justifications Export justifications to markdown file"); context.WriteLine(" --justifications-depth "); - context.WriteLine(" Markdown header depth for justifications (default: 1)"); + context.WriteLine(" Markdown header depth for justifications (overrides --depth)"); context.WriteLine(" --tests Test result files glob pattern (TRX or JUnit)"); context.WriteLine(" --matrix Export trace matrix to markdown file"); - context.WriteLine(" --matrix-depth Markdown header depth for trace matrix (default: 1)"); + context.WriteLine(" --matrix-depth Markdown header depth for trace matrix (overrides --depth)"); context.WriteLine(" --enforce Fail if requirements are not fully tested"); } diff --git a/src/DemaConsulting.ReqStream/SelfTest/Validation.cs b/src/DemaConsulting.ReqStream/SelfTest/Validation.cs index abf9613..1148aa8 100644 --- a/src/DemaConsulting.ReqStream/SelfTest/Validation.cs +++ b/src/DemaConsulting.ReqStream/SelfTest/Validation.cs @@ -85,7 +85,7 @@ public static void Run(Context context) /// The context for output. 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("| :------------------ | :------------------------------------------------- |"); diff --git a/test/DemaConsulting.ReqStream.Tests/Cli/ContextTests.cs b/test/DemaConsulting.ReqStream.Tests/Cli/ContextTests.cs index 12f9df7..6689249 100644 --- a/test/DemaConsulting.ReqStream.Tests/Cli/ContextTests.cs +++ b/test/DemaConsulting.ReqStream.Tests/Cli/ContextTests.cs @@ -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); @@ -745,4 +746,57 @@ public void Context_Create_InvalidJustificationsDepth_ThrowsException() var ex2 = Assert.ThrowsExactly(() => Context.Create(["--justifications-depth", "0"])); Assert.Contains("--justifications-depth requires a positive integer", ex2.Message); } + + /// + /// Test creating a context with depth flag sets all report depths. + /// + [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); + } + + /// + /// Test that specific depth flags override the default depth. + /// + [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); + } + + /// + /// Test creating a context with missing depth argument. + /// + [TestMethod] + public void Context_Create_MissingDepth_ThrowsException() + { + var ex = Assert.ThrowsExactly(() => Context.Create(["--depth"])); + Assert.Contains("--depth requires a depth argument", ex.Message); + } + + /// + /// Test creating a context with invalid depth. + /// + [TestMethod] + public void Context_Create_InvalidDepth_ThrowsException() + { + var ex1 = Assert.ThrowsExactly(() => Context.Create(["--depth", "invalid"])); + Assert.Contains("--depth requires a positive integer", ex1.Message); + + var ex2 = Assert.ThrowsExactly(() => Context.Create(["--depth", "0"])); + Assert.Contains("--depth requires a positive integer", ex2.Message); + } }