From dc9d437fe53ee9d334032f800f0731b6f8db2ab8 Mon Sep 17 00:00:00 2001 From: Alex Ozdemir Date: Wed, 6 Jul 2016 11:08:23 -0400 Subject: [PATCH 1/3] Added `is{scalar,value,..}`. Fixes leaf_paths bug. Now paths to `false` leaves are correctly returned. Added the test new functions to the docs. --- docs/content/3.manual/manual.yml | 14 +++++++++++++- src/builtin.jq | 26 +++++++++++++++++--------- tests/jq.test | 30 ++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/docs/content/3.manual/manual.yml b/docs/content/3.manual/manual.yml index b9b8a23af5..44a9941daa 100644 --- a/docs/content/3.manual/manual.yml +++ b/docs/content/3.manual/manual.yml @@ -892,6 +892,18 @@ sections: input: '[[],{},1,"foo",null,true,false]' output: ['1'] + - title: "`isarray`, `isobject`, `isboolean`, `isnumber`, `isnormal`, `isfinite`, `isstring`, `isnull`, `isvalue`, `isscalar`" + body: | + + These built-ins produce whether inputs are arrays, objects, + booleans, numbers, normal numbers, finite numbers, strings, + null, non-null values, and non-iterables, respectively. + + examples: + - program: '.[]|isnumber' + input: '[[],{},1,"foo",null,true,false]' + output: [false, false, true, false, false, false] + - title: "`empty`" body: | @@ -937,7 +949,7 @@ sections: That is, `paths(numbers)` outputs the paths to all numeric values. - `leaf_paths` is an alias of `paths(scalars)`; `leaf_paths` is + `leaf_paths` is an alias of `paths(isscalar)`; `leaf_paths` is *deprecated* and will be removed in the next major release. examples: diff --git a/src/builtin.jq b/src/builtin.jq index 53998a859c..088f09e62f 100644 --- a/src/builtin.jq +++ b/src/builtin.jq @@ -46,19 +46,27 @@ def all(generator; condition): def all(condition): all(.[]; condition); def all: all(.); def isfinite: type == "number" and (isinfinite | not); -def arrays: select(type == "array"); -def objects: select(type == "object"); +def isarray: type == "array"; +def arrays: select(isarray); +def isobject: type == "object"; +def objects: select(isobject); def iterables: arrays, objects; -def booleans: select(type == "boolean"); -def numbers: select(type == "number"); +def isboolean: type == "boolean"; +def booleans: select(isboolean); +def isnumber: type == "number"; +def numbers: select(isnumber); def normals: select(isnormal); def finites: select(isfinite); -def strings: select(type == "string"); -def nulls: select(type == "null"); -def values: select(. != null); -def scalars: select(. == null or . == true or . == false or type == "number" or type == "string"); +def isstring: type == "string"; +def strings: select(isstring); +def isnull: type == "null"; +def nulls: select(isnull); +def isvalue: . != null; +def values: select(isvalue); +def isscalar: . == null or . == true or . == false or type == "number" or type == "string"; +def scalars: select(isscalar); def scalars_or_empty: select(. == null or . == true or . == false or type == "number" or type == "string" or ((type=="array" or type=="object") and length==0)); -def leaf_paths: paths(scalars); +def leaf_paths: paths(isscalar); def join($x): reduce .[] as $i (null; (if .==null then "" else .+$x end) + ($i | if type=="boolean" or type=="number" then tostring else .//"" end) diff --git a/tests/jq.test b/tests/jq.test index 630c344d0f..c482e22228 100644 --- a/tests/jq.test +++ b/tests/jq.test @@ -747,6 +747,12 @@ path(.a[path(.b)[0]]) [1,[[],{"a":2}]] [[0],[1,1,"a"]] +# Verifying that leaf_paths picks up false and null leaves. +# Issue #1163 +[leaf_paths] +{ "a": 2, "b": false, "d": [] } +[ [ "a" ], [ "b" ] ] + ["foo",1] as $p | getpath($p), setpath($p; 20), delpaths([$p]) {"bar": 42, "foo": ["a", "b", "c", "d"]} "b" @@ -1134,10 +1140,18 @@ map([1,2][0:.]) [1,2,"foo",[],[3,[]],{},true,false,null] [[],[3,[]]] +[.[]|isarray] +[1,2,"foo",[],[3,[]],{},true,false,null] +[false,false,false,true,true,false,false,false,false] + [.[]|objects] [1,2,"foo",[],[3,[]],{},true,false,null] [{}] +[.[]|isobject] +[1,2,"foo",[],[3,[]],{},true,false,null] +[false,false,false,false,false,true,false,false,false] + [.[]|iterables] [1,2,"foo",[],[3,[]],{},true,false,null] [[],[3,[]],{}] @@ -1146,18 +1160,34 @@ map([1,2][0:.]) [1,2,"foo",[],[3,[]],{},true,false,null] [1,2,"foo",true,false,null] +[.[]|isscalar] +[1,2,"foo",[],[3,[]],{},true,false,null] +[true,true,true,false,false,false,true,true,true] + [.[]|values] [1,2,"foo",[],[3,[]],{},true,false,null] [1,2,"foo",[],[3,[]],{},true,false] +[.[]|isvalue] +[1,2,"foo",[],[3,[]],{},true,false,null] +[true,true,true,true,true,true,true,true,false] + [.[]|booleans] [1,2,"foo",[],[3,[]],{},true,false,null] [true,false] +[.[]|isboolean] +[1,2,"foo",[],[3,[]],{},true,false,null] +[false,false,false,false,false,false,true,true,false] + [.[]|nulls] [1,2,"foo",[],[3,[]],{},true,false,null] [null] +[.[]|isnull] +[1,2,"foo",[],[3,[]],{},true,false,null] +[false,false,false,false,false,false,false,false,true] + flatten [0, [1], [[2]], [[[3]]]] [0, 1, 2, 3] From c87889e255a314d349be7f7237e43ce9c4a79ff1 Mon Sep 17 00:00:00 2001 From: Alex Ozdemir Date: Wed, 6 Jul 2016 11:41:50 -0400 Subject: [PATCH 2/3] Fixed example formatting + Typo --- docs/content/3.manual/manual.yml | 2 +- tests/jq.test | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/3.manual/manual.yml b/docs/content/3.manual/manual.yml index 44a9941daa..602a70c2e3 100644 --- a/docs/content/3.manual/manual.yml +++ b/docs/content/3.manual/manual.yml @@ -902,7 +902,7 @@ sections: examples: - program: '.[]|isnumber' input: '[[],{},1,"foo",null,true,false]' - output: [false, false, true, false, false, false] + output: ['false', 'false', 'true', 'false', 'false', 'false', 'false'] - title: "`empty`" body: | diff --git a/tests/jq.test b/tests/jq.test index c482e22228..297f1e6e2e 100644 --- a/tests/jq.test +++ b/tests/jq.test @@ -747,7 +747,7 @@ path(.a[path(.b)[0]]) [1,[[],{"a":2}]] [[0],[1,1,"a"]] -# Verifying that leaf_paths picks up false and null leaves. +# Verifying that leaf_paths picks up false leaves. # Issue #1163 [leaf_paths] { "a": 2, "b": false, "d": [] } From cc573c4719a62824fdd137c8f5f2e5001daab3cb Mon Sep 17 00:00:00 2001 From: Alex Ozdemir Date: Thu, 7 Jul 2016 15:26:17 -0400 Subject: [PATCH 3/3] `paths` now includes paths to `null`s There are a few reasonable ways recurse could behave. The standard libraries implemntation does the following: `recurse(f;cond)` yields ., then . | f if . | f satisfies cond, then . | f | f if . | f | f satisfies cond, etc... But it turns out that for paths, you want the following behavior: `recurse(f;cond)` yields ., then . | f is . satisfies cond, then . | f | f if . | f satisfies cond, etc. The difference is non-trivial (and there are actually a few degrees of freedom here), so I gave paths its own definition, rather than adding a new builtin. This means we don't have two builtin recurses floating around. --- src/builtin.jq | 3 ++- tests/jq.test | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/builtin.jq b/src/builtin.jq index 088f09e62f..5c430f8f35 100644 --- a/src/builtin.jq +++ b/src/builtin.jq @@ -29,7 +29,8 @@ def indices($i): if type == "array" and ($i|type) == "array" then .[$i] else .[$i] end; def index($i): indices($i) | .[0]; # TODO: optimize def rindex($i): indices($i) | .[-1:][0]; # TODO: optimize -def paths: path(recurse(if (type|. == "array" or . == "object") then .[] else empty end))|select(length > 0); +def paths: def recurse_pre(f; cond): def r: ., (select(cond) | f | r); r; + path(recurse_pre(.[]?;.!=null))|select(length > 0); def paths(node_filter): . as $dot|paths|select(. as $p|$dot|getpath($p)|node_filter); def any(generator; condition): [label $out | foreach generator as $i diff --git a/tests/jq.test b/tests/jq.test index 297f1e6e2e..420f8ea354 100644 --- a/tests/jq.test +++ b/tests/jq.test @@ -743,6 +743,16 @@ path(.a[path(.b)[0]]) [1,[[],{"a":2}]] [[0],[1],[1,0],[1,1],[1,1,"a"]] +# Make sure we get paths to nulls +[paths] +[1,null] +[[0],[1]] + +# Make sure we don't get paths to lone items +[paths] +5 +[] + [leaf_paths] [1,[[],{"a":2}]] [[0],[1,1,"a"]]