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