-
Notifications
You must be signed in to change notification settings - Fork 0
/
themr.go
261 lines (214 loc) · 6.07 KB
/
themr.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
package main
import (
"errors"
"fmt"
"os"
"os/user"
"path/filepath"
"strings"
"sync"
"themr/config"
"github.com/charmbracelet/log"
"github.com/hellflame/argparse"
"gopkg.in/yaml.v3"
)
var (
logger log.Logger
debug *bool
)
const VERSION = "0.2.3"
func init() {
logger = log.New(log.WithTimeFormat(""))
}
func main() {
parser := argparse.NewParser("themr", "Set a theme in multiple programs by replacing strings in their config files.", &argparse.ParserConfig{DisableDefaultShowHelp: true})
chosen_theme_name := parser.String("", "theme", &argparse.Option{Positional: true})
list_configs_flag := parser.Flag("c", "list-configs", &argparse.Option{Help: "list supported configs"})
list_themes_flag := parser.Flag("l", "list-themes", &argparse.Option{Help: "list supported themes"})
print_version := parser.Flag("v", "version", &argparse.Option{Help: "print version"})
debug = parser.Flag("d", "debug", &argparse.Option{Help: "print debug messages"})
if e := parser.Parse(nil); e != nil {
// check if empty error, happens with -h flag
if msg := e.Error(); msg != "" {
logger.Error(e.Error())
}
os.Exit(1)
}
if *debug {
logger.SetLevel(log.DebugLevel)
}
config.SetLogger(logger)
if *print_version {
fmt.Println(os.Args[0], "v" + VERSION)
os.Exit(0)
}
// get config path
config_dir, err := os.UserConfigDir()
config_dir += "/themr/"
if err != nil {
logger.Error("Could not determine User Config Directory. (is $HOME unset?)")
os.Exit(1)
}
if *debug {
logger.Debug(config_dir)
}
configs, err := config.Load_configs(config_dir)
if err != nil {
logger.Error(err.Error())
os.Exit(1)
}
// valid config types is the set resulting from
// the union of config names and their types
config_types := make(set)
var str_configs string
for _, config := range configs {
str_configs += config.Name + "\n"
config_types[config.Name] = member
if config_type := config.Type; config_type != "" {
config_types[config_type] = member
}
}
logger.Debug(fmt.Sprintf("loaded %d configs", len(configs)), "names", str_configs)
// load themes
themes, err := load_themes(config_dir, config_types)
if err != nil {
logger.Error(err.Error())
os.Exit(1)
}
var str_themes string
for _, theme := range themes {
str_themes += theme["name"] + "\n"
}
logger.Debug(fmt.Sprintf("loaded %d themes", len(themes)), "names", str_themes)
if *list_configs_flag {
list_configs(configs)
os.Exit(0)
}
if *list_themes_flag {
list_themes(themes)
os.Exit(0)
}
if *chosen_theme_name == "" {
logger.Error("No theme name given")
os.Exit(1)
}
var chosen_theme theme_info
for _, theme := range themes {
if theme["name"] == *chosen_theme_name {
chosen_theme = theme
}
}
if chosen_theme == nil {
logger.Error("No such theme exists")
os.Exit(1)
}
chosen_theme.set(configs)
}
func (theme theme_info) set(all_configs []config.Config) {
// only keep configs with appropriate types
var configs []config.Config
for _, conf := range all_configs {
if theme.Map().contains_key(conf.Type) {
configs = append(configs, conf)
}
}
if *debug {
for _, config := range configs {
theme.set_for(config)
}
return
}
var wg sync.WaitGroup
for _, conf := range configs {
wg.Add(1)
go func(theme theme_info, conf config.Config) {
defer wg.Done()
theme.set_for(conf)
}(theme, conf)
}
wg.Wait()
}
func (theme theme_info) set_for(config config.Config) {
path := config.Path
if strings.HasPrefix(path, "~") {
usr, _ := user.Current()
path = filepath.Join(usr.HomeDir, path[2:])
}
// use theme name for the type of config
theme_name := theme[config.Type]
// unless it's overwitten by a theme specifying a theme_name for a config
if name, exists := theme[config.Name]; exists {
theme_name = name
}
// warn if directory of config does not exist
_, err := os.Stat(filepath.Dir(path))
if err != nil {
if errors.Is(err, os.ErrNotExist) {
logger.Warn("Path to config does not exist.\n\tMaybe you forgot to stow something?")
} else {
logger.Error("Stat() config dir: " + err.Error())
}
}
file, err := os.ReadFile(path)
// if the config tells use to create the file if it doesn't exist
// we just place the "Replace" string in the "file"'s contents
// and write those back
if err != nil {
if !config.Create {
logger.Error("Can't read file: " + err.Error())
return
}
file = []byte(config.Replace)
}
if !config.Regex.Match(file) {
logger.Error("Configuration: Regex `" + config.Regex.String() + "` failed to match in file: " + config.Path)
return
}
line := strings.ReplaceAll(config.Replace, "{}", theme_name)
new_contents := config.Regex.ReplaceAll(file, []byte(line))
// create path to config, incase iy does not exist
// err = os.MkdirAll(filepath.Dir(path), os.FileMode(0700))
// if err != nil {
// logger.Error("Can't create directory path to config: " + err.Error())
// }
// write back the file :)
err = os.WriteFile(path, new_contents, os.FileMode(0664))
if err != nil {
logger.Error("Can't save: " + err.Error())
}
err = config.RunCmd(theme_name, *debug)
if err != nil {
logger.Warn(fmt.Errorf("Command for "+config.Name+" failed: %w", err).Error())
}
}
func list_themes(themes []theme_info) {
fmt.Println("Found themes:")
for _, theme := range themes {
fmt.Println("\t" + theme["name"])
}
}
func list_configs(configs []config.Config) {
fmt.Println("Found configs:")
for _, config := range configs {
fmt.Println("\t" + config.Name)
}
}
func load_themes(config_dir string, config_types set) ([]theme_info, error) {
theme_path := config_dir + "themes.yaml"
themes := make(map[string]theme_info)
file, err := os.ReadFile(theme_path)
if err != nil {
return nil, err
}
yaml.Unmarshal(file, &themes)
var themes_list []theme_info
for theme_name, theme := range themes {
theme["name"] = theme_name
// check if theme contains at least one config type
if !theme.Map().contains_at_least_one_key(config_types) {
return nil, fmt.Errorf("Theme must have at least one config type: '" + theme["name"] + "' does not!")
}
themes_list = append(themes_list, theme)
}
return themes_list, err
}