MarkdownSnippets
Program
SourceMain
Task<int> Main(string[] args)
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)
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)
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)
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)
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)
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()
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)
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)
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)
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)
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
Source
Rewrites 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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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()
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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.