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.go b/devtools/hackdoc/config/config.go
new file mode 100644
index 0000000..70b7b46
--- /dev/null
+++ b/devtools/hackdoc/config/config.go
@@ -0,0 +1,152 @@
+package config
+
+import (
+ "fmt"
+ "html/template"
+ "strings"
+
+ "github.com/BurntSushi/toml"
+ "github.com/golang/glog"
+
+ "code.hackerspace.pl/hscloud/devtools/hackdoc/source"
+)
+
+// Config is a configuration concerning a given path of the source. It is built
+// from files present in the source, and from global configuration.
+type Config struct {
+ // DefaultIndex is the filenames that should attempt to be rendered if no exact path is given
+ DefaultIndex []string
+ // Templates are the templates available to render markdown files, keyed by template name.
+ Templates map[string]*template.Template
+
+ // Errors that occured while building this config (due to config file errors, etc).
+ Errors map[string]error
+}
+
+type configToml struct {
+ DefaultIndex []string `toml:"default_index"`
+ Templates map[string]*configTomlTemplate `toml:"template"`
+}
+
+type configTomlTemplate struct {
+ Sources []string `toml:"sources"`
+}
+
+func parseToml(data []byte) (*configToml, error) {
+ var c configToml
+ err := toml.Unmarshal(data, &c)
+ if err != nil {
+ return nil, err
+ }
+ if c.Templates == nil {
+ c.Templates = make(map[string]*configTomlTemplate)
+ }
+ return &c, nil
+}
+
+func configFileLocations(path string) []string {
+ // Support for unix-style filesystem prefix (/foo/bar/baz) and
+ // perforce-depot-style prefix (//foo/bar/baz).
+ // Also support relative paths.
+ pathTrimmed := strings.TrimLeft(path, "/")
+ prefixLen := len(path) - len(pathTrimmed)
+ prefix := path[:prefixLen]
+ path = pathTrimmed
+ if len(prefix) > 2 {
+ return nil
+ }
+
+ // Turn path into possible directory names, including root.
+ path = strings.Trim(path, "/")
+ parts := strings.Split(path, "/")
+ if parts[0] != "" {
+ parts = append([]string{""}, parts...)
+ }
+
+ locations := []string{}
+ for i, _ := range parts {
+ p := strings.Join(parts[:i+1], "/")
+ p += "/hackdoc.toml"
+ p = prefix + strings.Trim(p, "/")
+ locations = append(locations, p)
+ }
+ return locations
+}
+
+func ForPath(s source.Source, path string) (*Config, error) {
+ if path != "//" {
+ path = strings.TrimRight(path, "/")
+ }
+
+ // Try cache.
+ cacheKey := fmt.Sprintf("config:%s", path)
+ if v := s.CacheGet(cacheKey); v != nil {
+ cfg, ok := v.(*Config)
+ if !ok {
+ glog.Errorf("Cache key %q corrupted, deleting", cacheKey)
+ s.CacheSet([]string{}, cacheKey, nil)
+ } else {
+ return cfg, nil
+ }
+ }
+
+ // Feed cache.
+ cfg := &Config{
+ Templates: make(map[string]*template.Template),
+ Errors: make(map[string]error),
+ }
+
+ tomlPaths := configFileLocations(path)
+ for _, p := range tomlPaths {
+ file, err := s.IsFile(p)
+ if err != nil {
+ return nil, fmt.Errorf("IsFile(%q): %w", path, err)
+ }
+ if !file {
+ continue
+ }
+ data, err := s.ReadFile(p)
+ if err != nil {
+ return nil, fmt.Errorf("ReadFile(%q): %w", path, err)
+ }
+
+ c, err := parseToml(data)
+ if err != nil {
+ cfg.Errors[p] = err
+ continue
+ }
+
+ err = cfg.updateFromToml(p, s, c)
+ if err != nil {
+ return nil, fmt.Errorf("updating from %q: %w", p, err)
+ }
+ }
+
+ return cfg, nil
+}
+
+func (c *Config) updateFromToml(p string, s source.Source, t *configToml) error {
+ if t.DefaultIndex != nil {
+ c.DefaultIndex = t.DefaultIndex
+ }
+
+ for k, v := range t.Templates {
+ tmpl := template.New(k)
+
+ for _, source := range v.Sources {
+ data, err := s.ReadFile(source)
+ if err != nil {
+ c.Errors[p] = fmt.Errorf("reading template file %q: %w", source, err)
+ return nil
+ }
+ tmpl, err = tmpl.Parse(string(data))
+ if err != nil {
+ c.Errors[p] = fmt.Errorf("parsing template file %q: %w", source, err)
+ return nil
+ }
+ }
+ c.Templates[k] = tmpl
+ }
+
+ return nil
+}