cerrado @ 359499b1058a6b8cd4cc99aadd407e0a050ba4d7

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