File Coverage

blib/lib/Shipment/Temando.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Shipment::Temando;
2             $Shipment::Temando::VERSION = '0.18';
3 1     1   15420 use strict;
  1         2  
  1         33  
4 1     1   3 use warnings;
  1         1  
  1         22  
5              
6              
7 1     1   468 use Try::Tiny;
  1         2168  
  1         66  
8 1     1   915 use Moose 2.0000;
  0            
  0            
9             use Moose::Util::TypeConstraints;
10             use Shipment::SOAP::WSDL;
11              
12             #$Shipment::SOAP::WSDL::Debug = 1;
13              
14             extends 'Shipment::Base';
15              
16              
17             has 'username' => (
18             is => 'rw',
19             isa => 'Str',
20             );
21              
22             has 'password' => (
23             is => 'rw',
24             isa => 'Str',
25             );
26              
27             has 'client_id' => (
28             is => 'rw',
29             isa => 'Str',
30             );
31              
32              
33             has 'live' => (
34             is => 'rw',
35             isa => 'Bool',
36             default => 0,
37             );
38              
39              
40             has 'class' => (
41             is => 'rw',
42             default => 'General Goods',
43             );
44              
45             has 'subclass' => (
46             is => 'rw',
47             default => 'Household Goods',
48             );
49              
50              
51             has 'request_id' => (
52             is => 'rw',
53             isa => 'Str',
54             );
55              
56              
57             has 'comments' => (
58             is => 'rw',
59             isa => 'Str',
60             );
61              
62              
63             has 'credit_card_type' => (
64             is => 'rw',
65             isa => enum([qw( Visa MasterCard )]),
66             );
67              
68             has 'credit_card_expiry' => (
69             is => 'rw',
70             isa => 'Str',
71             );
72              
73             has 'credit_card_number' => (
74             is => 'rw',
75             isa => 'Str',
76             );
77              
78             has 'credit_card_name' => (
79             is => 'rw',
80             isa => 'Str',
81             );
82              
83              
84             enum 'BillingOptions' => [qw( sender account credit credit_card )];
85              
86             has '+bill_type' => (isa => 'BillingOptions',);
87              
88             my %bill_type_map = (
89             'sender' => 'Account',
90             'account' => 'Account',
91             'credit' => 'Credit',
92             'credit_card' => 'Credit Card',
93             );
94              
95             my %signature_type_map = (
96             'default' => '',
97             'required' => '',
98             'not_required' => undef,
99             'adult' => '',
100             );
101              
102             my %package_type_map = (
103             'custom' => 'Carton',
104             'carton' => 'Carton',
105             'envelope' => 'Document Envelope',
106             'tube' => 'Tube',
107             'box' => 'Box',
108             'pack' => 'Flat Pack',
109             'pallet' => 'Pallet',
110             'cylinder' => 'Cylinder',
111             'letter' => 'Letter',
112             'parcel' => 'Parcel',
113             'bag' => 'Satchel/Bag',
114             'skid' => 'Skid',
115             'unpacked' => 'Unpackaged or N/A',
116             'wheel' => 'Wheel/Tyre',
117             );
118              
119             my %units_type_map = (
120             'lb' => 'Pounds',
121             'oz' => 'Ounces',
122             'kg' => 'Kilograms',
123             'g' => 'Grams',
124             'in' => 'Inches',
125             'ft' => 'Feet',
126             'cm' => 'Centimetres',
127             'm' => 'Metres',
128             );
129              
130              
131             enum 'PackageOptions' => [keys %package_type_map];
132              
133             has '+package_type' => (
134             isa => 'PackageOptions',
135             default => 'custom',
136             );
137              
138              
139             my %printer_type_map = (
140             'pdf' => 'Standard',
141             'thermal' => 'Thermal',
142             'image' => 'Standard',
143             );
144              
145             my %content_type_map = (
146             'application/pdf' => 'pdf',
147             'application/msword' => 'doc',
148             'application/excel' => 'xls',
149             );
150              
151              
152             has '+currency' => (default => 'AUD',);
153              
154              
155             sub _build_services {
156             my $self = shift;
157              
158             use Shipment::Package;
159             use Shipment::Service;
160             use Shipment::Temando::WSDL::Interfaces::quoting_Service::quoting_port;
161              
162             my $interface =
163             Shipment::Temando::WSDL::Interfaces::quoting_Service::quoting_port->new(
164             {live => $self->live,});
165              
166             my $goods_value;
167             my $insured_value;
168              
169             my @pieces;
170             foreach (@{$self->packages}) {
171             push @pieces,
172             { class => $self->class,
173             subclass => $self->subclass,
174             packaging => ($_->type)
175             ? $package_type_map{$_->type}
176             : $package_type_map{$self->package_type},
177             qualifierFreightGeneralFragile => ($_->fragile) ? 'Y' : 'N',
178             distanceMeasurementType => $units_type_map{$self->dim_unit},
179             weightMeasurementType => $units_type_map{$self->weight_unit},
180             length => $_->length,
181             width => $_->width,
182             height => $_->height,
183             weight => $_->weight,
184             quantity => 1,
185             description => $_->notes,
186             };
187             $goods_value += $_->goods_value->value;
188             $insured_value += $_->insured_value->value;
189             }
190             $goods_value ||= 1;
191              
192             my $anytime;
193             $anytime = {
194             readyDate => $self->pickup_date->ymd,
195             readyTime => $self->pickup_date->strftime('%p'),
196             }
197             if $self->pickup_date;
198              
199             my $response;
200             my %services;
201              
202             try {
203              
204             $response = $interface->getQuotesByRequest(
205             { anythings => {anything => \@pieces,},
206             anywhere => {
207             itemNature => 'Domestic'
208             , ## Temando currently only supports domestic shipments
209             itemMethod =>
210             'Door to Door', ## Temando also supports 'Depot to Depot'
211             originCountry => $self->from_address->country_code,
212             originCode => $self->from_address->postal_code,
213             originSuburb => $self->from_address->city,
214             destinationCountry => $self->to_address->country_code,
215             destinationCode => $self->to_address->postal_code,
216             destinationSuburb => $self->to_address->city,
217             destinationIs =>
218             ($self->from_address->address_type eq 'residential')
219             ? 'Residence'
220             : 'Business',
221             originIs =>
222             ($self->from_address->address_type eq 'residential')
223             ? 'Residence'
224             : 'Business',
225             },
226             anytime => $anytime,
227             general => {goodsValue => $goods_value,},
228             clientId => $self->client_id,
229             },
230             { UsernameToken => {
231             Username => $self->username,
232             Password => $self->password,
233             },
234             },
235             );
236              
237             #warn $response;
238              
239             foreach my $quote (@{$response->get_quote}) {
240              
241             #warn $quote;
242             my $id = $quote->get_carrier->get_id->get_value
243             . $quote->get_deliveryMethod->get_value;
244             $services{$id} = Shipment::Service->new(
245             id => $id,
246             name => $quote->get_carrier->get_companyName->get_value . " - "
247             . $quote->get_deliveryMethod->get_value,
248             etd => $quote->get_etaTo->get_value,
249             pickup_etd => $quote->get_etaFrom->get_value,
250             cost => Data::Currency->new(
251             $quote->get_totalPrice->get_value,
252             $quote->get_currency->get_value
253             ),
254             base_cost => Data::Currency->new(
255             $quote->get_basePrice->get_value,
256             $quote->get_currency->get_value
257             ),
258             tax => Data::Currency->new(
259             $quote->get_tax->get_value,
260             $quote->get_currency->get_value
261             ),
262             carrier_id => $quote->get_carrier->get_id->get_value,
263             carrier_name =>
264             $quote->get_carrier->get_companyName->get_value,
265             service_name => $quote->get_deliveryMethod->get_value,
266             guaranteed => ($quote->get_guaranteedEta->get_value eq 'Y')
267             ? 1
268             : 0,
269             );
270              
271             my $adjustments = 0;
272             if ($quote->get_adjustments->get_adjustment) {
273             foreach
274             my $adjustment (@{$quote->get_adjustments->get_adjustment})
275             {
276             if ( $self->bill_type eq 'credit_card'
277             && $adjustment->get_description->get_value eq
278             'Credit Card Payment Adjustment')
279             {
280             $adjustments += $adjustment->get_amount->get_value
281             + $adjustment->get_tax->get_value;
282             }
283             if ( $self->bill_type eq 'credit'
284             && $adjustment->get_description->get_value eq
285             'Credit Payment Adjustment')
286             {
287             $adjustments += $adjustment->get_amount->get_value
288             + $adjustment->get_tax->get_value;
289             }
290             }
291             }
292              
293             my $extra_charges = 0;
294             if ($quote->get_extras->get_extra) {
295             foreach my $extra (@{$quote->get_extras->get_extra}) {
296             $services{$id}->extras->{$extra->get_summary->get_value} =
297             Shipment::Service->new(
298             id => $extra->get_summary->get_value,
299             name => $extra->get_details->get_value,
300             cost => Data::Currency->new(
301             $extra->get_totalPrice->get_value,
302             $quote->get_currency->get_value
303             ),
304             base_cost => Data::Currency->new(
305             $extra->get_basePrice->get_value,
306             $quote->get_currency->get_value
307             ),
308             tax => Data::Currency->new(
309             $extra->get_tax->get_value,
310             $quote->get_currency->get_value
311             ),
312             );
313             if ( $insured_value
314             && $extra->get_summary->get_value eq 'Insurance')
315             {
316             $extra_charges += $extra->get_totalPrice->get_value;
317             }
318             if ( $self->carbon_offset
319             && $extra->get_summary->get_value eq 'Carbon Offset')
320             {
321             $extra_charges += $extra->get_totalPrice->get_value;
322             }
323              
324             if ($quote->get_adjustments->get_adjustment) {
325             foreach my $adjustment (
326             @{$quote->get_adjustments->get_adjustment})
327             {
328             if ( $self->bill_type eq 'credit_card'
329             && $adjustment->get_description->get_value eq
330             'Credit Card Payment Adjustment')
331             {
332             $adjustments
333             += $adjustment->get_amount->get_value
334             + $adjustment->get_tax->get_value;
335             }
336             if ( $self->bill_type eq 'credit'
337             && $adjustment->get_description->get_value eq
338             'Credit Payment Adjustment')
339             {
340             $adjustments
341             += $adjustment->get_amount->get_value
342             + $adjustment->get_tax->get_value;
343             }
344             }
345             }
346             }
347             }
348             $services{$id}->extra_charges(
349             Data::Currency->new(
350             $extra_charges, $quote->get_currency->get_value
351             )
352             );
353             $services{$id}->adjustments(
354             Data::Currency->new(
355             $adjustments, $quote->get_currency->get_value
356             )
357             );
358              
359             my $type;
360             $type = 'ground' if $quote->get_usingGeneralRoad->get_value eq 'Y';
361             $type = 'express'
362             if $quote->get_usingExpressRoad->get_value eq 'Y';
363             $type = 'priority'
364             if $quote->get_usingExpressAir->get_value eq 'Y';
365              
366             if ($services{$type}) {
367             $services{$type} = $services{$id}
368             if $services{$type}->cost > $services{$id}->cost;
369             }
370             else {
371             $services{$type} = $services{$id};
372             }
373             }
374              
375             }
376             catch {
377             warn $_;
378             warn $response->get_faultstring;
379             $self->error($response->get_faultcode->get_value . ":"
380             . $response->get_faultstring->get_value);
381             };
382              
383             \%services;
384             }
385              
386              
387             sub rate {
388             my ($self, $service_id) = @_;
389              
390             try {
391             $service_id = $self->services->{$service_id}->id;
392             }
393             catch {
394             warn $_;
395             warn "service ($service_id) not available";
396             $self->error("service ($service_id) not available");
397             $service_id = '';
398             };
399             return unless $service_id;
400              
401             $self->service($self->services->{$service_id});
402              
403             }
404              
405              
406             sub ship {
407             my ($self, $service_id) = @_;
408              
409             try {
410             $self->rate($service_id);
411             $service_id = $self->service->id;
412             }
413             catch {
414             warn $_;
415             warn "service ($service_id) not available";
416             $self->error("service ($service_id) not available");
417             $service_id = '';
418             };
419             return unless $service_id;
420              
421             use Shipment::Package;
422             use Shipment::Service;
423             use Shipment::Temando::WSDL::Interfaces::quoting_Service::quoting_port;
424              
425             my $interface =
426             Shipment::Temando::WSDL::Interfaces::quoting_Service::quoting_port->new(
427             {live => $self->live,});
428              
429             my $goods_value;
430             my $insured_value;
431              
432             my @pieces;
433             foreach (@{$self->packages}) {
434             push @pieces,
435             { class => $self->class,
436             subclass => $self->subclass,
437             packaging => ($_->type)
438             ? $package_type_map{$_->type}
439             : $package_type_map{$self->package_type},
440             qualifierFreightGeneralFragile => ($_->fragile) ? 'Y' : 'N',
441             distanceMeasurementType => $units_type_map{$self->dim_unit},
442             weightMeasurementType => $units_type_map{$self->weight_unit},
443             length => $_->length,
444             width => $_->width,
445             height => $_->height,
446             weight => $_->weight,
447             quantity => 1,
448             description => $_->notes,
449             };
450             $goods_value += $_->goods_value->value;
451             $insured_value += $_->insured_value->value;
452             }
453             $goods_value ||= 1;
454              
455             my @extras;
456             if ($insured_value && $self->service->extras->{'Insurance'}) {
457             my $insurance = $self->service->extras->{'Insurance'};
458             push @extras,
459             { summary => $insurance->id,
460             details => $insurance->name,
461             totalPrice => $insurance->cost->value,
462             basePrice => $insurance->base_cost->value,
463             tax => $insurance->tax->value,
464             };
465             }
466              
467             if ($self->carbon_offset && $self->service->extras->{'Carbon Offset'}) {
468             my $carbon_offset = $self->service->extras->{'Carbon Offset'};
469             push @extras,
470             { summary => $carbon_offset->id,
471             details => $carbon_offset->name,
472             totalPrice => $carbon_offset->cost->value,
473             basePrice => $carbon_offset->base_cost->value,
474             tax => $carbon_offset->tax->value,
475             };
476             }
477              
478             my $anytime;
479             $anytime = {
480             readyDate => $self->pickup_date->ymd,
481             readyTime => $self->pickup_date->strftime('%p'),
482             }
483             if $self->pickup_date;
484              
485             my $payment;
486             $payment->{paymentType} = $bill_type_map{$self->bill_type};
487             if ($self->bill_type eq 'credit_card') {
488             $payment->{cardType} = $self->credit_card_type;
489             $payment->{cardExpiryDate} = $self->credit_card_expiry;
490             $payment->{cardNumber} = $self->credit_card_number;
491             $payment->{cardName} = $self->credit_card_name;
492             }
493              
494             my $response;
495             my %services;
496              
497             try {
498              
499             $response = $interface->makeBookingByRequest(
500             { anythings => {anything => \@pieces,},
501             anywhere => {
502             itemNature => 'Domestic'
503             , ## Temando currently only supports domestic shipments
504             itemMethod =>
505             'Door to Door', ## Temando also supports 'Depot to Depot'
506             originCountry => $self->from_address->country_code,
507             originCode => $self->from_address->postal_code,
508             originSuburb => $self->from_address->city,
509             destinationCountry => $self->to_address->country_code,
510             destinationCode => $self->to_address->postal_code,
511             destinationSuburb => $self->to_address->city,
512             destinationIs =>
513             ($self->from_address->address_type eq 'residential')
514             ? 'Residence'
515             : 'Business',
516             originIs =>
517             ($self->from_address->address_type eq 'residential')
518             ? 'Residence'
519             : 'Business',
520             },
521             anytime => $anytime,
522             general => {goodsValue => $goods_value,},
523             origin => {
524             contactName => $self->from_address->contact,
525             companyName => $self->from_address->company,
526             street => $self->from_address->address1 . " "
527             . $self->from_address->address2,
528             suburb => $self->from_address->city,
529             state => $self->from_address->province,
530             code => $self->from_address->postal_code,
531             country => $self->from_address->country_code,
532             phone1 => $self->from_address->phone || undef,
533             email => $self->from_address->email || undef,
534             },
535             destination => {
536             contactName => $self->to_address->contact,
537             companyName => $self->to_address->company,
538             street => $self->to_address->address1 . " "
539             . $self->to_address->address2,
540             suburb => $self->to_address->city,
541             state => $self->to_address->province,
542             code => $self->to_address->postal_code,
543             country => $self->to_address->country_code,
544             phone1 => $self->to_address->phone || undef,
545             email => $self->to_address->email || undef,
546             },
547             quote => {
548             totalPrice => $self->service->cost->value,
549             basePrice => $self->service->base_cost->value,
550             tax => $self->service->tax->value,
551             currency => $self->service->cost->code,
552             deliveryMethod => $self->service->service_name,
553             etaFrom => $self->service->pickup_etd,
554             etaTo => $self->service->etd,
555             guaranteedEta => ($self->service->guaranteed) ? 'Y' : 'N',
556             carrierId => $self->service->carrier_id,
557             extras => {extra => \@extras,},
558             },
559             payment => $payment,
560             instructions => $self->special_instructions,
561             reference => join(" ", $self->all_references),
562             comments => $self->comments,
563             labelPrinterType => $printer_type_map{$self->printer_type},
564             clientId => $self->client_id,
565             },
566             { UsernameToken => {
567             Username => $self->username,
568             Password => $self->password,
569             },
570             },
571             );
572              
573             #warn $response;
574              
575             use Shipment::Label;
576             use MIME::Base64;
577              
578             my $tracking_id =
579             $response->get_consignmentNumber->get_value
580             || $response->get_bookingNumber->get_value
581             || $response->get_requestId->get_value;
582             $self->tracking_id($tracking_id);
583             $self->request_id($response->get_requestId->get_value);
584              
585             my $data = decode_base64($response->get_labelDocument->get_value);
586             $self->documents(
587             Shipment::Label->new(
588             { tracking_id => $tracking_id,
589             content_type =>
590             $response->get_labelDocumentType->get_value,
591             data => $data,
592             file_name => $tracking_id
593             . '-labels.'
594             . $content_type_map{$response->get_labelDocumentType
595             ->get_value},
596             },
597             )
598             );
599              
600             $data = decode_base64($response->get_consignmentDocument->get_value);
601             $self->manifest(
602             Shipment::Label->new(
603             { content_type =>
604             $response->get_consignmentDocumentType->get_value,
605             data => $data,
606             file_name => $tracking_id
607             . '-manifest.'
608             . $content_type_map{$response
609             ->get_consignmentDocumentType->get_value},
610             },
611             )
612             );
613              
614             foreach (@{$self->packages}) {
615             $_->tracking_id($tracking_id);
616              
617             $data = decode_base64($response->get_labelDocument->get_value);
618              
619             $_->label(
620             Shipment::Label->new(
621             { tracking_id => $tracking_id,
622             content_type =>
623             $response->get_labelDocumentType->get_value,
624             data => $data,
625             file_name => $tracking_id . '.'
626             . $content_type_map{$response->get_labelDocumentType
627             ->get_value},
628             },
629             )
630             );
631             }
632              
633             }
634             catch {
635             warn $_;
636             warn $response->get_faultstring;
637             $self->error($response->get_faultcode->get_value . ":"
638             . $response->get_faultstring->get_value);
639             };
640              
641             }
642              
643              
644             sub cancel {
645             my $self = shift;
646              
647             if (!$self->request_id) {
648             $self->error('no request id provided');
649             return;
650             }
651              
652             use Shipment::Temando::WSDL::Interfaces::quoting_Service::quoting_port;
653              
654             my $interface =
655             Shipment::Temando::WSDL::Interfaces::quoting_Service::quoting_port->new(
656             {live => $self->live,});
657              
658             my $response = $interface->cancelRequest(
659             {requestId => $self->request_id,},
660             { UsernameToken => {
661             Username => $self->username,
662             Password => $self->password,
663             },
664             },
665             );
666              
667             #warn $response;
668              
669             my $error;
670             try {
671             $error = $response->get_faultstring;
672             }
673             catch {};
674              
675             if ($error) {
676             warn $response->get_faultstring;
677             $self->error($response->get_faultcode->get_value . ":"
678             . $response->get_faultstring->get_value);
679             return;
680             }
681             else {
682             return 'Cancelled';
683             }
684              
685             }
686              
687              
688             1;
689              
690             __END__
691              
692             =pod
693              
694             =encoding UTF-8
695              
696             =head1 NAME
697              
698             Shipment::Temando
699              
700             =head1 VERSION
701              
702             version 0.18
703              
704             =head1 SYNOPSIS
705              
706             use Shipment::Temando;
707             use Shipment::Address;
708             use Shipment::Package;
709              
710             my $shipment = Shipment::Temando->new(
711             from_address => Shipment::Address->new( ... ),
712             to_address => Shipment::Address->new( ... ),
713             packages => [ Shipment::Pacjage->new( ... ), ],
714             );
715              
716             foreach my $service ( $shipment->all_services ) {
717             print $service->id . " (" . $service->cost . ")\n";
718             }
719              
720             $shipment->rate( 'Service Name' );
721             print $shipment->service->cost . "\n";
722              
723             $shipment->ship( $service->id );
724             $shipment->get_package(0)->label->save;
725              
726             =head1 NAME
727              
728             Shipment::Temando - Interface to Temando Shipping Web Services
729              
730             =head1 ABOUT
731              
732             This class provides an interface to the Temando Web Services API. You must sign up for an API account with Temando in order to make use of ths module.
733              
734             http://www.temando.com/affiliateRegistration2.html
735              
736             It is an extension of L<Shipment::Base>
737              
738             It makes extensive use of SOAP::WSDL in order to create/decode xml requests and responses. The Shipment::Temando::WSDL interface was created primarily using the wsdl2perl.pl script from SOAP::WSDL.
739              
740             =head1 Class Attributes
741              
742             =head2 user, password, client_id
743              
744             Credentials required to access the Temando API.
745              
746             =head2 live
747              
748             This determines whether you will use the Temando training web service (for development) or the production web service (live)
749              
750             =head2 class, subclass
751              
752             These define the type of shipment.
753              
754             Currently the default (and only supported) options are class="General Goods" and subclass="Household Goods". This should be sane defaults for most shipments.
755              
756             Temando supports many other options here including Freight (pallets, containers, etc) and Vehicles.
757              
758             =head2 request_id
759              
760             The Temando request id
761              
762             This will be set upon a successful call to "ship"
763              
764             It can also be set before a call to "cancel"
765              
766             type: String
767              
768             =head2 comments
769              
770             Additional comments about the shipment
771              
772             type: String
773              
774             =head2 credit_card_type, credit_card_expiry, credit_card_number, credit_card_name
775              
776             Temando accepts payment by credit card
777              
778             credit_card_type can be one of 'Visa' or 'MasterCard'
779              
780             credit_card_expiry must be in the format '02-2013'
781              
782             =head2 Shipment::Base type maps
783              
784             Shipment::Base provides abstract types which need to be mapped to Temando (i.e. package_type of "envelope" maps to Temando "Document Envelope")
785              
786             =head2 custom package types
787              
788             Temando provides package types in addition to the defaults in Shipment::Base
789              
790             =head2 printer types
791              
792             Temando does not offer true thermal printing, all labels are provided as documents (pdf, doc, or xls), thermal labels are simply a 4x6 documents.
793              
794             =head2 default currency
795              
796             The default currency is AUD
797              
798             =head1 Class Methods
799              
800             =head2 _build_services
801              
802             This calls getQuotesByRequest from the Temando API
803              
804             Each Quote that is returned is added to services
805              
806             The following service mapping is used:
807             * ground => cheapest where <usingGeneralRoad>
808             * express => cheapest where <usingExpressRoad>
809             * priority => cheapest where <usingExpressAir>
810              
811             =head2 rate
812              
813             This method sets $self->service to $self->services{$service_id}
814              
815             =head2 ship
816              
817             This method calls makeBookingByRequest from the Temando API
818              
819             All labels are available in $self->documents
820              
821             The consignment document is available in $self->manifest
822              
823             =head2 cancel
824              
825             This method calls cancelRequest from the Temando API
826              
827             It uses $self->request_id to identify the request to be cancelled
828              
829             returns "Cancelled" if successful,
830              
831             =head1 AUTHOR
832              
833             Andrew Baerg @ <andrew at pullingshots dot ca>
834              
835             http://pullingshots.ca/
836              
837             =head1 ACKNOWLEDGEMENTS
838              
839             SiteSuite Australasia (http://www.sitesuite.com.au/) commissioned and paid for the development of this module
840              
841             =head1 BUGS
842              
843             Please contact me directly.
844              
845             =head1 COPYRIGHT
846              
847             Copyright (C) 2012 Andrew J Baerg, All Rights Reserved
848              
849             =head1 NO WARRANTY
850              
851             Absolutely, positively NO WARRANTY, neither express or implied, is
852             offered with this software. You use this software at your own risk. In
853             case of loss, no person or entity owes you anything whatsoever. You
854             have been warned.
855              
856             =head1 LICENSE
857              
858             This program is free software; you can redistribute it and/or modify it
859             under the same terms as Perl itself.
860              
861             =head1 AUTHOR
862              
863             Andrew Baerg <baergaj@cpan.org>
864              
865             =head1 COPYRIGHT AND LICENSE
866              
867             This software is copyright (c) 2013 by Andrew Baerg.
868              
869             This is free software; you can redistribute it and/or modify it under
870             the same terms as the Perl 5 programming language system itself.
871              
872             =cut