File Coverage

blib/lib/Acme/Numbers.pm
Criterion Covered Total %
statement 77 81 95.0
branch 31 34 91.1
condition 28 32 87.5
subroutine 12 15 80.0
pod 5 5 100.0
total 153 167 91.6


line stmt bran cond sub pod time code
1             package Acme::Numbers;
2 5     5   106213 use strict;
  5         14  
  5         198  
3 5     5   4249 use Lingua::EN::Words2Nums qw(words2nums);
  5         14834  
  5         886  
4             our $AUTOLOAD;
5             our $VERSION = '1.2';
6              
7              
8             =head1 NAME
9              
10             Acme::Numbers - a fluent numeric interface
11              
12             =head1 SYNOPSIS
13              
14             use Acme::Numbers;
15              
16             print one."\n"; # prints 1
17             print two.hundred."\n"; # prints 200
18             print forty.two."\n"; # prints 42
19             print six.hundred.and.sixty.six."\n"; # prints 666
20             print one.million."\n"; # prints 1000000
21              
22             print three.point.one.four."\n"; # prints 3.14
23             print one.point.zero.two."\n"; # prints 1.02
24             print zero.point.zero.five."\n"; # prints 0.05
25              
26             print four.pounds."\n"; # prints "4.00"
27             print four.pounds.five."\n"; # prints "4.05"
28             print four.pounds.fifty."\n"; # prints "4.50"
29             print four.pounds.fifty.five."\n"; # prints "4.55"
30              
31             print fifty.pence."\n"; # prints "0.50"
32             print fifty.five.pence."\n"; # prints "0.55"
33             print four.pounds.fifty.pence."\n"; # prints "4.50"
34             print four.pounds.and.fifty.p."\n"; # prints "4.50"
35              
36             print fifty.cents."\n"; # prints "0.50"
37             print fifty.five.cents."\n"; # prints "0.55"
38             print four.dollars.fifty.cents."\n"; # prints "4.55"
39              
40            
41              
42             =head1 DESCRIPTION
43              
44             Inspired by this post
45              
46             http://beautifulcode.oreillynet.com/2007/12/the_cardinality_of_a_fluent_in.php
47              
48             and a burning curiosity. At leats, I hope the burning
49             was curiosity.
50              
51             =head1 ONE BIIIIIIIIIIIILLION
52              
53             By default billion is 10**12 because, dammit, that's right.
54              
55             If you want it to be an American billion then do
56              
57             use Acme::Numbers billion => 10**9;
58              
59             Setting this automatically changes all the larger numbers
60             (trillion, quadrillion, etc) to match.
61              
62             =head1 METHODS
63              
64             You should never really use these methods on the class directly.
65              
66             All numbers handled by C are handled by this module.
67              
68             In addition ...
69              
70             =cut
71              
72             sub import {
73 5     5   37 my $class = shift;
74 5         14 my %opts = @_;
75              
76 5 100       31 $opts{billion} = 10**12 unless defined $opts{billion};
77 5     5   52 no strict 'refs';
  5         11  
  5         141  
78 5     5   24 no warnings 'redefine';
  5         9  
  5         4654  
79 5         18 my ($pkg, $file) = caller;
80 5         11 $Lingua::EN::Words2Nums::billion = $opts{billion};
81 5         112 foreach my $num ((keys %Lingua::EN::Words2Nums::nametosub,
82             'and', 'point', 'zero',
83             'pound', 'pounds', 'pence', 'p',
84             'dollars', 'cents'))
85             {
86 600     129   1536 *{"$pkg\::$num"} = sub { $class->$num };
  600         10883  
  129         695  
87             }
88             };
89              
90              
91              
92             =head2 new
93              
94             C can be 'num', 'and' or 'point'
95              
96             =cut
97              
98             sub new {
99 191     191 1 229 my $class = shift;
100 191 100       372 $class = ref $class if ref $class;
101 191         206 my $val = shift;
102 191         207 my $op = shift;
103 191   66     463 my $name = shift || $op;
104 191         890 bless { value => $val, operator => $op, name => $name }, $class;
105             }
106              
107             =head2 name
108              
109             The name of this object (i.e the method that was originally called).
110              
111             =cut
112              
113             sub name {
114 0     0 1 0 return $_[0]->{name};
115             }
116              
117             =head2 value
118              
119             The current numeric value
120              
121             =cut
122              
123             sub value {
124 38     38 1 44 my $self = shift;
125 38         72 my $val = $self->{value};
126             # if we're 'pence' then divide by 100 and then pretend we're pounds
127 38 100       158 if ($self->{operator} =~ m!^p(ence)?$!) {
128             # this fixes something where there's 0
129             # pounds and a trailing zero like 0.50
130 6         12 $self->{last_added} = $val;
131 6         13 $val = $val/100;
132 6         9 $self->{operator} = 'pounds';
133             }
134 38 100       114 if ($self->{operator} =~ m!^pounds?$!) {
135 22         92 my ($num, $frac) = split /\./, $val;
136 22   100     50 $frac ||= 0;
137             # this also fixes 0 pounds trailing zero
138 22 100 100     94 $frac = $self->{last_added} if defined $self->{last_added} && $self->{last_added}>$frac;
139             # we substr to fix one.pound.fifty.pence which
140             # leaves $frac as '500'
141 22         120 $val = sprintf("%d.%02d",$num,substr($frac,0,2));
142             }
143              
144 38         256 return $val;
145             }
146              
147             sub AUTOLOAD {
148 129     129   180 my $self = shift;
149 129         161 my $method = $AUTOLOAD;
150 129         488 $method =~ s/.*://; # strip fully-qualified portion
151 129         166 my $val;
152             # nasty override - we should probably have a
153             # generic major or minor currency indicator
154             # if we could store and propogate the currency
155             # then we could also throw errors at mismatched
156             # units e.g five.pounds.and.fifty.cents
157             # but maybe also print out the correct sigil
158             # e.g $5.50
159 129 100       276 $method = 'pounds' if $method eq 'dollars';
160 129 100       246 $method = 'pence' if $method eq 'cents';
161              
162             # dummy methods
163 129 100 100     561 if ($method eq 'and' || $method =~ m!^p!) {
164 43         96 $val = $self->new(0, $method)
165             } else {
166             # bit of a hack here
167 86 100       275 my $tmp = ($method eq 'zero')? 0 : words2nums($method);
168             # maybe this should die
169 86 50       5680 return unless defined $tmp;
170 86         220 $val = $self->new($tmp, 'num', $method);
171             }
172              
173             # If we're the first number in the chain
174             # then just return ourselves
175 129 50       252 if (!ref $self) {
176 129         584 return $val;
177             } else {
178             # Otherwise do the magic
179 0         0 return $self->handle($val);
180             }
181             }
182              
183             =head2 handle
184              
185             Handle putting these two objects together
186              
187             =cut
188              
189             sub handle {
190 91     91 1 119 my ($self, $val) = @_;
191             # If we haven't passed a pounds, pence or point marker
192 91 100       279 if ($self->{operator} !~ m!^p!) {
193             # If the new object is marker ...
194 45 100       141 if ($val->{operator} =~ m!^p!) {
195             # ... Just propogate along but make a note
196             # A pound should not be overidden by a pence
197 29 50       83 $self->{operator} = $val->{operator} unless $self->{operator} =~ m!^pounds?$!;
198 29         123 return $self;
199              
200             # Otherwise ...
201             } else {
202 16         27 my $val = $val->{value};
203             # If we're not currently adding and the new more than the old
204             # e.g two.hundred then multiply
205 16 100 66     62 if ($self->{value} < $val && $self->{operator} ne 'add') {
206 7         10 $val *= $self->{value};
207             # Otherwise add
208             } else {
209 9         15 $val += $self->{value};
210             }
211 16         41 return $self->new($val, 'num', $self->{operator});
212             }
213             } else { # point, pound, pence
214             # first get the fractional part
215 46         173 my ($num, $frac) = split /\./, $self->{value};
216             #$frac ||= 0;
217             # Cope with four.point.zero.four
218 46 100 100     405 if ((defined $frac && $frac>0 && $frac<10) || $val->{value} == 0 || (defined $self->{last_added} and $self->{last_added} eq '0')) {
      100        
      100        
      100        
      66        
219 21         38 $frac .= $val->{value};
220             } else {
221 25         38 $frac += $val->{value};
222             }
223             # Create the new object
224 46         134 my $new = $self->new("${num}.${frac}", $self->{operator});
225             # We use this to be able to do point.fifty.five and point.five.five
226 46         97 $new->{last_added} = $val->{value};
227 46         158 return $new;
228             }
229             }
230              
231             =head2 concat
232              
233             Concatenate two things.
234              
235             =cut
236              
237             sub concat {
238 129     129 1 200 my ($self, $new) = @_;
239 129         130 my $class = shift;
240             # If both objects are special numbers handle them
241 129 100 66     583 if (ref($new) && $new->isa(__PACKAGE__)) {
242 91         214 return $self->handle($new);
243             # Otherwise stringify both and concat
244             } else {
245 38         71 return $self->value.$new;
246             }
247             }
248              
249             sub _bool {
250 0     0     my ($self, $new, $op) = @_;
251             }
252              
253 5         36 use overload '""' => 'value',
254             '+0' => 'value',
255 5     5   6705 '.' => 'concat';
  5         4444  
256             # 'bool' => 'bool';
257              
258              
259 0     0     sub DESTROY {}
260              
261             1;