extract

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: 13 Imported by: 0

Documentation

Overview

Package extract finds directives in a file and binds `ds:def` directives to the block they define (docs/SPEC.md §7.1, §10).

An Extractor owns one tier: it knows the comment syntax of its file types, how to walk them, and what a def immediately above (or beside) a line binds to. The root module ships the tiers that need no dependency: plain text, config formats, markdown and html, and a heuristic code binder. A grammar tier (tree-sitter) lives in ext/treesitter and registers ahead of the heuristic one for the languages it supports.

Every extractor returns the same Found value: bound Defs, References, and Problems (a malformed directive, a def with nothing to bind to). Nothing here touches the filesystem or knows about the ledger; the scanner feeds bytes in and merges results.

Index

Constants

View Source
const (
	VerbDef   = "def"
	VerbBlock = "block"
	VerbCfg   = "cfg"
	VerbRun   = "run"
	VerbTable = "table"
	VerbClaim = "claim"
	VerbURL   = "url"
	VerbChain = "chain"
)

Verb names the core recognises at extraction time. Only `def` changes how a file is read (it binds); every other verb is recorded as a Reference and interpreted later. The list exists so extractors and the scanner spell them identically.

View Source
const Rule = 1

Rule is the version of the extraction rules: what bytes a block's hash covers, and what prose a citation binds to. It is stamped into the ledger and refs headers and onto every ack, so a hash or a sentence is never read as comparable with one taken under different rules.

Rule 1 is the first released set: a section stops before the next block's directive carrier, a standalone carrier inside a block is left out of its hash, and a citation binds to its whole markdown paragraph, where a sentence ends only before a capital, digit, quote or bracket, never after a listed abbreviation, with whitespace collapsed and HTML comments left out. Any later change to what those bytes cover bumps this by one.

The extraction cache is keyed on it, so a bump can never be served results computed under the old rule, and each ack records it, so an ack made under another rule is never compared as if its sentence were bound the same way. Such an ack reports once (check.msgAckOtherRule) and is cleared by acking the sentence again. dsself:def id=rule-7y7umdv9 owner=@docsync stability=stable

Variables

View Source
var (
	ErrNoID          = errors.New("extract: ds:def without id=")
	ErrNothingToBind = errors.New("extract: ds:def has nothing after it to bind to")
	// ErrSkippedCode is raised when the block a def would bind does not begin
	// on the first code line below the directive, so real code sits between
	// them. It exists because the alternative is silent and worse: a grammar
	// that does not recognise the construct under the directive used to walk
	// on and bind the NEXT declaration it did recognise, and the sentence
	// citing the def was then measured against something its author never
	// looked at (bug 18). An unbindable directive must be a finding the author
	// sees, not a binding to whatever came next.
	ErrSkippedCode = errors.New("extract: ds:def cannot bind the declaration below it")
	ErrBadSpan     = errors.New("extract: span= must be +N with N >= 0")
	// ErrUnclosedComment is a block comment that opens with a directive and
	// never closes. Reported rather than skipped, since a skipped directive
	// is a def that silently does not exist.
	ErrUnclosedComment = errors.New("extract: directive comment is never closed")
	// ErrLinkDestination is a `[text](ds:…)` that markdown does not read as
	// a link, so it renders as raw text. A destination holding a space is
	// the usual cause: write it as `[text](<ds:…>)` or encode the space as
	// %20.
	ErrLinkDestination = errors.New("extract: directive link is not a markdown link; a destination with spaces must be written <ds:…> or use %20")
	ErrDefOnImage      = errors.New("extract: ds:def on an image link defines nothing; use a text link")
	ErrNoExtractor     = errors.New("extract: no extractor matches this path")
)

Sentinel problem errors.

View Source
var (
	ErrSymbolNotFound = errors.New("extract: symbol not found")
	ErrLineOutOfRange = errors.New("extract: line out of range")
)

Locate errors.

BuiltinVerbs is the canonical list from the spec (§9).

