weather-sage 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|