File Coverage

blib/lib/Jenkins/i18n.pm
Criterion Covered Total %
statement 87 102 85.2
branch 34 52 65.3
condition 6 12 50.0
subroutine 14 16 87.5
pod 5 5 100.0
total 146 187 78.0


line stmt bran cond sub pod time code
1             package Jenkins::i18n;
2              
3 5     5   388907 use 5.014004;
  5         50  
4 5     5   27 use strict;
  5         9  
  5         115  
5 5     5   24 use warnings;
  5         12  
  5         141  
6 5     5   27 use Carp qw(confess);
  5         7  
  5         291  
7 5     5   42 use File::Find;
  5         23  
  5         315  
8 5     5   29 use File::Spec;
  5         9  
  5         143  
9 5     5   1752 use Set::Tiny;
  5         5320  
  5         265  
10              
11 5     5   2066 use Jenkins::i18n::Properties;
  5         15  
  5         199  
12              
13             =pod
14              
15             =head1 NAME
16              
17             Jenkins::i18n - functions for the jtt CLI
18              
19             =head1 SYNOPSIS
20              
21             use Jenkins::i18n qw(remove_unused find_files load_properties load_jelly find_langs);
22              
23             =head1 DESCRIPTION
24              
25             C is a CLI program used to help translating the Jenkins properties file.
26              
27             This module implements some of the functions used by the CLI.
28              
29             =cut
30              
31 5     5   35 use Exporter 'import';
  5         12  
  5         6284  
