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