# 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. ## Package Organization - **`github.com/warpfork/go-testmark/v2`** - Core testmark functionality - **`github.com/warpfork/go-testmark/v2/testexec`** - Command execution and Go program testing - **`github.com/warpfork/go-testmark/v2/doctest`** - Experimental documentation testing ## 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/fs` support for filesystem-like access - ✅ Enhanced testexec with Go module support - ✅ Contemporary flag handling - ✅ Method chaining for fluent APIs ## Quick Start ```go 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 (Go-idiomatic) iter := doc.Hunks() for hunk, ok := iter.Next(); ok; hunk, ok = iter.Next() { 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: ```go 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: ```go // 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: ```go // 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: ```go // 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 ```go 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: ```go 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: ```go 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 ```go // 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 ```go // 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: ```go // 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](example_test.go) for comprehensive examples of v2 usage patterns.