5 use open ":std", ":encoding(UTF-8)";
6 use POSIX
qw(strftime);
9 use AUR
::Json
qw(parse_json parse_json_aur);
12 # Dictionary for formatter string - subset of package-query(1) format options
13 # Save type of attribute (AUR, pacman or both) for --dump-format
15 'a' => ['array', 'Arch' ],
16 'c' => ['array', 'CheckDepends' ],
17 'C' => ['array', 'Conflicts' ],
18 'D' => ['array', 'Depends' ],
19 'e' => ['array', 'License' ],
20 'F' => ['array', 'Files' ], # aur-repo-parse
21 'g' => ['array', 'Groups' ],
22 'K' => ['array', 'Keywords' ],
23 'M' => ['array', 'MakeDepends' ],
24 'O' => ['array', 'OptDepends' ],
25 'P' => ['array', 'Provides' ],
26 'b' => ['string', 'PackageBase' ],
27 'd' => ['string', 'Description' ],
28 'f' => ['string', 'FileName' ], # aur-repo-parse
29 'm' => ['string', 'Maintainer' ],
30 'n' => ['string', 'Name' ],
31 'r' => ['string', 'DBPath' ], # aur-repo-parse
32 'U' => ['string', 'URL' ],
33 'v' => ['string', 'Version' ],
34 's' => ['string', 'Submitter' ], # aur-pkglist
35 'L' => ['epoch', 'LastModified' ],
36 'o' => ['epoch', 'OutOfDate' ],
37 'S' => ['epoch', 'FirstSubmitted'],
38 'p' => ['numeric', 'Popularity' ],
39 'w' => ['numeric', 'NumVotes' ]
42 # Known AUR types for use with --format, --gron
43 my %aur_types = map { ($_->[1] => $_->[0]) } values %aur_formats;
46 my ($format, $delim) = @_;
48 if (!length($format)) {
49 say STDERR
"$argv0: empty format specified";
52 # omit trailing empty fields
53 my @tokens = split('%', $format);
55 # ignore first field: split("%a%b") -> ("", 'a', 'b')
57 my @suffix = ($tokens[0]);
59 for my $i (1..$#tokens) {
60 my $token = $tokens[$i];
63 # Expand first character, preserve the rest
64 my $token_1 = substr($token, 0, 1);
65 my $label = $aur_formats{$token_1}->[1] // "";
68 if (not length($label) and (length($tokens[$i-1]) > 0 or $i == 1)) {
69 die $argv0 . ': invalid format key specified';
70 } elsif (not length($label)) {
71 $rest = $token; # Special case for %%
73 $rest = substr($token, 1);
75 # Unescape shell-quoted strings, e.g. --format '%n\t%v\n'
76 $rest =~ s/(?<!\\)\\t/\t/g; # do not unescape '\\t'
77 $rest =~ s/(?<!\\)\\n/\n/g;
78 $rest =~ s/(?<!\\)\\0/\0/g;
80 push(@labels, $label);
87 return \
@labels, \
@suffix;
90 sub info_expand_field
{
91 my ($value, $label, $delim, $time_fmt) = @_;
93 if (not defined($value)) {
95 } elsif (ref($value) eq 'ARRAY') {
96 return join($delim, @
{$value});
97 } elsif ($aur_types{$label} eq 'epoch') {
98 return strftime
($time_fmt, gmtime $value);
104 # Expand tokens to AUR data
106 my ($pkg, $labels, $rest, $delim, $verbose, $time_fmt) = @_;
108 if (ref($pkg) ne 'HASH') {
109 say STDERR
"$argv0: --format requires dictionary input";
114 for my $i (0..$#{$labels}) {
115 my ($label, $suffix) = ($labels->[$i], $rest->[$i]);
117 if (length($label)) {
118 my $field = info_expand_field
($pkg->{$label}, $label, $delim, $time_fmt);
120 if (not length($field) and $verbose) {
123 push(@fmt, $field . $suffix);
128 my $fmt_string = join('', @fmt);
133 my ($pkg, $prefix, $key) = @_;
135 if (not defined($pkg)) {
136 say join(' = ', $prefix, 'null;');
138 elsif (not length(ref($pkg))) {
139 # Use known types instead of best-effort basis (`looks_like_number`)
140 my $aur_type = $aur_types{$key // ""};
142 if (not (defined $aur_type and ($aur_type eq 'numeric' or $aur_type eq 'epoch'))) {
143 $pkg =~ s/\\/\\\\/g; # escape backslashes
144 $pkg =~ s/(?<!\\)\"/\\"/g; # escape double quotes
145 $pkg =~ s/\x1B/\\u001B/g; # escape ANSI sequences
146 $pkg = "\"$pkg\""; # enquote
148 say join(' = ', $prefix, $pkg . ';');
150 elsif (ref($pkg) eq 'HASH') {
153 for my $key (sort keys %{$pkg}) {
154 my $value = $pkg->{$key};
156 info_gron
($value, join(".", $prefix, $key), $key);
159 elsif (ref($pkg) eq 'ARRAY') {
163 map { info_gron
($_, $prefix . "[" . $index++ . "]", undef) } @
{$pkg};
167 # https://www.drdobbs.com/scripts-as-modules/184416165
172 my $opt_delim; # delimiter for arrays
173 my $opt_verbose = 0; # inserts "-" for empty fields with --format
178 'f|format=s' => sub { $opt_mode = 'format',
179 $opt_format = $_[1] },
180 'gron' => sub { $opt_mode = 'gron' },
181 'd|delim=s' => \
$opt_delim,
182 'v|verbose' => \
$opt_verbose,
183 'time-format=s' => \
$opt_time_fmt
186 if (not length($opt_time_fmt)) {
187 $opt_time_fmt = "%a %b %e %H:%M:%S %Y";
189 if (not length($opt_delim)) {
192 if (not length($opt_mode)) {
193 say STDERR
"$argv0: no mode specified";
198 if ($opt_mode eq 'gron') {
199 while (my $row = <ARGV
>) {
200 my $obj = parse_json
($row);
202 info_gron
($obj, "json");
206 elsif ($opt_mode eq 'format') {
207 while (my $row = <ARGV
>) {
208 my @results = parse_json_aur
($row);
210 my ($fmt, $suffix) = tokenize
($opt_format);
211 die unless (scalar @
{$fmt} eq scalar @
{$suffix});
213 map { info_format
($_, $fmt, $suffix, $opt_delim, $opt_verbose, $opt_time_fmt) } @results;
218 say STDERR
"$argv0: unknown mode $opt_mode";
223 # vim: set et sw=4 sts=4 ft=perl: