weather_jp 1.0.4 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|