From 566972f20109429aa751be53f7df23d0f5e9ff89 Mon Sep 17 00:00:00 2001 From: Mike Farah Date: Wed, 3 Nov 2021 13:33:58 +1100 Subject: [PATCH] How it works --- README.md | 2 - SUMMARY.md | 1 + how-it-works.md | 121 ++++++++++++++++++++++++++++++++++++++++++++ operators/README.md | 117 ++---------------------------------------- 4 files changed, 125 insertions(+), 116 deletions(-) create mode 100644 how-it-works.md diff --git a/README.md b/README.md index 4a904332..51207708 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ description: yq is a lightweight and portable command-line YAML processor # yq - ![Build](https://github.com/mikefarah/yq/workflows/Build/badge.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/mikefarah/yq.svg) ![Github Releases (by Release)](https://img.shields.io/github/downloads/mikefarah/yq/total.svg) ![Go Report](https://goreportcard.com/badge/github.com/mikefarah/yq) - `yq` is a lightweight and portable command-line YAML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously. `yq` is written in go - so you can download a dependency free binary for your platform and you are good to go! If you prefer there are a variety of package managers that can be used as well as docker, all listed below. diff --git a/SUMMARY.md b/SUMMARY.md index ace60907..15f5d9b6 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -1,6 +1,7 @@ # Table of contents * [yq](README.md) +* [How It Works](how-it-works.md) * [Upgrading from V3](upgrading-from-v3.md) ## Commands diff --git a/how-it-works.md b/how-it-works.md new file mode 100644 index 00000000..a463883d --- /dev/null +++ b/how-it-works.md @@ -0,0 +1,121 @@ +# How it works + +In `yq` expressions are made up of operators and pipes. A context of nodes is passed through the expression and each operation takes the context as input and returns a new context as output. That output is piped in as input for the next operation in the expression. To begin with, the context is set to the first yaml document of the first yaml file (if processing in sequence using eval). + +Lets look at a couple of examples. + +## Simple assignment example + +Given a document like: + +```yaml +a: cat +b: dog +``` + +with an expression: + +``` +.a = .b +``` + +Like math expression - operator precedence is important. + +The `=` operator takes two arguments, a `lhs` expression, which in this case is `.a` and `rhs` expression which is `.b`. + +It pipes the current, lets call it 'root' context through the `lhs` expression of `.a` to return the node + +```yaml +cat +``` + +Sidenote: this node holds not only its value 'cat', but comments and metadata too, including path and parent information. + +The `=` operator then pipes the 'root' context through the `rhs` expression of `.b` to return the node + +```yaml +dog +``` + +Both sides have now been evaluated, so now the operator copies across the value from the RHS (`.b`) to the the LHS (`.a`), and it returns the now updated context: + +```yaml +a: dog +b: dog +``` + + +## Complex assignment, operator precedence rules + +Just like math expression - `yq` expression have an order of precedence. The pipe `|` operator has a low order of precedence, so operators with higher precedence will get evalated first. + +Most of the time, this is intuitively what you'd want, for instance `.a = "cat" | .b = "dog"` is effectively: `(.a = "cat") | (.b = "dog")`. + +However, this is not always the case, particularly if you have a complex LHS or RHS expression, for instance if you want to select particular nodes to update. + +Lets say you had: + +```yaml +- name: bob + fruit: apple +- name: sally + fruit: orange + +``` + +Lets say you wanted to update the `sally` entry to have fruit: 'mango'. The _incorrect_ way to do that is: +`.[] | select(.name == "sally") | .fruit = "mango"`. + +Becasue `|` has a low operator precedence, this will be evaluated (_incorrectly_) as : `(.[]) | (select(.name == "sally")) | (.fruit = "mango")`. What you'll see is only: + +```yaml +name: sally +fruit: mango +``` + +Returned :( + + +In this case, you will need to use brackets (think BODMAS from maths) and wrap the entire LHS, so the _correct_ expression is: +`(.[] | select(.name == "sally") | .fruit) = "mango"` + + +## Relative update (e.g. `|=`) +There is another form of the `=` operator which we call the relative form. It's very similar to `=` but with one key difference when evaluating the RHS expression. + +In the plain form, we pass in the 'root' level context to the RHS expression. In relative form, we pass in _each result of the LHS_ to the RHS expression. Let's go through an example. + +Given a document like: + +```yaml +a: 1 +b: thing +``` + +with an expression: + +``` +.a |= . + 1 +``` + +Similar to the `=` operator, `|=` takes two operands, the LHS and RHS. + +It pipes the current context (the whole document) through the LHS expression of `.a` to get the node value: + +``` +1 +``` + +Now it pipes _that LHS context_ into the RHS expression `. + 1` (whereas in the `=` plain form it piped the original document context into the RHS) to yield: + + +``` +2 +``` + +The assignment operator then copies across the value from the RHS to the value on the LHS, and it returns the now updated 'root' context: + +```yaml +a: 2 +b: thing +``` \ No newline at end of file diff --git a/operators/README.md b/operators/README.md index ac099ca5..3111fd6c 100644 --- a/operators/README.md +++ b/operators/README.md @@ -1,116 +1,5 @@ -# How it works +# Operators -In `yq` expressions are made up of operators and pipes. A context of nodes is passed through the expression and each operation takes the context as input and returns a new context as output. That output is piped in as input for the next operation in the expression. To begin with, the context is set to the first yaml document of the first yaml file (if processing in sequence using eval). +Operators filter or update `yaml` nodes. Operators combined together form expressions. -Lets look at a couple of examples. - -## Example with a simple operator - -Given a document like: - -```yaml -- [a] -- "cat" -``` - -with an expression: - -``` -.[] | length -``` - -`yq` will initially set the context as single node of the entire yaml document, an array of two elements. - -```yaml -- [a] -- "cat" -``` - -This gets piped into the splat operator `.[]` which will split out the context into a collection of two nodes `[a]` and `"cat"`. Note that this is _not_ a yaml array. - -The `length` operator take no arguments, and will simply return the length of _each_ matching node in the context. So for the context of `[a]` and `"cat"`, it will return a new context of `1` and `3`. - -This being the last operation in the expression, the results will be printed out: - -``` -1 -3 -``` - -## Example with an operator that takes arguments. - -Given a document like: - -```yaml -a: cat -b: dog -``` - -with an expression: - -``` -.a = .b -``` - -The `=` operator takes two arguments, a `lhs` expression, which in this case is `.a` and `rhs` expression which is `.b`. - -It pipes the current, lets call it 'root' context through the `lhs` expression of `.a` to return the node - -```yaml -cat -``` - -Note that this node holds not only its value 'cat', but comments and metadata too, including path and parent information. - -The `=` operator then pipes the 'root' context through the `rhs` expression of `.b` to return the node - -```yaml -dog -``` - -Both sides have now been evaluated, so now the operator copies across the value from the RHS to the value on the LHS, and it returns the now updated context: - -```yaml -a: dog -b: dog -``` - -## Relative update (e.g. `|=`) -There is another form of the `=` operator which we call the relative form. It's very similar to `=` but with one key difference when evaluating the RHS expression. - -In the plain form, we pass in the 'root' level context to the RHS expression. In relative form, we pass in _each result of the LHS_ to the RHS expression. Let's go through an example. - -Given a document like: - -```yaml -a: 1 -b: thing -``` - -with an expression: - -``` -.a |= . + 1 -``` - -Similar to the `=` operator, `|=` takes two operands, the LHS and RHS. - -It pipes the current context (the whole document) through the LHS expression of `.a` to get the node value: - -``` -1 -``` - -Now it pipes _that LHS context_ into the RHS expression `. + 1` (whereas in the `=` plain form it piped the original document context into the RHS) to yield: - - -``` -2 -``` - -The assignment operator then copies across the value from the RHS to the value on the LHS, and it returns the now updated 'root' context: - -```yaml -a: 2 -b: thing -``` \ No newline at end of file +See [how it works](how-it-works.md) for more information. \ No newline at end of file