devtools/hackdoc: init

This is hackdoc, a documentation rendering tool for monorepos.

This is the first code iteration, that can only serve from a local git
checkout.

The code is incomplete, and is WIP.

Change-Id: I68ef7a991191c1bb1b0fdd2a8d8353aba642e28f
diff --git a/devtools/hackdoc/config/config_test.go b/devtools/hackdoc/config/config_test.go
new file mode 100644
index 0000000..ba542fe
--- /dev/null
+++ b/devtools/hackdoc/config/config_test.go
@@ -0,0 +1,99 @@
+package config
+
+import (
+	"testing"
+
+	"github.com/go-test/deep"
+)
+
+func TestParse(t *testing.T) {
+	for _, test := range []struct {
+		name string
+		data string
+		want *configToml
+	}{
+		{
+			name: "normal config",
+			data: `
+				default_index = ["foo.md", "bar.md"]
+				[template.default]
+				sources = ["hackdoc/bar.html", "hackdoc/baz.html"]
+				[template.foo]
+				sources = ["foo/bar.html", "foo/baz.html"]
+			`,
+			want: &configToml{
+				DefaultIndex: []string{"foo.md", "bar.md"},
+				Templates: map[string]*configTomlTemplate{
+					"default": &configTomlTemplate{
+						Sources: []string{"hackdoc/bar.html", "hackdoc/baz.html"},
+					},
+					"foo": &configTomlTemplate{
+						Sources: []string{"foo/bar.html", "foo/baz.html"},
+					},
+				},
+			},
+		}, {
+			name: "empty config",
+			data: "",
+			want: &configToml{
+				DefaultIndex: nil,
+				Templates:    map[string]*configTomlTemplate{},
+			},
+		},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			got, err := parseToml([]byte(test.data))
+			if err != nil {
+				t.Fatalf("could not parse config: %w", err)
+			}
+			if diff := deep.Equal(test.want, got); diff != nil {
+				t.Fatal(diff)
+			}
+		})
+	}
+}
+
+func TestLocations(t *testing.T) {
+	for _, test := range []struct {
+		name string
+		path string
+		want []string
+	}{
+		{
+			name: "perforce-style path",
+			path: "//foo/bar/baz",
+			want: []string{"//hackdoc.toml", "//foo/hackdoc.toml", "//foo/bar/hackdoc.toml", "//foo/bar/baz/hackdoc.toml"},
+		}, {
+			name: "unix-style path",
+			path: "/foo/bar/baz",
+			want: []string{"/hackdoc.toml", "/foo/hackdoc.toml", "/foo/bar/hackdoc.toml", "/foo/bar/baz/hackdoc.toml"},
+		}, {
+			name: "relative-style path",
+			path: "foo/bar/baz",
+			want: []string{"hackdoc.toml", "foo/hackdoc.toml", "foo/bar/hackdoc.toml", "foo/bar/baz/hackdoc.toml"},
+		}, {
+			name: "root perforce-style path",
+			path: "//",
+			want: []string{"//hackdoc.toml"},
+		}, {
+			name: "root unix-style path",
+			path: "/",
+			want: []string{"/hackdoc.toml"},
+		}, {
+			name: "empty path",
+			path: "",
+			want: []string{"hackdoc.toml"},
+		}, {
+			name: "weird path",
+			path: "///what/is///this///",
+			want: nil,
+		},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			got := configFileLocations(test.path)
+			if diff := deep.Equal(test.want, got); diff != nil {
+				t.Fatal(diff)
+			}
+		})
+	}
+}