File Coverage

blib/lib/Parse/SAMGov.pm
Criterion Covered Total %
statement 71 74 95.9
branch 27 40 67.5
condition 10 22 45.4
subroutine 10 10 100.0
pod 1 1 100.0
total 119 147 80.9


line stmt bran cond sub pod time code
1             package Parse::SAMGov;
2             $Parse::SAMGov::VERSION = '0.202';
3 4     4   1025730 use strict;
  4         8  
  4         157  
4 4     4   17 use warnings;
  4         7  
  4         257  
5 4     4   82 use 5.010;
  4         16  
6 4     4   21 use Carp;
  4         6  
  4         336  
7 4     4   1743 use IO::All;
  4         8457  
  4         50  
8 4     4   4680 use Text::CSV_XS;
  4         68755  
  4         305  
9 4     4   2313 use Parse::SAMGov::Entity;
  4         37  
  4         310  
10 4     4   2277 use Parse::SAMGov::Exclusion;
  4         12  
  4         169  
11 4     4   27 use Parse::SAMGov::Mo;
  4         8  
  4         21  
12              
13             # ABSTRACT: Parses SAM Entity Management Public Extract Layout from SAM.gov
14              
15              
16             sub parse_file {
17 6     6 1 74365 my ($self, $filename, $cb, $cb_arg) = @_;
18 6 50       153 croak "Unable to open file $filename: $!" unless -e $filename;
19 6         39 my $io = io $filename;
20 6 50       3169 croak "Unable to create IO::All object for reading $filename"
21             unless defined $io;
22 6         17 my $result = [];
23 6         12 my $is_entity = 0;
24 6         15 my $entity_info = {};
25 6         63 while (my $line = $io->getline) {
26 26         4400 chomp $line;
27 26         133 $line =~ s/^\s+//g;
28 26         211 $line =~ s/\s+$//g;
29 26 50       74 next unless length $line;
30 26         207 my $obj = Parse::SAMGov::Entity->new;
31 26 100       227 if ($line =~ /BOF\s+PUBLIC\s+(V2)?\s*(\d{8})\s+(\d{8})\s+(\d+)\s+(\d+)/) {
    100          
32 4         11 $is_entity = 1;
33 4   100     32 $entity_info->{version} = $1 // 'V1';
34 4 100       15 if ($entity_info->{version} eq 'V2') {
35 2         7 $entity_info->{date} = $3;
36 2         36 $entity_info->{rows} = $4;
37 2         20 $entity_info->{seqno} = $5;
38             } else {
39 2         9 $entity_info->{date} = $2;
40 2         16 $entity_info->{rows} = $4;
41 2         13 $entity_info->{seqno} = $5;
42             }
43 4         37 next;
44             } elsif ($line =~ /EOF\s+PUBLIC\s+(V2)?\s*(\d{8})\s+(\d{8})\s+(\d+)\s+(\d+)/) {
45 4   50     30 $entity_info->{version} = $1 // 'V1';
46 4 100       16 if ($entity_info->{version} eq 'V2') {
47             croak "Invalid footer q{$line} in file"
48             if ( $entity_info->{date} ne $3
49             or $entity_info->{rows} ne $4
50 2 50 33     24 or $entity_info->{seqno} ne $5);
      33        
51             } else {
52             croak "Invalid footer q{$line} in file"
53             if ( $entity_info->{date} ne $2
54             or $entity_info->{rows} ne $4
55 2 50 33     38 or $entity_info->{seqno} ne $5);
      33        
56             }
57 4         19 last;
58             } else {
59 18 100       57 last unless $is_entity; # skip this loop and do something else
60 16         3451 my @data = split /\|/x, $line;
61 16 50       98 carp "Invalid data line \n$line\n" unless $obj->load(@data);
62             }
63 16 100 66     74 if (defined $cb and ref $cb eq 'CODE') {
64 8         35 my $res = &$cb($obj, $cb_arg);
65 8 100       393 push @$result, $obj if $res;
66             } else {
67 8         86 push @$result, $obj;
68             }
69             }
70 6 100       23 unless ($is_entity) {
71 2 50       49 my $csv = Text::CSV_XS->new({ binary => 1 })
72             or croak "Failed to create Text::CSV_XS object: "
73             . Text::CSV_XS->error_diag();
74 2         823 my $obj = Parse::SAMGov::Exclusion->new;
75 2         12 while (my $row = $csv->getline($io->io_handle)) {
76 20 50       1292 carp "Invalid data line \n$row\n" unless $obj->load(@$row);
77 20 50 33     67 if (defined $cb and ref $cb eq 'CODE') {
78 0         0 my $res = &$cb($obj, $cb_arg);
79 0 0       0 push @$result, $obj if $res;
80             } else {
81 20         421 push @$result, $obj;
82             }
83             }
84 2 50       144 $csv->eof or $csv->error_diag();
85             }
86 6 50       186 return $result if scalar @$result;
87 0           return;
88             }
89              
90             1;
91              
92             =pod
93              
94             =encoding UTF-8
95              
96             =head1 NAME
97              
98             Parse::SAMGov - Parses SAM Entity Management Public Extract Layout from SAM.gov
99              
100             =head1 VERSION
101              
102             version 0.202
103              
104             =head1 SYNOPSIS
105              
106             my $parser = Parse::SAMGov->new;
107             my $entities = $parser->parse_file('SAM_PUBLIC_DAILY_20160701.dat');
108             foreach my $e (@$entities) {
109             ## do something with each entity
110             say $e->DUNS, ' is a valid entity';
111             }
112             #... use in filter mode like grep ...
113             my $entities_541511 = $parser->parse_file('SAM_PUBLIC_DAILY_20160701.dat',
114             sub {
115             # filter all companies with NAICS code
116             # being 541511
117             return $_[0] if exists $_[0]->NAICS->{541511};
118             return undef;
119             });
120              
121             # ... do something ...
122             my $exclusions = $parser->parse_file(exclusion => 'SAM_Exclusions_Public_Extract_16202.CSV');
123             foreach my $e (@$exclusions) {
124             ## do something with each entity that has been excluded
125             say $e->DUNS, ' has been excluded';
126             }
127              
128             =head1 METHODS
129              
130             =head2 parse_file
131              
132             This method takes as arguments the file to be parsed and returns an array
133             reference of L<Parse::SAMGov::Entity> or L<Parse::SAMGOv::Exclusion> objects
134             depending on the data being parsed.
135              
136             If the second argument is a coderef then passes each Entity or
137             Exclusion object into the callback where the user can select which objects they
138             want to return. The user has to return 1 if they want the object returned in the
139             array ref or undef if they do not.
140              
141             my $entities = $parser->parse_file('SAM_PUBLIC_DAILY_20160701.dat');
142             my $exclusions = $parser->parse_file('SAM_Exclusions_Public_Extract_16202.CSV');
143             my $entities = $parser->parse_file('SAM_PUBLIC_DAILY_20160701.dat', sub {
144             my ($entity_or_exclusion, $optional_user_arg) = @_;
145             #... do something ...
146             return 1 if (!$entity_or_exclusion->is_private);
147             return undef;
148             }, $optional_user_arg);
149              
150             =head1 SEE ALSO
151              
152             L<Parse::SAMGov::Entity> and L<Parse::SAMGov::Exclusion> for the object
153             definitions.
154              
155             =head1 AUTHOR
156              
157             Vikas N Kumar <vikas@cpan.org>
158              
159             =head1 COPYRIGHT AND LICENSE
160              
161             This software is copyright (c) 2023 by Selective Intellect LLC.
162              
163             This is free software; you can redistribute it and/or modify it under
164             the same terms as the Perl 5 programming language system itself.
165              
166             =cut
167              
168             __END__
169             ### COPYRIGHT: Selective Intellect LLC.
170             ### AUTHOR: Vikas N Kumar <vikas@cpan.org>