1package config
2
3import (
4 "errors"
5 "fmt"
6 "io"
7 "os"
8 "path"
9 "path/filepath"
10 "strconv"
11
12 "git.gabrielgio.me/cerrado/pkg/u"
13 "git.sr.ht/~emersion/go-scfg"
14)
15
16var (
17 ErrScanPath = errors.New("Scan path does not exist")
18 ErrRepoPath = errors.New("Repository path does not exist")
19 ErrInvalidProperty = errors.New("Invalid property")
20)
21
22type (
23
24 // scan represents piece of the scan from the configuration file.
25 scan struct {
26 Path string
27 Public bool
28 }
29
30 // configuration represents file configuration.
31 // fields needs to be exported to cmp to work
32 configuration struct {
33 Scan *scan
34 RootReadme string
35 ListenAddr string
36 Passphrase string
37 SyntaxHighlight string
38 AESKey string
39 Repositories []*GitRepositoryConfiguration
40 }
41
42 // This is a per repository configuration.
43 GitRepositoryConfiguration struct {
44 Name string
45 Path string
46 Description string
47 Public bool
48 About string
49 }
50
51 // ConfigurationRepository represents the configuration repository (as in
52 // database repositories).
53 // This holds all the function necessary to ask for configuration
54 // information.
55 ConfigurationRepository struct {
56 rootReadme string
57 listenAddr string
58 passphrase []byte
59 aesKey []byte
60 syntaxHighlight string
61 repositories []*GitRepositoryConfiguration
62 }
63)
64
65func LoadConfigurationRepository(configPath string) (*ConfigurationRepository, error) {
66 f, err := os.Open(configPath)
67 if err != nil {
68 return nil, err
69 }
70
71 config, err := parse(f)
72 if err != nil {
73 return nil, err
74 }
75
76 repo := &ConfigurationRepository{
77 aesKey: []byte(config.AESKey),
78 listenAddr: config.ListenAddr,
79 passphrase: []byte(config.Passphrase),
80 repositories: config.Repositories,
81 rootReadme: config.RootReadme,
82 syntaxHighlight: config.SyntaxHighlight,
83 }
84
85 if config.Scan.Path != "" {
86 err = repo.expandOnScanPath(config.Scan.Path, config.Scan.Public)
87 if err != nil {
88 return nil, err
89 }
90 }
91
92 return repo, nil
93}
94
95// GetRootReadme returns root read path
96func (c *ConfigurationRepository) GetRootReadme() string {
97 return c.rootReadme
98}
99
100func (c *ConfigurationRepository) GetSyntaxHighlight() string {
101 return c.syntaxHighlight
102}
103
104func (c *ConfigurationRepository) GetListenAddr() string {
105 return c.listenAddr
106}
107
108func (c *ConfigurationRepository) GetPassphrase() []byte {
109 return c.passphrase
110}
111
112func (c *ConfigurationRepository) GetBase64AesKey() []byte {
113 return c.aesKey
114}
115
116func (c *ConfigurationRepository) IsAuthEnabled() bool {
117 return len(c.passphrase) != 0
118}
119
120// GetByName returns configuration of repository for a given name.
121// It returns nil if there is not match for it.
122func (c *ConfigurationRepository) GetByName(name string) *GitRepositoryConfiguration {
123 for _, r := range c.repositories {
124 if r.Name == name {
125 return r
126 }
127 }
128 return nil
129}
130
131// List returns all the configuration for all repositories.
132func (c *ConfigurationRepository) List() []*GitRepositoryConfiguration {
133 return c.repositories
134}
135
136// expandOnScanPath scans the scanPath for folders taking them as repositories
137// and applying them default configuration.
138func (c *ConfigurationRepository) expandOnScanPath(scanPath string, public bool) error {
139 if !u.FileExist(scanPath) {
140 return ErrScanPath
141 }
142
143 entries, err := os.ReadDir(scanPath)
144 if err != nil {
145 return err
146 }
147
148 for _, e := range entries {
149 if !e.IsDir() {
150 continue
151 }
152
153 fullPath := path.Join(scanPath, e.Name())
154 if !c.repoExits(fullPath) {
155 c.repositories = append(c.repositories, &GitRepositoryConfiguration{
156 Name: e.Name(),
157 Path: fullPath,
158 Public: public,
159 })
160 }
161 }
162 return nil
163}
164
165func (c *ConfigurationRepository) repoExits(path string) bool {
166 for _, r := range c.repositories {
167 if path == r.Path {
168 return true
169 }
170 }
171 return false
172}
173
174func parse(r io.Reader) (*configuration, error) {
175 block, err := scfg.Read(r)
176 if err != nil {
177 return nil, err
178 }
179
180 config := defaultConfiguration()
181
182 err = setScan(block, config.Scan)
183 if err != nil {
184 return nil, err
185 }
186
187 err = setRootReadme(block, &config.RootReadme)
188 if err != nil {
189 return nil, err
190 }
191
192 err = setListenAddr(block, &config.ListenAddr)
193 if err != nil {
194 return nil, err
195 }
196
197 err = setPassphrase(block, &config.Passphrase)
198 if err != nil {
199 return nil, err
200 }
201
202 err = setAESKey(block, &config.AESKey)
203 if err != nil {
204 return nil, err
205 }
206
207 err = setSyntaxHighlight(block, &config.SyntaxHighlight)
208 if err != nil {
209 return nil, err
210 }
211
212 err = setRepositories(block, &config.Repositories)
213 if err != nil {
214 return nil, err
215 }
216
217 return config, nil
218}
219
220func setRepositories(block scfg.Block, repositories *[]*GitRepositoryConfiguration) error {
221 blocks := block.GetAll("repository")
222
223 for _, r := range blocks {
224 if len(r.Params) != 1 {
225 return fmt.Errorf(
226 "Invlid number of params for repository: %w",
227 ErrInvalidProperty,
228 )
229 }
230
231 path := u.FirstOrZero(r.Params)
232 repository := defaultRepisotryConfiguration(path)
233
234 for _, d := range r.Children {
235 // under repository there is only single param properties
236 if len(d.Params) != 1 {
237 return fmt.Errorf(
238 "Invlid number of params for %s: %w",
239 d.Name,
240 ErrInvalidProperty,
241 )
242 }
243
244 switch d.Name {
245 case "name":
246 if err := setString(d, &repository.Name); err != nil {
247 return err
248 }
249 case "description":
250 if err := setString(d, &repository.Description); err != nil {
251 return err
252 }
253 case "public":
254 if err := setBool(d, &repository.Public); err != nil {
255 return err
256 }
257 case "about":
258 if err := setString(d, &repository.About); err != nil {
259 return err
260 }
261 }
262 }
263
264 *repositories = append(*repositories, repository)
265 }
266
267 return nil
268}
269
270func defaultConfiguration() *configuration {
271 return &configuration{
272 Scan: defaultScan(),
273 RootReadme: "",
274 ListenAddr: defaultAddr(),
275 Repositories: make([]*GitRepositoryConfiguration, 0),
276 }
277}
278
279func defaultScan() *scan {
280 return &scan{
281 Public: false,
282 Path: "",
283 }
284}
285
286func defaultAddr() string {
287 return "tcp://localhost:8080"
288}
289
290func defaultRepisotryConfiguration(path string) *GitRepositoryConfiguration {
291 return &GitRepositoryConfiguration{
292 Path: path,
293 Name: filepath.Base(path),
294 Description: "",
295 Public: false,
296 About: "README.md",
297 }
298}
299
300func setRootReadme(block scfg.Block, readme *string) error {
301 scanDir := block.Get("root-readme")
302 return setString(scanDir, readme)
303}
304
305func setPassphrase(block scfg.Block, listenAddr *string) error {
306 scanDir := block.Get("passphrase")
307 return setString(scanDir, listenAddr)
308}
309
310func setAESKey(block scfg.Block, listenAddr *string) error {
311 scanDir := block.Get("aes-key")
312 return setString(scanDir, listenAddr)
313}
314
315func setSyntaxHighlight(block scfg.Block, listenAddr *string) error {
316 scanDir := block.Get("syntax-highlight")
317 return setString(scanDir, listenAddr)
318}
319
320func setListenAddr(block scfg.Block, listenAddr *string) error {
321 scanDir := block.Get("listen-addr")
322 return setString(scanDir, listenAddr)
323}
324
325func setScan(block scfg.Block, scan *scan) error {
326 scanDir := block.Get("scan")
327 if scanDir == nil {
328 return nil
329 }
330 err := setString(scanDir, &scan.Path)
331 if err != nil {
332 return err
333 }
334
335 public := scanDir.Children.Get("public")
336 return setBool(public, &scan.Public)
337}
338
339func setBool(dir *scfg.Directive, field *bool) error {
340 if dir != nil {
341
342 p1, _ := u.First(dir.Params)
343 v, err := strconv.ParseBool(p1)
344 if err != nil {
345 return fmt.Errorf("Error parsing bool param of %s: %w", dir.Name, err)
346 }
347 *field = v
348 }
349 return nil
350}
351
352func setString(dir *scfg.Directive, field *string) error {
353 if dir != nil {
354 *field = u.FirstOrZero(dir.Params)
355 }
356 return nil
357}