#!/usr/bin/env perl
use strict;
use FindBin;
use lib "$FindBin::Bin/../lib";
my $decoder;
my $decoder_module;
my $decoder_debug = 0;
my $decoder_choice;
# Parse command-line args before anything else
for (my $i = 0; $i < @ARGV; $i++) {
if ($ARGV[$i] eq '--debug') {
$decoder_debug = 1;
splice @ARGV, $i, 1;
redo;
}
if ($ARGV[$i] eq '--use') {
$decoder_choice = $ARGV[$i + 1];
splice @ARGV, $i, 2;
redo;
}
}
if ($decoder_choice) {
if ($decoder_choice eq 'JSON::MaybeXS') {
require JSON::MaybeXS;
$decoder = \&JSON::MaybeXS::decode_json;
$decoder_module = 'JSON::MaybeXS';
}
elsif ($decoder_choice eq 'Cpanel::JSON::XS') {
$decoder = \&Cpanel::JSON::XS::decode_json;
$decoder_module = 'Cpanel::JSON::XS';
}
elsif ($decoder_choice eq 'JSON::XS') {
require JSON::XS;
$decoder = \&JSON::XS::decode_json;
$decoder_module = 'JSON::XS';
}
elsif ($decoder_choice eq 'JSON::PP') {
require JSON::PP;
$decoder = \&JSON::PP::decode_json;
$decoder_module = 'JSON::PP';
}
else {
die "[ERROR] Unknown JSON module: $decoder_choice\n";
}
}
else {
if (eval { require JSON::MaybeXS; 1 }) {
$decoder = \&JSON::MaybeXS::decode_json;
$decoder_module = 'JSON::MaybeXS';
}
elsif (eval { require Cpanel::JSON::XS; 1 }) {
$decoder = \&Cpanel::JSON::XS::decode_json;
$decoder_module = 'Cpanel::JSON::XS';
}
elsif (eval { require JSON::XS; 1 }) {
$decoder = \&JSON::XS::decode_json;
$decoder_module = 'JSON::XS';
}
else {
require JSON::PP;
$decoder = \&JSON::PP::decode_json;
$decoder_module = 'JSON::PP';
}
}
# debug
warn "[DEBUG] Using $decoder_module\n" if $decoder_debug;
# Show help if no arguments are given
if (!@ARGV) {
print <<'USAGE';
jq-lite - A lightweight jq-like JSON query tool written in pure Perl
Usage:
jq-lite [options] '.query' [file.json]
Options:
-r, --raw-output Print raw strings instead of JSON-encoded values
--color Colorize JSON output (keys, strings, numbers, booleans)
--use <Module> Force JSON decoder module (e.g. JSON::PP, JSON::XS, Cpanel::JSON::XS)
--debug Show which JSON module is being used
-h, --help Show this help message
-v, --version Show version information
Examples:
cat users.json | jq-lite '.users[].name'
jq-lite '.users[] | select(.age > 25)' users.json
jq-lite -r '.users[] | .name' users.json
jq-lite '.meta has "version"' config.json
jq-lite --color '.items | sort | reverse | first' data.json
Homepage:
USAGE
exit 0;
}
# Argument parsing
my $raw_output = 0;
my $color_output = 0;
my $query;
my $filename;
while (@ARGV) {
my $arg = shift @ARGV;
if ($arg eq '--raw-output' || $arg eq '-r') {
$raw_output = 1;
}
elsif ($arg eq '--color') {
$color_output = 1;
}
elsif ($arg eq '--help' || $arg eq '-h') {
print <<'USAGE';
jq-lite - A lightweight jq-like JSON query tool written in pure Perl
Usage:
jq-lite [options] '.query' [file.json]
Options:
-r, --raw-output Print raw strings instead of JSON-encoded values
--color Colorize JSON output (keys, strings, numbers, booleans)
--use <Module> Force JSON decoder module (e.g. JSON::PP, JSON::XS, Cpanel::JSON::XS)
--debug Show which JSON module is being used
-h, --help Show this help message
-v, --version Show version information
Examples:
cat users.json | jq-lite '.users[].name'
jq-lite '.users[] | select(.age > 25)' users.json
jq-lite -r '.users[] | .name' users.json
jq-lite '.meta has "version"' config.json
jq-lite --color '.items | sort | reverse | first' data.json
Homepage:
USAGE
exit 0;
}
elsif ($arg eq '--version' || $arg eq '-v') {
print "jq-lite version $JQ::Lite::VERSION\n";
exit 0;
}
# First check if this argument is a file
elsif (!defined $filename && -f $arg) {
$filename = $arg;
}
# If not a file and query is not yet set, treat it as the query string
elsif (!defined $query) {
$query = $arg;
}
# Secondary filename fallback
elsif (!defined $filename && -f $arg) {
$filename = $arg;
}
else {
die "Usage: jq-lite [options] '.query' [file.json]\n";
}
}
# Load JSON from file or STDIN
my $json_text;
if (defined $filename) {
open my $fh, '<', $filename or die "Cannot open file '$filename': $!\n";
local $/;
$json_text = <$fh>;
close $fh;
}
else {
local $/;
$json_text = <STDIN>;
}
# Create JQ::Lite object
my $jq = JQ::Lite->new(raw => $raw_output);
# Colorizing helper
sub colorize_json {
my $json = shift;
$json =~ s/"([^"]+)"(?=\s*:)/color("cyan")."\"$1\"".color("reset")/ge;
$json =~ s/: "([^"]*)"/": ".color("green")."\"$1\"".color("reset")/ge;
$json =~ s/: (\d+(\.\d+)?)/": ".color("yellow")."$1".color("reset")/ge;
$json =~ s/: (true|false|null)/": ".color("magenta")."$1".color("reset")/ge;
return $json;
}
# Output helper
sub print_results {
my @results = @_;
my $pp = JSON::PP->new->utf8->canonical->pretty;
for my $r (@results) {
if (!defined $r) {
print "null\n";
}
elsif ($raw_output && !ref($r)) {
print "$r\n";
}
else {
my $json = $pp->encode($r);
$json = colorize_json($json) if $color_output;
print $json;
}
}
}
# Interactive mode
if (!defined $query) {
system("stty -icanon -echo");
$SIG{INT} = sub {
system("stty sane");
print "\n[EXIT]\n";
exit 0;
};
my $input = '';
my @last_results;
my $ok = eval {
@last_results = $jq->run_query($json_text, '.');
1;
};
if (!$ok || !@last_results) {
my $data = eval { JSON::PP->new->decode($json_text) };
if ($data) {
@last_results = ($data);
}
}
system("clear");
if (@last_results) {
print_results(@last_results);
} else {
print "[INFO] Failed to load initial JSON data.\n";
}
print "\nType query (ESC to quit):\n";
print "> $input\n";
while (1) {
my $char;
sysread(STDIN, $char, 1);
my $ord = ord($char);
last if $ord == 27;
if ($ord == 127 || $char eq "\b") {
chop $input if length($input);
} else {
$input .= $char;
}
system("clear");
my @results;
my $ok = eval {
@results = $jq->run_query($json_text, $input);
1;
};
if ($ok && @results) {
@last_results = @results;
}
if (!$ok) {
print "[INFO] Invalid or partial query. Showing last valid results.\n";
} elsif (!@results) {
print "[INFO] Query returned no results. Showing last valid results.\n";
}
if (@last_results) {
eval {
print_results(@last_results);
1;
} or do {
my $e = $@ || 'Unknown error';
print "[ERROR] Failed to print: $e\n";
};
} else {
print "[INFO] No previous valid results.\n";
}
print "\n> $input\n";
}
system("stty sane");
print "\nGoodbye.\n";
exit 0;
}
# One-shot mode
my @results = eval { $jq->run_query($json_text, $query) };
if ($@) {
die "[ERROR] Invalid query: $@\n";
}
if (!@results) {
warn "[INFO] No results returned for query.\n";
exit 1;
}
print_results(@results);