File Coverage

blib/lib/String/CaseProfile.pm
Criterion Covered Total %
statement 159 169 94.0
branch 77 86 89.5
condition 21 33 63.6
subroutine 15 15 100.0
pod 3 3 100.0
total 275 306 89.8


line stmt bran cond sub pod time code
1             package String::CaseProfile;
2              
3 4     4   191043 use 5.008;
  4         16  
  4         323  
4 4     4   24 use strict;
  4         8  
  4         198  
5 4     4   21 use warnings;
  4         12  
  4         145  
6 4     4   29 use Carp qw(carp);
  4         7  
  4         331  
7              
8 4     4   20 use Exporter;
  4         7  
  4         162  
9 4     4   21 use base 'Exporter';
  4         6  
  4         1028  
10             our @EXPORT_OK = qw(
11             get_profile
12             set_profile
13             copy_profile
14             );
15              
16             our %EXPORT_TAGS = ( 'all' => [ @EXPORT_OK ] );
17              
18             our $VERSION = '0.18';
19              
20              
21             my $word_re = qr{
22             \b(?:\p{Lu}{1,2}\.)+(?:\P{L}|$)
23             |
24             \b\p{Lu}{1,2}\/\p{Lu}{1,2}\b
25             |
26             (?:
27             \p{L}
28             |
29             (?<=\p{L})[-'\x92_&](?=\p{L})
30             |
31             (?<=[lL])\xB7(?=[lL])
32             |
33             \d
34             )+
35 4     4   4142 }x;
  4         38  
  4         52  
36              
37              
38             my %types = (
39             '1st_uc' => 'f',
40             'all_uc' => 'u',
41             'all_lc' => 'l',
42             'other' => 'o',
43             );
44              
45              
46             sub get_profile {
47 54     54 1 22926 my $string = shift;
48            
49 54         73 my (@excluded, $strict);
50            
51 54 100       238 if (ref $_[0] eq 'HASH') {
52            
53 5 100       21 if ($_[0]->{exclude}) {
54 3         6 @excluded = @{$_[0]->{exclude}};
  3         9  
55             }
56            
57 5         14 $strict = $_[0]->{strict};
58            
59             } else {
60            
61 49 100       177 if ( defined $_[0] ) {
62 7         8 @excluded = @{ $_[0]} ;
  7         20  
63             }
64            
65             }
66              
67             # read excluded words, if any
68 54         70 my %excluded;
69 54 100       136 if ( @excluded > 0 ) {
70 10         45 $excluded{$_}++ foreach ( @excluded );
71             }
72            
73 54         1440 my @words = $string =~ /($word_re)/g;
74            
75 54         8332 my @word_types;
76 54 100 66     216 if ( @words == 1 && length $words[0] == 1 ) {
77            
78 4 100       24 if ($words[0] =~ /^\p{Lu}$/) {
    100          
79            
80 1         3 push @word_types, 'all_uc';
81            
82             } elsif ($words[0] =~ /^\p{Ll}$/) {
83            
84 1         3 push @word_types, 'all_lc';
85            
86             } else {
87            
88 2         4 push @word_types, 'other';
89             }
90            
91             } else {
92            
93 179 100       397 @word_types = map {
94            
95 50         96 _exclude($_, \%excluded)
96             ?
97             'excluded'
98             :
99             _word_type($_)
100            
101             } @words;
102            
103             }
104            
105 54         147 my %profile;
106 54         127 ( $profile{fold}, $profile{string_type} ) = _string_type($strict, @word_types);
107            
108 54         194 for (my $i = 0; $i <= $#words; $i++) {
109 183         205 push @{$profile{words}}, {
  183         956  
110             word => $words[$i],
111             type => $word_types[$i],
112             }
113             }
114            
115 54         196 $profile{report} = _create_report($string, \%profile);
116            
117 54         508 return %profile;
118             }
119              
120              
121             sub _exclude {
122 179     179   262 my ($word, $excluded_href) = @_;
123            
124 179 100       456 return 1 if $excluded_href->{$word};
125              
126 171 100       531 if ($word =~ /[-']/) {
127 5         32 my @pieces = split /[-']/, $word;
128 5         12 my @excluded = grep { $excluded_href->{$_} } @pieces;
  11         26  
129 5 100       14 if (@excluded) { return 1 } else { return 0 };
  2         9  
  3         14  
130             } else {
131 166         553 return 0;
132             }
133             }
134              
135             sub _create_report {
136 54     54   85 my ($string, $prof_href) = @_;
137            
138 54         62 my %prof = %{$prof_href};
  54         229  
139            
140 54         79 my $report;
141 54         143 $report .= "String: $string\n";
142 54         131 $report .= "Type: $prof{string_type}\n";
143 54         121 $report .= "Pattern: $prof{fold}\n\n";
144 54         94 $report .= "Word Type\n--------------------------\n";
145 54         86 for ( my $i = 0; $i < scalar(@{$prof{words}}); $i++ ) {
  237         601  
146 183         824 $report .= sprintf "%-20s%-20s\n", $prof{words}[$i]->{word},
147             $prof{words}[$i]->{type};
148             }
149            
150 54         221 return $report;
151             }
152              
153              
154             sub set_profile {
155 25     25 1 13715 my ($string, %ref_profile) = @_;
156              
157 25         89 my %string_profile = get_profile($string, $ref_profile{exclude});
158            
159 25         64 my @words = map { $_->{word} } @{$string_profile{words}};
  100         288  
  25         54  
160 25         44 my @word_types = map { $_->{type} } @{$string_profile{words}};
  100         184  
  25         52  
161            
162 25         65 my $force = $ref_profile{'force_change'};
163            
164             # validate string_type
165 25         29 my ($legal, $ref_string_type);
166 25 100       65 if ($ref_profile{string_type}) {
167 19         28 $ref_string_type = $ref_profile{string_type};
168 19 100 100     112 if ($types{$ref_string_type} && $ref_string_type ne 'other') {
    100          
169 16         27 $legal = 1;
170             } elsif ($ref_string_type eq 'other') {
171 2         21 return $string;
172             } else {
173 1         24 carp "Illegal value of string_type";
174             }
175             }
176            
177 23         725 my @transformed;
178            
179 23 100       63 if ($legal) {
    100          
180 16 100       38 if ($ref_string_type eq '1st_uc') {
181 4 50       14 if ($word_types[0] eq 'excluded') {
182 0         0 $transformed[0] = $words[0];
183             } else {
184 4         13 $transformed[0] = _transform(
185             '1st_uc',
186             $words[0],
187             $word_types[0],
188             $force
189             );
190             }
191 4         20 for (my $i = 1; $i <= $#words; $i++) {
192 13 100       29 if ($word_types[$i] eq 'excluded') {
193 1         4 push @transformed, $words[$i];
194             } else {
195 12         28 push @transformed, _transform(
196             'all_lc',
197             $words[$i],
198             $word_types[$i],
199             $force
200             );
201             }
202             }
203             } else {
204 12         42 for (my $i = 0; $i <= $#words; $i++) {
205 48 100 100     200 if (
206             $word_types[$i] eq 'excluded'
207             && $ref_string_type ne 'all_uc'
208             ) {
209 2         7 push @transformed, $words[$i];
210             } else {
211 46         114 push @transformed, _transform(
212             $ref_string_type,
213             $words[$i],
214             $word_types[$i],
215             $force
216             );
217             }
218             }
219             }
220            
221             # custom profile
222             } elsif ($ref_profile{custom}) {
223            
224             # validate default type
225 6         12 my ($type, $default_type);
226 6 100       28 if ($ref_profile{custom}->{default}) {
227 4         12 $type = $ref_profile{custom}->{default};
228 4 100 66     35 if ($types{$type} && $types{$type} ne 'other') {
229 3         8 $default_type = $type;
230             } else {
231 1         13 carp "Illegal default value in custom profile";
232             }
233             }
234            
235 6         838 for (my $i = 0; $i <= $#word_types; $i++) {
236            
237 22         52 my $in_index = $ref_profile{custom}->{index}->{$i};
238 22         45 my $trigger_type = $ref_profile{custom}->{$word_types[$i]};
239            
240 22 100       70 if ($in_index) {
    100          
    100          
241 3 100 66     31 if (
    50          
242             $word_types[$i] eq 'excluded'
243             && $in_index ne 'all_uc'
244             ) {
245 1         5 push @transformed, $words[$i];
246             } elsif ($in_index ne $word_types[$i]) {
247 2         11 push @transformed, _transform(
248             $in_index,
249             $words[$i],
250             $word_types[$i],
251             $force
252             );
253             } else {
254 0         0 push @transformed, $words[$i];
255             }
256             } elsif ($trigger_type) {
257 5 50 33     24 if (
258             $word_types[$i] eq 'excluded'
259             && $ref_string_type ne 'all_uc'
260             ) {
261 0         0 push @transformed, $words[$i];
262             } else {
263 5         17 push @transformed, _transform(
264             $trigger_type,
265             $words[$i],
266             $word_types[$i],
267             $force
268             );
269             }
270              
271             } elsif ($default_type) { # use default type
272 7 50 33     27 if (
273             $word_types[$i] eq 'excluded'
274             && $ref_string_type ne 'all_uc'
275             ) {
276 0         0 push @transformed, $words[$i];
277             } else {
278 7         21 push @transformed, _transform(
279             $default_type,
280             $words[$i],
281             $word_types[$i],
282             $force
283             );
284             }
285             } else {
286 7         25 push @transformed, $words[$i];
287             }
288             }
289             }
290            
291             # transform string
292 23 100       65 if (@transformed) {
293 22         67 for (my $i = 0; $i <= $#words; $i++) {
294 87         1681 $string =~ s/\b$words[$i]\b/$transformed[$i]/;
295             }
296             }
297              
298 23         250 return $string;
299             }
300              
301              
302             sub copy_profile {
303 7     7 1 10173 my %options = @_;
304            
305 7         19 my $from = $options{from};
306 7         15 my $to = $options{to};
307 7         17 my $strict = $options{strict};
308 7         13 my $exclude = $options{exclude};
309            
310 7 50 33     51 if ( $from && $to ) {
    0 0        
    0          
311            
312 7 100 100     39 if ( $exclude || $strict ) {
313            
314 2         13 my %ref_profile = get_profile( $from, {
315             exclude => $exclude,
316             strict => $strict
317             }
318             );
319            
320 2         7 $ref_profile{exclude} = $exclude;
321            
322 2         10 return set_profile( $to, %ref_profile );
323            
324             } else {
325            
326 5         18 return set_profile($options{to}, get_profile($options{from}));
327            
328             }
329            
330             } elsif ( !$from && !$to ) {
331            
332 0         0 carp "Missing parameters\n";
333 0         0 return '';
334            
335             } elsif ( !$from ) {
336            
337 0         0 carp "Missing reference string\n";
338 0         0 return $to;
339            
340             } else {
341            
342 0         0 carp "Missing target string\n";
343 0         0 return '';
344            
345             }
346             }
347              
348              
349             sub _word_type {
350 169     169   241 my ($word) = @_;
351            
352 169 100       1294 if ($word =~ /^[bcdfghjklmnpqrstvwxyz]$/i) {
    100          
    100          
    100          
353 4         16 return 'other';
354             } elsif ($word =~ /^\p{Lu}(?:\p{Ll}|[-'\x92\xB7])*$/) {
355 23         77 return '1st_uc';
356             } elsif ($word =~ /^(?:\p{Ll}|[-'\x92\xB7])+$/) {
357 116         358 return 'all_lc';
358             } elsif ($word =~ /^(?:\p{Lu}|[-'\x92\xB7])+$/) {
359 18         54 return 'all_uc';
360             } else {
361 8         32 return 'other';
362             }
363            
364             }
365              
366              
367             sub _string_type {
368            
369 54     54   71 my $strict = shift;
370 54         109 my @types = @_;
371            
372 54         90 my $types_str = join "", map { $types{$_} } grep { $_ ne 'excluded' } @types;
  173         379  
  183         365  
373            
374             # remove 'other' word types
375 54         97 my $clean_str = $types_str;
376 54 100       171 $clean_str =~ s/o//g unless $strict;
377            
378 54         90 my $string_type;
379            
380 54 100       290 if ($clean_str =~ /^fl*$/) {
    100          
    100          
381 21         90 $string_type = '1st_uc';
382             } elsif ($clean_str =~ /^u+$/) {
383 9         31 $string_type = 'all_uc';
384             } elsif ($clean_str =~ /^l+$/) {
385 20         38 $string_type = 'all_lc';
386             } else {
387 4         10 $string_type = 'other';
388             }
389            
390 54         289 return ($types_str, $string_type);
391             }
392              
393              
394             sub _transform {
395 76     76   137 my ($type, $word, $word_type, $force) = @_;
396            
397 76 100 100     282 return $word if ($word_type eq 'other' && !$force);
398            
399 74         25551 my %dispatch = (
400             '1st_uc' => ucfirst(lc($word)),
401             'all_uc' => uc($word),
402             'all_lc' => lc($word),
403             'other' => $word,
404             );
405            
406 74         73071 $dispatch{$type};
407             }
408              
409              
410             1;
411             __END__