File Coverage

blib/lib/App/NDTools/NDQuery.pm
Criterion Covered Total %
statement 119 119 100.0
branch 43 50 86.0
condition 3 3 100.0
subroutine 20 20 100.0
pod 0 9 0.0
total 185 201 92.0


line stmt bran cond sub pod time code
1             package App::NDTools::NDQuery;
2              
3 1     1   747 use strict;
  1         3  
  1         44  
4 1     1   7 use warnings FATAL => 'all';
  1         2  
  1         42  
5 1     1   6 use parent 'App::NDTools::NDTool';
  1         2  
  1         28  
6              
7 1     1   60 use Digest::MD5 qw(md5_hex);
  1         3  
  1         50  
8 1     1   6 use JSON qw();
  1         2  
  1         17  
9 1     1   5 use Log::Log4Cli;
  1         3  
  1         101  
10 1     1   7 use App::NDTools::Slurp qw(s_dump);
  1         3  
  1         64  
11 1     1   23 use Struct::Path 0.80 qw(list_paths path path_delta);
  1         15  
  1         67  
12 1     1   469 use Struct::Path::PerlStyle 0.80 qw(str2path path2str);
  1         52357  
  1         85  
13 1     1   10 use Term::ANSIColor qw(color);
  1         2  
  1         1481  
