File Coverage

blib/lib/Shipment/Temando.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


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