1
0
mirror of https://github.com/taigrr/yq synced 2025-01-18 04:53:17 -08:00

Compare commits

...

9 Commits

Author SHA1 Message Date
Mike Farah
1b887e23b3 scripts/check works for local and docker build 2020-12-30 10:40:41 +11:00
Chris Warth
a76b72e691 find golangci_lint through PATH 2020-12-30 10:30:53 +11:00
Mike Farah
9509831cff Updated docs 2020-12-29 22:35:57 +11:00
Mike Farah
e92180e89d updated release instructions 2020-12-29 09:59:16 +11:00
Mike Farah
a6ae33c3f1 Cleaning up release process, fixed github action version 2020-12-29 09:50:21 +11:00
Mike Farah
94a563dfd8 Updated docs 2020-12-28 11:57:20 +11:00
Mike Farah
0328cfd619 Added prettyPrint flag 2020-12-28 11:40:41 +11:00
Mike Farah
88663a6ce3 Added recurse keys operator 2020-12-28 11:24:42 +11:00
Mike Farah
b10a9ccfc6 Removed TraversePrefs 2020-12-28 10:29:43 +11:00
28 changed files with 313 additions and 126 deletions

View File

@@ -68,7 +68,9 @@ jobs:
- name: Build and push image - name: Build and push image
run: | run: |
IMAGE_VERSION="$(git describe --tags --abbrev=0)" IMAGE_V_VERSION="$(git describe --tags --abbrev=0)"
IMAGE_VERSION=${IMAGE_V_VERSION:1}
SHORT_SHA1=$(git rev-parse --short HEAD) SHORT_SHA1=$(git rev-parse --short HEAD)
PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64" PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64"
echo "Building and pushing version ${IMAGE_VERSION} of image ${IMAGE_NAME}" echo "Building and pushing version ${IMAGE_VERSION} of image ${IMAGE_NAME}"

View File

@@ -4,6 +4,7 @@ COPY scripts/devtools.sh /opt/devtools.sh
RUN set -e -x \ RUN set -e -x \
&& /opt/devtools.sh && /opt/devtools.sh
ENV PATH=/go/bin:$PATH
# install mkdocs # install mkdocs
RUN set -ex \ RUN set -ex \

View File

@@ -158,6 +158,7 @@ Flags:
-M, --no-colors force print with no colors -M, --no-colors force print with no colors
-N, --no-doc Don't print document separators (---) -N, --no-doc Don't print document separators (---)
-n, --null-input Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch. -n, --null-input Don't read input, simply evaluate the expression given. Useful for creating yaml docs from scratch.
-P, --prettyPrint pretty print, shorthand for '... style = ""'
-j, --tojson output as json. Set indent to 0 to print json in one line. -j, --tojson output as json. Set indent to 0 to print json in one line.
-v, --verbose verbose mode -v, --verbose verbose mode
-V, --version Print version information and quit -V, --version Print version information and quit

View File

@@ -14,5 +14,6 @@ var noDocSeparators = false
var nullInput = false var nullInput = false
var verbose = false var verbose = false
var version = false var version = false
var prettyPrint = false
var completedSuccessfully = false var completedSuccessfully = false

View File

@@ -65,19 +65,19 @@ func evaluateAll(cmd *cobra.Command, args []string) error {
switch len(args) { switch len(args) {
case 0: case 0:
if pipingStdIn { if pipingStdIn {
err = allAtOnceEvaluator.EvaluateFiles("", []string{"-"}, printer) err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer)
} else { } else {
cmd.Println(cmd.UsageString()) cmd.Println(cmd.UsageString())
return nil return nil
} }
case 1: case 1:
if nullInput { if nullInput {
err = yqlib.NewStreamEvaluator().EvaluateNew(args[0], printer) err = yqlib.NewStreamEvaluator().EvaluateNew(processExpression(args[0]), printer)
} else { } else {
err = allAtOnceEvaluator.EvaluateFiles("", []string{args[0]}, printer) err = allAtOnceEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
} }
default: default:
err = allAtOnceEvaluator.EvaluateFiles(args[0], args[1:], printer) err = allAtOnceEvaluator.EvaluateFiles(processExpression(args[0]), args[1:], printer)
} }
completedSuccessfully = err == nil completedSuccessfully = err == nil

View File

