Added prefix command

This commit is contained in:
Mike Farah 2019-12-16 16:17:01 +11:00
parent b81fd638d7
commit a3cebec2fd
5 changed files with 106 additions and 379 deletions

View File

@ -605,6 +605,22 @@ func TestWriteCmd(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
func TestWriteCmdScript(t *testing.T) {
content := `b:
c: 3
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `IMPLEMENT ME`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestWriteMultiCmd(t *testing.T) { func TestWriteMultiCmd(t *testing.T) {
content := `b: content := `b:
c: 3 c: 3

View File

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

View File

@ -12,7 +12,7 @@ import (
type DataNavigator interface { type DataNavigator interface {
DebugNode(node *yaml.Node) DebugNode(node *yaml.Node)
Get(rootNode *yaml.Node, path []string) (*yaml.Node, error) Get(rootNode *yaml.Node, path []string) (*yaml.Node, error)
Update(rootNode *yaml.Node, path []string, changesToApply yaml.Node) error Update(rootNode *yaml.Node, path []string, changesToApply *yaml.Node) error
Delete(rootNode *yaml.Node, path []string) error Delete(rootNode *yaml.Node, path []string) error
GuessKind(tail []string, guess yaml.Kind) yaml.Kind GuessKind(tail []string, guess yaml.Kind) yaml.Kind
} }
@ -50,12 +50,12 @@ func (n *navigator) Get(value *yaml.Node, path []string) (*yaml.Node, error) {
return &newNode, nil return &newNode, nil
} }
func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply yaml.Node) error { func (n *navigator) Update(rootNode *yaml.Node, path []string, changesToApply *yaml.Node) error {
errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node) error { errorVisiting := n.Visit(rootNode, path, func(nodeToUpdate *yaml.Node) error {
n.log.Debug("going to update") n.log.Debug("going to update")
n.DebugNode(nodeToUpdate) n.DebugNode(nodeToUpdate)
n.log.Debug("with") n.log.Debug("with")
n.DebugNode(&changesToApply) n.DebugNode(changesToApply)
nodeToUpdate.Value = changesToApply.Value nodeToUpdate.Value = changesToApply.Value
nodeToUpdate.Tag = changesToApply.Tag nodeToUpdate.Tag = changesToApply.Tag
nodeToUpdate.Kind = changesToApply.Kind nodeToUpdate.Kind = changesToApply.Kind
@ -288,333 +288,3 @@ func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, v
value.Content[index] = n.getOrReplace(value.Content[index], n.GuessKind(tail, value.Content[index].Kind)) value.Content[index] = n.getOrReplace(value.Content[index], n.GuessKind(tail, value.Content[index].Kind))
return n.Visit(value.Content[index], tail, visitor) return n.Visit(value.Content[index], tail, visitor)
} }
// func entriesInSlice(context yaml.MapSlice, key string) []*yaml.MapItem {
// var matches = make([]*yaml.MapItem, 0)
// for idx := range context {
// var entry = &context[idx]
// if matchesKey(key, entry.Key) {
// matches = append(matches, entry)
// }
// }
// return matches
// }
// func getMapSlice(context interface{}) yaml.MapSlice {
// var mapSlice yaml.MapSlice
// switch context := context.(type) {
// case yaml.MapSlice:
// mapSlice = context
// default:
// mapSlice = make(yaml.MapSlice, 0)
// }
// return mapSlice
// }
// func getArray(context interface{}) (array []interface{}, ok bool) {
// switch context := context.(type) {
// case []interface{}:
// array = context
// ok = true
// default:
// array = make([]interface{}, 0)
// ok = false
// }
// return
// }
// func writeMap(context interface{}, paths []string, value interface{}) interface{} {
// log.Debugf("writeMap with path %v for %v to set value %v\n", paths, context, value)
// mapSlice := getMapSlice(context)
// if len(paths) == 0 {
// return context
// }
// children := entriesInSlice(mapSlice, paths[0])
// if len(children) == 0 && paths[0] == "*" {
// log.Debugf("\tNo matches, return map as is")
// return context
// }
// if len(children) == 0 {
// newChild := yaml.MapItem{Key: paths[0]}
// mapSlice = append(mapSlice, newChild)
// children = entriesInSlice(mapSlice, paths[0])
// log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice)
// }
// remainingPaths := paths[1:]
// for _, child := range children {
// child.Value = updatedChildValue(child.Value, remainingPaths, value)
// }
// log.Debugf("\tReturning mapSlice %v\n", mapSlice)
// return mapSlice
// }
// func updatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} {
// if len(remainingPaths) == 0 {
// return value
// }
// log.Debugf("updatedChildValue for child %v with path %v to set value %v", child, remainingPaths, value)
// log.Debugf("type of child is %v", reflect.TypeOf(child))
// switch child := child.(type) {
// case nil:
// if remainingPaths[0] == "+" || remainingPaths[0] == "*" {
// return writeArray(child, remainingPaths, value)
// }
// case []interface{}:
// _, nextIndexErr := strconv.ParseInt(remainingPaths[0], 10, 64)
// arrayCommand := nextIndexErr == nil || remainingPaths[0] == "+" || remainingPaths[0] == "*"
// if arrayCommand {
// return writeArray(child, remainingPaths, value)
// }
// }
// return writeMap(child, remainingPaths, value)
// }
// func writeArray(context interface{}, paths []string, value interface{}) []interface{} {
// log.Debugf("writeArray with path %v for %v to set value %v\n", paths, context, value)
// array, _ := getArray(context)
// if len(paths) == 0 {
// return array
// }
// log.Debugf("\tarray %v\n", array)
// rawIndex := paths[0]
// remainingPaths := paths[1:]
// var index int64
// // the append array indicator
// if rawIndex == "+" {
// index = int64(len(array))
// } else if rawIndex == "*" {
// for index, oldChild := range array {
// array[index] = updatedChildValue(oldChild, remainingPaths, value)
// }
// return array
// } else {
// index, _ = strconv.ParseInt(rawIndex, 10, 64) // nolint
// // writeArray is only called by updatedChildValue which handles parsing the
// // index, as such this renders this dead code.
// }
// for index >= int64(len(array)) {
// array = append(array, nil)
// }
// currentChild := array[index]
// log.Debugf("\tcurrentChild %v\n", currentChild)
// array[index] = updatedChildValue(currentChild, remainingPaths, value)
// log.Debugf("\tReturning array %v\n", array)
// return array
// }
// func readMap(context yaml.MapSlice, head string, tail []string) (interface{}, error) {
// log.Debugf("readingMap %v with key %v\n", context, head)
// if head == "*" {
// return readMapSplat(context, tail)
// }
// entries := entriesInSlice(context, head)
// if len(entries) == 1 {
// return calculateValue(entries[0].Value, tail)
// } else if len(entries) == 0 {
// return nil, nil
// }
// var errInIdx error
// values := make([]interface{}, len(entries))
// for idx, entry := range entries {
// values[idx], errInIdx = calculateValue(entry.Value, tail)
// if errInIdx != nil {
// log.Errorf("Error updating index %v in %v", idx, context)
// return nil, errInIdx
// }
// }
// return values, nil
// }
// func readMapSplat(context yaml.MapSlice, tail []string) (interface{}, error) {
// var newArray = make([]interface{}, len(context))
// var i = 0
// for _, entry := range context {
// if len(tail) > 0 {
// val, err := recurse(entry.Value, tail[0], tail[1:])
// if err != nil {
// return nil, err
// }
// newArray[i] = val
// } else {
// newArray[i] = entry.Value
// }
// i++
// }
// return newArray, nil
// }
// func recurse(value interface{}, head string, tail []string) (interface{}, error) {
// switch value := value.(type) {
// case []interface{}:
// if head == "*" {
// return readArraySplat(value, tail)
// }
// index, err := strconv.ParseInt(head, 10, 64)
// if err != nil {
// return nil, fmt.Errorf("error accessing array: %v", err)
// }
// return readArray(value, index, tail)
// case yaml.MapSlice:
// return readMap(value, head, tail)
// default:
// return nil, nil
// }
// }
// func readArray(array []interface{}, head int64, tail []string) (interface{}, error) {
// if head >= int64(len(array)) {
// return nil, nil
// }
// value := array[head]
// return calculateValue(value, tail)
// }
// func readArraySplat(array []interface{}, tail []string) (interface{}, error) {
// var newArray = make([]interface{}, len(array))
// for index, value := range array {
// val, err := calculateValue(value, tail)
// if err != nil {
// return nil, err
// }
// newArray[index] = val
// }
// return newArray, nil
// }
// func calculateValue(value interface{}, tail []string) (interface{}, error) {
// if len(tail) > 0 {
// return recurse(value, tail[0], tail[1:])
// }
// return value, nil
// }
// func deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) {
// log.Debugf("deleteMap for %v for %v\n", paths, context)
// mapSlice := getMapSlice(context)
// if len(paths) == 0 {
// return mapSlice, nil
// }
// var index int
// var child yaml.MapItem
// for index, child = range mapSlice {
// if matchesKey(paths[0], child.Key) {
// log.Debugf("\tMatched [%v] with [%v] at index %v", paths[0], child.Key, index)
// var badDelete error
// mapSlice, badDelete = deleteEntryInMap(mapSlice, child, index, paths)
// if badDelete != nil {
// return nil, badDelete
// }
// }
// }
// return mapSlice, nil
// }
// func deleteEntryInMap(original yaml.MapSlice, child yaml.MapItem, index int, paths []string) (yaml.MapSlice, error) {
// remainingPaths := paths[1:]
// var newSlice yaml.MapSlice
// if len(remainingPaths) > 0 {
// newChild := yaml.MapItem{Key: child.Key}
// var errorDeleting error
// newChild.Value, errorDeleting = deleteChildValue(child.Value, remainingPaths)
// if errorDeleting != nil {
// return nil, errorDeleting
// }
// newSlice = make(yaml.MapSlice, len(original))
// for i := range original {
// item := original[i]
// if i == index {
// item = newChild
// }
// newSlice[i] = item
// }
// } else {
// // Delete item from slice at index
// newSlice = append(original[:index], original[index+1:]...)
// log.Debugf("\tDeleted item index %d from original", index)
// }
// log.Debugf("\tReturning original %v\n", original)
// return newSlice, nil
// }
// func deleteArraySplat(array []interface{}, tail []string) (interface{}, error) {
// log.Debugf("deleteArraySplat for %v for %v\n", tail, array)
// var newArray = make([]interface{}, len(array))
// for index, value := range array {
// val, err := deleteChildValue(value, tail)
// if err != nil {
// return nil, err
// }
// newArray[index] = val
// }
// return newArray, nil
// }
// func deleteArray(array []interface{}, paths []string, index int64) (interface{}, error) {
// log.Debugf("deleteArray for %v for %v\n", paths, array)
// if index >= int64(len(array)) {
// return array, nil
// }
// remainingPaths := paths[1:]
// if len(remainingPaths) > 0 {
// // Recurse into the array element at index
// var errorDeleting error
// array[index], errorDeleting = deleteMap(array[index], remainingPaths)
// if errorDeleting != nil {
// return nil, errorDeleting
// }
// } else {
// // Delete the array element at index
// array = append(array[:index], array[index+1:]...)
// log.Debugf("\tDeleted item index %d from array, leaving %v", index, array)
// }
// log.Debugf("\tReturning array: %v\n", array)
// return array, nil
// }
// func deleteChildValue(child interface{}, remainingPaths []string) (interface{}, error) {
// log.Debugf("deleteChildValue for %v for %v\n", remainingPaths, child)
// var head = remainingPaths[0]
// var tail = remainingPaths[1:]
// switch child := child.(type) {
// case yaml.MapSlice:
// return deleteMap(child, remainingPaths)
// case []interface{}:
// if head == "*" {
// return deleteArraySplat(child, tail)
// }
// index, err := strconv.ParseInt(head, 10, 64)
// if err != nil {
// return nil, fmt.Errorf("error accessing array: %v", err)
// }
// return deleteArray(child, remainingPaths, index)
// }
// return child, nil
// }

View File

@ -10,14 +10,14 @@ import (
type UpdateCommand struct { type UpdateCommand struct {
Command string Command string
Path string Path string
Value yaml.Node Value *yaml.Node
} }
type YqLib interface { type YqLib interface {
DebugNode(node *yaml.Node) DebugNode(node *yaml.Node)
Get(rootNode *yaml.Node, path string) (*yaml.Node, error) Get(rootNode *yaml.Node, path string) (*yaml.Node, error)
Update(rootNode *yaml.Node, updateCommand UpdateCommand) error Update(rootNode *yaml.Node, updateCommand UpdateCommand) error
New(updateCommand UpdateCommand) (yaml.Node, error) New(path string) yaml.Node
} }
type lib struct { type lib struct {
@ -43,14 +43,10 @@ func (l *lib) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) {
return l.navigator.Get(rootNode, paths) return l.navigator.Get(rootNode, paths)
} }
func (l *lib) New(updateCommand UpdateCommand) (yaml.Node, error) { func (l *lib) New(path string) yaml.Node {
var paths = l.parser.ParsePath(updateCommand.Path) var paths = l.parser.ParsePath(path)
newNode := yaml.Node{Kind: l.navigator.GuessKind(paths, 0)} newNode := yaml.Node{Kind: l.navigator.GuessKind(paths, 0)}
errorUpdating := l.navigator.Update(&newNode, paths, updateCommand.Value) return newNode
if errorUpdating != nil {
return newNode, errorUpdating
}
return newNode, nil
} }
func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error { func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error {

114
yq.go
View File

@ -76,7 +76,7 @@ func newCommandCLI() *cobra.Command {
rootCmd.AddCommand( rootCmd.AddCommand(
createReadCmd(), createReadCmd(),
createWriteCmd(), createWriteCmd(),
// createPrefixCmd(), createPrefixCmd(),
createDeleteCmd(), createDeleteCmd(),
createNewCmd(), createNewCmd(),
// createMergeCmd(), // createMergeCmd(),
@ -143,28 +143,28 @@ a.b.e:
return cmdWrite return cmdWrite
} }
// func createPrefixCmd() *cobra.Command { func createPrefixCmd() *cobra.Command {
// var cmdWrite = &cobra.Command{ var cmdPrefix = &cobra.Command{
// Use: "prefix [yaml_file] [path]", Use: "prefix [yaml_file] [path]",
// Aliases: []string{"p"}, Aliases: []string{"p"},
// Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
// Example: ` Example: `
// yq prefix things.yaml a.b.c yq prefix things.yaml a.b.c
// yq prefix --inplace things.yaml a.b.c yq prefix --inplace things.yaml a.b.c
// yq prefix --inplace -- things.yaml --key-starting-with-dash yq prefix --inplace -- things.yaml --key-starting-with-dash
// yq p -i things.yaml a.b.c yq p -i things.yaml a.b.c
// yq p --doc 2 things.yaml a.b.d yq p --doc 2 things.yaml a.b.d
// yq p -d2 things.yaml a.b.d yq p -d2 things.yaml a.b.d
// `, `,
// Long: `Prefixes w.r.t to the yaml file at the given path. Long: `Prefixes w.r.t to the yaml file at the given path.
// 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.
// `, `,
// RunE: prefixProperty, RunE: prefixProperty,
// } }
// cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
// cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
// return cmdWrite return cmdPrefix
// } }
func createDeleteCmd() *cobra.Command { func createDeleteCmd() *cobra.Command {
var cmdDelete = &cobra.Command{ var cmdDelete = &cobra.Command{
@ -418,17 +418,13 @@ func writeProperty(cmd *cobra.Command, args []string) error {
} }
func newProperty(cmd *cobra.Command, args []string) error { func newProperty(cmd *cobra.Command, args []string) error {
var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide <filename> <path_to_update> <value>") var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide <path_to_update> <value>")
if updateCommandsError != nil { if updateCommandsError != nil {
return updateCommandsError return updateCommandsError
} }
firstCommand, restOfCommands := updateCommands[0], updateCommands[1:] newNode := lib.New(updateCommands[0].Path)
newNode, errorCreating := lib.New(firstCommand)
if errorCreating != nil {
return errorCreating
}
for _, updateCommand := range restOfCommands { for _, updateCommand := range updateCommands {
errorUpdating := lib.Update(&newNode, updateCommand) errorUpdating := lib.Update(&newNode, updateCommand)
@ -442,6 +438,40 @@ func newProperty(cmd *cobra.Command, args []string) error {
encoder.Encode(&newNode) encoder.Encode(&newNode)
encoder.Close() encoder.Close()
return nil return nil
}
func prefixProperty(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("Must provide <filename> <prefixed_path>")
}
updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]}
log.Debugf("args %v", args)
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
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 readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
} }
@ -575,12 +605,26 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn)
// return readAndUpdate(cmd.OutOrStdout(), input, updateData) // return readAndUpdate(cmd.OutOrStdout(), input, updateData)
// } // }
type updateCommandParsed struct {
Command string
Path string
Value yaml.Node
}
func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) { func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) {
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1) var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
if writeScript != "" { if writeScript != "" {
if err := readData(writeScript, 0, &updateCommands); err != nil { var parsedCommands = make([]updateCommandParsed, 0)
if err := readData(writeScript, 0, &parsedCommands); err != nil {
return nil, err return nil, err
} }
log.Debugf("Read write commands file '%v'", parsedCommands)
for index := range parsedCommands {
parsedCommand := parsedCommands[index]
updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value}
updateCommands = append(updateCommands, updateCommand)
}
log.Debugf("Read write commands file '%v'", updateCommands) log.Debugf("Read write commands file '%v'", updateCommands)
} else if len(args) < expectedArgs { } else if len(args) < expectedArgs {
return nil, errors.New(badArgsMessage) return nil, errors.New(badArgsMessage)
@ -594,7 +638,7 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string)
return updateCommands, nil return updateCommands, nil
} }
func parseValue(argument string) yaml.Node { func parseValue(argument string) *yaml.Node {
var err interface{} var err interface{}
var tag = customTag var tag = customTag
@ -618,11 +662,11 @@ func parseValue(argument string) yaml.Node {
tag = "!!null" tag = "!!null"
} }
if argument == "[]" { if argument == "[]" {
return yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode} return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode}
} }
} }
log.Debugf("Updating node to value '%v', tag: '%v'", argument, tag) log.Debugf("Updating node to value '%v', tag: '%v'", argument, tag)
return yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode} return &yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode}
} }
func safelyRenameFile(from string, to string) { func safelyRenameFile(from string, to string) {