// 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") }