File Coverage

blib/lib/Parse/SAMGov/Entity.pm
Criterion Covered Total %
statement 202 224 90.1
branch 117 156 75.0
condition 13 14 92.8
subroutine 17 17 100.0
pod 1 2 50.0
total 350 413 84.7


line stmt bran cond sub pod time code
1             package Parse::SAMGov::Entity;
2             $Parse::SAMGov::Entity::VERSION = '0.106';
3 3     3   735 use strict;
  3         4  
  3         76  
4 3     3   10 use warnings;
  3         3  
  3         73  
5 3     3   51 use 5.010;
  3         7  
6 3     3   1057 use Data::Dumper;
  3         11081  
  3         197  
7 3     3   1383 use Parse::SAMGov::Mo;
  3         6  
  3         13  
8 3     3   1561 use URI;
  3         9337  
  3         89  
9 3     3   2313 use DateTime;
  3         264808  
  3         115  
10 3     3   1786 use DateTime::Format::Strptime;
  3         34171  
  3         16  
11 3     3   1720 use Parse::SAMGov::Entity::Address;
  3         6  
  3         77  
12 3     3   1076 use Parse::SAMGov::Entity::PointOfContact;
  3         6  
  3         94  
13 3     3   13 use Carp;
  3         3  
  3         1614  
14              
15             #ABSTRACT: Object to denote each Entity in SAM
16              
17             use overload fallback => 1,
18             '""' => sub {
19 6     6   8359 my $self = $_[0];
20 6         7 my $str = '';
21 6 100       14 $str .= $self->name if $self->name;
22 6 100       14 $str .= ' dba ' . $self->dba_name if $self->dba_name;
23 6 50       15 $str .= "\nDUNS: " . $self->DUNS if $self->DUNS;
24 6 50       13 $str .= '+' . $self->DUNSplus4 if $self->DUNSplus4 ne '0000';
25 6 50       12 $str .= "\nCAGE: " . $self->CAGE if $self->CAGE;
26 6 100       12 $str .= "\nDODAAC: " . $self->DODAAC if $self->DODAAC;
27 6 50       11 $str .= "\nStatus: " . $self->extract_code if $self->extract_code;
28 6 100       11 $str .= "\nUpdated: Yes" if $self->updated;
29 6 50       10 $str .= "\nRegistration Purpose: " . $self->regn_purpose if $self->regn_purpose;
30 6 100       10 $str .= "\nRegistration Date: " . $self->regn_date->ymd('-') if $self->regn_date;
31 6 100       43 $str .= "\nExpiry Date: " . $self->expiry_date->ymd('-') if $self->expiry_date;
32 6 100       39 $str .= "\nLast Update Date: " . $self->lastupdate_date->ymd('-') if $self->lastupdate_date;
33 6 100       35 $str .= "\nActivation Date: " . $self->activation_date->ymd('-') if $self->activation_date;
34 6 50       37 $str .= "\nCompany Division: " . $self->company_division if $self->company_division;
35 6 50       14 $str .= "\nDivision No.: " . $self->division_no if $self->division_no;
36 6 100       28 $str .= "\nPhysical Address: " . $self->physical_address if $self->physical_address;
37 6 100       13 $str .= "\nBusiness Start Date: " . $self->start_date->ymd('-') if $self->start_date;
38 6 100       38 $str .= sprintf "\nFiscal Year End: %02d-%02d", $self->fiscalyear_date->month, $self->fiscalyear_date->day
39             if $self->fiscalyear_date;
40 6 100       28 $str .= "\nCorporate URL: " . $self->url if $self->url;
41 6         31 $str .= "\nBusiness Types: [" . join(',', @{$self->biztype}) . "]";
  6         10  
42 6         7 $str .= "\nNAICS Codes: [" . join(',', keys %{$self->NAICS}) . "]";
  6         11  
43 6 100       12 $str .= "\nSmall Business: " . ($self->is_smallbiz() ? 'Yes' : 'No');
44             {
45 6         7 local $Data::Dumper::Indent = 1;
  6         8  
46 6         7 local $Data::Dumper::Terse = 1;
47 6         11 $str .= "\nNAICS Details: " . Dumper($self->NAICS);
48             }
49 6         403 $str .= "\nPSC Codes: [" . join(',', @{$self->PSC}) . "]";
  6         37  
50 6 100       11 $str .= "\nMailing Address: " . $self->mailing_address if $self->mailing_address;
51 6 100       15 $str .= "\nGovt Business POC: " . $self->POC_gov if $self->POC_gov;
52 6 100       13 $str .= "\nGovt Business POC (alternate): " . $self->POC_gov_alt if $self->POC_gov_alt;
53 6 100       11 $str .= "\nPast Performance POC: " . $self->POC_pastperf if $self->POC_pastperf;
54 6 100       12 $str .= "\nPast Performance POC (alternate): " . $self->POC_pastperf_alt if $self->POC_pastperf_alt;
55 6 100       11 $str .= "\nElectronic POC: " . $self->POC_elec if $self->POC_elec;
56 6 100       11 $str .= "\nElectronic POC (alternate): " . $self->POC_elec_alt if $self->POC_elec_alt;
57 6 50       13 $str .= "\nDelinquent Federal Debt: " . ($self->delinquent_fed_debt ? 'Yes' : 'No');
58 6 50       10 $str .= "\nExclusion Status: " . $self->exclusion_status if $self->exclusion_status;
59             {
60 6         7 local $Data::Dumper::Indent = 0;
61 6         7 local $Data::Dumper::Terse = 1;
62 6         10 $str .= "\nSBA Business Type: " . Dumper($self->SBA);
63             }
64             {
65 6         6 local $Data::Dumper::Indent = 0;
  6         212  
  6         6  
66 6         6 local $Data::Dumper::Terse = 1;
67 6         13 $str .= "\nDisaster Response Type: " . Dumper($self->disaster_response);
68             }
69 6 100       159 $str .= "\nIs Private Listing: " . ($self->is_private ? 'Yes' : 'No');
70 6         39 return $str;
71 3     3   13 };
  3         4  
  3         36  
