File Coverage

blib/lib/Weather/NHC/TropicalCyclone.pm
Criterion Covered Total %
statement 75 79 94.9
branch 8 12 66.6
condition 8 16 50.0
subroutine 18 18 100.0
pod 5 8 62.5
total 114 133 85.7


line stmt bran cond sub pod time code
1             package Weather::NHC::TropicalCyclone;
2              
3 2     2   1253 use strict;
  2         4  
  2         47  
4 2     2   8 use warnings;
  2         3  
  2         34  
5 2     2   2094 use HTTP::Tiny ();
  2         56259  
  2         58  
6 2     2   738 use HTTP::Status qw/:constants/;
  2         7211  
  2         645  
7 2     2   13 use JSON::XS ();
  2         4  
  2         31  
8 2     2   740 use Util::H2O qw/h2o/;
  2         7691  
  2         84  
9 2     2   1391 use Weather::NHC::TropicalCyclone::Storm ();
  2         6  
  2         1304  
10              
11             our $VERSION = q{0.34};
12             our $DEFAULT_URL = q{https://www.nhc.noaa.gov/CurrentStorms.json};
13             our $DEFAULT_RSS_ATLANTIC = q{https://www.nhc.noaa.gov/index-at.xml};
14             our $DEFAULT_RSS_EAST_PACIFIC = q{https://www.nhc.noaa.gov/index-ep.xml};
15             our $DEFAULT_RSS_CENTRAL_PACIFIC = q{https://www.nhc.noaa.gov/index-cp.xml};
16             our $DEFAULT_TIMEOUT = 10;
17              
18             # container class for requesting JSON and providing
19             # iterator access and meta operations for the storms
20             # contained in the JSON returned by NHC
21              
22             sub new {
23 4     4 1 5752 my $pkg = shift;
24 4         13 my $self = {
25             _obj => undef,
26             _storms => {},
27             };
28 4         13 return bless $self, $pkg;
29             }
30              
31             sub fetch {
32 4     4 1 919 my ( $self, $timeout, $file ) = @_;
33 4         52 my $http = HTTP::Tiny->new();
34              
35 4     1   372 local $SIG{ALRM} = sub { die "Request has timed out.\n" };
  1         1000116  
36              
37 4   66     49 alarm( $timeout // $DEFAULT_TIMEOUT );
38              
39             # get content via $DEFAULT_URL unless --file option is passed
40 4         10 local $@;
41 4         6 my $response = eval { $http->get($DEFAULT_URL) };
  4         16  
42 4 50 66     43 if ( $@ or not $response or $response->{status} ne HTTP_OK ) {
      66        
43 1         36 die qq{request error\n};
44             }
45              
46 3         14 alarm 0;
47              
48 3         10 my $content = $response->{content};
49              
50             # if $file is provided, contents directly from the GET are
51             # written
52 3 50       15 if ($file) {
53 0   0     0 open my $fh, q{>}, $file || die qq{Can't open '$file': $!\n};
54 0         0 print $fh $content;
55 0         0 close $fh;
56             }
57              
58 3         4 my $ref = eval { JSON::XS::decode_json $content };
  3         267  
59              
60 3 50 33     16 if ( $@ or not $ref ) {
61 0         0 die qq{JSON decode error\n};
62             }
63              
64             # add accessors based on elements in returned hash ref
65 3         15 $ref = h2o -recurse, $ref;
66              
67 3         342 $self->{_obj} = $ref;
68              
69             # reset and update storms cache
70 3         11 $self->_update_storm_cache;
71              
72 3         61 return $self;
73             }
74              
75             sub active_storms {
76 4     4 1 2322 my $self = shift;
77 4         6 return [ values %{ $self->{_storms} } ];
  4         18  
78             }
79              
80             # there is no checking, if the storm is not in the cache,
81             # an undefined value is returned
82             sub get_storm_by_id {
83 2     2 1 704 my ( $self, $id ) = @_;
84 2         4 return $self->{_storms}->{$id};
85             }
86              
87             # returns storm Ids
88             sub get_storm_ids {
89 1     1 1 525 my $self = shift;
90 1         2 return [ keys %{ $self->{_storms} } ];
  1         5  
91             }
92              
93             sub _update_storm_cache {
94 3     3   4 my $self = shift;
95              
96             # purge cache
97 3         6 $self->{_storms} = {};
98              
99             REBUILD_STORMS_CACHE:
100 3         5 for my $storm ( @{ $self->{_obj}->{activeStorms} } ) {
  3         7  
101 6         25 my $s = Weather::NHC::TropicalCyclone::Storm->new($storm);
102 6         14328 my $storm_id = $s->id;
103              
104             # key storm by id (e.g., al182020, etc)
105 6         44 $self->{_storms}->{$storm_id} = $s;
106             }
107              
108             }
109              
110             sub fetch_rss_atlantic {
111 3     3 0 2147614 my ( $self, $local_file ) = @_;
112 3         10 return $self->_fetch_rss( $DEFAULT_RSS_ATLANTIC, $local_file );
113             }
114              
115             sub fetch_rss_east_pacific {
116 3     3 0 4220 my ( $self, $local_file ) = @_;
117 3         10 return $self->_fetch_rss( $DEFAULT_RSS_EAST_PACIFIC, $local_file );
118             }
119              
120             sub fetch_rss_central_pacific {
121 3     3 0 3758 my ( $self, $local_file ) = @_;
122 3         8 return $self->_fetch_rss( $DEFAULT_RSS_CENTRAL_PACIFIC, $local_file );
123             }
124              
125             sub _fetch_rss {
126 9     9   23 my ( $self, $rss_url, $local_file ) = @_;
127              
128 9         51 my $http = HTTP::Tiny->new;
129              
130 9         846 my $response = $http->get($rss_url);
131              
132 9 100       1054605 if ( not $response->{success} ) {
133 3   50     7 my $status = $response->{status} // q{Unknown};
134 3         21 die qq{Fetching of $rss_url failed. HTTP status: $status\n};
135             }
136              
137 6 100       16 if ($local_file) {
138 3 50       198 open my $fh, q{>}, $local_file or die qq{Error writing RSS file, $local_file: $!\n};
139 3         361 print $fh $response->{content};
140             }
141              
142 6         87 return $response->{content};
143             }
144              
145             1;
146              
147             __END__