mirror of
https://github.com/mikefarah/yq.git
synced 2024-12-19 20:19:04 +00:00
Add delete command
The delete (short option "d") will delete the YAML subtree at the provided path in the specified file (or STDIN), if it the node exists. More complex support is currently omitted, for example: - specify nodes to delete using an external script - deleting common elements from all elements of an array
This commit is contained in:
parent
26153b3eb5
commit
8072e66d46
@ -18,9 +18,7 @@ func entryInSlice(context yaml.MapSlice, key interface{}) *yaml.MapItem {
|
||||
return nil
|
||||
}
|
||||
|
||||
func writeMap(context interface{}, paths []string, value interface{}) yaml.MapSlice {
|
||||
log.Debugf("writeMap for %v for %v with value %v\n", paths, context, value)
|
||||
|
||||
func getMapSlice(context interface{}) yaml.MapSlice {
|
||||
var mapSlice yaml.MapSlice
|
||||
switch context.(type) {
|
||||
case yaml.MapSlice:
|
||||
@ -28,6 +26,25 @@ func writeMap(context interface{}, paths []string, value interface{}) yaml.MapSl
|
||||
default:
|
||||
mapSlice = make(yaml.MapSlice, 0)
|
||||
}
|
||||
return mapSlice
|
||||
}
|
||||
|
||||
func getArray(context interface{}) (array []interface{}, ok bool) {
|
||||
switch context.(type) {
|
||||
case []interface{}:
|
||||
array = context.([]interface{})
|
||||
ok = true
|
||||
default:
|
||||
array = make([]interface{}, 0)
|
||||
ok = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func writeMap(context interface{}, paths []string, value interface{}) yaml.MapSlice {
|
||||
log.Debugf("writeMap for %v for %v with value %v\n", paths, context, value)
|
||||
|
||||
mapSlice := getMapSlice(context)
|
||||
|
||||
if len(paths) == 0 {
|
||||
return mapSlice
|
||||
@ -66,13 +83,7 @@ func updatedChildValue(child interface{}, remainingPaths []string, value interfa
|
||||
|
||||
func writeArray(context interface{}, paths []string, value interface{}) []interface{} {
|
||||
log.Debugf("writeArray for %v for %v with value %v\n", paths, context, value)
|
||||
var array []interface{}
|
||||
switch context.(type) {
|
||||
case []interface{}:
|
||||
array = context.([]interface{})
|
||||
default:
|
||||
array = make([]interface{}, 0)
|
||||
}
|
||||
array, _ := getArray(context)
|
||||
|
||||
if len(paths) == 0 {
|
||||
return array
|
||||
@ -199,3 +210,93 @@ func mapToMapSlice(data map[interface{}]interface{}) yaml.MapSlice {
|
||||
sort.SliceStable(mapSlice, func(i, j int) bool { return mapSlice[i].Key.(string) < mapSlice[j].Key.(string) })
|
||||
return mapSlice
|
||||
}
|
||||
|
||||
func deleteMap(context interface{}, paths []string) yaml.MapSlice {
|
||||
log.Debugf("deleteMap for %v for %v\n", paths, context)
|
||||
|
||||
mapSlice := getMapSlice(context)
|
||||
|
||||
if len(paths) == 0 {
|
||||
return mapSlice
|
||||
}
|
||||
|
||||
var found bool
|
||||
var index int
|
||||
var child yaml.MapItem
|
||||
for index, child = range mapSlice {
|
||||
if child.Key == paths[0] {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return mapSlice
|
||||
}
|
||||
|
||||
remainingPaths := paths[1:]
|
||||
|
||||
var newSlice yaml.MapSlice
|
||||
if len(remainingPaths) > 0 {
|
||||
newChild := yaml.MapItem{Key: child.Key}
|
||||
newChild.Value = deleteChildValue(child.Value, remainingPaths)
|
||||
|
||||
newSlice = make(yaml.MapSlice, len(mapSlice))
|
||||
for i := range mapSlice {
|
||||
item := mapSlice[i]
|
||||
if i == index {
|
||||
item = newChild
|
||||
}
|
||||
newSlice[i] = item
|
||||
}
|
||||
} else {
|
||||
// Delete item from slice at index
|
||||
newSlice = append(mapSlice[:index], mapSlice[index+1:]...)
|
||||
log.Debugf("\tDeleted item index %d from mapSlice", index)
|
||||
}
|
||||
|
||||
log.Debugf("\t\tlen: %d\tcap: %d\tslice: %v", len(mapSlice), cap(mapSlice), mapSlice)
|
||||
log.Debugf("\tReturning mapSlice %v\n", mapSlice)
|
||||
return newSlice
|
||||
}
|
||||
|
||||
func deleteArray(context interface{}, paths []string, index int64) interface{} {
|
||||
log.Debugf("deleteArray for %v for %v\n", paths, context)
|
||||
|
||||
array, ok := getArray(context)
|
||||
if !ok {
|
||||
// did not get an array
|
||||
return context
|
||||
}
|
||||
|
||||
if index >= int64(len(array)) {
|
||||
return array
|
||||
}
|
||||
|
||||
remainingPaths := paths[1:]
|
||||
if len(remainingPaths) > 0 {
|
||||
// Recurse into the array element at index
|
||||
array[index] = deleteMap(array[index], remainingPaths)
|
||||
} 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
|
||||
}
|
||||
|
||||
func deleteChildValue(child interface{}, remainingPaths []string) interface{} {
|
||||
log.Debugf("deleteChildValue for %v for %v\n", remainingPaths, child)
|
||||
|
||||
idx, nextIndexErr := strconv.ParseInt(remainingPaths[0], 10, 64)
|
||||
if nextIndexErr != nil {
|
||||
// must be a map
|
||||
log.Debugf("\tdetected a map, invoking deleteMap\n")
|
||||
return deleteMap(child, remainingPaths)
|
||||
}
|
||||
|
||||
log.Debugf("\tdetected an array, so traversing element with index %d\n", idx)
|
||||
return deleteArray(child, remainingPaths, idx)
|
||||
}
|
||||
|
@ -308,3 +308,80 @@ func TestWriteArray_no_paths(t *testing.T) {
|
||||
result := writeArray(data, []string{}, 4)
|
||||
assertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
|
||||
}
|
||||
|
||||
func TestDelete_MapItem(t *testing.T) {
|
||||
var data = parseData(`
|
||||
a: 123
|
||||
b: 456
|
||||
`)
|
||||
var expected = parseData(`
|
||||
b: 456
|
||||
`)
|
||||
|
||||
result := deleteMap(data, []string{"a"})
|
||||
assertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
|
||||
}
|
||||
|
||||
// Ensure deleting an index into a string does nothing
|
||||
func TestDelete_index_to_string(t *testing.T) {
|
||||
var data = parseData(`
|
||||
a: mystring
|
||||
`)
|
||||
result := deleteMap(data, []string{"a", "0"})
|
||||
assertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
|
||||
}
|
||||
|
||||
func TestDelete_list_index(t *testing.T) {
|
||||
var data = parseData(`
|
||||
a: [3, 4]
|
||||
`)
|
||||
var expected = parseData(`
|
||||
a: [3]
|
||||
`)
|
||||
result := deleteMap(data, []string{"a", "1"})
|
||||
assertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
|
||||
}
|
||||
|
||||
func TestDelete_list_index_beyond_bounds(t *testing.T) {
|
||||
var data = parseData(`
|
||||
a: [3, 4]
|
||||
`)
|
||||
result := deleteMap(data, []string{"a", "5"})
|
||||
assertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
|
||||
}
|
||||
|
||||
func TestDelete_list_index_out_of_bounds_by_1(t *testing.T) {
|
||||
var data = parseData(`
|
||||
a: [3, 4]
|
||||
`)
|
||||
result := deleteMap(data, []string{"a", "2"})
|
||||
assertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
|
||||
}
|
||||
|
||||
func TestDelete_no_paths(t *testing.T) {
|
||||
var data = parseData(`
|
||||
a: [3, 4]
|
||||
b:
|
||||
- name: test
|
||||
`)
|
||||
result := deleteMap(data, []string{})
|
||||
assertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
|
||||
}
|
||||
|
||||
func TestDelete_array_map_item(t *testing.T) {
|
||||
var data = parseData(`
|
||||
b:
|
||||
- name: fred
|
||||
value: blah
|
||||
- name: john
|
||||
value: test
|
||||
`)
|
||||
var expected = parseData(`
|
||||
b:
|
||||
- value: blah
|
||||
- name: john
|
||||
value: test
|
||||
`)
|
||||
result := deleteMap(data, []string{"b", "0", "name"})
|
||||
assertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
|
||||
}
|
||||
|
60
yq.go
60
yq.go
@ -64,7 +64,13 @@ func newCommandCLI() *cobra.Command {
|
||||
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
|
||||
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
||||
|
||||
rootCmd.AddCommand(createReadCmd(), createWriteCmd(), createNewCmd(), createMergeCmd())
|
||||
rootCmd.AddCommand(
|
||||
createReadCmd(),
|
||||
createWriteCmd(),
|
||||
createDeleteCmd(),
|
||||
createNewCmd(),
|
||||
createMergeCmd(),
|
||||
)
|
||||
rootCmd.SetOutput(os.Stdout)
|
||||
|
||||
return rootCmd
|
||||
@ -121,6 +127,26 @@ a.b.e:
|
||||
return cmdWrite
|
||||
}
|
||||
|
||||
func createDeleteCmd() *cobra.Command {
|
||||
var cmdDelete = &cobra.Command{
|
||||
Use: "delete [yaml_file] [path]",
|
||||
Aliases: []string{"d"},
|
||||
Short: "yq d [--inplace/-i] sample.yaml a.b.c",
|
||||
Example: `
|
||||
yq delete things.yaml a.b.c
|
||||
yq delete --inplace things.yaml a.b.c
|
||||
yq d -i things.yaml a.b.c
|
||||
yq d things.yaml a.b.c
|
||||
`,
|
||||
Long: `Deletes the given path from the YAML file.
|
||||
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
|
||||
`,
|
||||
RunE: deleteProperty,
|
||||
}
|
||||
cmdDelete.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
|
||||
return cmdDelete
|
||||
}
|
||||
|
||||
func createNewCmd() *cobra.Command {
|
||||
var cmdNew = &cobra.Command{
|
||||
Use: "new [path] [value]",
|
||||
@ -316,6 +342,38 @@ func write(cmd *cobra.Command, filename string, updatedData interface{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteProperty(cmd *cobra.Command, args []string) error {
|
||||
updatedData, err := deleteYaml(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return write(cmd, args[0], updatedData)
|
||||
}
|
||||
|
||||
func deleteYaml(args []string) (interface{}, error) {
|
||||
var parsedData yaml.MapSlice
|
||||
var deletePath string
|
||||
|
||||
if len(args) < 2 {
|
||||
return nil, errors.New("Must provide <filename> <path_to_delete>")
|
||||
}
|
||||
|
||||
deletePath = args[1]
|
||||
|
||||
if err := readData(args[0], &parsedData); err != nil {
|
||||
var generalData interface{}
|
||||
if err = readData(args[0], &generalData); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item := yaml.MapItem{Key: "thing", Value: generalData}
|
||||
parsedData = yaml.MapSlice{item}
|
||||
deletePath = "thing." + deletePath
|
||||
}
|
||||
|
||||
path := parsePath(deletePath)
|
||||
return deleteMap(parsedData, path), nil
|
||||
}
|
||||
|
||||
func mergeProperties(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 2 {
|
||||
return errors.New("Must provide at least 2 yaml files")
|
||||
|
@ -122,3 +122,11 @@ func TestNewYaml_WithUnknownScript(t *testing.T) {
|
||||
expectedOutput := `open fake-unknown: no such file or directory`
|
||||
assertResult(t, expectedOutput, err.Error())
|
||||
}
|
||||
|
||||
func TestDeleteYaml(t *testing.T) {
|
||||
result, _ := deleteYaml([]string{"examples/sample.yaml", "b.c"})
|
||||
formattedResult := fmt.Sprintf("%v", result)
|
||||
assertResult(t,
|
||||
"[{a Easy! as one two three} {b [{d [3 4]} {e [[{name fred} {value 3}] [{name sam} {value 4}]]}]}]",
|
||||
formattedResult)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user