@@ -33,6 +33,16 @@ yq e '.a.b = "cool"' -i file.yaml
} }
return cmdEvalSequence return cmdEvalSequence
} }
func processExpression(expression string) string {
if prettyPrint && expression == "" {
return `... style=""`
} else if prettyPrint {
return fmt.Sprintf("%v | ... style= \"\"", expression)
}
return expression
}
func evaluateSequence(cmd *cobra.Command, args []string) error { func evaluateSequence(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true cmd.SilenceUsage = true
// 0 args, read std in // 0 args, read std in
@@ -76,16 +86,16 @@ func evaluateSequence(cmd *cobra.Command, args []string) error {
switch len(args) { switch len(args) {
case 0: case 0:
if pipingStdIn { if pipingStdIn {
err = streamEvaluator.EvaluateFiles("", []string{"-"}, printer) err = streamEvaluator.EvaluateFiles(processExpression(""), []string{"-"}, printer)
} else { } else {
cmd.Println(cmd.UsageString()) cmd.Println(cmd.UsageString())
return nil return nil
} }
case 1: case 1:
if nullInput { if nullInput {
err = streamEvaluator.EvaluateNew(args[0], printer) err = streamEvaluator.EvaluateNew(processExpression(args[0]), printer)
} else { } else {
err = streamEvaluator.EvaluateFiles("", []string{args[0]}, printer) err = streamEvaluator.EvaluateFiles(processExpression(""), []string{args[0]}, printer)
} }
default: default:
err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer) err = streamEvaluator.EvaluateFiles(args[0], args[1:], printer)

View File

@@ -48,6 +48,7 @@ func New() *cobra.Command {
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.") rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace of first yaml file given.")
rootCmd.PersistentFlags().BoolVarP(&unwrapScalar, "unwrapScalar", "", true, "unwrap scalar, print the value with no quotes, colors or comments") rootCmd.PersistentFlags().BoolVarP(&unwrapScalar, "unwrapScalar", "", true, "unwrap scalar, print the value with no quotes, colors or comments")
rootCmd.PersistentFlags().BoolVarP(&prettyPrint, "prettyPrint", "P", false, "pretty print, shorthand for '... style = \"\"'")
rootCmd.PersistentFlags().BoolVarP(&exitStatus, "exit-status", "e", false, "set exit status if there are no matches or null or false is returned") rootCmd.PersistentFlags().BoolVarP(&exitStatus, "exit-status", "e", false, "set exit status if there are no matches or null or false is returned")
rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors") rootCmd.PersistentFlags().BoolVarP(&forceColor, "colors", "C", false, "force print with colors")

View File

@@ -11,7 +11,7 @@ var (
GitDescribe string GitDescribe string
// Version is main version number that is being run at the moment. // Version is main version number that is being run at the moment.
Version = "4.1.0" Version = "4.2.0"
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string) // VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release // then it means that it is a final release. Otherwise, this is a pre-release

View File

@@ -1,4 +1,4 @@
FROM mikefarah/yq:3 FROM mikefarah/yq:4.2.0
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh

View File

@@ -26,7 +26,7 @@ a: frog
``` ```
then then
```bash ```bash
yq eval 'select(. | documentIndex == 1)' sample.yml yq eval 'select(documentIndex == 1)' sample.yml
``` ```
will output will output
```yaml ```yaml
@@ -42,7 +42,7 @@ a: frog
``` ```
then then
```bash ```bash
yq eval '.a | ({"match": ., "doc": (. | documentIndex)})' sample.yml yq eval '.a | ({"match": ., "doc": documentIndex})' sample.yml
``` ```
will output will output
```yaml ```yaml

View File

@@ -1,8 +1,55 @@
This operator recursively matches all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches, for instance to set the `style` of all nodes in a yaml doc: This operator recursively matches all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches. It can be used in either the
## match values form `..`
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
For instance to set the `style` of all _value_ nodes in a yaml doc, excluding map keys:
```bash ```bash
yq eval '.. style= "flow"' file.yaml yq eval '.. style= "flow"' file.yaml
``` ```
## match values and map keys form `...`
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling, tags and use anchors and aliases.
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
```bash
yq eval '... style= "flow"' file.yaml
```
## Recurse map (values only)
Given a sample.yml file of:
```yaml
a: frog
```
then
```bash
yq eval '..' sample.yml
```
will output
```yaml
a: frog
frog
```
## Recurse map (values and keys)
Note that the map key appears in the results
Given a sample.yml file of:
```yaml
a: frog
```
then
```bash
yq eval '...' sample.yml
```
will output
```yaml
a: frog
a
frog
```
## Aliases are not traversed ## Aliases are not traversed
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@@ -40,6 +40,26 @@ c: "3.2"
e: "true" e: "true"
``` ```
## Set double quote style on map keys too
Given a sample.yml file of:
```yaml
a: cat
b: 5
c: 3.2
e: true
```
then
```bash
yq eval '... style="double"' sample.yml
```
will output
```yaml
"a": "cat"
"b": "5"
"c": "3.2"
"e": "true"
```
## Set single quote style ## Set single quote style
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -126,18 +146,18 @@ will output
``` ```
## Pretty print ## Pretty print
Set empty (default) quote style Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat a: cat
b: 5 "b": 5
c: 3.2 'c': 3.2
e: true "e": true
``` ```
then then
```bash ```bash
yq eval '.. style=""' sample.yml yq eval '... style=""' sample.yml
``` ```
will output will output
```yaml ```yaml

View File

@@ -1,5 +1,19 @@
This operator recursively matches all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches, for instance to set the `style` of all nodes in a yaml doc: This operator recursively matches all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches. It can be used in either the
## match values form `..`
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
For instance to set the `style` of all _value_ nodes in a yaml doc, excluding map keys:
```bash ```bash
yq eval '.. style= "flow"' file.yaml yq eval '.. style= "flow"' file.yaml
``` ```
## match values and map keys form `...`
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling, tags and use anchors and aliases.
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
```bash
yq eval '... style= "flow"' file.yaml
```

View File

@@ -19,7 +19,7 @@ type OperationType struct {
} }
// operators TODO: // operators TODO:
// - cookbook doc for common things // - keys operator for controlling key metadata (particularly anchors/aliases)
// - mergeEmpty (sets only if the document is empty, do I do that now?) // - mergeEmpty (sets only if the document is empty, do I do that now?)
var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator} var Or = &OperationType{Type: "OR", NumArgs: 2, Precedence: 20, Handler: OrOperator}

View File

@@ -67,7 +67,9 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list.
} }
candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode) candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode)
splatted, err := Splat(d, nodeToMap(candidate))
splatted, err := Splat(d, nodeToMap(candidate),
&TraversePreferences{FollowAlias: false, IncludeMapKeys: false})
for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() { for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() {
splatEl.Value.(*CandidateNode).Path = nil splatEl.Value.(*CandidateNode).Path = nil

View File

@@ -17,7 +17,7 @@ var documentIndexScenarios = []expressionScenario{
{ {
description: "Filter by document index", description: "Filter by document index",
document: "a: cat\n---\na: frog\n", document: "a: cat\n---\na: frog\n",
expression: `select(. | documentIndex == 1)`, expression: `select(documentIndex == 1)`,
expected: []string{ expected: []string{
"D1, P[], (doc)::a: frog\n", "D1, P[], (doc)::a: frog\n",
}, },
@@ -25,7 +25,7 @@ var documentIndexScenarios = []expressionScenario{
{ {
description: "Print Document Index with matches", description: "Print Document Index with matches",
document: "a: cat\n---\na: frog\n", document: "a: cat\n---\na: frog\n",
expression: `.a | ({"match": ., "doc": (. | documentIndex)})`, expression: `.a | ({"match": ., "doc": documentIndex})`,
expected: []string{ expected: []string{
"D0, P[], (!!map)::match: cat\ndoc: 0\n", "D0, P[], (!!map)::match: cat\ndoc: 0\n",
"D0, P[], (!!map)::match: frog\ndoc: 1\n", "D0, P[], (!!map)::match: frog\ndoc: 1\n",

View File

@@ -85,7 +85,9 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode,
var results = list.New() var results = list.New()
// shouldn't recurse arrays if appending // shouldn't recurse arrays if appending
err := recursiveDecent(d, results, nodeToMap(rhs), !shouldAppendArrays) prefs := &RecursiveDescentPreferences{RecurseArray: !shouldAppendArrays,
TraversePreferences: &TraversePreferences{FollowAlias: false}}
err := recursiveDecent(d, results, nodeToMap(rhs), prefs)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -6,10 +6,16 @@ import (
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
type RecursiveDescentPreferences struct {
TraversePreferences *TraversePreferences
RecurseArray bool
}
func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
var results = list.New() var results = list.New()
err := recursiveDecent(d, results, matchMap, true) preferences := pathNode.Operation.Preferences.(*RecursiveDescentPreferences)
err := recursiveDecent(d, results, matchMap, preferences)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -17,7 +23,7 @@ func RecursiveDescentOperator(d *dataTreeNavigator, matchMap *list.List, pathNod
return results, nil return results, nil
} }
func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, recurseArray bool) error { func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.List, preferences *RecursiveDescentPreferences) error {
for el := matchMap.Front(); el != nil; el = el.Next() { for el := matchMap.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
@@ -27,14 +33,14 @@ func recursiveDecent(d *dataTreeNavigator, results *list.List, matchMap *list.Li
results.PushBack(candidate) results.PushBack(candidate)
if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 && if candidate.Node.Kind != yaml.AliasNode && len(candidate.Node.Content) > 0 &&
(recurseArray || candidate.Node.Kind != yaml.SequenceNode) { (preferences.RecurseArray || candidate.Node.Kind != yaml.SequenceNode) {
children, err := Splat(d, nodeToMap(candidate)) children, err := Splat(d, nodeToMap(candidate), preferences.TraversePreferences)
if err != nil { if err != nil {
return err return err
} }
err = recursiveDecent(d, results, children, recurseArray) err = recursiveDecent(d, results, children, preferences)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -13,6 +13,14 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::{}\n", "D0, P[], (!!map)::{}\n",
}, },
}, },
{
skipDoc: true,
document: `{}`,
expression: `...`,
expected: []string{
"D0, P[], (!!map)::{}\n",
},
},
{ {
skipDoc: true, skipDoc: true,
document: `[]`, document: `[]`,
@@ -21,6 +29,14 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::[]\n", "D0, P[], (!!seq)::[]\n",
}, },
}, },
{
skipDoc: true,
document: `[]`,
expression: `...`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
{ {
skipDoc: true, skipDoc: true,
document: `cat`, document: `cat`,
@@ -31,6 +47,14 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
}, },
{ {
skipDoc: true, skipDoc: true,
document: `cat`,
expression: `...`,
expected: []string{
"D0, P[], (!!str)::cat\n",
},
},
{
description: "Recurse map (values only)",
document: `{a: frog}`, document: `{a: frog}`,
expression: `..`, expression: `..`,
expected: []string{ expected: []string{
@@ -38,6 +62,17 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[a], (!!str)::frog\n", "D0, P[a], (!!str)::frog\n",
}, },
}, },
{
description: "Recurse map (values and keys)",
subdescription: "Note that the map key appears in the results",
document: `{a: frog}`,
expression: `...`,
expected: []string{
"D0, P[], (!!map)::{a: frog}\n",
"D0, P[a], (!!str)::a\n",
"D0, P[a], (!!str)::frog\n",
},
},
{ {
skipDoc: true, skipDoc: true,
document: `{a: {b: apple}}`, document: `{a: {b: apple}}`,
@@ -48,6 +83,18 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[a b], (!!str)::apple\n", "D0, P[a b], (!!str)::apple\n",
}, },
}, },
{
skipDoc: true,
document: `{a: {b: apple}}`,
expression: `...`,
expected: []string{
"D0, P[], (!!map)::{a: {b: apple}}\n",
"D0, P[a], (!!str)::a\n",
"D0, P[a], (!!map)::{b: apple}\n",
"D0, P[a b], (!!str)::b\n",
"D0, P[a b], (!!str)::apple\n",
},
},
{ {
skipDoc: true, skipDoc: true,
document: `[1,2,3]`, document: `[1,2,3]`,
@@ -59,6 +106,17 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[2], (!!int)::3\n", "D0, P[2], (!!int)::3\n",
}, },
}, },
{
skipDoc: true,
document: `[1,2,3]`,
expression: `...`,
expected: []string{
"D0, P[], (!!seq)::[1, 2, 3]\n",
"D0, P[0], (!!int)::1\n",
"D0, P[1], (!!int)::2\n",
"D0, P[2], (!!int)::3\n",
},
},
{ {
skipDoc: true, skipDoc: true,
document: `[{a: cat},2,true]`, document: `[{a: cat},2,true]`,
@@ -71,6 +129,19 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[2], (!!bool)::true\n", "D0, P[2], (!!bool)::true\n",
}, },
}, },
{
skipDoc: true,
document: `[{a: cat},2,true]`,
expression: `...`,
expected: []string{
"D0, P[], (!!seq)::[{a: cat}, 2, true]\n",
"D0, P[0], (!!map)::{a: cat}\n",
"D0, P[0 a], (!!str)::a\n",
"D0, P[0 a], (!!str)::cat\n",
"D0, P[1], (!!int)::2\n",
"D0, P[2], (!!bool)::true\n",
},
},
{ {
description: "Aliases are not traversed", description: "Aliases are not traversed",
document: `{a: &cat {c: frog}, b: *cat}`, document: `{a: &cat {c: frog}, b: *cat}`,
@@ -79,6 +150,20 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[a], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n", "D0, P[a], (!!seq)::- {a: &cat {c: frog}, b: *cat}\n- &cat {c: frog}\n- frog\n- *cat\n",
}, },
}, },
{
skipDoc: true,
document: `{a: &cat {c: frog}, b: *cat}`,
expression: `...`,
expected: []string{
"D0, P[], (!!map)::{a: &cat {c: frog}, b: *cat}\n",
"D0, P[a], (!!str)::a\n",
"D0, P[a], (!!map)::&cat {c: frog}\n",
"D0, P[a c], (!!str)::c\n",
"D0, P[a c], (!!str)::frog\n",
"D0, P[b], (!!str)::b\n",
"D0, P[b], (alias)::*cat\n",
},
},
{ {
description: "Merge docs are not traversed", description: "Merge docs are not traversed",
document: mergeDocSample, document: mergeDocSample,
@@ -87,6 +172,14 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- foobar_c\n- *foo\n- foobar_thing\n", "D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- foobar_c\n- *foo\n- foobar_thing\n",
}, },
}, },
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobar | [...]`,
expected: []string{
"D0, P[foobar], (!!seq)::- c: foobar_c\n !!merge <<: *foo\n thing: foobar_thing\n- c\n- foobar_c\n- !!merge <<\n- *foo\n- thing\n- foobar_thing\n",
},
},
{ {
skipDoc: true, skipDoc: true,
document: mergeDocSample, document: mergeDocSample,
@@ -100,6 +193,22 @@ var recursiveDescentOperatorScenarios = []expressionScenario{
"D0, P[foobarList c], (!!str)::foobarList_c\n", "D0, P[foobarList c], (!!str)::foobarList_c\n",
}, },
}, },
{
skipDoc: true,
document: mergeDocSample,
expression: `.foobarList | ...`,
expected: []string{
"D0, P[foobarList], (!!map)::b: foobarList_b\n!!merge <<: [*foo, *bar]\nc: foobarList_c\n",
"D0, P[foobarList b], (!!str)::b\n",
"D0, P[foobarList b], (!!str)::foobarList_b\n",
"D0, P[foobarList <<], (!!merge)::!!merge <<\n",
"D0, P[foobarList <<], (!!seq)::[*foo, *bar]\n",
"D0, P[foobarList << 0], (alias)::*foo\n",
"D0, P[foobarList << 1], (alias)::*bar\n",
"D0, P[foobarList c], (!!str)::c\n",
"D0, P[foobarList c], (!!str)::foobarList_c\n",
},
},
} }
func TestRecursiveDescentOperatorScenarios(t *testing.T) { func TestRecursiveDescentOperatorScenarios(t *testing.T) {

View File

@@ -21,6 +21,22 @@ var styleOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::a: \"cat\"\nb: \"5\"\nc: \"3.2\"\ne: \"true\"\n", "D0, P[], (!!map)::a: \"cat\"\nb: \"5\"\nc: \"3.2\"\ne: \"true\"\n",
}, },
}, },
{
description: "Set double quote style on map keys too",
document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `... style="double"`,
expected: []string{
"D0, P[], (!!map)::\"a\": \"cat\"\n\"b\": \"5\"\n\"c\": \"3.2\"\n\"e\": \"true\"\n",
},
},
{
skipDoc: true,
document: "bing: &foo frog\na:\n c: cat\n <<: [*foo]",
expression: `(... | select(tag=="!!str")) style="single"`,
expected: []string{
"D0, P[], (!!map)::'bing': &foo 'frog'\n'a':\n 'c': 'cat'\n !!merge <<: [*foo]\n",
},
},
{ {
description: "Set single quote style", description: "Set single quote style",
document: `{a: cat, b: 5, c: 3.2, e: true}`, document: `{a: cat, b: 5, c: 3.2, e: true}`,
@@ -71,9 +87,9 @@ e: >-
}, },
{ {
description: "Pretty print", description: "Pretty print",
subdescription: "Set empty (default) quote style", subdescription: "Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.",
document: `{a: cat, b: 5, c: 3.2, e: true}`, document: `{a: cat, "b": 5, 'c': 3.2, "e": true}`,
expression: `.. style=""`, expression: `... style=""`,
expected: []string{ expected: []string{
"D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n", "D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n",
}, },

View File

@@ -10,11 +10,12 @@ import (
) )
type TraversePreferences struct { type TraversePreferences struct {
DontFollowAlias bool FollowAlias bool
IncludeMapKeys bool
} }
func Splat(d *dataTreeNavigator, matches *list.List) (*list.List, error) { func Splat(d *dataTreeNavigator, matches *list.List, prefs *TraversePreferences) (*list.List, error) {
return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), false) return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), prefs)
} }
func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) { func TraversePathOperator(d *dataTreeNavigator, matchMap *list.List, pathNode *PathTreeNode) (*list.List, error) {
@@ -53,12 +54,8 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper
switch value.Kind { switch value.Kind {
case yaml.MappingNode: case yaml.MappingNode:
log.Debug("its a map with %v entries", len(value.Content)/2) log.Debug("its a map with %v entries", len(value.Content)/2)
followAlias := true prefs := &TraversePreferences{FollowAlias: true}
return traverseMap(matchingNode, operation.StringValue, prefs, false)
if operation.Preferences != nil {
followAlias = !operation.Preferences.(*TraversePreferences).DontFollowAlias
}
return traverseMap(matchingNode, operation.StringValue, followAlias, false)
case yaml.SequenceNode: case yaml.SequenceNode:
log.Debug("its a sequence of %v things!", len(value.Content)) log.Debug("its a sequence of %v things!", len(value.Content))
@@ -89,15 +86,15 @@ func TraverseArrayOperator(d *dataTreeNavigator, matchingNodes *list.List, pathN
} }
var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content
prefs := &TraversePreferences{FollowAlias: true}
return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, true) return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, prefs)
} }
func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, followAlias bool) (*list.List, error) { func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, prefs *TraversePreferences) (*list.List, error) {
var matchingNodeMap = list.New() var matchingNodeMap = list.New()
for el := matchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
newNodes, err := traverseArrayIndices(candidate, indicesToTraverse, followAlias) newNodes, err := traverseArrayIndices(candidate, indicesToTraverse, prefs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -107,7 +104,7 @@ func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse [
return matchingNodeMap, nil return matchingNodeMap, nil
} }
func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, followAlias bool) (*list.List, error) { // call this if doc / alias like the other traverse func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs *TraversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse
node := matchingNode.Node node := matchingNode.Node
if node.Tag == "!!null" { if node.Tag == "!!null" {
log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array") log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array")
@@ -118,32 +115,32 @@ func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml
if node.Kind == yaml.AliasNode { if node.Kind == yaml.AliasNode {
matchingNode.Node = node.Alias matchingNode.Node = node.Alias
return traverseArrayIndices(matchingNode, indicesToTraverse, followAlias) return traverseArrayIndices(matchingNode, indicesToTraverse, prefs)
} else if node.Kind == yaml.SequenceNode { } else if node.Kind == yaml.SequenceNode {
return traverseArrayWithIndices(matchingNode, indicesToTraverse) return traverseArrayWithIndices(matchingNode, indicesToTraverse)
} else if node.Kind == yaml.MappingNode { } else if node.Kind == yaml.MappingNode {
return traverseMapWithIndices(matchingNode, indicesToTraverse, followAlias) return traverseMapWithIndices(matchingNode, indicesToTraverse, prefs)
} else if node.Kind == yaml.DocumentNode { } else if node.Kind == yaml.DocumentNode {
return traverseArrayIndices(&CandidateNode{ return traverseArrayIndices(&CandidateNode{
Node: matchingNode.Node.Content[0], Node: matchingNode.Node.Content[0],
Filename: matchingNode.Filename, Filename: matchingNode.Filename,
FileIndex: matchingNode.FileIndex, FileIndex: matchingNode.FileIndex,
Document: matchingNode.Document}, indicesToTraverse, followAlias) Document: matchingNode.Document}, indicesToTraverse, prefs)
} }
log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, node.Tag) log.Debugf("OperatorArrayTraverse skipping %v as its a %v", matchingNode, node.Tag)
return list.New(), nil return list.New(), nil
} }
func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, followAlias bool) (*list.List, error) { func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, prefs *TraversePreferences) (*list.List, error) {
if len(indices) == 0 { if len(indices) == 0 {
return traverseMap(candidate, "", followAlias, true) return traverseMap(candidate, "", prefs, true)
} }
var matchingNodeMap = list.New() var matchingNodeMap = list.New()
for _, indexNode := range indices { for _, indexNode := range indices {
log.Debug("traverseMapWithIndices: %v", indexNode.Value) log.Debug("traverseMapWithIndices: %v", indexNode.Value)
newNodes, err := traverseMap(candidate, indexNode.Value, followAlias, false) newNodes, err := traverseMap(candidate, indexNode.Value, prefs, false)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -206,9 +203,9 @@ func keyMatches(key *yaml.Node, wantedKey string) bool {
return Match(key.Value, wantedKey) return Match(key.Value, wantedKey)
} }
func traverseMap(matchingNode *CandidateNode, key string, followAlias bool, splat bool) (*list.List, error) { func traverseMap(matchingNode *CandidateNode, key string, prefs *TraversePreferences, splat bool) (*list.List, error) {
var newMatches = orderedmap.NewOrderedMap() var newMatches = orderedmap.NewOrderedMap()
err := doTraverseMap(newMatches, matchingNode, key, followAlias, splat) err := doTraverseMap(newMatches, matchingNode, key, prefs, splat)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -237,7 +234,7 @@ func traverseMap(matchingNode *CandidateNode, key string, followAlias bool, spla
return results, nil return results, nil
} }
func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, followAlias bool, splat bool) error { func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, prefs *TraversePreferences, splat bool) error {
// value.Content is a concatenated array of key, value, // value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd. // so keys are in the even indexes, values in odd.
// merge aliases are defined first, but we only want to traverse them // merge aliases are defined first, but we only want to traverse them
@@ -252,14 +249,22 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode,
log.Debug("checking %v (%v)", key.Value, key.Tag) log.Debug("checking %v (%v)", key.Value, key.Tag)
//skip the 'merge' tag, find a direct match first //skip the 'merge' tag, find a direct match first
if key.Tag == "!!merge" && followAlias { if key.Tag == "!!merge" && prefs.FollowAlias {
log.Debug("Merge anchor") log.Debug("Merge anchor")
err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, splat) err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, prefs, splat)
if err != nil { if err != nil {
return err return err
} }
} else if splat || keyMatches(key, wantedKey) { } else if splat || keyMatches(key, wantedKey) {
log.Debug("MATCHED") log.Debug("MATCHED")
if prefs.IncludeMapKeys {
candidateNode := &CandidateNode{
Node: key,
Path: candidate.CreateChildPath(key.Value),
Document: candidate.Document,
}
newMatches.Set(fmt.Sprintf("keyOf-%v", candidateNode.GetKey()), candidateNode)
}
candidateNode := &CandidateNode{ candidateNode := &CandidateNode{
Node: value, Node: value,
Path: candidate.CreateChildPath(key.Value), Path: candidate.CreateChildPath(key.Value),
@@ -272,7 +277,7 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode,
return nil return nil
} }
func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, splat bool) error { func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs *TraversePreferences, splat bool) error {
switch value.Kind { switch value.Kind {
case yaml.AliasNode: case yaml.AliasNode:
candidateNode := &CandidateNode{ candidateNode := &CandidateNode{
@@ -280,10 +285,10 @@ func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *C
Path: originalCandidate.Path, Path: originalCandidate.Path,
Document: originalCandidate.Document, Document: originalCandidate.Document,
} }
return doTraverseMap(newMatches, candidateNode, wantedKey, true, splat) return doTraverseMap(newMatches, candidateNode, wantedKey, prefs, splat)
case yaml.SequenceNode: case yaml.SequenceNode:
for _, childValue := range value.Content { for _, childValue := range value.Content {
err := traverseMergeAnchor(newMatches, originalCandidate, childValue, wantedKey, splat) err := traverseMergeAnchor(newMatches, originalCandidate, childValue, wantedKey, prefs, splat)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -173,7 +173,11 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\)`), literalToken(CloseBracket, true)) lexer.Add([]byte(`\)`), literalToken(CloseBracket, true))
lexer.Add([]byte(`\.\[`), literalToken(TraverseArrayCollect, false)) lexer.Add([]byte(`\.\[`), literalToken(TraverseArrayCollect, false))
lexer.Add([]byte(`\.\.`), opToken(RecursiveDescent)) lexer.Add([]byte(`\.\.`), opTokenWithPrefs(RecursiveDescent, nil, &RecursiveDescentPreferences{RecurseArray: true,
TraversePreferences: &TraversePreferences{FollowAlias: false, IncludeMapKeys: false}}))
lexer.Add([]byte(`\.\.\.`), opTokenWithPrefs(RecursiveDescent, nil, &RecursiveDescentPreferences{RecurseArray: true,
TraversePreferences: &TraversePreferences{FollowAlias: false, IncludeMapKeys: true}}))
lexer.Add([]byte(`,`), opToken(Union)) lexer.Add([]byte(`,`), opToken(Union))
lexer.Add([]byte(`:\s*`), opToken(CreateMap)) lexer.Add([]byte(`:\s*`), opToken(CreateMap))

