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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 88a46156ea60f987d673c2040bd87524a787b34a
|
4
|
+
data.tar.gz: cc561056b3ee51d0e175eadf8f228e875e3ee9ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be3cb9d733a173cd41e077d525acca595f03e64722fa6cf700bd22c9e5f2941ac9196b0752cf7415d67d0ef1436526f736aa88f1df042643c707591e113f13c5
|
7
|
+
data.tar.gz: 0acb01fb7aa2d9031fb0c98267b93f98a84de529516943493354bcd8fd943bc118203ddee7aa64c0ec1c7d382acc0f813256bf2d888b8d11e2fb6c7def750c96
|
data/.coveralls.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
service_name: travis-ci
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# CHANGELOG
|
2
|
+
## 2.0.0
|
3
|
+
- `WeatherJp::City` now available. You can get latitude/longitude of the city.
|
4
|
+
- `WeatherJp::Weather#current`. The current weather is a little bit differrnt from day weather forecast.
|
5
|
+
- have: `temperature`, `wind_speed`, `wind_text`
|
6
|
+
- not have: `precip`, `high`, `low`.
|
7
|
+
- `WeatherJp::Weather#each` now contains the `DayWeather` object which represents current weather status. If you do not want current weather status, use `each_without_current` or `except_current` method.
|
8
|
+
- Remove `customize_to_s` method. This feature will be re-added as configurable procedure.
|
9
|
+
- Remove option from `WeatherJp.get`. If you use these option, use `WeatherJp::Weather#for`.
|
10
|
+
- Some deprecations for method names.
|
11
|
+
|
12
|
+
## 1.0.4
|
13
|
+
Bug fixes
|
14
|
+
|
15
|
+
- Rain probability and tempratures are returned as Fixnum. [Sho Hashimoto - @shokai]
|
16
|
+
- Can handle tempratures below zero. [Sho Hashimoto - @shokai]
|
17
|
+
|
18
|
+
## 1.0.0
|
19
|
+
- Add parse method to WeatherJp
|
20
|
+
- see READEME for more description
|
data/README.md
CHANGED
@@ -1,11 +1,20 @@
|
|
1
|
-
# weather_jp [![Build Status](https://
|
2
|
-
Japan weather
|
1
|
+
# weather_jp [![Build Status](https://travis-ci.org/taiki45/weather_jp.svg?branch=master)](https://travis-ci.org/taiki45/weather_jp) [![Coverage Status](https://coveralls.io/repos/taiki45/weather_jp/badge.svg?branch=master)](https://coveralls.io/r/taiki45/weather_jp?branch=master) [![Code Climate](https://codeclimate.com/github/taiki45/weather_jp/badges/gpa.svg)](https://codeclimate.com/github/taiki45/weather_jp) [![Gem Version](https://badge.fury.io/rb/weather_jp.svg)](http://badge.fury.io/rb/weather_jp)
|
2
|
+
Japan weather infomation API wrapper. Fetch Japan weather status or forecast as Ruby object easily.
|
3
3
|
|
4
|
-
|
4
|
+
## V2 upgrade guides
|
5
|
+
The backend server returns differrnt contents from before. So V1 is not working. Please update to V2.
|
5
6
|
|
6
|
-
|
7
|
+
And V2 contains some of new features, deprecations and removales.
|
7
8
|
|
8
|
-
|
9
|
+
- `WeatherJp::City` now available. You can get latitude/longitude of the city.
|
10
|
+
- `WeatherJp::Weather#current`. The current weather is a little bit differrnt from day weather forecast.
|
11
|
+
- have: `temperature`, `wind_speed`, `wind_text`
|
12
|
+
- not have: `precip`, `high`, `low`.
|
13
|
+
- `WeatherJp::Weather#each` now contains the `DayWeather` object which represents current weather status. If you do not want current weather status, use `each_without_current` or `except_current` method.
|
14
|
+
- Remove `customize_to_s` method. This feature will be re-added as configurable procedure.
|
15
|
+
- Remove option from `WeatherJp.get`. If you use these option, use `WeatherJp::Weather#for`.
|
16
|
+
|
17
|
+
See deprecations at [API document page](http://rubydoc.info/gems/weather_jp/).
|
9
18
|
|
10
19
|
## Installation
|
11
20
|
|
@@ -27,7 +36,6 @@ Or install it yourself as:
|
|
27
36
|
# creat weather object in differrnt ways
|
28
37
|
tokyo = WeatherJp.get :tokyo
|
29
38
|
akiba = WeatherJp.get "秋葉原"
|
30
|
-
abuja = WeatherJp::Weather.new("アブジャ")
|
31
39
|
tsuyama = WeatherJp.get "津山"
|
32
40
|
|
33
41
|
# get weather info as String
|
@@ -35,53 +43,42 @@ tokyo.today.to_s
|
|
35
43
|
#=> can be "東京都 東京の天気は曇りのち晴れ、最高気温34度...etc"
|
36
44
|
|
37
45
|
# or your have unparsed string
|
38
|
-
WeatherJp.parse("
|
46
|
+
WeatherJp.parse("今日の香川県の天気教えて下さい").to_s
|
39
47
|
#=> "香川県 高松の今日の天気は曇のち晴れ 最高気温25度 最低気温17度 降水確率は20% です。"
|
40
48
|
|
41
49
|
# to get weather info in differrnt ways
|
42
|
-
|
43
|
-
tokyo.
|
44
|
-
tokyo.get_weather(:tomorrow).rain
|
50
|
+
tokyo.today.text #=> can be "晴れ"
|
51
|
+
tokyo.tomorrow.precip #=> can be 10
|
45
52
|
akiba.day_after_tomorrow.to_s
|
46
|
-
WeatherJp.get(:tokyo, :today).forecast
|
47
53
|
|
48
|
-
#
|
54
|
+
# yields DayWeather object
|
49
55
|
tokyo.each do |w|
|
50
56
|
puts w.city_name
|
51
|
-
puts w.
|
52
|
-
puts w.
|
53
|
-
puts w.
|
54
|
-
puts w.
|
55
|
-
puts w.
|
56
|
-
w.
|
57
|
+
puts w.date
|
58
|
+
puts w.date_text
|
59
|
+
puts w.text
|
60
|
+
puts w.high
|
61
|
+
puts w.low
|
62
|
+
puts w.precip
|
57
63
|
end
|
58
64
|
|
59
|
-
|
65
|
+
# You can use WeatherJp::City object
|
66
|
+
tokyo.city => WeatherJp::City
|
67
|
+
[tokyo.city.longitude, tokyo.city.latitude]
|
68
|
+
|
69
|
+
akiba.map {|w| [w.date_text, w.text] }
|
60
70
|
|
61
71
|
# or use as simple Array or Hash
|
62
72
|
tokyo.to_a
|
73
|
+
akiba.weathers
|
63
74
|
tsuyama.each {|w| p w.to_hash }
|
64
|
-
akiba.day_weathers
|
65
|
-
|
66
|
-
# you can cutomize DayWeather#to_s method
|
67
|
-
WeatherJp.get(:tokyo).today.to_s #=> "東京 東京都の天気は晴れ....etc"
|
68
|
-
|
69
|
-
WeatherJp.customize_to_s do
|
70
|
-
word = "#{day}の#{city_name}は#{forecast} "
|
71
|
-
word << "最高気温は#{max_temp} " if max_temp
|
72
|
-
word << "最低気温は#{min_temp} " if min_temp
|
73
|
-
word << "降水確率は#{rain}%" if rain
|
74
|
-
word << "でし"
|
75
|
-
word
|
76
|
-
end
|
77
|
-
|
78
|
-
WeatherJp.get(:tokyo).today.to_s #=> "本日の東京 東京都は晴れ...."
|
79
|
-
|
80
75
|
```
|
81
76
|
|
77
|
+
See more detail in [API document page](http://rubydoc.info/gems/weather_jp/).
|
78
|
+
|
82
79
|
## Requires
|
83
80
|
|
84
|
-
Ruby >=
|
81
|
+
Ruby >= 2.0.0
|
85
82
|
|
86
83
|
## Documents
|
87
84
|
|
data/Rakefile
CHANGED
data/lib/weather_jp.rb
CHANGED
@@ -1,201 +1,36 @@
|
|
1
|
-
|
1
|
+
require 'forwardable'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
require 'active_support'
|
7
|
+
require 'active_support/core_ext/object'
|
8
|
+
require 'rack'
|
9
|
+
|
10
|
+
require 'weather_jp/city'
|
11
|
+
require 'weather_jp/weather'
|
2
12
|
require 'weather_jp/day_weather'
|
13
|
+
require 'weather_jp/adapter'
|
14
|
+
require 'weather_jp/request_parser'
|
3
15
|
require 'weather_jp/version'
|
4
|
-
require 'uri'
|
5
|
-
require 'open-uri'
|
6
|
-
require 'rss'
|
7
|
-
require 'nokogiri'
|
8
16
|
|
9
17
|
module WeatherJp
|
10
18
|
class WeatherJpError < StandardError; end
|
19
|
+
class ServiceUnavailable < WeatherJpError; end
|
11
20
|
|
12
21
|
class << self
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
def parse(str)
|
28
|
-
if day_and_city = parser(str)
|
29
|
-
WeatherJp.get(day_and_city[:city]).get_weather(day_and_city[:day])
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def parser(str)
|
34
|
-
if str =~ /((?<city>.*)の
|
35
|
-
(?<day>今日|きょう|今|いま|明日|あした|明後日|あさって|3日後|4日後|3日後|4日後)
|
36
|
-
の(天気|てんき).*) |
|
37
|
-
((?<day>今日|きょう|今|いま|明日|あした|明後日|あさって|3日後|4日後|3日後|4日後)の
|
38
|
-
(?<city>.*)の(天気|てんき))
|
39
|
-
/ux
|
40
|
-
data = Regexp.last_match
|
41
|
-
day = data[:day]
|
42
|
-
case day
|
43
|
-
when /今日|きょう|今|いま/u
|
44
|
-
day = 'today'
|
45
|
-
when /明日|あした/u
|
46
|
-
day = 'tomorrow'
|
47
|
-
when /明後日|あさって/u
|
48
|
-
day = 'day_after_tomorrow'
|
49
|
-
when /3日後|3日後/u
|
50
|
-
day = 3
|
51
|
-
when /4日後|4日後/u
|
52
|
-
day = 4
|
53
|
-
else
|
54
|
-
raise WeatherJpError, "Can't parse given String"
|
55
|
-
end
|
56
|
-
{day: day, city: data[:city]}
|
57
|
-
else
|
58
|
-
nil
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
class Weather
|
64
|
-
include Enumerable
|
65
|
-
|
66
|
-
def initialize(city_name)
|
67
|
-
@area_code, @city_name, @weathers = Wrapper.get(city_name)
|
68
|
-
@day_weathers = Array.new(@weathers.size) do |n|
|
69
|
-
DayWeather.new(@weathers,@city_name, n)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
attr_reader :city_name, :area_code, :day_weathers
|
74
|
-
|
75
|
-
def to_hash
|
76
|
-
@weathers
|
77
|
-
end
|
78
|
-
|
79
|
-
alias to_a to_hash
|
80
|
-
|
81
|
-
def each
|
82
|
-
@day_weathers.each do |i|
|
83
|
-
yield i
|
84
|
-
end
|
85
|
-
self
|
86
|
-
end
|
87
|
-
|
88
|
-
def get_weather(day = 0)
|
89
|
-
begin
|
90
|
-
day = day.to_sym if day.class == String
|
91
|
-
case day
|
92
|
-
when :today
|
93
|
-
day = 0
|
94
|
-
when :tomorrow
|
95
|
-
day = 1
|
96
|
-
when :day_after_tomorrow
|
97
|
-
day = 2
|
98
|
-
end
|
99
|
-
raise WeatherJpError if @day_weathers[day] == nil
|
100
|
-
@day_weathers[day]
|
101
|
-
rescue
|
102
|
-
raise WeatherJpError,
|
103
|
-
"unvaild argument '#{day}' for get_weather"
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
%w(today tomorrow day_after_tomorrow).each do |s|
|
108
|
-
define_method(s.to_sym) do
|
109
|
-
get_weather(s)
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
|
114
|
-
module Wrapper
|
115
|
-
class << self
|
116
|
-
def get(city_name)
|
117
|
-
area_code, city_name = get_area_code(city_name.to_s)
|
118
|
-
weathers = set_weathers(parse_rss(get_rss(area_code)))
|
119
|
-
[area_code, city_name, weathers]
|
120
|
-
end
|
121
|
-
|
122
|
-
def get_area_code(city_name)
|
123
|
-
get_xml city_name do |xml|
|
124
|
-
parse_xml xml
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
def get_xml(city_name)
|
129
|
-
result = []
|
130
|
-
open(URI.encode("http://weather.service.msn.com/" +
|
131
|
-
"find.aspx?outputview=search&weadegreetype=C&culture=ja-JP&" +
|
132
|
-
"weasearchstr=#{city_name}")) do |xml|
|
133
|
-
result = yield xml
|
134
|
-
end
|
135
|
-
result
|
136
|
-
end
|
137
|
-
|
138
|
-
def parse_xml(xml)
|
139
|
-
doc = Nokogiri::XML(xml)
|
140
|
-
begin
|
141
|
-
code =
|
142
|
-
doc.xpath('//weather').attr('weatherlocationcode').value
|
143
|
-
full_name =
|
144
|
-
doc.xpath('//weather').attr('weatherlocationname').value
|
145
|
-
rescue
|
146
|
-
raise WeatherJpError, "Cant't parse XML"
|
147
|
-
end
|
148
|
-
[code.slice(3..-1), full_name]
|
149
|
-
end
|
150
|
-
|
151
|
-
def get_rss(area_code)
|
152
|
-
begin
|
153
|
-
uri = URI.parse(
|
154
|
-
"http://weather.jp.msn.com/" +
|
155
|
-
"RSS.aspx?wealocations=wc:#{area_code}&" +
|
156
|
-
"weadegreetype=C&culture=ja-JP"
|
157
|
-
)
|
158
|
-
RSS::Parser.parse(uri, false)
|
159
|
-
rescue
|
160
|
-
raise WeatherJpError,
|
161
|
-
"the MSN weather sever may be downed, or got invaild city code"
|
162
|
-
end
|
163
|
-
end
|
164
|
-
|
165
|
-
def parse_rss(rss)
|
166
|
-
str = rss.channel.item(0).description
|
167
|
-
data = remove_html_tag(str).split(/%/)
|
168
|
-
data.pop
|
169
|
-
data.map {|i| i.delete!('"') }
|
170
|
-
end
|
171
|
-
|
172
|
-
def remove_html_tag(str)
|
173
|
-
str.gsub(/<(\"[^\"]*\"|'[^']*'|[^'\">])*>/,'""')
|
174
|
-
end
|
175
|
-
|
176
|
-
def set_weathers(raw_data)
|
177
|
-
weathers = Array.new
|
178
|
-
begin
|
179
|
-
raw_data.each do |i|
|
180
|
-
h = Hash.new
|
181
|
-
h[:day] = i.slice(/(.*?):\s+(.*?)\./, 1)
|
182
|
-
h[:forecast] = i.slice(/(.*?):\s+(.*?)\./, 2)
|
183
|
-
h[:max_temp] = i.slice(/(最高).*?(-?\d+)/u, 2)
|
184
|
-
h[:min_temp] = i.slice(/(最低).*?(-?\d+)/u, 2)
|
185
|
-
h[:rain] = i.slice(/(降水確率).*?(\d+)/u, 2)
|
186
|
-
[:max_temp, :min_temp, :rain].each do |j|
|
187
|
-
h[j] = h[j].to_i if h[j]
|
188
|
-
end
|
189
|
-
if h[:day].match /の天気/u
|
190
|
-
h[:day] = h[:day].slice /(.*)の天気/u, 1
|
191
|
-
end
|
192
|
-
weathers << h
|
193
|
-
end
|
194
|
-
weathers
|
195
|
-
rescue NameError
|
196
|
-
raise WeatherJpError,
|
197
|
-
"the MSN weather sever may be downed, or something wrong"
|
198
|
-
end
|
22
|
+
# @param [String] city_name
|
23
|
+
# @return [WeatherJp::Weather, nil]
|
24
|
+
def get(city_name)
|
25
|
+
Adapter.get(city_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Request like '明日の東京の天気教えて'.
|
29
|
+
# @param [String] query
|
30
|
+
# @return [WeatherJp::Weather, nil]
|
31
|
+
def parse(query)
|
32
|
+
if request = RequestParser.parser(query)
|
33
|
+
get(request.city).get_weather(request.day)
|
199
34
|
end
|
200
35
|
end
|
201
36
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
module WeatherJp
|
4
|
+
class Adapter
|
5
|
+
class << self
|
6
|
+
# Returns nil when not found weather forecasts.
|
7
|
+
# @param [String] city_name
|
8
|
+
# @return [WeatherJp::Weather, nil]
|
9
|
+
def get(city_name)
|
10
|
+
new(city_name).get
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
BASE_URL = 'http://weather.service.msn.com/'
|
15
|
+
ACTION = 'data.aspx'
|
16
|
+
LANG = 'ja-JP'
|
17
|
+
SCALE = 'C'
|
18
|
+
|
19
|
+
attr_reader :name
|
20
|
+
|
21
|
+
def initialize(city_name)
|
22
|
+
@name = city_name
|
23
|
+
end
|
24
|
+
|
25
|
+
def get
|
26
|
+
weather_node ? weather : nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_weather(attrs, day_weathers)
|
30
|
+
end
|
31
|
+
|
32
|
+
def weather
|
33
|
+
Weather.new(city, day_weathers)
|
34
|
+
end
|
35
|
+
|
36
|
+
def day_weathers
|
37
|
+
day_weather_nodes.each_with_index.map do |n, i|
|
38
|
+
attrs = Hash[n.attributes.map {|k, v| [k, v.value] }]
|
39
|
+
DayWeather.new(attrs, city, i - 1)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def day_weather_nodes
|
44
|
+
xml.xpath('/weatherdata/weather/*[attribute::skytextday or attribute::skytext]')
|
45
|
+
end
|
46
|
+
|
47
|
+
# Ruby 2.0.0 version has no `Nokogiri::XML::Element#to_h`.
|
48
|
+
def city
|
49
|
+
@city ||= begin
|
50
|
+
attrs = Hash[weather_node.attributes.map {|k, v| [k, v.value]}]
|
51
|
+
City.new(attrs['weathercityname'], attrs)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def weather_node
|
56
|
+
@weather_node ||= xml.xpath('/weatherdata/weather').first
|
57
|
+
end
|
58
|
+
|
59
|
+
def xml
|
60
|
+
@xml ||= Nokogiri::XML.parse(xml_str)
|
61
|
+
end
|
62
|
+
|
63
|
+
def xml_str
|
64
|
+
@xml_str ||= Reader.read(url)
|
65
|
+
end
|
66
|
+
|
67
|
+
def url
|
68
|
+
"#{BASE_URL}#{ACTION}?#{Rack::Utils.build_query(params)}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def params
|
72
|
+
{
|
73
|
+
weadegreetype: SCALE,
|
74
|
+
culture: LANG,
|
75
|
+
weasearchstr: name,
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
module Reader
|
80
|
+
class << self
|
81
|
+
def read(url)
|
82
|
+
open(url).read
|
83
|
+
rescue OpenURI::HTTPError => e
|
84
|
+
raise ServiceUnavailable.new("status=#{e.message[0..-2]}")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|