aboutsummaryrefslogtreecommitdiff
path: root/internal/config/load.go
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-09-11 14:47:10 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-09-11 15:01:57 +0200
commit42b02c47be9b285099203e44a2570636d4ca6f03 (patch)
tree82bcb9e19bd886f36e1ca7b2d94a204c988f1fd1 /internal/config/load.go
downloadkrino-42b02c47be9b285099203e44a2570636d4ca6f03.tar.gz
krino-42b02c47be9b285099203e44a2570636d4ca6f03.zip
krino: foundation — sexp reader, config language, init/new/check
Diffstat (limited to 'internal/config/load.go')
-rw-r--r--internal/config/load.go93
1 files changed, 93 insertions, 0 deletions
diff --git a/internal/config/load.go b/internal/config/load.go
new file mode 100644
index 0000000..f6eaeae
--- /dev/null
+++ b/internal/config/load.go
@@ -0,0 +1,93 @@
+// 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")
+}