View File

@@ -1,9 +1,11 @@
- increment version in version.go - increment version in version.go
- increment version in snapcraft.yaml - increment version in snapcraft.yaml
- increment version in github-action/Dockerfile
- make sure local build passes - make sure local build passes
- tag git with same version number - tag git with same version number
- commit vX tag - this will trigger github actions - commit vX tag - this will trigger github actions
- use github actions to publish docker and make github release - use github actions to publish docker and make github release
- check github updated yq action in marketplace
- snapcraft - snapcraft
- will auto create a candidate, test it works then promote - will auto create a candidate, test it works then promote

View File

@@ -3,7 +3,12 @@
set -o errexit set -o errexit
set -o pipefail set -o pipefail
if command -v golangci-lint &> /dev/null
then
golangci-lint run --timeout=5m
else
./bin/golangci-lint run --timeout=5m ./bin/golangci-lint run --timeout=5m
fi
# ./bin/golangci-lint \ # ./bin/golangci-lint \
# --tests \ # --tests \

View File

@@ -1,12 +0,0 @@
#!/bin/bash
set -ex
VERSION="$(git describe --tags --abbrev=0)"
docker build \
--target production \
--build-arg VERSION=${VERSION} \
-t mikefarah/yq:latest \
-t mikefarah/yq:${VERSION} \
-t mikefarah/yq:4 \
.
trivy image mikefarah/yq:${VERSION}

