File Coverage

blib/lib/Locale/Simple/Scraper.pm
Criterion Covered Total %
statement 119 179 66.4
branch 39 86 45.3
condition 1 24 4.1
subroutine 13 16 81.2
pod 0 5 0.0
total 172 310 55.4


line stmt bran cond sub pod time code
1 1     1   63017 use strict;
  1         3  
  1         28  
2 1     1   4 use warnings;
  1         2  
  1         48  
3              
4             package Locale::Simple::Scraper;
5             BEGIN {
6 1     1   22 $Locale::Simple::Scraper::AUTHORITY = 'cpan:GETTY';
7             }
8             $Locale::Simple::Scraper::VERSION = '0.019';
9             # ABSTRACT: scraper to find translation tokens in a directory
10              
11 1     1   5 use Exporter 'import';
  1         1  
  1         25  
12 1     1   1108 use Getopt::Long;
  1         10852  
  1         8  
13 1     1   167 use File::Find;
  1         2  
  1         76  
14 1     1   5 use Cwd;
  1         3  
  1         77  
15 1     1   739 use Locale::Simple;
  1         3  
  1         155  
16 1     1   2606 use Data::Dumper;
  1         7187  
  1         128  
17 1     1   782 use Locale::Simple::Scraper::Parser;
  1         5  
  1         2530  
