From f2fdae62a0c7e9c777f9659da8af695aeea0401a Mon Sep 17 00:00:00 2001 From: David Gamba Date: Mon, 31 Jan 2022 22:18:07 -0700 Subject: [PATCH] yaml-parse,yamlutils: Support multiple documents in a single yaml input https://yaml.org/spec/1.2-old/spec.html#id2800401 --- yaml-parse/go.mod | 2 +- yaml-parse/go.sum | 6 ++-- yaml-parse/main.go | 25 ++++++++++++++--- yamlutils/yamlutils.go | 62 ++++++++++++++++++++++++++---------------- 4 files changed, 62 insertions(+), 33 deletions(-) diff --git a/yaml-parse/go.mod b/yaml-parse/go.mod index d0acb8d..b105446 100644 --- a/yaml-parse/go.mod +++ b/yaml-parse/go.mod @@ -2,7 +2,7 @@ module github.com/DavidGamba/dgtools/yaml-parse require ( github.com/DavidGamba/dgtools/yamlutils v0.0.0 - github.com/DavidGamba/go-getoptions v0.23.0 + github.com/DavidGamba/go-getoptions v0.25.3 ) replace github.com/DavidGamba/dgtools => ../ diff --git a/yaml-parse/go.sum b/yaml-parse/go.sum index c9dcd28..7e69bfb 100644 --- a/yaml-parse/go.sum +++ b/yaml-parse/go.sum @@ -1,7 +1,5 @@ -github.com/DavidGamba/go-getoptions v0.21.0 h1:nVyFTaDSacJ1Sb3kRB4RgxNkGhf+b8khQ0M/ldihwS0= -github.com/DavidGamba/go-getoptions v0.21.0/go.mod h1:wYjd1McJbGzBFD61+lahGR+5A8QGA1aBnRZmfkBLy5A= -github.com/DavidGamba/go-getoptions v0.23.0 h1:j8q36PvconcXzKphnnOmmUcKGpcpHfbaAZ/FD0qwdd0= -github.com/DavidGamba/go-getoptions v0.23.0/go.mod h1:qLaLSYeQ8sUVOfKuu5JT5qKKS3OCwyhkYSJnoG+ggmo= +github.com/DavidGamba/go-getoptions v0.25.3 h1:lSPcMkwWvVZU05C+Uz4DKnKN5wz4bcD1QvJ/QHCRexo= +github.com/DavidGamba/go-getoptions v0.25.3/go.mod h1:qLaLSYeQ8sUVOfKuu5JT5qKKS3OCwyhkYSJnoG+ggmo= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/yaml-parse/main.go b/yaml-parse/main.go index e6ab036..878e75e 100644 --- a/yaml-parse/main.go +++ b/yaml-parse/main.go @@ -33,7 +33,7 @@ func program(args []string) int { opt := getoptions.New() opt.Self("", `Parses YAML input passed from file or piped to STDIN and filters it by key or index. - Source: https://github.com/DavidGamba/dgtools`) + Source: https://github.com/DavidGamba/dgtools`) opt.Bool("help", false, opt.Alias("?")) opt.Bool("debug", false) opt.Bool("version", false, opt.Alias("V")) @@ -46,6 +46,7 @@ func program(args []string) int { opt.Description(`Key or index to descend to. Multiple keys allow to descend further. Indexes are positive integers.`)) + opt.IntOptional("document", 1, opt.Description("Document number"), opt.Alias("d"), opt.ArgName("number")) _, err := opt.Parse(os.Args[1:]) if opt.Called("help") { fmt.Println(opt.Help()) @@ -90,11 +91,11 @@ func realMain(opt *getoptions.GetOpt) error { stdinIsDevice := (statStdin.Mode() & os.ModeDevice) != 0 var err error - var yml *yamlutils.YML + var ymlList []*yamlutils.YML if !stdinIsDevice && !opt.Called("file") { Logger.Printf("Reading from stdin\n") reader := os.Stdin - yml, err = yamlutils.NewFromReader(reader) + ymlList, err = yamlutils.NewFromReader(reader) if err != nil { return fmt.Errorf("reading yaml from STDIN: %w", err) } @@ -103,13 +104,29 @@ func realMain(opt *getoptions.GetOpt) error { if !opt.Called("file") { return fmt.Errorf("missing argument '--file '") } - yml, err = yamlutils.NewFromFile(file) + ymlList, err = yamlutils.NewFromFile(file) if err != nil { fmt.Fprintf(os.Stderr, "ERROR: reading yaml file: %s\n", err) os.Exit(1) } } + if !opt.Called("document") && len(ymlList) > 1 { + fmt.Fprintf(os.Stderr, + `WARNING: provided input contains %d YAML documents + specify '--document ' to remove this warning +`, len(ymlList)) + } + + n := 0 + if opt.Called("document") { + n = opt.Value("document").(int) - 1 + } + if len(ymlList) < n+1 { + return fmt.Errorf("wrong document number: %d", n+1) + } + yml := ymlList[n] + if opt.Called("add") { str, err := yml.AddString(xpath, add) if err != nil { diff --git a/yamlutils/yamlutils.go b/yamlutils/yamlutils.go index bf7f887..20d168c 100644 --- a/yamlutils/yamlutils.go +++ b/yamlutils/yamlutils.go @@ -12,12 +12,12 @@ Package yamlutils - Utilities to read yml files like if using xpath package yamlutils import ( - "bytes" "errors" "fmt" "io" "io/ioutil" "log" + "os" "strconv" "strings" @@ -39,33 +39,47 @@ type YML struct { Tree interface{} } -// NewFromFile returns a pointer to a YML object from a file. -func NewFromFile(filename string) (*YML, error) { - data, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - var tree interface{} - err = yaml.Unmarshal(data, &tree) - if err != nil { - return nil, err +// NewFromFile returns a list of pointers to a YML object from a file. +// +// Returns a list since YAML files can contain multiple documents: +// https://yaml.org/spec/1.2-old/spec.html#id2800401 +func NewFromFile(filename string) ([]*YML, error) { + list := []*YML{} + fh, err := os.Open(filename) + decoder := yaml.NewDecoder(fh) + for { + var tree interface{} + err = decoder.Decode(&tree) + if err != nil { + if !errors.Is(err, io.EOF) { + return nil, err + } + break + } + list = append(list, &YML{Tree: tree}) } - return &YML{Tree: tree}, nil + return list, nil } -// NewFromReader returns a pointer to a YML object from an io.Reader. -func NewFromReader(reader io.Reader) (*YML, error) { - var tree interface{} - buf := new(bytes.Buffer) - _, err := buf.ReadFrom(reader) - if err != nil { - return nil, err - } - err = yaml.Unmarshal(buf.Bytes(), &tree) - if err != nil { - return nil, err +// NewFromReader returns a list of pointers to a YML object from an io.Reader. +// +// Returns a list since YAML files can contain multiple documents: +// https://yaml.org/spec/1.2-old/spec.html#id2800401 +func NewFromReader(reader io.Reader) ([]*YML, error) { + list := []*YML{} + decoder := yaml.NewDecoder(reader) + for { + var tree interface{} + err := decoder.Decode(&tree) + if err != nil { + if !errors.Is(err, io.EOF) { + return nil, err + } + break + } + list = append(list, &YML{Tree: tree}) } - return &YML{Tree: tree}, nil + return list, nil } // NewFromString - returns a pointer to a YML object from a string.