weather-sage 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/weather-sage/cache-entry.rb +13 -0
- data/lib/weather-sage/cache.rb +79 -0
- data/lib/weather-sage/census/geocoder.rb +48 -0
- data/lib/weather-sage/census/match.rb +19 -0
- data/lib/weather-sage/census.rb +7 -0
- data/lib/weather-sage/cli/commands/base-forecast.rb +70 -0
- data/lib/weather-sage/cli/commands/command.rb +42 -0
- data/lib/weather-sage/cli/commands/forecast.rb +28 -0
- data/lib/weather-sage/cli/commands/geocode.rb +51 -0
- data/lib/weather-sage/cli/commands/help.rb +82 -0
- data/lib/weather-sage/cli/commands/hourly.rb +28 -0
- data/lib/weather-sage/cli/commands/now.rb +87 -0
- data/lib/weather-sage/cli/commands/stations.rb +67 -0
- data/lib/weather-sage/cli/commands.rb +15 -0
- data/lib/weather-sage/cli/env/cache.rb +38 -0
- data/lib/weather-sage/cli/env/context.rb +29 -0
- data/lib/weather-sage/cli/env/env.rb +35 -0
- data/lib/weather-sage/cli/env/log.rb +41 -0
- data/lib/weather-sage/cli/env/vars.rb +20 -0
- data/lib/weather-sage/cli/env.rb +14 -0
- data/lib/weather-sage/cli/forecast.rb +70 -0
- data/lib/weather-sage/cli/help.rb +68 -0
- data/lib/weather-sage/cli.rb +29 -0
- data/lib/weather-sage/context.rb +13 -0
- data/lib/weather-sage/http/cache.rb +72 -0
- data/lib/weather-sage/http/error.rb +15 -0
- data/lib/weather-sage/http/fetcher.rb +79 -0
- data/lib/weather-sage/http/parser.rb +46 -0
- data/lib/weather-sage/http.rb +9 -0
- data/lib/weather-sage/weather/base-object.rb +36 -0
- data/lib/weather-sage/weather/forecast.rb +47 -0
- data/lib/weather-sage/weather/observation.rb +33 -0
- data/lib/weather-sage/weather/period.rb +44 -0
- data/lib/weather-sage/weather/point.rb +71 -0
- data/lib/weather-sage/weather/station.rb +43 -0
- data/lib/weather-sage/weather.rb +11 -0
- data/lib/weather-sage.rb +1 -1
- metadata +39 -3
- data/test/make-csvs.sh +0 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d8a169b0525d21c350826048cf01cc233741f1c1d897f88a1f2f05787aa38ad
|
4
|
+
data.tar.gz: 440a697b31b7e806c82afd0e572e313d1fb9e033a67cda205ba1c00be301bc6d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 80305e078b4be11c351509cb1aaef299ae47fb8db851f35a1ed1651319f2ff89b4dd3246c8a5b2a23ad01a87adddcb060b4353fc9001fbbcc0c997bac4df306f
|
7
|
+
data.tar.gz: b1f752c2f412cf374b85caec3563801d299ec17e94102276f71da9ac5be2fea5fe895c8329bbc9e2ca1c1ffd5f0fb579959282246a33bec97214e195e60ba94c
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'pstore'
|
2
|
+
|
3
|
+
#
|
4
|
+
# Minimal cache implementation.
|
5
|
+
#
|
6
|
+
class WeatherSage::Cache
|
7
|
+
#
|
8
|
+
# Create a new Cache instance bound to file at path +path+.
|
9
|
+
#
|
10
|
+
def initialize(path)
|
11
|
+
@pstore = ::PStore.new(path)
|
12
|
+
end
|
13
|
+
|
14
|
+
#
|
15
|
+
# Returns true if the key exists and is it still valid.
|
16
|
+
#
|
17
|
+
def key?(key)
|
18
|
+
@pstore.transaction(true) do
|
19
|
+
return false unless entry = @pstore[key]
|
20
|
+
entry.valid?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
#
|
25
|
+
# Set entry in cache with key +key+ to value +val+.
|
26
|
+
#
|
27
|
+
# An optional timeout (in seconds) may be provided with +timout+.
|
28
|
+
#
|
29
|
+
def set(key, val, timeout = nil)
|
30
|
+
@pstore.transaction do
|
31
|
+
# calculate expiration time
|
32
|
+
expires = timeout ? (Time.now.to_i + timeout) : nil
|
33
|
+
|
34
|
+
# save entry
|
35
|
+
@pstore[key] = ::WeatherSage::CacheEntry.new(expires, val)
|
36
|
+
|
37
|
+
# purge any expired entries
|
38
|
+
flush
|
39
|
+
end
|
40
|
+
|
41
|
+
# return value
|
42
|
+
val
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Get entry in cache with key +key+.
|
47
|
+
#
|
48
|
+
# Returns *nil* if no such entry exists.
|
49
|
+
#
|
50
|
+
def get(key)
|
51
|
+
@pstore.transaction(true) do
|
52
|
+
return nil unless entry = @pstore[key]
|
53
|
+
entry.valid? ? entry.value : nil
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# Delete entry in cache with key +key+.
|
59
|
+
#
|
60
|
+
def delete(key)
|
61
|
+
@pstore.transaction do
|
62
|
+
@pstore.delete(key)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
alias :[] :get
|
67
|
+
alias :[]= :set
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
#
|
72
|
+
# Purge expired entries.
|
73
|
+
#
|
74
|
+
def flush
|
75
|
+
@pstore.roots.each do |key|
|
76
|
+
@pstore.delete(key) unless @pstore[key].valid?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#
|
2
|
+
# Wrapper around Census geocoder API.
|
3
|
+
#
|
4
|
+
class WeatherSage::Census::Geocoder
|
5
|
+
#
|
6
|
+
# URL endpoint for Census Geocoder API.
|
7
|
+
#
|
8
|
+
URL = 'https://geocoding.geo.census.gov/geocoder/locations/onelineaddress'
|
9
|
+
|
10
|
+
#
|
11
|
+
# Static parameters for geocoder requests.
|
12
|
+
#
|
13
|
+
# Source:
|
14
|
+
# https://geocoding.geo.census.gov/geocoder/Geocoding_Services_API.pdf
|
15
|
+
PARAMS = {
|
16
|
+
returntype: 'locations',
|
17
|
+
benchmark: 'Public_AR_Current',
|
18
|
+
format: 'json',
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
#
|
22
|
+
# Create new Geocoder instance.
|
23
|
+
#
|
24
|
+
def initialize(ctx)
|
25
|
+
@ctx = ctx
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# Geocode given address string +s+ and return an array of Match
|
30
|
+
# objects.
|
31
|
+
#
|
32
|
+
def run(s)
|
33
|
+
# exec request
|
34
|
+
data = @ctx.cache.get(URL, PARAMS.merge({
|
35
|
+
address: s,
|
36
|
+
}))
|
37
|
+
|
38
|
+
# log data
|
39
|
+
@ctx.log.debug('Geocoder#run') do
|
40
|
+
'data = %p' % [data]
|
41
|
+
end
|
42
|
+
|
43
|
+
# map matches and return result
|
44
|
+
data['result']['addressMatches'].map { |row|
|
45
|
+
::WeatherSage::Census::Match.new(@ctx, row)
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#
|
2
|
+
# Matching address returned by Geocoder.
|
3
|
+
#
|
4
|
+
class WeatherSage::Census::Match
|
5
|
+
attr :point, :address, :data
|
6
|
+
|
7
|
+
#
|
8
|
+
# Create a new Match object.
|
9
|
+
#
|
10
|
+
def initialize(ctx, data)
|
11
|
+
# get coordinates
|
12
|
+
x, y = %w{x y}.map { |k| data['coordinates'][k] }
|
13
|
+
|
14
|
+
# cache data, address, and point
|
15
|
+
@data = data.freeze
|
16
|
+
@address = @data['matchedAddress']
|
17
|
+
@point = ::WeatherSage::Weather::Point.new(ctx, x, y).freeze
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
#
|
2
|
+
# Base forecast command.
|
3
|
+
#
|
4
|
+
# Used by ForecastCommand and HourlyCommand.
|
5
|
+
#
|
6
|
+
class WeatherSage::CLI::Commands::BaseForecastCommand < WeatherSage::CLI::Commands::Command
|
7
|
+
#
|
8
|
+
# Do not invoke this method directly; subclass this class and
|
9
|
+
# override the +run+ method.
|
10
|
+
#
|
11
|
+
def initialize(ctx, app)
|
12
|
+
super(ctx, app)
|
13
|
+
@forecast_method = self.class.const_get(:FORECAST_METHOD)
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Run command.
|
18
|
+
#
|
19
|
+
def run(args)
|
20
|
+
# get mode and args
|
21
|
+
mode, args = parse_args(args)
|
22
|
+
|
23
|
+
CSV(STDOUT) do |csv|
|
24
|
+
# write column names
|
25
|
+
csv << columns(mode).map { |col| col[:name] }
|
26
|
+
|
27
|
+
args.each do |arg|
|
28
|
+
# geocode argument, get first point
|
29
|
+
if pt = geocode(arg).first
|
30
|
+
# walk forecast periods
|
31
|
+
pt.point.send(@forecast_method).periods.each do |p|
|
32
|
+
csv << make_row(mode, arg, p)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
#
|
42
|
+
# Extract mode and args from command-line arguments.
|
43
|
+
#
|
44
|
+
def parse_args(args)
|
45
|
+
case args.first
|
46
|
+
when /^-f|--full$/
|
47
|
+
[:full, args[1 .. -1]]
|
48
|
+
when /^-b|--brief$/
|
49
|
+
[:brief, args[1 .. -1]]
|
50
|
+
else
|
51
|
+
[:brief, args]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Convert forecast period to CSV row.
|
57
|
+
#
|
58
|
+
def make_row(mode, address, p)
|
59
|
+
[address] + columns(mode).select { |col|
|
60
|
+
col[:prop]
|
61
|
+
}.map { |col| p.data[col[:prop]] }
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Get columns for given mode.
|
66
|
+
#
|
67
|
+
def columns(mode)
|
68
|
+
::WeatherSage::CLI::Forecast::columns(@forecast_method, mode)
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#
|
2
|
+
# Base class for command-line commands.
|
3
|
+
#
|
4
|
+
# You should subclass this class to create new commands.
|
5
|
+
#
|
6
|
+
class WeatherSage::CLI::Commands::Command
|
7
|
+
#
|
8
|
+
# Do not invoke this method directly; subclass Command and
|
9
|
+
# override the +run+ method.
|
10
|
+
#
|
11
|
+
def initialize(ctx, app)
|
12
|
+
@ctx, @app = ctx, app
|
13
|
+
|
14
|
+
# create geocoder
|
15
|
+
@geocoder = ::WeatherSage::Census::Geocoder.new(ctx)
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Run command.
|
20
|
+
#
|
21
|
+
def self.run(ctx, app, args)
|
22
|
+
new(ctx, app).run(args)
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Virtual method. You need to subclass and override this method
|
27
|
+
# to add a new command.
|
28
|
+
#
|
29
|
+
def run(args)
|
30
|
+
raise "not implemented"
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
#
|
36
|
+
# Geocode given street address and return array of
|
37
|
+
# Census::Geocode::Match results.
|
38
|
+
#
|
39
|
+
def geocode(s)
|
40
|
+
@geocoder.run(s)
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#
|
2
|
+
# Implementation of *forecast* command.
|
3
|
+
#
|
4
|
+
class WeatherSage::CLI::Commands::ForecastCommand < WeatherSage::CLI::Commands::BaseForecastCommand
|
5
|
+
#
|
6
|
+
# Help for this command.
|
7
|
+
#
|
8
|
+
# Used by the *help* command.
|
9
|
+
#
|
10
|
+
HELP = {
|
11
|
+
line: '
|
12
|
+
Get weather forecast for address.
|
13
|
+
'.strip,
|
14
|
+
|
15
|
+
full: [
|
16
|
+
'Get weather forecast for address.',
|
17
|
+
'',
|
18
|
+
'Use --full to see additional columns.',
|
19
|
+
].join("\n")
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
#
|
23
|
+
# Forecast method.
|
24
|
+
#
|
25
|
+
# Used by BaseForecastCommand to call correct forecast method.
|
26
|
+
#
|
27
|
+
FORECAST_METHOD = :forecast
|
28
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module WeatherSage
|
2
|
+
module CLI
|
3
|
+
module Commands
|
4
|
+
#
|
5
|
+
# Implementation of *geocode* command.
|
6
|
+
#
|
7
|
+
class GeocodeCommand < Command
|
8
|
+
#
|
9
|
+
# Help for this command.
|
10
|
+
#
|
11
|
+
# Used by the *help* command.
|
12
|
+
#
|
13
|
+
HELP = {
|
14
|
+
line: '
|
15
|
+
Geocode address.
|
16
|
+
'.strip,
|
17
|
+
|
18
|
+
full: [
|
19
|
+
'Geocode address.',
|
20
|
+
].join("\n"),
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
#
|
24
|
+
# CSV columns.
|
25
|
+
#
|
26
|
+
CSV_COLS = %w{input_address match_address x y}
|
27
|
+
|
28
|
+
#
|
29
|
+
# Entry point for *geocode* command-line command.
|
30
|
+
#
|
31
|
+
def run(args)
|
32
|
+
# create geocoder
|
33
|
+
geocoder = Census::Geocoder.new(@ctx)
|
34
|
+
|
35
|
+
CSV(STDOUT) do |csv|
|
36
|
+
# write column headers
|
37
|
+
csv << CSV_COLS
|
38
|
+
|
39
|
+
# iterate command-line arguments and geocode each one
|
40
|
+
args.each do |arg|
|
41
|
+
# geocode argument and write results to output CSV
|
42
|
+
geocoder.run(arg).each do |row|
|
43
|
+
csv << [arg, row.address, row.point.x, row.point.y]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#
|
2
|
+
# Implementation of *help* command-line command.
|
3
|
+
#
|
4
|
+
class WeatherSage::CLI::Commands::HelpCommand < ::WeatherSage::CLI::Commands::Command
|
5
|
+
#
|
6
|
+
# Help for this command.
|
7
|
+
#
|
8
|
+
# Used by the *help* command.
|
9
|
+
#
|
10
|
+
HELP = {
|
11
|
+
line: '
|
12
|
+
List commands.
|
13
|
+
'.strip,
|
14
|
+
|
15
|
+
full: [
|
16
|
+
'List commands. Use "help <command>" to show details for a',
|
17
|
+
'specific command.',
|
18
|
+
].join("\n"),
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
#
|
22
|
+
# Entry point for *help* command-line command.
|
23
|
+
#
|
24
|
+
def run(args)
|
25
|
+
if args.size > 0
|
26
|
+
# show details of given commands
|
27
|
+
show_details(args)
|
28
|
+
else
|
29
|
+
# print full list of commands
|
30
|
+
list_commands
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
#
|
37
|
+
# Print full list of commands.
|
38
|
+
#
|
39
|
+
def list_commands
|
40
|
+
puts template(:all, {
|
41
|
+
app: File.basename(@app),
|
42
|
+
|
43
|
+
cmds: ::WeatherSage::CLI::Help::COMMANDS.values.map { |cmd|
|
44
|
+
template(:cmd, cmd)
|
45
|
+
}.join("\n"),
|
46
|
+
|
47
|
+
envs: ::WeatherSage::CLI::Env::VARS.map { |row|
|
48
|
+
template(:env, row)
|
49
|
+
}.join("\n\n"),
|
50
|
+
})
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Print details of given commands.
|
55
|
+
#
|
56
|
+
def show_details(args)
|
57
|
+
# get a list of unknown commands
|
58
|
+
unknown_cmds = args.select do |arg|
|
59
|
+
!WeatherSage::CLI::Help::COMMANDS.key?(arg)
|
60
|
+
end
|
61
|
+
|
62
|
+
if unknown_cmds.size > 0
|
63
|
+
# print list of unknown commands and exit
|
64
|
+
puts 'Unknown commands: %s' % [unknown_cmds.join(', ')]
|
65
|
+
exit -1
|
66
|
+
end
|
67
|
+
|
68
|
+
# print detailed help for each argument
|
69
|
+
puts args.map { |arg| template(:one, commands[arg]) }
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Expand template.
|
74
|
+
#
|
75
|
+
def template(key, args = {})
|
76
|
+
unless WeatherSage::CLI::Help::TEMPLATES.key?(key)
|
77
|
+
raise "unknown template: #{key}"
|
78
|
+
end
|
79
|
+
|
80
|
+
WeatherSage::CLI::Help::TEMPLATES[key] % args
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#
|
2
|
+
# Implementation of *hourly* command.
|
3
|
+
#
|
4
|
+
class WeatherSage::CLI::Commands::HourlyCommand < WeatherSage::CLI::Commands::BaseForecastCommand
|
5
|
+
#
|
6
|
+
# Help for this command.
|
7
|
+
#
|
8
|
+
# Used by the *help* command.
|
9
|
+
#
|
10
|
+
HELP = {
|
11
|
+
line: '
|
12
|
+
Get hourly weather forecast for address.
|
13
|
+
'.strip,
|
14
|
+
|
15
|
+
full: [
|
16
|
+
'Get hourly weather forecast for address.',
|
17
|
+
'',
|
18
|
+
'Use --full to see additional columns.',
|
19
|
+
].join("\n")
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
#
|
23
|
+
# Forecast method.
|
24
|
+
#
|
25
|
+
# Used by BaseForecastCommand to call correct forecast method.
|
26
|
+
#
|
27
|
+
FORECAST_METHOD = :hourly_forecast
|
28
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
#
|
2
|
+
# Implementation of *now* command-line command.
|
3
|
+
#
|
4
|
+
class WeatherSage::CLI::Commands::NowCommand < WeatherSage::CLI::Commands::Command
|
5
|
+
#
|
6
|
+
# Help for this command.
|
7
|
+
#
|
8
|
+
# Used by the *help* command.
|
9
|
+
#
|
10
|
+
HELP = {
|
11
|
+
line: '
|
12
|
+
Get current weather from station closest to address.
|
13
|
+
'.strip,
|
14
|
+
|
15
|
+
full: [
|
16
|
+
'Get current weather at from station closest to address.',
|
17
|
+
].join("\n")
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
#
|
21
|
+
# CSV column names.
|
22
|
+
#
|
23
|
+
COL_NAMES = %w{
|
24
|
+
address
|
25
|
+
name
|
26
|
+
type
|
27
|
+
value
|
28
|
+
unit
|
29
|
+
quality_control
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
#
|
33
|
+
# Run *now* command.
|
34
|
+
#
|
35
|
+
def run(args)
|
36
|
+
CSV(STDOUT) do |csv|
|
37
|
+
# write column names
|
38
|
+
csv << COL_NAMES
|
39
|
+
|
40
|
+
# iterate over command-line arguments and write each one
|
41
|
+
args.each do |arg|
|
42
|
+
# geocode to first point
|
43
|
+
if pt = geocode(arg).first
|
44
|
+
# get first station
|
45
|
+
if st = pt.point.stations.first
|
46
|
+
# get latest observation data
|
47
|
+
data = st.latest_observations
|
48
|
+
|
49
|
+
# write observations
|
50
|
+
make_rows(arg, data) do |row|
|
51
|
+
csv << row
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
#
|
62
|
+
# Map observation properties in result to CSV rows and yield each
|
63
|
+
# row.
|
64
|
+
#
|
65
|
+
# FIXME: this is a bit of a hack.
|
66
|
+
#
|
67
|
+
def make_rows(address, data, &block)
|
68
|
+
::WeatherSage::Weather::Observation::PROPERTIES.each do |key, type|
|
69
|
+
# get observation
|
70
|
+
if v = data[key.to_s]
|
71
|
+
# map observation to row, then yield row
|
72
|
+
block.call(case type
|
73
|
+
when :text, :time, :url
|
74
|
+
[address, key, type, v]
|
75
|
+
when :value
|
76
|
+
[address, key, type, v['value'], v['unitCode'], v['qualityControl']]
|
77
|
+
when :cloud
|
78
|
+
# hack: only show data for first cloud layer
|
79
|
+
base = v.first['base']
|
80
|
+
[address, key, type, base['value'], base['unitCode']]
|
81
|
+
else
|
82
|
+
raise "unkown type: #{type}"
|
83
|
+
end)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module WeatherSage
|
2
|
+
module CLI
|
3
|
+
module Commands
|
4
|
+
#
|
5
|
+
# Implementation of *stations* command.
|
6
|
+
#
|
7
|
+
class StationsCommand < Command
|
8
|
+
#
|
9
|
+
# Help for this command.
|
10
|
+
#
|
11
|
+
# Used by the *help* command.
|
12
|
+
#
|
13
|
+
HELP = {
|
14
|
+
line: '
|
15
|
+
List weather stations near address.
|
16
|
+
'.strip,
|
17
|
+
|
18
|
+
full: [
|
19
|
+
'List weather stations near address.',
|
20
|
+
].join("\n")
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
#
|
24
|
+
# CSV column names.
|
25
|
+
#
|
26
|
+
COL_NAMES = %w{
|
27
|
+
address
|
28
|
+
station_id
|
29
|
+
station_name
|
30
|
+
x
|
31
|
+
y
|
32
|
+
elevation
|
33
|
+
time_zone
|
34
|
+
}.freeze
|
35
|
+
|
36
|
+
#
|
37
|
+
# Run *stations* command.
|
38
|
+
#
|
39
|
+
def run(args)
|
40
|
+
CSV(STDOUT) do |csv|
|
41
|
+
# write column names
|
42
|
+
csv << COL_NAMES
|
43
|
+
|
44
|
+
args.each do |arg|
|
45
|
+
# geocode argument, get first point
|
46
|
+
if pt = geocode(arg).first
|
47
|
+
# walk stations
|
48
|
+
pt.point.stations.each do |s|
|
49
|
+
csv << make_row(arg, s)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
#
|
59
|
+
# Convert station to CSV row.
|
60
|
+
#
|
61
|
+
def make_row(address, s)
|
62
|
+
[address, s.id, s.name, s.x, s.y, s.elevation, s.time_zone]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
#
|
2
|
+
# Namespace for command-line commands.
|
3
|
+
#
|
4
|
+
module WeatherSage::CLI::Commands
|
5
|
+
LIB_DIR = File.join(__dir__, 'commands') # :nodoc:
|
6
|
+
|
7
|
+
autoload :Command, File.join(LIB_DIR, 'command.rb')
|
8
|
+
autoload :HelpCommand, File.join(LIB_DIR, 'help.rb')
|
9
|
+
autoload :GeocodeCommand, File.join(LIB_DIR, 'geocode.rb')
|
10
|
+
autoload :NowCommand, File.join(LIB_DIR, 'now.rb')
|
11
|
+
autoload :StationsCommand, File.join(LIB_DIR, 'stations.rb')
|
12
|
+
autoload :BaseForecastCommand, File.join(LIB_DIR, 'base-forecast.rb')
|
13
|
+
autoload :ForecastCommand, File.join(LIB_DIR, 'forecast.rb')
|
14
|
+
autoload :HourlyCommand, File.join(LIB_DIR, 'hourly.rb')
|
15
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
#
|
4
|
+
# Create HTTP::Cache fron environment variables.
|
5
|
+
#
|
6
|
+
class WeatherSage::CLI::Env::Cache < ::WeatherSage::HTTP::Cache
|
7
|
+
#
|
8
|
+
# Default cache path.
|
9
|
+
#
|
10
|
+
DEFAULT_PATH = '~/.config/weather-sage/http-cache.pstore'
|
11
|
+
|
12
|
+
#
|
13
|
+
# Create HTTP::Cache fron environment variables.
|
14
|
+
#
|
15
|
+
# Uses the following environment variables:
|
16
|
+
#
|
17
|
+
# - WEATHER_SAGE_CACHE_PATH: Path to HTTP cache file. Defaults to
|
18
|
+
# "~/.config/weather-sage/http-cache.pstore".
|
19
|
+
#
|
20
|
+
def initialize(env, log)
|
21
|
+
# get cache path
|
22
|
+
unless path = env.get('CACHE_PATH')
|
23
|
+
# use default cache path
|
24
|
+
path = File.expand_path(DEFAULT_PATH)
|
25
|
+
|
26
|
+
# create parent directories (if necessary)
|
27
|
+
FileUtils.mkdir_p(File.dirname(path))
|
28
|
+
end
|
29
|
+
|
30
|
+
# log cache path
|
31
|
+
log.info('Env::Cache#initialize') do
|
32
|
+
'path = %p' % [path]
|
33
|
+
end
|
34
|
+
|
35
|
+
# return cache instance
|
36
|
+
super(path, log)
|
37
|
+
end
|
38
|
+
end
|