18              
19             our @EXPORT = qw(scrape);
20              
21             sub scrape {
22 3     3 0 780937 @ARGV = @_;
23              
24 3         10 $| = 1;
25              
26             # Supported filetypes:
27 3         8 my $js_ext = ""; # Javascript
28 3         8 my $pl_ext = ""; # Perl
29 3         7 my $py_ext = ""; # Python
30 3         6 my $tx_ext = ""; # Text::Xslate (Kolon or Metakolon)
31              
32 3         21 my @ignores;
33             my @only;
34              
35 3         12 my $output = 'po';
36 3         4 my ($md5, $no_line_numbers);
37              
38 3         25 GetOptions(
39             "js=s" => \$js_ext,
40             "pl=s" => \$pl_ext,
41             "py=s" => \$py_ext,
42             "tx=s" => \$tx_ext,
43             "ignore=s" => \@ignores,
44             "only=s" => \@only,
45             "output=s" => \$output,
46             "md5" => \$md5,
47             "no_line_numbers" => \$no_line_numbers,
48             );
49              
50             # could add Getopt::Long here for override
51              
52 3         3566 my @js = split( ",", $js_ext );
53 3         7 push @js, 'js';
54              
55 3         9 my @pl = split( ",", $pl_ext );
56 3         9 push @pl, 'pl', 'pm', 't';
57              
58 3         7 my @tx = split( ",", $tx_ext );
59 3         8 push @tx, 'tx';
60              
61 3         7 my @py = split( ",", $py_ext );
62 3         6 push @py, 'py';
63              
64             # extension list
65 3         12 my %e = (
66 11         21 ( map { $_ => 'js' } @js ),
67 3         8 ( map { $_ => 'pl' } @pl ),
68 3         21 ( map { $_ => 'tx' } @tx ),
69 3         7 ( map { $_ => 'py' } @py ),
70             );
71              
72             # functions with count of locale simple with function of parameter
73             #
74             # 1 = msgid
75             # 2 = msgid_plural
76             # 3 = msgctxt
77             # 4 = domain
78             #
79 3         40 my %f = (
80             l => [1],
81             ln => [ 1, 2 ],
82             ld => [ 4, 1 ],
83             lp => [ 3, 1 ],
84             lnp => [ 3, 1, 2 ],
85             ldn => [ 4, 1, 2 ],
86             ldp => [ 4, 3, 1 ],
87             ldnp => [ 4, 3, 1, 2 ],
88             );
89              
90 3         6 my %files;
91              
92 3         25 my $dir = getcwd;
93 3         6 my $re_dir = $dir;
94 3         16 $re_dir =~ s/\./\\./g;
95              
96             finddepth(
97             sub {
98 402     402   522 my $filename = $File::Find::name;
99 402         413 my $stored_filename = $filename;
100 402 50       760 if ( $md5 ) {
101 0         0 eval {
102 0         0 require Digest::MD5;
103 0         0 Digest::MD5->import( 'md5_hex' );
104             };
105 0 0       0 die "This feature requires Digest::MD5" if $@;
106 0         0 $stored_filename = md5_hex( $filename );
107             }
108 402         2005 $filename =~ s/^$dir\///g;
109 402         698 for ( @ignores ) {
110 1794 100       20139 return if $filename =~ /$_/;
111             }
112 273 100       642 if ( @only ) {
113 182         205 my $found = 0;
114 182         245 for ( @only ) {
115 182 100       609 $found = 1 if $filename =~ /$_/;
116             }
117 182 100       5587 return unless $found;
118             }
119 93         321 my @fileparts = split( '\.', $File::Find::name );
120 93         137 my $ext = pop @fileparts;
121 93 100       217 $files{$File::Find::name} = [ $ext, $filename, $stored_filename ] if grep { $ext eq $_ } keys %e;
  560         11615  
122             },
123 3         686 $dir
124             );
125              
126 3         34 my @found;
127 3         31 for my $file ( sort keys %files ) {
128 21         36 my ( $ext, $filename, $stored_filename ) = @{ $files{$file} };
  21         90  
129 21         51 my $type = $e{$ext};
130 21         819 print STDERR $type . " => " . $file . "\n";
131 21 50 33     820 return if -l $file and not -e readlink( $file );
132 21         938 my $parses = Locale::Simple::Scraper::Parser->new( type => $type )->from_file( $file );
133 58         181 my @file_things = map {
134 21         50 {
135 58         76 %{ result_from_params( $_->{args}, $f{ $_->{func} } ) },
136             line => $_->{line},
137             file => $stored_filename,
138             type => $type,
139             }
140 21         654 } @{$parses};
141 21         134 push @found, @file_things;
142             }
143              
144 3 50       13 if ( $output eq 'po' ) {
    0          
    0          
    0          
145 3         4 my %files;
146             my %token;
147 3         7 for ( @found ) {
148 58 100       103 my $key .= defined $_->{domain} ? '"' . $_->{domain} . '"' : 'undef';
149 58 100       96 $key .= defined $_->{msgctxt} ? '"' . $_->{msgctxt} . '"' : 'undef';
150 58 50       214 $key .= defined $_->{msgid} ? '"' . $_->{msgid} . '"' : 'undef';
151 58 100       121 $key .= defined $_->{msgid_plural} ? '"' . $_->{msgid_plural} . '"' : 'undef';
152 58 100       147 $token{$key} = $_ unless defined $token{$key};
153 58 100       137 $files{$key} = [] unless defined $files{$key};
154 58         59 push @{ $files{$key} }, $_->{file} . ':' . $_->{line};
  58         176  
155             }
156 3         19 for my $k ( sort { $a cmp $b } keys %files ) {
  94         83  
157 29         304 print "\n";
158 29 50       61 print "#: " . join( ' ', @{ $files{$k} } ) . "\n" if !$no_line_numbers;
  29         276  
159 29         221 print "#, locale-simple-format";
160 29 100       72 print " " . $token{$k}{domain} if defined $token{$k}{domain};
161 29         217 print "\n";
162 29         45 for ( qw( msgctxt msgid msgid_plural ) ) {
163 87 100       273 print $_. ' "' . Locale::Simple::gettext_escape( $token{$k}{$_} ) . '"' . "\n"
164             if defined $token{$k}{$_};
165             }
166 29 100       64 my $plural_marker = $token{$k}{msgid_plural} ? "[0]" : "";
167 29         358 print qq[msgstr$plural_marker ""\n];
168              
169             }
170             }
171             elsif ( $output eq 'perl' ) {
172 0         0 print Dumper \@found;
173             }
174             elsif ( $output eq 'json' ) {
175             eval {
176 0         0 require JSON;
177 0         0 JSON->import;
178 0         0 print encode_json( \@found );
179 0 0       0 } or do {
180 0         0 die "You require the module JSON for this output";
181             };
182             }
183             elsif ( $output eq 'yaml' ) {
184             eval {
185 0         0 require YAML;
186 0         0 YAML->import;
187 0         0 print Dump( \@found );
188 0 0       0 } or do {
189 0         0 die "You require the module YAML for this output";
190             };
191             }
192             }
193              
194             sub parse_line {
195 0     0 0 0 my ( $line, $type, $f, @results ) = @_;
196 0 0       0 return if $line =~ /^\s*\#.*/;
197 0         0 for ( keys %{$f} ) {
  0         0  
198 0         0 my @args = @{ $f->{$_} };
  0         0  
199 0         0 my $params = get_func_params( $_, $line );
200 0 0       0 next if !$params;
201 0         0 my $argc = scalar @args;
202 0         0 my ( $remainder, @params ) = parse_params( $params, $type, $argc );
203 0 0       0 if ( scalar @params == $argc ) {
204 0         0 push @results, result_from_params( \@params, \@args ), parse_line( $remainder, $type, $f );
205             }
206             }
207 0         0 return @results;
208             }
209              
210             sub result_from_params {
211 58     58 0 75 my ( $params, $args ) = @_;
212 58         63 my %result;
213 58         61 my $pos = 0;
214 58         70 for ( @{$args} ) {
  58         116  
215 81 100       233 $result{msgid} = $params->[$pos] if $_ eq 1;
216 81 100       170 $result{msgid_plural} = $params->[$pos] if $_ eq 2;
217 81 100       149 $result{msgctxt} = $params->[$pos] if $_ eq 3;
218 81 100       148 $result{domain} = $params->[$pos] if $_ eq 4;
219 81         138 $pos++;
220             }
221 58         453 return \%result;
222             }
223              
224             sub get_func_params {
225 0     0 0   my ( $func, $line ) = @_;
226 0           $line =~ /([^\w]|^)$func\((.*)/;
227 0           return $2;
228             }
229              
230             sub parse_params {
231 0     0 0   my ( $params, $type, $argc ) = @_;
232 0           my @chars = split( '', $params );
233 0           my @args;
234 0           my $arg = "";
235 0           my $q_state = 0; # 0 = code, 1 = qoute, 2 = double qoute
236 0           my $comma_state = 1;
237 0           while ( defined( my $c = shift @chars ) ) {
238 0 0 0       next if $c =~ /\s/ and !$q_state;
239 0 0         if ( $q_state ) {
240 0 0 0       if ( $c eq '\\' ) {
    0 0        
      0        
241 0           my $esc = shift @chars;
242 0 0 0       if ( $esc eq "'" or $esc eq '"' or $esc eq '\\' ) {
      0        
243 0           $arg .= $esc;
244             }
245             else {
246 0           warn "Unknown escape char '" . $esc . "'";
247             }
248             }
249             elsif ( ( $c eq "'" and $q_state == 1 ) or ( $c eq '"' and $q_state == 2 ) ) {
250 0           $q_state = 0;
251 0           $comma_state = 0;
252 0           push @args, $arg;
253 0           $arg = "";
254 0 0         last if scalar @args == $argc;
255             }
256             else {
257 0           $arg .= $c;
258             }
259             }
260             else {
261 0 0 0       if ( $c eq "'" or $c eq '"' ) {
    0          
    0          
262 0 0         die "quote found where comma expected: " . $params unless $comma_state;
263 0 0         $q_state = $c eq "'" ? 1 : 2;
264             }
265             elsif ( $c eq ',' ) {
266 0 0         die "comma found after comma in code: " . $params if $comma_state;
267 0           $comma_state = 1;
268             }
269             elsif ( $type eq 'js' ) {
270 0           last;
271             }
272             else {
273 0           last;
274             }
275             }
276             }
277 0           return join( '', @chars ), @args;
278             }
279              
280             1;
281              
282             __END__