From 02614b3781f6acdfc6df0e7b07d856b2779c4ac7 Mon Sep 17 00:00:00 2001 From: "Gabriel A. Giovanini" Date: Sun, 9 Jun 2024 19:35:34 +0200 Subject: feat: Per repository configuration --- config.example.scfg | 12 ++--- pkg/config/config.go | 134 ++++++++++++++++++++++++++++++++++++++-------- pkg/config/config_test.go | 82 ++++++++++++++++++++++++++-- pkg/service/git.go | 19 ++++--- pkg/u/list.go | 8 +++ pkg/u/list_test.go | 30 +++++++++++ templates/base.qtpl | 2 +- templates/base.qtpl.go | 2 +- 8 files changed, 243 insertions(+), 46 deletions(-) diff --git a/config.example.scfg b/config.example.scfg index 1e3180f..3961e51 100644 --- a/config.example.scfg +++ b/config.example.scfg @@ -3,6 +3,12 @@ scan /srv/git/ { public true } +repository /srv/git/cerrado.git { + name cerrado + description "Self host single person forge" + public true +} + # TBD #user admin:iKlvHe1g0UoXE # @@ -15,9 +21,3 @@ scan /srv/git/ { # default false #} # -#repository cerrado { -# title Cerrado -# description "Self host single person readonly forge" -# list main -# public true -#} diff --git a/pkg/config/config.go b/pkg/config/config.go index 419d49d..3e539f7 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -6,6 +6,7 @@ import ( "io" "os" "path" + "path/filepath" "strconv" "git.gabrielgio.me/cerrado/pkg/u" @@ -13,8 +14,9 @@ import ( ) var ( - ScanPathErr = errors.New("Scan path does not exist") - RepoPathErr = errors.New("Repository path does not exist") + ScanPathErr = errors.New("Scan path does not exist") + RepoPathErr = errors.New("Repository path does not exist") + InvalidPropertyErr = errors.New("Invalid property") ) type ( @@ -26,16 +28,19 @@ type ( } // configuration represents file configuration. + // fields needs to be exported to cmp to work configuration struct { - Scan *scan - RootReadme string + Scan *scan + RootReadme string + Repositories []*GitRepositoryConfiguration } // This is a per repository configuration. GitRepositoryConfiguration struct { - Name string - Path string - Public bool + Name string + Path string + Description string + Public bool } // ConfigurationRepository represents the configuration repository (as in @@ -60,13 +65,17 @@ func LoadConfigurationRepository(configPath string) (*ConfigurationRepository, e } repo := &ConfigurationRepository{ - rootReadme: config.RootReadme, + rootReadme: config.RootReadme, + repositories: config.Repositories, } - err = repo.expandOnScanPath(config.Scan.Path, config.Scan.Public) - if err != nil { - return nil, err + if config.Scan.Path != "" { + err = repo.expandOnScanPath(config.Scan.Path, config.Scan.Public) + if err != nil { + return nil, err + } } + return repo, nil } @@ -104,22 +113,32 @@ func (c *ConfigurationRepository) expandOnScanPath(scanPath string, public bool) return err } - c.repositories = make([]*GitRepositoryConfiguration, 0) for _, e := range entries { if !e.IsDir() { continue } fullPath := path.Join(scanPath, e.Name()) - c.repositories = append(c.repositories, &GitRepositoryConfiguration{ - Name: e.Name(), - Path: fullPath, - Public: public, - }) + 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 { @@ -138,16 +157,82 @@ func parse(r io.Reader) (*configuration, error) { 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", + InvalidPropertyErr, + ) + } + + 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, + InvalidPropertyErr, + ) + } + + 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 + } + } + } + + *repositories = append(*repositories, repository) + } + + return nil +} + func defaultConfiguration() *configuration { return &configuration{ - Scan: &scan{ - Public: true, - Path: "", - }, - RootReadme: "", + Scan: defaultScan(), + RootReadme: "", + Repositories: make([]*GitRepositoryConfiguration, 0), + } +} + +func defaultScan() *scan { + return &scan{ + Public: false, + Path: "", + } + +} + +func defaultRepisotryConfiguration(path string) *GitRepositoryConfiguration { + return &GitRepositoryConfiguration{ + Path: path, + Name: filepath.Base(path), + Description: "", + Public: false, } } @@ -158,6 +243,9 @@ func setRootReadme(block scfg.Block, readme *string) error { 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 @@ -182,7 +270,7 @@ func setBool(dir *scfg.Directive, field *bool) error { func setString(dir *scfg.Directive, field *string) error { if dir != nil { - *field, _ = u.First(dir.Params) + *field = u.FirstOrZero(dir.Params) } return nil } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 7afbaef..9109ecb 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -19,21 +19,94 @@ func TestFileParsing(t *testing.T) { config: `scan "/srv/git"`, expectedConfig: &configuration{ Scan: &scan{ - Public: true, + Public: false, Path: "/srv/git", }, + Repositories: []*GitRepositoryConfiguration{}, }, }, { name: "complete scan", - config: `scan "/srv/git" { - public false + config: ` +scan "/srv/git" { + public true }`, expectedConfig: &configuration{ Scan: &scan{ - Public: false, + Public: true, Path: "/srv/git", }, + Repositories: []*GitRepositoryConfiguration{}, + }, + }, + { + name: "minimal repository", + config: `repository /srv/git/cerrado.git`, + expectedConfig: &configuration{ + Scan: defaultScan(), + Repositories: []*GitRepositoryConfiguration{ + { + Name: "cerrado.git", + Path: "/srv/git/cerrado.git", + Description: "", + Public: false, + }, + }, + }, + }, + { + name: "complete repository", + config: ` +repository /srv/git/cerrado.git { + name cerrado + description "Single person forge" + public true +}`, + expectedConfig: &configuration{ + Scan: defaultScan(), + Repositories: []*GitRepositoryConfiguration{ + { + Name: "cerrado", + Path: "/srv/git/cerrado.git", + Description: "Single person forge", + Public: true, + }, + }, + }, + }, + { + name: "complete", + config: ` +scan "/srv/git" { + public true +} + +repository /srv/git/linux.git + +repository /srv/git/cerrado.git { + name cerrado + description "Single person forge" + public true +}`, + expectedConfig: &configuration{ + Scan: &scan{ + Public: true, + Path: "/srv/git", + }, + Repositories: []*GitRepositoryConfiguration{ + { + Name: "linux.git", + Path: "/srv/git/linux.git", + Description: "", + Public: false, + }, + { + Name: "cerrado", + Path: "/srv/git/cerrado.git", + Description: "Single person forge", + Public: true, + }, + }, }, }, } @@ -49,7 +122,6 @@ func TestFileParsing(t *testing.T) { if diff := cmp.Diff(tc.expectedConfig, config); diff != "" { t.Errorf("Wrong result given - wanted + got\n %s", diff) } - }) } diff --git a/pkg/service/git.go b/pkg/service/git.go index 94e2adc..7418d97 100644 --- a/pkg/service/git.go +++ b/pkg/service/git.go @@ -16,7 +16,6 @@ import ( type ( Repository struct { Name string - Title string Description string LastCommitDate string Ref string @@ -48,8 +47,8 @@ func NewGitService(configRepo configurationRepository) *GitService { func (g *GitService) ListRepositories() ([]*Repository, error) { rs := g.configRepo.List() - repos := make([]*Repository, len(rs)) - for i, r := range rs { + repos := make([]*Repository, 0, len(rs)) + for _, r := range rs { repo, err := git.OpenRepository(r.Path) if err != nil { return nil, err @@ -57,12 +56,14 @@ func (g *GitService) ListRepositories() ([]*Repository, error) { obj, err := repo.LastCommit() if err != nil { - return nil, err + slog.Error("Error fetching last commit", "repository", r.Path, "error", err) + continue } head, err := repo.Head() if err != nil { - return nil, err + slog.Error("Error fetching head", "repository", r.Path, "error", err) + continue } d := path.Join(r.Path, "description") @@ -75,14 +76,12 @@ func (g *GitService) ListRepositories() ([]*Repository, error) { } } - baseName := path.Base(r.Path) - repos[i] = &Repository{ - Name: baseName, - Title: baseName, + repos = append(repos, &Repository{ + Name: r.Name, Description: description, LastCommitDate: obj.Author.When.Format(timeFormat), Ref: head.Name().Short(), - } + }) } return repos, nil diff --git a/pkg/u/list.go b/pkg/u/list.go index cf71909..7271ef3 100644 --- a/pkg/u/list.go +++ b/pkg/u/list.go @@ -16,6 +16,14 @@ func FirstOrZero[T any](v []T) T { return v[0] } +func Map[T any, V any](ts []T, fun func(T) V) []V { + rs := make([]V, len(ts)) + for i := range ts { + rs[i] = fun(ts[i]) + } + return rs +} + func ChunkBy[T any](items []T, chunkSize int) [][]T { var chunks = make([][]T, 0, (len(items)/chunkSize)+1) for chunkSize < len(items) { diff --git a/pkg/u/list_test.go b/pkg/u/list_test.go index 805a209..3a856b9 100644 --- a/pkg/u/list_test.go +++ b/pkg/u/list_test.go @@ -3,6 +3,7 @@ package u import ( + "strconv" "testing" "github.com/google/go-cmp/cmp" @@ -129,3 +130,32 @@ func TestFirstOrZero(t *testing.T) { }) } } + +func TestMap(t *testing.T) { + testCases := []struct { + name string + in []int + out []string + }{ + { + name: "empty", + in: []int{}, + out: []string{}, + }, + { + name: "not empty", + in: []int{1, 2, 3}, + out: []string{"1", "2", "3"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + out := Map(tc.in, func(v int) string { return strconv.Itoa(v) }) + + if diff := cmp.Diff(tc.out, out); diff != "" { + t.Errorf("Map error:\n%s", diff) + } + }) + } +} diff --git a/templates/base.qtpl b/templates/base.qtpl index 9b0c4f5..ae9f7a6 100644 --- a/templates/base.qtpl +++ b/templates/base.qtpl @@ -42,7 +42,7 @@ Page prints a page implementing Page interface. - cerrado | {%= p.Title() %} + {%= p.Title() %} diff --git a/templates/base.qtpl.go b/templates/base.qtpl.go index d2bcc73..bc40252 100644 --- a/templates/base.qtpl.go +++ b/templates/base.qtpl.go @@ -87,7 +87,7 @@ func StreamPageTemplate(qw422016 *qt422016.Writer, p Page) { - cerrado | `) + <title>`) //line base.qtpl:45 p.StreamTitle(qw422016) //line base.qtpl:45 -- cgit v1.2.3