diff --git a/commands_test.go b/commands_test.go index 8e09b81d..9002d816 100644 --- a/commands_test.go +++ b/commands_test.go @@ -605,6 +605,22 @@ func TestWriteCmd(t *testing.T) { 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) { content := `b: c: 3 diff --git a/examples/instruction_sample.yaml b/examples/instruction_sample.yaml index 9834c942..24445bca 100644 --- a/examples/instruction_sample.yaml +++ b/examples/instruction_sample.yaml @@ -6,5 +6,6 @@ - command: update path: b.e[+].name value: Mike Farah -- command: delete - path: a \ No newline at end of file +- command: update + path: d.a + value: Cow \ No newline at end of file diff --git a/pkg/yqlib/data_navigator.go b/pkg/yqlib/data_navigator.go index 66d4c710..1e2d03e0 100644 --- a/pkg/yqlib/data_navigator.go +++ b/pkg/yqlib/data_navigator.go @@ -12,7 +12,7 @@ import ( type DataNavigator interface { DebugNode(node *yaml.Node) 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 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 } -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 { n.log.Debug("going to update") n.DebugNode(nodeToUpdate) n.log.Debug("with") - n.DebugNode(&changesToApply) + n.DebugNode(changesToApply) nodeToUpdate.Value = changesToApply.Value nodeToUpdate.Tag = changesToApply.Tag 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)) 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 -// } diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index 9fb1591d..2415daea 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -10,14 +10,14 @@ import ( type UpdateCommand struct { Command string Path string - Value yaml.Node + Value *yaml.Node } type YqLib interface { DebugNode(node *yaml.Node) Get(rootNode *yaml.Node, path string) (*yaml.Node, error) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error - New(updateCommand UpdateCommand) (yaml.Node, error) + New(path string) yaml.Node } 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) } -func (l *lib) New(updateCommand UpdateCommand) (yaml.Node, error) { - var paths = l.parser.ParsePath(updateCommand.Path) +func (l *lib) New(path string) yaml.Node { + var paths = l.parser.ParsePath(path) newNode := yaml.Node{Kind: l.navigator.GuessKind(paths, 0)} - errorUpdating := l.navigator.Update(&newNode, paths, updateCommand.Value) - if errorUpdating != nil { - return newNode, errorUpdating - } - return newNode, nil + return newNode } func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand) error { diff --git a/yq.go b/yq.go index 3e5cbe9f..a9f14528 100644 --- a/yq.go +++ b/yq.go @@ -76,7 +76,7 @@ func newCommandCLI() *cobra.Command { rootCmd.AddCommand( createReadCmd(), createWriteCmd(), - // createPrefixCmd(), + createPrefixCmd(), createDeleteCmd(), createNewCmd(), // createMergeCmd(), @@ -143,28 +143,28 @@ a.b.e: return cmdWrite } -// func createPrefixCmd() *cobra.Command { -// var cmdWrite = &cobra.Command{ -// Use: "prefix [yaml_file] [path]", -// Aliases: []string{"p"}, -// Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", -// Example: ` -// yq prefix things.yaml a.b.c -// yq prefix --inplace things.yaml a.b.c -// yq prefix --inplace -- things.yaml --key-starting-with-dash -// yq p -i things.yaml a.b.c -// yq p --doc 2 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. -// Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. -// `, -// RunE: prefixProperty, -// } -// cmdWrite.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)") -// return cmdWrite -// } +func createPrefixCmd() *cobra.Command { + var cmdPrefix = &cobra.Command{ + Use: "prefix [yaml_file] [path]", + Aliases: []string{"p"}, + Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", + Example: ` +yq prefix things.yaml a.b.c +yq prefix --inplace things.yaml a.b.c +yq prefix --inplace -- things.yaml --key-starting-with-dash +yq p -i things.yaml a.b.c +yq p --doc 2 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. +Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. +`, + RunE: prefixProperty, + } + cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") + cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") + return cmdPrefix +} func createDeleteCmd() *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 { - var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ") + var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide ") if updateCommandsError != nil { return updateCommandsError } - firstCommand, restOfCommands := updateCommands[0], updateCommands[1:] - newNode, errorCreating := lib.New(firstCommand) - if errorCreating != nil { - return errorCreating - } + newNode := lib.New(updateCommands[0].Path) - for _, updateCommand := range restOfCommands { + for _, updateCommand := range updateCommands { errorUpdating := lib.Update(&newNode, updateCommand) @@ -442,6 +438,40 @@ func newProperty(cmd *cobra.Command, args []string) error { encoder.Encode(&newNode) encoder.Close() return nil +} + +func prefixProperty(cmd *cobra.Command, args []string) error { + + if len(args) < 2 { + return errors.New("Must provide ") + } + 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) // } +type updateCommandParsed struct { + Command string + Path string + Value yaml.Node +} + 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 err := readData(writeScript, 0, &updateCommands); err != nil { + var parsedCommands = make([]updateCommandParsed, 0) + if err := readData(writeScript, 0, &parsedCommands); err != nil { 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) } else if len(args) < expectedArgs { return nil, errors.New(badArgsMessage) @@ -594,7 +638,7 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) return updateCommands, nil } -func parseValue(argument string) yaml.Node { +func parseValue(argument string) *yaml.Node { var err interface{} var tag = customTag @@ -618,11 +662,11 @@ func parseValue(argument string) yaml.Node { tag = "!!null" } 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) - 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) {