Přemysl Janouch
a38ad4d64d
- skip_ws() -> gettoken() as it doesn't always skip whitespace - add a newline after each top-level token - since we've become a streaming parser, GNU parallel may not apply, so remove the comment at the top of the file
155 lines
3.7 KiB
Perl
Executable File
155 lines
3.7 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
use strict;
|
|
use warnings;
|
|
use Term::ANSIColor;
|
|
use Getopt::Long;
|
|
|
|
my $reset = color('reset');
|
|
my %format = (
|
|
FIELD => color('bold'),
|
|
NULL => color('cyan'),
|
|
BOOL => color('red'),
|
|
NUMBER => color('magenta'),
|
|
STRING => color('blue'),
|
|
ERROR => color('bold white on_red'),
|
|
);
|
|
|
|
my ($color, $keep_ws, $help) = 'auto';
|
|
if (!GetOptions('color=s' => \$color, 'keep-ws' => \$keep_ws, 'help' => \$help)
|
|
|| $help) {
|
|
print STDERR
|
|
"Usage: $0 [OPTION...] [FILE...]\n" .
|
|
"Pretty-print and colorify JSON\n" .
|
|
"\n" .
|
|
" --help print this help\n" .
|
|
" --keep-ws retain all original whitespace\n" .
|
|
" --color=COLOR 'always', 'never' or 'auto' (the default)\n";
|
|
exit 2;
|
|
}
|
|
|
|
%format = ()
|
|
if $color eq 'never' || $color eq 'auto' && !-t STDOUT;
|
|
|
|
# Hash lookup is the fastest way to qualify tokens, however it cannot be used
|
|
# for everything and we need to fall back to regular expressions
|
|
my %lookup = (
|
|
'[' => 'LBRACKET', '{' => 'LBRACE',
|
|
']' => 'RBRACKET', '}' => 'RBRACE',
|
|
':' => 'COLON', ',' => 'COMMA',
|
|
'true' => 'BOOL', 'false' => 'BOOL', 'null' => 'NULL',
|
|
);
|
|
my @pats = (
|
|
['"(?:[^\\\\"]*|\\\\(?:u[\da-f]{4}|["\\\\/bfnrt]))*"' => 'STRING'],
|
|
['-?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?' => 'NUMBER'],
|
|
['[ \t\r\n]+' => 'WS'],
|
|
);
|
|
my @tokens = map {[qr/^$_->[0]$/s, $_->[1]]} @pats;
|
|
|
|
# m//g is the fastest way to explode text into tokens in the first place
|
|
# and we need to construct an all-encompassing regular expression for it
|
|
my @all_pats = map {$_->[0]} @pats;
|
|
push @all_pats, quotemeta for keys %lookup;
|
|
my $any_token = qr/\G(${\join '|', @all_pats})/;
|
|
|
|
# FIXME: this probably shouldn't be a global variable
|
|
my $indent = 0;
|
|
|
|
sub nexttoken ($) {
|
|
my $json = shift;
|
|
if (!@$json) {
|
|
return unless defined (my $line = <>);
|
|
push @$json, $line =~ /$any_token/gsc;
|
|
push @$json, substr $line, pos $line
|
|
if pos $line != length $line;
|
|
}
|
|
|
|
my $text = shift @$json;
|
|
if (my $s = $lookup{$text}) {
|
|
return $s, $text;
|
|
}
|
|
for my $s (@tokens) {
|
|
return $s->[1], $text if $text =~ $s->[0];
|
|
}
|
|
return 'ERROR', $text;
|
|
}
|
|
|
|
sub gettoken ($) {
|
|
my $json = shift;
|
|
while (my ($token, $text) = nexttoken $json) {
|
|
next if !$keep_ws && $token eq 'WS';
|
|
return $token, $text;
|
|
}
|
|
return;
|
|
}
|
|
|
|
sub printindent () {
|
|
print "\n", ' ' x $indent;
|
|
}
|
|
|
|
sub do_value ($$$);
|
|
sub do_object ($) {
|
|
my $json = shift;
|
|
my $in_field_name = 1;
|
|
my $first = 1;
|
|
while (my ($token, $text) = gettoken $json) {
|
|
if ($token eq 'COLON') {
|
|
$in_field_name = 0;
|
|
} elsif ($token eq 'COMMA') {
|
|
$in_field_name = 1;
|
|
} elsif ($token eq 'STRING') {
|
|
$token = 'FIELD' if $in_field_name;
|
|
}
|
|
if ($token eq 'RBRACE') {
|
|
$indent--;
|
|
printindent unless $keep_ws;
|
|
} elsif ($first) {
|
|
printindent unless $keep_ws;
|
|
$first = 0;
|
|
}
|
|
do_value $token, $text, $json;
|
|
return if $token eq 'RBRACE';
|
|
}
|
|
}
|
|
|
|
sub do_array ($) {
|
|
my $json = shift;
|
|
my $first = 1;
|
|
while (my ($token, $text) = gettoken $json) {
|
|
if ($token eq 'RBRACKET') {
|
|
$indent--;
|
|
printindent unless $keep_ws;
|
|
} elsif ($first) {
|
|
printindent unless $keep_ws;
|
|
$first = 0;
|
|
}
|
|
do_value $token, $text, $json;
|
|
return if $token eq 'RBRACKET';
|
|
}
|
|
}
|
|
|
|
sub do_value ($$$) {
|
|
my ($token, $text, $json) = @_;
|
|
if (my $format = $format{$token}) {
|
|
print $format, $text, $reset;
|
|
} else {
|
|
print $text;
|
|
}
|
|
if ($token eq 'LBRACE') {
|
|
$indent++;
|
|
do_object $json;
|
|
} elsif ($token eq 'LBRACKET') {
|
|
$indent++;
|
|
do_array $json;
|
|
} elsif ($token eq 'COMMA') {
|
|
printindent unless $keep_ws;
|
|
} elsif ($token eq 'COLON') {
|
|
print ' ' unless $keep_ws;
|
|
}
|
|
}
|
|
|
|
my @buffer;
|
|
while (my ($token, $text) = gettoken \@buffer) {
|
|
do_value $token, $text, \@buffer;
|
|
print "\n" unless $keep_ws;
|
|
}
|