View Source
var ErrBadTarget = errors.New("extract: target must be path#Symbol, path:line or path:start-end")

ErrBadTarget is a `ds def` target that is not `path#Symbol`, `path:line` or `path:start-end`.

View Source
var NoComments = map[string]bool{
	".json": true, ".jsonl": true, ".ndjson": true, ".geojson": true, ".webmanifest": true,
	".csv": true, ".tsv": true,
	"go.sum": true,
}

NoComments are formats with no comment syntax at all, keyed like Styles. A directive can never be valid in one, so a directive line found there is damage without exception, and the only repair is to remove it.

It is a separate, explicit list rather than "anything not in Styles" because the unknown includes plain prose -- an extensionless NOTES file carries a bare directive line correctly -- and flagging those would bury the real damage. These are the formats the old fallback actually broke, plus the ones shaped like them.

Deliberately absent, because they DO take comments: yarn.lock opens with a `#` line, Cargo.lock is TOML and pnpm-lock.yaml is YAML. package-lock.json is covered by its extension.

View Source
var Styles = map[string]Style{
	".go":    {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".ts":    {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".tsx":   {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".js":    {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".jsx":   {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".mjs":   {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".cjs":   {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".mts":   {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".cts":   {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".rs":    {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".c":     {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".h":     {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".cpp":   {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".java":  {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".kt":    {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".swift": {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".cs":    {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".php":   {Line: []string{"//", "#"}, BlockOpen: "/*", BlockClose: "*/"},
	".scala": {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".dart":  {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".zig":   {Line: []string{"//"}},

	".txt":  {Bare: true, NoStrings: true},
	".text": {Bare: true, NoStrings: true},
	".pkl":  {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},

	"go.mod":        {Line: []string{"//"}},
	"go.work":       {Line: []string{"//"}},
	".css":          {BlockOpen: "/*", BlockClose: "*/"},
	".scss":         {Line: []string{"//"}, BlockOpen: "/*", BlockClose: "*/"},
	".sql":          {Line: []string{"--"}, BlockOpen: "/*", BlockClose: "*/"},
	".lua":          {Line: []string{"--"}},
	".hs":           {Line: []string{"--"}},
	".py":           {Line: []string{"#"}},
	".pyi":          {Line: []string{"#"}},
	".rb":           {Line: []string{"#"}},
	".sh":           {Line: []string{"#"}},
	".bash":         {Line: []string{"#"}},
	".zsh":          {Line: []string{"#"}},
	".pl":           {Line: []string{"#"}},
	".r":            {Line: []string{"#"}},
	".ex":           {Line: []string{"#"}},
	".exs":          {Line: []string{"#"}},
	".nix":          {Line: []string{"#"}},
	".tf":           {Line: []string{"#", "//"}, BlockOpen: "/*", BlockClose: "*/"},
	".hcl":          {Line: []string{"#", "//"}, BlockOpen: "/*", BlockClose: "*/"},
	".tfvars":       {Line: []string{"#", "//"}, BlockOpen: "/*", BlockClose: "*/"},
	".yaml":         {Line: []string{"#"}, Trailing: true},
	".yml":          {Line: []string{"#"}, Trailing: true},
	".toml":         {Line: []string{"#"}, Trailing: true},
	".ini":          {Line: []string{"#", ";"}},
	".cfg":          {Line: []string{"#", ";"}},
	".conf":         {Line: []string{"#"}},
	".env":          {Line: []string{"#"}},
	".tpl":          {Line: []string{"#"}},
	".properties":   {Line: []string{"#", "!"}},
	".editorconfig": {Line: []string{"#", ";"}},
	".gitignore":    {Line: []string{"#"}},
	".dockerignore": {Line: []string{"#"}},
	"dockerfile":    {Line: []string{"#"}},
	"makefile":      {Line: []string{"#"}},
	"taskfile.yml":  {Line: []string{"#"}, Trailing: true},
	".adoc":         {Line: []string{"//"}, NoStrings: true},
	".asciidoc":     {Line: []string{"//"}, NoStrings: true},
	".rst":          {Line: []string{".."}, NoStrings: true},
	".md":           {BlockOpen: "<!--", BlockClose: "-->", NoStrings: true},
	".markdown":     {BlockOpen: "<!--", BlockClose: "-->", NoStrings: true},
	".mdx":          {BlockOpen: "{/*", BlockClose: "*/}", AltOpen: "<!--", AltClose: "-->", NoStrings: true},
	".html":         {BlockOpen: "<!--", BlockClose: "-->", NoStrings: true},
	".htm":          {BlockOpen: "<!--", BlockClose: "-->", NoStrings: true},
	".xml":          {BlockOpen: "<!--", BlockClose: "-->", NoStrings: true},
	".svg":          {BlockOpen: "<!--", BlockClose: "-->", NoStrings: true},
	".vue":          {Line: []string{"//"}, BlockOpen: "<!--", BlockClose: "-->", AltOpen: "/*", AltClose: "*/"},
	".svelte":       {Line: []string{"//"}, BlockOpen: "<!--", BlockClose: "-->", AltOpen: "/*", AltClose: "*/"},
}

Styles keyed by the lowercase base name first, then by Ext(path), so a file whose name decides its syntax (`go.mod`, `taskfile.yml`, `dockerfile`) is matched before its extension is considered. Kept as one table so `ds doctor` can print it and tests can prove every entry parses.

A file type absent from this table has no carrier as far as docsync is concerned, and nothing will write a directive into it: see ErrNoCarrier. Adding an entry is therefore how a format becomes writable, and every entry is proved by inserting a directive and running that language's own parser over the result.

Functions

func CarrierLines

func CarrierLines(p string, lines []string) []string

CarrierLines returns the lines of a document with everything that cannot carry a directive blanked out, keeping line numbers and, within a line, byte offsets: frontmatter, fenced and indented code, and inline code spans; for html, fenced code only. Any other path comes back unchanged. It is the one view of a page every reader of its directives uses, so what check counts is what render renders and adopt rewrites: `ds adopt` reading raw lines rewrote a backticked example into a live citation, and `ds render` rendered one.

func Carriers

func Carriers(p string, lines []string, prefix string) map[int]bool

Carriers returns the lines of p holding a standalone directive carrier — every def and cite that occupies a line by itself — for HashedJoin. A path with no known comment style has none.

func Enclosing

func Enclosing(p string, src []byte, line int) (block.Block, error)

Enclosing binds the smallest declared block that contains line: in code, the nearest declaration above the line whose extent reaches it; in markdown, the section or paragraph containing it. It falls back to Locate when nothing encloses the line. `adopt` uses it so a link to a line inside a function defines the function, not the statement.

func Ext

func Ext(p string) string

Ext returns the lowercase extension of p including the dot, or the lowercase base name for extensionless files such as `Dockerfile`, so tables can key on either.

func Extensions

func Extensions() map[string][]string

Extensions returns the sorted extensions and base names a set of tiers claims; `ds doctor` prints it so a team can see which files are read by which tier without reading source.

func FirstCodeLine

func FirstCodeLine(p string, lines []string, from int) int

FirstCodeLine returns the first line of p at or after from (1-based) that is neither blank, a comment, nor a decorator, or 0 when there is none. A tier with its own parser uses it to check that the block it chose actually begins where the directive points; see ErrSkippedCode.

func HasNoComments

func HasNoComments(p string) bool

HasNoComments reports a path whose format has no comment syntax, so no directive can be written into it or be valid in it.

func HashedJoin

func HashedJoin(lines []string, start, end int, carriers map[int]bool) string

HashedJoin is lines start..end joined the way a block's hash sees them: with the standalone carriers in carriers left out. An external tier that hashes source lines must use it rather than Join, or anchoring anything nested inside one of its blocks changes that block's hash (see hashedJoin).

func IsProse

func IsProse(ex Extractor) bool

IsProse reports whether ex declared its files prose (ProseTier).

func Join

func Join(lines []string, start, end int) string

Join returns lines start..end (1-based, inclusive) joined by "\n", clamped to the slice.

func Locate

func Locate(p string, src []byte, t Target) (block.Block, error)

Locate binds a target in src with the same rules the def binder uses for that file kind, and returns the block (without an id) plus the line where a `ds:def` directive should be inserted. The insertion line is the block's first line; the caller places the directive above it in the host's comment style, or as a trailing comment for one-line config values.

Symbol lookup matches declarations in code, headings in markdown, and keys in config files. Line lookup starts at that line and binds forward exactly as a def written above it would.

func MultiLineDirective added in v0.1.3

func MultiLineDirective(lines []string, i int, st Style, head string) (text string, last int, ok bool)

MultiLineDirective reads a directive in a block comment that opens at the start of line i and closes on a later line:

<!-- ds:def id=sess-policy-h2n8wq4t
     owner=@auth -->

It returns the comment's text with the delimiters removed and its lines joined by spaces, and the 0-based line the comment closes on. ok is false when line i opens no such comment. A comment that never closes comes back ok with empty text and last at the end of the file, for the caller to report.

Why: a markdown author breaks a long directive over lines the way any HTML comment is broken, and that comment was skipped without a word -- the def did not exist and nothing said so (bug 50). Everything inside the comment is the directive, so no continuation rule is needed to decide which lines belong to it.

func NothingToBind

func NothingToBind(o Occurrence, f *Found)

NothingToBind records the problem a def raises when no block follows it.

func ParseSpan added in v0.1.3

func ParseSpan(d directive.Directive) (int, bool, error)

ParseSpan reads a directive's `span=+N`: N, whether the key is present, and ErrBadSpan when it is present but not `+N`. Every tier reads the key through it, so `span=` cannot mean "accepted and ignored" in one tier while it widens the block in another.

func Prelude

func Prelude(o Occurrence, f *Found) (id string, span int, hasSpan, ok bool)

Prelude performs the checks every def needs before binding: the id must be present, a remote def (file=) is complete as written, and span= must parse. It returns the id and span, and ok=false when the caller has nothing left to do (the def was recorded as remote, or a problem was recorded).

func Region added in v0.1.3

func Region(lines []string, from int, prefix string) *block.Region

Region is region for other packages: render replaces an existing repo-mode copy with its fresh rendering by the same rule the scanner finds it by, so the two can never disagree about where a copy ends. from is the 1-based line after the directive.

func SkippedCode

func SkippedCode(o Occurrence, f *Found, start int)

SkippedCode records the problem a def raises when the block it would bind starts below the first code line under the directive.

func SpanEnd

func SpanEnd(lines []string, start, want int, carriers map[int]bool) int

SpanEnd is the last line of a span that starts at start and holds want content lines — lines that are not standalone carriers (see spanEnd).

Types

type Code

type Code struct{}

Code is the heuristic syntax tier: it binds a `ds:def` to the declaration after it in languages with C-like braces, Python-like indentation, or SQL-like statements, without a grammar. It exists so docsync is useful on day one in any language; ext/treesitter registers ahead of it and takes over exactly for the languages it supports.

Binding rules, in order: skip comment and decorator/attribute lines; the first code line starts the block; if that line or the lines up to the first `{` open a brace block, the block ends at the matching `}`; if it ends with `:` (Python), the block is the following deeper-indented lines; if the language is SQL-like, the block ends at the first `;`; otherwise the block is the blank-bounded run of lines. The symbol is extracted by a small set of declaration patterns and is "" when none match, which is honest rather than wrong.

func (Code) Extract

func (Code) Extract(p string, src []byte, prefix string) Found

Extract implements Extractor.

func (Code) Match

func (Code) Match(p string) bool

Match implements Extractor.

func (Code) Name

func (Code) Name() string

Name implements Extractor.

type Config

type Config struct{}

Config is the structured tier for formats that need no parser dependency (§10): env, ini, properties, and line-mode yaml and toml. A `ds:def` as a trailing comment binds the key on that line; as a whole-line comment it binds the next non-blank line. The block is the key's line; the symbol is the key path (`auth.port` for nested yaml, `[server]` table prefixes for toml, `section.key` for ini); the default value pick is that key's value. `span=+N` widens the block for multi-line values.

This is line-mode on purpose: it handles the shape real config files have and never returns a wrong value silently, because a key whose value is a nested map or a block scalar produces a range, not a value, and `cfg` refuses ranges. Full parsers live in ext/structured for the rest.

func (Config) Extract

func (Config) Extract(p string, src []byte, prefix string) Found

Extract implements Extractor.

func (Config) Match

func (Config) Match(p string) bool

Match implements Extractor.

func (Config) Name

func (Config) Name() string

Name implements Extractor.

type Def

type Def struct {
	Directive directive.Directive
	Block     block.Block
	Remote    bool
}

Def is a bound definition: the directive as written plus the block it produced. Remote is set when the def carries `file=` and points elsewhere; the extractor cannot bind it (it has no access to other files) and leaves Block.Pos empty for the scanner to resolve.

func NewDef

func NewDef(o Occurrence, id string, kind block.Kind, symbol string, pos block.Position, content string) Def

NewDef builds a bound def with its hash over content.

type Document

type Document struct{}

Document is the document tier for AsciiDoc and reStructuredText (§10: "markdown mdx html asciidoc rst"). Markdown and HTML keep their own extractor because their carriers differ; these two write directives in line comments (`// ds:def` in AsciiDoc, `.. ds:def` in rst) and bind the way markdown does: a heading binds its section until the next heading of the same or higher level, anything else binds the blank-bounded paragraph, `span=+N` overrides. Cites are the free-text `ds:verb?…` form (AsciiDoc `link:ds:block?id=…[text]` contains it), bound to their sentence.

func (Document) Extract

func (Document) Extract(p string, src []byte, prefix string) Found

Extract implements Extractor.

func (Document) Match

func (Document) Match(p string) bool

Match implements Extractor.

func (Document) Name

func (Document) Name() string

Name implements Extractor.

func (Document) Prose

func (Document) Prose() bool

Prose implements ProseTier: AsciiDoc and rst paragraphs are prose.

type Extractor

type Extractor interface {
	// Name is a short identifier for reports, e.g. "text", "config", "markdown".
	Name() string
	// Match reports whether this extractor handles the path.
	Match(path string) bool
	// Extract scans src, which is the whole file, with the workspace prefix.
	Extract(path string, src []byte, prefix string) Found
}

Extractor is the capability interface for a tier (§37.3). Match decides by path; Extract never returns an error for content problems (those are Problems), only for misuse such as an invalid prefix.

type Found

type Found struct {
	Defs     []Def
	Refs     []Ref
	Problems []Problem
	// Page carries page-level declarations from markdown frontmatter (§13):
	// the ids this page covers and its review interval. Nil for non-pages.
	Page *Page
}

Found is everything one extractor produced for one file.

type Markdown

type Markdown struct{}

Markdown is the document tier (§7.1, §10): markdown, MDX, and html-family files. Directives sit in html comments (`<!-- ds:… -->`), JSX comments in MDX, or link targets. A `ds:def` comment above a heading binds the section until the next heading of the same or higher level; above a paragraph it binds that paragraph; above an html element it binds through the matching close tag on the same nesting level. An inline def link `[8081](ds:def?…)` binds its own link text, which is how a fact lives in prose.

Code is never a carrier: a directive inside a fenced block, an indented code block, or an inline code span is an example, not a directive. Frontmatter is skipped entirely and is never part of a block.

func (Markdown) Extract

func (Markdown) Extract(p string, src []byte, prefix string) Found

Extract implements Extractor.

func (Markdown) Match

func (Markdown) Match(p string) bool

Match implements Extractor.

func (Markdown) Name

func (Markdown) Name() string

Name implements Extractor.

func (Markdown) Prose

func (Markdown) Prose() bool

Prose implements ProseTier: markdown paragraphs are often one long line.

type Occurrence

type Occurrence struct {
	Directive directive.Directive
	Pos       block.Position
	Carrier   block.Carrier
	Trailing  bool
	Code      string
}

Occurrence is one directive found in a comment: what was parsed and where it sat. Trailing means it followed code on the same line; Code is that code.

func ScanCode

func ScanCode(p string, src []byte, prefix string, f *Found) (lines []string, defs []Occurrence)

ScanCode reads every directive in p's comments the way the built-in code tier does: references and link-carrier cites are appended to f, parse problems are recorded, and the `ds:def` occurrences are returned for the caller to bind. lines is the split source, 1-based through Occurrence.Pos. A path with no known comment style yields no occurrences and no problems: a grammar tier that matched such a path has nothing to read directives from.

type Page

type Page struct {
	Covers      []string
	ReviewEvery string
}

Page is the `ds:` block of a markdown page's frontmatter.

type Problem

type Problem struct {
	Pos block.Position
	Err error
}

Problem is a directive the extractor could not use: a parse error, a def with no id, a def with nothing after it. It becomes a finding with the file and line so an author can fix it; extraction never aborts a file for one bad line.

type ProseTier

type ProseTier interface {
	Prose() bool
}

ProseTier is the optional upgrade an extractor implements when its files are prose. The scanner skips a file with a very long line as minified code, which is right for code and wrong for prose: a paragraph written on one line — the norm where markdown is not hard-wrapped — easily runs past the limit, and skipping the doc silently dropped every citation in it, along with any unreviewed change they carried.

type Ref

type Ref struct {
	Directive directive.Directive
	Reference block.Reference
}

Ref is a reference occurrence with its parsed directive.

func ScanLinks(lines []string, prefix string, f *Found, sentenceOf func(line string, col int) string) []Ref

ScanLinks is scanLinks for a tier outside this package: it finds the link-form directives in lines and returns them as references, recording a malformed one as a Problem on f. A tier that should read links only in its comments passes the comment text of each line and "" for the rest, keeping line numbers; ext/structured does that for YAML, TOML and HCL (bug 142). One link grammar, so no tier can drift from the others.

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry is an ordered list of extractors; the first whose Match returns true wins. Order is therefore precedence: a grammar tier is registered before the heuristic code tier for the same extensions.

func Default

func Default() *Registry

Default returns the root module's tiers in precedence order: markdown and html, config formats, the heuristic code binder, then plain text as the universal fallback. The fallback matches every path, so For never fails with the default registry; a caller that wants "unknown files are skipped" filters by scan globs first.

func NewRegistry

func NewRegistry(extractors ...Extractor) *Registry

NewRegistry builds a registry from extractors in precedence order.

func (*Registry) Add

func (r *Registry) Add(e Extractor)

Add appends an extractor at the lowest precedence.

func (*Registry) For

func (r *Registry) For(p string) (Extractor, error)

For returns the extractor for a path or ErrNoExtractor.

func (*Registry) Names

func (r *Registry) Names() []string

Names lists registered extractors in precedence order, for `ds doctor`.

func (*Registry) Prepend

func (r *Registry) Prepend(e Extractor)

Prepend inserts an extractor at the highest precedence.

type Style

type Style struct {
	// Line prefixes that start a comment running to end of line: `//`, `#`, `--`.
	Line []string
	// BlockOpen and BlockClose delimit block comments: `/*` `*/`, `<!--` `-->`,
	// `{/*` `*/}`. Both empty means no block comments.
	BlockOpen, BlockClose string
	// AltOpen and AltClose are a second block form for hosts with two (MDX has
	// JSX comments and, in some renderers, HTML comments).
	AltOpen, AltClose string
	// NoStrings marks a host with no string literals, where a quote is
	// punctuation: prose and markup. Tracking quotes there read the
	// apostrophe in "It's" as opening a string that never closed, and every
	// trailing directive after it on the line was dropped without a word.
	NoStrings bool
	// Bare marks a format that carries a directive as a line of its own with
	// no delimiter at all, which is the text tier's carrier (§10). It is set
	// explicitly rather than assumed for anything unrecognised: writing a bare
	// line into a format that has syntax is not a fallback, it is corruption,
	// and JSON, go.work and Pkl each proved that in the field. A type absent
	// from this table is refused, not written bare.
	Bare bool
	// Trailing marks a format whose own parser reads a comment after a value
	// on the same line as a comment, so `ds def` may write a key's directive
	// there. Everywhere else a key's directive goes on the line above it.
	//
	// Opt-in because the failure is silent data corruption: Java properties,
	// Python's configparser, EditorConfig and `docker run --env-file` read
	// `port=8080   # ds:def id=…` as the value "8080   # ds:def id=…", and
	// `ds def` used to write exactly that into every key-value format.
	// Reading is unaffected: a trailing directive a person wrote is still
	// found, since that is their choice to make about their parser.
	Trailing bool
}

Style describes how a file type writes comments. Every extractor except the bare-text tier reads directives through one of these. Only the shapes in the spec's carrier table exist (§7.1); there is no per-project comment customisation because the scanner must never guess.

func StyleFor

func StyleFor(p string) (Style, bool)

StyleFor returns the style for a path and whether one is known. The base name wins over the extension, so `go.mod` is not read as some `.mod` format and `taskfile.yml` can differ from `.yml` if it ever needs to. Before this the base-name keys with a dot in them were unreachable, because Ext returns an extension whenever the name has one.

type Target

type Target struct {
	Symbol string
	Line   int
	// End is the last line of a `path:start-end` target, 0 for a single line.
	// Locate binds from Line as it always does; the caller decides whether
	// the tier can bind exactly Line..End (docsync.Define widens with span=
	// and refuses when no span gives that range). It is carried rather than
	// dropped because a range read as its first line, silently, defined a
	// block the author never asked for.
	End int
	// Prefix is the configured directive prefix. Locate needs it to see
	// which lines are directive carriers, so the extent it reports is the
	// one the scanner will compute for the same block; empty means the
	// default prefix.
	Prefix string
}

Target names a block without a directive: a symbol, or a 1-based line. Exactly one is set. `ds def file#Symbol` and `ds def file:line` both land here (§22).

func ParseTarget

func ParseTarget(s string) (path string, t Target, err error)

ParseTarget reads `path#Symbol`, `path:line` or `path:start-end`.

The line part must be all digits: it used to be read with Sscanf, which takes the leading number and ignores the rest, so `path:3-5` quietly meant `path:3` and `path:3x` meant it too (bug 43).

type Text

type Text struct{}

Text is the universal fallback tier (§10, "text"): any file, no comment syntax assumed. A directive is a bare line whose trimmed form starts with the prefix. A def binds to the following lines until a blank line, or to `span=+N` lines. Renderers strip the bare directive line.

It also serves files whose Style has line comments (`#`, `//`): if the file is not claimed by a more specific tier, a directive inside such a comment is recognised too, so a `.conf` with no dedicated tier still works.

func (Text) Extract

func (Text) Extract(p string, src []byte, prefix string) Found

Extract implements Extractor.

func (Text) Match

func (Text) Match(string) bool

Match implements Extractor: the text tier matches everything, which is why it must be last in a registry.

func (Text) Name

func (Text) Name() string

func (Text) Prose

func (Text) Prose() bool

Name implements Extractor. Prose implements ProseTier: plain text is written, not minified.

Jump to

Keyboard shortcuts

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