blob: aba384bc9319ff7aafdc3980b164abc76e97335e [file] [log] [blame]
Sergiusz Bazanskic881cf32020-04-08 20:03:12 +02001package config
2
3import (
Sergiusz Bazanskif157b4d2020-04-10 17:39:43 +02004 "context"
Sergiusz Bazanskic881cf32020-04-08 20:03:12 +02005 "fmt"
6 "html/template"
7 "strings"
8
9 "github.com/BurntSushi/toml"
Sergiusz Bazanskic881cf32020-04-08 20:03:12 +020010
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.
16type 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
26type configToml struct {
27 DefaultIndex []string `toml:"default_index"`
28 Templates map[string]*configTomlTemplate `toml:"template"`
29}
30
31type configTomlTemplate struct {
32 Sources []string `toml:"sources"`
33}
34
35func 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
47func 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 Bazanskif157b4d2020-04-10 17:39:43 +020076func ForPath(ctx context.Context, s source.Source, path string) (*Config, error) {
Sergiusz Bazanskic881cf32020-04-08 20:03:12 +020077 if path != "//" {
78 path = strings.TrimRight(path, "/")
79 }
80
Sergiusz Bazanskic881cf32020-04-08 20:03:12 +020081 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 Bazanskif157b4d2020-04-10 17:39:43 +020088 file, err := s.IsFile(ctx, p)
Sergiusz Bazanskic881cf32020-04-08 20:03:12 +020089 if err != nil {
90 return nil, fmt.Errorf("IsFile(%q): %w", path, err)
91 }
92 if !file {
93 continue
94 }
Sergiusz Bazanskif157b4d2020-04-10 17:39:43 +020095 data, err := s.ReadFile(ctx, p)
Sergiusz Bazanskic881cf32020-04-08 20:03:12 +020096 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 Bazanskif157b4d2020-04-10 17:39:43 +0200106 err = cfg.updateFromToml(ctx, p, s, c)
Sergiusz Bazanskic881cf32020-04-08 20:03:12 +0200107 if err != nil {
108 return nil, fmt.Errorf("updating from %q: %w", p, err)
109 }
110 }
111
112 return cfg, nil
113}
114
Sergiusz Bazanskif157b4d2020-04-10 17:39:43 +0200115func (c *Config) updateFromToml(ctx context.Context, p string, s source.Source, t *configToml) error {
Sergiusz Bazanskic881cf32020-04-08 20:03:12 +0200116 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 Bazanskif157b4d2020-04-10 17:39:43 +0200124 data, err := s.ReadFile(ctx, source)
Sergiusz Bazanskic881cf32020-04-08 20:03:12 +0200125 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}