File Coverage

blib/lib/Money/ChangeMaker.pm
Criterion Covered Total %
statement 78 92 84.7
branch 27 42 64.2
condition 9 18 50.0
subroutine 10 12 83.3
pod 6 6 100.0
total 130 170 76.4


line stmt bran cond sub pod time code
1             # Money::ChangeMaker -- Making change.
2              
3             package Money::ChangeMaker;
4              
5             require 5;
6 7     7   71995 use strict;
  7         17  
  7         483  
7 7     7   43 use vars qw($VERSION);
  7         15  
  7         324  
8 7     7   4440 use Money::ChangeMaker::Denomination;
  7         16  
  7         199  
9 7     7   5063 use Money::ChangeMaker::Presets;
  7         17  
  7         7754  
10              
11             $VERSION = "0.3";
12              
13             # These are private class members
14              
15             # This is a reference to the set of predefined monetary sets.
16             # See perldocs in Presets.pm for more information.
17             my($_presets) = Money::ChangeMaker::Presets::_gen_presets_hash();
18              
19             ## Here there be methods
20              
21             # The constructor method. It's very simple.
22             sub new {
23 6     6 1 86 my($proto) = shift;
24 6         15 my($options) = shift;
25 6   33     53 my($class) = ref($proto) || $proto;
26 6         16 my($self) = {};
27 6         19 bless($self, $class);
28 6         50 $self->{_DENOMINATIONS} = $_presets->{USA};
29 6 50 33     37 unless ( defined($options) && (ref($options) eq "HASH") ) {
30 6         14 $options = {};
31             }
32 6 50       31 if (exists($options->{'denominations'})) {
33 0         0 $self->denominations($options->{'denominations'});
34             }
35 6         45 return($self);
36             }
37              
38             # Sets the monetary denominations for making change, if a parameter is passed.
39             # Returns the new value. A denominations value is a reference to an array of
40             # Money::ChangeMaker::Denomination objects.
41             sub denominations {
42 5     5 1 14 my($self) = shift;
43 5         11 my($option) = shift;
44 5 50       21 if(defined($option)) {
45 5 50       20 if (_check_denom($option)) {
46 5         12 $self->{_DENOMINATIONS} = [(sort {$b->value <=> $a->value} @{$option})];
  120         374  
  5         34  
47             }
48             }
49 5         29 return $self->{_DENOMINATIONS};
50             }
51              
52             # The whole point of this module. The entire algorithm is, um... 6 lines.
53             # :) Sometimes I wonder.
54             # Take 2 values -- first the "price" of a thing, and second the amount
55             # of money that was paid.
56             # In list context, returns a list of Denomination objects, representing
57             # the units of currency that would make up the most efficient set of
58             # change given the transation. Larger denominations are at the front of
59             # the list, and denomination objects will be repeated to indicate multiples.
60             # In scalar context, returns the results of passing the above described
61             # output through
62             sub make_change {
63 12     12 1 31 my($self) = shift;
64 12         35 my($price) = shift;
65 12         39 my($tendered) = shift;
66 12 50 33     98 unless (defined($price) && defined($tendered)) {
67 0         0 return _warn("Price and amount tendered must both be defined");
68             }
69 12 50 33     82 if ($price < 1 || $tendered < 1) {
70 0         0 return _warn(
71             "Price and amount tendered must both be numbers, greater than 0."
72             );
73             }
74 12 50       46 if ($tendered < $price) {
75 0         0 return _warn("Insufficient funds tendered to cover price.");
76             }
77 12         30 my @ret;
78 12         22 for my $denom (@{$self->{_DENOMINATIONS}}) {
  12         34  
79 138         399 while ($tendered - $price >= $denom->value) {
80 106         171 push(@ret, $denom);
81 106         259 $tendered -= $denom->value;
82             }
83             }
84 12 100       43 if (wantarray) {
85 6         53 return @ret;
86             }
87             else {
88 6         38 return as_string(@ret);
89             }
90             }
91              
92             # This makes sure that a scalar is a proper reference to a list of
93             # proper Denomination objects.
94             sub _check_denom {
95 5     5   11 my($test) = shift;
96 5 50       25 unless (ref($test) eq 'ARRAY') {
97 0         0 return _warn("The denominations must be a reference to a list.");
98             }
99 5         12 for my $i (0..$#{$test}) {
  5         23  
100 59 50       124 unless (defined($test->[$i])) {
101 0         0 return _warn("Element " . $i + 1 . " is undef in denomination array.");
102             }
103 59 50       225 unless ($test->[$i]->isa("Money::ChangeMaker::Denomination")) {
104 0         0 return _warn(
105             "Element " . $i + 1 .
106             " is not of the proper type in denomination array."
107             );
108             }
109             }
110 5         23 return 1;
111             }
112              
113             # A simple method to print a warning.
114             # I may want to consider a different mechanism for handling non-fatals, so
115             # this may change.
116             sub _warn {
117 0     0   0 warn(shift());
118 0         0 return undef;
119             }
120              
121             # Gets a reference to a list from the set of preset money sets by name.
122             sub get_preset {
123             # Get rid of the first arg if this was called as an object or class method
124 5 50   5 1 45 shift if UNIVERSAL::isa($_[0], __PACKAGE__);
125 5         19 my($preset) = shift;
126 5         39 return $_presets->{$preset};
127             }
128              
129             # Gets a list of available names of preset money sets.
130             sub get_preset_names {
131             # Get rid of the first arg if this was called as an object or class method
132 0 0   0 1 0 shift if UNIVERSAL::isa($_[0], __PACKAGE__);
133 0         0 return (keys %{$_presets});
  0         0  
134             }
135              
136             # Given, as input, the list output of the make_change method, will return a
137             # string with a basic english representation of the data. See the
138             # make_change method for more information on its output.
139             sub as_string {
140             # Get rid of the first arg if this was called as an object or class method
141 7 100   7 1 70 shift if UNIVERSAL::isa($_[0], __PACKAGE__);
142 7         23 my $thisDenom;
143             my $num;
144 0         0 my $ret;
145 7         26 for my $denom (@_) {
146 64 50       226 unless (UNIVERSAL::isa($denom, "Money::ChangeMaker::Denomination")) {
147 0         0 return _warn("All arguments must be of proper Denomination class");
148             }
149 64 100 66     383 if (
      100        
150             defined($thisDenom) && defined($denom) &&
151             $denom->name eq $thisDenom->name
152             ) {
153 25         50 $num++;
154             }
155             else {
156 39 100       96 if (defined($thisDenom)) {
157 32 100       102 if ($ret) {
158 25         46 $ret .= ", ";
159             }
160 32         94 $ret .= "$num ";
161 32 100       79 if ($num > 1) {
162 9         37 $ret .= $thisDenom->plural;
163             }
164             else {
165 23         63 $ret .= $thisDenom->name;
166             }
167             }
168 39         58 $thisDenom = $denom;
169 39         77 $num = 1;
170             }
171             }
172 7 50       34 if ($ret) {
173 7         16 $ret .= ", ";
174             }
175 7         17 $ret .= "$num ";
176 7 100       23 if ($num > 1) {
177 4         22 $ret .= $thisDenom->plural;
178             }
179             else {
180 3         15 $ret .= $thisDenom->name;
181             }
182 7         125 $ret =~ s/(.*), (\d)/$1 and $2/;
183 7         56 return $ret;
184             }
185              
186             1;
187              
188             __END__