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