File Coverage

blib/lib/Data/Currency.pm
Criterion Covered Total %
statement 151 152 99.3
branch 88 90 97.7
condition 35 46 76.0
subroutine 34 35 97.1
pod 8 8 100.0
total 316 331 95.4


line stmt bran cond sub pod time code
1             ## no critic (RequireUseStrict)
2             package Data::Currency;
3             {
4             $Data::Currency::VERSION = '0.06000';
5             }
6             ## use critic
7 13     13   140988 use strict;
  13         101  
  13         491  
8 13     13   70 use warnings;
  13         25  
  13         3644  
9              
10             use overload
11 0     0   0 '0+' => sub { shift->value },
12 1     1   47 'bool' => sub { shift->value },
13 1     1   5 '""' => sub { shift->stringify },
14 13         318 '+' => \&_add,
15             '-' => \&_substract,
16             '*' => \&_multiply,
17             '/' => \&_divide,
18             '%' => \&_modulo,
19             '<=>' => \&_three_way_compare,
20             'cmp' => \&_three_way_compare_string,
21             'abs' => \&_abs,
22             'int' => \&_int,
23             'neg' => \&_negate,
24 13     13   60465 fallback => 1;
  13         19429  
25              
26             # TODO Operations
27             # '+=' => \&add_in_place,
28             # '-=' => \&subtract_in_place,
29             # '*=' => \&multiply_in_place,
30             # '/=' => \&divide_in_place,
31              
32             BEGIN {
33 13     13   2940 use base qw/Class::Accessor::Grouped/;
  13         29  
  13         26522  
34 13     13   309928 use Locale::Currency ();
  13         442896  
  13         424  
35 13     13   23068 use Locale::Currency::Format;
  13         83654  
  13         4751  
36 13     13   164 use Scalar::Util ();
  13         26  
  13         237  
37 13     13   32215 use Class::Inspector ();
  13         52182  
  13         361  
38 13     13   121 use Carp;
  13         28  
  13         1563  
39              
40 13     13   327 __PACKAGE__->mk_group_accessors(
41             'inherited', qw/
42             format value converter converter_class
43             /
44             );
45 13         19670 __PACKAGE__->mk_group_accessors( 'component_class', qw/converter_class/ );
46             }
47              
48             __PACKAGE__->converter_class('Finance::Currency::Convert::WebserviceX');
49             __PACKAGE__->value(0);
50             __PACKAGE__->code('USD');
51             __PACKAGE__->format('FMT_COMMON');
52              
53             my %codes;
54              
55             sub new {
56 120     120 1 54536 my ( $class, $value, $code, $format ) = @_;
57 120         363 my $self = bless {}, $class;
58              
59 120 100       455 if ( ref $value eq 'HASH' ) {
60 2         4 foreach my $key ( keys %{$value} ) {
  2         9  
61 6 100       97 $self->$key( $value->{$key} ) if defined $value->{$key};
62             }
63             }
64             else {
65 118 100       382 if ( defined $value ) {
66 112         5035 $self->value($value);
67             }
68 118 100       3050 if ($code) {
69 101         264 $self->code($code);
70             }
71 117 100       1135 if ($format) {
72 32         1259 $self->format($format);
73             }
74             }
75              
76 119         876 return $self;
77             }
78              
79             sub code {
80 290     290 1 8073 my $self = shift;
81              
82 290 100       714 if ( scalar @_ ) {
83 117         174 my $code = shift;
84              
85 117 100       307 croak "Invalid currency code: $code"
86             unless _is_currency_code($code);
87              
88 115         1695 $self->set_inherited( 'code', $code );
89             }
90              
91 288         1796 return $self->get_inherited('code');
92             }
93              
94             sub convert {
95 8     8 1 8811 my ( $self, $to ) = @_;
96 8         32 my $class = Scalar::Util::blessed($self);
97 8         24 my $from = $self->code;
98              
99 8 100 100     94 croak 'Invalid currency code source: ' . ( $from || 'undef' )
100             unless _is_currency_code($from);
101              
102 6 100 100     14 croak 'Invalid currency code target: ' . ( $to || 'undef' )
103             unless _is_currency_code($to);
104              
105 4 100       18 if ( uc($from) eq uc($to) ) {
106 1         4 return $self;
107             }
108              
109 3 100       91 if ( !$self->converter ) {
110 2         763 $self->converter( $self->converter_class->new );
111             }
112              
113 3   100     327 return $class->new( $self->converter->convert( $self->value, $from, $to )
114             || 0,
115             $to, $self->format );
116             }
117              
118             sub name {
119 4     4 1 3238 my $self = shift;
120 4         12 my $name = Locale::Currency::code2currency( $self->code );
121              
122             ## Fix for older Locale::Currency w/mispelled Candian
123 4         386 $name =~ s/Candian/Canadian/;
124              
125 4         20 return $name;
126             }
127              
128             *as_string = \&stringify;
129              
130             sub stringify {
131 28     28 1 29627 my $self = shift;
132 28   100     9358 my $format = shift || $self->format;
133 28         782 my $code = $self->code;
134 28         1435 my $value = $self->value;
135              
136 28 100       444 if ( !$format ) {
137 1         3 $format = 'FMT_COMMON';
138             }
139              
140             ## funky eval to get string versions of constants back into the values
141             ## no critic (ProhibitStringyEval)
142 28         4312 eval '$format = Locale::Currency::Format::' . $format;
143             ## use critic
144              
145 28 100 100     343 croak 'Invalid currency code: ' . ( $code || 'undef' )
146             unless _is_currency_code($code);
147              
148 26         967 return _to_utf8(
149             Locale::Currency::Format::currency_format( $code, $value, $format ) );
150             }
151              
152             sub as_float {
153 23     23 1 15234 my $self = shift;
154 23         77 my $radix = $self->_radix;
155 23         1350 return sprintf( "%.0${radix}f", $self->value );
156             }
157              
158             sub _is_currency_code {
159 159 100   159   507 my $value = defined $_[0] ? uc(shift) : '';
160              
161 159 100       3932 return unless ( $value =~ /^[A-Z]{3}$/ );
162              
163 156 100       510 if ( !keys %codes ) {
164 2301         9088 %codes =
165 13         99 map { uc($_) => uc($_) } Locale::Currency::all_currency_codes();
166             }
167 156         2904 return exists $codes{$value};
168             }
169              
170             sub _to_utf8 {
171 26     26   10486 my $value = shift;
172              
173 26 100       159 if ( $] >= 5.008 ) {
174 25         181 require utf8;
175 25         76 utf8::upgrade($value);
176             }
177              
178 26         162 return $value;
179             }
180              
181             sub get_component_class {
182 4     4 1 84 my ( $self, $field ) = @_;
183              
184 4         14 return $self->get_inherited($field);
185             }
186              
187             sub set_component_class {
188 16     16 1 1520 my ( $self, $field, $value ) = @_;
189              
190 16 100       84 if ($value) {
191 15 100       186 if ( !Class::Inspector->loaded($value) ) {
192              
193             ## no critic (ProhibitStringyEval)
194 13     13   29470 eval "use $value";
  12         1095888  
  12         346  
  13         2455  
195             ## use critic
196              
197 13 100       384 croak "The $field $value could not be loaded: $@" if $@;
198             }
199             }
200              
201 15         594 $self->set_inherited( $field, $value );
202              
203 15         216 return;
204             }
205              
206             sub _radix {
207 23     23   33 my $self = shift;
208 23   100     266 return Locale::Currency::Format::decimal_precision( $self->code ) || 0;
209             }
210              
211             sub _add {
212 7     7   239 my ( $self, $other ) = @_;
213              
214 7 100 66     58 if ( Scalar::Util::blessed($other) && $other->isa(__PACKAGE__) ) {
215 3 100       9 croak "Unable to perform math operation with different currency types"
216             if $self->code ne $other->code;
217              
218 2         70 $other = $other->value;
219             }
220              
221 6 100       42 $other = defined $other ? $other : 0;
222 6         167 __PACKAGE__->new( $self->value + $other, $self->code, $self->format );
223             }
224              
225             sub _substract {
226 6     6   229 my ( $self, $other, $reversed ) = @_;
227              
228 6 100 66     53 if ( Scalar::Util::blessed($other) && $other->isa(__PACKAGE__) ) {
229 3 100       10 croak "Unable to perform math operation with different currency types"
230             if $self->code ne $other->code;
231              
232 2         68 $other = $other->value;
233             }
234              
235 5 100       34 $other = defined $other ? $other : 0;
236 5 100       127 my $new_value = $reversed ? $other - $self->value : $self->value - $other;
237 5         70 __PACKAGE__->new( $new_value, $self->code, $self->format );
238             }
239              
240             sub _multiply {
241 5     5   215 my ( $self, $other ) = @_;
242              
243 5 100 66     47 if ( Scalar::Util::blessed($other) && $other->isa(__PACKAGE__) ) {
244 2 100       6 croak "Unable to perform math operation with different currency types"
245             if $self->code ne $other->code;
246              
247 1         35 $other = $other->value;
248             }
249              
250 4 100       19 $other = defined $other ? $other : 0;
251 4         117 __PACKAGE__->new( $self->value * $other, $self->code, $self->format );
252             }
253              
254             sub _divide {
255 7     7   1218 my ( $self, $other, $reversed ) = @_;
256              
257 7 100 66     69 if ( Scalar::Util::blessed($other) && $other->isa(__PACKAGE__) ) {
258 4 100       12 croak "Unable to perform math operation with different currency types"
259             if $self->code ne $other->code;
260              
261 3         114 $other = $other->value;
262             }
263              
264 6 100       48 $other = defined $other ? $other : 0;
265              
266 6 100 66     86 croak "Illegal division by zero"
      66        
267             if $other == 0
268             or ( $reversed and $self->value == 0 );
269              
270 4 100       140 my $new_value = $reversed ? $other / $self->value : $self->value / $other;
271 4         65 __PACKAGE__->new( $new_value, $self->code, $self->format );
272             }
273              
274             sub _modulo {
275 6     6   256 my ( $self, $other, $reversed ) = @_;
276              
277 6 100 66     72 if ( Scalar::Util::blessed($other) && $other->isa(__PACKAGE__) ) {
278 4 100       10 croak "Unable to perform math operation with different currency types"
279             if $self->code ne $other->code;
280              
281 3         127 $other = $other->value;
282             }
283              
284 5 100 66     104 croak "Illegal modulus zero"
      66        
285             if $other == 0
286             or ( $reversed and $self->value == 0 );
287              
288 4 100       130 my $new_value = $reversed ? $other % $self->value : $self->value % $other;
289 4         59 __PACKAGE__->new( $new_value, $self->code, $self->format );
290             }
291              
292             sub _three_way_compare {
293 5     5   420 my ( $self, $other, $reversed ) = @_;
294              
295 5 100 66     42 if ( Scalar::Util::blessed($other) && $other->isa(__PACKAGE__) ) {
296 2 50       40 croak "Unable to perform comparison with different currency types"
297             if $self->code ne $other->code;
298              
299 2         79 $other = $other->value;
300             }
301              
302 5 100       164 return $reversed ? $other <=> $self->value : $self->value <=> $other;
303             }
304              
305             sub _three_way_compare_string {
306 5     5   492 my ( $self, $other, $reversed ) = @_;
307              
308 5 100 66     42 if ( Scalar::Util::blessed($other) && $other->isa(__PACKAGE__) ) {
309 3 50       8 croak "Unable to perform comparison with different currency types"
310             if $self->code ne $other->code;
311              
312 3         35 $other = $other->as_string;
313             }
314              
315 5 100       26 return $reversed
316             ? $other cmp $self->as_string
317             : $self->as_string cmp $other;
318             }
319              
320             sub _abs {
321 2     2   5 my $self = shift;
322 2         83 __PACKAGE__->new( abs( $self->value ), $self->code, $self->format );
323             }
324              
325             sub _int {
326 2     2   4 my $self = shift;
327 2         50 __PACKAGE__->new( int( $self->value ), $self->code, $self->format );
328             }
329              
330             sub _negate {
331 1     1   2 my $self = shift;
332 1         48 __PACKAGE__->new( -$self->value, $self->code, $self->format );
333             }
334              
335             1;
336              
337             __END__
338              
339             =pod
340              
341             =head1 NAME
342              
343             Data::Currency
344              
345             =head1 VERSION
346              
347             version 0.06000
348              
349             =head1 SYNOPSIS
350              
351             use Data::Currency;
352              
353             my $price = Data::Currency->new(1.2, 'USD');
354              
355             print $price; # 1.20 USD
356             print $price->code; # USD
357             print $price->format; # FMT_SYMBOL
358             print $price->as_string; # 1.20 USD
359             print $price->as_string('FMT_SYMBOL'); # $1.20
360              
361             print 'Your price in Canadian Dollars is: ';
362             print $price->convert('CAD')->value;
363              
364             =head1 DESCRIPTION
365              
366             The Data::Currency module provides basic currency formatting and conversion:
367              
368             my $price = 1.23;
369             my $currency = Data::Currency->new($price);
370              
371             print $currency->convert('CAD')->as_string;
372              
373             Each Data::Currency object will stringify to the original value except in string
374             context, where it stringifies to the format specified in C<format>.
375              
376             =head1 NAME
377              
378             Data::Currency - Container class for currency conversion/formatting
379              
380             =head1 VERSION
381              
382             version 0.06000
383              
384             =head1 CONSTRUCTOR
385              
386             =head2 new
387              
388             =over
389              
390             =item Arguments: $price [, $code, $format] || \%options
391              
392             =back
393              
394             To create a new Data::Currency object, simply call C<new> and pass in the
395             price to be formatted:
396              
397             my $currency = Data::Currency->new(10.23);
398              
399             my $currency = Data::Currency->new({
400             value => 1.23,
401             code => 'CAD',
402             format => 'FMT_SYMBOL',
403             converter_class => 'MyConverterClass'
404             });
405              
406             You can also pass in the default currency code and/or currency format to be
407             used for each instance. If no code or format are supplied, future calls to
408             C<as_string> and C<convert> will use the default format and code values.
409              
410             You can set the defaults by calling the code/format values as class methods:
411              
412             Data::Currency->code('USD');
413             Data::Currency->format('FMT_COMMON');
414              
415             my $currency = Data::Currency->new(1.23);
416             print $currency->as_string; # $1.23
417              
418             my $currency = Data::Currency->new(1.23, 'CAD', 'FMT_STANDARD');
419             print $currency->as_string; # 1.23 CAD
420              
421             The following defaults are set when Data::Currency is loaded:
422              
423             value: 0
424             code: USD
425             format: FMT_COMMON
426              
427             =head1 METHODS
428              
429             =head2 code
430              
431             =over
432              
433             =item Arguments: $code
434              
435             =back
436              
437             Gets/sets the three letter currency code for the current currency object.
438             C<code> dies loudly if C<code> isn't a valid currency code.
439              
440             =head2 convert
441              
442             =over
443              
444             =item Arguments: $code
445              
446             =back
447              
448             Returns a new Data::Currency object containing the converted value.
449              
450             If no C<code> is specified, the current value of C<code> will be used. If the
451             currency you are converting to is the same as the current objects currency
452             code, convert will just return itself.
453              
454             Remember, convert returns another currency object, so you can chain away:
455              
456             my $price = Data::Currency->new(1.25, 'USD');
457             print $price->convert('CAD')->as_string;
458              
459             C<convert> dies if C<code> isn't valid currency code or isn't defined.
460              
461             =head2 converter_class
462              
463             =over
464              
465             =item Arguments: $converter_class
466              
467             =back
468              
469             Gets/sets the converter class to be used when converting currency numbers.
470              
471             Data::Currency->converter_class('MyCurrencyConverter');
472              
473             The converter class can be any class that supports the following method
474             signature:
475              
476             sub convert {
477             my ($self, $price, $from, $to) = @_;
478              
479             return $converted_price;
480             };
481              
482             This method dies if the specified class can not be loaded.
483              
484             =head2 format
485              
486             =over
487              
488             =item Arguments: $options
489              
490             =back
491              
492             Gets/sets the format to be used when C<as_string> is called. See
493             L<Locale::Currency::Format|Locale::Currency::Format> for the available
494             formatting options.
495              
496             =head2 name
497              
498             Returns the currency name for the current objects currency code. If no
499             currency code is set the method will die.
500              
501             =head2 stringify
502              
503             Sames as C<as_string>.
504              
505             =head2 as_string
506              
507             Returns the current objects value as a formatted currency string.
508              
509             =head2 as_float
510              
511             Returns the value formatted as float using decimal places specified by currency
512             code
513              
514             =head2 value
515              
516             Returns the original price value given to C<new>.
517              
518             =head2 get_component_class
519              
520             =over
521              
522             =item Arguments: $name
523              
524             =back
525              
526             Gets the current class for the specified component name.
527              
528             my $class = $self->get_component_class('converter_class');
529              
530             There is no good reason to use this. Use the specific class accessors instead.
531              
532             =head2 set_component_class
533              
534             =over
535              
536             =item Arguments: $name, $value
537              
538             =back
539              
540             Sets the current class for the specified component name.
541              
542             $self->set_component_class('converter_class', 'MyCurrencyConverter');
543              
544             This method will croak if the specified class can not be loaded. There is no
545             good reason to use this. Use the specific class accessors instead.
546              
547             =head1 SEE ALSO
548              
549             L<Locale::Currency>, L<Locale::Currency::Format>,
550             L<Finance::Currency::Convert::WebserviceX>
551              
552             =head1 AUTHOR
553              
554             Christopher H. Laco <claco _at_ chrislaco.com>, Mariano Wahlmann <dichoso _at_ gmail.com>
555              
556             =head1 COPYRIGHT AND LICENSE
557              
558             This software is copyright (c) 2013 by Christopher H. Laco, Mariano Wahlmann.
559              
560             This is free software; you can redistribute it and/or modify it under
561             the same terms as the Perl 5 programming language system itself.
562              
563             =cut