1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
// SPDX-License-Identifier: GPL-3.0-or-later
package config
import (
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"krino/internal/sexp"
"krino/internal/xdg"
)
// Config is the whole configuration: the main file and the directories it
// includes.
type Config struct {
Main *Main
Dirs []*Dir
}
// DefaultFile is krino.conf in the XDG config directory.
func DefaultFile() string {
return filepath.Join(xdg.ConfigHome(), "krino", "krino.conf")
}
// DirFile is where directory name's config lives, beside the main file.
func DirFile(mainFile, name string) string {
return filepath.Join(filepath.Dir(mainFile), "dirs", name+".conf")
}
// Load reads the main file and the files of the directories it includes.
// With names, only those directories are read, and each must be included.
// The Config is nil only when the main file itself cannot be read.
func Load(mainFile string, names ...string) (*Config, []*Diag) {
src, err := os.ReadFile(mainFile)
if errors.Is(err, fs.ErrNotExist) {
return nil, []*Diag{{File: mainFile, Msg: "not found; create it with: krino init"}}
}
if err != nil {
return nil, []*Diag{{File: mainFile, Msg: err.Error()}}
}
m, errs := ParseMain(mainFile, src)
cfg := &Config{Main: m}
if _, perr := sexp.Parse(mainFile, src); perr != nil {
// krino.conf itself is unreadable: report just that, and load none
// of the directories, so a syntax error never also produces
// "NAME is not in include" for names the caller asked for.
return cfg, errs
}
want := m.Include
if len(names) > 0 {
want = nil
for _, n := range names {
if _, ok := m.IncludePos[n]; !ok {
errs = append(errs, &Diag{File: mainFile, Msg: fmt.Sprintf("%q is not in include", n)})
continue
}
want = append(want, n)
}
}
for _, name := range want {
file := DirFile(mainFile, name)
src, err := os.ReadFile(file)
if err != nil {
msg := err.Error()
if errors.Is(err, fs.ErrNotExist) {
msg = fmt.Sprintf("included %q, but %s does not exist; create it with: krino new %s PATH", name, file, name)
}
errs = append(errs, &Diag{File: mainFile, Pos: m.IncludePos[name], Msg: msg})
continue
}
dir, derrs := ParseDir(name, file, src)
errs = append(errs, derrs...)
cfg.Dirs = append(cfg.Dirs, dir)
}
return cfg, errs
}
// Resolved is the settings that apply in dir: built-in, then the main
// file's defaults, then the directory's own.
func (c *Config) Resolved(dir *Dir) Resolved {
return dir.Settings.Over(c.Main.Defaults.Over(Builtin()))
}
// LogFile is the main file's (log ...), or the default under XDG_STATE_HOME.
func (c *Config) LogFile() string {
if c.Main.Log != "" {
return c.Main.Log
}
return filepath.Join(xdg.StateHome(), "krino", "krino.log")
}
// LockFile is where a directory's lock lives while a run is active:
// $XDG_STATE_HOME/krino/<name>.lock, beside the log (spec §3).
func (c *Config) LockFile(name string) string {
return filepath.Join(xdg.StateHome(), "krino", name+".lock")
}
|