14              
15             our $VERSION = '0.36';
16              
17             sub arg_opts {
18 47     47 0 99 my $self = shift;
19              
20             return (
21             $self->SUPER::arg_opts(),
22             'colors!' => \$self->{OPTS}->{colors},
23             'delete|ignore=s@' => \$self->{OPTS}->{delete},
24             'depth|d=i' => \$self->{OPTS}->{depth},
25             'grep=s@' => \$self->{OPTS}->{grep},
26             'keys' => \$self->{OPTS}->{keys},
27             'list|l' => \$self->{OPTS}->{list},
28             'md5' => \$self->{OPTS}->{md5},
29             'path|p=s' => \$self->{OPTS}->{path},
30 4     4   5657 'raw-output' => sub { $self->{OPTS}->{ofmt} = 'RAW' },
31             'replace' => \$self->{OPTS}->{replace},
32             'strict!' => \$self->{OPTS}->{strict},
33             'values|vals' => \$self->{OPTS}->{values},
34 47         185 );
35             }
36              
37             sub check_args {
38 45     45 0 72 my $self = shift;
39              
40 45 100       112 if ($self->{OPTS}->{replace}) {
41             die_fatal "--replace opt can't be used with --keys", 1
42 4 50       16 if ($self->{OPTS}->{keys});
43             die_fatal "--replace opt can't be used with --list", 1
44 4 100       17 if ($self->{OPTS}->{list});
45             die_fatal "--replace opt can't be used with --md5", 1
46 3 100       14 if ($self->{OPTS}->{md5});
47             }
48              
49 43         76 return $self;
50             }
51              
52             sub configure {
53 45     45 0 82 my $self = shift;
54              
55 45         169 $self->SUPER::configure();
56              
57             $self->{OPTS}->{colors} = $self->{TTY}
58 43 100       146 unless (defined $self->{OPTS}->{colors});
59              
60 43         68 for (
61 43         94 @{$self->{OPTS}->{grep}},
62 43         107 @{$self->{OPTS}->{delete}}
63             ) {
64 12         20 my $tmp = eval { str2path($_) };
  12         78  
65 12 50       9615 die_fatal "Failed to parse '$_'", 4 if ($@);
66 12         32 $_ = $tmp;
67             }
68              
69 43         89 return $self;
70             }
71              
72             sub defaults {
73 47     47 0 94 my $self = shift;
74              
75             return {
76 47         70 %{$self->SUPER::defaults()},
  47         156  
77             'color-common' => 'bold black',
78             'strict' => 1, # exit with 8 if unexisted path specified
79             'ofmt' => 'JSON',
80             };
81             }
82              
83             sub dump {
84 29     29 0 67 my ($self, $uri, $data) = @_;
85              
86 29 100       94 $uri = \*STDOUT unless ($self->{OPTS}->{replace});
87 29         84 s_dump($uri, $self->{OPTS}->{ofmt}, {pretty => $self->{OPTS}->{pretty}}, @{$data});
  29         109  
88             }
89              
90             sub exec {
91 43     43 0 73 my $self = shift;
92              
93 43 50       70 for my $uri (@{$self->{ARGV}} ? @{$self->{ARGV}} : \*STDIN) {
  43         119  
  43         102  
94 47         190 my @data = $self->load_struct($uri, $self->{OPTS}->{ifmt});
95              
96 47 100       151 if (defined $self->{OPTS}->{path}) {
97 15         40 my $spath = eval { str2path($self->{OPTS}->{path}) };
  15         97  
98 15 50       13710 die_fatal "Failed to parse '$self->{OPTS}->{path}'", 4 if ($@);
99              
100 15 100       80 unless (@data = path($data[0], $spath, deref => 1)) {
101             die_fatal "Failed to lookup path '$self->{OPTS}->{path}'", 8
102 2 100       101 if ($self->{OPTS}->{strict});
103 1         12 next;
104             }
105             }
106              
107             @data = $self->grep($self->{OPTS}->{grep}, @data)
108 45 100       10233 if (@{$self->{OPTS}->{grep}});
  45         167  
109              
110 45         107 for my $spath (@{$self->{OPTS}->{delete}}) {
  45         125  
111 4 50       148 map { path($_, $spath, delete => 1) if (ref $_) } @data;
  4         47  
112             }
113              
114 45 100       563 if ($self->{OPTS}->{keys}) {
    100          
    100          
115 4         19 $self->list_keys(\@data);
116             } elsif ($self->{OPTS}->{list}) {
117 7         27 $self->list($uri, \@data);
118             } elsif ($self->{OPTS}->{md5}) {
119 5         18 $self->md5($uri, \@data);
120             } else {
121 29         104 $self->dump($uri, \@data);
122             }
123             }
124              
125 42         249 die_info "All done", 0;
126             }
127              
128             my $JSON = JSON->new->canonical->allow_nonref;
129              
130             sub list_keys {
131 4     4 0 9 my ($self, $data) = @_;
132 4         9 my @out;
133              
134 4         7 for (@{$data}) {
  4         10  
135 10 100       30 if (ref $_ eq 'HASH') {
    100          
136 1         2 push @out, sort keys %{$_};
  1         7  
137             } elsif (ref $_ eq 'ARRAY') {
138 3         7 push @out, "0 .. " . $#{$_};
  3         11  
139             } else {
140 6         40 push @out, $JSON->encode($_);
141             }
142             }
143              
144 4         210 print join("\n", @out) . "\n";
145             }
146              
147             sub list {
148 7     7 0 16 my ($self, $uri, $data) = @_;
149              
150             my $base_pfx = $self->{OPTS}->{colors}
151 7 100       32 ? color($self->{OPTS}->{'color-common'}) : "";
152 7 100       77 my $base_sfx = $self->{OPTS}->{colors} ? color('reset') : "";
153              
154 7         29 for (@{$data}) {
  7         14  
155 7         24 my @list = list_paths($_, depth => $self->{OPTS}->{depth});
156 7         1405 my (@delta, $line, $path, $prev, $value, @out);
157              
158 7         18 while (@list) {
159 58         117 ($path, $value) = splice @list, 0, 2;
160              
161 58         143 @delta = path_delta($prev, $path);
162             $line = $base_pfx .
163 58         3457 path2str([@{$path}[0 .. @{$path} - @delta - 1]]) . $base_sfx .
  58         160  
  58         109  
164             path2str(\@delta);
165              
166 58 100       5441 if ($self->{OPTS}->{values}) {
167 37         69 $line .= " = ";
168 37 100 100     121 if ($self->{OPTS}->{ofmt} eq 'RAW' and not ref ${$value}) {
  3         13  
169 2         5 $line .= ${$value};
  2         11  
170             } else {
171 35         49 $line .= $JSON->encode(${$value});
  35         152  
172             }
173             }
174              
175 58         120 push @out, $line;
176 58         171 $prev = $path;
177             }
178              
179 7         316 print join("\n", @out) . "\n";
180             }
181             }
182              
183             sub md5 {
184 5     5 0 13 my ($self, $uri, $data) = @_;
185              
186 5 50       7 print md5_hex($JSON->encode(@{$data} == 1 ? $data->[0] : $data)) .
  5 50       240  
187             (ref $uri ? " -\n" : " $uri\n");
188             }
189              
190             1; # End of App::NDTools::NDQuery