weather_jp 1.0.4 → 2.0.0
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/.coveralls.yml +1 -0
- data/.gitignore +1 -0
- data/.travis.yml +3 -5
- data/CHANGELOG.md +20 -0
- data/README.md +33 -36
- data/Rakefile +1 -1
- data/lib/weather_jp.rb +26 -191
- data/lib/weather_jp/adapter.rb +89 -0
- data/lib/weather_jp/city.rb +52 -0
- data/lib/weather_jp/day_weather.rb +124 -38
- data/lib/weather_jp/request_parser.rb +42 -0
- data/lib/weather_jp/version.rb +1 -1
- data/lib/weather_jp/weather.rb +82 -0
- data/spec/fixture/not_found.xml +1 -0
- data/spec/fixture/tokyo.xml +1 -0
- data/spec/spec_helper.rb +32 -28
- data/spec/weather_jp/adapter_spec.rb +44 -0
- data/spec/weather_jp/city_spec.rb +35 -0
- data/spec/weather_jp/day_weather_spec.rb +91 -0
- data/spec/weather_jp/request_parser_spec.rb +31 -0
- data/spec/weather_jp/weather_spec.rb +51 -0
- data/spec/weather_jp_spec.rb +10 -44
- data/weather_jp.gemspec +8 -3
- metadata +108 -30
- data/HISTORY.md +0 -16
- data/README.jp.md +0 -107
- data/spec/day_weather_spec.rb +0 -69
- data/spec/fixture/RSS.rss +0 -1
- data/spec/fixture/ny.rss +0 -33
- data/spec/weather_spec.rb +0 -125
- data/spec/wrapper_spec.rb +0 -56
@@ -0,0 +1,52 @@
|
|
1
|
+
module WeatherJp
|
2
|
+
# @attr_reader [String] name
|
3
|
+
class City
|
4
|
+
# Canonical key names for attributes.
|
5
|
+
KEYS = %i(name full_name code latitude longitude)
|
6
|
+
|
7
|
+
attr_reader :name
|
8
|
+
|
9
|
+
# @param [String] name
|
10
|
+
# @param [Hash] attrs
|
11
|
+
def initialize(name, attrs = {})
|
12
|
+
@name = name
|
13
|
+
@attrs = attrs
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [String]
|
17
|
+
def full_name
|
18
|
+
@attrs['weatherlocationname']
|
19
|
+
end
|
20
|
+
|
21
|
+
# i.e. 'JAXX0085'
|
22
|
+
# @return [String]
|
23
|
+
def code
|
24
|
+
if str = @attrs['weatherlocationcode']
|
25
|
+
str.split(':').last
|
26
|
+
else
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
alias_method :area_code, :code
|
31
|
+
alias_method :location_code, :code
|
32
|
+
|
33
|
+
# @return [Float]
|
34
|
+
def latitude
|
35
|
+
@attrs['lat'].try(:to_f)
|
36
|
+
end
|
37
|
+
alias_method :lat, :latitude
|
38
|
+
|
39
|
+
# @return [Float]
|
40
|
+
def longitude
|
41
|
+
@attrs['long'].try(:to_f)
|
42
|
+
end
|
43
|
+
alias_method :long, :longitude
|
44
|
+
|
45
|
+
alias_method :to_s, :name
|
46
|
+
|
47
|
+
# @return [Hash]
|
48
|
+
def to_hash
|
49
|
+
Hash[KEYS.map {|k| [k, public_send(k)] }]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,51 +1,137 @@
|
|
1
|
-
# -*- coding:utf-8 -*-
|
2
|
-
|
3
1
|
module WeatherJp
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
# @attr_reader [WeatherJp::City] city
|
3
|
+
# @attr_reader [Integer] date_code Current is 0, today is 1, tomorrow is 2
|
4
|
+
class DayWeather
|
5
|
+
extend Forwardable
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
@city_name = city_name
|
11
|
-
end
|
7
|
+
# Canonical key names for attributes.
|
8
|
+
KEYS = %i(text high low rain date date_text date_code city_name city)
|
12
9
|
|
13
|
-
|
10
|
+
attr_reader :city, :date_code
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
# @param [Hash] attrs
|
13
|
+
# @param [WeatherJp::City] city
|
14
|
+
# @param [Integer] date_code
|
15
|
+
def initialize(attrs, city, date_code)
|
16
|
+
@attrs = attrs
|
17
|
+
@city = city
|
18
|
+
@date_code = date_code
|
19
|
+
end
|
18
20
|
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
# @return [String]
|
22
|
+
def city_name
|
23
|
+
city.name
|
24
|
+
end
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
+
# Sky status. i.e. 'sunny' or '晴れ'.
|
27
|
+
# @return [String]
|
28
|
+
def text
|
29
|
+
@attrs['skytext'] || @attrs['skytextday']
|
30
|
+
end
|
26
31
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
word << "@rain=#{rain}>"
|
32
|
-
word
|
33
|
-
end
|
32
|
+
# @return [Integer, nil]
|
33
|
+
def high
|
34
|
+
get('high').try(:to_i)
|
35
|
+
end
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
word << "最高気温#{max_temp}度 " if max_temp
|
40
|
-
word << "最低気温#{min_temp}度 " if min_temp
|
41
|
-
word << "降水確率は#{rain}% " if rain
|
42
|
-
word << "です。"
|
43
|
-
word
|
44
|
-
end
|
37
|
+
# @return [Integer, nil]
|
38
|
+
def low
|
39
|
+
get('low').try(:to_i)
|
40
|
+
end
|
45
41
|
|
46
|
-
|
47
|
-
|
42
|
+
# @return [Integer, nil]
|
43
|
+
def precip
|
44
|
+
get('precip').try(:to_i)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Only available for current weather.
|
48
|
+
# @return [Integer, nil]
|
49
|
+
def temperature
|
50
|
+
get('temperature').try(:to_i)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Only available for current weather.
|
54
|
+
# @return [Integer, nil]
|
55
|
+
def wind_speed
|
56
|
+
get('windspeed').try(:to_i)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Only available for current weather.
|
60
|
+
# i.e. '風向: 北北西 / 風速: 20 マイル'
|
61
|
+
# @return [String, nil]
|
62
|
+
def wind_text
|
63
|
+
get('winddisplay')
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Date, nil]
|
67
|
+
def date
|
68
|
+
raw_date ? Date.parse(raw_date) : nil
|
69
|
+
end
|
70
|
+
|
71
|
+
# Japanese date text from current day.
|
72
|
+
# @return [String, nil]
|
73
|
+
def date_text
|
74
|
+
case date_code
|
75
|
+
when -1
|
76
|
+
"今"
|
77
|
+
when 0
|
78
|
+
"今日"
|
79
|
+
when 1
|
80
|
+
"明日"
|
81
|
+
when 2
|
82
|
+
"明後日"
|
83
|
+
else
|
84
|
+
"#{date_code}日後"
|
48
85
|
end
|
49
86
|
end
|
87
|
+
|
88
|
+
# @return [String]
|
89
|
+
def to_s
|
90
|
+
word ="#{date_text}の"
|
91
|
+
word << "#{city_name}の天気は"
|
92
|
+
word << "#{text}"
|
93
|
+
word << ", 最高気温#{high}度" if high
|
94
|
+
word << ", 最低気温#{low}度" if low
|
95
|
+
word << ", 降水確率は#{precip}%" if precip
|
96
|
+
word << "です。"
|
97
|
+
word
|
98
|
+
end
|
99
|
+
|
100
|
+
# Same as {Hash#each}
|
101
|
+
def_delegators :each, :to_hash
|
102
|
+
|
103
|
+
# @return [Hash]
|
104
|
+
def to_hash
|
105
|
+
h = Hash[KEYS.map {|k| [k, public_send(k)] }]
|
106
|
+
h[:city] = h[:city].to_hash
|
107
|
+
h
|
108
|
+
end
|
109
|
+
|
110
|
+
# @deprecated Use {#high}
|
111
|
+
alias_method :max_temp, :high
|
112
|
+
|
113
|
+
# @deprecated Use {#low}
|
114
|
+
alias_method :min_temp, :low
|
115
|
+
|
116
|
+
# @deprecated Use {#text}
|
117
|
+
alias_method :forecast, :text
|
118
|
+
|
119
|
+
# @deprecated Use {#date_text}
|
120
|
+
alias_method :day, :date_text
|
121
|
+
|
122
|
+
# @deprecated Use {#precip}
|
123
|
+
alias_method :rain, :precip
|
124
|
+
|
125
|
+
private
|
126
|
+
|
127
|
+
# Convert empty string to nil
|
128
|
+
def get(key)
|
129
|
+
@attrs[key].present? ? @attrs[key] : nil
|
130
|
+
end
|
131
|
+
|
132
|
+
# @return [String]
|
133
|
+
def raw_date
|
134
|
+
get('date')
|
135
|
+
end
|
50
136
|
end
|
51
137
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module WeatherJp
|
2
|
+
module RequestParser
|
3
|
+
class Request < Struct.new(:city, :day)
|
4
|
+
end
|
5
|
+
|
6
|
+
class << self
|
7
|
+
dates_regexp = "今日|きょう|明日|あした|明後日|あさって|今|いま|3日後|3日後|4日後|4日後"
|
8
|
+
RequestMatcher = /
|
9
|
+
(
|
10
|
+
(?<city>.*)の(?<day>#{dates_regexp})の(天気|てんき).*
|
11
|
+
)|(
|
12
|
+
(?<day>#{dates_regexp})の(?<city>.*)の(天気|てんき)
|
13
|
+
)
|
14
|
+
/ux
|
15
|
+
|
16
|
+
def parser(str)
|
17
|
+
if str =~ RequestMatcher
|
18
|
+
data = Regexp.last_match
|
19
|
+
day = data[:day]
|
20
|
+
case day
|
21
|
+
when /今日|きょう/u
|
22
|
+
day = :today
|
23
|
+
when /明日|あした/u
|
24
|
+
day = :tomorrow
|
25
|
+
when /明後日|あさって/u
|
26
|
+
day = :day_after_tomorrow
|
27
|
+
when /3日後|3日後/
|
28
|
+
day = 3
|
29
|
+
when /4日後|4日後/
|
30
|
+
day = 4
|
31
|
+
when /今|いま/
|
32
|
+
day = :current
|
33
|
+
end
|
34
|
+
|
35
|
+
Request.new(data[:city], day)
|
36
|
+
else
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/weather_jp/version.rb
CHANGED
@@ -0,0 +1,82 @@
|
|
1
|
+
module WeatherJp
|
2
|
+
# @attr_reader [WeatherJp::City] city
|
3
|
+
# @attr_reader [Array<WeatherJp::DayWeather>] weathers
|
4
|
+
# @attr_reader [WeatherJp::DayWeather] current
|
5
|
+
# @attr_reader [WeatherJp::DayWeather] today
|
6
|
+
# @attr_reader [WeatherJp::DayWeather] tomorrow
|
7
|
+
# @attr_reader [WeatherJp::DayWeather] day_after_tomorrow
|
8
|
+
class Weather
|
9
|
+
class << self
|
10
|
+
private
|
11
|
+
# Define extra readers
|
12
|
+
def define_readers(*names)
|
13
|
+
names.each {|s| define_method(s) { self.for(s) } }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
extend Forwardable
|
18
|
+
include Enumerable
|
19
|
+
|
20
|
+
define_readers :current, :today, :tomorrow, :day_after_tomorrow
|
21
|
+
attr_reader :city, :weathers
|
22
|
+
alias_method :day_weathers, :weathers
|
23
|
+
alias_method :to_a, :weathers
|
24
|
+
|
25
|
+
# @param [WeatherJp::City] city
|
26
|
+
# @param [Array<WeatherJp::DayWeather>] weathers
|
27
|
+
def initialize(city, weathers)
|
28
|
+
@city = city
|
29
|
+
@weathers = weathers
|
30
|
+
end
|
31
|
+
|
32
|
+
# @yield [WeatherJp::DayWeather]
|
33
|
+
# @return [WeatherJp::Weather]
|
34
|
+
def each(&block)
|
35
|
+
each_with_all(&block)
|
36
|
+
end
|
37
|
+
|
38
|
+
# @yield [WeatherJp::DayWeather]
|
39
|
+
# @return [WeatherJp::Weather]
|
40
|
+
def each_with_all
|
41
|
+
weathers.each {|i| yield i }
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
# @yield [WeatherJp::DayWeather]
|
46
|
+
# @return [WeatherJp::Weather]
|
47
|
+
def each_without_current
|
48
|
+
except_current.each {|i| yield i }
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
# Except current weather status.
|
53
|
+
# @return [Array<WeatherJp::DayWeather>]
|
54
|
+
def except_current
|
55
|
+
weathers.reject {|w| w.date_code == -1 }
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [String, Symbol, Integer] date
|
59
|
+
# @return [WeatherJp::DayWeather, nil]
|
60
|
+
def for(date)
|
61
|
+
case date
|
62
|
+
when Date
|
63
|
+
raise NotImplementedError
|
64
|
+
when :current
|
65
|
+
day = 0
|
66
|
+
when :today
|
67
|
+
day = 1
|
68
|
+
when :tomorrow
|
69
|
+
day = 2
|
70
|
+
when :day_after_tomorrow
|
71
|
+
day = 3
|
72
|
+
else
|
73
|
+
day = date + 1
|
74
|
+
end
|
75
|
+
|
76
|
+
weathers[day]
|
77
|
+
end
|
78
|
+
|
79
|
+
# @deprecated Use {#for}
|
80
|
+
alias_method :get_weather, :for
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?><weatherdata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
|
@@ -0,0 +1 @@
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?><weatherdata xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><weather weatherlocationcode="wc:JAXX0085" weatherlocationname="東京都 東京" zipcode="" weathercityname="東京" url="http://weather.jp.msn.com/local.aspx?wealocations=wc:JAXX0085&q=%e6%9d%b1%e4%ba%ac%e9%83%bd+%e6%9d%b1%e4%ba%ac" imagerelativeurl="http://wst.s-msn.com/i/ja/" degreetype="F" provider="Foreca" attribution="提供 Foreca" attribution2="© Foreca" lat="35.6751957" long="139.7695956" timezone="9" alert="" entityid="33568" encodedlocationname="%e6%9d%b1%e4%ba%ac%e9%83%bd+%e6%9d%b1%e4%ba%ac"><current temperature="41" skycode="33" skytext="晴時々曇" date="2015-01-17" observationtime="19:30:00" observationpoint="Tokyo International Airport" feelslike="32" humidity="27" winddisplay="風向: 北北西 / 風速: 20 マイル" day="土曜日" shortday="土" windspeed="20" /><forecast low="" high="" skycodeday="27" jpskycode="81" skytextday="曇のち晴" date="2015-01-17" day="土曜日" shortday="土" precip="10" /><forecast low="34" high="52" skycodeday="32" jpskycode="32" skytextday="晴れ" date="2015-01-18" day="日曜日" shortday="日" precip="0" /><forecast low="32" high="55" skycodeday="30" jpskycode="48" skytextday="晴時々曇" date="2015-01-19" day="月曜日" shortday="月" precip="10" /><forecast low="37" high="50" skycodeday="30" jpskycode="48" skytextday="晴時々曇" date="2015-01-20" day="火曜日" shortday="火" precip="10" /><forecast low="34" high="46" skycodeday="26" jpskycode="26" skytextday="曇り" date="2015-01-21" day="水曜日" shortday="水" precip="40" /><toolbar timewindow="60" minversion="1.0.1965.0" /></weather></weatherdata>
|
data/spec/spec_helper.rb
CHANGED
@@ -1,38 +1,42 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
1
|
+
require 'webmock/rspec'
|
2
|
+
require 'pry'
|
4
3
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def get_area_code(city_name)
|
11
|
-
["JAXX0085", 'tokyo']
|
12
|
-
end
|
4
|
+
# Use RSpec context
|
5
|
+
module Fixturable
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
end
|
13
9
|
|
14
|
-
|
15
|
-
|
10
|
+
module ClassMethods
|
11
|
+
def set_fixture(name)
|
12
|
+
before do
|
13
|
+
allow(WeatherJp::Adapter::Reader).to receive(:read).
|
14
|
+
and_return(fixture_for(name))
|
15
|
+
end
|
16
16
|
end
|
17
17
|
end
|
18
|
-
end
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
# @return [String]
|
20
|
+
def fixture_for(name)
|
21
|
+
File.open(File.join(fixture_path, name)).read
|
22
|
+
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
26
|
-
open(fixture_path + '/RSS.rss') do |file|
|
27
|
-
rss = RSS::Parser.parse(file.read)
|
24
|
+
def fixture_path
|
25
|
+
File.expand_path(File.join('..', 'fixture'), __FILE__)
|
28
26
|
end
|
29
|
-
rss
|
30
27
|
end
|
31
28
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
require 'simplecov'
|
30
|
+
require 'coveralls'
|
31
|
+
|
32
|
+
Coveralls.wear!
|
33
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
34
|
+
SimpleCov::Formatter::HTMLFormatter,
|
35
|
+
Coveralls::SimpleCov::Formatter
|
36
|
+
]
|
37
|
+
SimpleCov.start do
|
38
|
+
add_filter '/spec'
|
38
39
|
end
|
40
|
+
|
41
|
+
$:.unshift File.expand_path(File.join('..', '..', 'lib'), __FILE__)
|
42
|
+
require 'weather_jp'
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe WeatherJp::Adapter do
|
4
|
+
describe '.get' do
|
5
|
+
include Fixturable
|
6
|
+
|
7
|
+
context 'when found' do
|
8
|
+
set_fixture 'tokyo.xml'
|
9
|
+
subject { described_class.get('tokyo') }
|
10
|
+
it { is_expected.to be_kind_of(WeatherJp::Weather) }
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'when not found' do
|
14
|
+
set_fixture 'not_found.xml'
|
15
|
+
subject { described_class.get('no_found_city') }
|
16
|
+
it { is_expected.to be_nil }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe WeatherJp::Adapter::Reader do
|
21
|
+
describe '.read' do
|
22
|
+
subject { described_class.read('http://example.com') }
|
23
|
+
|
24
|
+
context 'with valid condition' do
|
25
|
+
before { stub_request(:get, 'http://example.com') }
|
26
|
+
it { is_expected.to eq('') }
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with server down' do
|
30
|
+
before { stub_request(:get, 'http://example.com').to_return(status: 500) }
|
31
|
+
specify do
|
32
|
+
expect { subject }.to raise_error(WeatherJp::ServiceUnavailable, /500/)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with server maintenance' do
|
37
|
+
before { stub_request(:get, 'http://example.com').to_return(status: 503) }
|
38
|
+
specify do
|
39
|
+
expect { subject }.to raise_error(WeatherJp::ServiceUnavailable, /503/)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|