File Coverage

blib/lib/App/NDTools/NDQuery.pm
Criterion Covered Total %
statement 116 119 97.4
branch 42 50 84.0
condition 1 3 33.3
subroutine 20 20 100.0
pod 0 9 0.0
total 179 201 89.0


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