cerrado @ e1664fcbc4685906d3dabc66bf947a17bce7efc0

  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	ScanPathErr        = errors.New("Scan path does not exist")
 18	RepoPathErr        = errors.New("Repository path does not exist")
 19	InvalidPropertyErr = 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		Repositories []*GitRepositoryConfiguration
 37	}
 38
 39	// This is a per repository configuration.
 40	GitRepositoryConfiguration struct {
 41		Name        string
 42		Path        string
 43		Description string
 44		Public      bool
 45		About       string
 46	}
 47
 48	// ConfigurationRepository represents the configuration repository (as in
 49	// database repositories).
 50	// This holds all the function necessary to ask for configuration
 51	// information.
 52	ConfigurationRepository struct {
 53		rootReadme   string
 54		listenAddr   string
 55		repositories []*GitRepositoryConfiguration
 56	}
 57)
 58
 59func LoadConfigurationRepository(configPath string) (*ConfigurationRepository, error) {
 60	f, err := os.Open(configPath)
 61	if err != nil {
 62		return nil, err
 63	}
 64
 65	config, err := parse(f)
 66	if err != nil {
 67		return nil, err
 68	}
 69
 70	repo := &ConfigurationRepository{
 71		rootReadme:   config.RootReadme,
 72		listenAddr:   config.ListenAddr,
 73		repositories: config.Repositories,
 74	}
 75
 76	if config.Scan.Path != "" {
 77		err = repo.expandOnScanPath(config.Scan.Path, config.Scan.Public)
 78		if err != nil {
 79			return nil, err
 80		}
 81	}
 82
 83	return repo, nil
 84
 85}
 86
 87// GetRootReadme returns root read path
 88func (c *ConfigurationRepository) GetRootReadme() string {
 89	return c.rootReadme
 90}
 91
 92func (c *ConfigurationRepository) GetListenAddr() string {
 93	return c.listenAddr
 94}
 95
 96// GetByName returns configuration of repository for a given name.
 97// It returns nil if there is not match for it.
 98func (c *ConfigurationRepository) GetByName(name string) *GitRepositoryConfiguration {
 99	for _, r := range c.repositories {
100		if r.Name == name {
101			return r
102		}
103	}
104	return nil
105}
106
107// List returns all the configuration for all repositories.
108func (c *ConfigurationRepository) List() []*GitRepositoryConfiguration {
109	return c.repositories
110}
111
112// expandOnScanPath scans the scanPath for folders taking them as repositories
113// and applying them default configuration.
114func (c *ConfigurationRepository) expandOnScanPath(scanPath string, public bool) error {
115	if !u.FileExist(scanPath) {
116		return ScanPathErr
117	}
118
119	entries, err := os.ReadDir(scanPath)
120	if err != nil {
121		return err
122	}
123
124	for _, e := range entries {
125		if !e.IsDir() {
126			continue
127		}
128
129		fullPath := path.Join(scanPath, e.Name())
130		if !c.repoExits(fullPath) {
131			c.repositories = append(c.repositories, &GitRepositoryConfiguration{
132				Name:   e.Name(),
133				Path:   fullPath,
134				Public: public,
135			})
136		}
137	}
138	return nil
139}
140
141func (c *ConfigurationRepository) repoExits(path string) bool {
142	for _, r := range c.repositories {
143		if path == r.Path {
144			return true
145		}
146	}
147	return false
148}
149
150func parse(r io.Reader) (*configuration, error) {
151	block, err := scfg.Read(r)
152	if err != nil {
153		return nil, err
154	}
155
156	config := defaultConfiguration()
157
158	err = setScan(block, config.Scan)
159	if err != nil {
160		return nil, err
161	}
162
163	err = setRootReadme(block, &config.RootReadme)
164	if err != nil {
165		return nil, err
166	}
167
168	err = setListenAddr(block, &config.ListenAddr)
169	if err != nil {
170		return nil, err
171	}
172
173	err = setRepositories(block, &config.Repositories)
174	if err != nil {
175		return nil, err
176	}
177
178	return config, nil
179}
180
181func setRepositories(block scfg.Block, repositories *[]*GitRepositoryConfiguration) error {
182	blocks := block.GetAll("repository")
183
184	for _, r := range blocks {
185		if len(r.Params) != 1 {
186			return fmt.Errorf(
187				"Invlid number of params for repository: %w",
188				InvalidPropertyErr,
189			)
190		}
191
192		path := u.FirstOrZero(r.Params)
193		repository := defaultRepisotryConfiguration(path)
194
195		for _, d := range r.Children {
196			// under repository there is only single param properties
197			if len(d.Params) != 1 {
198				return fmt.Errorf(
199					"Invlid number of params for %s: %w",
200					d.Name,
201					InvalidPropertyErr,
202				)
203			}
204
205			switch d.Name {
206			case "name":
207				if err := setString(d, &repository.Name); err != nil {
208					return err
209				}
210			case "description":
211				if err := setString(d, &repository.Description); err != nil {
212					return err
213				}
214			case "public":
215				if err := setBool(d, &repository.Public); err != nil {
216					return err
217				}
218			case "about":
219				if err := setString(d, &repository.About); err != nil {
220					return err
221				}
222			}
223		}
224
225		*repositories = append(*repositories, repository)
226	}
227
228	return nil
229}
230
231func defaultConfiguration() *configuration {
232	return &configuration{
233		Scan:         defaultScan(),
234		RootReadme:   "",
235		ListenAddr:   defaultAddr(),
236		Repositories: make([]*GitRepositoryConfiguration, 0),
237	}
238}
239
240func defaultScan() *scan {
241	return &scan{
242		Public: false,
243		Path:   "",
244	}
245}
246
247func defaultAddr() string {
248	return "tcp://localhost:8080"
249}
250
251func defaultRepisotryConfiguration(path string) *GitRepositoryConfiguration {
252	return &GitRepositoryConfiguration{
253		Path:        path,
254		Name:        filepath.Base(path),
255		Description: "",
256		Public:      false,
257		About:       "README.md",
258	}
259}
260
261func setRootReadme(block scfg.Block, readme *string) error {
262	scanDir := block.Get("root-readme")
263	return setString(scanDir, readme)
264}
265
266func setListenAddr(block scfg.Block, listenAddr *string) error {
267	scanDir := block.Get("listen-addr")
268	return setString(scanDir, listenAddr)
269}
270
271func setScan(block scfg.Block, scan *scan) error {
272	scanDir := block.Get("scan")
273	if scanDir == nil {
274		return nil
275	}
276	err := setString(scanDir, &scan.Path)
277	if err != nil {
278		return err
279	}
280
281	public := scanDir.Children.Get("public")
282	return setBool(public, &scan.Public)
283}
284
285func setBool(dir *scfg.Directive, field *bool) error {
286	if dir != nil {
287
288		p1, _ := u.First(dir.Params)
289		v, err := strconv.ParseBool(p1)
290		if err != nil {
291			return fmt.Errorf("Error parsing bool param of %s: %w", dir.Name, err)
292		}
293		*field = v
294	}
295	return nil
296}
297
298func setString(dir *scfg.Directive, field *string) error {
299	if dir != nil {
300		*field = u.FirstOrZero(dir.Params)
301	}
302	return nil
303}