cerrado @ 57efc8d2173fdff1ef99157a2633d3f1d366a290

  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
116// GetByName returns configuration of repository for a given name.
117// It returns nil if there is not match for it.
118func (c *ConfigurationRepository) GetByName(name string) *GitRepositoryConfiguration {
119	for _, r := range c.repositories {
120		if r.Name == name {
121			return r
122		}
123	}
124	return nil
125}
126
127// List returns all the configuration for all repositories.
128func (c *ConfigurationRepository) List() []*GitRepositoryConfiguration {
129	return c.repositories
130}
131
132// expandOnScanPath scans the scanPath for folders taking them as repositories
133// and applying them default configuration.
134func (c *ConfigurationRepository) expandOnScanPath(scanPath string, public bool) error {
135	if !u.FileExist(scanPath) {
136		return ErrScanPath
137	}
138
139	entries, err := os.ReadDir(scanPath)
140	if err != nil {
141		return err
142	}
143
144	for _, e := range entries {
145		if !e.IsDir() {
146			continue
147		}
148
149		fullPath := path.Join(scanPath, e.Name())
150		if !c.repoExits(fullPath) {
151			c.repositories = append(c.repositories, &GitRepositoryConfiguration{
152				Name:   e.Name(),
153				Path:   fullPath,
154				Public: public,
155			})
156		}
157	}
158	return nil
159}
160
161func (c *ConfigurationRepository) repoExits(path string) bool {
162	for _, r := range c.repositories {
163		if path == r.Path {
164			return true
165		}
166	}
167	return false
168}
169
170func parse(r io.Reader) (*configuration, error) {
171	block, err := scfg.Read(r)
172	if err != nil {
173		return nil, err
174	}
175
176	config := defaultConfiguration()
177
178	err = setScan(block, config.Scan)
179	if err != nil {
180		return nil, err
181	}
182
183	err = setRootReadme(block, &config.RootReadme)
184	if err != nil {
185		return nil, err
186	}
187
188	err = setListenAddr(block, &config.ListenAddr)
189	if err != nil {
190		return nil, err
191	}
192
193	err = setPassphrase(block, &config.Passphrase)
194	if err != nil {
195		return nil, err
196	}
197
198	err = setAESKey(block, &config.AESKey)
199	if err != nil {
200		return nil, err
201	}
202
203	err = setSyntaxHighlight(block, &config.SyntaxHighlight)
204	if err != nil {
205		return nil, err
206	}
207
208	err = setRepositories(block, &config.Repositories)
209	if err != nil {
210		return nil, err
211	}
212
213	return config, nil
214}
215
216func setRepositories(block scfg.Block, repositories *[]*GitRepositoryConfiguration) error {
217	blocks := block.GetAll("repository")
218
219	for _, r := range blocks {
220		if len(r.Params) != 1 {
221			return fmt.Errorf(
222				"Invlid number of params for repository: %w",
223				ErrInvalidProperty,
224			)
225		}
226
227		path := u.FirstOrZero(r.Params)
228		repository := defaultRepisotryConfiguration(path)
229
230		for _, d := range r.Children {
231			// under repository there is only single param properties
232			if len(d.Params) != 1 {
233				return fmt.Errorf(
234					"Invlid number of params for %s: %w",
235					d.Name,
236					ErrInvalidProperty,
237				)
238			}
239
240			switch d.Name {
241			case "name":
242				if err := setString(d, &repository.Name); err != nil {
243					return err
244				}
245			case "description":
246				if err := setString(d, &repository.Description); err != nil {
247					return err
248				}
249			case "public":
250				if err := setBool(d, &repository.Public); err != nil {
251					return err
252				}
253			case "about":
254				if err := setString(d, &repository.About); err != nil {
255					return err
256				}
257			}
258		}
259
260		*repositories = append(*repositories, repository)
261	}
262
263	return nil
264}
265
266func defaultConfiguration() *configuration {
267	return &configuration{
268		Scan:         defaultScan(),
269		RootReadme:   "",
270		ListenAddr:   defaultAddr(),
271		Repositories: make([]*GitRepositoryConfiguration, 0),
272	}
273}
274
275func defaultScan() *scan {
276	return &scan{
277		Public: false,
278		Path:   "",
279	}
280}
281
282func defaultAddr() string {
283	return "tcp://localhost:8080"
284}
285
286func defaultRepisotryConfiguration(path string) *GitRepositoryConfiguration {
287	return &GitRepositoryConfiguration{
288		Path:        path,
289		Name:        filepath.Base(path),
290		Description: "",
291		Public:      false,
292		About:       "README.md",
293	}
294}
295
296func setRootReadme(block scfg.Block, readme *string) error {
297	scanDir := block.Get("root-readme")
298	return setString(scanDir, readme)
299}
300
301func setPassphrase(block scfg.Block, listenAddr *string) error {
302	scanDir := block.Get("passphrase")
303	return setString(scanDir, listenAddr)
304}
305
306func setAESKey(block scfg.Block, listenAddr *string) error {
307	scanDir := block.Get("aes-key")
308	return setString(scanDir, listenAddr)
309}
310
311func setSyntaxHighlight(block scfg.Block, listenAddr *string) error {
312	scanDir := block.Get("syntax-highlight")
313	return setString(scanDir, listenAddr)
314}
315
316func setListenAddr(block scfg.Block, listenAddr *string) error {
317	scanDir := block.Get("listen-addr")
318	return setString(scanDir, listenAddr)
319}
320
321func setScan(block scfg.Block, scan *scan) error {
322	scanDir := block.Get("scan")
323	if scanDir == nil {
324		return nil
325	}
326	err := setString(scanDir, &scan.Path)
327	if err != nil {
328		return err
329	}
330
331	public := scanDir.Children.Get("public")
332	return setBool(public, &scan.Public)
333}
334
335func setBool(dir *scfg.Directive, field *bool) error {
336	if dir != nil {
337
338		p1, _ := u.First(dir.Params)
339		v, err := strconv.ParseBool(p1)
340		if err != nil {
341			return fmt.Errorf("Error parsing bool param of %s: %w", dir.Name, err)
342		}
343		*field = v
344	}
345	return nil
346}
347
348func setString(dir *scfg.Directive, field *string) error {
349	if dir != nil {
350		*field = u.FirstOrZero(dir.Params)
351	}
352	return nil
353}