| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
#!/usr/bin/env perl |
|
2
|
|
|
|
|
|
|
|
|
3
|
63
|
|
|
63
|
|
359031
|
use strict; |
|
|
63
|
|
|
|
|
138
|
|
|
|
63
|
|
|
|
|
2624
|
|
|
4
|
63
|
|
|
63
|
|
304
|
use warnings; |
|
|
63
|
|
|
|
|
94
|
|
|
|
63
|
|
|
|
|
3775
|
|
|
5
|
63
|
|
|
63
|
|
56950
|
use JSON::PP; |
|
|
63
|
|
|
|
|
1552452
|
|
|
|
63
|
|
|
|
|
6636
|
|
|
6
|
63
|
|
|
63
|
|
48327
|
use IO::File; |
|
|
63
|
|
|
|
|
672081
|
|
|
|
63
|
|
|
|
|
8751
|
|
|
7
|
63
|
|
|
63
|
|
37532
|
use FindBin; |
|
|
63
|
|
|
|
|
96957
|
|
|
|
63
|
|
|
|
|
3846
|
|
|
8
|
63
|
|
|
63
|
|
46379
|
use Term::ANSIColor; |
|
|
63
|
|
|
|
|
851774
|
|
|
|
63
|
|
|
|
|
7142
|
|
|
9
|
63
|
|
|
63
|
|
54916
|
use Getopt::Long qw(GetOptions :config no_ignore_case bundling no_pass_through); |
|
|
63
|
|
|
|
|
935621
|
|
|
|
63
|
|
|
|
|
364
|
|
|
10
|
63
|
|
|
63
|
|
53136
|
use lib "$FindBin::Bin/../lib"; |
|
|
63
|
|
|
|
|
48389
|
|
|
|
63
|
|
|
|
|
528
|
|
|
11
|
63
|
|
|
63
|
|
48867
|
use JQ::Lite; |
|
|
63
|
|
|
|
|
310
|
|
|
|
63
|
|
|
|
|
2887
|
|
|
12
|
63
|
|
|
63
|
|
416
|
use JQ::Lite::Util (); |
|
|
63
|
|
|
|
|
151
|
|
|
|
63
|
|
|
|
|
184561
|
|
|
13
|
|
|
|
|
|
|
|
|
14
|
63
|
|
|
|
|
3844837
|
my $decoder; |
|
15
|
|
|
|
|
|
|
my $decoder_module; |
|
16
|
63
|
|
|
|
|
174
|
my $decoder_debug = 0; |
|
17
|
63
|
|
|
|
|
128
|
my $decoder_choice; |
|
18
|
63
|
|
|
|
|
132
|
my $raw_input = 0; |
|
19
|
63
|
|
|
|
|
166
|
my $raw_output = 0; |
|
20
|
63
|
|
|
|
|
139
|
my $compact_output = 0; |
|
21
|
63
|
|
|
|
|
188
|
my $ascii_output = 0; |
|
22
|
63
|
|
|
|
|
134
|
my $color_output = 0; |
|
23
|
63
|
|
|
|
|
130
|
my $null_input = 0; |
|
24
|
63
|
|
|
|
|
127
|
my $slurp_input = 0; |
|
25
|
63
|
|
|
|
|
123
|
my @query_files; |
|
26
|
63
|
|
|
|
|
121
|
my $force_yaml = 0; |
|
27
|
63
|
|
|
|
|
125
|
my $exit_status = 0; |
|
28
|
63
|
|
|
|
|
355
|
my $yaml_module; |
|
29
|
|
|
|
|
|
|
my $yaml_loader; |
|
30
|
63
|
|
|
|
|
0
|
my %arg_vars; |
|
31
|
63
|
|
|
|
|
0
|
my @slurpfiles; |
|
32
|
63
|
|
|
|
|
147
|
my $slurpfile_probably_missing_file = 0; |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
sub _usage_error { |
|
35
|
16
|
|
|
16
|
|
567
|
my ($message) = @_; |
|
36
|
16
|
|
50
|
|
|
64
|
$message ||= 'invalid usage'; |
|
37
|
16
|
|
|
|
|
122
|
$message =~ s/\s+\z//; |
|
38
|
16
|
|
|
|
|
323
|
warn "[USAGE]$message\n"; |
|
39
|
16
|
|
|
|
|
2182
|
exit 5; |
|
40
|
|
|
|
|
|
|
} |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
sub _input_error { |
|
43
|
2
|
|
|
2
|
|
378
|
my ($message) = @_; |
|
44
|
2
|
|
50
|
|
|
8
|
$message ||= 'input error'; |
|
45
|
2
|
|
|
|
|
18
|
$message =~ s/\s+\z//; |
|
46
|
2
|
|
|
|
|
77
|
warn "[INPUT]$message\n"; |
|
47
|
2
|
|
|
|
|
176
|
exit 4; |
|
48
|
|
|
|
|
|
|
} |
|
49
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
sub _compile_error { |
|
51
|
3
|
|
|
3
|
|
10
|
my ($message) = @_; |
|
52
|
3
|
|
50
|
|
|
10
|
$message ||= 'compile error'; |
|
53
|
3
|
|
|
|
|
19
|
$message =~ s/\s+\z//; |
|
54
|
3
|
|
|
|
|
120
|
warn "[COMPILE]$message\n"; |
|
55
|
3
|
|
|
|
|
270
|
exit 2; |
|
56
|
|
|
|
|
|
|
} |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
sub _runtime_error { |
|
59
|
3
|
|
|
3
|
|
10
|
my ($message) = @_; |
|
60
|
3
|
|
50
|
|
|
9
|
$message ||= 'runtime error'; |
|
61
|
3
|
|
|
|
|
16
|
$message =~ s/\s+\z//; |
|
62
|
3
|
|
|
|
|
69
|
warn "[RUNTIME]$message\n"; |
|
63
|
3
|
|
|
|
|
288
|
exit 3; |
|
64
|
|
|
|
|
|
|
} |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub _validate_query_syntax { |
|
67
|
46
|
|
|
46
|
|
159
|
my ($query) = @_; |
|
68
|
|
|
|
|
|
|
|
|
69
|
46
|
50
|
33
|
|
|
399
|
return if !defined $query || $query eq ''; |
|
70
|
|
|
|
|
|
|
|
|
71
|
46
|
|
|
|
|
129
|
my @stack; |
|
72
|
|
|
|
|
|
|
my $string; |
|
73
|
46
|
|
|
|
|
102
|
my $escape = 0; |
|
74
|
|
|
|
|
|
|
|
|
75
|
46
|
|
|
|
|
275
|
for my $char (split //, $query) { |
|
76
|
352
|
100
|
|
|
|
630
|
if (defined $string) { |
|
77
|
8
|
100
|
|
|
|
15
|
if ($escape) { |
|
78
|
2
|
|
|
|
|
4
|
$escape = 0; |
|
79
|
2
|
|
|
|
|
3
|
next; |
|
80
|
|
|
|
|
|
|
} |
|
81
|
|
|
|
|
|
|
|
|
82
|
6
|
100
|
|
|
|
14
|
if ($char eq '\\') { |
|
83
|
2
|
|
|
|
|
5
|
$escape = 1; |
|
84
|
2
|
|
|
|
|
4
|
next; |
|
85
|
|
|
|
|
|
|
} |
|
86
|
|
|
|
|
|
|
|
|
87
|
4
|
100
|
|
|
|
11
|
if ($char eq $string) { |
|
88
|
3
|
|
|
|
|
7
|
undef $string; |
|
89
|
|
|
|
|
|
|
} |
|
90
|
4
|
|
|
|
|
5
|
next; |
|
91
|
|
|
|
|
|
|
} |
|
92
|
|
|
|
|
|
|
|
|
93
|
344
|
100
|
66
|
|
|
1090
|
if ($char eq "'" || $char eq '"') { |
|
94
|
3
|
|
|
|
|
5
|
$string = $char; |
|
95
|
3
|
|
|
|
|
5
|
next; |
|
96
|
|
|
|
|
|
|
} |
|
97
|
|
|
|
|
|
|
|
|
98
|
341
|
100
|
100
|
|
|
1457
|
if ($char eq '(' || $char eq '[' || $char eq '{') { |
|
|
|
|
66
|
|
|
|
|
|
99
|
19
|
|
|
|
|
69
|
push @stack, $char; |
|
100
|
19
|
|
|
|
|
49
|
next; |
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
|
|
103
|
322
|
100
|
100
|
|
|
1423
|
if ($char eq ')' || $char eq ']' || $char eq '}') { |
|
|
|
|
66
|
|
|
|
|
|
104
|
18
|
|
|
|
|
43
|
my $open = pop @stack; |
|
105
|
18
|
|
|
|
|
98
|
my %pairs = ( |
|
106
|
|
|
|
|
|
|
')' => '(', |
|
107
|
|
|
|
|
|
|
']' => '[', |
|
108
|
|
|
|
|
|
|
'}' => '{', |
|
109
|
|
|
|
|
|
|
); |
|
110
|
18
|
50
|
33
|
|
|
184
|
if (!defined $open || $open ne $pairs{$char}) { |
|
111
|
0
|
|
|
|
|
0
|
_compile_error('Invalid query syntax: unmatched brackets'); |
|
112
|
|
|
|
|
|
|
} |
|
113
|
|
|
|
|
|
|
} |
|
114
|
|
|
|
|
|
|
} |
|
115
|
|
|
|
|
|
|
|
|
116
|
46
|
100
|
66
|
|
|
324
|
if (defined $string || @stack) { |
|
117
|
1
|
|
|
|
|
5
|
_compile_error('Invalid query syntax: unmatched brackets'); |
|
118
|
|
|
|
|
|
|
} |
|
119
|
|
|
|
|
|
|
|
|
120
|
45
|
|
|
|
|
348
|
my @pipeline_parts = JQ::Lite::Util::_split_top_level_pipes($query); |
|
121
|
45
|
100
|
66
|
|
|
193
|
if (grep { !defined $_ || $_ !~ /\S/ } @pipeline_parts) { |
|
|
57
|
|
|
|
|
705
|
|
|
122
|
1
|
|
|
|
|
5
|
_compile_error('Invalid query syntax: empty filter segment'); |
|
123
|
|
|
|
|
|
|
} |
|
124
|
|
|
|
|
|
|
|
|
125
|
44
|
|
|
|
|
301
|
for my $segment (@pipeline_parts) { |
|
126
|
55
|
|
|
|
|
293
|
my @comma_parts = JQ::Lite::Util::_split_top_level_commas($segment); |
|
127
|
55
|
100
|
66
|
|
|
153
|
if (grep { !defined $_ || $_ !~ /\S/ } @comma_parts) { |
|
|
56
|
|
|
|
|
669
|
|
|
128
|
1
|
|
|
|
|
4
|
_compile_error('Invalid query syntax: empty filter segment'); |
|
129
|
|
|
|
|
|
|
} |
|
130
|
|
|
|
|
|
|
} |
|
131
|
|
|
|
|
|
|
} |
|
132
|
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
sub _is_truthy { |
|
134
|
4
|
|
|
4
|
|
51
|
my ($value) = @_; |
|
135
|
|
|
|
|
|
|
|
|
136
|
4
|
100
|
|
|
|
25
|
return 0 unless defined $value; |
|
137
|
|
|
|
|
|
|
|
|
138
|
2
|
100
|
|
|
|
9
|
if (ref($value) eq 'JSON::PP::Boolean') { |
|
139
|
1
|
50
|
|
|
|
33
|
return $value ? 1 : 0; |
|
140
|
|
|
|
|
|
|
} |
|
141
|
|
|
|
|
|
|
|
|
142
|
1
|
|
|
|
|
4
|
return 1; |
|
143
|
|
|
|
|
|
|
} |
|
144
|
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
# ---------- Help text ---------- |
|
146
|
63
|
|
|
|
|
189
|
my $USAGE = <<'USAGE'; |
|
147
|
|
|
|
|
|
|
jq-lite - minimal jq-style JSON filter (pure Perl) |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
Usage: |
|
150
|
|
|
|
|
|
|
jq-lite [options] '.query' [file.json] |
|
151
|
|
|
|
|
|
|
jq-lite [options] -f query.jq [file.json] |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
Options: |
|
154
|
|
|
|
|
|
|
-R, --raw-input Read input as raw text instead of JSON (one filter run per line) |
|
155
|
|
|
|
|
|
|
-r, --raw-output Print raw strings instead of JSON-encoded values |
|
156
|
|
|
|
|
|
|
-c, --compact-output Print JSON results on a single line (no pretty-printing) |
|
157
|
|
|
|
|
|
|
-a, --ascii-output Escape all non-ASCII characters in JSON output |
|
158
|
|
|
|
|
|
|
--color Colorize JSON output (keys, strings, numbers, booleans) |
|
159
|
|
|
|
|
|
|
--use Force JSON decoder module (e.g. JSON::PP, JSON::XS, Cpanel::JSON::XS) |
|
160
|
|
|
|
|
|
|
--debug Show which JSON module is being used |
|
161
|
|
|
|
|
|
|
-e, --exit-status Set exit code to 1 when the final result is false, null, or empty |
|
162
|
|
|
|
|
|
|
--arg NAME VALUE Set jq-style variable $NAME to the string VALUE |
|
163
|
|
|
|
|
|
|
--rawfile NAME FILE Set jq-style variable $NAME to the raw contents of FILE |
|
164
|
|
|
|
|
|
|
--slurpfile NAME FILE Set jq-style variable $NAME to an array of JSON values from FILE |
|
165
|
|
|
|
|
|
|
--argjson NAME JSON Set jq-style variable $NAME to a JSON-decoded value |
|
166
|
|
|
|
|
|
|
--argfile NAME FILE Set jq-style variable $NAME to a JSON-decoded value from FILE |
|
167
|
|
|
|
|
|
|
-f, --from-file FILE Read jq filter from FILE instead of the command line |
|
168
|
|
|
|
|
|
|
(use '-' to read the filter from STDIN) |
|
169
|
|
|
|
|
|
|
-n, --null-input Use null as input instead of reading JSON data |
|
170
|
|
|
|
|
|
|
-s, --slurp Read entire input stream as an array of JSON values |
|
171
|
|
|
|
|
|
|
--yaml Parse input as YAML (auto-detected for .yml/.yaml files) |
|
172
|
|
|
|
|
|
|
-h, --help Show this help message |
|
173
|
|
|
|
|
|
|
--help-functions Show list of all supported functions |
|
174
|
|
|
|
|
|
|
-v, --version Show version information |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
Examples: |
|
177
|
|
|
|
|
|
|
cat users.json | jq-lite '.users[].name' |
|
178
|
|
|
|
|
|
|
jq-lite '.users[] | select(.age > 25)' users.json |
|
179
|
|
|
|
|
|
|
jq-lite -r '.users[] | .name' users.json |
|
180
|
|
|
|
|
|
|
jq-lite '.meta has "version"' config.json |
|
181
|
|
|
|
|
|
|
jq-lite --color '.items | sort | reverse | first' data.json |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
Homepage: |
|
184
|
|
|
|
|
|
|
https://metacpan.org/pod/JQ::Lite |
|
185
|
|
|
|
|
|
|
USAGE |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
# ---------- Option parsing (Getopt::Long) ---------- |
|
188
|
63
|
|
|
|
|
172
|
my ($want_help, $want_version, $help_functions) = (0, 0, 0); |
|
189
|
|
|
|
|
|
|
|
|
190
|
63
|
|
|
|
|
167
|
my @getopt_errors; |
|
191
|
|
|
|
|
|
|
my $getopt_ok; |
|
192
|
|
|
|
|
|
|
{ |
|
193
|
63
|
|
|
|
|
163
|
my $orig_warn = $SIG{__WARN__}; |
|
|
63
|
|
|
|
|
202
|
|
|
194
|
|
|
|
|
|
|
local $SIG{__WARN__} = sub { |
|
195
|
10
|
|
|
10
|
|
2367
|
my ($msg) = @_; |
|
196
|
10
|
100
|
|
|
|
75
|
if ($msg =~ /^\[(?:COMPILE|RUNTIME|INPUT|USAGE)\]/) { |
|
197
|
9
|
50
|
|
|
|
34
|
if ($orig_warn) { |
|
198
|
0
|
|
|
|
|
0
|
$orig_warn->($msg); |
|
199
|
|
|
|
|
|
|
} |
|
200
|
|
|
|
|
|
|
else { |
|
201
|
9
|
|
|
|
|
200
|
CORE::warn($msg); |
|
202
|
|
|
|
|
|
|
} |
|
203
|
9
|
|
|
|
|
47
|
return; |
|
204
|
|
|
|
|
|
|
} |
|
205
|
|
|
|
|
|
|
|
|
206
|
1
|
|
|
|
|
6
|
push @getopt_errors, $msg; |
|
207
|
63
|
|
|
|
|
709
|
}; |
|
208
|
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
$getopt_ok = GetOptions( |
|
210
|
|
|
|
|
|
|
'raw-input|R' => \$raw_input, |
|
211
|
|
|
|
|
|
|
'raw-output|r' => \$raw_output, |
|
212
|
|
|
|
|
|
|
'compact-output|c'=> \$compact_output, |
|
213
|
|
|
|
|
|
|
'ascii-output|a' => \$ascii_output, |
|
214
|
|
|
|
|
|
|
'color' => \$color_output, |
|
215
|
|
|
|
|
|
|
'use=s' => \$decoder_choice, |
|
216
|
|
|
|
|
|
|
'debug' => \$decoder_debug, |
|
217
|
|
|
|
|
|
|
'exit-status|e' => \$exit_status, |
|
218
|
|
|
|
|
|
|
'null-input|n' => \$null_input, |
|
219
|
|
|
|
|
|
|
'slurp|s' => \$slurp_input, |
|
220
|
|
|
|
|
|
|
'yaml' => \$force_yaml, |
|
221
|
|
|
|
|
|
|
'help|h' => \$want_help, |
|
222
|
|
|
|
|
|
|
'help-functions' => \$help_functions, |
|
223
|
|
|
|
|
|
|
'version|v' => \$want_version, |
|
224
|
|
|
|
|
|
|
'from-file|f=s' => \@query_files, |
|
225
|
|
|
|
|
|
|
'slurpfile=s' => sub { |
|
226
|
5
|
|
|
5
|
|
13309
|
my ($opt_name, $var_name) = @_; |
|
227
|
5
|
50
|
33
|
|
|
53
|
_usage_error('--slurpfile requires a variable name') if !defined $var_name || $var_name eq ''; |
|
228
|
5
|
50
|
|
|
|
38
|
if ($var_name !~ /^[A-Za-z_]\w*$/) { |
|
229
|
0
|
|
|
|
|
0
|
_usage_error("Invalid variable name '$var_name' for --slurpfile"); |
|
230
|
|
|
|
|
|
|
} |
|
231
|
5
|
50
|
|
|
|
18
|
_usage_error('--slurpfile requires a file path') if !@ARGV; |
|
232
|
5
|
|
|
|
|
15
|
my $file_path = shift @ARGV; |
|
233
|
5
|
50
|
|
|
|
40
|
$file_path = '' unless defined $file_path; |
|
234
|
|
|
|
|
|
|
|
|
235
|
5
|
100
|
66
|
|
|
29
|
$slurpfile_probably_missing_file = 1 if @ARGV == 0 && $file_path =~ /^\./; |
|
236
|
|
|
|
|
|
|
|
|
237
|
5
|
|
|
|
|
66
|
push @slurpfiles, $var_name, $file_path; |
|
238
|
|
|
|
|
|
|
}, |
|
239
|
|
|
|
|
|
|
'arg=s' => sub { |
|
240
|
3
|
|
|
3
|
|
7720
|
my ($opt_name, $var_name) = @_; |
|
241
|
3
|
50
|
33
|
|
|
31
|
_usage_error('--arg requires a variable name') if !defined $var_name || $var_name eq ''; |
|
242
|
3
|
100
|
|
|
|
24
|
if ($var_name !~ /^[A-Za-z_]\w*$/) { |
|
243
|
1
|
|
|
|
|
6
|
_usage_error("Invalid variable name '$var_name' for --arg"); |
|
244
|
|
|
|
|
|
|
} |
|
245
|
2
|
100
|
|
|
|
12
|
_usage_error('--arg requires a value') if !@ARGV; |
|
246
|
1
|
|
|
|
|
3
|
my $value = shift @ARGV; |
|
247
|
1
|
50
|
|
|
|
5
|
$value = '' unless defined $value; |
|
248
|
1
|
|
|
|
|
15
|
$arg_vars{$var_name} = "$value"; |
|
249
|
|
|
|
|
|
|
}, |
|
250
|
|
|
|
|
|
|
'argjson=s' => sub { |
|
251
|
7
|
|
|
7
|
|
15055
|
my ($opt_name, $var_name) = @_; |
|
252
|
7
|
50
|
33
|
|
|
64
|
_usage_error('--argjson requires a variable name') if !defined $var_name || $var_name eq ''; |
|
253
|
7
|
100
|
|
|
|
48
|
if ($var_name !~ /^[A-Za-z_]\w*$/) { |
|
254
|
1
|
|
|
|
|
8
|
_usage_error("Invalid variable name '$var_name' for --argjson"); |
|
255
|
|
|
|
|
|
|
} |
|
256
|
6
|
100
|
|
|
|
25
|
_usage_error('--argjson requires a value') if !@ARGV; |
|
257
|
5
|
|
|
|
|
9
|
my $value_text = shift @ARGV; |
|
258
|
5
|
50
|
|
|
|
17
|
$value_text = '' unless defined $value_text; |
|
259
|
|
|
|
|
|
|
|
|
260
|
5
|
|
|
|
|
10
|
my $decoded = eval { JQ::Lite::Util::_decode_json($value_text) }; |
|
|
5
|
|
|
|
|
29
|
|
|
261
|
5
|
100
|
|
|
|
1836
|
if (my $err = $@) { |
|
262
|
2
|
|
|
|
|
19
|
$err =~ s/\s+\z//; |
|
263
|
2
|
|
|
|
|
12
|
_usage_error("invalid JSON for --argjson $var_name: $err"); |
|
264
|
|
|
|
|
|
|
} |
|
265
|
|
|
|
|
|
|
|
|
266
|
3
|
|
|
|
|
40
|
$arg_vars{$var_name} = $decoded; |
|
267
|
|
|
|
|
|
|
}, |
|
268
|
|
|
|
|
|
|
'argfile=s' => sub { |
|
269
|
3
|
|
|
3
|
|
6269
|
my ($opt_name, $var_name) = @_; |
|
270
|
3
|
50
|
33
|
|
|
27
|
_usage_error('--argfile requires a variable name') if !defined $var_name || $var_name eq ''; |
|
271
|
3
|
50
|
|
|
|
21
|
if ($var_name !~ /^[A-Za-z_]\w*$/) { |
|
272
|
0
|
|
|
|
|
0
|
_usage_error("Invalid variable name '$var_name' for --argfile"); |
|
273
|
|
|
|
|
|
|
} |
|
274
|
3
|
50
|
|
|
|
10
|
_usage_error('--argfile requires a file path') if !@ARGV; |
|
275
|
3
|
|
|
|
|
8
|
my $file_path = shift @ARGV; |
|
276
|
3
|
50
|
|
|
|
8
|
$file_path = '' unless defined $file_path; |
|
277
|
|
|
|
|
|
|
|
|
278
|
3
|
100
|
|
|
|
30
|
my $fh = IO::File->new($file_path, 'r') |
|
279
|
|
|
|
|
|
|
or _usage_error("Cannot open file '$file_path' for --argfile $var_name: $!"); |
|
280
|
2
|
|
|
|
|
305
|
local $/; |
|
281
|
2
|
|
|
|
|
89
|
my $json_text = <$fh>; |
|
282
|
2
|
50
|
|
|
|
13
|
$json_text = '' unless defined $json_text; |
|
283
|
2
|
|
|
|
|
21
|
$fh->close; |
|
284
|
|
|
|
|
|
|
|
|
285
|
2
|
|
|
|
|
37
|
my $decoded = eval { JQ::Lite::Util::_decode_json($json_text) }; |
|
|
2
|
|
|
|
|
11
|
|
|
286
|
2
|
100
|
|
|
|
936
|
if (my $err = $@) { |
|
287
|
1
|
|
|
|
|
7
|
$err =~ s/\s+\z//; |
|
288
|
1
|
|
|
|
|
4
|
_usage_error("invalid JSON in --argfile $var_name: $err"); |
|
289
|
|
|
|
|
|
|
} |
|
290
|
|
|
|
|
|
|
|
|
291
|
1
|
|
|
|
|
23
|
$arg_vars{$var_name} = $decoded; |
|
292
|
|
|
|
|
|
|
}, |
|
293
|
|
|
|
|
|
|
'rawfile=s' => sub { |
|
294
|
2
|
|
|
2
|
|
3871
|
my ($opt_name, $var_name) = @_; |
|
295
|
2
|
50
|
33
|
|
|
20
|
_usage_error('--rawfile requires a variable name') if !defined $var_name || $var_name eq ''; |
|
296
|
2
|
50
|
|
|
|
11
|
if ($var_name !~ /^[A-Za-z_]\w*$/) { |
|
297
|
0
|
|
|
|
|
0
|
_usage_error("Invalid variable name '$var_name' for --rawfile"); |
|
298
|
|
|
|
|
|
|
} |
|
299
|
2
|
50
|
|
|
|
7
|
_usage_error('--rawfile requires a file path') if !@ARGV; |
|
300
|
2
|
|
|
|
|
4
|
my $file_path = shift @ARGV; |
|
301
|
2
|
50
|
|
|
|
23
|
$file_path = '' unless defined $file_path; |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
open my $fh, '<', $file_path |
|
304
|
2
|
100
|
|
|
|
206
|
or do { |
|
305
|
1
|
|
|
|
|
19
|
_usage_error("Cannot open file '$file_path' for --rawfile $var_name: $!"); |
|
306
|
|
|
|
|
|
|
}; |
|
307
|
1
|
|
|
|
|
14
|
local $/; |
|
308
|
1
|
|
|
|
|
41
|
my $content = <$fh>; |
|
309
|
1
|
|
|
|
|
16
|
close $fh; |
|
310
|
1
|
50
|
|
|
|
5
|
$content = '' unless defined $content; |
|
311
|
|
|
|
|
|
|
|
|
312
|
1
|
|
|
|
|
22
|
$arg_vars{$var_name} = $content; |
|
313
|
|
|
|
|
|
|
}, |
|
314
|
63
|
|
|
|
|
2781
|
); |
|
315
|
|
|
|
|
|
|
} |
|
316
|
|
|
|
|
|
|
|
|
317
|
54
|
100
|
|
|
|
107747
|
if (!$getopt_ok) { |
|
318
|
1
|
50
|
|
|
|
5
|
my $message = @getopt_errors ? $getopt_errors[0] : 'invalid option(s)'; |
|
319
|
1
|
|
|
|
|
9
|
$message =~ s/\s+\z//; |
|
320
|
1
|
|
|
|
|
5
|
_usage_error($message); |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
|
|
323
|
53
|
50
|
|
|
|
219
|
if ($help_functions) { |
|
324
|
0
|
|
|
|
|
0
|
print_supported_functions(); |
|
325
|
0
|
|
|
|
|
0
|
exit 0; |
|
326
|
|
|
|
|
|
|
} |
|
327
|
|
|
|
|
|
|
|
|
328
|
53
|
50
|
|
|
|
209
|
if ($want_help) { |
|
329
|
0
|
|
|
|
|
0
|
print $USAGE; |
|
330
|
0
|
|
|
|
|
0
|
exit 0; |
|
331
|
|
|
|
|
|
|
} |
|
332
|
|
|
|
|
|
|
|
|
333
|
53
|
50
|
|
|
|
188
|
if ($want_version) { |
|
334
|
0
|
|
|
|
|
0
|
print "jq-lite $JQ::Lite::VERSION\n"; |
|
335
|
0
|
|
|
|
|
0
|
exit 0; |
|
336
|
|
|
|
|
|
|
} |
|
337
|
|
|
|
|
|
|
|
|
338
|
53
|
|
|
1
|
|
966
|
$SIG{PIPE} = sub { exit 0 }; |
|
|
1
|
|
|
|
|
1272
|
|
|
339
|
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
END { |
|
341
|
63
|
50
|
|
63
|
|
455
|
return unless defined fileno(STDOUT); |
|
342
|
|
|
|
|
|
|
|
|
343
|
63
|
100
|
|
|
|
31
|
if (!close STDOUT) { |
|
344
|
|
|
|
|
|
|
# On older perls a closed pipeline can report EINVAL instead of |
|
345
|
|
|
|
|
|
|
# EPIPE. Both conditions indicate the writer has no consumer, so do |
|
346
|
|
|
|
|
|
|
# not emit a warning in either case. |
|
347
|
63
|
50
|
33
|
63
|
|
35992
|
return if $!{EPIPE} || $!{EINVAL}; |
|
|
63
|
|
|
|
|
115140
|
|
|
|
63
|
|
|
|
|
735
|
|
|
|
1
|
|
|
|
|
16
|
|
|
348
|
0
|
|
|
|
|
0
|
warn "Unable to flush stdout: $!"; |
|
349
|
|
|
|
|
|
|
} |
|
350
|
|
|
|
|
|
|
} |
|
351
|
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
# ---------- Positional args: '.query' and [file.json] ---------- |
|
353
|
|
|
|
|
|
|
# Unknown options are already rejected above; only query and file remain. |
|
354
|
53
|
|
|
|
|
212
|
my ($query, $filename); |
|
355
|
|
|
|
|
|
|
|
|
356
|
53
|
|
100
|
|
|
1679
|
my $non_help_option_used = $raw_input |
|
357
|
|
|
|
|
|
|
|| $raw_output |
|
358
|
|
|
|
|
|
|
|| $compact_output |
|
359
|
|
|
|
|
|
|
|| $ascii_output |
|
360
|
|
|
|
|
|
|
|| $color_output |
|
361
|
|
|
|
|
|
|
|| defined $decoder_choice |
|
362
|
|
|
|
|
|
|
|| $decoder_debug |
|
363
|
|
|
|
|
|
|
|| $exit_status |
|
364
|
|
|
|
|
|
|
|| $null_input |
|
365
|
|
|
|
|
|
|
|| $slurp_input |
|
366
|
|
|
|
|
|
|
|| $force_yaml |
|
367
|
|
|
|
|
|
|
|| @query_files |
|
368
|
|
|
|
|
|
|
|| keys(%arg_vars) |
|
369
|
|
|
|
|
|
|
|| @slurpfiles; |
|
370
|
|
|
|
|
|
|
|
|
371
|
53
|
100
|
|
|
|
233
|
if (@query_files) { |
|
372
|
3
|
50
|
|
|
|
11
|
_usage_error('--from-file may only be specified once') if @query_files > 1; |
|
373
|
|
|
|
|
|
|
|
|
374
|
3
|
|
|
|
|
8
|
my $file = $query_files[0]; |
|
375
|
3
|
100
|
66
|
|
|
20
|
if (defined $file && $file eq '-') { |
|
376
|
2
|
50
|
66
|
|
|
13
|
if (!$null_input && !@ARGV) { |
|
377
|
1
|
|
|
|
|
5
|
_usage_error('Cannot use --from-file - when reading JSON from STDIN. Provide input file or use --null-input.'); |
|
378
|
|
|
|
|
|
|
} |
|
379
|
|
|
|
|
|
|
} |
|
380
|
|
|
|
|
|
|
|
|
381
|
2
|
100
|
66
|
|
|
11
|
if (defined $file && $file eq '-') { |
|
382
|
1
|
|
|
|
|
4
|
local $/; |
|
383
|
1
|
|
|
|
|
30
|
$query = ; |
|
384
|
1
|
50
|
|
|
|
5
|
$query = '' unless defined $query; |
|
385
|
|
|
|
|
|
|
} |
|
386
|
|
|
|
|
|
|
else { |
|
387
|
1
|
50
|
|
|
|
12
|
my $fh = IO::File->new($file, 'r') |
|
388
|
|
|
|
|
|
|
or _input_error("Cannot open query file '$file': $!"); |
|
389
|
1
|
|
|
|
|
172
|
local $/; |
|
390
|
1
|
|
50
|
|
|
36
|
$query = <$fh> // ''; |
|
391
|
1
|
|
|
|
|
12
|
$fh->close; |
|
392
|
|
|
|
|
|
|
} |
|
393
|
|
|
|
|
|
|
} |
|
394
|
|
|
|
|
|
|
|
|
395
|
52
|
100
|
|
|
|
384
|
if (@ARGV == 0) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
396
|
4
|
100
|
|
|
|
14
|
if (!defined $query) { |
|
397
|
3
|
100
|
|
|
|
11
|
if ($slurpfile_probably_missing_file) { |
|
398
|
1
|
|
|
|
|
6
|
_usage_error('--slurpfile requires a file path'); |
|
399
|
|
|
|
|
|
|
} |
|
400
|
|
|
|
|
|
|
|
|
401
|
2
|
50
|
|
|
|
15
|
if ($raw_input) { |
|
402
|
2
|
100
|
|
|
|
10
|
my $reason = $slurp_input |
|
403
|
|
|
|
|
|
|
? '--raw-input requires a query when used with --slurp.' |
|
404
|
|
|
|
|
|
|
: '--raw-input requires a query when not using --slurp.'; |
|
405
|
2
|
|
|
|
|
11
|
_usage_error($reason); |
|
406
|
|
|
|
|
|
|
} |
|
407
|
|
|
|
|
|
|
|
|
408
|
0
|
0
|
|
|
|
0
|
if ($null_input) { |
|
409
|
0
|
|
|
|
|
0
|
$query = '.'; |
|
410
|
|
|
|
|
|
|
} else { |
|
411
|
0
|
0
|
|
|
|
0
|
if ($non_help_option_used) { |
|
412
|
0
|
|
|
|
|
0
|
_usage_error('filter expression is required'); |
|
413
|
|
|
|
|
|
|
} |
|
414
|
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
# If no args and no options, show help |
|
416
|
0
|
|
|
|
|
0
|
print $USAGE; |
|
417
|
0
|
|
|
|
|
0
|
exit 0; |
|
418
|
|
|
|
|
|
|
} |
|
419
|
|
|
|
|
|
|
} |
|
420
|
|
|
|
|
|
|
} |
|
421
|
|
|
|
|
|
|
elsif (@ARGV == 1) { |
|
422
|
|
|
|
|
|
|
# Single arg: query or file |
|
423
|
35
|
50
|
100
|
|
|
1837
|
if (!$null_input && -f $ARGV[0] && !defined $query) { |
|
|
|
100
|
66
|
|
|
|
|
|
424
|
0
|
|
|
|
|
0
|
$filename = $ARGV[0]; |
|
425
|
|
|
|
|
|
|
# No query -> go to interactive mode (handled later) |
|
426
|
|
|
|
|
|
|
} |
|
427
|
|
|
|
|
|
|
elsif (!defined $query) { |
|
428
|
34
|
|
|
|
|
112
|
$query = $ARGV[0]; |
|
429
|
|
|
|
|
|
|
} |
|
430
|
|
|
|
|
|
|
else { |
|
431
|
|
|
|
|
|
|
# Query already provided via -f; treat arg as filename if it exists |
|
432
|
1
|
|
|
|
|
3
|
my $f = $ARGV[0]; |
|
433
|
1
|
50
|
|
|
|
3
|
if ($null_input) { |
|
434
|
0
|
|
|
|
|
0
|
_usage_error('--null-input cannot be combined with file input'); |
|
435
|
|
|
|
|
|
|
} |
|
436
|
1
|
50
|
|
|
|
31
|
if (-f $f) { |
|
437
|
1
|
|
|
|
|
4
|
$filename = $f; |
|
438
|
|
|
|
|
|
|
} else { |
|
439
|
0
|
|
|
|
|
0
|
_input_error("Cannot open file '$f': $!"); |
|
440
|
|
|
|
|
|
|
} |
|
441
|
|
|
|
|
|
|
} |
|
442
|
|
|
|
|
|
|
} |
|
443
|
|
|
|
|
|
|
elsif (@ARGV == 2) { |
|
444
|
|
|
|
|
|
|
# Two args: query + file (in this order) |
|
445
|
13
|
50
|
|
|
|
45
|
_usage_error('--null-input cannot be combined with file input') if $null_input; |
|
446
|
|
|
|
|
|
|
|
|
447
|
13
|
50
|
|
|
|
67
|
if (!defined $query) { |
|
448
|
13
|
|
|
|
|
36
|
$query = $ARGV[0]; |
|
449
|
|
|
|
|
|
|
} else { |
|
450
|
0
|
|
|
|
|
0
|
_usage_error('Cannot provide both --from-file and a query argument'); |
|
451
|
|
|
|
|
|
|
} |
|
452
|
|
|
|
|
|
|
|
|
453
|
13
|
|
|
|
|
28
|
my $f = $ARGV[1]; |
|
454
|
13
|
100
|
|
|
|
536
|
if (-f $f) { |
|
455
|
12
|
|
|
|
|
41
|
$filename = $f; |
|
456
|
|
|
|
|
|
|
} else { |
|
457
|
1
|
|
|
|
|
20
|
_input_error("Cannot open file '$f': $!"); |
|
458
|
|
|
|
|
|
|
} |
|
459
|
|
|
|
|
|
|
} |
|
460
|
|
|
|
|
|
|
else { |
|
461
|
0
|
|
|
|
|
0
|
_usage_error("Usage: jq-lite [options] '.query' [file.json]\n" . |
|
462
|
|
|
|
|
|
|
" jq-lite [options] -f query.jq [file.json]"); |
|
463
|
|
|
|
|
|
|
} |
|
464
|
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
# ---------- JSON decoder selection ---------- |
|
466
|
48
|
50
|
|
|
|
145
|
if ($decoder_choice) { |
|
467
|
0
|
0
|
|
|
|
0
|
if ($decoder_choice eq 'JSON::MaybeXS') { |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
468
|
0
|
|
|
|
|
0
|
require JSON::MaybeXS; |
|
469
|
0
|
|
|
|
|
0
|
$decoder = \&JSON::MaybeXS::decode_json; |
|
470
|
0
|
|
|
|
|
0
|
$decoder_module = 'JSON::MaybeXS'; |
|
471
|
|
|
|
|
|
|
} |
|
472
|
|
|
|
|
|
|
elsif ($decoder_choice eq 'Cpanel::JSON::XS') { |
|
473
|
0
|
|
|
|
|
0
|
require Cpanel::JSON::XS; |
|
474
|
0
|
|
|
|
|
0
|
$decoder = \&Cpanel::JSON::XS::decode_json; |
|
475
|
0
|
|
|
|
|
0
|
$decoder_module = 'Cpanel::JSON::XS'; |
|
476
|
|
|
|
|
|
|
} |
|
477
|
|
|
|
|
|
|
elsif ($decoder_choice eq 'JSON::XS') { |
|
478
|
0
|
|
|
|
|
0
|
require JSON::XS; |
|
479
|
0
|
|
|
|
|
0
|
$decoder = \&JSON::XS::decode_json; |
|
480
|
0
|
|
|
|
|
0
|
$decoder_module = 'JSON::XS'; |
|
481
|
|
|
|
|
|
|
} |
|
482
|
|
|
|
|
|
|
elsif ($decoder_choice eq 'JSON::PP') { |
|
483
|
0
|
|
|
|
|
0
|
require JSON::PP; |
|
484
|
0
|
|
|
|
|
0
|
$decoder = \&JQ::Lite::Util::_decode_json; |
|
485
|
0
|
|
|
|
|
0
|
$decoder_module = 'JSON::PP'; |
|
486
|
|
|
|
|
|
|
} |
|
487
|
|
|
|
|
|
|
else { |
|
488
|
0
|
|
|
|
|
0
|
_usage_error("Unknown JSON module: $decoder_choice"); |
|
489
|
|
|
|
|
|
|
} |
|
490
|
|
|
|
|
|
|
} |
|
491
|
|
|
|
|
|
|
else { |
|
492
|
48
|
50
|
|
|
|
141
|
if (eval { require JSON::MaybeXS; 1 }) { |
|
|
48
|
0
|
|
|
|
32103
|
|
|
|
48
|
0
|
|
|
|
457772
|
|
|
493
|
48
|
|
|
|
|
205
|
$decoder = \&JSON::MaybeXS::decode_json; |
|
494
|
48
|
|
|
|
|
139
|
$decoder_module = 'JSON::MaybeXS'; |
|
495
|
|
|
|
|
|
|
} |
|
496
|
0
|
|
|
|
|
0
|
elsif (eval { require Cpanel::JSON::XS; 1 }) { |
|
|
0
|
|
|
|
|
0
|
|
|
497
|
0
|
|
|
|
|
0
|
$decoder = \&Cpanel::JSON::XS::decode_json; |
|
498
|
0
|
|
|
|
|
0
|
$decoder_module = 'Cpanel::JSON::XS'; |
|
499
|
|
|
|
|
|
|
} |
|
500
|
0
|
|
|
|
|
0
|
elsif (eval { require JSON::XS; 1 }) { |
|
|
0
|
|
|
|
|
0
|
|
|
501
|
0
|
|
|
|
|
0
|
$decoder = \&JSON::XS::decode_json; |
|
502
|
0
|
|
|
|
|
0
|
$decoder_module = 'JSON::XS'; |
|
503
|
|
|
|
|
|
|
} |
|
504
|
|
|
|
|
|
|
else { |
|
505
|
0
|
|
|
|
|
0
|
require JSON::PP; |
|
506
|
0
|
|
|
|
|
0
|
$decoder = \&JQ::Lite::Util::_decode_json; |
|
507
|
0
|
|
|
|
|
0
|
$decoder_module = 'JSON::PP'; |
|
508
|
|
|
|
|
|
|
} |
|
509
|
|
|
|
|
|
|
} |
|
510
|
|
|
|
|
|
|
|
|
511
|
48
|
50
|
|
|
|
252
|
warn "[DEBUG] Using $decoder_module\n" if $decoder_debug; |
|
512
|
|
|
|
|
|
|
|
|
513
|
|
|
|
|
|
|
# ---------- --slurpfile handling ---------- |
|
514
|
48
|
100
|
|
|
|
232
|
if (@slurpfiles) { |
|
515
|
4
|
|
|
|
|
20
|
for (my $i = 0; $i < @slurpfiles; $i += 2) { |
|
516
|
4
|
|
|
|
|
23
|
my ($var_name, $file_path) = @slurpfiles[$i, $i + 1]; |
|
517
|
|
|
|
|
|
|
|
|
518
|
4
|
50
|
33
|
|
|
41
|
_usage_error('--slurpfile requires a variable name') if !defined $var_name || $var_name eq ''; |
|
519
|
4
|
50
|
|
|
|
37
|
if ($var_name !~ /^[A-Za-z_]\w*$/) { |
|
520
|
0
|
|
|
|
|
0
|
_usage_error("Invalid variable name '$var_name' for --slurpfile"); |
|
521
|
|
|
|
|
|
|
} |
|
522
|
|
|
|
|
|
|
|
|
523
|
4
|
50
|
33
|
|
|
27
|
_usage_error('--slurpfile requires a file path') if !defined $file_path || $file_path eq ''; |
|
524
|
|
|
|
|
|
|
|
|
525
|
4
|
100
|
|
|
|
48
|
my $fh = IO::File->new($file_path, 'r') |
|
526
|
|
|
|
|
|
|
or _usage_error("cannot read file: $file_path"); |
|
527
|
3
|
|
|
|
|
464
|
local $/; |
|
528
|
3
|
|
|
|
|
76
|
my $json_text = <$fh>; |
|
529
|
3
|
50
|
|
|
|
13
|
$json_text = '' unless defined $json_text; |
|
530
|
3
|
|
|
|
|
26
|
$fh->close; |
|
531
|
|
|
|
|
|
|
|
|
532
|
3
|
|
|
|
|
75
|
my $decoded = eval { _decode_json_stream($json_text) }; |
|
|
3
|
|
|
|
|
16
|
|
|
533
|
3
|
100
|
|
|
|
337
|
if ($@) { |
|
534
|
1
|
|
|
|
|
5
|
_usage_error("invalid JSON in slurpfile $var_name"); |
|
535
|
|
|
|
|
|
|
} |
|
536
|
|
|
|
|
|
|
|
|
537
|
2
|
|
|
|
|
19
|
$arg_vars{$var_name} = $decoded; |
|
538
|
|
|
|
|
|
|
} |
|
539
|
|
|
|
|
|
|
} |
|
540
|
|
|
|
|
|
|
|
|
541
|
|
|
|
|
|
|
# ---------- Validate query before reading input ---------- |
|
542
|
46
|
50
|
|
|
|
179
|
if (defined $query) { |
|
543
|
46
|
|
|
|
|
275
|
_validate_query_syntax($query); |
|
544
|
|
|
|
|
|
|
} |
|
545
|
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
# ---------- Read JSON input ---------- |
|
547
|
43
|
|
|
|
|
603
|
my $json_text; |
|
548
|
43
|
|
|
|
|
404
|
my $use_yaml = 0; |
|
549
|
43
|
100
|
|
|
|
319
|
if ($null_input) { |
|
|
|
100
|
|
|
|
|
|
|
550
|
8
|
100
|
|
|
|
28
|
$json_text = $slurp_input ? '[]' : 'null'; |
|
551
|
|
|
|
|
|
|
} |
|
552
|
|
|
|
|
|
|
elsif (defined $filename) { |
|
553
|
13
|
50
|
|
|
|
734
|
open my $fh, '<', $filename or _input_error("Cannot open file '$filename': $!"); |
|
554
|
13
|
|
|
|
|
77
|
local $/; |
|
555
|
13
|
|
|
|
|
469
|
$json_text = <$fh>; |
|
556
|
13
|
|
|
|
|
196
|
close $fh; |
|
557
|
13
|
100
|
66
|
|
|
242
|
$use_yaml = 1 if !$force_yaml && $filename =~ /\.ya?ml\z/i; |
|
558
|
|
|
|
|
|
|
} |
|
559
|
|
|
|
|
|
|
else { |
|
560
|
|
|
|
|
|
|
# When no file is given, if STDIN is a TTY, error out to avoid blocking |
|
561
|
22
|
50
|
|
|
|
232
|
if (-t STDIN) { |
|
562
|
0
|
|
|
|
|
0
|
_input_error('No input provided. Pass a file or pipe JSON via STDIN.'); |
|
563
|
|
|
|
|
|
|
} |
|
564
|
22
|
|
|
|
|
136
|
local $/; |
|
565
|
22
|
|
|
|
|
833
|
$json_text = ; |
|
566
|
|
|
|
|
|
|
} |
|
567
|
|
|
|
|
|
|
|
|
568
|
43
|
100
|
|
|
|
197
|
$use_yaml = 0 if $raw_input; |
|
569
|
43
|
100
|
66
|
|
|
240
|
$use_yaml = 1 if $force_yaml && !$null_input; |
|
570
|
|
|
|
|
|
|
|
|
571
|
43
|
100
|
|
|
|
211
|
if ($use_yaml) { |
|
572
|
2
|
|
|
|
|
10
|
$json_text = convert_yaml_to_json($json_text); |
|
573
|
2
|
50
|
33
|
|
|
598
|
warn "[DEBUG] Parsing YAML with $yaml_module\n" if $decoder_debug && $yaml_module; |
|
574
|
|
|
|
|
|
|
} |
|
575
|
|
|
|
|
|
|
|
|
576
|
|
|
|
|
|
|
# ---------- JQ::Lite core ---------- |
|
577
|
43
|
|
|
|
|
647
|
my $jq = JQ::Lite->new(raw => $raw_output, vars => \%arg_vars); |
|
578
|
|
|
|
|
|
|
|
|
579
|
43
|
100
|
|
|
|
181
|
if ($raw_input) { |
|
580
|
4
|
|
|
|
|
8
|
$use_yaml = 0; # Raw input is plain text; ignore YAML handling |
|
581
|
|
|
|
|
|
|
} |
|
582
|
|
|
|
|
|
|
|
|
583
|
43
|
|
|
|
|
95
|
my @raw_lines; |
|
584
|
43
|
100
|
|
|
|
182
|
if ($raw_input) { |
|
585
|
4
|
|
50
|
|
|
14
|
my $text = $json_text // ''; |
|
586
|
4
|
|
|
|
|
38
|
my $encoder = JSON::PP->new->utf8->allow_nonref; |
|
587
|
|
|
|
|
|
|
|
|
588
|
4
|
100
|
|
|
|
342
|
if ($slurp_input) { |
|
589
|
2
|
|
|
|
|
9
|
$json_text = $encoder->encode($text); |
|
590
|
|
|
|
|
|
|
} |
|
591
|
|
|
|
|
|
|
else { |
|
592
|
2
|
|
|
|
|
6442
|
@raw_lines = split /\r?\n/, $text, -1; |
|
593
|
2
|
50
|
33
|
|
|
57
|
if (@raw_lines && $raw_lines[-1] eq '' && $text =~ /\r?\n\z/) { |
|
|
|
|
33
|
|
|
|
|
|
594
|
2
|
|
|
|
|
36
|
pop @raw_lines; |
|
595
|
|
|
|
|
|
|
} |
|
596
|
|
|
|
|
|
|
} |
|
597
|
|
|
|
|
|
|
} |
|
598
|
|
|
|
|
|
|
|
|
599
|
43
|
100
|
100
|
|
|
820
|
if (!$raw_input && $slurp_input && !$null_input) { |
|
|
|
|
100
|
|
|
|
|
|
600
|
2
|
|
|
|
|
5
|
my $decoded = eval { _decode_json_stream($json_text) }; |
|
|
2
|
|
|
|
|
10
|
|
|
601
|
2
|
0
|
33
|
|
|
26
|
if (!$decoded && $@) { |
|
602
|
0
|
|
|
|
|
0
|
_input_error("Failed to decode JSON stream for --slurp: $@"); |
|
603
|
|
|
|
|
|
|
} |
|
604
|
|
|
|
|
|
|
|
|
605
|
2
|
|
|
|
|
12
|
$json_text = JSON::PP->new->encode($decoded); |
|
606
|
|
|
|
|
|
|
} |
|
607
|
|
|
|
|
|
|
|
|
608
|
43
|
100
|
|
|
|
734
|
if (!$raw_input) { |
|
609
|
39
|
100
|
|
|
|
107
|
eval { JQ::Lite::Util::_decode_json($json_text); 1 } |
|
|
39
|
|
|
|
|
353
|
|
|
|
38
|
|
|
|
|
18650
|
|
|
610
|
|
|
|
|
|
|
or _input_error("Failed to parse JSON input: $@"); |
|
611
|
|
|
|
|
|
|
} |
|
612
|
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
sub convert_yaml_to_json { |
|
614
|
2
|
|
|
2
|
|
4
|
my ($text) = @_; |
|
615
|
2
|
50
|
|
|
|
5
|
$text = '' unless defined $text; |
|
616
|
|
|
|
|
|
|
|
|
617
|
2
|
50
|
|
|
|
14
|
($yaml_loader, $yaml_module) = _build_yaml_loader() unless $yaml_loader; |
|
618
|
|
|
|
|
|
|
|
|
619
|
2
|
|
|
|
|
4
|
my @docs = eval { $yaml_loader->($text) }; |
|
|
2
|
|
|
|
|
6
|
|
|
620
|
2
|
50
|
|
|
|
7
|
if (my $err = $@) { |
|
621
|
0
|
|
|
|
|
0
|
$err =~ s/\s+\z//; |
|
622
|
0
|
|
|
|
|
0
|
_input_error("Failed to parse YAML input: $err"); |
|
623
|
|
|
|
|
|
|
} |
|
624
|
|
|
|
|
|
|
|
|
625
|
2
|
|
|
|
|
3
|
my $data; |
|
626
|
2
|
50
|
|
|
|
13
|
if (!@docs) { |
|
|
|
50
|
|
|
|
|
|
|
627
|
0
|
|
|
|
|
0
|
$data = undef; |
|
628
|
|
|
|
|
|
|
} |
|
629
|
|
|
|
|
|
|
elsif (@docs == 1) { |
|
630
|
2
|
|
|
|
|
4
|
$data = $docs[0]; |
|
631
|
|
|
|
|
|
|
} |
|
632
|
|
|
|
|
|
|
else { |
|
633
|
0
|
|
|
|
|
0
|
$data = \@docs; |
|
634
|
|
|
|
|
|
|
} |
|
635
|
|
|
|
|
|
|
|
|
636
|
2
|
|
|
|
|
27
|
return JSON::PP->new->allow_nonref->encode($data); |
|
637
|
|
|
|
|
|
|
} |
|
638
|
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
sub _build_yaml_loader { |
|
640
|
2
|
50
|
|
2
|
|
4
|
if (eval { require YAML::XS; 1 }) { |
|
|
2
|
50
|
|
|
|
497
|
|
|
|
0
|
|
|
|
|
0
|
|
|
641
|
0
|
|
|
0
|
|
0
|
return (sub { YAML::XS::Load($_[0]) }, 'YAML::XS'); |
|
|
0
|
|
|
|
|
0
|
|
|
642
|
|
|
|
|
|
|
} |
|
643
|
2
|
|
|
|
|
113
|
elsif (eval { require YAML::PP; 1 }) { |
|
|
0
|
|
|
|
|
0
|
|
|
644
|
0
|
|
|
|
|
0
|
require YAML::PP; |
|
645
|
0
|
|
|
|
|
0
|
my $yp = YAML::PP->new(boolean => 'JSON::PP'); |
|
646
|
0
|
|
|
0
|
|
0
|
return (sub { $yp->load_string($_[0]) }, 'YAML::PP'); |
|
|
0
|
|
|
|
|
0
|
|
|
647
|
|
|
|
|
|
|
} |
|
648
|
|
|
|
|
|
|
else { |
|
649
|
2
|
|
|
|
|
927
|
require CPAN::Meta::YAML; |
|
650
|
|
|
|
|
|
|
return ( |
|
651
|
|
|
|
|
|
|
sub { |
|
652
|
2
|
|
50
|
2
|
|
63
|
my $string = shift // ''; |
|
653
|
2
|
|
|
|
|
22
|
my $docs = CPAN::Meta::YAML->read_string($string); |
|
654
|
2
|
50
|
|
|
|
1046
|
if (!defined $docs) { |
|
655
|
0
|
|
|
|
|
0
|
my $err; |
|
656
|
|
|
|
|
|
|
{ |
|
657
|
63
|
|
|
63
|
|
138516
|
no warnings 'once'; |
|
|
63
|
|
|
|
|
139
|
|
|
|
63
|
|
|
|
|
5999479
|
|
|
|
0
|
|
|
|
|
0
|
|
|
658
|
0
|
|
|
|
|
0
|
$err = $CPAN::Meta::YAML::errstr; |
|
659
|
|
|
|
|
|
|
} |
|
660
|
0
|
|
0
|
|
|
0
|
$err ||= 'Unknown YAML parsing error'; |
|
661
|
0
|
|
|
|
|
0
|
die $err; |
|
662
|
|
|
|
|
|
|
} |
|
663
|
2
|
|
|
|
|
3
|
return @{$docs}; |
|
|
2
|
|
|
|
|
18
|
|
|
664
|
|
|
|
|
|
|
}, |
|
665
|
2
|
|
|
|
|
10865
|
'CPAN::Meta::YAML' |
|
666
|
|
|
|
|
|
|
); |
|
667
|
|
|
|
|
|
|
} |
|
668
|
|
|
|
|
|
|
} |
|
669
|
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
sub _decode_json_stream { |
|
671
|
5
|
|
|
5
|
|
15
|
my ($text) = @_; |
|
672
|
5
|
50
|
|
|
|
18
|
$text = '' unless defined $text; |
|
673
|
|
|
|
|
|
|
|
|
674
|
5
|
|
|
|
|
12
|
my $length = length $text; |
|
675
|
5
|
|
|
|
|
23
|
my $offset = 0; |
|
676
|
5
|
|
|
|
|
15
|
my @values; |
|
677
|
5
|
|
|
|
|
51
|
my $decoder = JSON::PP->new->utf8->allow_nonref; |
|
678
|
|
|
|
|
|
|
|
|
679
|
5
|
|
|
|
|
403
|
while (1) { |
|
680
|
11
|
|
100
|
|
|
87
|
while ($offset < $length && substr($text, $offset, 1) =~ /\s/) { |
|
681
|
6
|
|
|
|
|
23
|
$offset++; |
|
682
|
|
|
|
|
|
|
} |
|
683
|
11
|
100
|
|
|
|
28
|
last if $offset >= $length; |
|
684
|
|
|
|
|
|
|
|
|
685
|
7
|
|
|
|
|
12
|
my ($value, $consumed); |
|
686
|
7
|
|
|
|
|
39
|
($value, $consumed) = $decoder->decode_prefix(substr($text, $offset)); |
|
687
|
|
|
|
|
|
|
|
|
688
|
6
|
|
|
|
|
1770
|
push @values, $value; |
|
689
|
6
|
|
|
|
|
15
|
$offset += $consumed; |
|
690
|
|
|
|
|
|
|
} |
|
691
|
|
|
|
|
|
|
|
|
692
|
4
|
|
|
|
|
57
|
return \@values; |
|
693
|
|
|
|
|
|
|
} |
|
694
|
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
# ---------- Colorization ---------- |
|
696
|
|
|
|
|
|
|
sub colorize_json { |
|
697
|
0
|
|
|
0
|
|
0
|
my $json = shift; |
|
698
|
|
|
|
|
|
|
|
|
699
|
0
|
|
|
|
|
0
|
$json =~ s/"([^"]+)"(?=\s*:)/color("cyan")."\"$1\"".color("reset")/ge; |
|
|
0
|
|
|
|
|
0
|
|
|
700
|
0
|
|
|
|
|
0
|
$json =~ s/(:\s*)"([^"]*)"/$1.color("green")."\"$2\"".color("reset")/ge; |
|
|
0
|
|
|
|
|
0
|
|
|
701
|
0
|
|
|
|
|
0
|
$json =~ s/(:\s*)(-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)/$1.color("yellow")."$2".color("reset")/ge; |
|
|
0
|
|
|
|
|
0
|
|
|
702
|
0
|
|
|
|
|
0
|
$json =~ s/(:\s*)(true|false|null)/$1.color("magenta")."$2".color("reset")/ge; |
|
|
0
|
|
|
|
|
0
|
|
|
703
|
|
|
|
|
|
|
|
|
704
|
0
|
|
|
|
|
0
|
return $json; |
|
705
|
|
|
|
|
|
|
} |
|
706
|
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
# ---------- Output ---------- |
|
708
|
|
|
|
|
|
|
sub print_results { |
|
709
|
39
|
|
|
39
|
|
2092
|
my @results = @_; |
|
710
|
39
|
|
|
|
|
453
|
my $pp = JSON::PP->new->utf8->allow_nonref->canonical; |
|
711
|
39
|
100
|
|
|
|
13388
|
$pp->pretty unless $compact_output; |
|
712
|
39
|
|
|
|
|
2226
|
$pp->ascii($ascii_output); |
|
713
|
|
|
|
|
|
|
|
|
714
|
39
|
|
|
|
|
586
|
for my $r (@results) { |
|
715
|
2089
|
100
|
33
|
|
|
5898
|
if (!defined $r) { |
|
|
|
50
|
|
|
|
|
|
|
716
|
2
|
|
|
|
|
47
|
print "null\n"; |
|
717
|
|
|
|
|
|
|
} |
|
718
|
|
|
|
|
|
|
elsif ($raw_output && !ref($r)) { |
|
719
|
0
|
|
|
|
|
0
|
print "$r\n"; |
|
720
|
|
|
|
|
|
|
} |
|
721
|
|
|
|
|
|
|
else { |
|
722
|
2087
|
|
|
|
|
4270
|
my $json = $pp->encode($r); |
|
723
|
2087
|
50
|
|
|
|
135260
|
$json = colorize_json($json) if $color_output; |
|
724
|
2087
|
|
|
|
|
4024
|
print $json; |
|
725
|
2087
|
100
|
|
|
|
7326
|
print "\n" unless $json =~ /\n\z/; |
|
726
|
|
|
|
|
|
|
} |
|
727
|
|
|
|
|
|
|
} |
|
728
|
|
|
|
|
|
|
} |
|
729
|
|
|
|
|
|
|
|
|
730
|
|
|
|
|
|
|
# ---------- Interactive mode ---------- |
|
731
|
42
|
50
|
66
|
|
|
266
|
if ($raw_input && !defined $query) { |
|
732
|
0
|
0
|
|
|
|
0
|
my $reason = $slurp_input |
|
733
|
|
|
|
|
|
|
? '--raw-input requires a query when used with --slurp.' |
|
734
|
|
|
|
|
|
|
: '--raw-input requires a query when not using --slurp.'; |
|
735
|
0
|
|
|
|
|
0
|
_usage_error($reason); |
|
736
|
|
|
|
|
|
|
} |
|
737
|
|
|
|
|
|
|
|
|
738
|
42
|
50
|
|
|
|
219
|
if (!defined $query) { |
|
739
|
0
|
|
|
|
|
0
|
system("stty -icanon -echo"); |
|
740
|
|
|
|
|
|
|
|
|
741
|
|
|
|
|
|
|
$SIG{INT} = sub { |
|
742
|
0
|
|
|
0
|
|
0
|
system("stty sane"); |
|
743
|
0
|
|
|
|
|
0
|
print "\n[EXIT]\n"; |
|
744
|
0
|
|
|
|
|
0
|
exit 0; |
|
745
|
0
|
|
|
|
|
0
|
}; |
|
746
|
|
|
|
|
|
|
|
|
747
|
0
|
|
|
|
|
0
|
my $input = ''; |
|
748
|
0
|
|
|
|
|
0
|
my @last_results; |
|
749
|
|
|
|
|
|
|
|
|
750
|
0
|
|
|
|
|
0
|
my $ok = eval { |
|
751
|
0
|
|
|
|
|
0
|
@last_results = $jq->run_query($json_text, '.'); |
|
752
|
0
|
|
|
|
|
0
|
1; |
|
753
|
|
|
|
|
|
|
}; |
|
754
|
0
|
0
|
0
|
|
|
0
|
if (!$ok || !@last_results) { |
|
755
|
0
|
|
|
|
|
0
|
my $data = eval { JQ::Lite::Util::_decode_json($json_text) }; |
|
|
0
|
|
|
|
|
0
|
|
|
756
|
0
|
0
|
|
|
|
0
|
if ($data) { |
|
757
|
0
|
|
|
|
|
0
|
@last_results = ($data); |
|
758
|
|
|
|
|
|
|
} |
|
759
|
|
|
|
|
|
|
} |
|
760
|
|
|
|
|
|
|
|
|
761
|
0
|
|
|
|
|
0
|
system("clear"); |
|
762
|
0
|
0
|
|
|
|
0
|
if (@last_results) { |
|
763
|
0
|
|
|
|
|
0
|
print_results(@last_results); |
|
764
|
|
|
|
|
|
|
} else { |
|
765
|
0
|
|
|
|
|
0
|
print "[INFO] Failed to load initial JSON data.\n"; |
|
766
|
|
|
|
|
|
|
} |
|
767
|
|
|
|
|
|
|
|
|
768
|
0
|
|
|
|
|
0
|
print "\nType query (ESC to quit):\n"; |
|
769
|
0
|
|
|
|
|
0
|
print "> $input\n"; |
|
770
|
|
|
|
|
|
|
|
|
771
|
0
|
|
|
|
|
0
|
while (1) { |
|
772
|
0
|
|
|
|
|
0
|
my $char; |
|
773
|
0
|
|
|
|
|
0
|
sysread(STDIN, $char, 1); |
|
774
|
|
|
|
|
|
|
|
|
775
|
0
|
|
|
|
|
0
|
my $ord = ord($char); |
|
776
|
0
|
0
|
|
|
|
0
|
last if $ord == 27; # ESC |
|
777
|
|
|
|
|
|
|
|
|
778
|
0
|
0
|
0
|
|
|
0
|
if ($ord == 127 || $char eq "\b") { |
|
779
|
0
|
0
|
|
|
|
0
|
chop $input if length($input); |
|
780
|
|
|
|
|
|
|
} else { |
|
781
|
0
|
|
|
|
|
0
|
$input .= $char; |
|
782
|
|
|
|
|
|
|
} |
|
783
|
|
|
|
|
|
|
|
|
784
|
0
|
|
|
|
|
0
|
system("clear"); |
|
785
|
|
|
|
|
|
|
|
|
786
|
0
|
|
|
|
|
0
|
my @results; |
|
787
|
0
|
|
|
|
|
0
|
my $ok = eval { |
|
788
|
0
|
|
|
|
|
0
|
@results = $jq->run_query($json_text, $input); |
|
789
|
0
|
|
|
|
|
0
|
1; |
|
790
|
|
|
|
|
|
|
}; |
|
791
|
|
|
|
|
|
|
|
|
792
|
0
|
0
|
0
|
|
|
0
|
if ($ok && @results) { |
|
793
|
0
|
|
|
|
|
0
|
@last_results = @results; |
|
794
|
|
|
|
|
|
|
} |
|
795
|
|
|
|
|
|
|
|
|
796
|
0
|
0
|
|
|
|
0
|
if (!$ok) { |
|
|
|
0
|
|
|
|
|
|
|
797
|
0
|
|
|
|
|
0
|
print "[INFO] Invalid or partial query. Showing last valid results.\n"; |
|
798
|
|
|
|
|
|
|
} elsif (!@results) { |
|
799
|
0
|
|
|
|
|
0
|
print "[INFO] Query returned no results. Showing last valid results.\n"; |
|
800
|
|
|
|
|
|
|
} |
|
801
|
|
|
|
|
|
|
|
|
802
|
0
|
0
|
|
|
|
0
|
if (@last_results) { |
|
803
|
|
|
|
|
|
|
eval { |
|
804
|
0
|
|
|
|
|
0
|
print_results(@last_results); |
|
805
|
0
|
|
|
|
|
0
|
1; |
|
806
|
0
|
0
|
|
|
|
0
|
} or do { |
|
807
|
0
|
|
0
|
|
|
0
|
my $e = $@ || 'Unknown error'; |
|
808
|
0
|
|
|
|
|
0
|
print "[RUNTIME]Failed to print: $e\n"; |
|
809
|
|
|
|
|
|
|
}; |
|
810
|
|
|
|
|
|
|
} else { |
|
811
|
0
|
|
|
|
|
0
|
print "[INFO] No previous valid results.\n"; |
|
812
|
|
|
|
|
|
|
} |
|
813
|
|
|
|
|
|
|
|
|
814
|
0
|
|
|
|
|
0
|
print "\n> $input\n"; |
|
815
|
|
|
|
|
|
|
} |
|
816
|
|
|
|
|
|
|
|
|
817
|
0
|
|
|
|
|
0
|
system("stty sane"); |
|
818
|
0
|
|
|
|
|
0
|
print "\nGoodbye.\n"; |
|
819
|
0
|
|
|
|
|
0
|
exit 0; |
|
820
|
|
|
|
|
|
|
} |
|
821
|
|
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
# ---------- One-shot mode ---------- |
|
823
|
42
|
|
|
|
|
321
|
my @results; |
|
824
|
|
|
|
|
|
|
|
|
825
|
42
|
100
|
100
|
|
|
327
|
if ($raw_input && !$slurp_input) { |
|
826
|
2
|
|
|
|
|
12
|
my $encoder = JSON::PP->new->utf8->allow_nonref; |
|
827
|
2
|
|
|
|
|
197
|
for my $line (@raw_lines) { |
|
828
|
5002
|
|
|
|
|
15322
|
my $encoded_line = $encoder->encode($line); |
|
829
|
5002
|
|
|
|
|
341794
|
my @line_results = eval { $jq->run_query($encoded_line, $query) }; |
|
|
5002
|
|
|
|
|
16325
|
|
|
830
|
5002
|
50
|
|
|
|
12550
|
if ($@) { |
|
831
|
0
|
|
0
|
|
|
0
|
my $err = $@ || 'Unknown error'; |
|
832
|
0
|
|
|
|
|
0
|
$err =~ s/\s+\z//; |
|
833
|
0
|
|
|
|
|
0
|
_runtime_error($err); |
|
834
|
|
|
|
|
|
|
} |
|
835
|
5002
|
|
|
|
|
15738
|
push @results, @line_results; |
|
836
|
|
|
|
|
|
|
} |
|
837
|
|
|
|
|
|
|
} |
|
838
|
|
|
|
|
|
|
else { |
|
839
|
40
|
|
|
|
|
113
|
@results = eval { $jq->run_query($json_text, $query) }; |
|
|
40
|
|
|
|
|
337
|
|
|
840
|
40
|
100
|
|
|
|
312
|
if ($@) { |
|
841
|
3
|
|
50
|
|
|
10
|
my $err = $@ || 'Unknown error'; |
|
842
|
3
|
|
|
|
|
27
|
$err =~ s/\s+\z//; |
|
843
|
3
|
|
|
|
|
26
|
_runtime_error($err); |
|
844
|
|
|
|
|
|
|
} |
|
845
|
|
|
|
|
|
|
} |
|
846
|
|
|
|
|
|
|
|
|
847
|
39
|
|
|
|
|
170
|
my $status = 0; |
|
848
|
39
|
100
|
|
|
|
170
|
if ($exit_status) { |
|
849
|
4
|
100
|
|
|
|
15
|
my $last = @results ? $results[-1] : undef; |
|
850
|
4
|
100
|
|
|
|
15
|
$status = _is_truthy($last) ? 0 : 1; |
|
851
|
|
|
|
|
|
|
} |
|
852
|
|
|
|
|
|
|
|
|
853
|
|
|
|
|
|
|
sub print_supported_functions { |
|
854
|
0
|
|
|
0
|
|
|
print <<'EOF'; |
|
855
|
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
Supported Functions: |
|
857
|
|
|
|
|
|
|
length - Count array elements, hash keys, or characters in scalars |
|
858
|
|
|
|
|
|
|
keys - Extract sorted keys from a hash or indexes from an array |
|
859
|
|
|
|
|
|
|
keys_unsorted - Extract object keys without sorting (jq-compatible) |
|
860
|
|
|
|
|
|
|
values - Extract values from a hash (v0.34) |
|
861
|
|
|
|
|
|
|
leaf_paths() - Emit only terminal paths to leaf values |
|
862
|
|
|
|
|
|
|
sort - Sort array items |
|
863
|
|
|
|
|
|
|
sort_desc - Sort array items in descending order |
|
864
|
|
|
|
|
|
|
sort_by(KEY) - Sort array of objects by key |
|
865
|
|
|
|
|
|
|
pluck(KEY) - Collect a key's value from each object in an array |
|
866
|
|
|
|
|
|
|
pick(KEYS...) - Build new objects containing only the supplied keys (arrays handled element-wise) |
|
867
|
|
|
|
|
|
|
merge_objects() - Merge arrays of objects into a single hash (last-write-wins) |
|
868
|
|
|
|
|
|
|
unique - Remove duplicate values |
|
869
|
|
|
|
|
|
|
unique_by(KEY) - Remove duplicates by projecting each item on KEY |
|
870
|
|
|
|
|
|
|
reverse - Reverse an array |
|
871
|
|
|
|
|
|
|
first / last - Get first / last element of an array |
|
872
|
|
|
|
|
|
|
limit(N) - Limit array to first N elements |
|
873
|
|
|
|
|
|
|
drop(N) - Skip the first N elements of an array |
|
874
|
|
|
|
|
|
|
rest - Drop the first element of an array |
|
875
|
|
|
|
|
|
|
tail(N) - Return the final N elements of an array |
|
876
|
|
|
|
|
|
|
chunks(N) - Split array into subarrays each containing up to N items |
|
877
|
|
|
|
|
|
|
range(START; END[, STEP]) |
|
878
|
|
|
|
|
|
|
- Emit numbers from START (default 0) up to but not including END using STEP (default 1) |
|
879
|
|
|
|
|
|
|
enumerate() - Pair each array element with its zero-based index |
|
880
|
|
|
|
|
|
|
transpose() - Rotate arrays-of-arrays from rows into columns |
|
881
|
|
|
|
|
|
|
scalars - Pass through only scalar inputs (string/number/bool/null) |
|
882
|
|
|
|
|
|
|
objects - Pass through only object inputs |
|
883
|
|
|
|
|
|
|
count - Count total number of matching items |
|
884
|
|
|
|
|
|
|
map(EXPR) - Map/filter array items with a subquery |
|
885
|
|
|
|
|
|
|
map_values(FILTER) |
|
886
|
|
|
|
|
|
|
- Apply FILTER to each value in an object (dropping keys when FILTER yields no result) |
|
887
|
|
|
|
|
|
|
if COND then A [elif COND then B ...] [else Z] end |
|
888
|
|
|
|
|
|
|
- jq-style conditional branching across optional elif/else chains |
|
889
|
|
|
|
|
|
|
foreach(EXPR as $var (init; update [; extract])) |
|
890
|
|
|
|
|
|
|
- jq-compatible streaming reducer with lexical bindings and optional emitters |
|
891
|
|
|
|
|
|
|
walk(FILTER) - Recursively apply FILTER to every value in arrays and objects |
|
892
|
|
|
|
|
|
|
recurse([FILTER]) |
|
893
|
|
|
|
|
|
|
- Emit the current value and depth-first descendants using optional FILTER for children |
|
894
|
|
|
|
|
|
|
add / sum - Sum all numeric values in an array |
|
895
|
|
|
|
|
|
|
sum_by(KEY) - Sum numeric values projected from each array item |
|
896
|
|
|
|
|
|
|
avg_by(KEY) - Average numeric values projected from each array item |
|
897
|
|
|
|
|
|
|
median_by(KEY) - Return the median of numeric values projected from each array item |
|
898
|
|
|
|
|
|
|
min_by(PATH) - Return the element with the smallest projected value |
|
899
|
|
|
|
|
|
|
max_by(PATH) - Return the element with the largest projected value |
|
900
|
|
|
|
|
|
|
product - Multiply all numeric values in an array |
|
901
|
|
|
|
|
|
|
min / max - Return minimum / maximum numeric value in an array |
|
902
|
|
|
|
|
|
|
avg - Return the average of numeric values in an array |
|
903
|
|
|
|
|
|
|
median - Return the median of numeric values in an array |
|
904
|
|
|
|
|
|
|
mode - Return the most frequent value in an array (ties pick earliest occurrence) |
|
905
|
|
|
|
|
|
|
percentile(P) - Return the requested percentile (0-100 or 0-1) of numeric array values |
|
906
|
|
|
|
|
|
|
variance - Return the variance of numeric values in an array |
|
907
|
|
|
|
|
|
|
stddev - Return the standard deviation of numeric values in an array |
|
908
|
|
|
|
|
|
|
abs - Convert numbers (and array elements) to their absolute value |
|
909
|
|
|
|
|
|
|
ceil() - Round numbers up to the nearest integer |
|
910
|
|
|
|
|
|
|
floor() - Round numbers down to the nearest integer |
|
911
|
|
|
|
|
|
|
round() - Round numbers to the nearest integer (half-up semantics) |
|
912
|
|
|
|
|
|
|
clamp(MIN, MAX) - Clamp numeric values within an inclusive range |
|
913
|
|
|
|
|
|
|
tostring() - Convert values into their JSON string representation |
|
914
|
|
|
|
|
|
|
@json - Format the input as JSON text (jq-style formatter) |
|
915
|
|
|
|
|
|
|
@csv - Format arrays/scalars as a single CSV row |
|
916
|
|
|
|
|
|
|
@tsv - Format arrays/scalars as a single TSV row |
|
917
|
|
|
|
|
|
|
@base64 - Encode input as a Base64 string |
|
918
|
|
|
|
|
|
|
@base64d - Decode Base64-encoded text into strings/arrays |
|
919
|
|
|
|
|
|
|
@uri - Percent-encode text (URL-safe) |
|
920
|
|
|
|
|
|
|
tojson() - Encode values as JSON text regardless of type |
|
921
|
|
|
|
|
|
|
fromjson() - Decode JSON text into native values (arrays handled element-wise) |
|
922
|
|
|
|
|
|
|
tonumber() / to_number() |
|
923
|
|
|
|
|
|
|
- Coerce numeric-looking strings/booleans into numbers |
|
924
|
|
|
|
|
|
|
nth(N) - Get the Nth element of an array (zero-based index) |
|
925
|
|
|
|
|
|
|
index(VALUE) - Return the zero-based index of VALUE within arrays or strings |
|
926
|
|
|
|
|
|
|
rindex(VALUE) - Return the zero-based index of the last VALUE within arrays or strings |
|
927
|
|
|
|
|
|
|
indices(VALUE) - Return every index where VALUE occurs within arrays or strings |
|
928
|
|
|
|
|
|
|
group_by(KEY) - Group array items by field |
|
929
|
|
|
|
|
|
|
group_count(KEY) - Count grouped items by field |
|
930
|
|
|
|
|
|
|
join(SEPARATOR) - Join array elements with a string |
|
931
|
|
|
|
|
|
|
split(SEPARATOR) - Split string values (and arrays of strings) by a literal separator |
|
932
|
|
|
|
|
|
|
substr(START[, LENGTH]) |
|
933
|
|
|
|
|
|
|
- Extract substring using zero-based indices (arrays handled element-wise) |
|
934
|
|
|
|
|
|
|
slice(START[, LENGTH]) |
|
935
|
|
|
|
|
|
|
- Return a subarray using zero-based indices (negative starts count from the end) |
|
936
|
|
|
|
|
|
|
replace(OLD; NEW) |
|
937
|
|
|
|
|
|
|
- Replace literal substrings (arrays handled element-wise) |
|
938
|
|
|
|
|
|
|
to_entries - Convert objects/arrays into [{"key","value"}, ...] pairs |
|
939
|
|
|
|
|
|
|
from_entries - Convert entry arrays back into an object |
|
940
|
|
|
|
|
|
|
with_entries(FILTER) |
|
941
|
|
|
|
|
|
|
- Transform entries using FILTER and rebuild an object |
|
942
|
|
|
|
|
|
|
delpaths(PATHS) - Delete keys at the supplied PATHS array (jq-compatible) |
|
943
|
|
|
|
|
|
|
has - Check if objects contain a key or arrays expose an index |
|
944
|
|
|
|
|
|
|
contains - Check if strings include a fragment, arrays contain an element, or hashes have a key |
|
945
|
|
|
|
|
|
|
contains_subset(VALUE) |
|
946
|
|
|
|
|
|
|
- jq-style subset containment for arrays (order-insensitive) |
|
947
|
|
|
|
|
|
|
inside(CONTAINER) - Check if the input value is contained within CONTAINER |
|
948
|
|
|
|
|
|
|
any([FILTER]) - Return true if any input (optionally filtered) is truthy |
|
949
|
|
|
|
|
|
|
all([FILTER]) - Return true if every input (optionally filtered) is truthy |
|
950
|
|
|
|
|
|
|
not - Logical negation following jq truthiness rules |
|
951
|
|
|
|
|
|
|
test("pattern"[, "flags"]) - Check strings against regexes (flags: i, m, s, x) |
|
952
|
|
|
|
|
|
|
explode() - Convert strings to arrays of Unicode code points |
|
953
|
|
|
|
|
|
|
implode() - Turn arrays of code points back into strings |
|
954
|
|
|
|
|
|
|
flatten - Explicitly flatten arrays (same as .[]) |
|
955
|
|
|
|
|
|
|
flatten_all() - Recursively flatten nested arrays into a single array |
|
956
|
|
|
|
|
|
|
flatten_depth(N) - Flatten nested arrays up to N levels deep |
|
957
|
|
|
|
|
|
|
arrays - Pass through inputs that are arrays, discarding others |
|
958
|
|
|
|
|
|
|
del(KEY) - Remove a key from objects in the result |
|
959
|
|
|
|
|
|
|
compact - Remove undefined values from arrays |
|
960
|
|
|
|
|
|
|
path - Return available keys for objects or indexes for arrays |
|
961
|
|
|
|
|
|
|
paths - Emit every path to nested values as an array of keys/indices |
|
962
|
|
|
|
|
|
|
getpath(PATH) - Retrieve the value(s) at the supplied path array or expression |
|
963
|
|
|
|
|
|
|
setpath(PATH; VALUE) |
|
964
|
|
|
|
|
|
|
- Set or create value(s) at a path using literal or filter input |
|
965
|
|
|
|
|
|
|
is_empty - Check if an array or object is empty |
|
966
|
|
|
|
|
|
|
upper() - Convert scalars and array elements to uppercase |
|
967
|
|
|
|
|
|
|
lower() - Convert scalars and array elements to lowercase |
|
968
|
|
|
|
|
|
|
ascii_upcase() - ASCII-only uppercase conversion (leaves non-ASCII untouched) |
|
969
|
|
|
|
|
|
|
ascii_downcase() - ASCII-only lowercase conversion (leaves non-ASCII untouched) |
|
970
|
|
|
|
|
|
|
titlecase() - Convert scalars and array elements to title case |
|
971
|
|
|
|
|
|
|
trim() - Strip leading/trailing whitespace from strings (recurses into arrays) |
|
972
|
|
|
|
|
|
|
ltrimstr(PFX) - Remove PFX when it appears at the start of strings (arrays handled element-wise) |
|
973
|
|
|
|
|
|
|
rtrimstr(SFX) - Remove SFX when it appears at the end of strings (arrays handled element-wise) |
|
974
|
|
|
|
|
|
|
startswith(PFX) - Check if a string (or array of strings) begins with PFX |
|
975
|
|
|
|
|
|
|
endswith(SFX) - Check if a string (or array of strings) ends with SFX |
|
976
|
|
|
|
|
|
|
empty - Discard all output (for side-effect use) |
|
977
|
|
|
|
|
|
|
type() - Return the type of value ("string", "number", "boolean", "array", "object", "null") |
|
978
|
|
|
|
|
|
|
lhs // rhs - jq-style alternative operator yielding rhs when lhs is null/missing |
|
979
|
|
|
|
|
|
|
default(VALUE) - Substitute VALUE when result is undefined |
|
980
|
|
|
|
|
|
|
|
|
981
|
|
|
|
|
|
|
EOF |
|
982
|
|
|
|
|
|
|
} |
|
983
|
|
|
|
|
|
|
|
|
984
|
39
|
|
|
|
|
546
|
print_results(@results); |
|
985
|
|
|
|
|
|
|
|
|
986
|
38
|
|
|
|
|
4054
|
exit $status; |
|
987
|
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
__END__ |