Documentation
¶
Overview ¶
Package directive parses and formats the single docsync directive shape,
ds:<verb> key=value key=value …
as it appears inside any host comment (`// ds:def id=x`, `# ds:def id=x`, `<!-- ds:block id=x -->`) and inside markdown link targets as a URL scheme (`ds:block?id=x&lines=1-6`). See docs/SPEC.md §7.
Why this package exists on its own: every other part of docsync (scanner, verbs, ledger, renderer) consumes Directive values and must never re-parse text. The grammar is deliberately tiny — no positionals, no escape character — so the whole thing fits in one file and can be fuzzed to exhaustion. The scanner searches for the prefix only; verbs are resolved by a registry elsewhere, so this package accepts any verb name that matches the identifier rule.
Invariants callers may rely on:
- Parse(Format(d)) == d for every well-formed Directive (pinned by tests).
- A repeated key is an error, never a silent overwrite.
- Unknown verbs and keys are NOT errors here; policy lives in the caller.
Index ¶
Constants ¶
const DefaultPrefix = "ds"
DefaultPrefix is the prefix the scanner searches for when a workspace has not configured another. It is glued to the verb (`ds:def`) precisely so that the English word "ds" in a comment can never start a directive.
const Separator = ":"
Separator joins prefix and verb. It is a constant rather than a literal at use sites because both the comment carrier and the link carrier depend on it.
Variables ¶
var ( // ErrNoDirective is returned when the text does not start with the prefix. // It is the common "not for us" case and callers must treat it as a skip, // not a failure. ErrNoDirective = errors.New("directive: text does not start with the prefix") // ErrBadVerb is returned when the token after the prefix is not an identifier. ErrBadVerb = errors.New("directive: verb must match [a-z][a-z0-9_]*") // ErrPositional is returned for a bare word where key=value was expected. // The grammar has no positionals on purpose (§7): one shape for every verb. ErrPositional = errors.New("directive: bare word where key=value was expected") // ErrBadKey is returned when a key is not an identifier. ErrBadKey = errors.New("directive: key must match [a-z][a-z0-9_]*") // ErrDuplicateKey is returned when the same key appears twice. Never a // silent overwrite: a duplicate is almost always a copy-paste mistake. ErrDuplicateKey = errors.New("directive: duplicate key") // ErrUnterminatedQuote is returned when a quoted value has no closing quote. ErrUnterminatedQuote = errors.New("directive: unterminated quoted value") // ErrBadLink is returned when a link target has the prefix but is malformed. ErrBadLink = errors.New("directive: malformed link target") )
Sentinel errors. Callers branch with errors.Is; messages carry positions.
var ErrUnquotable = errors.New("directive: value contains both quote kinds; use a continuation line")
ErrUnquotable is returned by Format for a value containing both quote kinds.
Functions ¶
func Fold ¶
Fold merges continuation lines into a directive's text before parsing. A continuation is a comment line directly below the directive whose body starts with whitespace followed by key=value (§7). Callers pass the comment bodies (delimiters stripped) of the directive line and every following comment line; Fold returns the joined text and the number of lines consumed (including the first). It stops at the first line that is not a continuation. Fold never parses; it only decides which lines belong together, so a malformed continuation is reported by Parse with a column into the joined text.
func Format ¶
Format renders a Directive in comment form using prefix, emitting keys in d.Keys order (falling back to sorted order for any key missing from Keys, so a hand-built Directive still formats deterministically). Values containing whitespace are double-quoted; values containing a double quote are single-quoted. A value containing both cannot be expressed on one line — that is the documented limit of the grammar — and Format returns an error rather than emitting something Parse would reject.
func FormatLink ¶
FormatLink renders a Directive as a markdown link target. Values are percent-encoded, so every value is expressible (unlike Format).
Types ¶
type Directive ¶
type Directive struct {
// Verb is the word after the prefix, e.g. "def", "block". Never empty.
Verb string
// Args holds key=value pairs. Values are unquoted; quoting is a carrier
// detail removed at parse time and re-added by Format only when needed.
Args map[string]string
// Keys is the order keys appeared in the source. Format emits in this order
// so a parse→format→parse cycle is byte-stable. Len(Keys) == len(Args).
Keys []string
}
Directive is one parsed directive. Args preserves insertion order through Keys so Format is deterministic and round-trips.
func Parse ¶
Parse parses the body of a comment: the text with the host's comment delimiters already stripped. Leading and trailing whitespace is ignored. Continuation folding (§7, "a comment line directly below starting with whitespace and key=value") is the caller's job because only the caller knows line structure; Fold does that step once the lines are collected.
The prefix must be the very first non-space token and must be glued to the verb with Separator. Text that does not start that way returns ErrNoDirective so scanners can cheaply skip ordinary comments.
func ParseLink ¶
ParseLink parses a markdown link target in URL-scheme form:
ds:block?id=sess-save-k7m2p4xq&lines=1-6
The scheme is the prefix, the path is the verb, and the query carries the same key=value pairs as the comment form (§7.1). Values are percent-decoded. A repeated key is an error exactly as in Parse. A target that does not begin with the prefix scheme returns ErrNoDirective.
func (Directive) Get ¶
Get returns the value for key and whether it was present. A present key with an empty value (`empty=`) reports ok=true, value "".
type ParseError ¶
ParseError reports where in the text a parse failed. Column is a zero-based byte offset into the text handed to Parse, so a caller that knows the line can point an editor at the exact spot.
func (*ParseError) Error ¶
func (e *ParseError) Error() string
func (*ParseError) Unwrap ¶
func (e *ParseError) Unwrap() error
Unwrap lets callers use errors.Is against the sentinels above.