summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNicolas Williams <nico@cryptonector.com>2023-07-30 18:09:18 -0500
committerNico Williams <nico@cryptonector.com>2023-08-03 14:41:53 -0500
commitddef804945e0a49162e11e155a9b32cf840fe90e (patch)
tree55bc07de8a417bdca3cb1e75669bae7a8e59e871
parent949d38e6dc7330712b50697d7fe833eec85dede1 (diff)
Clarify the `//` operator (close #2189)
-rw-r--r--docs/content/manual/manual.yml38
-rw-r--r--jq.1.prebuilt20
-rw-r--r--tests/man.test14
3 files changed, 67 insertions, 5 deletions
diff --git a/docs/content/manual/manual.yml b/docs/content/manual/manual.yml
index d2c5f538..ca342605 100644
--- a/docs/content/manual/manual.yml
+++ b/docs/content/manual/manual.yml
@@ -2384,9 +2384,16 @@ sections:
- title: "Alternative operator: `//`"
body: |
- A filter of the form `a // b` produces the same
- results as `a`, if `a` produces results other than `false`
- and `null`. Otherwise, `a // b` produces the same results as `b`.
+ The `//` operator produces all the values of its left-hand
+ side that are neither `false` nor `null`, or, if the
+ left-hand side produces no values other than `false` or
+ `true`, then `//` produces all the values of its right-hand
+ side.
+
+ A filter of the form `a // b` produces all the results of
+ `a` that are not `false` or `null`. If `a` produces no
+ results, or no results other than `false` or `null`, then `a
+ // b` produces the results of `b`.
This is useful for providing defaults: `.foo // 1` will
evaluate to `1` if there's no `.foo` element in the
@@ -2394,13 +2401,36 @@ sections:
(jq's `or` operator is reserved for strictly Boolean
operations).
- examples:
+ Note: `some_generator // defaults_here` is not the same
+ as `some_generator | . // defaults_here`. The latter will
+ produce default values for all non-`false`, non-`null`
+ values of the left-hand side, while the former will not.
+ Precedence rules can make this confusing. For example, in
+ `false, 1 // 2` the left-hand side of `//` is `1`, not
+ `false, 1` -- `false, 1 // 2` parses the same way as `false,
+ (1 // 2)`. In `(false, null, 1) | . // 42` the left-hand
+ side of `//` is `.`, which always produces just one value,
+ while in `(false, null, 1) // 42` the left-hand side is a
+ generator of three values, and since it produces a
+ value other `false` and `null`, the default `42` is not
+ produced.
+
+ examples:
+ - program: 'empty // 42'
+ input: 'null'
+ output: ['42']
- program: '.foo // 42'
input: '{"foo": 19}'
output: ['19']
- program: '.foo // 42'
input: '{}'
output: ['42']
+ - program: '(false, null, 1) // 42'
+ input: 'null'
+ output: ['1']
+ - program: '(false, null, 1) | . // 42'
+ input: 'null'
+ output: ['42', '42', '1']
- title: try-catch
body: |
diff --git a/jq.1.prebuilt b/jq.1.prebuilt
index 5166c79b..2387ab1d 100644
--- a/jq.1.prebuilt
+++ b/jq.1.prebuilt
@@ -2571,15 +2571,25 @@ jq \'[true, false | not]\'
.IP "" 0
.
.SS "Alternative operator: //"
-A filter of the form \fBa // b\fR produces the same results as \fBa\fR, if \fBa\fR produces results other than \fBfalse\fR and \fBnull\fR\. Otherwise, \fBa // b\fR produces the same results as \fBb\fR\.
+The \fB//\fR operator produces all the values of its left\-hand side that are neither \fBfalse\fR nor \fBnull\fR, or, if the left\-hand side produces no values other than \fBfalse\fR or \fBtrue\fR, then \fB//\fR produces all the values of its right\-hand side\.
+.
+.P
+A filter of the form \fBa // b\fR produces all the results of \fBa\fR that are not \fBfalse\fR or \fBnull\fR\. If \fBa\fR produces no results, or no results other than \fBfalse\fR or \fBnull\fR, then \fBa // b\fR produces the results of \fBb\fR\.
.
.P
This is useful for providing defaults: \fB\.foo // 1\fR will evaluate to \fB1\fR if there\'s no \fB\.foo\fR element in the input\. It\'s similar to how \fBor\fR is sometimes used in Python (jq\'s \fBor\fR operator is reserved for strictly Boolean operations)\.
.
+.P
+Note: \fBsome_generator // defaults_here\fR is not the same as \fBsome_generator | \. // defaults_here\fR\. The latter will produce default values for all non\-\fBfalse\fR, non\-\fBnull\fR values of the left\-hand side, while the former will not\. Precedence rules can make this confusing\. For example, in \fBfalse, 1 // 2\fR the left\-hand side of \fB//\fR is \fB1\fR, not \fBfalse, 1\fR \-\- \fBfalse, 1 // 2\fR parses the same way as \fBfalse, (1 // 2)\fR\. In \fB(false, null, 1) | \. // 42\fR the left\-hand side of \fB//\fR is \fB\.\fR, which always produces just one value, while in \fB(false, null, 1) // 42\fR the left\-hand side is a generator of three values, and since it produces a value other \fBfalse\fR and \fBnull\fR, the default \fB42\fR is not produced\.
+.
.IP "" 4
.
.nf
+jq \'empty // 42\'
+ null
+=> 42
+
jq \'\.foo // 42\'
{"foo": 19}
=> 19
@@ -2587,6 +2597,14 @@ jq \'\.foo // 42\'
jq \'\.foo // 42\'
{}
=> 42
+
+jq \'(false, null, 1) // 42\'
+ null
+=> 1
+
+jq \'(false, null, 1) | \. // 42\'
+ null
+=> 42, 42, 1
.
.fi
.
diff --git a/tests/man.test b/tests/man.test
index 354043ba..822de5ab 100644
--- a/tests/man.test
+++ b/tests/man.test
@@ -769,6 +769,10 @@ false
null
[false, true]
+empty // 42
+null
+42
+
.foo // 42
{"foo": 19}
19
@@ -777,6 +781,16 @@ null
{}
42
+(false, null, 1) // 42
+null
+1
+
+(false, null, 1) | . // 42
+null
+42
+42
+1
+
try .a catch ". is not an object"
true
". is not an object"