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.
@@ -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