diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d6d8aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.project +.includepath +.build +Weather-Underground-Forecast* diff --git a/Changes b/Changes new file mode 100644 index 0000000..f2ca7f3 --- /dev/null +++ b/Changes @@ -0,0 +1,2 @@ +{{$NEXT}} + - Initial Release \ No newline at end of file diff --git a/README b/README deleted file mode 100644 index e69de29..0000000 diff --git a/dist.ini b/dist.ini new file mode 100644 index 0000000..e259ef0 --- /dev/null +++ b/dist.ini @@ -0,0 +1,18 @@ +name = Weather-Underground-Forecast +abstract = Weather Underground Forecast Data + +[VersionFromModule] + +[NextRelease] + +[@Git] + +[@Basic] + +[PodSyntaxTests] +[PodCoverageTests] +[CriticTests] + +[AutoPrereqs] + + diff --git a/lib/Weather/Underground/Forecast.pm b/lib/Weather/Underground/Forecast.pm new file mode 100644 index 0000000..fd8d910 --- /dev/null +++ b/lib/Weather/Underground/Forecast.pm @@ -0,0 +1,199 @@ +package Weather::Underground::Forecast; +use Moose; +use namespace::autoclean; +use LWP::Simple; +use XML::Simple; + +use Data::Dumper::Concise; + +our $VERSION = '0.01'; + +=head1 Name + +Weather::Underground::Forecast - Simple API to Weather Underground Forecast Data + +=head1 Synopsis + + Get the weather forecast: + + my $forecast = Weather::Underground::Forecast->new( + location => $location, + temperature_units => 'fahrenheit', # or 'celsius' + ); + + Where the $location can be: + * city,state (Bloomington,IN) + * zip code (47401) + * latitude,longitude (46,-113) + + my ($highs, $lows) = $forecast->temperatures; + +NOTE: I is the only required parameter to C + + +=cut + +has 'location' => ( + is => 'rw', + isa => 'Str', + required => 1, + writer => 'set_location', +); +has 'temperature_units' => ( + is => 'ro', + isa => 'Str', + 'default' => 'fahrenheit', +); +has 'data' => ( + is => 'rw', + isa => 'ArrayRef[HashRef]', + lazy_build => 1, +); +has 'source_URL' => ( + is => 'ro', + isa => 'Any', + lazy_build => 1, +); + +# When the location changes, we want to clear the data to insure a new data fetch will happen. +# We need this since data is lazily built, and we used a distinct name for the writer +# so we only clear data when we set the location anytime after initial object construction. +after 'set_location' => sub { + my $self = shift; + $self->clear_data; +}; + +=head1 Methods + +=head2 temperatures + +Get the high and low temperatures for the number of days specified. + + Returns: Array of two ArrayRefs being the high and low temperatures + Example: my ($highs, $lows) = $wunder->forecast_temperaures; + +=cut + +sub temperatures { + my $self = shift; + return ( $self->highs, $self->lows ); +} + +=head2 highs + +Get an ArrayRef[Int] of the forecasted high temperatures. + +=cut + +sub highs { + my $self = shift; + + my $key1 = 'high'; + my $key2 = $self->temperature_units; + return $self->get_forecast_data_by_two_keys( $key1, $key2 ); +} + +=head2 lows + +Get an ArrayRef[Int] of the forecasted low temperatures. + +=cut + +sub lows { + my $self = shift; + + my $key1 = 'low'; + my $key2 = $self->temperature_units; + return $self->get_forecast_data_by_two_keys( $key1, $key2 ); +} + +=head2 precipitation + +Get an ArrayRef[Int] of the forecasted chance of precipitation. + +=cut + +sub precipitation { + my $self = shift; + + return $self->get_forecast_data_by_one_key('pop'); +} + +=head2 get_forecast_data_by_one_key + +Get the values for a single forecast metric that is +only one key deep. An examples is: 'pop' (prob. of precip.) + +NOTE: One can dump the data attribute to see +the exact data structure and keys available. + +=cut + +sub get_forecast_data_by_one_key { + my ( $self, $key ) = @_; + + return [ map { $_->{$key} } @{ $self->data } ]; +} + +=head2 get_forecast_data_by_two_keys + +Like the one_key method above but for values that are +two keys deep in the data structure. + +=cut + +sub get_forecast_data_by_two_keys { + my ( $self, $key1, $key2 ) = @_; + + return [ map { $_->{$key1}->{$key2} } @{ $self->data } ]; +} + +sub _query_URL { + my $self = shift; + return $self->source_URL . $self->location; +} + +# Builders + +sub _build_data { + my $self = shift; + + my $content = get( $self->_query_URL ); + die "Couldn't get URL: ", $self->_query_URL unless defined $content; + + my $xml = XML::Simple->new; + my $data_ref = $xml->XMLin($content); + my $forecasts = $data_ref->{simpleforecast}->{forecastday}; + + return $forecasts; +} + +sub _build_source_URL { + my $self = shift; + return + 'http://api.wunderground.com/auto/wui/geo/ForecastXML/index.xml?query='; +} + +__PACKAGE__->meta->make_immutable; +1 + +__END__ + +=head1 Limitations + +It is possible that location could have more than one forecast. +The behavior of that possibility has not been tested. + +=head1 Authors + +Mateu Hunter C + +=head1 Copyright + +Copyright 2010, Mateu Hunter + +=head1 License + +You may distribute this code under the same terms as Perl itself. + +=cut diff --git a/t/basic.t b/t/basic.t new file mode 100644 index 0000000..7a16bd2 --- /dev/null +++ b/t/basic.t @@ -0,0 +1,31 @@ +use strict; +use warnings; +use Test::More; +use Weather::Underground::Forecast; +use LWP::Simple; +use Data::Dumper::Concise; + +my $wunder_forecast = Weather::Underground::Forecast->new( + location => 'Missoula,MT', + temperature_units => 'fahrenheit', # or 'celsius' +); + +isa_ok( $wunder_forecast, 'Weather::Underground::Forecast' ); +can_ok( 'Weather::Underground::Forecast', ( 'temperatures', 'precipitation' ) ); + +SKIP: +{ + + # Test internet connection + my $source_URL = $wunder_forecast->_query_URL; + my $content = get($source_URL); + skip( 'Skipping live test using Internet', 3 ) if !$content; + + my ( $highs, $lows ) = $wunder_forecast->temperatures; + my $chance_of_precip = $wunder_forecast->precipitation; + is( ref($highs), 'ARRAY', 'highs data structure' ); + is( ref($lows), 'ARRAY', 'lows data structure' ); + is( ref($chance_of_precip), 'ARRAY', 'precips data structure' ); +} + +done_testing();