cerrado @ e8265117372cf262ce7cb4f6ce3b61f19373aee6

  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}