all repos

viye @ 0fa0360

my shot at reimplementing xiki

viye/internal/config/config.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
config: support comments, 1 month ago
1
package config
2
3
import (
4
	"bytes"
5
	"errors"
6
	"maps"
7
	"os"
8
	"path/filepath"
9
	"slices"
10
	"strings"
11
12
	_ "embed"
13
)
14
15
var (
16
	ErrConfigFileNotFound = errors.New("config file not found")
17
	ErrInvalidConfig      = errors.New("config file is invalid")
18
)
19
20
//go:embed default.conf
21
var defaultConfig []byte
22
23
type Config struct {
24
	cfgPath string
25
	cfg     map[string]string
26
}
27
28
func Load() (*Config, error) {
29
	cfgDir, err := os.UserConfigDir()
30
	if err != nil {
31
		return nil, err
32
	}
33
34
	cfgPath := filepath.Join(cfgDir, "viye.conf")
35
	c := Config{cfgPath: cfgPath}
36
37
	// write default config
38
	if _, serr := os.Stat(cfgPath); serr != nil {
39
		if !os.IsNotExist(serr) {
40
			return nil, serr
41
		}
42
		if cerr := os.WriteFile(cfgPath, defaultConfig, 0o644); cerr != nil {
43
			return nil, cerr
44
		}
45
		c.cfg = make(map[string]string)
46
		if cerr := c.read(defaultConfig); cerr != nil {
47
			return nil, cerr
48
		}
49
		return &c, nil
50
	}
51
52
	file, err := os.ReadFile(cfgPath)
53
	if err != nil {
54
		return nil, err
55
	}
56
57
	c.cfg = make(map[string]string)
58
	if err := c.read(file); err != nil {
59
		return nil, err
60
	}
61
62
	return &c, nil
63
}
64
65
func (c *Config) GetAll() map[string]string { return c.cfg }
66
func (c *Config) Get(key string) (val string, ok bool) {
67
	val, ok = c.cfg[key]
68
	return val, ok
69
}
70
71
func (c *Config) Set(key, value string) error {
72
	c.cfg[key] = value
73
	return c.save()
74
}
75
76
func (c *Config) Delete(key string) error {
77
	delete(c.cfg, key)
78
	return c.save()
79
}
80
81
func (c *Config) read(inp []byte) error {
82
	for line := range strings.SplitSeq(string(inp), "\n") {
83
		if line == "" || strings.HasPrefix(line, "#") {
84
			continue
85
		}
86
		parts := strings.Split(line, " = ")
87
		if len(parts) < 2 || len(parts) > 2 {
88
			return ErrInvalidConfig // TODO: improve error reporting
89
		}
90
		key := strings.TrimSpace(parts[0])
91
		if strings.Contains(key, " ") {
92
			return ErrInvalidConfig
93
		}
94
		c.cfg[key] = strings.TrimSpace(parts[1])
95
	}
96
	return nil
97
}
98
99
func (c *Config) save() error {
100
	var buf bytes.Buffer
101
	for _, key := range slices.Sorted(maps.Keys(c.cfg)) {
102
		val := c.cfg[key]
103
		_, _ = buf.WriteString(key)
104
		_, _ = buf.WriteString(" = ")
105
		_, _ = buf.WriteString(val)
106
		_ = buf.WriteByte('\n')
107
	}
108
	err := os.WriteFile(c.cfgPath, buf.Bytes(), 0o644)
109
	return err
110
}