From 38114cf5ce5033dd3b38d9665c2bb1b9cc585848 Mon Sep 17 00:00:00 2001 From: Jaap de Haan <261428+jdehaan@users.noreply.github.com> Date: Sat, 21 Mar 2026 18:09:07 +0000 Subject: [PATCH] refactor: Use slices to simplify code --- .golangci.yml | 1 + backup/internal/check.go | 25 ++++++----------- backup/internal/config.go | 59 +++++++++++++-------------------------- backup/internal/job.go | 24 +++++++--------- 4 files changed, 39 insertions(+), 70 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 9e7e0f4..bbfb0c5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -23,6 +23,7 @@ linters: - os - path/filepath - sort + - slices - strings - testing - time diff --git a/backup/internal/check.go b/backup/internal/check.go index 104599a..a1976d5 100644 --- a/backup/internal/check.go +++ b/backup/internal/check.go @@ -4,7 +4,7 @@ import ( "fmt" "log" "path/filepath" - "sort" + "slices" "strings" "github.com/spf13/afero" @@ -40,20 +40,17 @@ func (c *CoverageChecker) ListUncoveredPaths(cfg Config) []string { c.checkPath(source.Path, cfg, &result, seen) } - sort.Strings(result) // Ensure consistent ordering for test comparison + slices.Sort(result) // Ensure consistent ordering for test comparison return result } func (c *CoverageChecker) isExcluded(path string, job Job) bool { - for _, exclusion := range job.Exclusions { - exclusionPath := filepath.Join(job.Source, exclusion) - if strings.HasPrefix(NormalizePath(path), exclusionPath) { - return true - } - } + normalized := NormalizePath(path) - return false + return slices.ContainsFunc(job.Exclusions, func(exclusion string) bool { + return strings.HasPrefix(normalized, filepath.Join(job.Source, exclusion)) + }) } func (c *CoverageChecker) isCoveredByJob(path string, job Job) bool { @@ -73,13 +70,9 @@ func (c *CoverageChecker) isCoveredByJob(path string, job Job) bool { } func (c *CoverageChecker) isCovered(path string, jobs []Job) bool { - for _, job := range jobs { - if c.isCoveredByJob(path, job) { - return true - } - } - - return false + return slices.ContainsFunc(jobs, func(job Job) bool { + return c.isCoveredByJob(path, job) + }) } func (c *CoverageChecker) checkPath(path string, cfg Config, result *[]string, seen map[string]bool) { diff --git a/backup/internal/config.go b/backup/internal/config.go index 6324c39..bf0eb6a 100644 --- a/backup/internal/config.go +++ b/backup/internal/config.go @@ -7,6 +7,7 @@ import ( "log" "os" "path/filepath" + "slices" "strings" "gopkg.in/yaml.v3" @@ -47,29 +48,22 @@ func (cfg Config) Apply(rsync JobCommand, logger *log.Logger, output io.Writer) logger.Printf("Rsync Version Info: %s", versionInfo) } - var succeeded, failed, skipped int + counts := make(map[JobStatus]int) for _, job := range cfg.Jobs { status := job.Apply(rsync) logger.Printf("STATUS [%s]: %s", job.Name, status) fmt.Fprintf(output, "Status [%s]: %s\n", job.Name, status) - - switch status { - case Success: - succeeded++ - case Failure: - failed++ - case Skipped: - skipped++ - } + counts[status]++ } - summary := fmt.Sprintf("Summary: %d succeeded, %d failed, %d skipped", succeeded, failed, skipped) + summary := fmt.Sprintf("Summary: %d succeeded, %d failed, %d skipped", + counts[Success], counts[Failure], counts[Skipped]) logger.Print(summary) fmt.Fprintln(output, summary) - if failed > 0 { - return fmt.Errorf("%w: %d of %d jobs", ErrJobFailure, failed, len(cfg.Jobs)) + if counts[Failure] > 0 { + return fmt.Errorf("%w: %d of %d jobs", ErrJobFailure, counts[Failure], len(cfg.Jobs)) } return nil @@ -89,12 +83,12 @@ func LoadConfig(reader io.Reader) (Config, error) { } func SubstituteVariables(input string, variables map[string]string) string { + oldnew := make([]string, 0, len(variables)*2) //nolint:mnd // 2 entries per variable: key placeholder + value for key, value := range variables { - placeholder := fmt.Sprintf("${%s}", key) - input = strings.ReplaceAll(input, placeholder, value) + oldnew = append(oldnew, "${"+key+"}", value) } - return input + return strings.NewReplacer(oldnew...).Replace(input) } func ResolveConfig(cfg Config) Config { @@ -108,7 +102,8 @@ func ResolveConfig(cfg Config) Config { } func ValidateJobNames(jobs []Job) error { - invalidNames := []string{} + var invalidNames []string + nameSet := make(map[string]bool) for _, job := range jobs { @@ -118,12 +113,8 @@ func ValidateJobNames(jobs []Job) error { nameSet[job.Name] = true } - for _, r := range job.Name { - if r > 127 || r == ' ' { - invalidNames = append(invalidNames, "invalid characters in job name: "+job.Name) - - break - } + if strings.ContainsFunc(job.Name, func(r rune) bool { return r > 127 || r == ' ' }) { + invalidNames = append(invalidNames, "invalid characters in job name: "+job.Name) } } @@ -135,10 +126,8 @@ func ValidateJobNames(jobs []Job) error { } func ValidatePath(jobPath string, paths []Path, pathType string, jobName string) error { - for _, path := range paths { - if strings.HasPrefix(jobPath, path.Path) { - return nil - } + if slices.ContainsFunc(paths, func(p Path) bool { return strings.HasPrefix(jobPath, p.Path) }) { + return nil } return fmt.Errorf("%w for job '%s': %s %s", ErrInvalidPath, jobName, pathType, jobPath) @@ -172,19 +161,9 @@ func validateJobPaths(jobs []Job, pathType string, getPath func(job Job) string) if i != j { path1, path2 := NormalizePath(getPath(job1)), NormalizePath(getPath(job2)) - // Check if path2 is part of job1's exclusions - excluded := false - - if pathType == "source" { - for _, exclusion := range job2.Exclusions { - exclusionPath := NormalizePath(filepath.Join(job2.Source, exclusion)) - if strings.HasPrefix(path1, exclusionPath) { - excluded = true - - break - } - } - } + excluded := pathType == "source" && slices.ContainsFunc(job2.Exclusions, func(exclusion string) bool { + return strings.HasPrefix(path1, NormalizePath(filepath.Join(job2.Source, exclusion))) + }) if !excluded && strings.HasPrefix(path1, path2) { return fmt.Errorf("%w: job '%s' has a %s path overlapping with job '%s'", diff --git a/backup/internal/job.go b/backup/internal/job.go index b185988..9d7e20c 100644 --- a/backup/internal/job.go +++ b/backup/internal/job.go @@ -36,6 +36,14 @@ func (job Job) Apply(rsync JobCommand) JobStatus { return rsync.Run(job) } +func boolDefault(ptr *bool, defaultVal bool) bool { + if ptr != nil { + return *ptr + } + + return defaultVal +} + // UnmarshalYAML implements custom YAML unmarshaling to handle defaults properly. func (job *Job) UnmarshalYAML(node *yaml.Node) error { var jobYAML JobYAML @@ -45,24 +53,12 @@ func (job *Job) UnmarshalYAML(node *yaml.Node) error { return fmt.Errorf("failed to decode YAML node: %w", err) } - // Copy basic fields job.Name = jobYAML.Name job.Source = jobYAML.Source job.Target = jobYAML.Target job.Exclusions = jobYAML.Exclusions - - // Handle boolean fields with defaults - if jobYAML.Delete != nil { - job.Delete = *jobYAML.Delete - } else { - job.Delete = true // default value - } - - if jobYAML.Enabled != nil { - job.Enabled = *jobYAML.Enabled - } else { - job.Enabled = true // default value - } + job.Delete = boolDefault(jobYAML.Delete, true) + job.Enabled = boolDefault(jobYAML.Enabled, true) return nil }