mdconfig

package module
v0.0.2 Latest Latest
Warning

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

Go to latest
Published: Jun 21, 2026 License: MIT Imports: 9 Imported by: 0

README

mdconfig

A lightweight, robust Go library designed to parse structured configurations directly from Markdown files. It parses Markdown elements into logical sections, typed tables, standard lists, and task-lists (boolean maps), while preserving non-config prose as first-class comments for documentation and future round-trip editing.

Features

  • Two parsing modes: Parse decodes Markdown into Go structs via mdconfig struct tags; DynamicParse returns the full Config for runtime inspection.
  • Comments as first-class data: Paragraphs, blockquotes, code blocks, HTML blocks, and thematic breaks that are not used as config values are preserved as Comment values on sections.
  • Sections as Categories: Headings (#, ##, ###) are interpreted as configuration section boundaries, mapping titles directly to isolated configuration environments.
  • Typed Tables: Automatically parses Markdown tables, allowing you to enforce data types (string, int, float, bool) on columns using the :type suffix.
  • Lists & Checklists:
    • Standard sequential lists (- item) are parsed into lists of strings.
    • Checklists (- [x] key, - [ ] key) are parsed into boolean maps.
  • Config Validation: Validates actual cell contents against declared column schemas, returning structured, detailed errors including section names, row and column indices, expected types, and original values. Validation ignores comments.
  • Stable Serialization: Outputs structured text representation of parsed configurations via AsString(), including preserved comments, utilizing deterministic sorted keys for consistent testing and debugging.

Parsing modes

Struct-tag mode: Parse

Use Parse when you know the expected config shape and want typed Go values:

type Repo struct {
    ID     string `mdconfig:"id"`
    Path   string `mdconfig:"path"`
    Remote string `mdconfig:"remote"`
}

type Project struct {
    Name  string `mdconfig:"project.name"`
    Repos []Repo `mdconfig:"repos"`
}

var cfg Project
err := mdconfig.Parse(markdownBytes, &cfg)

Tag rules:

  • mdconfig:"section" selects a heading section.
  • mdconfig:"section.column" reads a scalar from the first table row in that section.
  • Struct or *Struct with mdconfig:"section" binds a nested struct to that section; scalar fields inside use mdconfig:"column" (equivalent to the flat section.column form).
  • []string reads the first ordinary list in the section.
  • map[string]bool reads the first checklist in the section.
  • []Struct reads the first table in the section and maps columns to nested struct fields by tag or field name.
Dynamic mode: DynamicParse

Use DynamicParse when you want to inspect sections, tables, lists, and preserved comments without a predefined Go schema:

cfg, err := mdconfig.DynamicParse(markdownBytes)
sec := cfg.Sections["services"]
for _, comment := range sec.Comments {
    fmt.Println(comment.Kind, comment.Text)
}

Supported Format Example

# Global configuration defined before any heading belongs to the "Root" section.

This prose is preserved as a root comment.

## services

The services configuration table:
| name:string | enabled:bool | retries:int | timeout:float |
|-------------|--------------|-------------|---------------|
| api         | true         | 3           | 1.5           |
| gateway     | false        | 5           | 5.0           |

## features

- [x] auth
- [ ] billing
- [x] rate_limiting

## hosts

- localhost
- 127.0.0.1

In dynamic mode, a paragraph immediately before a table becomes the table name only when it ends with :. Other prose becomes comments.

Types and Coercion

Types can be specified inside column headers using the column_name:type suffix. The library supports the following data types:

  • string: No coercion needed. Default type if no suffix is specified.
  • int: Parsed into an int64 using base-10 conversion.
  • float: Parsed into a float64.
  • bool: Parsed into a bool using Go's strconv.ParseBool conventions (true, false, 1, 0, etc.).

Usage Example

package main

import (
    "fmt"
    "log"

    "github.com/konkero-project/mdconfig"
)

func main() {
    markdownData := []byte(`
## appV
| port:int | debug:bool | ratio:float |
|----------|------------|-------------|
| 8080     | true       | 0.75        |
`)

    type App struct {
        Port  int64   `mdconfig:"port"`
        Debug bool    `mdconfig:"debug"`
        Ratio float64 `mdconfig:"ratio"`
    }

    type Top struct {
        App App `mdconfig:"app"`
    }

    // Flat style is equivalent for the same table:
    // type App struct {
    //     Port  int64   `mdconfig:"app.port"`
    //     Debug bool    `mdconfig:"app.debug"`
    //     Ratio float64 `mdconfig:"app.ratio"`
    // }

    var top Top
    if err := mdconfig.Parse(markdownData, &top); err != nil {
        log.Fatalf("Failed to parse: %v", err)
    }

    fmt.Printf("App Port: %d, Debug: %t, Ratio: %f\n", top.App.Port, top.App.Debug, top.App.Ratio)

    cfg, err := mdconfig.DynamicParse(markdownData)
    if err != nil {
        log.Fatalf("Failed to dynamic parse: %v", err)
    }
    if err := cfg.Validate(); err != nil {
        log.Fatalf("Validation failed: %v", err)
    }

    fmt.Println(cfg.AsString())
}

Round-trip

Comments are preserved during parsing with best-effort source ranges. Full write-back of edited values while preserving all Markdown layout is planned as a separate phase and is not part of the current API.

Running Tests

Tests are written using the Ginkgo v2 BDD testing framework and Gomega matchers.

To run tests:

make test

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Parse

func Parse(text []byte, out any) error

Parse decodes Markdown into out using mdconfig struct tags on the fields of out. out must be a non-nil pointer to a struct. Non-config Markdown is preserved internally during parsing but is not written into out.

func ParseFrom

func ParseFrom(r io.Reader, out any) error

ParseFrom reads Markdown from r and decodes it into out using struct tags.

Types

type Column

type Column struct {
	// Name is the clean name of the column without any type suffixes.
	Name string
	// Type is the declared column type, defaulting to TypeString if not specified.
	Type ColumnType
}

Column defines the schema of a single table column.

type ColumnType

type ColumnType string

ColumnType represents the parsed data type of a table column.

const (
	// TypeString represents a string column type.
	TypeString ColumnType = "string"
	// TypeInt represents an integer column type.
	TypeInt ColumnType = "int"
	// TypeFloat represents a floating-point column type.
	TypeFloat ColumnType = "float"
	// TypeBool represents a boolean column type.
	TypeBool ColumnType = "bool"
)

type Comment added in v0.0.2

type Comment struct {
	// Text is the Markdown source fragment for this comment block.
	Text string
	// Kind is the Markdown node category that produced this comment.
	Kind CommentKind
	// Range is the best-effort source location of Text in the original document.
	Range SourceRange
	// Target is an optional anchor such as a section title or config block name.
	Target string
}

Comment is documentation prose or other non-config Markdown preserved during parsing.

type CommentKind added in v0.0.2

type CommentKind string

CommentKind identifies the Markdown node type preserved as a comment.

const (
	// CommentParagraph is a normal prose paragraph.
	CommentParagraph CommentKind = "paragraph"
	// CommentBlockQuote is a blockquote block.
	CommentBlockQuote CommentKind = "blockquote"
	// CommentCodeBlock is a fenced or indented code block.
	CommentCodeBlock CommentKind = "code_block"
	// CommentHTML is raw HTML or an HTML comment block.
	CommentHTML CommentKind = "html"
	// CommentThematicBreak is a horizontal rule.
	CommentThematicBreak CommentKind = "thematic_break"
	// CommentOther is an unsupported or unknown block node.
	CommentOther CommentKind = "other"
)

type Config

type Config struct {
	// RootSection holds tables, lists, checklists, and comments defined before any Markdown heading.
	RootSection Section
	// Sections maps section heading titles to their respective parsed Section configurations.
	Sections map[string]*Section
}

Config is the root configuration structure containing all parsed Markdown data.

func DynamicParse added in v0.0.2

func DynamicParse(text []byte) (*Config, error)

DynamicParse processes a Markdown byte slice and returns a structured Config with comments preserved.

func DynamicParseFrom added in v0.0.2

func DynamicParseFrom(inp io.Reader) (*Config, error)

DynamicParseFrom reads Markdown data from an io.Reader and parses it into a structured Config.

func (*Config) AsString

func (c *Config) AsString() string

AsString generates a stable, deterministic, and highly detailed human-readable textual representation of the entire parsed configuration. It lists the root section first, followed by all other sections sorted alphabetically by their titles. For each section, it outputs its tables (including schemas and cell data with parsed types), sequential lists, checklists (with keys sorted alphabetically to ensure consistency across separate executions), and preserved comments. This is highly useful for debugging and golden-file testing.

func (*Config) String

func (c *Config) String() string

String provides a convenience wrapper over AsString, satisfying the standard fmt.Stringer interface. It returns the stable, human-readable dump of the parsed configuration structure.

func (*Config) Validate

func (c *Config) Validate() error

Validate checks all table cells in the configuration against their schemas and returns any validation errors.

type DecodeError added in v0.0.2

type DecodeError struct {
	Field   string
	Tag     string
	Section string
	Message string
}

DecodeError reports a failure while mapping Markdown config data into a Go struct field.

func (*DecodeError) Error added in v0.0.2

func (e *DecodeError) Error() string

type Section

type Section struct {
	// Title is the heading text that represents the section category name.
	Title string
	// Level is the Markdown heading level (e.g., 1 for #, 2 for ##, etc.).
	Level int
	// Tables is the collection of tables parsed within this section.
	Tables []Table
	// Lists contains ordinary sequential list items grouped by list.
	Lists [][]string
	// BoolLists contains checklist maps representing checklist items and their checked status.
	BoolLists []map[string]bool
	// Comments holds non-config Markdown blocks within this section.
	Comments []Comment
}

Section represents a configuration category defined by a Markdown heading.

type SourceRange added in v0.0.2

type SourceRange struct {
	// Start is the inclusive byte offset in the source document.
	Start int
	// End is the exclusive byte offset in the source document.
	End int
	// Line is the 1-based line number at Start when known, otherwise zero.
	Line int
	// Col is the 1-based column number at Start when known, otherwise zero.
	Col int
}

SourceRange maps a parsed block back to its location in the original Markdown bytes.

type Table

type Table struct {
	// Name is the name of the table, typically extracted from the preceding paragraph.
	Name string
	// Columns is the list of columns defining the table's schema.
	Columns []Column
	// Rows contains the table data, where each row is a slice of typed Values corresponding to the columns.
	Rows [][]Value
}

Table represents a parsed Markdown table.

type ValidationError

type ValidationError struct {
	// Section is the title of the section containing the invalid table.
	Section string
	// TableIndex is the 0-based index of the table within its section.
	TableIndex int
	// TableName is the name of the table, if specified.
	TableName string
	// RowIndex is the 0-based index of the row containing the invalid value.
	RowIndex int
	// ColIndex is the 0-based index of the column containing the invalid value.
	ColIndex int
	// ColName is the name of the column containing the invalid value.
	ColName string
	// ExpectedType is the schema type that the value failed to conform to.
	ExpectedType ColumnType
	// Value is the raw string value that caused the validation failure.
	Value string
	// Message is the detailed description of the type conversion failure.
	Message string
}

ValidationError describes an error that occurred while validating a table cell value against its column schema.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error formats the ValidationError into a detailed, human-readable string.

type Value

type Value struct {
	// Raw is the original string value extracted from the Markdown document.
	Raw string
	// Typed contains the validated Go typed value (string, int64, float64, bool) if parsed successfully, or nil otherwise.
	Typed interface{}
}

Value represents a single cell or list item value that holds both its raw string representation and its successfully parsed typed value.

Jump to

Keyboard shortcuts

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