72              
73              
74             has 'DUNS';
75             has DUNSplus4 => default => sub { '0000' };
76             has 'CAGE';
77             has 'DODAAC';
78              
79              
80             has 'extract_code';
81             has 'updated';
82             has 'regn_purpose' => coerce => sub {
83             my $p = $_[0];
84             return 'Federal Assistance Awards' if $p eq 'Z1';
85             return 'All Awards' if $p eq 'Z2';
86             return 'IGT-Only' if $p eq 'Z3';
87             return 'Federal Assistance Awards & IGT' if $p eq 'Z4';
88             return 'All Awards & IGT' if $p eq 'Z5';
89             };
90              
91             sub _parse_yyyymmdd {
92 78 50   78   158 if (@_) {
93 78         80 my $d = shift;
94 78 100       142 if (length($d) == 4) {
95 10         35 my $y = DateTime->now->year;
96 10         1893 $d = "$y$d";
97             }
98 78         89 state $Strp =
99             DateTime::Format::Strptime->new(pattern => '%Y%m%d',
100             time_zone => 'America/New_York',);
101 78         27025 return $Strp->parse_datetime($d);
102             }
103 0         0 return;
104             }
105              
106              
107             has 'regn_date' => coerce => sub { _parse_yyyymmdd $_[0] };
108             has 'expiry_date' => coerce => sub { _parse_yyyymmdd $_[0] };
109             has 'lastupdate_date' => coerce => sub { _parse_yyyymmdd $_[0] };
110             has 'activation_date' => coerce => sub { _parse_yyyymmdd $_[0] };
111              
112              
113             has 'name';
114             has 'dba_name';
115             has 'company_division';
116             has 'division_no';
117             has 'physical_address' => default => sub { return Parse::SAMGov::Entity::Address->new; };
118              
119              
120             has 'start_date' => coerce => sub { _parse_yyyymmdd $_[0] };
121             has 'fiscalyear_date' => coerce => sub { _parse_yyyymmdd $_[0] };
122             has 'url' => coerce => sub { URI->new($_[0]) };
123             has 'entity_structure';
124              
125              
126             has 'incorporation_state';
127             has 'incorporation_country';
128              
129              
130              
131             has 'biztype' => default => sub { [] };
132             has 'NAICS' => default => sub { {} };
133             has 'PSC' => default => sub { [] };
134             has 'creditcard';
135             has 'correspondence_type';
136             has 'mailing_address' => default => sub { return Parse::SAMGov::Entity::Address->new; };
137             has 'POC_gov' => default => sub { return
138             Parse::SAMGov::Entity::PointOfContact->new; };
139             has 'POC_gov_alt' => default => sub {
140             Parse::SAMGov::Entity::PointOfContact->new; };
141             has 'POC_pastperf' => default => sub {
142             Parse::SAMGov::Entity::PointOfContact->new; };
143             has 'POC_pastperf_alt' => default => sub {
144             Parse::SAMGov::Entity::PointOfContact->new; };
145             has 'POC_elec' => default => sub {
146             Parse::SAMGov::Entity::PointOfContact->new; };
147             has 'POC_elec_alt' => default => sub {
148             Parse::SAMGov::Entity::PointOfContact->new; };
149             has 'delinquent_fed_debt';
150             has 'exclusion_status';
151             has 'is_private';
152             has 'disaster_response' => default => sub { {} };
153             has 'SBA' => default => sub { {} };
154              
155             has 'SBA_descriptions' => default => sub {
156             {
157             A4 => 'SBA Certified Small Disadvantaged Business',
158             A6 => 'SBA Certified 8A Program Participant',
159             JT => 'SBA Certified 8A Joint Venture',
160             XX => 'SBA Certified HUBZone Firm',
161             }
162             };
163              
164             sub is_smallbiz {
165 6     6 1 7 my $self = shift;
166 6         6 my $res = 0;
167 6         5 foreach my $k (keys %{$self->NAICS}) {
  6         11  
168 6 100       11 $res = 1 if $self->NAICS->{$k}->{small_biz};
169 6 50       11 $res = 1 if $self->NAICS->{$k}->{exception}->{small_biz};
170 6 100       13 last if $res;
171             }
172 6         18 return $res;
173             }
174              
175             sub _trim {
176             # from Mojo::Util::trim
177 540     540   464 my $s = shift;
178 540         601 $s =~ s/^\s+//g;
179 540         569 $s =~ s/\s+$//g;
180 540         1416 return $s;
181             }
182              
183             sub load {
184 12     12 0 18 my $self = shift;
185 12 50       27 return unless (scalar(@_) == 150);
186 12         33 $self->DUNS(shift);
187 12   50     73 $self->DUNSplus4(shift || '0000');
188 12         33 $self->CAGE(shift);
189 12         29 $self->DODAAC(shift);
190 12         35 $self->updated(0);
191 12         13 my $code = shift;
192 12 100       71 if ($code =~ /A|2|3/x) {
    50          
193 10         22 $self->extract_code('active');
194 10 100       32 $self->updated(1) if $code eq '3';
195             } elsif ($code =~ /E|1|4/x) {
196 2         8 $self->extract_code('expired');
197 2 50       7 $self->updated(1) if $code eq '1';
198             }
199 12         30 $self->regn_purpose(shift);
200 12         30 $self->regn_date(shift);
201 12         40 $self->expiry_date(shift);
202 12         38 $self->lastupdate_date(shift);
203 12         41 $self->activation_date(shift);
204 12         28 $self->name(_trim(shift));
205 12         26 $self->dba_name(_trim(shift));
206 12         23 $self->company_division(_trim(shift));
207 12         25 $self->division_no(_trim(shift));
208 12         36 my $paddr = Parse::SAMGov::Entity::Address->new(
209             # the order of shifting matters
210             address => _trim(join(' ', shift, shift)),
211             city => shift,
212             state => shift,
213             zip => sprintf("%s-%s", shift, shift),
214             country => shift,
215             district => shift,
216             );
217 12         32 $self->physical_address($paddr);
218 12         117 $self->start_date(shift);
219 12         51 $self->fiscalyear_date(shift);
220 12         28 $self->url(_trim(shift));
221 12         35 $self->entity_structure(shift);
222 12         29 $self->incorporation_state(shift);
223 12         24 $self->incorporation_country(shift);
224 12   100     20 my $count = int(_trim(shift) || 0);
225 12 100       28 if ($count > 0) {
226 10         31 my @biztypes = grep { length($_) > 0 } split /~/, shift;
  34         56  
227 10         35 $self->biztype([@biztypes]);
228             } else {
229 2         3 shift; # ignore
230             }
231 12         21 my $pnaics = _trim(shift);
232 12         33 $self->NAICS->{$pnaics} = {};
233 12 100 100     22 $count = int(_trim(shift) || 0) + (length($pnaics) ? 1 : 0);
234 12 100       23 if ($count > 0) {
235 10         27 my @naics = grep { length($_) > 0 } split /~/, shift;
  16         26  
236 10         21 foreach my $c (@naics) {
237 16 50       69 if ($c =~ /(\d+)(Y|N|E)/) {
238 16 100       35 $self->NAICS->{$1} = {} unless ref $self->NAICS->{$1} eq 'HASH';
239 16 100       2289 $self->NAICS->{$1}->{is_primary} = 1 if $pnaics eq $1;
240 16 100       46 $self->NAICS->{$1}->{small_biz} = 1 if $2 eq 'Y';
241 16 100       38 $self->NAICS->{$1}->{small_biz} = 0 if $2 eq 'N';
242 16 100       47 $self->NAICS->{$1}->{exception} = {} if $2 eq 'E';
243             }
244             }
245             } else {
246 2         3 shift; # ignore
247             }
248 12   100     22 $count = int(_trim(shift) || 0);
249 12 100       28 if ($count > 0) {
250 2         7 my @psc = grep { length ($_) > 0 } split /~/, shift;
  2         8  
251 2         10 $self->PSC([@psc]);
252             } else {
253 10         10 shift; # ignore
254             }
255 12 100       67 $self->creditcard((shift eq 'Y') ? 1 : 0);
256 12         17 $code = shift; # re-use variable
257 12 50       48 $self->correspondence_type('mail') if $code eq 'M';
258 12 50       19 $self->correspondence_type('fax') if $code eq 'F';
259 12 50       24 $self->correspondence_type('email') if $code eq 'E';
260 12         38 my $maddr = Parse::SAMGov::Entity::Address->new(
261             # the order of shifting matters
262             address => _trim(join(' ', shift, shift)),
263             city => shift,
264             zip => sprintf("%s-%s", shift, shift),
265             country => shift,
266             state => shift,
267             );
268 12         31 $self->mailing_address($maddr);
269 12         25 for my $i (0..5) {
270 72         106 my $poc = Parse::SAMGov::Entity::PointOfContact->new(
271             first => _trim(shift),
272             middle => _trim(shift),
273             last => _trim(shift),
274             title => _trim(shift),
275             address => _trim(join(' ', shift, shift)),
276             city => shift,
277             zip => sprintf("%s-%s", shift, shift),
278             country => shift,
279             state => shift,
280             phone => shift,
281             phone_ext => shift,
282             phone_nonUS => shift,
283             fax => shift,
284             email => shift,
285             );
286 72 100       177 $self->POC_gov($poc) if $i == 0;
287 72 100       116 $self->POC_gov_alt($poc) if $i == 1;
288 72 100       123 $self->POC_pastperf($poc) if $i == 2;
289 72 100       124 $self->POC_pastperf_alt($poc) if $i == 3;
290 72 100       120 $self->POC_elec($poc) if $i == 4;
291 72 100       140 $self->POC_elec_alt($poc) if $i == 5;
292             }
293 12   100     21 $count = int(_trim(shift) || 0);
294 12 100       28 if ($count > 0) {
295 2         5 my @naics = grep { length($_) > 0 } split /~/, shift;
  4         8  
296 2         4 foreach my $c (@naics) {
297 4 50       14 if ($c =~ /(\d+)([YN ]*)/) {
298 4         9 my @es = split //, $2;
299 4 50       9 if (@es) {
300 4 50       7 $self->NAICS->{$1}->{exception} = {} unless ref $self->NAICS->{$1}->{exception} eq 'HASH';
301 4 50       12 $self->NAICS->{$1}->{exception}->{small_biz} = 1 if $es[0] eq 'Y';
302 4 50       12 $self->NAICS->{$1}->{exception}->{small_biz} = 0 if $es[0] eq 'N';
303             }
304             }
305             }
306             } else {
307 10         11 shift; # ignore
308             }
309 12         13 $code = shift;
310 12 50       23 $self->delinquent_fed_debt(1) if $code eq 'Y';
311 12 100       37 $self->delinquent_fed_debt(0) if $code eq 'N';
312 12         18 $self->exclusion_status(_trim(shift));
313 12   100     18 $count = int(_trim(shift) || 0);
314 12 50       27 if ($count > 0) {
315 0         0 my @sba = grep { length($_) > 0 } split /~/, shift;
  0         0  
316 0         0 foreach my $c (@sba) {
317 0 0       0 if ($c =~ /(\w{2})(\d{8})/) {
318 0         0 my $t = $1;
319 0 0       0 $self->SBA->{$t} = {} unless ref $self->SBA->{$t} eq 'HASH';
320 0         0 $self->SBA->{$t}->{description} = $self->SBA_descriptions->{$t};
321 0         0 $self->SBA->{$t}->{expiration} = _parse_yyyymmdd($2);
322             }
323             }
324             } else {
325 12         13 shift; # ignore
326             }
327 12 100       37 $self->is_private(length(shift) ? 1 : 0);
328 12   100     34 $count = int(_trim(shift) || 0);
329 12 50       22 if ($count > 0) {
330 0         0 my @dres = grep { length($_) > 0 } split /~/, shift;
  0         0  
331 0         0 my $h = {};
332 0         0 my %desc = (
333             ANY => 'Any area',
334             CTY => 'County',
335             STA => 'State',
336             MSA => 'Metropolitan Service Area',
337             );
338 0         0 foreach my $c (@dres) {
339 0 0       0 if ($c =~ /(\w{3})(\w*)/) {
340 0 0       0 $h->{$1} = {} unless ref $h->{$1} eq 'HASH';
341 0         0 $h->{$1}->{description} = $desc{$1};
342 0 0       0 $h->{$1}->{areas} = [] unless ref $h->{$1}->{areas} eq 'HASH';
343 0         0 my $a = _trim($2);
344 0 0       0 push @{$h->{$1}->{areas}}, $a if length $a;
  0         0  
345             }
346             }
347 0         0 $self->disaster_response($h);
348             } else {
349 12         11 shift; # ignore
350             }
351 12         12 my $eof = shift;
352 12 50       26 carp "Invalid end of record '$eof' seen. Expected '!end'" if $eof ne '!end';
353 12         139 return 1;
354             }
355              
356             1;
357              
358             =pod
359              
360             =encoding UTF-8
361              
362             =head1 NAME
363              
364             Parse::SAMGov::Entity - Object to denote each Entity in SAM
365              
366             =head1 VERSION
367              
368             version 0.106
369              
370             =head1 SYNOPSIS
371              
372             my $e = Parse::SAMGov::Entity->new(DUNS => 12345);
373             say $e; #... stringification supported ...
374              
375             =head1 METHODS
376              
377             =head2 DUNS
378              
379             This holds the unique identifier of the entity, currently the Data
380             Universal Numbering System (DUNS) number. This has a maximum length of 9 characters.
381             This number can be gotten from Dun & Bradstreet.
382              
383             =head2 DUNSplus4
384              
385             This holds the DUNS+4 value which is of 4 characters. If an entity doesn't have
386             this value set, it will be set as '0000'.
387              
388             =head2 CAGE
389              
390             This holds the CAGE code of the Entity.
391              
392             =head2 DODAAC
393              
394             This holds the DODAAC code of the entity.
395              
396             =head2 extract_code
397              
398             This denotes whether the SAM entry is active or expired
399             during extraction of the data.
400              
401             =head2 updated
402              
403             This denotes whether the SAM entry has been updated recently. Has a boolean
404             value of 1 if updated and 0 or undef otherwise.
405              
406             =head2 regn_purpose
407              
408             This denotes whether the purpose of registration is Federal Assistance Awards,
409             All Awards, IGT-only, Federal Assistance Awards & IGT or All Awards & IGT.
410              
411             =head2 regn_date
412              
413             Registration date of the entity with the input in YYYYMMDD format and it returns
414             a DateTime object.
415              
416             =head2 expiry_date
417              
418             Expiration date of the registration of the entity. The input is in YYYYMMDD
419             format and it returns a DateTime object.
420              
421             =head2 lastupdate_date
422              
423             Last update date of the registration of the entity. The input is in YYYYMMDD
424             format and it returns a DateTime object.
425              
426             =head2 activation_date
427              
428             Activation date of the registration of the entity. The input is in YYYYMMDD
429             format and it returns a DateTime object.
430              
431             =head2 name
432              
433             The legal business name of the entity.
434              
435             =head2 dba_name
436              
437             The Doing Business As (DBA) name of the entity.
438              
439             =head2 company_division
440              
441             The company division listed in the entity.
442              
443             =head2 division_no
444              
445             The divison number of the company division.
446              
447             =head2 physical_address
448              
449             This is the physical address of the entity represented as a
450             Parse::SAMGov::Entity::Address object.
451              
452             =head2 start_date
453              
454             This denotes the business start date. It takes as input the date in YYYYMMDD
455             format and returns a DateTime object.
456              
457             =head2 fiscalyear_date
458              
459             This denotes the current fiscal year end close date in YYYYMMDD format and
460             returns a DateTime object.
461              
462             =head2 url
463              
464             The corporate URL is denoted in this method. Returns a URI object and takes a
465             string value.
466              
467             =head2 entity_structure
468              
469             Get/Set the entity structure of the entity.
470              
471             =head2 incorporation_state
472              
473             Get/Set the two-character abbreviation of the state of incorporation.
474              
475             =head2 incorporation_country
476              
477             Get/Set the three-character abbreviation of the country of incorporation.
478              
479             =head2 biztype
480              
481             Get/Set the various business types that the entity holds. Requires an array
482             reference. The full list of business type codes can be retrieved from the SAM
483             Functional Data Dictionary.
484              
485             =head2 is_smallbiz
486              
487             Returns 1 or 0 if the business is defined as a small business or not.
488              
489             =head2 NAICS
490              
491             Get/Set the NAICS codes for the entity. This is a hash reference with the keys
492             being the numeric NAICS codes and the values being a hash reference with the
493             following keys:
494              
495             {
496             124567 => {
497             small_biz => 1,
498             exceptions => {
499             small_biz => 0,
500             # ... undocumented others ...
501             },
502             },
503             # ...
504             }
505             whether it is a small
506             business (value is 1) or not (value is 0) or has an exception (value is 2).
507              
508             =head2 PSC
509              
510             Get/Set the PSC codes for the entity. This requires an array reference.
511              
512             =head2 creditcard
513              
514             This denotes whether the entity uses a credit card.
515              
516             =head2 correspondence_type
517              
518             This denotes whether the entity prefers correspondence by mail, fax or email.
519             Returns a string of value 'mail', 'fax' or 'email'.
520              
521             =head2 mailing_address
522              
523             The mailing address of the entity as a L object.
524              
525             =head2 POC_gov
526              
527             This denotes the Government business Point of Contact for the entity and holds an
528             L object.
529              
530             =head2 POC_gov_alt
531              
532             This denotes the alternative Government business Point of Contact for the entity and
533             holds an L object.
534              
535             =head2 POC_pastperf
536              
537             This denotes the Past Performance Point of Contact for the entity and
538             holds an L object.
539              
540             =head2 POC_pastperf_alt
541              
542             This denotes the alternative Past Performance Point of Contact for the entity and
543             holds an L object.
544              
545             =head2 POC_elec
546              
547             This denotes the electronic business Point of Contact for the entity and
548             holds an L object.
549              
550             =head2 POC_elec_alt
551              
552             This denotes the alternative electronic business Point of Contact for the entity and
553             holds an L object.
554              
555             =head2 delinquent_fed_debt
556              
557             Get/Set the delinquent federal debt flag.
558              
559             =head2 exclusion_status
560              
561             Get/Set the exclusion status flag.
562              
563             =head2 is_private
564              
565             This flag denotes whether the listing is private or not.
566              
567             =head2 SBA
568              
569             This holds a hash-ref of Small Business Administration codes such as Hubzone,
570             8(a) certifications and the expiration dates. The structure looks like below:
571              
572             {
573             A4 => { description => 'SBA Certified Small Disadvantaged Busines',
574             expiration => '2016-12-01', #... this is a DateTime object...
575             },
576             }
577              
578             =head2 disaster_response
579              
580             This holds an array ref of disaster response (FEMA) codes that the entity falls
581             under, if applicable.
582              
583             =head1 AUTHOR
584              
585             Vikas N Kumar
586              
587             =head1 COPYRIGHT AND LICENSE
588              
589             This software is copyright (c) 2016 by Selective Intellect LLC.
590              
591             This is free software; you can redistribute it and/or modify it under
592             the same terms as the Perl 5 programming language system itself.
593              
594             =cut
595              
596             __END__