File Coverage

blib/lib/Lingua/EN/Inflexion.pm
Criterion Covered Total %
statement 81 91 89.0
branch 30 58 51.7
condition 16 29 55.1
subroutine 15 18 83.3
pod 4 5 80.0
total 146 201 72.6


line stmt bran cond sub pod time code
1             package Lingua::EN::Inflexion;
2 24     24   1212886 use 5.010; use warnings;
  24     24   289  
  24         120  
  24         37  
  24         579  
3 24     24   152 use Carp;
  24         40  
  24         1533  
4              
5             our $VERSION = '0.001005';
6              
7             # Import noun, verb, and adj classes...
8 24     24   6842 use Lingua::EN::Inflexion::Term;
  24         79  
  24         2278  
9              
10             sub import {
11 24     24   255 my (undef, @exports) = @_;
12              
13             # Export interface...
14 24 100       109 @exports = qw< noun verb adj inflect wordlist > if !@exports;
15              
16             # Handle renames...
17 24         49 my %export_name;
18 24 100       709 @exports = map { ref eq 'HASH' ? do { @export_name{keys %$_} = values %$_; keys %$_ } : $_ }
  116         312  
  1         8  
  1         7  
19             @exports;
20              
21 24     24   247 no strict 'refs';
  24         50  
  24         21592  
22 24         65 for my $func (@exports) {
23 118   66     177 *{caller().'::'.($export_name{$func}//$func)} = \&{$func};
  118         301082  
  118         228  
24             }
25             }
26              
27              
28             # Noun constructor...
29             sub noun ($) {
30 20962     20962 1 700842 my ($noun) = @_;
31 20962         63261 return Lingua::EN::Inflexion::Noun->new($noun);
32             }
33              
34             # Verb constructor...
35             sub verb ($) {
36 4032     4032 1 82727 my ($verb) = @_;
37 4032         10948 return Lingua::EN::Inflexion::Verb->new($verb);
38             }
39              
40              
41             # Verb constructor...
42             sub adj ($) {
43 80     80 1 24415 my ($adj) = @_;
44 80         310 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 121 my (@words, %opt);
51              
52             # Unpack the argument list...
53 29         36 my $sep = ',';
54 29         37 my $conj = 'and';
55 29         47 for my $arg (@_) {
56 101         131 my $argtype = ref($arg);
57              
58 101 100       163 if ($argtype eq q{}) { push @words, $arg; $sep = ';' if $arg =~ /,/; }
  77 100       100  
  77 50       158  
59 24         84 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     80 $conj = $opt{conj} // $conj;
65 29   33     76 $sep = $opt{sep} // $sep;
66              
67             # Set the Oxford comma...
68 29   66     58 my $oxford = $opt{final_sep} // $sep;
69              
70             # Construct the list phrase...
71 29 100       115 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         109 $list =~ s/(\s)\s+/$1/g;
77              
78 29         129 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 2     2 1 542 my ($string) = @_;
103              
104 2         7 my $inflexion = 'singular';
105              
106             my $transform = {
107             'N' => sub{
108 1     1   3 my ($term, $opts) = @_;
109             carp "Unknown '$_' option to command"
110 1         3 for $opts =~ /([^cps])/;
111              
112 1         4 my $word = noun($term);
113 1 50       4 $word = $word->classical if $opts =~ /c/i;
114              
115 1 50       7 return $opts =~ /p/i ? $word->plural
    50          
116             : $opts =~ /s/i ? $word->singular
117             : $word->$inflexion;
118             },
119              
120 1     1   4 'V' => sub{ return verb(shift)->$inflexion; },
121              
122 0     0   0 'A' => sub{ return adj(shift)->$inflexion; },
123              
124             '#' => sub{
125 2     2   9 my ($count, $opts) = @_;
126 2         10 $opts =~ s{e}{asw}g;
127             carp "Unknown '$_' option to <#:...> command"
128 2         10 for $opts =~ /([^acdefinosw\d])/;
129              
130             # Increment count if requested...
131 2 100       15 if ($opts =~ /i/i) {
132 1         3 $count++;
133             }
134              
135             # Decide which inflexion the count requires...
136             $inflexion
137 2 50 33     42 = $count == 1 || $opts =~ /s/i && $count == 0 || $opts =~ /o/i ? 'singular'
138             : 'plural';
139              
140             # Defer handling of A/AN...
141 2 50 33     15 if ($count == 1 && $opts =~ /a/i) {
142 0         0 return "<#a:>";
143             }
144              
145 2 100       27 my $count_word = $opts =~ /w|o/i ? noun($count) : undef;
146 2 50 66     62 $count_word = $count_word->classical if $count_word && $opts =~ /c/i;
147              
148 2 50       15 my $count_thresh = $opts =~ /w(\d+)/i ? $1 : 11;
149              
150             # Otherwise, interpolate count or its equivalent (deferring fuzzies)...
151 2 50 66     57 return $opts =~ /n|s/i && $count == 0 ? 'no'
    50 66        
    50          
    100          
    50          
    50          
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 2         44 };
160              
161             # Inflect markups...
162 2         27 $string =~ s{ (?
163             < (? (?-i: [#NVA] ) ) # FUNC is case-sensitive
164             (? [^:]* ) \s*
165             : \s* (? [^>]+? ) \s*
166             >
167             (? \s* )
168             )
169             }{
170 24     24   8199 my %parsed = %+;
  24         7569  
  24         8193  
  4         102  
171 4         26 my $opts = $normalize_opts->($parsed{OPTS});
172 4   50 0   24 my $func = $transform->{ uc $parsed{FUNC} } // sub{shift};
  0         0  
173 4         14 my $replacement = $func->( $parsed{TERM}, $opts );
174 3 50       31 length $replacement > 0 ? $replacement . $parsed{TWS} : q{}
175             }gexmsi;
176              
177             # Inflect consequent A/AN's...
178 1         4 $string =~ s{ <[#]a:> \s*+ (? \S++) }{ noun($+{next_word})->indefinite }gxe;
  0         0  
179 1         56 $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 1         7 };
193              
194 1         3 $string =~ s{ <\#f: (? \d++) > (?= \s*+ [[:alpha:]]) }
  0         0  
195 1         2 { $fuzzy->($+{count}) }gxe;
  0         0  
196             $string =~ s{ <\#f: (? \d++) > (?= [^[:alpha:]]*+ \Z) }
197             { $fuzzy->($+{count}, 'postfix') }xe;
198 1         23  
199             # And we're done...
200             return $string;
201             }
202              
203              
204             1; # Magic true value required at end of module
205             __END__