diff --git a/commands_test.go b/commands_test.go index 9002d81..58d58e1 100644 --- a/commands_test.go +++ b/commands_test.go @@ -94,6 +94,24 @@ func TestReadCmd(t *testing.T) { test.AssertResult(t, "2\n", result.Output) } +// func TestReadRawCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "read examples/sample.yaml b.c") +// if result.Error != nil { +// t.Error(result.Error) +// } +// test.AssertResult(t, "21\n", result.Output) +// } + +// func TestReadRawMultiCmd(t *testing.T) { +// cmd := getRootCommand() +// result := test.RunCmd(cmd, "read examples/sample.yaml b.c") +// if result.Error != nil { +// t.Error(result.Error) +// } +// test.AssertResult(t, "21\n", result.Output) +// } + func TestReadInvalidDocumentIndexCmd(t *testing.T) { cmd := getRootCommand() result := test.RunCmd(cmd, "read -df examples/sample.yaml b.c") diff --git a/go.mod b/go.mod index efc8ffd..6171905 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/mikefarah/yq/v3 require ( - github.com/mikefarah/yaml/v2 v2.4.0 + github.com/mikefarah/yaml/v2 v2.4.0 // indirect github.com/pkg/errors v0.8.1 github.com/spf13/cobra v0.0.5 - golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0 // indirect - gopkg.in/imdario/mergo.v0 v0.3.7 + golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 // indirect + gopkg.in/imdario/mergo.v0 v0.3.7 // indirect gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 ) diff --git a/go.sum b/go.sum index 307ebb4..2f045e2 100644 --- a/go.sum +++ b/go.sum @@ -35,14 +35,21 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0 h1:s5lp4ug7qHzUccgyFdjsX7OZDzHXRaePrF3B3vmUiuM= golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 h1:kJQZhwFzSwJS2BxboKjdZzWczQOZx8VuH7Y8hhuGUtM= +golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/imdario/mergo.v0 v0.3.7 h1:QDotlIZtaO/p+Um0ok18HRTpq5i5/SAk/qprsor+9c8= diff --git a/pkg/yqlib/value_parser.go b/pkg/yqlib/value_parser.go index d08da02..7a51d7c 100644 --- a/pkg/yqlib/value_parser.go +++ b/pkg/yqlib/value_parser.go @@ -2,34 +2,50 @@ package yqlib import ( "strconv" + + logging "gopkg.in/op/go-logging.v1" + yaml "gopkg.in/yaml.v3" ) type ValueParser interface { - ParseValue(argument string) interface{} + Parse(argument string, customTag string) *yaml.Node } -type valueParser struct{} - -func NewValueParser() ValueParser { - return &valueParser{} +type valueParser struct { + log *logging.Logger } -func (v *valueParser) ParseValue(argument string) interface{} { - var value, err interface{} +func NewValueParser(l *logging.Logger) ValueParser { + return &valueParser{log: l} +} + +func (v *valueParser) Parse(argument string, customTag string) *yaml.Node { + var err interface{} + var tag = customTag + var inQuotes = len(argument) > 0 && argument[0] == '"' - if !inQuotes { - value, err = strconv.ParseFloat(argument, 64) + if tag == "" && !inQuotes { + + _, err = strconv.ParseBool(argument) if err == nil { - return value + tag = "!!bool" } - value, err = strconv.ParseBool(argument) + _, err = strconv.ParseFloat(argument, 64) if err == nil { - return value + tag = "!!float" + } + _, err = strconv.ParseInt(argument, 10, 64) + if err == nil { + tag = "!!int" + } + + if argument == "null" { + tag = "!!null" } if argument == "[]" { - return make([]interface{}, 0) + return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode} } - return argument } - return argument[1 : len(argument)-1] + v.log.Debugf("parsed value '%v', tag: '%v'", argument, tag) + return &yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode} } diff --git a/yq.go b/yq.go index a9f1452..d4998e6 100644 --- a/yq.go +++ b/yq.go @@ -29,7 +29,7 @@ var version = false var docIndex = "0" var log = logging.MustGetLogger("yq") var lib = yqlib.NewYqLib(log) -var valueParser = yqlib.NewValueParser() +var valueParser = yqlib.NewValueParser(log) func main() { cmd := newCommandCLI() @@ -263,28 +263,14 @@ func readProperty(cmd *cobra.Command, args []string) error { for { var dataBucket yaml.Node errorReading := decoder.Decode(&dataBucket) - log.Debugf("decoded node for doc %v", currentIndex) - lib.DebugNode(&dataBucket) + if errorReading == io.EOF { - log.Debugf("done %v / %v", currentIndex, docIndexInt) - if !updateAll && currentIndex <= docIndexInt { - return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex) - } - return nil + return handleEOF(updateAll, docIndexInt, currentIndex) } - log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) - if updateAll || currentIndex == docIndexInt { - log.Debugf("reading %v in document %v", path, currentIndex) - mappedDoc, errorParsing := lib.Get(&dataBucket, path) - lib.DebugNode(mappedDoc) - log.Debugf("carry on") - if errorParsing != nil { - return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) - } else if mappedDoc != nil { - - mappedDocs = append(mappedDocs, mappedDoc) - } - + var errorParsing error + mappedDocs, errorParsing = appendDocument(mappedDocs, dataBucket, path, updateAll, docIndexInt, currentIndex) + if errorParsing != nil { + return errorParsing } currentIndex = currentIndex + 1 } @@ -292,7 +278,38 @@ func readProperty(cmd *cobra.Command, args []string) error { if errorReadingStream != nil { return errorReadingStream - } else if len(mappedDocs) == 0 { + } + + return printResults(mappedDocs, cmd) +} + +func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error { + log.Debugf("done %v / %v", currentIndex, docIndexInt) + if !updateAll && currentIndex <= docIndexInt { + return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex) + } + return nil +} + +func appendDocument(mappedDocs []*yaml.Node, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yaml.Node, error) { + log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt) + lib.DebugNode(&dataBucket) + if !updateAll && currentIndex != docIndexInt { + return mappedDocs, nil + } + log.Debugf("reading %v in document %v", path, currentIndex) + mappedDoc, errorParsing := lib.Get(&dataBucket, path) + lib.DebugNode(mappedDoc) + if errorParsing != nil { + return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex) + } else if mappedDoc != nil { + return append(mappedDocs, mappedDoc), nil + } + return mappedDocs, nil +} + +func printResults(mappedDocs []*yaml.Node, cmd *cobra.Command) error { + if len(mappedDocs) == 0 { log.Debug("no matching results, nothing to print") return nil } @@ -307,7 +324,7 @@ func readProperty(cmd *cobra.Command, args []string) error { cmd.Println(mappedDoc.Value) } } - } else if !updateAll { + } else if len(mappedDocs) == 1 { err = encoder.Encode(mappedDocs[0]) } else { err = encoder.Encode(&yaml.Node{Kind: yaml.SequenceNode, Content: mappedDocs}) @@ -319,43 +336,6 @@ func readProperty(cmd *cobra.Command, args []string) error { return nil } -// func newProperty(cmd *cobra.Command, args []string) error { -// updatedData, err := newYaml(args) -// if err != nil { -// return err -// } -// dataStr, err := toString(updatedData) -// if err != nil { -// return err -// } -// cmd.Println(dataStr) -// return nil -// } - -// func newYaml(args []string) (interface{}, error) { -// var writeCommands, writeCommandsError = readUpdateCommands(args, 2, "Must provide ") -// if writeCommandsError != nil { -// return nil, writeCommandsError -// } - -// var dataBucket interface{} -// var isArray = strings.HasPrefix(writeCommands[0].Key.(string), "[") -// if isArray { -// dataBucket = make([]interface{}, 0) -// } else { -// dataBucket = make(yaml.MapSlice, 0) -// } - -// for _, entry := range writeCommands { -// path := entry.Key.(string) -// value := entry.Value -// log.Debugf("setting %v to %v", path, value) -// dataBucket = lib.WritePath(dataBucket, path, value) -// } - -// return dataBucket, nil -// } - func parseDocumentIndex() (bool, int, error) { if docIndex == "*" { return true, -1, nil @@ -454,25 +434,27 @@ func prefixProperty(cmd *cobra.Command, args []string) error { } var updateData = func(dataBucket *yaml.Node, currentIndex int) error { - if updateAll || currentIndex == docIndexInt { - log.Debugf("Prefixing document %v", currentIndex) - lib.DebugNode(dataBucket) - updateCommand.Value = dataBucket.Content[0] - dataBucket.Content = make([]*yaml.Node, 1) - - newNode := lib.New(updateCommand.Path) - dataBucket.Content[0] = &newNode - - errorUpdating := lib.Update(dataBucket, updateCommand) - if errorUpdating != nil { - return errorUpdating - } - - } - return nil + return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand) } return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) +} +func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error { + if updateAll || currentIndex == docIndexInt { + log.Debugf("Prefixing document %v", currentIndex) + lib.DebugNode(dataBucket) + updateCommand.Value = dataBucket.Content[0] + dataBucket.Content = make([]*yaml.Node, 1) + + newNode := lib.New(updateCommand.Path) + dataBucket.Content[0] = &newNode + + errorUpdating := lib.Update(dataBucket, updateCommand) + if errorUpdating != nil { + return errorUpdating + } + } + return nil } func deleteProperty(cmd *cobra.Command, args []string) error { @@ -506,29 +488,6 @@ func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io return readAndUpdate(writer, inputFile, updateData) } -// func prefixProperty(cmd *cobra.Command, args []string) error { -// if len(args) != 2 { -// return errors.New("Must provide ") -// } -// prefixPath := args[1] - -// var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() -// if errorParsingDocIndex != nil { -// return errorParsingDocIndex -// } - -// var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { - -// if updateAll || currentIndex == docIndexInt { -// log.Debugf("Prefixing %v to doc %v", prefixPath, currentIndex) -// var mapDataBucket = lib.PrefixPath(dataBucket, prefixPath) -// return mapDataBucket, nil -// } -// return dataBucket, nil -// } -// return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) -// } - func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error { var destination io.Writer var destinationName string @@ -633,42 +592,11 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) log.Debug("args %v", args) log.Debug("path %v", args[expectedArgs-2]) log.Debug("Value %v", args[expectedArgs-1]) - updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: parseValue(args[expectedArgs-1])} + updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag)} } return updateCommands, nil } -func parseValue(argument string) *yaml.Node { - var err interface{} - var tag = customTag - - var inQuotes = len(argument) > 0 && argument[0] == '"' - if tag == "" && !inQuotes { - - _, err = strconv.ParseBool(argument) - if err == nil { - tag = "!!bool" - } - _, err = strconv.ParseFloat(argument, 64) - if err == nil { - tag = "!!float" - } - _, err = strconv.ParseInt(argument, 10, 64) - if err == nil { - tag = "!!int" - } - - if argument == "null" { - tag = "!!null" - } - if argument == "[]" { - return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode} - } - } - log.Debugf("Updating node to value '%v', tag: '%v'", argument, tag) - return &yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode} -} - func safelyRenameFile(from string, to string) { if renameError := os.Rename(from, to); renameError != nil { log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)