File Coverage

blib/lib/App/jt.pm
Criterion Covered Total %
statement 69 132 52.2
branch 19 50 38.0
condition 1 5 20.0
subroutine 15 16 93.7
pod 0 8 0.0
total 104 211 49.2


line stmt bran cond sub pod time code
1             package App::jt;
2             {
3             $App::jt::VERSION = '0.43';
4             }
5             # ABSTRACT: JSON transformer
6              
7 2     2   83266 use 5.010;
  2         8  
  2         79  
8 2     2   14211 use Moo;
  2         65055  
  2         15  
9 2     2   7549 use MooX::Options;
  2         2832  
  2         14  
10 2     2   1001955 use JSON::PP;
  2         30516  
  2         180  
11 2     2   19 use IO::Handle;
  2         5  
  2         83  
12 2     2   2080 use Hash::Flatten qw(flatten unflatten);
  2         26522  
  2         196  
13 2     2   5267 use List::MoreUtils qw(any);
  2         3034  
  2         4526  
14              
15             has output_handle => (
16             is => "ro",
17             default => sub {
18             my $io = IO::Handle->new;
19             $io->fdopen( fileno(STDOUT), "w");
20             binmode $io, ":utf8";
21             return $io;
22             }
23             );
24              
25             has input_handle => (
26             is => "ro",
27             default => sub {
28             my $io = IO::Handle->new;
29             $io->fdopen( fileno(STDIN), "r");
30             binmode $io, ":utf8";
31             return $io;
32             }
33             );
34              
35             option 'ugly' => (
36             is => "ro",
37             doc => "Produce uglyfied json output"
38             );
39              
40             option 'pick' => (
41             is => "ro",
42             format => "i@",
43             autosplit => "..",
44             doc => "`--pick n`: Pick n objects randomly. `--pick n..m`: Pick object in this range."
45             );
46              
47             option 'csv' => (
48             is => "ro",
49             default => sub { 0 },
50             doc => "Produce csv output for scalar values."
51             );
52              
53             option 'tsv' => (
54             is => "ro",
55             default => sub { 0 },
56             doc => "Produce csv output for scalar values."
57             );
58              
59             option 'silent' => (
60             is => "ro",
61             doc => "Silent output."
62             );
63              
64             option 'fields' => (
65             is => "ro",
66             format => "s@",
67             autosplit => ",",
68             doc => "Filter the input to contain only these fields."
69             );
70              
71             option 'output_flatten' => (
72             is => "ro",
73             default => sub { 0 },
74             doc => "Produce flatten output."
75             );
76              
77             option 'map' => (
78             is => "ro",
79             format => "s",
80             doc => "Run the specified code for each object, with %_ containing the object content."
81             );
82              
83             option 'grep' => (
84             is => "ro",
85             format => "s",
86             doc => "Filter the objects by given code. %_ containing the object content."
87             );
88              
89             option 'json_path' => (
90             is => "ro",
91             doc => "A JSONPath string for filtering document.",
92             format => "s",
93             );
94              
95             has data => (
96             is => "rw",
97             doc => "The data that keeps transforming."
98             );
99              
100             sub run {
101 5     5 0 50 my ($self) = @_;
102              
103 5         49 my $json_decoder = JSON::PP->new;
104 5         221 $json_decoder->allow_singlequote(1)->allow_barekey(1);
105 5         238 my $IN = $self->input_handle;
106 5         10 my $text = do { local $/; <$IN> };
  5         18  
  5         33  
107 5         123 $self->data( $json_decoder->decode($text) );
108 5         1978 $self->transform;
109              
110 5 100       46 if ($self->csv) {
    100          
    100          
111 1         5 $self->output_csv;
112             }
113             elsif ($self->tsv) {
114 1         5 $self->output_tsv;
115             }
116             elsif (!$self->silent) {
117 2         7 $self->output_json;
118             }
119             }
120              
121             sub data_as_arrayref {
122 2     2 0 4 my ($self) = @_;
123 2         11 my $data = $self->data;
124 2 50       13 return $data if ref($data) eq "ARRAY";
125 0         0 return [ $data ];
126             }
127              
128             sub out {
129 8     8 0 291 my ($self, $x) = @_;
130 8   50     24 $x ||= "";
131 8 100       31 $x .= "\n" unless substr($x, -1, 1) eq "\n";
132 8         46 $self->output_handle->print($x);
133             }
134              
135             sub output_json {
136 2     2 0 4 my ($self) = @_;
137 2         6 my $json_encoder = JSON::PP->new;
138 2 100       32 $json_encoder->pretty unless $self->ugly;
139 2         109 $self->out( $json_encoder->encode($self->data) );
140             }
141              
142             sub output_asv {
143 2     2 0 1252 require Text::CSV;
144              
145 2         13184 my ($self, $args) = @_;
146 2         10 my $data = $self->data_as_arrayref;
147 2 50       9 my $o = $data->[0] or return;
148 2 50       14 my @keys = ($self->fields) ? (@{$self->{fields}}) : ( grep { !ref($o->{$_}) } keys %$o );
  0         0  
  4         14  
149              
150 2         22 my $csv = Text::CSV->new({ binary => 1, %$args });
151 2         190 $csv->combine(@keys);
152              
153 2         229 $self->out($csv->string);
154 2         53 for $o (@$data) {
155 4         53 my $o_ = flatten($o);
156 4         968 $csv->combine(@{$o_}{@keys});
  4         19  
157 4         264 $self->out( $csv->string );
158             }
159             }
160              
161             sub output_csv {
162 1     1 0 4 my ($self) = @_;
163 1         26 $self->output_asv({ sep_char => "," });
164             }
165              
166             sub output_tsv {
167 1     1 0 3 my ($self) = @_;
168 1         7 $self->output_asv({ sep_char => "\t" });
169             }
170              
171             sub transform {
172 5     5 0 13 my ($self) = @_;
173              
174 5 50       27 if ($self->pick) {
175 0         0 my ($m, $n) = @{$self->pick};
  0         0  
176 0 0 0     0 if (defined($m) && defined($n)) {
    0          
177 0         0 @{$self->data} = @{ $self->data }[ $m..$n ];
  0         0  
  0         0  
178             }
179             elsif (defined($m)) {
180 0         0 my $len = scalar @{ $self->data };
  0         0  
181 0         0 my @wanted = map { rand($len) } 1..$m;
  0         0  
182 0         0 @{$self->data} = @{ $self->data }[ @wanted ];
  0         0  
  0         0  
183             }
184             }
185              
186 5 50       23 if ($self->map) {
187 0         0 my $code = $self->map;
188 0         0 for my $o (@{ $self->data }) {
  0         0  
189 0         0 local $_ = $o;
190 0 0       0 if (not ref $o) {
    0          
    0          
191 0         0 eval "$code";
192 0         0 $o = $_;
193             }
194             elsif (ref($o) eq 'ARRAY') {
195 0         0 local @_ = @$o;
196 0         0 eval "$code";
197 0         0 @$o = @_;
198             }
199             elsif (ref($o) eq 'HASH') {
200 0         0 local %_ = %$o;
201 0         0 eval "$code";
202 0         0 %$o = %_;
203             }
204             }
205             }
206 5 50       49 if ($self->grep) {
    50          
    50          
207 0         0 my $code = $self->grep;
208 0         0 my @objs;
209 0         0 for my $o (@{ $self->data }) {
  0         0  
210 0         0 local %_ = %$o;
211 0         0 my $wanted = eval "$code";
212 0 0       0 if ($wanted) {
213 0         0 push @objs, $o;
214             }
215 0         0 $self->data(\@objs);
216             }
217             }
218              
219             elsif ($self->json_path) {
220 0         0 require JSON::Path;
221              
222 0         0 my $jpath = JSON::Path->new($self->json_path);
223 0         0 my @values = $jpath->values($self->data);
224              
225 0         0 $self->data( \@values );
226             }
227             elsif ($self->fields) {
228 0         0 my @fields = @{ $self->fields };
  0         0  
229 0         0 my $data = $self->data;
230             my $pick_fields_of_hash = sub {
231 0     0   0 my $data = shift;
232 0         0 my $data_ = flatten($data);
233              
234 0         0 for my $k (keys %$data_) {
235 0 0       0 delete $data_->{$k} unless any { $k =~ m!(\A|[:\.]) \Q$_\E ([:\.]|\z)!x } @fields;
  0         0  
236             }
237 0         0 return unflatten($data_);
238 0         0 };
239              
240 0 0       0 if (ref($data) eq "ARRAY") {
    0          
241 0         0 for my $o (@$data) {
242 0         0 %$o = %{ $pick_fields_of_hash->($o) };
  0         0  
243             }
244             }
245             elsif (ref($data) eq "HASH") {
246 0         0 %$data = %{ $pick_fields_of_hash->($data) };
  0         0  
247             }
248             }
249              
250 5 50       24 if ($self->output_flatten) {
251 0         0 my $data = $self->data;
252 0 0       0 if (ref($data) eq "HASH") {
    0          
253 0         0 $self->data( flatten( $data ) );
254             }
255             elsif (ref($data) eq "ARRAY") {
256 0         0 for my $o (@$data) {
257 0         0 %$o = %{ flatten($o) };
  0         0  
258             }
259             }
260             }
261              
262 5         10 return $self;
263             }
264              
265             1;
266              
267             __END__