directive

package
v0.1.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

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

View Source
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.

View Source
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

View Source
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.

View Source
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

func Fold(lines []string) (joined string, consumed int)

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

func Format(prefix string, d Directive) (string, error)

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(prefix string, d Directive) string

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

func Parse(prefix, text string) (Directive, error)

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(prefix, target string) (Directive, error)

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

func (d Directive) Get(key string) (string, bool)

Get returns the value for key and whether it was present. A present key with an empty value (`empty=`) reports ok=true, value "".

func (Directive) Has

func (d Directive) Has(key string) bool

Has reports whether key is present, regardless of value.

func (Directive) List

func (d Directive) List(key string) []string

List splits a comma-separated value into trimmed, non-empty items. Lists are carried inside one value (`tags=a,b,c`) because the grammar has no list syntax of its own (§7). A missing key yields nil.

type ParseError

type ParseError struct {
	Column int
	Err    error
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL