/
cfg.go
191 lines (160 loc) · 5.38 KB
/
cfg.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
package cfg
import (
"fmt"
"os"
"path"
"github.com/BurntSushi/toml"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
// configFile used to parse the config file
type configFile struct {
// ConfigVersion - never remove this
ConfigVersion *string
// added in legacy
Port *int
SesswatchPeriodSeconds *uint
SesshistInitHistorySize *int
BindControlR *bool
Debug *bool
// added in v1
LogLevel *string
// added in legacy
// deprecated in v1
BindArrowKeysBash *bool
BindArrowKeysZsh *bool
}
// Config returned by this package to be used in the rest of the project
type Config struct {
// Port used by daemon and rest of the components to communicate
// Make sure to restart the daemon when you change it
Port int
// BindControlR causes CTRL+R to launch the search app
BindControlR bool
// LogLevel used to filter logs
LogLevel zapcore.Level
// Debug mode for search app
Debug bool
// SessionWatchPeriodSeconds is how often should daemon check if terminal
// sessions are still alive
// There is not much need to adjust the value because both memory overhead of watched sessions
// and the CPU overhead of checking them are quite low
SessionWatchPeriodSeconds uint
// ReshHistoryMinSize is how large resh history needs to be for
// daemon to ignore standard shell history files
// Ignoring standard shell history gives us more consistent experience
// but you can increase this to something large to see standard shell history in RESH search
ReshHistoryMinSize int
}
// defaults for config
var defaults = Config{
Port: 2627,
LogLevel: zap.InfoLevel,
BindControlR: true,
Debug: false,
SessionWatchPeriodSeconds: 600,
ReshHistoryMinSize: 1000,
}
const headerComment = `##
######################
## RESH config (v1) ##
######################
## Here you can find info about RESH configuration options.
## You can uncomment the options and customize them.
## Required.
## The config format can change in future versions.
## ConfigVersion helps us seamlessly upgrade to the new formats.
# ConfigVersion = "v1"
## Port used by RESH daemon and rest of the components to communicate.
## Make sure to restart the daemon (pkill resh-daemon) when you change it.
# Port = 2627
## Controls how much and how detailed logs all RESH components produce.
## Use "debug" for full logs when you encounter an issue
## Options: "debug", "info", "warn", "error", "fatal"
# LogLevel = "info"
## When BindControlR is "true" RESH search app is bound to CTRL+R on terminal startup
# BindControlR = true
## When Debug is "true" the RESH search app runs in debug mode.
## This is useful for development.
# Debug = false
## Daemon keeps track of running terminal sessions.
## SessionWatchPeriodSeconds controls how often daemon checks if the sessions are still alive.
## You shouldn't need to adjust this.
# SessionWatchPeriodSeconds = 600
## When RESH is first installed there is no RESH history so there is nothing to search.
## As a temporary workaround, RESH daemon parses bash/zsh shell history and searches it.
## Once RESH history is big enough RESH stops using bash/zsh history.
## ReshHistoryMinSize controls how big RESH history needs to be before this happens.
## You can increase this this to e.g. 10000 to get RESH to use bash/zsh history longer.
# ReshHistoryMinSize = 1000
`
func getConfigPath() (string, error) {
fname := "resh.toml"
xdgDir, found := os.LookupEnv("XDG_CONFIG_HOME")
if found {
return path.Join(xdgDir, fname), nil
}
homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("could not get user home dir: %w", err)
}
return path.Join(homeDir, ".config", fname), nil
}
func readConfig(path string) (*configFile, error) {
var config configFile
if _, err := toml.DecodeFile(path, &config); err != nil {
return &config, fmt.Errorf("could not decode config: %w", err)
}
return &config, nil
}
func getConfig() (*configFile, error) {
path, err := getConfigPath()
if err != nil {
return nil, fmt.Errorf("could not get config file path: %w", err)
}
return readConfig(path)
}
// returned config is always usable, returned errors are informative
func processAndFillDefaults(configF *configFile) (Config, error) {
config := defaults
if configF.Port != nil {
config.Port = *configF.Port
}
if configF.SesswatchPeriodSeconds != nil {
config.SessionWatchPeriodSeconds = *configF.SesswatchPeriodSeconds
}
if configF.SesshistInitHistorySize != nil {
config.ReshHistoryMinSize = *configF.SesshistInitHistorySize
}
var err error
if configF.LogLevel != nil {
logLevel, err := zapcore.ParseLevel(*configF.LogLevel)
if err != nil {
err = fmt.Errorf("could not parse log level: %w", err)
} else {
config.LogLevel = logLevel
}
}
if configF.BindControlR != nil {
config.BindControlR = *configF.BindControlR
}
return config, err
}
// New returns a config file
// returned config is always usable, returned errors are informative
func New() (Config, error) {
configF, err := getConfig()
if err != nil {
return defaults, fmt.Errorf("using default config because of error while getting/reading config: %w", err)
}
config, err := processAndFillDefaults(configF)
if err != nil {
return config, fmt.Errorf("errors while processing config: %w", err)
}
return config, nil
}
// GetPath returns path to config
// Shouldn't be necessary for basic use
func GetPath() (string, error) {
return getConfigPath()
}