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}