File Coverage

bin/yfrom
Criterion Covered Total %
statement 58 90 64.4
branch 19 32 59.3
condition 1 2 50.0
subroutine 11 14 78.5
pod n/a
total 89 138 64.4


line stmt bran cond sub pod time code
1             #!/usr/bin/env perl
2             package yfrom;
3             # ABSTRACT: Build YAML from another format (like JSON or CSV)
4             $yfrom::VERSION = '0.015';
5 1     1   405 use App::YAML::Filter::Base;
  1         1  
  1         15  
6 1     1   539 use Pod::Usage::Return qw( pod2usage );
  1         36083  
  1         73  
7 1     1   881 use Getopt::Long qw( GetOptionsFromArray );
  1         8646  
  1         10  
8 1     1   241 use YAML;
  1         2  
  1         88  
9 1     1   7 use Module::Runtime qw( use_module );
  1         1  
  1         10  
10 1     1   561 use Text::Trim qw( ltrim );
  1         430  
  1         858  
11              
12             $|++; # no buffering
13              
14             sub main {
15 4     4   23290 my ( $class, @argv ) = @_;
16 4         7 my %opt;
17 4         25 GetOptionsFromArray( \@argv, \%opt,
18             'help|h',
19             'version',
20             'trim!',
21             );
22 4 50       937 return pod2usage(0) if $opt{help};
23 4 50       14 if ( $opt{version} ) {
24 0         0 print "yfrom version $yfrom::VERSION (Perl $^V)\n";
25 0         0 return 0;
26             }
27              
28             # Have to set the default like this, because it doesn't seem to work in the %opt
29             # hash
30 4   50     24 $opt{trim} //= 1;
31              
32 4         4 my $format = shift @argv;
33 4 100       19 return pod2usage("ERROR: Must give a format") unless $format;
34 3 100       13 return pod2usage("ERROR: Unknown format '$format'")
35             unless $class->format_modules( $format );
36              
37 2 100       6 push @argv, "-" unless @argv;
38 2         5 for $ARGV ( @argv ) {
39             # We're doing a similar behavior to <>, but manually for easier testing.
40 2         3 my $fh;
41 2 100       6 if ( $ARGV eq '-' ) {
42             # Use the existing STDIN so tests can fake it
43 1         2 $fh = \*STDIN;
44             }
45             else {
46 1 50       30 unless ( open $fh, '<', $ARGV ) {
47 0         0 warn "Could not open file '$ARGV' for reading: $!\n";
48 0         0 next;
49             }
50             }
51              
52 2 50       12 my $f = $class->can_format( $format, %opt )
53             or die "Could not load format module for '$format' (tried: "
54             . ( join ", ", $class->format_modules( $format ) )
55             . ")\n";
56              
57 2         35 while ( my $line = <$fh> ) {
58 20         10747 my @docs = $f->( $line );
59 20 100       61 print YAML::Dump( @docs ) if @docs;
60             }
61             }
62              
63 2         1537 return 0;
64             }
65              
66             my %formatter;
67              
68             # Hash of "format" => [ MODULE... ]
69             # The modules are in order we should try to load them, so put the better ones first
70             my %FORMAT_MODULES = (
71             json => [qw( JSON::XS JSON::PP )],
72             csv => [qw( Text::CSV_XS Text::CSV )],
73             );
74              
75             # Hash of MODULE => formatter sub
76             sub format_sub {
77 4     4   8 my ( $class, $module, %opt ) = @_;
78              
79             # Do this to reset the state() variables every time
80             my %FORMAT_SUB = (
81             'JSON::XS' => sub {
82 20     20   33 state $json = JSON::XS->new->relaxed;
83 20         77 return $json->incr_parse( @_ );
84             },
85              
86             'JSON::PP' => sub {
87 0     0   0 state $json = JSON::PP->new->relaxed;
88 0         0 return $json->incr_parse( @_ );
89             },
90              
91             'Text::CSV_XS' => sub {
92 0     0   0 state $csv = Text::CSV_XS->new;
93 0         0 state @names;
94 0 0       0 if ( !@names ) {
95 0         0 $csv->parse( shift );
96 0         0 @names = $csv->fields;
97 0         0 return;
98             }
99 0         0 $csv->parse( shift );
100 0         0 my @values = $csv->fields;
101 0         0 my $doc = { map {; $names[ $_ ] => $values[ $_ ] } 0..$#values };
  0         0  
102 0 0       0 if ( $opt{trim} ) {
103 0         0 ltrim for values %$doc;
104             }
105 0         0 return $doc;
106             },
107              
108             'Text::CSV' => sub {
109 0     0   0 state $csv = Text::CSV->new;
110 0         0 state @names;
111 0 0       0 if ( !@names ) {
112 0         0 $csv->parse( shift );
113 0         0 @names = $csv->fields;
114 0         0 return;
115             }
116 0         0 $csv->parse( shift );
117 0         0 my @values = $csv->fields;
118 0         0 my $doc = { map {; $names[ $_ ] => $values[ $_ ] } 0..$#values };
  0         0  
119 0 0       0 if ( $opt{trim} ) {
120 0         0 ltrim for values %$doc;
121             }
122 0         0 return $doc;
123             },
124              
125 4         60 );
126              
127 4         157 return $FORMAT_SUB{ $module };
128             }
129              
130             sub format_modules {
131 8     8   17 my ( $class, $format ) = @_;
132 8 100       29 return unless $FORMAT_MODULES{ $format };
133 7         6 return @{ $FORMAT_MODULES{ $format } };
  7         25  
134             }
135              
136             sub can_format {
137 4     4   15321 my ( $class, $format, %opt ) = @_;
138 4         7 my $m = $formatter{ $format };
139 4 50       14 if ( !$m ) {
140 4         10 my @modules = $class->format_modules( $format );
141 4         10 for my $module ( @modules ) {
142 5 100       210 if ( eval { use_module( $module ); 1 } ) {
  5         17  
  3         91  
143 3         3 $m = $module;
144 3         8 last;
145             }
146             }
147             }
148 4         162 return $class->format_sub( $m, %opt );
149             }
150              
151             exit __PACKAGE__->main( @ARGV ) unless caller(0);
152              
153             __END__