Add profile repository foundations
This commit is contained in:
@@ -5,12 +5,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"gitea.maximumdirect.net/eric/scriptorium/internal/domain"
|
||||
"gitea.maximumdirect.net/eric/scriptorium/internal/filecatalog"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -30,11 +30,56 @@ func NewFilesystemRepository(dir string) Repository {
|
||||
}
|
||||
|
||||
func (r *filesystemRepository) GetProfile(ctx context.Context, id string) (*domain.ExecutionProfile, error) {
|
||||
return loadProfile(ctx, os.DirFS(r.dir), ".", id)
|
||||
}
|
||||
|
||||
type fsRepository struct {
|
||||
fsys fs.FS
|
||||
root string
|
||||
}
|
||||
|
||||
func NewFSRepository(fsys fs.FS, root string) Repository {
|
||||
return &fsRepository{fsys: fsys, root: root}
|
||||
}
|
||||
|
||||
func (r *fsRepository) GetProfile(ctx context.Context, id string) (*domain.ExecutionProfile, error) {
|
||||
return loadProfile(ctx, r.fsys, r.root, id)
|
||||
}
|
||||
|
||||
type overlayRepository struct {
|
||||
primary Repository
|
||||
fallback Repository
|
||||
}
|
||||
|
||||
func NewOverlayRepository(primary, fallback Repository) Repository {
|
||||
return &overlayRepository{primary: primary, fallback: fallback}
|
||||
}
|
||||
|
||||
func (r *overlayRepository) GetProfile(ctx context.Context, id string) (*domain.ExecutionProfile, error) {
|
||||
if r.primary != nil {
|
||||
prof, err := r.primary.GetProfile(ctx, id)
|
||||
if err == nil {
|
||||
return prof, nil
|
||||
}
|
||||
if !errors.Is(err, ErrProfileNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if r.fallback == nil {
|
||||
return nil, ErrProfileNotFound
|
||||
}
|
||||
return r.fallback.GetProfile(ctx, id)
|
||||
}
|
||||
|
||||
func loadProfile(ctx context.Context, fsys fs.FS, root string, id string) (*domain.ExecutionProfile, error) {
|
||||
if strings.TrimSpace(id) == "" {
|
||||
return nil, fmt.Errorf("%w: profile id is required", ErrInvalidProfile)
|
||||
}
|
||||
if fsys == nil {
|
||||
return nil, fmt.Errorf("failed to read profile directory: filesystem is nil")
|
||||
}
|
||||
|
||||
files, err := filecatalog.FindYAMLFiles(ctx, r.dir)
|
||||
files, err := findProfileYAMLFiles(ctx, fsys, root)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read profile directory: %w", err)
|
||||
}
|
||||
@@ -47,9 +92,9 @@ func (r *filesystemRepository) GetProfile(ctx context.Context, id string) (*doma
|
||||
default:
|
||||
}
|
||||
|
||||
relPath := filecatalog.RelativePath(r.dir, fullPath)
|
||||
fileMatch := filecatalog.Stem(filepath.Base(fullPath)) == id
|
||||
data, err := os.ReadFile(fullPath)
|
||||
relPath := displayPath(root, fullPath)
|
||||
fileMatch := profileFileStem(path.Base(fullPath)) == id
|
||||
data, err := fs.ReadFile(fsys, fullPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read profile file %s: %w", relPath, err)
|
||||
}
|
||||
@@ -102,6 +147,61 @@ func (r *filesystemRepository) GetProfile(ctx context.Context, id string) (*doma
|
||||
return nil, ErrProfileNotFound
|
||||
}
|
||||
|
||||
func findProfileYAMLFiles(ctx context.Context, fsys fs.FS, root string) ([]string, error) {
|
||||
cleanRoot := cleanFSRoot(root)
|
||||
var files []string
|
||||
err := fs.WalkDir(fsys, cleanRoot, func(name string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
if d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
if !isProfileYAMLFile(d.Name()) {
|
||||
return nil
|
||||
}
|
||||
files = append(files, name)
|
||||
return nil
|
||||
})
|
||||
return files, err
|
||||
}
|
||||
|
||||
func cleanFSRoot(root string) string {
|
||||
root = strings.TrimSpace(root)
|
||||
if root == "" || root == "." {
|
||||
return "."
|
||||
}
|
||||
return path.Clean(root)
|
||||
}
|
||||
|
||||
func displayPath(root string, name string) string {
|
||||
cleanRoot := cleanFSRoot(root)
|
||||
cleanName := path.Clean(name)
|
||||
if cleanRoot == "." {
|
||||
return cleanName
|
||||
}
|
||||
prefix := strings.TrimSuffix(cleanRoot, "/") + "/"
|
||||
if strings.HasPrefix(cleanName, prefix) {
|
||||
return strings.TrimPrefix(cleanName, prefix)
|
||||
}
|
||||
return cleanName
|
||||
}
|
||||
|
||||
func profileFileStem(name string) string {
|
||||
name = strings.TrimSuffix(name, ".yaml")
|
||||
name = strings.TrimSuffix(name, ".yml")
|
||||
return name
|
||||
}
|
||||
|
||||
func isProfileYAMLFile(name string) bool {
|
||||
return strings.HasSuffix(name, ".yaml") || strings.HasSuffix(name, ".yml")
|
||||
}
|
||||
|
||||
type profileMatch struct {
|
||||
profile *domain.ExecutionProfile
|
||||
path string
|
||||
|
||||
Reference in New Issue
Block a user