On this page
The structured-marker parser: the one-liner, block-open, attribute, separator, body and block-close markers that embed generated content in Markdown.
#internal/directives
#internal/directives
Package directives is selfdoc's structured-marker parser.
Directive syntax:
One-liner: > [selfdoc: unknown directive 'name' for go extractor] ... Block open: :<: name [key="value" ...] Attr line: :@: key="value" Body sep: :=: Body line: ::: content Block close: :>:
Directives inside fenced code blocks (``` or ~~~) are ignored. An unclosed block directive at EOF is a [DirectiveError].
#Offsets are bytes, columns are characters
Every index this package computes internally is a byte offset, because that is what Go's regexp reports. The one index it publishes -- [Directive.Column] -- is a CHARACTER offset, so a rendered "file:line:column" reads the same as the Python surface this package replaces. [BlankBacktickSpans] is the exception that proves the rule: it preserves byte length, so a caller that matched against its output must convert the byte offset it found into a character column against the ORIGINAL line, never against the blanked one.
#DirectiveError
type DirectiveError structDirectiveError reports a malformed directive: an unknown or ungrammatical name, an unexpected line inside a block, or a block still open at EOF.
#InlineOutputError
type InlineOutputError structInlineOutputError reports an inline directive whose resolver returned more than one line. An inline directive is substituted into the middle of a line of prose, so multi-line output has nowhere to go.
#Directive
type Directive structDirective is one parsed directive block.
Body is never nil: a directive with no body carries an empty slice, matching the Python dataclass's default_factory=list, so a resolver may index it without a nil check. Column is set only for an inline directive, and is a CHARACTER offset into its line.
#NameSet
type NameSet = map[string]struct{}NameSet is a set of directive names. A nil NameSet means "accept any name"; an empty non-nil one accepts nothing.
#Resolver
type Resolver func(name string, attrs map[string]string, body []string) (string, error)Resolver renders one directive occurrence into markdown.
It receives the directive's name, its parsed attributes and its body lines, and returns the text that replaces the directive. An error aborts the whole resolution -- this is the Python resolver callable's raise.
#Marker
type Marker structMarker is one directive marker found by [FindDirectiveMarkers]: the 1-based line it stands on and the marker itself.
#BacktickSpan
type BacktickSpan structBacktickSpan is one CommonMark code span located in a line.
Start and End bound the whole span including both delimiters; ContentStart and ContentEnd bound the text between them. All four are byte offsets into the string the span was found in. Fence is the number of backticks in each delimiter.
#ValidateDirectiveNames
func ValidateDirectiveNames(names []string) errorValidateDirectiveNames checks that each name matches the directive-name grammar, [a-zA-Z][\w-]*, and returns a [DirectiveError] naming the first one that does not.
#ParseDirectives
func ParseDirectives(content string, validNames NameSet) ([]Directive, error)ParseDirectives extracts every directive from markdown content, in document order.
Both standalone directives (pass 1) and inline ones (pass 2) are returned; directives inside fenced code blocks and backtick code spans are ignored. A directive opened and never closed, or a name validNames does not carry, is a [DirectiveError].
#FindInlineDirectives
func FindInlineDirectives(line string, lineNum int, validNames NameSet) ([]Directive, error)FindInlineDirectives finds the inline > [selfdoc: unknown directive 'directives' for go extractor] in one line, skipping backtick code spans.
Each returned directive has Inline true and Column set to the match's character offset in the original (unmasked) line.
#FindDirectiveMarkers
func FindDirectiveMarkers(content string) []MarkerFindDirectiveMarkers finds every directive marker in content, by line, in document order.
Fenced code blocks and backtick code spans are skipped, so a post that writes :-: ref as an example of the syntax carries no marker.
This is the detection counterpart of [ParseDirectives]: it reports syntax without resolving, validating names, or requiring a block to be closed. A document that declares it holds no directives is checked with this, because parsing it would fail on the very markers the check exists to report.
#ResolveDirectives
func ResolveDirectives(content string, resolver Resolver, validNames NameSet) (string, error)ResolveDirectives replaces each directive in content with the output of resolver, leaving non-directive content unchanged.
Directives inside fenced code blocks are left as they stand -- they are not directives. Resolution runs in two passes, exactly as parsing does: the block machine first, then an inline pass over the resolver's own output, so a directive that expands to text carrying an inline directive resolves that one too.
#FindBacktickSpans
func FindBacktickSpans(text string) []BacktickSpanFindBacktickSpans locates every backtick code span in text, in document order and without overlap.
This is the one definition of where a code span begins and ends, shared deliberately: the directive scanner skips what is inside a span, the spell mask refuses to read it, and the renderer marks it as code. Three readers, one scanner -- otherwise one of them decides a stretch of text is code while another decides it is prose, which is how a masked span came to be rendered as unmarked prose.
The rules are CommonMark's, and reproduce the Python surface's backreference-and-lookaround regex ((+)(?!)(.+?)(?<!)\1(?!)) including its backtracking:
- An opening delimiter is a run of N backticks taken whole, so the character after it is never a backtick. - The closing delimiter is the first later run of EXACTLY N backticks -- a run of a different length closes nothing. - The content is at least one character and carries no newline, because the Python pattern's "." never matched one. - When a run of N backticks closes nothing, the scan retries at the next position, which inside that run means retrying with a shorter opening delimiter -- the exact effect of the regex engine advancing its start position into the run.
#MaskBacktickSpans
func MaskBacktickSpans(line string) (string, []string)MaskBacktickSpans replaces every backtick code span in line with a null-byte placeholder, returning the masked line and the original text of each span in document order.
This is the masking for REWRITING a line: the placeholder is shorter than what it replaces, so every offset after a span shifts. Restore the line with [UnmaskBacktickSpans]. A rule that reports a column must use [BlankBacktickSpans] instead.
#UnmaskBacktickSpans
func UnmaskBacktickSpans(line string, placeholders []string) stringUnmaskBacktickSpans restores the placeholders [MaskBacktickSpans] wrote to the original span text.
#BlankBacktickSpans
func BlankBacktickSpans(line string) stringBlankBacktickSpans replaces every backtick code span in line with spaces, one per byte, so the result has the same length and every byte offset in it addresses the same byte in line.
This is the variant for a rule that reports a position: what is inside a code span is code, and no prose rule should read it, but blanking it must not move anything after it. A caller that wants a character column converts the byte offset it found against the ORIGINAL line.
#DirectiveError.Error
func (e *DirectiveError) Error() string { return e.Message }#InlineOutputError.Error
func (e *InlineOutputError) Error() string