/
settings.go
138 lines (115 loc) · 2.81 KB
/
settings.go
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package launcher
import (
"fmt"
"os"
"github.com/99designs/keyring"
"github.com/BurntSushi/toml"
"github.com/brawaru/marct/utils/slices"
"github.com/mitchellh/mapstructure"
)
type Versioned struct {
Version int `mapstructure:"version"`
}
type Settings struct {
Versioned `mapstructure:",squash"`
Keyring struct {
Backend *keyring.BackendType `mapstructure:"backend"`
PassCmd string `mapstructure:"pass-cmd"`
PassDir string `mapstructure:"pass-dir"`
} `mapstructure:"keyring"`
}
type SettingsFile struct {
Settings // Actual settings.
fp string // Settings file path.
}
func (s *SettingsFile) Save() error {
f, err := os.Create(s.fp)
if err != nil {
return fmt.Errorf("create %s: %w", s.fp, err)
}
err = toml.NewEncoder(f).Encode(s.Settings)
if err != nil {
return fmt.Errorf("encode %s: %w", s.fp, err)
}
return err
}
type settingsV1 struct {
Versioned `mapstructure:",squash"`
Keyring struct {
BanBackends []keyring.BackendType `mapstructure:"ban-backends"`
PassCmd string `mapstructure:"pass-cmd"`
PassDir string `mapstructure:"pass-dir"`
} `mapstructure:"keyring"`
}
func runMigrations(m map[string]any) (migrated bool, err error) {
initalVersion := -1
for {
var v Versioned
err = mapstructure.Decode(m, &v)
if err != nil {
return
}
if initalVersion == -1 {
initalVersion = v.Version
} else {
migrated = v.Version != initalVersion
}
switch v.Version {
case 0:
fallthrough // 0 = unset, thus 1
case 1:
var f settingsV1
if err = mapstructure.Decode(m, &f); err != nil {
err = fmt.Errorf("cannot decode v1 settings file: %w", err)
return
}
r := &Settings{}
for _, b := range keyring.AvailableBackends() {
if !slices.Includes(f.Keyring.BanBackends, b) {
r.Keyring.Backend = &b
break
}
}
r.Keyring.PassCmd = f.Keyring.PassCmd
r.Keyring.PassDir = f.Keyring.PassDir
r.Version = 2
if err = mapstructure.Decode(r, &m); err != nil {
err = fmt.Errorf("cannot encode v2 settings file: %w", err)
return
}
case 2:
return
default:
err = fmt.Errorf("unknown settings version %d", v.Version)
return
}
}
}
func (s *SettingsFile) Read() (err error) {
m := make(map[string]any)
_, err = toml.DecodeFile(s.fp, &m)
if err != nil {
err = fmt.Errorf("decode %s: %w", s.fp, err)
return
}
migrated, err := runMigrations(m)
if err != nil {
err = fmt.Errorf("migrate %s: %w", s.fp, err)
return
}
if err = mapstructure.Decode(m, &s.Settings); err != nil {
err = fmt.Errorf("map %s: %w", s.fp, err)
return
}
if migrated {
err = s.Save()
if err != nil {
err = fmt.Errorf("save (after migration) %s: %w", s.fp, err)
}
}
return
}
func NewSettings(name string) *SettingsFile {
s := &SettingsFile{fp: name}
return s
}