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 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 ErrScanPath
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 ErrInvalidProperty,
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 ErrInvalidProperty,
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}