README.md
go-testmark v2๐
A clean, modern API for working with testmark documents
This is version 2 of go-testmark, designed to address the pain points and improve the user experience of the original library.
Key Improvements in v2๐
๐ No More Boilerplate๐
- โ
Directory indexing happens automatically (no more
doc.BuildDirIndex()) - โ Simplified patch accumulation with built-in support
- โ All functionality in one package (no more multiple imports)
๐ Clean API Design๐
- โ All struct fields are private (no accidental mutations)
- โ Clean accessor methods and constructors
- โ Immutable operations (patching returns new documents)
โก Enhanced Features๐
- โ Safe iterators for documents and directories
- โ
Built-in
io/fssupport for filesystem-like access - โ Enhanced testexec with Go module support
- โ Contemporary flag handling
- โ Method chaining for fluent APIs
Quick Start๐
package main
import (
"fmt"
testmark "github.com/warpfork/go-testmark/v2"
)
func main() {
// Parse a document - directory indexing happens automatically!
doc, err := testmark.ReadFile("example.md")
if err != nil {
panic(err)
}
// Access hunks easily
hunk := doc.HunkByName("my-data")
fmt.Printf("Content: %s\n", hunk.String())
// Use clean iterators
for iter := doc.Hunks(); iter.Next(); {
hunk := iter.Hunk()
fmt.Printf("%s: %d bytes\n", hunk.Name(), len(hunk.Body()))
}
// Navigate like a filesystem
config := doc.Root().Child("config").Child("database")
if config != nil {
fmt.Printf("DB config: %s\n", config.Hunk().String())
}
}
Core Concepts๐
Documents with Private Fields๐
All document internals are now private. Use accessor methods:
doc := testmark.MustParseString(data)
// โ
Clean accessors
fmt.Println(doc.Count()) // number of hunks
fmt.Println(doc.HunkNames()) // all hunk names
hunk := doc.HunkByName("my-hunk") // get specific hunk
root := doc.Root() // get directory tree
// โ No more direct field access
// doc.DataHunks - this doesn't exist anymore!
// doc.BuildDirIndex() - happens automatically!
Safe Iterators๐
No more exposing internal slices and maps:
// Iterate over hunks
for iter := doc.Hunks(); iter.Next(); {
hunk := iter.Hunk()
// work with hunk
}
// Iterate over directory children
for iter := entry.Children(); iter.Next(); {
child := iter.Entry()
// work with child
}
// Functional style
doc.Hunks().ForEach(func(hunk *testmark.Hunk) error {
fmt.Println(hunk.Name())
return nil
})
Simplified Patching๐
Patching is now clean and supports accumulation:
// Simple patching
newHunk := testmark.NewHunkString("data", "", "new content")
patched := doc.Patch(newHunk)
// Accumulated patching
doc = doc.EnablePatching() // method chaining!
patches := doc.Patches()
patches.Add(testmark.NewHunkString("hunk1", "", "content1"))
patches.Add(testmark.NewHunkString("hunk2", "", "content2"))
result := patches.ApplyTo(doc)
// Auto-write with patches
doc.AutoWriteFile("output.md") // applies patches if enabled
Built-in Filesystem Support๐
No more separate fs package:
// Use document as filesystem
fs := doc.FS()
content, err := doc.ReadFile("path/to/hunk")
entries, err := doc.ReadDir("directory")
matches, err := doc.Glob("config/*")
// Walk the entire tree
doc.Root().Walk(func(entry *testmark.DirEntry) error {
fmt.Printf("Visiting: %s\n", entry.Path())
return nil
})
Enhanced Test Execution๐
Simple Test Execution๐
func TestMyCommands(t *testing.T) {
doc := testmark.MustReadFile("tests.md")
// Test execution with sensible defaults
testmark.TestExec(t, doc.Root().Child("my-test"), nil)
}
Go Module Support๐
Built-in support for running Go programs:
config := testmark.DefaultExecConfig()
config.ExecFn = testmark.GoExecFunc("github.com/myorg/mymodule")
// Now "go run main.go" and "./main.go" both work automatically
testmark.TestExec(t, entry, config)
Test Suites๐
Simplified test suite management:
func TestSuite(t *testing.T) {
// Use fsx.DirFS for write support if needed
fs := fsx.DirFS(".")
suite := testmark.NewSuite(fs)
suite.MustAddExec("*.md", "test-*") // basic execution
suite.MustAddGoExec("go-*.md", "*", "my/module") // Go support
suite.Run(t)
}
Contemporary Flag Handling๐
// Modern flag handling
var regenFlag = flag.Bool("testmark.regen", false, "regenerate fixtures")
func TestMain(m *testing.M) {
flag.Parse()
// Set global regen behavior
if *regenFlag {
testmark.SetAutoRegen(true)
}
os.Exit(m.Run())
}
// Or check directly
if testmark.ShouldRegen() {
// regenerate fixtures
}
Migration from v1๐
Key Changes๐
| v1 | v2 |
|---|---|
doc.BuildDirIndex() |
Automatic |
doc.DataHunks |
doc.Hunks() iterator |
doc.HunksByName[name] |
doc.HunkByName(name) |
doc.DirEnt |
doc.Root() |
&testmark.PatchAccumulator{} |
doc.EnablePatching().Patches() |
| Multiple imports | Single v2 import |
| Public fields | Private fields + accessors |
Migration Example๐
// v1 code
func oldCode() {
doc, _ := testmark.ReadFile("test.md")
doc.BuildDirIndex() // manual step
patches := &testmark.PatchAccumulator{}
for _, hunk := range doc.DataHunks {
// work with hunk
}
newDoc := testmark.Patch(doc, patches.Patches...)
}
// v2 equivalent
func newCode() {
doc := testmark.MustReadFile("test.md") // auto-indexed
doc = doc.EnablePatching() // fluent
for iter := doc.Hunks(); iter.Next(); {
hunk := iter.Hunk()
// work with hunk
if shouldUpdate {
doc.Patches().Add(testmark.NewHunkString(hunk.Name(), "", "new content"))
}
}
result := doc.Patches().ApplyTo(doc) // or doc.AutoWrite()
}
Compatibility๐
v2 is designed as a separate module to avoid breaking existing v1 users:
// v1 users continue unchanged
import "github.com/warpfork/go-testmark"
// v2 users get the new API
import testmark "github.com/warpfork/go-testmark/v2"
Both versions will be maintained, but new features will focus on v2.
Examples๐
See example_test.go for comprehensive examples of v2 usage patterns.
v2/README.md ยท 6.4 KiB ยท 4d0e1b6afd