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