View File

@@ -1,21 +0,0 @@
#!/bin/bash
set -ex
GITHUB_TOKEN="${GITHUB_TOKEN:?missing required input \'GITHUB_TOKEN\'}"
CURRENT="$(git describe --tags --abbrev=0)"
PREVIOUS="$(git describe --tags --abbrev=0 --always "${CURRENT}"^)"
OWNER="mikefarah"
REPO="yq"
release() {
github-release release \
--user "$OWNER" \
--draft \
--repo "$REPO" \
--tag "$CURRENT"
}
release

View File

@@ -1,28 +0,0 @@
#!/bin/bash
set -ex
GITHUB_TOKEN="${GITHUB_TOKEN:?missing required input \'GITHUB_TOKEN\'}"
CURRENT="$(git describe --tags --abbrev=0)"
PREVIOUS="$(git describe --tags --abbrev=0 --always "${CURRENT}"^)"
OWNER="mikefarah"
REPO="yq"
upload() {
mkdir -p ./build-done
while IFS= read -r -d $'\0'; do
file=$REPLY
BINARY=$(basename "${file}")
echo "--> ${BINARY}"
github-release upload \
--replace \
--user "$OWNER" \
--repo "$REPO" \
--tag "$CURRENT" \
--name "${BINARY}" \
--file "$file"
mv "$file" "./build-done/${BINARY}"
done < <(find ./build -mindepth 1 -maxdepth 1 -print0)
}
upload

View File

@@ -1,5 +1,5 @@
name: yq name: yq
version: '4.1.0' version: '4.2.0'
summary: A lightweight and portable command-line YAML processor summary: A lightweight and portable command-line YAML processor
description: | description: |
The aim of the project is to be the jq or sed of yaml files. The aim of the project is to be the jq or sed of yaml files.