fmlines

package module
v0.5.1 Latest Latest
Warning

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

Go to latest
Published: Jul 30, 2026 License: MIT Imports: 4 Imported by: 0

README

fmlines

Minimalist front matter line extractor for Go — deliberately not a parser: it tokenizes a header line by line and classifies each one; what the lines mean (which keys, which sections — the dialect) is the consumer's business. Every line read becomes a token, the unrecognized ones too — raw and numbered, never lost.

flr := fmlines.NewFmLineReader(file, nil)
if err := flr.ReadHeader(); err != nil {   // reads the --- fenced header, then STOPS
	return err
}
for _, line := range *flr.Lines {
	fmt.Println(line.Type, line.Name(), line.Value())
}
body, _ := flr.Reader.ReadLine() // the body is still in the reader, untouched

The token

Each line comes back as an FmLine:

Field Meaning
Type StartBlock / EndBlock (the --- fences), KeyValue, Section (a bare name:), Empty (blank or # comment), Invalid (anything else — kept, never dropped)
RawLine the line verbatim: the single source of truth
LineNum 1-based position
KvSplit index of the first :
Left indentation width
Parent / Children / Level the section tree, doubly linked at birth: an indented line hangs from its innermost section

Name() and Value() are raw views over RawLine — no trimming, no unquoting: cleaning up is interpretation, and interpretation belongs to the consumer.

The three doors

All of them speak linereader.IoLineReader (anything with a ReadLine() ([]byte, error)github.com/pablo-botella/linereader is the reference):

  • lines.ReadLine(lr) — one line in, its FmLineType out.
  • lines.ReadHeader(lr) — a fenced header (.md): tokens from the opening --- to the closing one, and it stops right there — the body stays in the reader, keep reading it there (by lines, by bytes, or hand the reader to any io.Reader consumer). No opening fence: no front matter — the one consumed line comes back as the only token. Unclosed fence: error.
  • lines.ReadAll(lr) — a bare header (.fm): every line to EOF.

FmLineReader bundles a reader and its list for the common case: NewFmLineReader(r io.Reader, lines *FmLines) (nil list: one is created), then flr.ReadLine() / flr.ReadHeader() / flr.ReadAll() without parameters.

What it deliberately does not do

No YAML engine: no multiline lists (- item lines classify as Invalid — but raw, numbered and adopted by their section, so nothing is lost), no multiline strings, no anchors, no type coercion, no unquoting. When a tool writes what a tool reads, that machinery is dead weight — and whoever needs it has the raw lines, intact.

Scope, honestly: this is a minimalist take for parsing simple front matter. When you need more control — real YAML, lists, types, the whole specification — reach for a full parser such as gopkg.in/yaml.v3 or github.com/goccy/go-yaml instead; this package is not trying to compete with them.

Install

go get github.com/pablo-botella/fmlines

License

MIT — see LICENSE.

Documentation

Overview

Package fmlines is a front matter line extractor — not a parser: it tokenizes a header line by line and classifies each one; what the lines MEAN (the dialect: which keys, which sections) is the consumer's business. Every line read becomes a token, the unrecognized ones too — raw and numbered, never lost.

It reads through a github.com/pablo-botella/linereader LineReader. The family: ReadLine takes ONE line into the list and tells its type, ReadHeader takes a fenced header (.md) and stops right after the closing --- — the body stays in the reader, keep reading it there — and ReadAll takes everything (.fm: everything is header).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type FmLine

type FmLine struct {
	Type     FmLineType
	LineNum  int
	RawLine  string
	KvSplit  int
	Left     int
	Parent   *FmLine
	Level    int
	Children *FmLines
}

func (*FmLine) Name

func (l *FmLine) Name() string

func (*FmLine) Value

func (l *FmLine) Value() string

type FmLineReader

type FmLineReader struct {
	Lines  *FmLines
	Reader linereader.IoLineReader
}

func NewFmLineReader

func NewFmLineReader(reader io.Reader, lines *FmLines) *FmLineReader

func (*FmLineReader) ReadAll

func (flr *FmLineReader) ReadAll() error

func (*FmLineReader) ReadHeader

func (flr *FmLineReader) ReadHeader() error

func (*FmLineReader) ReadLine

func (flr *FmLineReader) ReadLine() (FmLineType, error)

type FmLineType

type FmLineType int
const (
	FmLineInvalid    FmLineType = 0
	FmLineStartBlock FmLineType = 0x0001
	FmLineEndBlock   FmLineType = 0x0002
	FmLineKeyValue   FmLineType = 0x0010
	FmLineSection    FmLineType = 0x0020
	FmLineEmpty      FmLineType = 0x0100
)

type FmLines

type FmLines []*FmLine

func (*FmLines) ReadAll

func (lines *FmLines) ReadAll(lr linereader.IoLineReader) error

ReadAll tokenizes a bare header (.fm) into the list: every line to EOF is front matter — no fences, no body.

func (*FmLines) ReadHeader

func (lines *FmLines) ReadHeader(lr linereader.IoLineReader) error

ReadHeader tokenizes a fenced header (.md) into the list and STOPS right after the closing ---: the body stays in the reader — keep reading it there, by lines, by bytes, or hand the reader to any io.Reader consumer. The first line must be the opening fence; when it is not, there is no front matter: that single line comes back as the only token (read is read, nothing gets lost) and the rest of the document was never touched. An unclosed fence is an error.

func (*FmLines) ReadLine

func (lines *FmLines) ReadLine(lr linereader.IoLineReader) (FmLineType, error)

ReadLine reads ONE line from the reader, puts it into the list and tells what it was. Fences are not its business (that is ReadHeader's): a --- here is just an invalid line. io.EOF when the reader is done.

Jump to

Keyboard shortcuts

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