cerrado @ cb6060a60d71ce1be1591bb10f499916155160de

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