| package config |
| |
| import ( |
| "context" |
| "fmt" |
| "html/template" |
| "strings" |
| |
| "github.com/BurntSushi/toml" |
| |
| "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(ctx context.Context, s source.Source, path string) (*Config, error) { |
| if path != "//" { |
| path = strings.TrimRight(path, "/") |
| } |
| |
| cfg := &Config{ |
| Templates: make(map[string]*template.Template), |
| Errors: make(map[string]error), |
| } |
| |
| tomlPaths := configFileLocations(path) |
| for _, p := range tomlPaths { |
| file, err := s.IsFile(ctx, p) |
| if err != nil { |
| return nil, fmt.Errorf("IsFile(%q): %w", path, err) |
| } |
| if !file { |
| continue |
| } |
| data, err := s.ReadFile(ctx, 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(ctx, p, s, c) |
| if err != nil { |
| return nil, fmt.Errorf("updating from %q: %w", p, err) |
| } |
| } |
| |
| return cfg, nil |
| } |
| |
| func (c *Config) updateFromToml(ctx context.Context, 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(ctx, 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 |
| } |