weather-sage 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 390dc567cf5a2696830a36bd2a6a27020a2310721a95f69530ccbd0688c06d28
4
+ data.tar.gz: 9fc5165689410c41763750e06b5590452294f2588880cb2f027dbe98191e0be6
5
+ SHA512:
6
+ metadata.gz: b23545938bb233191fb042f1cb7d45c098332421dbfb53c6fae126f5a3ef8f6543d75c16d4537582222e519e7da320fc11abbcaadaeed0f7fd4fcb31f83cdefc
7
+ data.tar.gz: 1541a3ef620d4667e45fe513f95f61e891ff259ab0309c4286ca709498eca8bca2972d9ede59e0587bd51dbacb9403c9ebdff1d491af33320f58400fae619c9f
data/README.md ADDED
@@ -0,0 +1,88 @@
1
+ Weather Sage
2
+ ============
3
+ Ruby library and command-line tool to get the weather forecast and
4
+ current weather observations for an address.
5
+
6
+ Uses the [Census Bureau Geocoding API][census-api] to geocode street
7
+ addresses, and the [National Weather Service Weather API][weather-api]
8
+ to get weather forecasts and current weather observations.
9
+
10
+ Here's an example of using the command-line tool to get the current
11
+ weather forecast for the address "1600 pennsylvania ave nw, washington
12
+ dc":
13
+
14
+ ```
15
+ > bin/weather-sage forecast '1600 pennsylvania ave nw washington dc'
16
+ address,name,temperature,temperature_unit,wind_speed,wind_direction,short_forecast
17
+ 1600 pennsylvania ave washington dc,Today,97,F,7 mph,SW,Scattered Showers And Thunderstorms
18
+ 1600 pennsylvania ave washington dc,Tonight,76,F,2 to 7 mph,SW,Scattered Showers And Thunderstorms then Mostly Cloudy
19
+ 1600 pennsylvania ave washington dc,Tuesday,94,F,6 mph,E,Slight Chance Rain Showers then Chance Showers And Thunderstorms
20
+ 1600 pennsylvania ave washington dc,Tuesday Night,75,F,2 to 6 mph,S,Chance Showers And Thunderstorms
21
+ 1600 pennsylvania ave washington dc,Wednesday,94,F,3 to 9 mph,SW,Chance Showers And Thunderstorms
22
+ 1600 pennsylvania ave washington dc,Wednesday Night,76,F,5 to 8 mph,SW,Chance Showers And Thunderstorms
23
+ ...
24
+ ```
25
+
26
+ Installation
27
+ ------------
28
+ Install via [RubyGems][]:
29
+
30
+ ```
31
+ # install weather-sage
32
+ > sudo gem install weather-sage
33
+ ```
34
+
35
+ Examples
36
+ --------
37
+ **Get weather forecast for an address:**
38
+ ```
39
+ # get weather forecast for address
40
+ > bin/weather-sage forecast '1600 pennsylvania ave washington dc'
41
+ address,name,temperature,temperature_unit,wind_speed,wind_direction,short_forecast
42
+ 1600 pennsylvania ave washington dc,Today,97,F,7 mph,SW,Scattered Showers And Thunderstorms
43
+ 1600 pennsylvania ave washington dc,Tonight,76,F,2 to 7 mph,SW,Scattered Showers And Thunderstorms then Mostly Cloudy
44
+ 1600 pennsylvania ave washington dc,Tuesday,94,F,6 mph,E,Slight Chance Rain Showers then Chance Showers And Thunderstorms
45
+ 1600 pennsylvania ave washington dc,Tuesday Night,75,F,2 to 6 mph,S,Chance Showers And Thunderstorms
46
+ 1600 pennsylvania ave washington dc,Wednesday,94,F,3 to 9 mph,SW,Chance Showers And Thunderstorms
47
+ 1600 pennsylvania ave washington dc,Wednesday Night,76,F,5 to 8 mph,SW,Chance Showers And Thunderstorms
48
+ ...
49
+ ```
50
+
51
+ **Get current observations from nearest weather station:**
52
+ ```
53
+ # get current observations from nearest weather station
54
+ > weather-sage now '1600 pennsylvania ave washington dc'
55
+ address,name,type,value,unit,quality_control
56
+ 1600 pennsylvania ave washington dc,timestamp,time,2019-08-19T06:52:00+00:00
57
+ 1600 pennsylvania ave washington dc,textDescription,text,Mostly Cloudy
58
+ 1600 pennsylvania ave washington dc,temperature,value,26.700000000000045,unit:degC,qc:V
59
+ 1600 pennsylvania ave washington dc,relativeHumidity,value,81.65039907186703,unit:percent,qc:C
60
+ ...
61
+ ```
62
+
63
+ Commands
64
+ --------
65
+ Available commands in command-line interface.
66
+
67
+ * `forecast`: Get weather forecast for address.
68
+ * `geocode`: Geocode address.
69
+ * `help`: List commands.
70
+ * `hourly`: Get hourly weather forecast for address.
71
+ * `now`: Get current weather from station closest to address.
72
+ * `stations`: List weather stations near address.
73
+
74
+ Environment Variables
75
+ ---------------------
76
+ The command-line tool can be configured via the following environment
77
+ variables:
78
+
79
+ * `WEATHER_SAGE_LOG_LEVEL`: Log level. One of: `fatal`, `error`,
80
+ `warning`, `info`, `debug`). Defaults to `warn`.
81
+ * `WEATHER_SAGE_LOG_PATH`: Path to log file. Defaults to standard error.
82
+ * `WEATHER_SAGE_CACHE_PATH`: Path to HTTP cache. Defaults to
83
+ `~/.config/weather-sage/http-cache.pstore`.
84
+
85
+ [ruby]: https://ruby-lang.org/ "Ruby programming language"
86
+ [rubygems]: https://rubygems.org/ "Ruby package manager"
87
+ [weather-api]: https://api.weather.gov/ "national Weather Service (NWS) Weather API"
88
+ [census-api]: https://geocoding.geo.census.gov/geocoder/ "Census Bureau Geocoding API"
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'rake/testtask'
2
+ require 'rdoc/task'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'test'
6
+ end
7
+
8
+ RDoc::Task.new :docs do |t|
9
+ t.main = "lib/weather-sage.rb"
10
+ t.rdoc_files.include('lib/*.rb')
11
+ t.rdoc_dir = 'docs'
12
+ # t.options << "--all"
13
+ end
14
+
15
+ desc "Run tests"
16
+ task default: :test
data/bin/weather-sage ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/weather-sage'
4
+
5
+ # run command-line interface
6
+ WeatherSage::CLI.run($0, ARGV)
@@ -0,0 +1,25 @@
1
+ #
2
+ # Get weather forecast and current weather for address.
3
+ #
4
+ # Wrapper around the following APIs:
5
+ #
6
+ # * National Weather Service Weather API (https://api.weather.gov/)
7
+ # * Census Bureau Geocoder API (https://geocoding.geo.census.gov/geocoder/)
8
+ #
9
+ module WeatherSage
10
+ #
11
+ # Release version.
12
+ #
13
+ VERSION = '0.1.0'
14
+
15
+ # :nodoc:
16
+ LIB_DIR = File.join(__dir__, 'weather-sage').freeze
17
+
18
+ autoload :CacheEntry, File.join(LIB_DIR, 'cache-entry.rb')
19
+ autoload :Cache, File.join(LIB_DIR, 'cache.rb')
20
+ autoload :HTTP, File.join(LIB_DIR, 'http.rb')
21
+ autoload :Context, File.join(LIB_DIR, 'context.rb')
22
+ autoload :Census, File.join(LIB_DIR, 'census.rb')
23
+ autoload :Weather, File.join(LIB_DIR, 'weather.rb')
24
+ autoload :CLI, File.join(LIB_DIR, 'cli.rb')
25
+ end
data/license.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Paul Duncan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a
4
+ copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included
12
+ in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/test/make-csvs.sh ADDED
@@ -0,0 +1,10 @@
1
+ #!/bin/sh
2
+
3
+ # TODO: move to rake task
4
+ ADDR='7309 carol ln falls church va 22042'
5
+
6
+ bin/weather-sage hourly -f "$ADDR" > hourly-full.csv
7
+ bin/weather-sage hourly -b "$ADDR" > hourly-brief.csv
8
+ bin/weather-sage forecast -b "$ADDR" > forecast-brief.csv
9
+ bin/weather-sage forecast -f "$ADDR" > forecast-full.csv
10
+ bin/weather-sage now "$ADDR" > now.csv
@@ -0,0 +1,64 @@
1
+ require 'minitest/autorun'
2
+ require 'fileutils'
3
+ require_relative '../lib/weather-sage'
4
+
5
+ class TestWeatherSageCache < Minitest::Test
6
+ def setup
7
+ # create temporary directory that is removed on exit
8
+ @dir = Dir.mktmpdir('test-cache')
9
+ at_exit { FileUtils.rm_r(@dir) }
10
+
11
+ # build absolute path to backing store for cache
12
+ path = File.join(@dir, 'cache.pstore')
13
+
14
+ # create cache instance
15
+ @cache = WeatherSage::Cache.new(path)
16
+ end
17
+
18
+ def test_new
19
+ # build absolute path to backing store for cache
20
+ path = File.join(@dir, 'test-new.pstore')
21
+
22
+ # asset that cache is not nil
23
+ assert WeatherSage::Cache.new(path)
24
+ end
25
+
26
+ def test_set
27
+ key, want = 'test_set', 'bar'
28
+ assert_equal want, @cache.set(key, want)
29
+ end
30
+
31
+ def test_get
32
+ # set value
33
+ key, want = 'test_get', 'bar'
34
+ @cache.set(key, want)
35
+
36
+ # get value, test result
37
+ got = @cache.get(key)
38
+ assert_equal want, got
39
+ end
40
+
41
+ def test_key?
42
+ key, want = 'test_key?', 'bar'
43
+ @cache.set(key, want)
44
+ assert @cache.key?(key)
45
+ end
46
+
47
+ def test_set_timeout
48
+ key, want, timeout = 'test_set_timeout', 'bar', 10
49
+
50
+ # set long timeout, then get result before expiration
51
+ @cache.set(key, want, timeout)
52
+ assert_equal want, @cache.get(key)
53
+ end
54
+
55
+ def test_set_timeout_expired
56
+ key, want, timeout = 'test_set_timeout_expired', 'bar', 0.1
57
+
58
+ # set short timeout, then wait for it to pass
59
+ @cache.set(key, want, timeout)
60
+ sleep(timeout + 1)
61
+
62
+ assert_nil @cache.get(key)
63
+ end
64
+ end
@@ -0,0 +1,49 @@
1
+ require 'fileutils'
2
+ require 'logger'
3
+ require 'minitest/autorun'
4
+ require_relative '../lib/weather-sage'
5
+ require_relative './test-helpers'
6
+
7
+ class TestWeatherSageCensusGeocoder < Minitest::Test
8
+ #
9
+ # Test addresses.
10
+ #
11
+ TESTS = [{
12
+ text: '2565 van buren st eugene or 97405',
13
+ want: [-123.109825, 44.03039],
14
+ }, {
15
+ text: '1600 pennsylvania ave nw washington dc',
16
+ want: [-77.03535, 38.898754],
17
+ }, {
18
+ text: '3150 fairview park dr falls church va 22042',
19
+ want: [-77.216194, 38.859795],
20
+ }]
21
+
22
+ def setup
23
+ # cache test context
24
+ @ctx = WeatherSage::Tests.get_test_context
25
+
26
+ # create and save geocoder
27
+ @geocoder = WeatherSage::Census::Geocoder.new(@ctx)
28
+ end
29
+
30
+ def test_new
31
+ # create new geocoder
32
+ assert WeatherSage::Census::Geocoder.new(@ctx)
33
+ end
34
+
35
+ def test_run
36
+ # assert that geocoder can geocode a single address
37
+ assert @geocoder.run(TESTS.first[:text])
38
+ end
39
+
40
+ def test_results
41
+ TESTS.each do |test|
42
+ # geocode address, get first result
43
+ pt = @geocoder.run(test[:text]).first.point
44
+
45
+ assert_in_delta pt.x, test[:want][0]
46
+ assert_in_delta pt.y, test[:want][1]
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,24 @@
1
+ module WeatherSage::Tests
2
+ def self.get_test_context
3
+ unless @ctx
4
+ # create temporary directory that is removed on exit
5
+ @dir = Dir.mktmpdir('test-weather-sage')
6
+ at_exit { FileUtils.rm_r(@dir) }
7
+
8
+ # build absolute path to backing store for cache
9
+ cache_path = File.join(@dir, 'cache.pstore')
10
+
11
+ # create null logger
12
+ log = ::Logger.new('/dev/null')
13
+
14
+ # create cache instance
15
+ cache = WeatherSage::HTTP::Cache.new(cache_path, log)
16
+
17
+ # create and save context
18
+ @ctx = WeatherSage::Context.new(log, cache)
19
+ end
20
+
21
+ # return cached context
22
+ @ctx
23
+ end
24
+ end
@@ -0,0 +1,103 @@
1
+ require 'minitest/autorun'
2
+ require 'open-uri'
3
+ require 'fileutils'
4
+ require 'logger'
5
+ require_relative '../lib/weather-sage'
6
+
7
+ class TestWeatherSageHttpCache < Minitest::Test
8
+ #
9
+ # list of test urls
10
+ #
11
+ URLS = {
12
+ good: 'https://pablotron.org/',
13
+ bad: 'https://pablotron.org/does-not-exist',
14
+ }
15
+
16
+ def setup
17
+ # create temporary directory that is removed on exit
18
+ @dir = Dir.mktmpdir('test-http-cache')
19
+ at_exit { FileUtils.rm_r(@dir) }
20
+
21
+ # build absolute path to backing store for cache
22
+ path = File.join(@dir, 'cache.pstore')
23
+
24
+ # create null logger
25
+ @log = ::Logger.new('/dev/null')
26
+
27
+ # create cache instance
28
+ @cache = WeatherSage::HTTP::Cache.new(path, @log)
29
+ end
30
+
31
+ def test_new
32
+ # build absolute path to backing store for cache
33
+ path = File.join(@dir, 'test-new.pstore')
34
+
35
+ # asset that cache is not nil
36
+ assert WeatherSage::HTTP::Cache.new(path, @log)
37
+ end
38
+
39
+ def test_new_timeout
40
+ # build absolute path to backing store for cache
41
+ path = File.join(@dir, 'test-new-timeout.pstore')
42
+
43
+ # set timeout to one second
44
+ timeout = 1
45
+
46
+ # create cache with timeout
47
+ cache = WeatherSage::HTTP::Cache.new(path, @log, timeout)
48
+
49
+ # asset that timeout is saved
50
+ assert_equal timeout, cache.timeout
51
+ end
52
+
53
+ def test_new_timeout_expired
54
+ # build absolute path to backing store for cache
55
+ path = File.join(@dir, 'test-new-timeout.pstore')
56
+
57
+ # set timeout to one seconds
58
+ timeout = 1
59
+
60
+ # create cache with one second
61
+ cache = WeatherSage::HTTP::Cache.new(path, @log, timeout)
62
+
63
+ # get known good url
64
+ cache.get(URLS[:good])
65
+
66
+ # wait for entry to expire
67
+ sleep(timeout + 0.1)
68
+
69
+ # asset that entry is expired
70
+ assert !cache.key?(URLS[:good])
71
+ end
72
+
73
+ def test_get
74
+ # read URl directly
75
+ want = read_url(URLS[:good])
76
+
77
+ # get URL through cache
78
+ got = @cache.get(URLS[:good])
79
+
80
+ # test result
81
+ assert_equal want, got
82
+ end
83
+
84
+ def test_get_bad
85
+ assert_raises(WeatherSage::HTTP::Error) do
86
+ @cache.get(URLS[:bad])
87
+ end
88
+ end
89
+
90
+ private
91
+
92
+ #
93
+ # Read given URL.
94
+ #
95
+ # Note: currently forces UTF-8 encoding.
96
+ #
97
+ def read_url(url)
98
+ # open(url, 'rb') { |fh| fh.read }
99
+
100
+ # read contents of URL, force UTF-8 encoding
101
+ open(url, 'rb', encoding: 'UTF-8') { |fh| fh.read }
102
+ end
103
+ end
@@ -0,0 +1,32 @@
1
+ require 'fileutils'
2
+ require 'logger'
3
+ require 'minitest/autorun'
4
+ require_relative '../lib/weather-sage'
5
+ require_relative './test-helpers'
6
+
7
+ class WeatherSage::Tests::TestWeatherPoint < Minitest::Test
8
+ #
9
+ # Test addresses.
10
+ #
11
+ TESTS = [{
12
+ id: '2565 van buren st eugene or 97405',
13
+ pt: [-123.109825, 44.03039],
14
+ }, {
15
+ id: '1600 pennsylvania ave nw washington dc',
16
+ pt: [-77.03535, 38.898754],
17
+ }, {
18
+ id: '3150 fairview park dr falls church va 22042',
19
+ pt: [-77.216194, 38.859795],
20
+ }]
21
+
22
+ def setup
23
+ # cache test context
24
+ @ctx = WeatherSage::Tests.get_test_context
25
+ end
26
+
27
+ def test_new
28
+ # create point from lat/long
29
+ x, y = TESTS.first[:pt]
30
+ assert WeatherSage::Weather::Point.new(@ctx, x, y)
31
+ end
32
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: weather-sage
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Paul Duncan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-08-18 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: "\n Ruby library and command-line tool to get the weather forecast
14
+ and\n current weather observations for an address.\n\n Uses the Census Bureau
15
+ Geocoding API to geocode street addresses,\n and the National Weather Service
16
+ Weather API to get weather\n forecasts and current weather observations.\n "
17
+ email: pabs@pablotron.org
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - README.md
23
+ - Rakefile
24
+ - bin/weather-sage
25
+ - lib/weather-sage.rb
26
+ - license.txt
27
+ - test/make-csvs.sh
28
+ - test/test-cache.rb
29
+ - test/test-census-geocoder.rb
30
+ - test/test-helpers.rb
31
+ - test/test-http-cache.rb
32
+ - test/test-weather-point.rb
33
+ homepage: https://github.com/pablotron/weather-sage
34
+ licenses:
35
+ - MIT
36
+ metadata:
37
+ bug_tracker_uri: https://github.com/pablotron/weather-sage/issues
38
+ documentation_uri: https://pablotron.github.io/weather-sage/
39
+ homepage_uri: https://github.com/pablotron/weather-sage
40
+ source_code_uri: https://github.com/pablotron/weather-sage
41
+ wiki_uri: https://github.com/pablotron/weather-sage/wiki
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubyforge_project:
58
+ rubygems_version: 2.7.6.2
59
+ signing_key:
60
+ specification_version: 4
61
+ summary: Get weather forecast for an address.
62
+ test_files: []