File Coverage

blib/lib/Lingua/EN/Inflexion.pm
Criterion Covered Total %
statement 83 91 91.2
branch 34 58 58.6
condition 20 29 68.9
subroutine 15 18 83.3
pod 4 5 80.0
total 156 201 77.6


line stmt bran cond sub pod time code
1             package Lingua::EN::Inflexion;
2 24     24   1639973 use 5.010; use warnings;
  24     24   359  
  24         155  
  24         60  
  24         2096  
3 24     24   154 use Carp;
  24         53  
  24         2098  
4              
5             our $VERSION = '0.001004';
6              
7             # Import noun, verb, and adj classes...
8 24     24   8708 use Lingua::EN::Inflexion::Term;
  24         92  
  24         2653  
9              
10             sub import {
11 24     24   309 my (undef, @exports) = @_;
12              
13             # Export interface...
14 24 100       143 @exports = qw< noun verb adj inflect wordlist > if !@exports;
15              
16             # Handle renames...
17 24         60 my %export_name;
18 24 100       904 @exports = map { ref eq 'HASH' ? do { @export_name{keys %$_} = values %$_; keys %$_ } : $_ }
  116         368  
  1         5  
  1         5  
19             @exports;
20              
21 24     24   326 no strict 'refs';
  24         60  
  24         26065  
22 24         71 for my $func (@exports) {
23 118   66     197 *{caller().'::'.($export_name{$func}//$func)} = \&{$func};
  118         319786  
  118         954  
24             }
25             }
26              
27              
28             # Noun constructor...
29             sub noun ($) {
30 20994     20994 1 929425 my ($noun) = @_;
31 20994         70300 return Lingua::EN::Inflexion::Noun->new($noun);
32             }
33              
34             # Verb constructor...
35             sub verb ($) {
36 4062     4062 1 132131 my ($verb) = @_;
37 4062         16734 return Lingua::EN::Inflexion::Verb->new($verb);
38             }
39              
40              
41             # Verb constructor...
42             sub adj ($) {
43 80     80 1 17445 my ($adj) = @_;
44 80         231 return Lingua::EN::Inflexion::Adjective->new($adj);
45             }
46              
47              
48             # Convert a list of words to...a list of words in a single string...
49             sub wordlist {
50 29     29 0 133 my (@words, %opt);
51              
52             # Unpack the argument list...
53 29         48 my $sep = ',';
54 29         39 my $conj = 'and';
55 29         50 for my $arg (@_) {
56 101         149 my $argtype = ref($arg);
57              
58 101 100       183 if ($argtype eq q{}) { push @words, $arg; $sep = ';' if $arg =~ /,/; }
  77 100       115  
  77 50       182  
59 24         100 elsif ($argtype eq q{HASH}) { @opt{keys %$arg} = values %$arg }
60 0         0 else { croak 'Invalid $argtype argument to wordlist' }
61             }
62              
63             # Fill in defaults...
64 29   66     94 $conj = $opt{conj} // $conj;
65 29   33     90 $sep = $opt{sep} // $sep;
66              
67             # Set the Oxford comma...
68 29   66     75 my $oxford = $opt{final_sep} // $sep;
69              
70             # Construct the list phrase...
71 29 100       140 my $list = @words < 3
72             ? join(" $conj ", @words)
73             : join("$sep ", @words[0..$#words-1]) . "$oxford $conj $words[-1]";
74              
75             # Condense any extra whitespace...
76 29         130 $list =~ s/(\s)\s+/$1/g;
77              
78 29         161 return $list;
79             }
80              
81              
82             # All-in-one inflexions...
83             my %word_for_number = (
84             0 => 'zero', 5 => 'five',
85             1 => 'one', 6 => 'six',
86             2 => 'two', 7 => 'seven',
87             3 => 'three', 8 => 'eight',
88             4 => 'four', 9 => 'nine',
89             10 => 'ten',
90             );
91              
92             my $normalize_opts = sub {
93             my ($opts) = @_;
94              
95             if ($opts =~ m{ [[:upper:]] }x) {
96             $opts =~ s{ [[:lower:]] }{}gx;
97             }
98             return lc $opts;
99             };
100              
101             sub inflect($) {
102 32     32 1 16166 my ($string) = @_;
103              
104 32         58 my $inflexion = 'singular';
105              
106             my $transform = {
107             'N' => sub{
108 31     31   66 my ($term, $opts) = @_;
109             carp "Unknown '$_' option to command"
110 31         75 for $opts =~ /([^cps])/;
111              
112 31         65 my $word = noun($term);
113 31 100       113 $word = $word->classical if $opts =~ /c/i;
114              
115 31 50       166 return $opts =~ /p/i ? $word->plural
    50          
116             : $opts =~ /s/i ? $word->singular
117             : $word->$inflexion;
118             },
119              
120 31     31   72 'V' => sub{ return verb(shift)->$inflexion; },
121              
122 0     0   0 'A' => sub{ return adj(shift)->$inflexion; },
123              
124             '#' => sub{
125 32     32   73 my ($count, $opts) = @_;
126 32         65 $opts =~ s{e}{asw}g;
127             carp "Unknown '$_' option to <#:...> command"
128 32         85 for $opts =~ /([^acdefinosw\d])/;
129              
130             # Increment count if requested...
131 32 100       76 if ($opts =~ /i/i) {
132 1         4 $count++;
133             }
134              
135             # Decide which inflexion the count requires...
136             $inflexion
137 32 100 66     233 = $count == 1 || $opts =~ /s/i && $count == 0 || $opts =~ /o/i ? 'singular'
138             : 'plural';
139              
140             # Defer handling of A/AN...
141 32 100 100     109 if ($count == 1 && $opts =~ /a/i) {
142 2         6 return "<#a:>";
143             }
144              
145 30 100       106 my $count_word = $opts =~ /w|o/i ? noun($count) : undef;
146 30 50 66     109 $count_word = $count_word->classical if $count_word && $opts =~ /c/i;
147              
148 30 50       64 my $count_thresh = $opts =~ /w(\d+)/i ? $1 : 11;
149              
150             # Otherwise, interpolate count or its equivalent (deferring fuzzies)...
151 30 50 100     247 return $opts =~ /n|s/i && $count == 0 ? 'no'
    50 66        
    50          
    100          
    50          
    100          
152             : $opts =~ /w/i && $opts =~ /o/i ? $count_word->ordinal($count_thresh)
153             : $opts =~ /w/i ? $count_word->cardinal($count_thresh)
154             : $opts =~ /o/i ? $count_word->ordinal(0)
155             : $opts =~ /f/i ? "<#f:$count>"
156             : $opts =~ /d/ ? q{}
157             : $count;
158             },
159 32         357 };
160              
161             # Inflect markups...
162 32         281 $string =~ s{ (?
163             < (? (?-i: [#NVA] ) ) # FUNC is case-sensitive
164             (? [^:]* ) \s*
165             : \s* (? [^>]+? ) \s*
166             >
167             (? \s* )
168             )
169             }{
170 24     24   10111 my %parsed = %+;
  24         8576  
  24         9911  
  94         1476  
171 94         399 my $opts = $normalize_opts->($parsed{OPTS});
172 94   50 0   307 my $func = $transform->{ uc $parsed{FUNC} } // sub{shift};
  0         0  
173 94         195 my $replacement = $func->( $parsed{TERM}, $opts );
174 93 50       875 length $replacement > 0 ? $replacement . $parsed{TWS} : q{}
175             }gexmsi;
176              
177             # Inflect consequent A/AN's...
178 31         77 $string =~ s{ <[#]a:> \s*+ (? \S++) }{ noun($+{next_word})->indefinite }gxe;
  2         11  
179 31         63 $string =~ s{ <[#]a:> \s*+ \Z }{ "a" }xe;
  0         0  
180              
181             # Inflect fuzzies...
182             state $fuzzy = sub {
183 0     0   0 my ($count, $is_postfix) = @_;
184              
185 0 0       0 return $count >= 10 ? 'many'
    0          
    0          
    0          
    0          
    0          
    0          
186             : $count >= 6 ? 'several'
187             : $count >= 3 ? 'a few'
188             : $count == 2 ? 'a couple' . ($is_postfix ? q{} : ' of')
189             : $count == 1 ? 'one'
190             : ($is_postfix ? 'none' : 'no')
191             ;
192 31         53 };
193              
194 31         49 $string =~ s{ <\#f: (? \d++) > (?= \s*+ [[:alpha:]]) }
  0         0  
195 31         50 { $fuzzy->($+{count}) }gxe;
  0         0  
196             $string =~ s{ <\#f: (? \d++) > (?= [^[:alpha:]]*+ \Z) }
197             { $fuzzy->($+{count}, 'postfix') }xe;
198 31         453  
199             # And we're done...
200             return $string;
201             }
202              
203              
204             1; # Magic true value required at end of module
205             __END__