Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 1 | package config |
| 2 | |
| 3 | import ( |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 4 | "context" |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 5 | "fmt" |
| 6 | "html/template" |
| 7 | "strings" |
| 8 | |
| 9 | "github.com/BurntSushi/toml" |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 10 | |
| 11 | "code.hackerspace.pl/hscloud/devtools/hackdoc/source" |
| 12 | ) |
| 13 | |
| 14 | // Config is a configuration concerning a given path of the source. It is built |
| 15 | // from files present in the source, and from global configuration. |
| 16 | type Config struct { |
| 17 | // DefaultIndex is the filenames that should attempt to be rendered if no exact path is given |
| 18 | DefaultIndex []string |
| 19 | // Templates are the templates available to render markdown files, keyed by template name. |
| 20 | Templates map[string]*template.Template |
| 21 | |
| 22 | // Errors that occured while building this config (due to config file errors, etc). |
| 23 | Errors map[string]error |
| 24 | } |
| 25 | |
| 26 | type configToml struct { |
| 27 | DefaultIndex []string `toml:"default_index"` |
| 28 | Templates map[string]*configTomlTemplate `toml:"template"` |
| 29 | } |
| 30 | |
| 31 | type configTomlTemplate struct { |
| 32 | Sources []string `toml:"sources"` |
| 33 | } |
| 34 | |
| 35 | func parseToml(data []byte) (*configToml, error) { |
| 36 | var c configToml |
| 37 | err := toml.Unmarshal(data, &c) |
| 38 | if err != nil { |
| 39 | return nil, err |
| 40 | } |
| 41 | if c.Templates == nil { |
| 42 | c.Templates = make(map[string]*configTomlTemplate) |
| 43 | } |
| 44 | return &c, nil |
| 45 | } |
| 46 | |
| 47 | func configFileLocations(path string) []string { |
| 48 | // Support for unix-style filesystem prefix (/foo/bar/baz) and |
| 49 | // perforce-depot-style prefix (//foo/bar/baz). |
| 50 | // Also support relative paths. |
| 51 | pathTrimmed := strings.TrimLeft(path, "/") |
| 52 | prefixLen := len(path) - len(pathTrimmed) |
| 53 | prefix := path[:prefixLen] |
| 54 | path = pathTrimmed |
| 55 | if len(prefix) > 2 { |
| 56 | return nil |
| 57 | } |
| 58 | |
| 59 | // Turn path into possible directory names, including root. |
| 60 | path = strings.Trim(path, "/") |
| 61 | parts := strings.Split(path, "/") |
| 62 | if parts[0] != "" { |
| 63 | parts = append([]string{""}, parts...) |
| 64 | } |
| 65 | |
| 66 | locations := []string{} |
| 67 | for i, _ := range parts { |
| 68 | p := strings.Join(parts[:i+1], "/") |
| 69 | p += "/hackdoc.toml" |
| 70 | p = prefix + strings.Trim(p, "/") |
| 71 | locations = append(locations, p) |
| 72 | } |
| 73 | return locations |
| 74 | } |
| 75 | |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 76 | func ForPath(ctx context.Context, s source.Source, path string) (*Config, error) { |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 77 | if path != "//" { |
| 78 | path = strings.TrimRight(path, "/") |
| 79 | } |
| 80 | |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 81 | cfg := &Config{ |
| 82 | Templates: make(map[string]*template.Template), |
| 83 | Errors: make(map[string]error), |
| 84 | } |
| 85 | |
| 86 | tomlPaths := configFileLocations(path) |
| 87 | for _, p := range tomlPaths { |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 88 | file, err := s.IsFile(ctx, p) |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 89 | if err != nil { |
| 90 | return nil, fmt.Errorf("IsFile(%q): %w", path, err) |
| 91 | } |
| 92 | if !file { |
| 93 | continue |
| 94 | } |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 95 | data, err := s.ReadFile(ctx, p) |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 96 | if err != nil { |
| 97 | return nil, fmt.Errorf("ReadFile(%q): %w", path, err) |
| 98 | } |
| 99 | |
| 100 | c, err := parseToml(data) |
| 101 | if err != nil { |
| 102 | cfg.Errors[p] = err |
| 103 | continue |
| 104 | } |
| 105 | |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 106 | err = cfg.updateFromToml(ctx, p, s, c) |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 107 | if err != nil { |
| 108 | return nil, fmt.Errorf("updating from %q: %w", p, err) |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | return cfg, nil |
| 113 | } |
| 114 | |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 115 | func (c *Config) updateFromToml(ctx context.Context, p string, s source.Source, t *configToml) error { |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 116 | if t.DefaultIndex != nil { |
| 117 | c.DefaultIndex = t.DefaultIndex |
| 118 | } |
| 119 | |
| 120 | for k, v := range t.Templates { |
| 121 | tmpl := template.New(k) |
| 122 | |
| 123 | for _, source := range v.Sources { |
Sergiusz Bazanski | f157b4d | 2020-04-10 17:39:43 +0200 | [diff] [blame] | 124 | data, err := s.ReadFile(ctx, source) |
Sergiusz Bazanski | c881cf3 | 2020-04-08 20:03:12 +0200 | [diff] [blame] | 125 | if err != nil { |
| 126 | c.Errors[p] = fmt.Errorf("reading template file %q: %w", source, err) |
| 127 | return nil |
| 128 | } |
| 129 | tmpl, err = tmpl.Parse(string(data)) |
| 130 | if err != nil { |
| 131 | c.Errors[p] = fmt.Errorf("parsing template file %q: %w", source, err) |
| 132 | return nil |
| 133 | } |
| 134 | } |
| 135 | c.Templates[k] = tmpl |
| 136 | } |
| 137 | |
| 138 | return nil |
| 139 | } |