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.
@@ -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
- class Weather
5
- class DayWeather
6
- include Enumerable
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
- def initialize(weathers, city_name, day)
9
- @weather = weathers[day]
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
- attr_reader :city_name
10
+ attr_reader :city, :date_code
14
11
 
15
- def to_hash
16
- @weather
17
- end
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
- def each
20
- @weather.each {|_k, v| yield v }
21
- end
21
+ # @return [String]
22
+ def city_name
23
+ city.name
24
+ end
22
25
 
23
- def each_pair
24
- @weather.each_pair {|k,v| yield k, v }
25
- end
26
+ # Sky status. i.e. 'sunny' or '晴れ'.
27
+ # @return [String]
28
+ def text
29
+ @attrs['skytext'] || @attrs['skytextday']
30
+ end
26
31
 
27
- def inspect
28
- word = "\#<DayWeather:@city_name = #{city_name}, "
29
- word << "@day=#{day}, @forecast=#{forecast}, "
30
- word << "@max_temp=#{max_temp}, @min_temp=#{min_temp}, "
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
- def to_s
36
- word = "#{city_name}の"
37
- word << "#{day}の天気は"
38
- word << "#{forecast} "
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
- def method_missing(key)
47
- @weather[key]
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
@@ -1,3 +1,3 @@
1
1
  module WeatherJp
2
- VERSION = "1.0.4"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -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&amp;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>
@@ -1,38 +1,42 @@
1
- # -*- coding: utf-8 -*-
2
- $LOAD_PATH.unshift File.expand_path("../", __FILE__)
3
- $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
1
+ require 'webmock/rspec'
2
+ require 'pry'
4
3
 
5
- require 'nokogiri'
6
- require 'weather_jp'
7
-
8
- module WeatherJp::Wrapper
9
- class << self
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
- def get_rss(area_code)
15
- get_dummy_rss
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
- def fixture_path
21
- File.expand_path('../fixture/', __FILE__)
22
- end
19
+ # @return [String]
20
+ def fixture_for(name)
21
+ File.open(File.join(fixture_path, name)).read
22
+ end
23
23
 
24
- def get_dummy_rss
25
- rss = ''
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
- def get_ny_rss
33
- rss = ''
34
- open fixture_path + '/ny.rss' do |file|
35
- rss = RSS::Parser.parse file.read
36
- end
37
- rss
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