diff --git a/CHANGELOG.md b/CHANGELOG.md index c871e56d..ab07c577 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,13 @@ ## master (unreleased) +- feat: allow dumping with JSON ([#485](https://github.com/evilmartians/lefthook/pull/485) by @mrexox + ## 1.4.0 (2023-05-18) -- feat: add adaptive colors ([#482](https://github.com/evilmartians/lefthook/pull/482)) -- fix: skip output for interactive commands if configured ([#483](https://github.com/evilmartians/lefthook/pull/483)) -- feat: add dump command ([#481](https://github.com/evilmartians/lefthook/pull/481)) +- feat: add adaptive colors ([#482](https://github.com/evilmartians/lefthook/pull/482)) by @mrexox +- fix: skip output for interactive commands if configured ([#483](https://github.com/evilmartians/lefthook/pull/483)) by @mrexox +- feat: add dump command ([#481](https://github.com/evilmartians/lefthook/pull/481)) by @mrexox ## 1.3.13 (2023-05-11) diff --git a/cmd/dump.go b/cmd/dump.go index 2bf28af6..e4ada4e4 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -7,14 +7,20 @@ import ( ) func newDumpCmd(opts *lefthook.Options) *cobra.Command { + dumpArgs := lefthook.DumpArgs{} dumpCmd := cobra.Command{ Use: "dump", - Short: "Prints config merged from all extensions", + Short: "Prints config merged from all extensions (in YAML format by default)", Example: "lefthook dump", - Run: func(cmd *cobra.Command, hooks []string) { - lefthook.Dump(opts) + Run: func(cmd *cobra.Command, args []string) { + lefthook.Dump(opts, dumpArgs) }, } + dumpCmd.Flags().BoolVarP( + &dumpArgs.JSON, "json", "j", false, + "dump in JSON format", + ) + return &dumpCmd } diff --git a/internal/config/command.go b/internal/config/command.go index e578a81b..1ab87750 100644 --- a/internal/config/command.go +++ b/internal/config/command.go @@ -12,21 +12,21 @@ import ( var errFilesIncompatible = errors.New("One of your runners contains incompatible file types") type Command struct { - Run string `mapstructure:"run" yaml:",omitempty"` + Run string `mapstructure:"run" yaml:",omitempty" json:"run,omitempty"` - Skip interface{} `mapstructure:"skip" yaml:",omitempty"` - Only interface{} `mapstructure:"only" yaml:",omitempty"` - Tags []string `mapstructure:"tags" yaml:",omitempty"` - Glob string `mapstructure:"glob" yaml:",omitempty"` - Files string `mapstructure:"files" yaml:",omitempty"` - Env map[string]string `mapstructure:"env" yaml:",omitempty"` + Skip interface{} `mapstructure:"skip" yaml:",omitempty" json:"skip,omitempty"` + Only interface{} `mapstructure:"only" yaml:",omitempty" json:"only,omitempty"` + Tags []string `mapstructure:"tags" yaml:",omitempty" json:"tags,omitempty"` + Glob string `mapstructure:"glob" yaml:",omitempty" json:"glob,omitempty"` + Files string `mapstructure:"files" yaml:",omitempty" json:"files,omitempty"` + Env map[string]string `mapstructure:"env" yaml:",omitempty" json:"env,omitempty"` - Root string `mapstructure:"root" yaml:",omitempty"` - Exclude string `mapstructure:"exclude" yaml:",omitempty"` + Root string `mapstructure:"root" yaml:",omitempty" json:"root,omitempty"` + Exclude string `mapstructure:"exclude" yaml:",omitempty" json:"exclude,omitempty"` - FailText string `mapstructure:"fail_text" yaml:"fail_text,omitempty"` - Interactive bool `mapstructure:"interactive" yaml:",omitempty"` - StageFixed bool `mapstructure:"stage_fixed" yaml:"stage_fixed,omitempty"` + FailText string `mapstructure:"fail_text" yaml:"fail_text,omitempty" json:"fail_text,omitempty"` + Interactive bool `mapstructure:"interactive" yaml:",omitempty" json:"interactive,omitempty"` + StageFixed bool `mapstructure:"stage_fixed" yaml:"stage_fixed,omitempty" json:"stage_fixed,omitempty"` } func (c Command) Validate() error { diff --git a/internal/config/config.go b/internal/config/config.go index 122060f1..b039900c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,34 +1,44 @@ package config import ( + "encoding/json" "os" "gopkg.in/yaml.v3" + "github.com/evilmartians/lefthook/internal/log" "github.com/evilmartians/lefthook/internal/version" ) const dumpIndent = 2 type Config struct { - Colors interface{} `mapstructure:"colors" yaml:",omitempty"` - Extends []string `mapstructure:"extends" yaml:",omitempty"` - Remote Remote `mapstructure:"remote" yaml:",omitempty"` - MinVersion string `mapstructure:"min_version" yaml:"min_version,omitempty"` - SkipOutput []string `mapstructure:"skip_output" yaml:"skip_output,omitempty"` - SourceDir string `mapstructure:"source_dir" yaml:"source_dir,omitempty"` - SourceDirLocal string `mapstructure:"source_dir_local" yaml:"source_dir_local,omitempty"` - Rc string `mapstructure:"rc" yaml:",omitempty"` - NoTTY bool `mapstructure:"no_tty" yaml:"no_tty,omitempty"` - - Hooks map[string]*Hook + Colors interface{} `mapstructure:"colors" yaml:"colors,omitempty" json:"colors,omitempty"` + Extends []string `mapstructure:"extends" yaml:"extends,omitempty" json:"extends,omitempty"` + Remote Remote `mapstructure:"remote" yaml:"remote,omitempty" json:"remote,omitempty"` + MinVersion string `mapstructure:"min_version" yaml:"min_version,omitempty" json:"min_version,omitempty"` + SkipOutput []string `mapstructure:"skip_output" yaml:"skip_output,omitempty" json:"skip_output,omitempty"` + SourceDir string `mapstructure:"source_dir" yaml:"source_dir,omitempty" json:"source_dir,omitempty"` + SourceDirLocal string `mapstructure:"source_dir_local" yaml:"source_dir_local,omitempty" json:"source_dir_local,omitempty"` + Rc string `mapstructure:"rc" yaml:"rc,omitempty" json:"rc,omitempty"` + NoTTY bool `mapstructure:"no_tty" yaml:"no_tty,omitempty" json:"no_tty,omitempty"` + + Hooks map[string]*Hook `yaml:",inline" json:"-"` } func (c *Config) Validate() error { return version.CheckCovered(c.MinVersion) } -func (c *Config) Dump() error { +func (c *Config) Dump(asJSON bool) error { + if asJSON { + return c.dumpJSON() + } + + return c.dumpYAML() +} + +func (c *Config) dumpYAML() error { encoder := yaml.NewEncoder(os.Stdout) encoder.SetIndent(dumpIndent) defer encoder.Close() @@ -40,3 +50,35 @@ func (c *Config) Dump() error { return nil } + +func (c *Config) dumpJSON() error { + // This hack allows to inline Hooks + type ConfigForMarshalling *Config + res, err := json.Marshal(ConfigForMarshalling(c)) + if err != nil { + return err + } + + var rawMarshalled map[string]json.RawMessage + if err = json.Unmarshal(res, &rawMarshalled); err != nil { + return err + } + + for hook, contents := range c.Hooks { + var hookMarshalled json.RawMessage + hookMarshalled, err = json.Marshal(contents) + if err != nil { + return err + } + rawMarshalled[hook] = hookMarshalled + } + + res, err = json.MarshalIndent(rawMarshalled, "", " ") + if err != nil { + return err + } + + log.Info(string(res)) + + return nil +} diff --git a/internal/config/hook.go b/internal/config/hook.go index 4c1dd296..449cc1b9 100644 --- a/internal/config/hook.go +++ b/internal/config/hook.go @@ -18,20 +18,20 @@ type Hook struct { // Should be unmarshalled with `mapstructure:"commands"` // But replacing '{cmd}' is still an issue // Unmarshaling it manually, so omit auto unmarshaling - Commands map[string]*Command `mapstructure:"?" yaml:",omitempty"` + Commands map[string]*Command `mapstructure:"?" yaml:",omitempty" json:"commands,omitempty"` // Should be unmarshalled with `mapstructure:"scripts"` // But parsing keys with dots in it is still an issue: https://github.com/spf13/viper/issues/324 // Unmarshaling it manually, so omit auto unmarshaling - Scripts map[string]*Script `mapstructure:"?" yaml:",omitempty"` - - Files string `mapstructure:"files" yaml:",omitempty"` - Parallel bool `mapstructure:"parallel" yaml:",omitempty"` - Piped bool `mapstructure:"piped" yaml:",omitempty"` - ExcludeTags []string `mapstructure:"exclude_tags" yaml:"exclude_tags,omitempty"` - Skip interface{} `mapstructure:"skip" yaml:",omitempty"` - Only interface{} `mapstructure:"only" yaml:",omitempty"` - Follow bool `mapstructure:"follow" yaml:",omitempty"` + Scripts map[string]*Script `mapstructure:"?" yaml:",omitempty" json:"scripts,omitempty"` + + Files string `mapstructure:"files" yaml:",omitempty" json:"files,omitempty"` + Parallel bool `mapstructure:"parallel" yaml:",omitempty" json:"parallel,omitempty"` + Piped bool `mapstructure:"piped" yaml:",omitempty" json:"piped,omitempty"` + ExcludeTags []string `mapstructure:"exclude_tags" yaml:"exclude_tags,omitempty" json:"exclude_tags,omitempty"` + Skip interface{} `mapstructure:"skip" yaml:",omitempty" json:"skip,omitempty"` + Only interface{} `mapstructure:"only" yaml:",omitempty" json:"only,omitempty"` + Follow bool `mapstructure:"follow" yaml:",omitempty" json:"follow,omitempty"` } func (h *Hook) Validate() error { diff --git a/internal/config/remote.go b/internal/config/remote.go index 525c59e3..b7c4acb8 100644 --- a/internal/config/remote.go +++ b/internal/config/remote.go @@ -1,9 +1,9 @@ package config type Remote struct { - GitURL string `mapstructure:"git_url"` - Ref string `mapstructure:"ref"` - Config string `mapstructure:"config"` + GitURL string `mapstructure:"git_url" yaml:"git_url,omitempty" json:"git_url,omitempty"` + Ref string `mapstructure:"ref" yaml:",omitempty" json:"ref,omitempty"` + Config string `mapstructure:"config" yaml:",omitempty" json:"config,omitempty"` } func (r Remote) Configured() bool { diff --git a/internal/config/script.go b/internal/config/script.go index 10a0218d..71f9d998 100644 --- a/internal/config/script.go +++ b/internal/config/script.go @@ -10,16 +10,16 @@ import ( ) type Script struct { - Runner string `mapstructure:"runner" yaml:",omitempty"` + Runner string `mapstructure:"runner" yaml:",omitempty" json:"runner,omitempty"` - Skip interface{} `mapstructure:"skip" yaml:",omitempty"` - Only interface{} `mapstructure:"only" yaml:",omitempty"` - Tags []string `mapstructure:"tags" yaml:",omitempty"` - Env map[string]string `mapstructure:"env" yaml:",omitempty"` + Skip interface{} `mapstructure:"skip" yaml:",omitempty" json:"skip,omitempty"` + Only interface{} `mapstructure:"only" yaml:",omitempty" json:"only,omitempty"` + Tags []string `mapstructure:"tags" yaml:",omitempty" json:"tags,omitempty"` + Env map[string]string `mapstructure:"env" yaml:",omitempty" json:"env,omitempty"` - FailText string `mapstructure:"fail_text" yaml:"fail_text,omitempty"` - Interactive bool `mapstructure:"interactive" yaml:",omitempty"` - StageFixed bool `mapstructure:"stage_fixed" yaml:"stage_fixed,omitempty"` + FailText string `mapstructure:"fail_text" yaml:"fail_text,omitempty" json:"fail_text,omitempty"` + Interactive bool `mapstructure:"interactive" yaml:",omitempty" json:"interactive,omitempty"` + StageFixed bool `mapstructure:"stage_fixed" yaml:"stage_fixed,omitempty" json:"stage_fixed,omitempty"` } func (s Script) DoSkip(gitState git.State) bool { diff --git a/internal/lefthook/dump.go b/internal/lefthook/dump.go index e6f4927c..260b1f7e 100644 --- a/internal/lefthook/dump.go +++ b/internal/lefthook/dump.go @@ -5,7 +5,11 @@ import ( "github.com/evilmartians/lefthook/internal/log" ) -func Dump(opts *Options) { +type DumpArgs struct { + JSON bool +} + +func Dump(opts *Options, args DumpArgs) { lefthook, err := initialize(opts) if err != nil { log.Errorf("couldn't initialize lefthook: %s\n", err) @@ -18,7 +22,7 @@ func Dump(opts *Options) { return } - if err := cfg.Dump(); err != nil { + if err := cfg.Dump(args.JSON); err != nil { log.Errorf("couldn't dump config: %s\n", err) return }