diff --git a/README.md b/README.md index 1668c7e..735b37a 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ Usage: versionmark [options] | `-?`, `-h`, `--help` | Display help message | | `--silent` | Suppress console output | | `--log ` | Write output to log file | +| `--depth ` | Heading depth for validation and publish mode (default: 1, 1-6) | | **Lint Mode** | | | `--lint []` | Check configuration file (default: `.versionmark.yaml`) | | **Capture Mode** | | @@ -98,7 +99,7 @@ Usage: versionmark [options] | **Publish Mode** | | | `--publish` | Enable publish mode | | `--report ` | **(Required)** Output markdown file path | -| `--report-depth ` | Heading depth for markdown output (default: 2, min: 1, max: 6) | +| `--report-depth ` | Heading depth for markdown output (default: --depth value, 1-6) | | `-- ` | Glob patterns for JSON files (default: `versionmark-*.json`) | | **Self-Validation** | | | `--validate` | Run self-validation tests | @@ -186,6 +187,12 @@ its current environment. Run with `--validate`: versionmark --validate ``` +Use `--depth` to control the heading depth of the report (default: 1): + +```bash +versionmark --validate --depth 2 +``` + Example output: ```text diff --git a/docs/design/version-mark/cli/context.md b/docs/design/version-mark/cli/context.md index d317418..eef04e8 100644 --- a/docs/design/version-mark/cli/context.md +++ b/docs/design/version-mark/cli/context.md @@ -22,7 +22,8 @@ command-line state and output routing. It is constructed via the `Create` factor | `ToolNames` | `string[]` | `[]` | Tool names after `--` separator in capture| | `Publish` | `bool` | `false` | `--publish` flag | | `ReportFile` | `string?` | `null` | `--report ` | -| `ReportDepth` | `int` | `2` | `--report-depth ` | +| `Depth` | `int` | `1` | `--depth ` (heading depth, def: 1) | +| `ReportDepth` | `int` | `Depth` | `--report-depth ` (def: `Depth`) | | `GlobPatterns`| `string[]` | `[]` | Patterns after `--` separator in publish | | `ExitCode` | `int` | `0`/`1` | 0 for success, 1 if errors reported | diff --git a/docs/reqstream/version-mark/cli/context.yaml b/docs/reqstream/version-mark/cli/context.yaml index 4d0fbab..ef5b7cd 100644 --- a/docs/reqstream/version-mark/cli/context.yaml +++ b/docs/reqstream/version-mark/cli/context.yaml @@ -27,13 +27,23 @@ sections: - Context_Create_PublishFlag_SetsPublishTrue - Context_Create_ReportParameter_SetsReportFile - Context_Create_ReportDepthParameter_SetsReportDepth - - Context_Create_NoReportDepth_DefaultsToTwo + - Context_Create_NoReportDepth_DefaultsToDepthOne - Context_Create_GlobPatternsAfterSeparator_CapturesPatterns - Context_Create_PublishWithoutReport_ParsesSuccessfully - Context_Create_NoGlobPatterns_EmptyArray - Context_Create_LintFlag_SetsLintTrue - Context_Create_LintFlag_WithFile_SetsLintFile - Context_Create_LintFlag_FollowedByFlag_DoesNotConsumeFlagAsFile + - Context_Create_DepthParameter_SetsDepth + - Context_Create_NoDepth_DefaultsToOne + - Context_Create_DepthParameter_SetsDefaultReportDepth + - Context_Create_ExplicitReportDepthOverridesDepth + - Context_Create_DepthZero_ThrowsArgumentException + - Context_Create_DepthNegative_ThrowsArgumentException + - Context_Create_DepthSeven_ThrowsArgumentException + - Context_Create_ReportDepthZero_ThrowsArgumentException + - Context_Create_ReportDepthNegative_ThrowsArgumentException + - Context_Create_ReportDepthSeven_ThrowsArgumentException - id: VersionMark-Context-WriteLine title: The Context.WriteLine method shall write output respecting the silent flag. diff --git a/docs/reqstream/version-mark/self-test/validation.yaml b/docs/reqstream/version-mark/self-test/validation.yaml index 814dbfd..89eb494 100644 --- a/docs/reqstream/version-mark/self-test/validation.yaml +++ b/docs/reqstream/version-mark/self-test/validation.yaml @@ -30,3 +30,13 @@ sections: tests: - VersionMark_LintPassesForValidConfig - VersionMark_LintReportsErrorsForInvalidConfig + + - id: VersionMark-Validation-HeaderDepth + title: >- + The Validation class shall use the context Depth for the self-validation + report heading depth. + justification: | + The --depth argument allows callers to embed the self-validation report + at the appropriate heading level within a larger markdown document. + tests: + - SelfTest_Run_WithDepthTwo_WritesHashHashHeader diff --git a/docs/user_guide/guide.md b/docs/user_guide/guide.md index 7f5139f..d23c66a 100644 --- a/docs/user_guide/guide.md +++ b/docs/user_guide/guide.md @@ -95,6 +95,7 @@ This generates a markdown file consolidating versions from all jobs. | `-?`, `-h`, `--help` | Display help message | | `--silent` | Suppress console output | | `--log ` | Write output to log file | +| `--depth ` | Heading depth for validation and publish mode (default: 1, 1-6) | | **Lint Mode** | | | `--lint []` | Check configuration file (default: `.versionmark.yaml`) | | **Capture Mode** | | @@ -105,7 +106,7 @@ This generates a markdown file consolidating versions from all jobs. | **Publish Mode** | | | `--publish` | Enable publish mode | | `--report ` | **(Required)** Output markdown file path | -| `--report-depth ` | Heading depth for markdown output (default: 2, min: 1, max: 6) | +| `--report-depth ` | Heading depth for markdown output (default: --depth value, 1-6) | | `-- ` | Glob patterns for JSON files (default: `versionmark-*.json`) | | **Self-Validation** | | | `--validate` | Run self-validation tests | @@ -203,9 +204,12 @@ versionmark --publish --report versions.md # Use custom glob patterns versionmark --publish --report docs/tool-versions.md -- captured/*.json build/*.json -# Control heading depth (default is ##, depth 2) +# Control heading depth (default is #, depth 1) versionmark --publish --report versions.md --report-depth 3 +# Use --depth to set both self-validation heading and report-depth default +versionmark --publish --report versions.md --depth 3 + # Combine options versionmark --publish --report docs/versions.md --report-depth 1 -- versionmark-*.json ``` @@ -395,6 +399,13 @@ Use `--silent` to suppress console output while still writing a results file: versionmark --silent --validate --results results.trx ``` +Use `--depth` to embed the self-validation report at a specific heading level within a larger +markdown document: + +```bash +versionmark --validate --depth 2 +``` + ## Validation Report Example output: diff --git a/src/DemaConsulting.VersionMark/Cli/Context.cs b/src/DemaConsulting.VersionMark/Cli/Context.cs index 224b809..3e42baf 100644 --- a/src/DemaConsulting.VersionMark/Cli/Context.cs +++ b/src/DemaConsulting.VersionMark/Cli/Context.cs @@ -100,10 +100,15 @@ internal sealed class Context : IDisposable /// public string? ReportFile { get; private init; } + /// + /// Gets the markdown header depth for the self-validation report and default for publish mode. + /// + public int Depth { get; private init; } = 1; + /// /// Gets the report depth for markdown heading levels in publish mode. /// - public int ReportDepth { get; private init; } = 2; + public int ReportDepth { get; private init; } = 1; /// /// Gets the list of glob patterns for JSON files in publish mode. @@ -149,6 +154,7 @@ public static Context Create(string[] args) ToolNames = parser.ToolNames, Publish = parser.Publish, ReportFile = parser.ReportFile, + Depth = parser.Depth, ReportDepth = parser.ReportDepth, GlobPatterns = parser.GlobPatterns }; @@ -256,10 +262,20 @@ private sealed class ArgumentParser /// public string? ReportFile { get; private set; } + /// + /// Gets the markdown header depth for the self-validation report and default for publish mode. + /// + public int Depth { get; private set; } = 1; + + /// + /// Backing field for the explicitly-specified report depth. + /// + private int? _explicitReportDepth; + /// /// Gets the report depth for markdown heading levels in publish mode. /// - public int ReportDepth { get; private set; } = 2; + public int ReportDepth => _explicitReportDepth ?? Depth; /// /// Gets the list of glob patterns for JSON files in publish mode. @@ -371,10 +387,19 @@ private int ParseArgument(string arg, string[] args, int index) return index + 1; case "--report-depth": - ReportDepth = GetRequiredIntArgument(arg, args, index, "a depth value"); - if (ReportDepth < 1) + _explicitReportDepth = GetRequiredIntArgument(arg, args, index, "a depth value"); + if (_explicitReportDepth is < 1 or > 6) + { + throw new ArgumentException($"{arg} requires an integer value between 1 and 6", nameof(args)); + } + + return index + 1; + + case "--depth": + Depth = GetRequiredIntArgument(arg, args, index, "a depth value"); + if (Depth is < 1 or > 6) { - throw new ArgumentException($"{arg} requires a positive integer value (minimum 1)", nameof(args)); + throw new ArgumentException($"{arg} requires an integer value between 1 and 6", nameof(args)); } return index + 1; diff --git a/src/DemaConsulting.VersionMark/Program.cs b/src/DemaConsulting.VersionMark/Program.cs index dc07b13..2c47d9d 100644 --- a/src/DemaConsulting.VersionMark/Program.cs +++ b/src/DemaConsulting.VersionMark/Program.cs @@ -174,6 +174,7 @@ private static void PrintHelp(Context context) context.WriteLine(" -v, --version Display version information"); context.WriteLine(" -?, -h, --help Display this help message"); context.WriteLine(" --silent Suppress console output"); + context.WriteLine(" --depth Heading depth for self-validation and --report-depth default (default: 1, range: 1-6)"); context.WriteLine(" --validate Run self-validation"); context.WriteLine(" --results Write validation results to file (.trx or .xml)"); context.WriteLine(" --log Write output to log file"); @@ -191,7 +192,7 @@ private static void PrintHelp(Context context) context.WriteLine("Publish Mode:"); context.WriteLine(" --publish Generate markdown report from JSON files"); context.WriteLine(" --report Output markdown file (required)"); - context.WriteLine(" --report-depth Heading depth for markdown (default: 2)"); + context.WriteLine(" --report-depth Heading depth for markdown (default: --depth value, range: 1-6)"); context.WriteLine(" -- Glob patterns for JSON files (default: versionmark-*.json)"); } diff --git a/src/DemaConsulting.VersionMark/SelfTest/Validation.cs b/src/DemaConsulting.VersionMark/SelfTest/Validation.cs index ab9d753..5fd63b4 100644 --- a/src/DemaConsulting.VersionMark/SelfTest/Validation.cs +++ b/src/DemaConsulting.VersionMark/SelfTest/Validation.cs @@ -84,7 +84,8 @@ public static void Run(Context context) /// The context for output. private static void PrintValidationHeader(Context context) { - context.WriteLine("# DEMA Consulting VersionMark"); + var headingPrefix = new string('#', context.Depth); + context.WriteLine($"{headingPrefix} DEMA Consulting VersionMark"); context.WriteLine(""); context.WriteLine("| Information | Value |"); context.WriteLine("| :------------------ | :------------------------------------------------- |"); diff --git a/test/DemaConsulting.VersionMark.Tests/Cli/ContextTests.cs b/test/DemaConsulting.VersionMark.Tests/Cli/ContextTests.cs index dc6d979..4de2337 100644 --- a/test/DemaConsulting.VersionMark.Tests/Cli/ContextTests.cs +++ b/test/DemaConsulting.VersionMark.Tests/Cli/ContextTests.cs @@ -468,17 +468,17 @@ public void Context_Create_ReportDepthParameter_SetsReportDepth() /// /// Test creating a context with default report-depth. /// What is tested: Default report-depth value when not specified - /// What the assertions prove: The default report depth is 2 + /// What the assertions prove: The default report depth is 1 (matching the --depth default) /// [TestMethod] - public void Context_Create_NoReportDepth_DefaultsToTwo() + public void Context_Create_NoReportDepth_DefaultsToDepthOne() { // Arrange & Act - Create context without --report-depth using var context = Context.Create(["--publish", "--report", "output.md"]); - // Assert - Verify default report depth is 2 - // What is proved: Report depth defaults to 2 when not specified - Assert.AreEqual(2, context.ReportDepth); + // Assert - Verify default report depth is 1 (the default --depth value) + // What is proved: Report depth defaults to the --depth value (1) when not specified + Assert.AreEqual(1, context.ReportDepth); } /// @@ -507,6 +507,125 @@ public void Context_Create_ReportDepthNegative_ThrowsArgumentException() Context.Create(["--publish", "--report", "output.md", "--report-depth", "-1"])); } + /// + /// Test that --report-depth 7 throws ArgumentException. + /// What is tested: Validation rejects a depth value greater than 6 + /// What the assertions prove: ArgumentException is thrown for report-depth values greater than 6 + /// + [TestMethod] + public void Context_Create_ReportDepthSeven_ThrowsArgumentException() + { + // Arrange & Act & Assert - 7 exceeds the maximum Markdown heading level of 6 + Assert.ThrowsExactly(() => + Context.Create(["--publish", "--report", "output.md", "--report-depth", "7"])); + } + + /// + /// Test creating a context with the depth parameter. + /// What is tested: --depth parameter parsing captures the depth value + /// What the assertions prove: The depth is correctly parsed as an integer + /// + [TestMethod] + public void Context_Create_DepthParameter_SetsDepth() + { + // Arrange & Act - Create context with --depth flag + using var context = Context.Create(["--depth", "3"]); + + // Assert - Verify depth is captured + // What is proved: --depth parameter value is correctly parsed as an integer + Assert.AreEqual(3, context.Depth); + Assert.AreEqual(0, context.ExitCode); + } + + /// + /// Test creating a context with no depth parameter. + /// What is tested: Default depth value when not specified + /// What the assertions prove: The depth defaults to 1 + /// + [TestMethod] + public void Context_Create_NoDepth_DefaultsToOne() + { + // Arrange & Act - Create context without --depth + using var context = Context.Create([]); + + // Assert - Verify default depth is 1 + // What is proved: Depth defaults to 1 when not specified + Assert.AreEqual(1, context.Depth); + } + + /// + /// Test that --depth sets the default for --report-depth. + /// What is tested: --depth value is used as default for ReportDepth when --report-depth not specified + /// What the assertions prove: ReportDepth equals Depth when --report-depth is not given + /// + [TestMethod] + public void Context_Create_DepthParameter_SetsDefaultReportDepth() + { + // Arrange & Act - Create context with --depth but no --report-depth + using var context = Context.Create(["--publish", "--report", "output.md", "--depth", "3"]); + + // Assert - Verify report depth defaults to the depth value + // What is proved: --depth value is used as the default for ReportDepth + Assert.AreEqual(3, context.ReportDepth); + Assert.AreEqual(0, context.ExitCode); + } + + /// + /// Test that explicit --report-depth overrides --depth. + /// What is tested: --report-depth takes precedence over --depth for ReportDepth + /// What the assertions prove: ReportDepth equals the explicit --report-depth value, not --depth + /// + [TestMethod] + public void Context_Create_ExplicitReportDepthOverridesDepth() + { + // Arrange & Act - Create context with both --depth and --report-depth + using var context = Context.Create(["--publish", "--report", "output.md", "--depth", "2", "--report-depth", "4"]); + + // Assert - Verify explicit --report-depth takes precedence over --depth + // What is proved: --report-depth value (4) overrides --depth value (2) for ReportDepth + Assert.AreEqual(4, context.ReportDepth); + Assert.AreEqual(0, context.ExitCode); + } + + /// + /// Test that --depth 0 throws ArgumentException. + /// What is tested: Validation rejects a depth value less than 1 + /// What the assertions prove: ArgumentException is thrown for depth values less than 1 + /// + [TestMethod] + public void Context_Create_DepthZero_ThrowsArgumentException() + { + // Arrange & Act & Assert - Zero is not a valid heading depth + Assert.ThrowsExactly(() => + Context.Create(["--depth", "0"])); + } + + /// + /// Test that a negative --depth throws ArgumentException. + /// What is tested: Validation rejects negative depth values + /// What the assertions prove: ArgumentException is thrown for negative depth values + /// + [TestMethod] + public void Context_Create_DepthNegative_ThrowsArgumentException() + { + // Arrange & Act & Assert - Negative values are not valid heading depths + Assert.ThrowsExactly(() => + Context.Create(["--depth", "-1"])); + } + + /// + /// Test that --depth 7 throws ArgumentException. + /// What is tested: Validation rejects a depth value greater than 6 + /// What the assertions prove: ArgumentException is thrown for depth values greater than 6 + /// + [TestMethod] + public void Context_Create_DepthSeven_ThrowsArgumentException() + { + // Arrange & Act & Assert - 7 exceeds the maximum Markdown heading level of 6 + Assert.ThrowsExactly(() => + Context.Create(["--depth", "7"])); + } + /// /// Test creating a context with glob patterns after -- separator. /// What is tested: Glob patterns after -- are captured in GlobPatterns array diff --git a/test/DemaConsulting.VersionMark.Tests/IntegrationTests.cs b/test/DemaConsulting.VersionMark.Tests/IntegrationTests.cs index 90cd418..a399bb7 100644 --- a/test/DemaConsulting.VersionMark.Tests/IntegrationTests.cs +++ b/test/DemaConsulting.VersionMark.Tests/IntegrationTests.cs @@ -527,7 +527,7 @@ public void VersionMark_PublishCommand_GeneratesMarkdownReport() var reportContent = File.ReadAllText(reportFile); // Verify markdown structure - Assert.Contains("## Tool Versions", reportContent); + Assert.Contains("# Tool Versions", reportContent); // Verify tools are present and sorted alphabetically Assert.Contains("dotnet", reportContent); diff --git a/test/DemaConsulting.VersionMark.Tests/ProgramTests.cs b/test/DemaConsulting.VersionMark.Tests/ProgramTests.cs index 2c64a16..2aaa7c9 100644 --- a/test/DemaConsulting.VersionMark.Tests/ProgramTests.cs +++ b/test/DemaConsulting.VersionMark.Tests/ProgramTests.cs @@ -579,7 +579,7 @@ public void Program_Run_WithPublishCommand_GeneratesMarkdownReport() Assert.IsTrue(File.Exists(reportFile), "Report file was not created"); var reportContent = File.ReadAllText(reportFile); - Assert.Contains("## Tool Versions", reportContent); + Assert.Contains("# Tool Versions", reportContent); Assert.Contains("dotnet", reportContent); Assert.Contains("node", reportContent); Assert.Contains("8.0.0", reportContent); diff --git a/test/DemaConsulting.VersionMark.Tests/SelfTest/SelfTestTests.cs b/test/DemaConsulting.VersionMark.Tests/SelfTest/SelfTestTests.cs index 74484a0..5684e40 100644 --- a/test/DemaConsulting.VersionMark.Tests/SelfTest/SelfTestTests.cs +++ b/test/DemaConsulting.VersionMark.Tests/SelfTest/SelfTestTests.cs @@ -92,7 +92,7 @@ public void SelfTest_Run_WithResultsFlag_WritesResultsFile() using var context = Context.Create(["--validate", "--silent", "--results", resultsFile]); // Act - Run self-validation with --results to write TRX output - Program.Run(context); + Validation.Run(context); // Assert - The TRX file should exist and contain XML content Assert.AreEqual(0, context.ExitCode); @@ -110,4 +110,34 @@ public void SelfTest_Run_WithResultsFlag_WritesResultsFile() } } } + + /// + /// Test that the self-validation pipeline writes a ## heading when --depth 2 is specified. + /// What is tested: The --depth argument controls the heading level in the self-validation report + /// What the assertions prove: Output contains "## DEMA Consulting VersionMark" with depth 2 + /// + [TestMethod] + public void SelfTest_Run_WithDepthTwo_WritesHashHashHeader() + { + // Arrange - Redirect console output to capture the validation report + var originalOut = Console.Out; + using var writer = new System.IO.StringWriter(); + Console.SetOut(writer); + try + { + using var context = Context.Create(["--validate", "--depth", "2"]); + + // Act - Run self-validation with --depth 2 + Validation.Run(context); + + // Assert - Output should contain the ## heading for depth 2 + var output = writer.ToString(); + Assert.IsTrue(output.Contains("## DEMA Consulting VersionMark"), + "Self-validation report should use ## heading when --depth 2 is specified"); + } + finally + { + Console.SetOut(originalOut); + } + } }