diff --git a/commands_test.go b/commands_test.go index f3032e75..7ff5efb4 100644 --- a/commands_test.go +++ b/commands_test.go @@ -341,6 +341,180 @@ func TestReadCmd_ToJsonLong(t *testing.T) { assertResult(t, "2\n", result.Output) } +func TestPrefixCmd(t *testing.T) { + content := `b: + c: 3 +` + filename := writeTempYamlFile(content) + defer removeTempYamlFile(filename) + + cmd := getRootCommand() + result := runCmd(cmd, fmt.Sprintf("prefix %s d", filename)) + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `d: + b: + c: 3 +` + assertResult(t, expectedOutput, result.Output) +} + +func TestPrefixCmd_MultiLayer(t *testing.T) { + content := `b: + c: 3 +` + filename := writeTempYamlFile(content) + defer removeTempYamlFile(filename) + + cmd := getRootCommand() + result := runCmd(cmd, fmt.Sprintf("prefix %s d.e.f", filename)) + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `d: + e: + f: + b: + c: 3 +` + assertResult(t, expectedOutput, result.Output) +} + +func TestPrefixMultiCmd(t *testing.T) { + content := `b: + c: 3 +--- +apples: great +` + filename := writeTempYamlFile(content) + defer removeTempYamlFile(filename) + + cmd := getRootCommand() + result := runCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename)) + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `b: + c: 3 +--- +d: + apples: great +` + assertResult(t, expectedOutput, result.Output) +} +func TestPrefixInvalidDocumentIndexCmd(t *testing.T) { + content := `b: + c: 3 +` + filename := writeTempYamlFile(content) + defer removeTempYamlFile(filename) + + cmd := getRootCommand() + result := runCmd(cmd, fmt.Sprintf("prefix %s -df d", filename)) + if result.Error == nil { + t.Error("Expected command to fail due to invalid path") + } + expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax` + assertResult(t, expectedOutput, result.Error.Error()) +} + +func TestPrefixBadDocumentIndexCmd(t *testing.T) { + content := `b: + c: 3 +` + filename := writeTempYamlFile(content) + defer removeTempYamlFile(filename) + + cmd := getRootCommand() + result := runCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename)) + if result.Error == nil { + t.Error("Expected command to fail due to invalid path") + } + expectedOutput := `Asked to process document index 1 but there are only 1 document(s)` + assertResult(t, expectedOutput, result.Error.Error()) +} +func TestPrefixMultiAllCmd(t *testing.T) { + content := `b: + c: 3 +--- +apples: great +` + filename := writeTempYamlFile(content) + defer removeTempYamlFile(filename) + + cmd := getRootCommand() + result := runCmd(cmd, fmt.Sprintf("prefix %s -d * d", filename)) + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `d: + b: + c: 3 +--- +d: + apples: great` + assertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) +} + +func TestPrefixCmd_Error(t *testing.T) { + cmd := getRootCommand() + result := runCmd(cmd, "prefix") + if result.Error == nil { + t.Error("Expected command to fail due to missing arg") + } + expectedOutput := `Must provide ` + assertResult(t, expectedOutput, result.Error.Error()) +} + +func TestPrefixCmd_ErrorUnreadableFile(t *testing.T) { + cmd := getRootCommand() + result := runCmd(cmd, "prefix fake-unknown a.b") + if result.Error == nil { + t.Error("Expected command to fail due to unknown file") + } + expectedOutput := `open fake-unknown: no such file or directory` + assertResult(t, expectedOutput, result.Error.Error()) +} + +func TestPrefixCmd_Verbose(t *testing.T) { + content := `b: + c: 3 +` + filename := writeTempYamlFile(content) + defer removeTempYamlFile(filename) + + cmd := getRootCommand() + result := runCmd(cmd, fmt.Sprintf("-v prefix %s x", filename)) + if result.Error != nil { + t.Error(result.Error) + } + expectedOutput := `x: + b: + c: 3 +` + assertResult(t, expectedOutput, result.Output) +} + +func TestPrefixCmd_Inplace(t *testing.T) { + content := `b: + c: 3 +` + filename := writeTempYamlFile(content) + defer removeTempYamlFile(filename) + + cmd := getRootCommand() + result := runCmd(cmd, fmt.Sprintf("prefix -i %s d", filename)) + if result.Error != nil { + t.Error(result.Error) + } + gotOutput := readTempYamlFile(filename) + expectedOutput := `d: + b: + c: 3` + assertResult(t, expectedOutput, strings.Trim(gotOutput, "\n ")) +} + func TestNewCmd(t *testing.T) { cmd := getRootCommand() result := runCmd(cmd, "new b.c 3") diff --git a/mkdocs/prefix.md b/mkdocs/prefix.md new file mode 100644 index 00000000..0e0995fd --- /dev/null +++ b/mkdocs/prefix.md @@ -0,0 +1,96 @@ +Paths can be prefixed using the 'prefix' command. +The complete yaml content will be nested inside the new prefix path. + +``` +yq p +``` + +### To Stdout +Given a data1.yaml file of: +```yaml +a: simple +b: [1, 2] +``` +then +```bash +yq p data1.yaml c +``` +will output: +```yaml +c: + a: simple + b: [1, 2] +``` + +### Arbitrary depth +Given a data1.yaml file of: +```yaml +a: + b: [1, 2] +``` +then +```bash +yq p data1.yaml c.d +``` +will output: +```yaml +c: + d: + a: + b: [1, 2] +``` + +### Updating files in-place +Given a data1.yaml file of: +```yaml +a: simple +b: [1, 2] +``` +then +```bash +yq p -i data1.yaml c +``` +will update the data1.yaml file so that the path 'c' is prefixed to all other paths. + +### Multiple Documents - update a single document +Given a data1.yaml file of: +```yaml +something: else +--- +a: simple +b: cat +``` +then +```bash +yq p -d1 data1.yaml c +``` +will output: +```yaml +something: else +--- +c: + a: simple + b: cat +``` + +### Multiple Documents - update a single document +Given a data1.yaml file of: +```yaml +something: else +--- +a: simple +b: cat +``` +then +```bash +yq p -d'*' data1.yaml c +``` +will output: +```yaml +c: + something: else +--- +c: + a: simple + b: cat +``` diff --git a/yq.go b/yq.go index 2e403f89..293c731e 100644 --- a/yq.go +++ b/yq.go @@ -73,6 +73,7 @@ func newCommandCLI() *cobra.Command { rootCmd.AddCommand( createReadCmd(), createWriteCmd(), + createPrefixCmd(), createDeleteCmd(), createNewCmd(), createMergeCmd(), @@ -137,6 +138,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 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 createDeleteCmd() *cobra.Command { var cmdDelete = &cobra.Command{ Use: "delete [yaml_file] [path]", @@ -394,6 +417,40 @@ func writeProperty(cmd *cobra.Command, args []string) error { return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) } +func prefixProperty(cmd *cobra.Command, args []string) error { + if len(args) != 2 { + return errors.New("Must provide ") + } + var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() + if errorParsingDocIndex != nil { + return errorParsingDocIndex + } + + var paths = parsePath(args[1]) + + // Inverse order + for i := len(paths)/2 - 1; i >= 0; i-- { + opp := len(paths) - 1 - i + paths[i], paths[opp] = paths[opp], paths[i] + } + + var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { + + if updateAll || currentIndex == docIndexInt { + log.Debugf("Prefixing %v to doc %v", paths, currentIndex) + var mapDataBucket = dataBucket + for _, key := range paths { + nestedBucket := make(map[string]interface{}) + nestedBucket[key] = mapDataBucket + mapDataBucket = nestedBucket + } + 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