cerrado @ 5ce7d18d415c6911740ba15e3b4e6a1b94562019

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