MarkdownSnippets
Program
SourceMain
Task<int> Main(string[] args)
Source
Process entry point that runs the CLI against the current working directory.
Parameters
argsCommand-line arguments passed by the host process.
Returns
The process exit code.
RunAsync
Task<int> RunAsync(string[] args, TextWriter standardOut, TextWriter standardError, string currentDirectory, CancellationToken cancellationToken = default)
Source
Runs the Markdown snippet CLI command contract.
Parameters
argsCommand-line arguments. The first argument must begenerate,verify,--help, or-h. Command options aftergenerateorverifyare parsed byMarkdownSnippetCommandOptions.Parse.standardOutDestination for help and successful command output.standardErrorDestination for validation and command failure output.currentDirectoryWorking directory used for option defaults. When--repo-rootis omitted, this becomes the repository root; when--documentis omitted, the default document isWeb/ForgeTrust.RazorWire/README.mdunder that root.cancellationTokenCancellation token for file IO.
Returns
0 for success or help, 1 for invalid input or stale snippets.
Remarks
Relative --repo-root values resolve from currentDirectory; relative --document values resolve from the resolved repository root. Unknown commands, unknown options, missing option values, invalid snippet directives, unsafe paths, and stale generated blocks are reported as 1 with actionable error text rather than escaping as unhandled exceptions.
IsHelp
bool IsHelp(string argument)
Source
Determines whether an argument requests command help.
Parameters
argumentArgument to inspect.
Returns
true for --help or -h.
MarkdownSnippetCommandOptions
SourceParse
MarkdownSnippetCommandOptions Parse(string[] args, string currentDirectory)
Source
Parses command options into a repository/document request.
Parameters
argsOnly--repo-root <path>and--document <path>are supported.currentDirectoryCurrent working directory used by omitted or relative--repo-root.
Returns
Parsed options with full repository and document paths.
Exceptions
MarkdownSnippetExceptionThrown when an option is unknown or a required option value is missing.
Remarks
The default repository root is currentDirectory. The default document is Web/ForgeTrust.RazorWire/README.md under the resolved repository root. A rooted --document is allowed at parse time but later rejected by generation/verification when it is outside the repository.
ReadRequiredValue
string ReadRequiredValue(string[] args, ref int index, string argument)
Source
Reads the required value immediately following an option name.
Parameters
argsComplete option argument array.indexCurrent option index, advanced to the value index on success.argumentOption name for diagnostics.
Returns
The raw option value.
Exceptions
MarkdownSnippetExceptionThrown when the option has no following non-blank value or the next token looks like another option.
ResolvePath
string ResolvePath(string? value, string baseDirectory, string defaultPath)
Source
Resolves a rooted, relative, or omitted path to a full path.
Parameters
valueOptional user-provided path.baseDirectoryDirectory used for relative values.defaultPathPath used whenvalueis omitted.
Returns
A canonical full path.
MarkdownSnippetRequest
SourceGetRepositoryRelativeDocumentPath
string GetRepositoryRelativeDocumentPath()
Source
Gets the document path relative to RepositoryRoot with forward slashes for stable diagnostics and CLI output.
Returns
The repository-relative document path.
Remarks
Request validation ensures the document stays under the repository root before generation or verification uses this value for user-facing messages.
MarkdownSnippetGenerator
SourceGenerateToFileAsync
Task GenerateToFileAsync(MarkdownSnippetRequest request, CancellationToken cancellationToken = default)
Source
Generates the canonical Markdown document and writes it back to disk.
Parameters
requestRepository root and Markdown document to update.cancellationTokenCancellation token for file IO.
Exceptions
MarkdownSnippetExceptionThrown when the repository/document path is invalid or snippet extraction fails.
GenerateAsync
Task<string> GenerateAsync(MarkdownSnippetRequest request, CancellationToken cancellationToken = default)
Source
Generates the canonical Markdown text for all managed snippet blocks.
Parameters
requestRepository root and Markdown document to read.cancellationTokenCancellation token for file IO.
Returns
The rewritten Markdown document with \n line endings.
Exceptions
MarkdownSnippetExceptionThrown when the document has no managed blocks, a block is malformed, a source path escapes the repository, markers are missing or duplicated, or extracted snippet content is empty.
Remarks
Generation is the mutating workflow used by maintainers after source sample changes. It normalizes document line endings and replaces only managed blocks outside existing Markdown code fences.
VerifyAsync
Task VerifyAsync(MarkdownSnippetRequest request, CancellationToken cancellationToken = default)
Source
Verifies that the checked-in Markdown already matches generated output.
Parameters
requestRepository root and Markdown document to verify.cancellationTokenCancellation token for file IO.
Exceptions
MarkdownSnippetExceptionThrown when validation fails or the checked-in Markdown is stale.
Remarks
Verification is the CI workflow. It compares canonical generated text to the current document after normalizing line endings, so CRLF checkouts do not fail solely because of platform line-ending conversion.
ValidateRequest
void ValidateRequest(MarkdownSnippetRequest request)
Source
Validates repository/document existence and document containment.
Parameters
requestRequest to validate before source files are read.
Exceptions
MarkdownSnippetExceptionThrown when the repository root is missing, the document is missing, or the document is outside the repository root.
MarkdownSnippetRewriter
SourceRewrites managed <!-- appsurface:snippet ... --> blocks in Markdown.
Remarks
A managed block must contain an opening directive, a generated fenced code block, and the exact closing directive <!-- /appsurface:snippet -->. Directives are ignored inside existing Markdown code fences, and the managed closing directive is only recognized outside the generated block's code fence. Supported attributes are id, file, marker, lang, and optional dedent. Attributes must use quoted name="value" syntax. file is repository-relative; rooted paths and .. escapes fail before source files are read. dedent defaults to true, which removes common indentation from extracted non-blank source lines.
Rewrite
string Rewrite(MarkdownSnippetRequest request, string markdown)
Source
Rewrites managed snippet blocks and returns canonical Markdown.
Parameters
requestRepository root and document path used for path resolution and diagnostics.markdownOriginal Markdown text.
Returns
Markdown with generated blocks rendered using \n line endings.
Exceptions
MarkdownSnippetExceptionThrown when no managed blocks are present, a directive is malformed, a source file is unsafe or missing, markers are invalid, or a block is not closed.
ParseBlock
MarkdownSnippetBlock ParseBlock(MarkdownSnippetRequest request, string[] lines, ref int index)
Source
Parses one managed snippet block and extracts its replacement content.
Parameters
requestRepository/document request for path resolution and diagnostics.linesNormalized Markdown lines.indexCurrent opening-line index, advanced to the managed closing line.
Returns
The parsed block with source-backed content.
Exceptions
MarkdownSnippetExceptionThrown when attributes are invalid, the source file path is unsafe, source markers are invalid, or the managed block is missing its closing directive.
ParseAttributes
Dictionary<string, string> ParseAttributes(string openingLine, string documentPath, int lineNumber)
Source
Parses and validates quoted directive attributes from an opening line.
Exceptions
MarkdownSnippetExceptionThrown for missing-->, duplicate attributes, unsupported attribute syntax, empty attribute sets, or unknown attribute names.
ValidateAttributeNames
void ValidateAttributeNames(IReadOnlyDictionary<string, string> attributes, string documentPath, int lineNumber)
Source
Ensures all directive attribute names are supported by the snippet contract.
Parameters
attributesParsed directive attributes.documentPathRepository-relative document path for diagnostics.lineNumberOne-based directive line number.
Exceptions
MarkdownSnippetExceptionThrown when any attribute name is unknown.
ReadRequiredAttribute
string ReadRequiredAttribute(IReadOnlyDictionary<string, string> attributes, string name, MarkdownSnippetRequest request, int lineNumber)
Source
Reads a required directive attribute and trims its value.
Exceptions
MarkdownSnippetExceptionThrown when the attribute is missing or blank.
ReadBooleanAttribute
bool ReadBooleanAttribute(IReadOnlyDictionary<string, string> attributes, string name, bool defaultValue, MarkdownSnippetRequest request, int lineNumber)
Source
Reads an optional boolean directive attribute.
Exceptions
MarkdownSnippetExceptionThrown when the value is not a valid boolean.
Remarks
dedent uses this helper with a default of true. Only true and false values accepted by the platform boolean parser are valid.
ValidateId
void ValidateId(string value, string description, MarkdownSnippetRequest request, int lineNumber)
Source
Validates a snippet id or source marker id.
Parameters
valueCandidate id.descriptionDiagnostic description of the id role.requestRequest used to name the document in diagnostics.lineNumberOne-based directive line number.
Exceptions
MarkdownSnippetExceptionThrown when the id uses unsupported characters.
ValidateLanguage
void ValidateLanguage(string language, MarkdownSnippetRequest request, int lineNumber)
Source
Validates the Markdown code fence language token.
Parameters
languageCandidate language token.requestRequest used to name the document in diagnostics.lineNumberOne-based directive line number.
Exceptions
MarkdownSnippetExceptionThrown when the language token contains unsupported characters.
FindClosingLine
int FindClosingLine(string[] lines, int startIndex, MarkdownSnippetRequest request, string id)
Source
Finds the managed block closing directive while ignoring text inside Markdown fences.
Parameters
linesNormalized Markdown lines.startIndexLine index immediately after the opening directive.requestRequest used to name the document in diagnostics.idSnippet id for diagnostics.
Returns
The line index containing <!-- /appsurface:snippet -->.
Exceptions
MarkdownSnippetExceptionThrown when no compatible closing directive is found.
AppendLine
void AppendLine(StringBuilder builder, string line, int index, int lineCount)
Source
Appends one original Markdown line while preserving the canonical final newline shape.
Parameters
builderDestination builder.lineLine text without its newline.indexCurrent line index.lineCountTotal line count.
MarkdownCodeFence
SourceTryParse
MarkdownCodeFence? TryParse(string trimmedLine)
Source
Parses a Markdown code fence opener or closer.
Parameters
trimmedLineLine trimmed of surrounding whitespace.
Returns
A fence when the line begins with at least three backticks or tildes; otherwise null.
IsClosedBy
bool IsClosedBy(string trimmedLine)
Source
Determines whether a trimmed line closes this fence.
Parameters
trimmedLineLine trimmed of surrounding whitespace.
Returns
true when the line uses the same fence character, has length at least as long as the opener, and contains only that fence character.
MarkdownSnippetBlock
SourceRender
string Render()
Source
Renders a managed block using an automatically sized backtick fence.
Returns
The complete managed block with \n line endings.
Remarks
The fence is one backtick longer than the longest backtick run in the content, with a minimum length of three, so nested Markdown examples stay literal inside the generated block.
MarkdownSnippetSourceExtractor
SourceExtracts source snippets between exact documentation marker lines.
Remarks
Supported marker forms are delegated to MarkdownSnippetMarker: C# line comments, Razor comments, and HTML comments. A marker pair must be unique, ordered as :start then :end, and contain at least one non-blank line. Marker-like text in strings or inline comments is ignored because markers must occupy the whole trimmed line. When dedent is enabled, common leading spaces or tabs across non-blank snippet lines are removed and surrounding blank lines are trimmed.
Extract
string Extract(string source, string markerId, string sourcePath, bool dedent)
Source
Extracts and optionally dedents the snippet for a marker id.
Parameters
sourceSource file text.markerIdMarker id without:startor:end.sourcePathRepository-relative source path for diagnostics.dedentWhether to remove common indentation from extracted lines.
Returns
Extracted snippet content with \n line endings.
Exceptions
MarkdownSnippetExceptionThrown when start/end markers are missing, duplicated, reversed, or enclose only blank content.
Dedent
string Dedent(string[] lines)
Source
Removes common indentation from snippet lines.
Parameters
linesSnippet lines between markers.
Returns
Dedented snippet text with surrounding blank lines trimmed.
Remarks
Only non-blank lines contribute to the minimum indent. Indentation counts both spaces and tabs as one character because snippets preserve source text rather than reformatting it.
CountIndent
int CountIndent(string line)
Source
Counts leading spaces and tabs on a source line.
Parameters
lineLine to inspect.
Returns
The number of leading indentation characters.
MarkdownSnippetMarker
SourceIsValidId
bool IsValidId(string value)
Source
Determines whether a snippet id is safe for marker and directive use.
Parameters
valueCandidate id.
Returns
true when the id starts with an ASCII letter or digit and then uses only letters, digits, _, -, or ..
TryParse
MarkdownSnippetMarkerKind TryParse(string line, string markerId)
Source
Parses a whole-line source snippet marker.
Parameters
lineSource line to inspect. A UTF-8 BOM on the first line is ignored.markerIdExpected marker id.
Returns
The marker kind, or MarkdownSnippetMarkerKind.None.
Remarks
Valid marker forms are exactly // docs:snippet id:start, @* docs:snippet id:start *@, and <!-- docs:snippet id:start -->, with matching :end variants. Whitespace may surround the whole line but not the inner marker text.
IsMarker
bool IsMarker(string trimmed, string markerId, string markerKind)
Source
Matches one exact marker form for a marker id and kind.
Parameters
trimmedSource line trimmed of surrounding whitespace and any leading BOM.markerIdExpected marker id.markerKindExpected marker kind, usuallystartorend.
Returns
true when the line is an exact supported comment marker.
MarkdownFence
SourceCreate
string Create(string content)
Source
Creates a Markdown backtick fence that safely contains the supplied content.
Parameters
contentSnippet content to wrap.
Returns
At least three backticks, or one more than the longest run in content.
MarkdownSnippetPath
SourceResolveRepositoryFilePath
string ResolveRepositoryFilePath(string repositoryRoot, string repositoryRelativePath, string description)
Source
Resolves a repository-relative source file path and enforces repository containment.
Parameters
repositoryRootRepository root directory.repositoryRelativePathSource path from a snippet directive.descriptionHuman-readable path description for diagnostics.
Returns
The full source file path.
Exceptions
MarkdownSnippetExceptionThrown when the path is rooted, escapes the repository root, or points to a missing file.
Remarks
Snippet file attributes must be repository-relative so generated documentation is portable across machines and CI checkouts.
TryGetRepositoryRelativePath
bool TryGetRepositoryRelativePath(string repositoryRoot, string path, out string relativePath)
Source
Converts a path to a repository-relative path when it is safely contained in the repository.
Parameters
repositoryRootRepository root directory.pathCandidate full or relative path.relativePathRepository-relative path with forward slashes when successful.
Returns
true when path is inside repositoryRoot, is not the root itself, and does not cross a reparse-point segment.
Remarks
The containment check starts with lexical path normalization, then walks the repository-relative path segments and rejects symlinks or other reparse points before file IO can follow them outside the repository.
ContainsReparsePointSegment
bool ContainsReparsePointSegment(string fullRoot, string relativePath)
Source
Determines whether any existing path segment below the repository root is a reparse point.
Parameters
fullRootCanonical repository root path.relativePathRepository-relative candidate path with forward slashes.
Returns
true when an inspected segment is a symlink or other reparse point.