Skip to content

Commit

Permalink
feat: goconfig merge files <src_path> <dest_path>
Browse files Browse the repository at this point in the history
NOTE: upgrade from implicit gopkg.in/yaml.v2 to
            explicit gopkg.in/yaml.v3 to fix
            go-yaml/yaml#139
  • Loading branch information
davidalpert committed Jan 19, 2023
1 parent 37a7698 commit 7a651f3
Show file tree
Hide file tree
Showing 8 changed files with 549 additions and 0 deletions.
51 changes: 51 additions & 0 deletions features/goconfig/merge_files.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
Feature: merge files <src_path> <dest_path>

this command can be used to merge two files together and the
result is written to standard out and formatted based on the
output flags (defaulting to yaml)

NOTE: the keys in yaml output are sorted

Background:
Given I have installed "goconfig" locally into the path
And a file named "config/app1/default.yaml" with:
"""
log_level: WARN
env: REQUIRED
auth:
username: default_user
password: default_pass
"""
And a file named "config/app1/dev.yaml" with:
"""
log_level: DEBUG
env: dev
auth:
password: dev_pass
"""

# @announce-stdout
Scenario: yaml merge to yaml output sorts keys
When I successfully run `goconfig merge files config/app1/dev.yaml config/app1/default.yaml -o yaml`
Then the stdout should contain:
"""
auth:
password: dev_pass
username: default_user
env: dev
log_level: DEBUG
"""

Scenario: yaml merge to json output sorts keys
When I successfully run `goconfig merge files config/app1/dev.yaml config/app1/default.yaml -o json`
Then the stdout should contain:
"""
{
"auth": {
"password": "dev_pass",
"username": "default_user"
},
"env": "dev",
"log_level": "DEBUG"
}
"""
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ require (
github.com/onsi/gomega v1.24.2 // indirect
github.com/sirupsen/logrus v1.8.1
github.com/smartystreets/goconvey v1.7.2 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cobra v1.6.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
371 changes: 371 additions & 0 deletions go.sum

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions internal/app/fs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package app

import "github.com/spf13/afero"

var Fs = afero.NewOsFs()
19 changes: 19 additions & 0 deletions internal/cmd/merge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package cmd

import (
"github.com/davidalpert/go-printers/v1"
"github.com/spf13/cobra"
)

func NewCmdMerge(ioStreams printers.IOStreams) *cobra.Command {
var cmd = &cobra.Command{
Use: "merge",
Aliases: []string{"m"},
Short: "merge subcommands",
Args: cobra.NoArgs,
}

cmd.AddCommand(NewCmdMergeFiles(ioStreams))

return cmd
}
93 changes: 93 additions & 0 deletions internal/cmd/merge_files.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package cmd

import (
"fmt"
"github.com/davidalpert/go-deep-merge/internal/app"
v1 "github.com/davidalpert/go-deep-merge/v1"
"github.com/davidalpert/go-printers/v1"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)

type MergeFilesOptions struct {
*printers.PrinterOptions
Source []byte
Destination []byte
Debug bool
}

func NewMergeFilesOptions(ioStreams printers.IOStreams) *MergeFilesOptions {
return &MergeFilesOptions{
PrinterOptions: printers.NewPrinterOptions().WithStreams(ioStreams).WithDefaultOutput("text"),
}
}

func NewCmdMergeFiles(ioStreams printers.IOStreams) *cobra.Command {
o := NewMergeFilesOptions(ioStreams)
var cmd = &cobra.Command{
Use: "files <src_file> <dest_file>",
Short: "merge two config files together",
Aliases: []string{"f", "fs", "file"},
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
if err := o.Complete(cmd, args); err != nil {
return err
}
if err := o.Validate(); err != nil {
return err
}
return o.Run()
},
}

o.PrinterOptions.AddPrinterFlags(cmd.Flags())
cmd.Flags().BoolVarP(&o.Debug, "debug", "d", false, "enable debug output")

return cmd
}

// Complete the options
func (o *MergeFilesOptions) Complete(cmd *cobra.Command, args []string) error {
if b, err := afero.ReadFile(app.Fs, args[0]); err != nil {
return fmt.Errorf("reading %#v: %#v", args[0], err)
} else {
o.Source = b
}
if b, err := afero.ReadFile(app.Fs, args[1]); err != nil {
return fmt.Errorf("reading %#v: %#v", args[1], err)
} else {
o.Destination = b
}
return nil
}

// Validate the options
func (o *MergeFilesOptions) Validate() error {
return o.PrinterOptions.Validate()
}

// Run the command
func (o *MergeFilesOptions) Run() error {
var src, dest map[string]interface{}
if err := yaml.Unmarshal(o.Source, &src); err != nil {
return fmt.Errorf("unmarshalling src: %#v", err)
}

if err := yaml.Unmarshal(o.Destination, &dest); err != nil {
return fmt.Errorf("unmarshalling dest: %#v", err)
}

//fmt.Fprintln(o.Out, "source:")
//o.WriteOutput(src)
//fmt.Fprintln(o.Out, "dest:")
//o.WriteOutput(dest)
//fmt.Fprintln(o.Out, "result:")

r, err := v1.MergeWithOptions(src, dest, v1.NewConfigDeeperMergeBang().WithMergeHashArrays(true).WithDebug(o.Debug))
if err != nil {
return fmt.Errorf("merging files: %#v", err)
}

return o.WriteOutput(r)
}
1 change: 1 addition & 0 deletions internal/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func NewRootCmd(ioStreams printers.IOStreams) *cobra.Command {

// Register subcommands
rootCmd.AddCommand(NewCmdVersion(ioStreams))
rootCmd.AddCommand(NewCmdMerge(ioStreams))

rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", fmt.Sprintf("config file (default is $HOME/.%s/config.yaml)", version.Detail.AppName))
rootCmd.PersistentFlags().BoolP("verbose", "v", false, "enable verbose output")
Expand Down
7 changes: 7 additions & 0 deletions v1/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,13 @@ func TestMergeHashArrays(t *testing.T) {
opt: NewConfigDeeperMergeBang(),
want: `{"item" => ""}`,
},
{
name: `Merging config hashes`,
src: `{"log_level": "DEBUG", "env": "dev", "auth": { "password": "dev_pass" } }`,
dest: `{"log_level": "WARN", "env": "REQUIRED", "auth": { "username": "default_user", "password": "default_pass" } }`,
opt: NewConfigDeeperMergeBang().WithMergeHashArrays(true),
want: `{"log_level": "DEBUG", "env": "dev", "auth": { "username": "default_user", "password": "dev_pass" } }`,
},
}

for i, tt := range tests {
Expand Down

0 comments on commit 7a651f3

Please sign in to comment.