package config import ( "errors" "fmt" "io" "os" "path" "path/filepath" "strconv" "git.gabrielgio.me/cerrado/pkg/u" "git.sr.ht/~emersion/go-scfg" ) var ( ErrScanPath = errors.New("Scan path does not exist") ErrRepoPath = errors.New("Repository path does not exist") ErrInvalidProperty = errors.New("Invalid property") ) type ( // scan represents piece of the scan from the configuration file. scan struct { Path string Public bool } // configuration represents file configuration. // fields needs to be exported to cmp to work configuration struct { Scan *scan RootReadme string ListenAddr string Passphrase string SyntaxHighlight string AESKey string Repositories []*GitRepositoryConfiguration } // This is a per repository configuration. GitRepositoryConfiguration struct { Name string Path string Description string Public bool About string } // ConfigurationRepository represents the configuration repository (as in // database repositories). // This holds all the function necessary to ask for configuration // information. ConfigurationRepository struct { rootReadme string listenAddr string passphrase []byte aesKey []byte syntaxHighlight string repositories []*GitRepositoryConfiguration } ) func LoadConfigurationRepository(configPath string) (*ConfigurationRepository, error) { f, err := os.Open(configPath) if err != nil { return nil, err } config, err := parse(f) if err != nil { return nil, err } repo := &ConfigurationRepository{ aesKey: []byte(config.AESKey), listenAddr: config.ListenAddr, passphrase: []byte(config.Passphrase), repositories: config.Repositories, rootReadme: config.RootReadme, syntaxHighlight: config.SyntaxHighlight, } if config.Scan.Path != "" { err = repo.expandOnScanPath(config.Scan.Path, config.Scan.Public) if err != nil { return nil, err } } return repo, nil } // GetRootReadme returns root read path func (c *ConfigurationRepository) GetRootReadme() string { return c.rootReadme } func (c *ConfigurationRepository) GetSyntaxHighlight() string { return c.syntaxHighlight } func (c *ConfigurationRepository) GetListenAddr() string { return c.listenAddr } func (c *ConfigurationRepository) GetPassphrase() []byte { return c.passphrase } func (c *ConfigurationRepository) GetBase64AesKey() []byte { return c.aesKey } func (c *ConfigurationRepository) IsAuthEnabled() bool { return len(c.passphrase) != 0 } // GetByName returns configuration of repository for a given name. // It returns nil if there is not match for it. func (c *ConfigurationRepository) GetByName(name string) *GitRepositoryConfiguration { for _, r := range c.repositories { if r.Name == name { return r } } return nil } // List returns all the configuration for all repositories. func (c *ConfigurationRepository) List() []*GitRepositoryConfiguration { return c.repositories } // expandOnScanPath scans the scanPath for folders taking them as repositories // and applying them default configuration. func (c *ConfigurationRepository) expandOnScanPath(scanPath string, public bool) error { if !u.FileExist(scanPath) { return ErrScanPath } entries, err := os.ReadDir(scanPath) if err != nil { return err } for _, e := range entries { if !e.IsDir() { continue } fullPath := path.Join(scanPath, e.Name()) if !c.repoExits(fullPath) { c.repositories = append(c.repositories, &GitRepositoryConfiguration{ Name: e.Name(), Path: fullPath, Public: public, }) } } return nil } func (c *ConfigurationRepository) repoExits(path string) bool { for _, r := range c.repositories { if path == r.Path { return true } } return false } func parse(r io.Reader) (*configuration, error) { block, err := scfg.Read(r) if err != nil { return nil, err } config := defaultConfiguration() err = setScan(block, config.Scan) if err != nil { return nil, err } err = setRootReadme(block, &config.RootReadme) if err != nil { return nil, err } err = setListenAddr(block, &config.ListenAddr) if err != nil { return nil, err } err = setPassphrase(block, &config.Passphrase) if err != nil { return nil, err } err = setAESKey(block, &config.AESKey) if err != nil { return nil, err } err = setSyntaxHighlight(block, &config.SyntaxHighlight) if err != nil { return nil, err } err = setRepositories(block, &config.Repositories) if err != nil { return nil, err } return config, nil } func setRepositories(block scfg.Block, repositories *[]*GitRepositoryConfiguration) error { blocks := block.GetAll("repository") for _, r := range blocks { if len(r.Params) != 1 { return fmt.Errorf( "Invlid number of params for repository: %w", ErrInvalidProperty, ) } path := u.FirstOrZero(r.Params) repository := defaultRepisotryConfiguration(path) for _, d := range r.Children { // under repository there is only single param properties if len(d.Params) != 1 { return fmt.Errorf( "Invlid number of params for %s: %w", d.Name, ErrInvalidProperty, ) } switch d.Name { case "name": if err := setString(d, &repository.Name); err != nil { return err } case "description": if err := setString(d, &repository.Description); err != nil { return err } case "public": if err := setBool(d, &repository.Public); err != nil { return err } case "about": if err := setString(d, &repository.About); err != nil { return err } } } *repositories = append(*repositories, repository) } return nil } func defaultConfiguration() *configuration { return &configuration{ Scan: defaultScan(), RootReadme: "", ListenAddr: defaultAddr(), Repositories: make([]*GitRepositoryConfiguration, 0), } } func defaultScan() *scan { return &scan{ Public: false, Path: "", } } func defaultAddr() string { return "tcp://localhost:8080" } func defaultRepisotryConfiguration(path string) *GitRepositoryConfiguration { return &GitRepositoryConfiguration{ Path: path, Name: filepath.Base(path), Description: "", Public: false, About: "README.md", } } func setRootReadme(block scfg.Block, readme *string) error { scanDir := block.Get("root-readme") return setString(scanDir, readme) } func setPassphrase(block scfg.Block, listenAddr *string) error { scanDir := block.Get("passphrase") return setString(scanDir, listenAddr) } func setAESKey(block scfg.Block, listenAddr *string) error { scanDir := block.Get("aes-key") return setString(scanDir, listenAddr) } func setSyntaxHighlight(block scfg.Block, listenAddr *string) error { scanDir := block.Get("syntax-highlight") return setString(scanDir, listenAddr) } func setListenAddr(block scfg.Block, listenAddr *string) error { scanDir := block.Get("listen-addr") return setString(scanDir, listenAddr) } func setScan(block scfg.Block, scan *scan) error { scanDir := block.Get("scan") if scanDir == nil { return nil } err := setString(scanDir, &scan.Path) if err != nil { return err } public := scanDir.Children.Get("public") return setBool(public, &scan.Public) } func setBool(dir *scfg.Directive, field *bool) error { if dir != nil { p1, _ := u.First(dir.Params) v, err := strconv.ParseBool(p1) if err != nil { return fmt.Errorf("Error parsing bool param of %s: %w", dir.Name, err) } *field = v } return nil } func setString(dir *scfg.Directive, field *string) error { if dir != nil { *field = u.FirstOrZero(dir.Params) } return nil }