wip json encoding

This commit is contained in:
Mike Farah 2020-01-10 22:01:59 +11:00
parent feba7b04fa
commit 854f5f0fc9
7 changed files with 94 additions and 35 deletions

View File

@ -1,15 +1,12 @@
# Update doco / notes
- --autocreate=false to turn off default flags
- add comments to test yaml to ensure they are kept on updating.
- update built in command notes to includes quotes around path args.
# New Features # New Features
- Keeps comments and formatting (e.g. inline arrays)! - Keeps comments and formatting (e.g. inline arrays)!
- Handles anchors! - Handles anchors!
- Can specify yaml tags (e.g. !!int), quoting values no longer sufficient, need to specify the tag value instead. - Can specify yaml tags (e.g. !!int), quoting values no longer sufficient, need to specify the tag value instead.
- JSON output works for all commands! Yaml files with multiple documents are printed out as one JSON document per line.
# Update scripts file format has changed # Update scripts file format has changed to be more powerful. Comments can be added, and delete commands have been introduced.
# Merge command # Merge command
- autocreates missing entries in target by default, new flag to turn that off. - New flag 'autocreates' missing entries in target by default, new flag to turn that off.

View File

@ -494,7 +494,7 @@ func TestReadCmd_Verbose(t *testing.T) {
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
test.AssertResult(t, "2", result.Output) test.AssertResult(t, "2\n", result.Output)
} }
// func TestReadCmd_ToJson(t *testing.T) { // func TestReadCmd_ToJson(t *testing.T) {

View File

@ -6,6 +6,5 @@
- command: update - command: update
path: b.e[+].name path: b.e[+].name
value: Mike Farah value: Mike Farah
- command: update - command: delete
path: d.a path: b.d
value: Cow

View File

@ -1,4 +1,4 @@
a: Easy! as one two three a: true
b: b:
c: 2 c: 2
d: [3, 4, 5] d: [3, 4, 5]

44
pkg/yqlib/encoder.go Normal file
View File

@ -0,0 +1,44 @@
package yqlib
import (
"encoding/json"
"io"
yaml "gopkg.in/yaml.v3"
)
type Encoder interface {
Encode(node *yaml.Node) error
}
type yamlEncoder struct {
encoder *yaml.Encoder
}
func NewYamlEncoder(destination io.Writer) Encoder {
var encoder = yaml.NewEncoder(destination)
encoder.SetIndent(2)
return &yamlEncoder{encoder}
}
func (ye *yamlEncoder) Encode(node *yaml.Node) error {
return ye.encoder.Encode(node)
}
type jsonEncoder struct {
encoder *json.Encoder
}
func NewJsonEncoder(destination io.Writer) Encoder {
var encoder = json.NewEncoder(destination)
return &jsonEncoder{encoder}
}
func (je *jsonEncoder) Encode(node *yaml.Node) error {
var dataBucket interface{}
errorDecoding := node.Decode(&dataBucket)
if errorDecoding != nil {
return errorDecoding
}
return je.encoder.Encode(dataBucket)
}

View File