32             our @EXPORT_OK = (
33             'remove_unused', 'find_files', 'load_properties', 'load_jelly',
34             'find_langs'
35             );
36              
37             our $VERSION = '0.08';
38              
39             =head2 EXPORT
40              
41             None by default.
42              
43             =head2 FUNCTIONS
44              
45             =head3 remove_unused
46              
47             Remove unused keys from a properties file.
48              
49             Each translation in every language depends on the original properties files
50             that are written in English.
51              
52             This function gets a set of keys and compare with those that are stored in the
53             translation file: anything that exists outside the original set in English is
54             considered deprecated and so removed.
55              
56             Expects as positional parameters:
57              
58             =over
59              
60             =item 1
61              
62             file: the complete path to the translation file to be checked.
63              
64             =item 2
65              
66             keys: a L instance of the keys from the original English properties
67             file.
68              
69             =item 3
70              
71             license: a scalar reference with a license to include the header of the
72             translated properties file.
73              
74             =item 4
75              
76             backup: a boolean (0 or 1) if a backup file should be created in the same path
77             of the file parameter. Optional.
78              
79             =back
80              
81             Returns the number of keys removed (as an integer).
82              
83             =cut
84              
85             sub remove_unused {
86 6     6 1 8002 my $file = shift;
87 6 100       36 confess "file is a required parameter\n" unless ( defined($file) );
88 5         8 my $keys = shift;
89 5 100       21 confess "keys is a required parameter\n" unless ( defined($keys) );
90 4 100       21 confess "keys must be a Set::Tiny instance\n"
91             unless ( ref($keys) eq 'Set::Tiny' );
92 3         4 my $license_ref = shift;
93 3 100       17 confess "license must be an array reference"
94             unless ( ref($license_ref) eq 'ARRAY' );
95 2         4 my $use_backup = shift;
96 2 100       6 $use_backup = 0 unless ( defined($use_backup) );
97              
98 2         4 my $props_handler;
99              
100 2 100       5 if ($use_backup) {
101 1         4 my $backup = "$file.bak";
102 1 50       61 rename( $file, $backup )
103             or confess "Cannot rename $file to $backup: $!\n";
104 1         9 $props_handler = Jenkins::i18n::Properties->new( file => $backup );
105             }
106             else {
107 1         8 $props_handler = Jenkins::i18n::Properties->new( file => $file );
108             }
109              
110 2         57 my $curr_keys = Set::Tiny->new( $props_handler->propertyNames );
111 2         243 my $to_delete = $curr_keys->difference($keys);
112              
113 2         74 foreach my $key ( $to_delete->members ) {
114 24         363 $props_handler->deleteProperty($key);
115             }
116              
117 2 50       201 open( my $out, '>', $file ) or confess "Cannot write to $file: $!\n";
118 2         15 $props_handler->save( $out, $license_ref );
119 2 50       134 close($out) or confess "Cannot save $file: $!\n";
120              
121 2         12 return $to_delete->size;
122             }
123              
124             =head2 find_files
125              
126             Find all Jelly and Java Properties files that could be translated from English,
127             i.e., files that do not have a ISO 639-1 standard language based code as a
128             filename prefix (before the file extension).
129              
130             Expects as parameter a complete path to a directory that might contain such
131             files.
132              
133             Returns an sorted array reference with the complete path to those files.
134              
135             =cut
136              
137             # Relative paths inside the Jenkins project repository
138             my $src_test_path = File::Spec->catfile( 'src', 'test' );
139             my $target_path = File::Spec->catfile( 'target', '' );
140             my $src_regex = qr/$src_test_path/;
141             my $target_regex = qr/$target_path/;
142             my $msgs_regex = qr/Messages\.properties$/;
143             my $jelly_regex = qr/\.jelly$/;
144              
145             sub find_files {
146 4     4 1 4952 my $dir = shift;
147 4 100       30 confess 'Must provide a string, invalid directory parameter'
148             unless ($dir);
149 3 100       18 confess 'Must provide a string as directory, not a reference'
150             unless ( ref($dir) eq '' );
151 2 100       116 confess "Directory $dir must exists" unless ( -d $dir );
152 1         4 my @files;
153              
154             find(
155             sub {
156 5     5   12 my $file = $File::Find::name;
157              
158 5 50 33     34 unless ( ( $file =~ $src_regex ) or ( $file =~ $target_regex ) ) {
159 5 100 100     183 push( @files, $file )
160             if ( ( $file =~ $msgs_regex )
161             or ( $file =~ $jelly_regex ) );
162             }
163             },
164 1         118 $dir
165             );
166 1         15 my @sorted = sort(@files);
167 1         10 return \@sorted;
168             }
169              
170             my $regex = qr/_([a-z]{2})(_[A-Z]{2})?\.properties$/;
171              
172             =head2 find_langs
173              
174             Finds all ISO 639-1 standard language based codes available in the Jenkins
175             repository based on the filenames sufix (before the file extension) of the
176             translated files.
177              
178             This is basically the opposite of C does.
179              
180             It expect as parameters the complete path to a directory to search for the
181             files.
182              
183             Returns a instance of the L class containing all the language codes
184             that were identified.
185              
186             =cut
187              
188             sub find_langs {
189 0     0 1 0 my $dir = shift;
190 0 0       0 confess 'Must provide a string, invalid directory parameter'
191             unless ($dir);
192 0 0       0 confess 'Must provide a string as directory, not a reference'
193             unless ( ref($dir) eq '' );
194 0 0       0 confess "Directory $dir must exists" unless ( -d $dir );
195 0         0 my $langs = Set::Tiny->new;
196              
197             find(
198             sub {
199 0     0   0 my $file = $File::Find::name;
200              
201 0 0 0     0 unless ( ( $file =~ $src_regex ) or ( $file =~ $target_regex ) ) {
202 0 0       0 if ( $file =~ $regex ) {
203 0         0 my $lang;
204              
205 0 0       0 if ($2) {
206 0         0 $lang = $1 . $2;
207             }
208             else {
209 0         0 $lang = $1;
210             }
211              
212 0         0 $langs->insert($lang);
213             }
214             }
215             },
216 0         0 $dir
217             );
218              
219 0         0 return $langs;
220             }
221              
222             =head2 load_properties
223              
224             Loads the content of a Java Properties file into a hash.
225              
226             Expects as position parameters:
227              
228             =over
229              
230             =item 1
231              
232             The complete path to a Java Properties file.
233              
234             =item 2
235              
236             True (1) or false (0) if a warn should be printed to C in case the file
237             is missing.
238              
239             =back
240              
241             Returns an hash reference with the file content. If the file doesn't exist,
242             returns an empty hash reference.
243              
244             =cut
245              
246             sub load_properties {
247 4     4 1 3106 my ( $file, $must_warn ) = @_;
248 4 50       12 confess 'The complete path to the properties file is required'
249             unless ($file);
250 4 100       28 confess 'Must pass if a warning is required or not'
251             unless ( defined($must_warn) );
252              
253 3 100       64 unless ( -f $file ) {
254 2 100       19 warn "File $file doesn't exist, skipping it...\n" if ($must_warn);
255 2         12 return {};
256             }
257              
258 1         20 my $props_handler = Jenkins::i18n::Properties->new( file => $file );
259 1         37 return $props_handler->getProperties;
260             }
261              
262             =head2 load_jelly
263              
264             Fill a hash with key/1 pairs from a C<.jelly> file.
265              
266             Expects as parameter the path to a Jelly file.
267              
268             Returns a hash reference.
269              
270             =cut
271              
272             # TODO: replace regex with XML parser
273             sub load_jelly {
274 2     2 1 2141 my $file = shift;
275 2         4 my %ret;
276              
277 2 50       107 open( my $fh, '<', $file ) or confess "Cannot read $file: $!\n";
278              
279 2         68 while (<$fh>) {
280 64 100       198 next if ( !/\$\{.*?\%([^\(]+?).*\}/ );
281 4         9 my $line = $_;
282 4   66     34 while ($line =~ /^.*?\$\{\%([^\(\}]+)(.*)$/
283             || $line =~ /^.*?\$\{.*?['"]\%([^\(\}\"\']+)(.*)$/ )
284             {
285 4         14 $line = $2;
286 4         8 my $word = $1;
287 4         8 $word =~ s/\(.+$//g;
288 4         7 $word =~ s/'+/''/g;
289 4         11 $word =~ s/ /\\ /g;
290 4         7 $word =~ s/\>/>/g;
291 4         7 $word =~ s/\</
292 4         5 $word =~ s/\&/&/g;
293 4         9 $word =~ s/([#:=])/\\$1/g;
294 4         30 $ret{$word} = 1;
295             }
296             }
297              
298 2         27 close($fh);
299 2         15 return \%ret;
300             }
301              
302             1;
303              
304             __END__