From 1ad2903102af575560ce5c8a79b1ad1753fa675c Mon Sep 17 00:00:00 2001 From: Valentin Kiselev Date: Mon, 2 Dec 2024 11:50:34 +0300 Subject: [PATCH] fix: add special handling for {cmd} --- internal/config/command.go | 73 -------------------------------- internal/config/load.go | 80 +++++++++++++++++------------------ internal/config/script.go | 85 ++------------------------------------ 3 files changed, 43 insertions(+), 195 deletions(-) diff --git a/internal/config/command.go b/internal/config/command.go index 6693c435..4f51dd5c 100644 --- a/internal/config/command.go +++ b/internal/config/command.go @@ -2,9 +2,6 @@ package config import ( "errors" - "strings" - - "github.com/knadh/koanf/v2" "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/system" @@ -34,10 +31,6 @@ type Command struct { StageFixed bool `json:"stage_fixed,omitempty" koanf:"stage_fixed" mapstructure:"stage_fixed" toml:"stage_fixed,omitempty" yaml:"stage_fixed,omitempty"` } -type commandRunReplace struct { - Run string `mapstructure:"run"` -} - func (c Command) Validate() error { if !isRunnerFilesCompatible(c.Run) { return errFilesIncompatible @@ -54,69 +47,3 @@ func (c Command) DoSkip(state func() git.State) bool { func (c Command) ExecutionPriority() int { return c.Priority } - -func mergeCommands(base, extra *koanf.Koanf) (map[string]*Command, error) { - if base == nil && extra == nil { - return nil, nil - } - - if base == nil { - return unmarshalCommands(extra.Cut("commands")) - } - - if extra == nil { - return unmarshalCommands(base.Cut("commands")) - } - - commandsOrigin := base.Cut("commands") - commandsOverride := extra.Cut("commands") - if commandsOrigin == nil { - return unmarshalCommands(commandsOverride) - } - if commandsOverride == nil { - return unmarshalCommands(commandsOrigin) - } - - runReplaces := make(map[string]*commandRunReplace) - for key := range commandsOrigin.Raw() { - var replace commandRunReplace - - substructure := commandsOrigin.Cut(key) - if substructure == nil { - continue - } - - if err := substructure.Unmarshal("", &replace); err != nil { - return nil, err - } - - runReplaces[key] = &replace - } - - err := commandsOrigin.Merge(commandsOverride) - if err != nil { - return nil, err - } - - commands, err := unmarshalCommands(commandsOrigin) - if err != nil { - return nil, err - } - - for key, replace := range runReplaces { - if replace.Run != "" { - commands[key].Run = strings.ReplaceAll(commands[key].Run, CMD, replace.Run) - } - } - - return commands, nil -} - -func unmarshalCommands(k *koanf.Koanf) (map[string]*Command, error) { - commands := make(map[string]*Command) - if err := k.Unmarshal("", &commands); err != nil { - return nil, err - } - - return commands, nil -} diff --git a/internal/config/load.go b/internal/config/load.go index cf4e6404..1196d216 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -306,9 +306,48 @@ func addHook(name string, main, secondary *koanf.Koanf, c *Config) error { mainHook := main.Cut(name) overrideHook := secondary.Cut(name) + // Special merge func to support merging {cmd} templates options := koanf.WithMergeFunc(func(src, dest map[string]interface{}) error { - // TODO: merge dest to src replacing {cmd} properly + // perf: allocate with len of src commands here + destCommands := make(map[string]string) + + switch commands := dest["commands"].(type) { + case map[string]interface{}: + for cmdName, command := range commands { + switch cmd := command.(type) { + case map[string]interface{}: + switch run := cmd["run"].(type) { + case string: + destCommands[cmdName] = run + default: + } + default: + } + } + default: + } + maps.Merge(src, dest) + + if len(destCommands) > 0 { + switch commands := dest["commands"].(type) { + case map[string]interface{}: + for cmdName, command := range commands { + switch cmd := command.(type) { + case map[string]interface{}: + switch run := cmd["run"].(type) { + case string: + newRun := strings.ReplaceAll(run, CMD, destCommands[cmdName]) + command.(map[string]interface{})["run"] = newRun + default: + } + default: + } + } + default: + } + } + return nil }) @@ -328,45 +367,6 @@ func addHook(name string, main, secondary *koanf.Koanf, c *Config) error { return nil } -// func unmarshalHook(main, override *koanf.Koanf) (*Hook, error) { -// if main == nil && override == nil { -// return nil, nil -// } - -// commands, err := mergeCommands(main, override) -// if err != nil { -// return nil, err -// } - -// scripts, err := mergeScripts(main, override) -// if err != nil { -// return nil, err -// } - -// hook := Hook{ -// Commands: commands, -// Scripts: scripts, -// } - -// if main == nil { -// main = override -// } else if override != nil { -// if err = main.Merge(override); err != nil { -// return nil, err -// } -// } - -// if err := main.Unmarshal("", &hook); err != nil { -// return nil, err -// } - -// if tags := os.Getenv("LEFTHOOK_EXCLUDE"); tags != "" { -// hook.ExcludeTags = append(hook.ExcludeTags, strings.Split(tags, ",")...) -// } - -// return &hook, nil -// } - // Rewritten from afero.NewIOFS to support opening paths starting with '/'. type iofs struct { diff --git a/internal/config/script.go b/internal/config/script.go index ec162ba3..cf7d90ea 100644 --- a/internal/config/script.go +++ b/internal/config/script.go @@ -1,11 +1,6 @@ package config import ( - "strings" - - "github.com/knadh/koanf/v2" - "github.com/mitchellh/mapstructure" - "github.com/evilmartians/lefthook/internal/git" "github.com/evilmartians/lefthook/internal/system" ) @@ -25,10 +20,6 @@ type Script struct { StageFixed bool `json:"stage_fixed,omitempty" mapstructure:"stage_fixed" toml:"stage_fixed,omitempty" yaml:"stage_fixed,omitempty"` } -type scriptRunnerReplace struct { - Runner string `mapstructure:"runner"` -} - func (s Script) DoSkip(state func() git.State) bool { skipChecker := NewSkipChecker(system.Cmd) return skipChecker.check(state, s.Skip, s.Only) @@ -38,76 +29,6 @@ func (s Script) ExecutionPriority() int { return s.Priority } -func mergeScripts(base, extra *koanf.Koanf) (map[string]*Script, error) { - if base == nil && extra == nil { - return nil, nil - } - - if base == nil { - return unmarshalScripts(extra.Cut("scripts").Raw()) - } - - if extra == nil { - return unmarshalScripts(base.Cut("scripts").Raw()) - } - - scriptsOrigin := base.Cut("scripts").Raw() - scriptsOverride := extra.Cut("scripts").Raw() - if scriptsOrigin == nil { - return unmarshalScripts(scriptsOverride) - } - if scriptsOverride == nil { - return unmarshalScripts(scriptsOrigin) - } - - runReplaces := make(map[string]*scriptRunnerReplace) - for key, originConfig := range scriptsOrigin { - var runReplace scriptRunnerReplace - - if err := unmarshal(originConfig, &runReplace); err != nil { - return nil, err - } - - runReplaces[key] = &runReplace - } - - if err := base.Set("scripts", scriptsOverride); err != nil { - return nil, err - } - - scripts, err := unmarshalScripts(base.Cut("scripts").Raw()) - if err != nil { - return nil, err - } - - for key, replace := range runReplaces { - if replace.Runner != "" { - scripts[key].Runner = strings.ReplaceAll(scripts[key].Runner, CMD, replace.Runner) - } - } - - return scripts, nil -} - -func unmarshalScripts(s map[string]interface{}) (map[string]*Script, error) { - if len(s) == 0 { - return nil, nil - } - - scripts := make(map[string]*Script) - for name, scriptConfig := range s { - var script Script - - if err := unmarshal(scriptConfig, &script); err != nil { - return nil, err - } - - scripts[name] = &script - } - - return scripts, nil -} - // `scripts` are unmarshalled manually because viper // uses "." as a key delimiter. So, this definition: // @@ -132,6 +53,6 @@ func unmarshalScripts(s map[string]interface{}) (map[string]*Script, error) { // // This is not an expected behavior and cannot be controlled yet // Working with GetStringMap is the only way to get the structure "as is". -func unmarshal(input, output interface{}) error { - return mapstructure.WeakDecode(input, &output) -} +// func unmarshal(input, output interface{}) error { +// return mapstructure.WeakDecode(input, &output) +// }