@ -14,7 +14,7 @@ var parseValueTests = []struct {
testDescription string testDescription string
}{ }{
{"true", "", "!!bool", "boolean"}, {"true", "", "!!bool", "boolean"},
{"true", "!!string", "!!string", "boolean forced as string"}, {"true", "!!str", "!!str", "boolean forced as string"},
{"3.4", "", "!!float", "float"}, {"3.4", "", "!!float", "float"},
{"1212121", "", "!!int", "big number"}, {"1212121", "", "!!int", "big number"},
{"1212121.1", "", "!!float", "big float number"}, {"1212121.1", "", "!!float", "big float number"},

65
yq.go
View File

@ -22,6 +22,7 @@ var customTag = ""
var printMode = "v" var printMode = "v"
var writeInplace = false var writeInplace = false
var writeScript = "" var writeScript = ""
var outputToJSON = false
var overwriteFlag = false var overwriteFlag = false
var autoCreateFlag = true var autoCreateFlag = true
var allowEmptyFlag = false var allowEmptyFlag = false
@ -73,6 +74,7 @@ func newCommandCLI() *cobra.Command {
} }
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json")
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
rootCmd.AddCommand( rootCmd.AddCommand(
@ -97,9 +99,9 @@ func createReadCmd() *cobra.Command {
yq read things.yaml a.b.c yq read things.yaml a.b.c
yq r - a.b.c (reads from stdin) yq r - a.b.c (reads from stdin)
yq r things.yaml a.*.c yq r things.yaml a.*.c
yq r -d1 things.yaml a.array[0].blah yq r -d1 things.yaml 'a.array[0].blah'
yq r things.yaml a.array[*].blah yq r things.yaml 'a.array[*].blah'
yq r -- things.yaml --key-starting-with-dashes yq r -- things.yaml --key-starting-with-dashes.blah
`, `,
Long: "Outputs the value of the given path in the yaml file to STDOUT", Long: "Outputs the value of the given path in the yaml file to STDOUT",
RunE: readProperty, RunE: readProperty,
@ -115,13 +117,15 @@ func createWriteCmd() *cobra.Command {
Aliases: []string{"w"}, Aliases: []string{"w"},
Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml a.b.c newValue", Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml a.b.c newValue",
Example: ` Example: `
yq write things.yaml a.b.c cat yq write things.yaml a.b.c true
yq write things.yaml a.b.c --tag '!!str' true
yq write things.yaml a.b.c --tag '!!float' 3
yq write --inplace -- things.yaml a.b.c --cat yq write --inplace -- things.yaml a.b.c --cat
yq w -i things.yaml a.b.c cat yq w -i things.yaml a.b.c cat
yq w --script update_script.yaml things.yaml yq w --script update_script.yaml things.yaml
yq w -i -s update_script.yaml things.yaml yq w -i -s update_script.yaml things.yaml
yq w --doc 2 things.yaml a.b.d[+] foo yq w --doc 2 things.yaml 'a.b.d[+]' foo
yq w -d2 things.yaml a.b.d[+] foo yq w -d2 things.yaml 'a.b.d[+]' foo
`, `,
Long: `Updates the yaml file w.r.t the given path and value. Long: `Updates the yaml file w.r.t the given path and value.
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
@ -129,12 +133,16 @@ Outputs to STDOUT unless the inplace flag is used, in which case the file is upd
Append value to array adds the value to the end of array. Append value to array adds the value to the end of array.
Update Scripts: Update Scripts:
Note that you can give an update script to perform more sophisticated updated. Update script Note that you can give an update script to perform more sophisticated update. Update script
format is a yaml map where the key is the path and the value is..well the value. e.g.: format is list of update commands (update or delete) like so:
--- ---
a.b.c: true, - command: update
a.b.e: path: b.c
- name: bob value:
#great
things: frog # wow!
- command: delete
path: b.d
`, `,
RunE: writeProperty, RunE: writeProperty,
} }
@ -198,6 +206,7 @@ func createNewCmd() *cobra.Command {
Example: ` Example: `
yq new a.b.c cat yq new a.b.c cat
yq n a.b.c cat yq n a.b.c cat
yq n a.b[+] --tag '!!str' true
yq n -- --key-starting-with-dash cat yq n -- --key-starting-with-dash cat
yq n --script create_script.yaml yq n --script create_script.yaml
`, `,
@ -226,6 +235,7 @@ yq m -i things.yaml other.yaml
yq m --overwrite things.yaml other.yaml yq m --overwrite things.yaml other.yaml
yq m -i -x things.yaml other.yaml yq m -i -x things.yaml other.yaml
yq m -i -a things.yaml other.yaml yq m -i -a things.yaml other.yaml
yq m -i --autocreate=false things.yaml other.yaml
`, `,
Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s).
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
@ -313,16 +323,18 @@ func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.
} }
func printValue(node *yaml.Node, cmd *cobra.Command) error { func printValue(node *yaml.Node, cmd *cobra.Command) error {
if node.Kind == yaml.ScalarNode { bufferedWriter := bufio.NewWriter(cmd.OutOrStdout())
cmd.Print(node.Value) defer safelyFlush(bufferedWriter)
return nil
var encoder yqlib.Encoder
if outputToJSON {
encoder = yqlib.NewJsonEncoder(bufferedWriter)
} else {
encoder = yqlib.NewYamlEncoder(bufferedWriter)
} }
var encoder = yaml.NewEncoder(cmd.OutOrStdout())
encoder.SetIndent(2)
if err := encoder.Encode(node); err != nil { if err := encoder.Encode(node); err != nil {
return err return err
} }
encoder.Close()
return nil return nil
} }
@ -376,7 +388,7 @@ func parseDocumentIndex() (bool, int, error) {
type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error
func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderFn { func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderFn {
return func(decoder *yaml.Decoder) error { return func(decoder *yaml.Decoder) error {
var dataBucket yaml.Node var dataBucket yaml.Node
var errorReading error var errorReading error
@ -561,14 +573,21 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn)
safelyRenameFile(tempFile.Name(), inputFile) safelyRenameFile(tempFile.Name(), inputFile)
}() }()
} else { } else {
var writer = bufio.NewWriter(stdOut) destination = stdOut
destination = writer
destinationName = "Stdout" destinationName = "Stdout"
defer safelyFlush(writer)
} }
var encoder = yaml.NewEncoder(destination)
encoder.SetIndent(2)
log.Debugf("Writing to %v from %v", destinationName, inputFile) log.Debugf("Writing to %v from %v", destinationName, inputFile)
bufferedWriter := bufio.NewWriter(destination)
defer safelyFlush(bufferedWriter)
var encoder yqlib.Encoder
if outputToJSON {
encoder = yqlib.NewJsonEncoder(bufferedWriter)
} else {
encoder = yqlib.NewYamlEncoder(bufferedWriter)
}
return readStream(inputFile, mapYamlDecoder(updateData, encoder)) return readStream(inputFile, mapYamlDecoder(updateData, encoder))
} }