Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ yq -P -oy sample.json
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredPropertiesPreferences.KeyValueSeparator, "properties-separator", yqlib.ConfiguredPropertiesPreferences.KeyValueSeparator, "separator to use between keys and values")
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredPropertiesPreferences.UseArrayBrackets, "properties-array-brackets", yqlib.ConfiguredPropertiesPreferences.UseArrayBrackets, "use [x] in array paths (e.g. for SpringBoot)")

rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredShellVariablesPreferences.KeySeparator, "shell-key-separator", yqlib.ConfiguredShellVariablesPreferences.KeySeparator, "separator for shell variable key paths")
if err = rootCmd.RegisterFlagCompletionFunc("shell-key-separator", cobra.NoFileCompletions); err != nil {
panic(err)
}

rootCmd.PersistentFlags().BoolVar(&yqlib.StringInterpolationEnabled, "string-interpolation", yqlib.StringInterpolationEnabled, "Toggles strings interpolation of \\(exp)")

rootCmd.PersistentFlags().BoolVarP(&nullInput, "null-input", "n", false, "Don't read input, simply evaluate the expression given. Useful for creating docs from scratch.")
Expand Down
20 changes: 20 additions & 0 deletions pkg/yqlib/doc/usage/shellvariables.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,23 @@ will output
name='Miles O'"'"'Brien'
```

## Encode shell variables: custom separator
Use --shell-key-separator to specify a custom separator between keys. This is useful when the original keys contain underscores.

Given a sample.yml file of:
```yaml
my_app:
db_config:
host: localhost
port: 5432
```
then
```bash
yq -o=shell --shell-key-separator="__" sample.yml
```
will output
```sh
my_app__db_config__host=localhost
my_app__db_config__port=5432
```

13 changes: 8 additions & 5 deletions pkg/yqlib/encoder_shellvariables.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ import (
)

type shellVariablesEncoder struct {
prefs ShellVariablesPreferences
}

func NewShellVariablesEncoder() Encoder {
return &shellVariablesEncoder{}
return &shellVariablesEncoder{
prefs: ConfiguredShellVariablesPreferences,
}
}

func (pe *shellVariablesEncoder) CanHandleAliases() bool {
Expand Down Expand Up @@ -58,7 +61,7 @@ func (pe *shellVariablesEncoder) doEncode(w *io.Writer, node *CandidateNode, pat
return err
case SequenceNode:
for index, child := range node.Content {
err := pe.doEncode(w, child, appendPath(path, index))
err := pe.doEncode(w, child, pe.appendPath(path, index))
if err != nil {
return err
}
Expand All @@ -68,7 +71,7 @@ func (pe *shellVariablesEncoder) doEncode(w *io.Writer, node *CandidateNode, pat
for index := 0; index < len(node.Content); index = index + 2 {
key := node.Content[index]
value := node.Content[index+1]
err := pe.doEncode(w, value, appendPath(path, key.Value))
err := pe.doEncode(w, value, pe.appendPath(path, key.Value))
if err != nil {
return err
}
Expand All @@ -81,7 +84,7 @@ func (pe *shellVariablesEncoder) doEncode(w *io.Writer, node *CandidateNode, pat
}
}

func appendPath(cookedPath string, rawKey interface{}) string {
func (pe *shellVariablesEncoder) appendPath(cookedPath string, rawKey interface{}) string {

// Shell variable names must match
// [a-zA-Z_]+[a-zA-Z0-9_]*
Expand Down Expand Up @@ -126,7 +129,7 @@ func appendPath(cookedPath string, rawKey interface{}) string {
}
return key
}
return cookedPath + "_" + key
return cookedPath + pe.prefs.KeySeparator + key
}

func quoteValue(value string) string {
Expand Down
44 changes: 44 additions & 0 deletions pkg/yqlib/encoder_shellvariables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,47 @@ func TestShellVariablesEncoderEmptyMap(t *testing.T) {
func TestShellVariablesEncoderScalarNode(t *testing.T) {
assertEncodesTo(t, "some string", "value='some string'")
}

func assertEncodesToWithSeparator(t *testing.T, yaml string, shellvars string, separator string) {
var output bytes.Buffer
writer := bufio.NewWriter(&output)

// Save the original separator
originalSeparator := ConfiguredShellVariablesPreferences.KeySeparator
defer func() {
ConfiguredShellVariablesPreferences.KeySeparator = originalSeparator
}()

// Set the custom separator
ConfiguredShellVariablesPreferences.KeySeparator = separator

var encoder = NewShellVariablesEncoder()
inputs, err := readDocuments(strings.NewReader(yaml), "test.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
if err != nil {
panic(err)
}
node := inputs.Front().Value.(*CandidateNode)
err = encoder.Encode(writer, node)
if err != nil {
panic(err)
}
writer.Flush()

test.AssertResult(t, shellvars, strings.TrimSuffix(output.String(), "\n"))
}

func TestShellVariablesEncoderCustomSeparator(t *testing.T) {
assertEncodesToWithSeparator(t, "a:\n b: Lewis\n c: Carroll", "a__b=Lewis\na__c=Carroll", "__")
}

func TestShellVariablesEncoderCustomSeparatorNested(t *testing.T) {
assertEncodesToWithSeparator(t, "my_app:\n db_config:\n host: localhost", "my_app__db_config__host=localhost", "__")
}

func TestShellVariablesEncoderCustomSeparatorArray(t *testing.T) {
assertEncodesToWithSeparator(t, "a: [{n: Alice}, {n: Bob}]", "a__0__n=Alice\na__1__n=Bob", "__")
}

func TestShellVariablesEncoderCustomSeparatorSingleChar(t *testing.T) {
assertEncodesToWithSeparator(t, "a:\n b: value", "aXb=value", "X")
}
14 changes: 14 additions & 0 deletions pkg/yqlib/shellvariables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package yqlib

type ShellVariablesPreferences struct {
KeySeparator string
}

func NewDefaultShellVariablesPreferences() ShellVariablesPreferences {
return ShellVariablesPreferences{
KeySeparator: "_",
}
}

var ConfiguredShellVariablesPreferences = NewDefaultShellVariablesPreferences()

37 changes: 34 additions & 3 deletions pkg/yqlib/shellvariables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,33 @@ var shellVariablesScenarios = []formatScenario{
input: "name: Miles O'Brien",
expected: `name='Miles O'"'"'Brien'` + "\n",
},
{
description: "Encode shell variables: custom separator",
subdescription: "Use --shell-key-separator to specify a custom separator between keys. This is useful when the original keys contain underscores.",
input: "" +
"my_app:" + "\n" +
" db_config:" + "\n" +
" host: localhost" + "\n" +
" port: 5432",
expected: "" +
"my_app__db_config__host=localhost" + "\n" +
"my_app__db_config__port=5432" + "\n",
scenarioType: "shell-separator",
},
}

func TestShellVariableScenarios(t *testing.T) {
for _, s := range shellVariablesScenarios {
//fmt.Printf("\t<%s> <%s>\n", s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder()))
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder()), s.description)
if s.scenarioType == "shell-separator" {
// Save and restore the original separator
originalSeparator := ConfiguredShellVariablesPreferences.KeySeparator
ConfiguredShellVariablesPreferences.KeySeparator = "__"
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder()), s.description)
ConfiguredShellVariablesPreferences.KeySeparator = originalSeparator
} else {
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder()), s.description)
}
}
genericScenarios := make([]interface{}, len(shellVariablesScenarios))
for i, s := range shellVariablesScenarios {
Expand Down Expand Up @@ -87,12 +108,22 @@ func documentShellVariableScenario(_ *testing.T, w *bufio.Writer, i interface{})

expression := s.expression

if expression != "" {
if s.scenarioType == "shell-separator" {
writeOrPanic(w, "```bash\nyq -o=shell --shell-key-separator=\"__\" sample.yml\n```\n")
} else if expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq -o=shell '%v' sample.yml\n```\n", expression))
} else {
writeOrPanic(w, "```bash\nyq -o=shell sample.yml\n```\n")
}
writeOrPanic(w, "will output\n")

writeOrPanic(w, fmt.Sprintf("```sh\n%v```\n\n", mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder())))
if s.scenarioType == "shell-separator" {
// Save and restore the original separator
originalSeparator := ConfiguredShellVariablesPreferences.KeySeparator
ConfiguredShellVariablesPreferences.KeySeparator = "__"
writeOrPanic(w, fmt.Sprintf("```sh\n%v```\n\n", mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder())))
ConfiguredShellVariablesPreferences.KeySeparator = originalSeparator
} else {
writeOrPanic(w, fmt.Sprintf("```sh\n%v```\n\n", mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewShellVariablesEncoder())))
}
}