testmark-go

Add directory-like indexing feature.

commit 28e155e57d39e10b53b203141469cf2811815644 · parent 79ab310c54 · browse this tree
Eric Myhre, 2021-08-18 14:57:36 UTC

diff --git a/index.go b/index.go
new file mode 100644
index 0000000..25c554c
--- /dev/null
+++ b/index.go
@@ -0,0 +1,41 @@
+package testmark
+
+import (
+	"strings"
+)
+
+// BuildDirIndex mutates the Document to set the DirEnt field.
+//
+// The order of ChildrenList in the DirEnt
+// is determined by the order in which things are first seen in the Document's Hunk list.
+// The "directories" can be implied,
+// e.g. a Hunk with name="foo/bar" will cause the creation of a DirEnt with name "foo".
+//
+// No concept of path "cleaning" is applied.  Paths like "." and ".." are not treated specially.
+// A path containing repeated slashes is a fairly deranged thing to do, but also won't be rejected.
+func (doc *Document) BuildDirIndex() {
+	doc.DirEnt = &DirEnt{}
+	for _, hunk := range doc.DataHunks {
+		doc.DirEnt.fill(strings.Split(hunk.Name, "/"), hunk.Hunk)
+	}
+}
+
+func (dirent *DirEnt) fill(pathSegs []string, hunk Hunk) {
+	if len(pathSegs) == 0 {
+		dirent.Hunk = &hunk
+		return
+	}
+	if dirent.Children == nil {
+		dirent.Children = make(map[string]*DirEnt)
+	}
+	if next, exists := dirent.Children[pathSegs[0]]; exists {
+		next.fill(pathSegs[1:], hunk)
+	} else {
+		l := len(dirent.ChildrenList)
+		dirent.ChildrenList = append(dirent.ChildrenList, DirEnt{
+			Name: pathSegs[0],
+		})
+		dirent.Children[pathSegs[0]] = &dirent.ChildrenList[l]
+		dirent.ChildrenList[l].fill(pathSegs[1:], hunk)
+	}
+}
diff --git a/index_test.go b/index_test.go
new file mode 100644
index 0000000..a4c9c19
--- /dev/null
+++ b/index_test.go
@@ -0,0 +1,39 @@
+package testmark
+
+import (
+	"path/filepath"
+	"testing"
+)
+
+func TestIndexingDirs(t *testing.T) {
+	testdata, err := filepath.Abs("testdata")
+	if err != nil {
+		panic(err)
+	}
+	doc, err := ReadFile(filepath.Join(testdata, "exampleWithDirs.md"))
+	doc.BuildDirIndex()
+	if len(doc.DirEnt.ChildrenList) != 2 {
+		t.Errorf("root dirent list should be length 2")
+	}
+	if len(doc.DirEnt.Children) != 2 {
+		t.Errorf("root dirent map should be length 2")
+	}
+	if doc.DirEnt.ChildrenList[0].Name != "one" {
+		t.Errorf("first child of root dirent should've been 'one'")
+	}
+	if doc.DirEnt.ChildrenList[1].Name != "really" {
+		t.Errorf("second child of root dirent should've been 'really'")
+	}
+	if doc.DirEnt.Children["one"].Hunk.Name != "one" {
+		t.Errorf("hunk 'one' looked up through dir maps should still have the right name")
+	}
+	if string(doc.DirEnt.Children["one"].Hunk.Body) != "baz\n" {
+		t.Errorf("hunk 'one' looked up through dir maps should have the right content")
+	}
+	if doc.DirEnt.Children["really"].Children["deep"].Children["dirs"].Children["wow"].Hunk.Name != "really/deep/dirs/wow" {
+		t.Errorf("hunk 'really/deep/dirs/wow' looked up through dir maps should still have the right name")
+	}
+	if string(doc.DirEnt.Children["really"].Children["deep"].Children["dirs"].Children["wow"].Hunk.Body) != "zot\n" {
+		t.Errorf("hunk 'really/deep/dirs/wow' looked up through dir maps should have the right content")
+	}
+}
diff --git a/testdata/exampleWithDirs.md b/testdata/exampleWithDirs.md
new file mode 100644
index 0000000..92efb59
--- /dev/null
+++ b/testdata/exampleWithDirs.md
@@ -0,0 +1,42 @@
+an example with "directories"
+=============================
+
+This testmark implementation provides convenience functions for treating
+the data hunk names as if they were "paths" in some sort of "filesystem".
+
+So, in this file we'll exercise that by using a bunch of hunk names that have slashes in them.
+
+### directories can be implied
+
+[testmark]:# (one/two)
+```text
+foo
+```
+
+[testmark]:# (one/three)
+```text
+bar
+```
+
+### a path can have both children and data
+
+(Maybe avoid leaning on this, though, because some would argue it's weird.)
+
+[testmark]:# (one)
+```text
+baz
+```
+
+### directories can be implied deeply
+
+[testmark]:# (really/deep/dirs/wow)
+```text
+zot
+```
+
+### files in the same directory don't have to be subsequent
+
+[testmark]:# (one/four/bang)
+```text
+mop
+```
diff --git a/testmark.go b/testmark.go
index d6ad9eb..e570ff0 100644
--- a/testmark.go
+++ b/testmark.go
@@ -18,6 +18,11 @@ type Document struct {
 
 	// Like it says on the tin.
 	HunksByName map[string]DocHunk
+
+	// An index over the hunks, which treats their names as if they were unix-style paths --
+	// meaning they're split by slashes, and each segment is considered a directory.
+	// You must call the `BuildDirIndex()` function to cause this to be populated.
+	DirEnt *DirEnt
 }
 
 // DocHunk is the Document's internal idea of where hunks are.
@@ -49,3 +54,18 @@ type Hunk struct {
 	// (This is *still* a subslice of Document.Original, if this hunk was created by Parse, but probably a unique slice otherwise.)
 	Body []byte
 }
+
+// DirEnt describes an index over hunks in a document that acts much like a filesystem.
+// See Document.BuildDirIndex for details.
+type DirEnt struct {
+	// The name of just this path segment.
+	// (Note that if there's a Hunk in this DirEnt, its name may be different -- it still has the *full* path name.)
+	Name string
+
+	// A hunk, or nil.
+	Hunk *Hunk
+
+	// Children, recursively.
+	Children     map[string]*DirEnt
+	ChildrenList []DirEnt
+}