metar-parser 0.9.11 → 0.9.12
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +15 -9
- data/Rakefile +23 -14
- data/bin/download_raw.rb +0 -2
- data/lib/metar/data.rb +84 -73
- data/lib/metar/parser.rb +2 -8
- data/lib/metar/raw.rb +1 -5
- data/lib/metar/report.rb +22 -48
- data/lib/metar/station.rb +16 -63
- data/lib/metar/version.rb +1 -1
- data/locales/en.yml +8 -2
- data/locales/it.yml +6 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/unit/distance_spec.rb +85 -0
- data/spec/unit/parser_spec.rb +234 -0
- data/spec/unit/pressure_spec.rb +32 -0
- data/spec/unit/raw_spec.rb +196 -0
- data/spec/unit/report_spec.rb +187 -0
- data/spec/unit/runway_visible_range_spec.rb +88 -0
- data/spec/unit/sky_condition_spec.rb +73 -0
- data/spec/unit/speed_spec.rb +50 -0
- data/spec/unit/station_spec.rb +254 -0
- data/spec/unit/temperature_spec.rb +47 -0
- data/spec/unit/variable_wind_spec.rb +34 -0
- data/spec/unit/vertical_visibility_spec.rb +37 -0
- data/spec/unit/visibility_spec.rb +84 -0
- data/spec/unit/weather_phenomenon_spec.rb +68 -0
- data/spec/unit/wind_spec.rb +99 -0
- metadata +85 -18
- data/test/all_tests.rb +0 -6
- data/test/metar_test_helper.rb +0 -33
- data/test/unit/data_test.rb +0 -376
- data/test/unit/parser_test.rb +0 -144
- data/test/unit/raw_test.rb +0 -37
- data/test/unit/report_test.rb +0 -105
- data/test/unit/station_test.rb +0 -88
@@ -0,0 +1,32 @@
|
|
1
|
+
load File.expand_path( '../spec_helper.rb', File.dirname(__FILE__) )
|
2
|
+
|
3
|
+
describe Metar::Pressure do
|
4
|
+
|
5
|
+
context '.parse' do
|
6
|
+
|
7
|
+
it 'interprets the Q prefix as hectopascals' do
|
8
|
+
Metar::Pressure.parse( 'Q1300' ).value.
|
9
|
+
should be_within( 0.01 ).of( 1.3 )
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'interprets the A prefix as inches of mercury' do
|
13
|
+
Metar::Pressure.parse( 'A1234' ).value.
|
14
|
+
should be_within( 0.01 ).of( 0.42 )
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'require 4 digits' do
|
18
|
+
Metar::Pressure.parse( 'Q12345' ).
|
19
|
+
should be_nil
|
20
|
+
Metar::Pressure.parse( 'A123' ).
|
21
|
+
should be_nil
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'returns nil for nil' do
|
25
|
+
Metar::Pressure.parse( nil ).
|
26
|
+
should be_nil
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,196 @@
|
|
1
|
+
load File.expand_path( '../spec_helper.rb', File.dirname(__FILE__) )
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require 'net/ftp'
|
5
|
+
|
6
|
+
describe Metar::Raw do
|
7
|
+
|
8
|
+
after :each do
|
9
|
+
Metar::Raw.send( :class_variable_set, '@@connection', nil )
|
10
|
+
end
|
11
|
+
|
12
|
+
context '.connection' do
|
13
|
+
|
14
|
+
context 'uncached' do
|
15
|
+
|
16
|
+
it 'sets up the connection' do
|
17
|
+
ftp = stub( 'ftp', :login => nil, :chdir => nil, :passive= => nil )
|
18
|
+
|
19
|
+
Net::FTP. should_receive( :new ).
|
20
|
+
and_return( ftp )
|
21
|
+
|
22
|
+
Metar::Raw.connect
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'cached' do
|
28
|
+
|
29
|
+
before :each do
|
30
|
+
@ftp = stub( 'ftp' )
|
31
|
+
Metar::Raw.send( :class_variable_set, '@@connection', @ftp )
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'does not connect to FTP' do
|
35
|
+
Net::FTP. should_not_receive( :new )
|
36
|
+
|
37
|
+
Metar::Raw.connection
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns the cached connection' do
|
41
|
+
connection = Metar::Raw.connection
|
42
|
+
|
43
|
+
connection. should == @ftp
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
context '.connect' do
|
51
|
+
|
52
|
+
it 'sets up the connection' do
|
53
|
+
ftp = stub( 'ftp' )
|
54
|
+
|
55
|
+
Net::FTP. should_receive( :new ).
|
56
|
+
and_return( ftp )
|
57
|
+
ftp. should_receive( :login )
|
58
|
+
ftp. should_receive( :chdir )
|
59
|
+
ftp. should_receive( :passive= ).
|
60
|
+
with( true )
|
61
|
+
|
62
|
+
Metar::Raw.connect
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
context '.fetch' do
|
68
|
+
|
69
|
+
it 'uses the connection' do
|
70
|
+
ftp = stub( 'ftp', :login => nil, :chdir => nil, :passive= => nil, :retrbinary => nil )
|
71
|
+
|
72
|
+
Net::FTP. should_receive( :new ).
|
73
|
+
and_return( ftp )
|
74
|
+
|
75
|
+
Metar::Raw.fetch( 'the_cccc' )
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'downloads the raw report' do
|
79
|
+
ftp = stub( 'ftp', :login => nil, :chdir => nil, :passive= => nil )
|
80
|
+
Net::FTP.stub!( :new ).and_return( ftp )
|
81
|
+
|
82
|
+
ftp. should_receive( :retrbinary ) do | *args, &block |
|
83
|
+
args[ 0 ]. should == 'RETR the_cccc.TXT'
|
84
|
+
args[ 1 ]. should be_a Fixnum
|
85
|
+
end
|
86
|
+
|
87
|
+
Metar::Raw.fetch( 'the_cccc' )
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'returns the data' do
|
91
|
+
ftp = stub( 'ftp', :login => nil, :chdir => nil, :passive= => nil )
|
92
|
+
def ftp.retrbinary( *args, &block )
|
93
|
+
block.call "chunk 1\n"
|
94
|
+
block.call "chunk 2\n"
|
95
|
+
end
|
96
|
+
Net::FTP.stub!( :new ).and_return( ftp )
|
97
|
+
|
98
|
+
raw = Metar::Raw.fetch( 'the_cccc' )
|
99
|
+
|
100
|
+
raw. should == "chunk 1\nchunk 2\n"
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'retries retrieval once' do
|
104
|
+
ftp = stub( 'ftp', :login => nil, :chdir => nil, :passive= => nil )
|
105
|
+
def ftp.attempt
|
106
|
+
@attempt
|
107
|
+
end
|
108
|
+
def ftp.attempt=(a)
|
109
|
+
@attempt = a
|
110
|
+
end
|
111
|
+
ftp.attempt = 0
|
112
|
+
def ftp.retrbinary( *args, &block )
|
113
|
+
self.attempt = self.attempt + 1
|
114
|
+
raise Net::FTPTempError if self.attempt == 1
|
115
|
+
block.call "chunk 1\n"
|
116
|
+
block.call "chunk 2\n"
|
117
|
+
end
|
118
|
+
Net::FTP.stub!( :new ).and_return( ftp )
|
119
|
+
|
120
|
+
raw = Metar::Raw.fetch( 'the_cccc' )
|
121
|
+
|
122
|
+
raw. should == "chunk 1\nchunk 2\n"
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'fails with an error, if retrieval fails twice' do
|
126
|
+
ftp = stub( 'ftp', :login => nil, :chdir => nil, :passive= => nil )
|
127
|
+
def ftp.attempt
|
128
|
+
@attempt
|
129
|
+
end
|
130
|
+
def ftp.attempt=(a)
|
131
|
+
@attempt = a
|
132
|
+
end
|
133
|
+
ftp.attempt = 0
|
134
|
+
def ftp.retrbinary( *args, &block )
|
135
|
+
self.attempt = self.attempt + 1
|
136
|
+
raise Net::FTPTempError
|
137
|
+
end
|
138
|
+
Net::FTP.stub!( :new ).and_return( ftp )
|
139
|
+
|
140
|
+
expect do
|
141
|
+
Metar::Raw.fetch( 'the_cccc' )
|
142
|
+
end. to raise_error( RuntimeError, /failed 2 times/)
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'initialization' do
|
148
|
+
|
149
|
+
it 'should accept CCCC codes' do
|
150
|
+
raw = Metar::Raw.new( 'XXXX' )
|
151
|
+
|
152
|
+
raw.cccc. should == 'XXXX'
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should accept Stations' do
|
156
|
+
station = stub( 'Metar::Station', :cccc => 'YYYY' )
|
157
|
+
raw = Metar::Raw.new( station )
|
158
|
+
|
159
|
+
raw.cccc. should == 'YYYY'
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should parse data, if supplied' do
|
163
|
+
raw = Metar::Raw.new( 'XXXX', raw_metar )
|
164
|
+
|
165
|
+
raw.data. should == raw_metar
|
166
|
+
raw.raw_time. should == @raw_time
|
167
|
+
raw.metar. should == @metar
|
168
|
+
raw.time. should == Time.parse( @raw_time )
|
169
|
+
end
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'lazy loading' do
|
174
|
+
|
175
|
+
it 'should fetch data on demand' do
|
176
|
+
raw = Metar::Raw.new( 'ESSB' )
|
177
|
+
|
178
|
+
Metar::Raw. should_receive( :fetch ).
|
179
|
+
with( 'ESSB' ).
|
180
|
+
and_return( raw_metar )
|
181
|
+
|
182
|
+
raw.metar
|
183
|
+
|
184
|
+
raw.data. should == raw_metar
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
def raw_metar
|
190
|
+
@raw_time = "2010/02/15 10:20"
|
191
|
+
@metar = "ESSB 151020Z 26003KT 2000 R12/1000N R30/1500N VV002 M07/M07 Q1013 1271//55"
|
192
|
+
"#{ @raw_time }\n#{ @metar }"
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|
196
|
+
|
@@ -0,0 +1,187 @@
|
|
1
|
+
load File.expand_path( '../spec_helper.rb', File.dirname(__FILE__) )
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
describe Metar::Report do
|
5
|
+
|
6
|
+
context 'initialization' do
|
7
|
+
|
8
|
+
it 'loads the Station' do
|
9
|
+
station = stub( 'station' )
|
10
|
+
parser = stub( 'parser', :station_code => 'SSSS' )
|
11
|
+
|
12
|
+
Metar::Station. should_receive( :find_by_cccc ).
|
13
|
+
with( 'SSSS' ).
|
14
|
+
and_return( station )
|
15
|
+
|
16
|
+
Metar::Report.new( parser )
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'attributes' do
|
22
|
+
|
23
|
+
before :each do
|
24
|
+
@locale = I18n.locale
|
25
|
+
@station_code = 'SSSS'
|
26
|
+
@metar_date = '2008/05/06'
|
27
|
+
@metar_time = '10:56'
|
28
|
+
@metar_datetime = "#{@metar_date} #{@metar_time}"
|
29
|
+
@station = stub( 'station', :name => 'Airport 1',
|
30
|
+
:country => 'Wwwwww' )
|
31
|
+
@parser = stub( 'parser', :station_code => @station_code,
|
32
|
+
:date => Date.parse( @metar_date ),
|
33
|
+
:time => Time.parse( @metar_datetime ),
|
34
|
+
:observer => :real )
|
35
|
+
Metar::Station.stub( :find_by_cccc ).with( @station_code ).and_return( @station )
|
36
|
+
end
|
37
|
+
|
38
|
+
subject { Metar::Report.new( @parser ) }
|
39
|
+
|
40
|
+
after :each do
|
41
|
+
I18n.locale = @locale
|
42
|
+
end
|
43
|
+
|
44
|
+
context '#date' do
|
45
|
+
|
46
|
+
it 'localizes' do
|
47
|
+
I18n.locale = :en
|
48
|
+
|
49
|
+
subject.date. should == '06/05/2008'
|
50
|
+
|
51
|
+
I18n.locale = :it
|
52
|
+
|
53
|
+
subject.date. should == '06/05/2008'
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
context '#time' do
|
59
|
+
specify { subject.time. should == @metar_time }
|
60
|
+
end
|
61
|
+
|
62
|
+
context '#observer' do
|
63
|
+
specify { subject.observer. should == 'real' }
|
64
|
+
end
|
65
|
+
|
66
|
+
specify { subject.station_name.
|
67
|
+
should == 'Airport 1' }
|
68
|
+
|
69
|
+
specify { subject.station_country.
|
70
|
+
should == 'Wwwwww' }
|
71
|
+
|
72
|
+
specify { subject.station_code.
|
73
|
+
should == @station_code }
|
74
|
+
|
75
|
+
context 'proxied from parser' do
|
76
|
+
|
77
|
+
context 'singly' do
|
78
|
+
[
|
79
|
+
:wind,
|
80
|
+
:variable_wind,
|
81
|
+
:visibility,
|
82
|
+
:vertical_visibility,
|
83
|
+
:temperature,
|
84
|
+
:dew_point,
|
85
|
+
:sea_level_pressure,
|
86
|
+
].each do | attribute |
|
87
|
+
example attribute do
|
88
|
+
@attr = stub( attribute.to_s )
|
89
|
+
@parser.stub!( attribute => @attr )
|
90
|
+
|
91
|
+
@attr. should_receive( :to_s )
|
92
|
+
|
93
|
+
subject.send( attribute )
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
context '#sky_summary' do
|
98
|
+
|
99
|
+
it 'returns the summary' do
|
100
|
+
@skies1 = stub('sky_conditions')
|
101
|
+
@parser.stub!( :sky_conditions => [@skies1] )
|
102
|
+
|
103
|
+
@skies1. should_receive( :to_summary ).
|
104
|
+
and_return( 'skies1' )
|
105
|
+
|
106
|
+
subject.sky_summary. should == 'skies1'
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'clear skies when missing' do
|
110
|
+
@parser.stub!( :sky_conditions => [] )
|
111
|
+
|
112
|
+
subject.sky_summary. should == 'clear skies'
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'uses the last, if there is more than 1' do
|
116
|
+
@skies1 = stub('sky_conditions1' )
|
117
|
+
@skies2 = stub('sky_conditions2' )
|
118
|
+
@parser.stub!( :sky_conditions => [ @skies1, @skies2 ] )
|
119
|
+
|
120
|
+
@skies2. should_receive( :to_summary ).
|
121
|
+
and_return( 'skies2' )
|
122
|
+
|
123
|
+
subject.sky_summary. should == 'skies2'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'joined' do
|
129
|
+
|
130
|
+
it '#runway_visible_range' do
|
131
|
+
@rvr1 = stub( 'rvr1', :to_s => 'rvr1' )
|
132
|
+
@rvr2 = stub( 'rvr2', :to_s => 'rvr2' )
|
133
|
+
@parser.stub!( :runway_visible_range => [ @rvr1, @rvr2 ] )
|
134
|
+
|
135
|
+
subject.runway_visible_range.
|
136
|
+
should == 'rvr1, rvr2'
|
137
|
+
end
|
138
|
+
|
139
|
+
it '#present_weather' do
|
140
|
+
@parser.stub!( :present_weather => [ 'pw1', 'pw2' ] )
|
141
|
+
|
142
|
+
subject.present_weather.should == 'pw1, pw2'
|
143
|
+
end
|
144
|
+
|
145
|
+
it '#remarks' do
|
146
|
+
@parser.stub!( :remarks => [ 'rem1', 'rem2' ] )
|
147
|
+
|
148
|
+
subject.remarks. should == 'rem1, rem2'
|
149
|
+
end
|
150
|
+
|
151
|
+
it '#sky_conditions' do
|
152
|
+
sky1 = stub( 'sky1', :to_s => 'sky1' )
|
153
|
+
sky2 = stub( 'sky2', :to_s => 'sky2' )
|
154
|
+
@parser.stub!( :sky_conditions => [ sky1, sky2 ] )
|
155
|
+
|
156
|
+
subject.sky_conditions. should == 'sky1, sky2'
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
it '#to_s' do
|
164
|
+
sky1 = stub( 'sky1', :to_summary => 'sky1' )
|
165
|
+
sky2 = stub( 'sky2', :to_summary => 'sky2' )
|
166
|
+
@parser.stub!( :wind => 'wind',
|
167
|
+
:visibility => 'visibility',
|
168
|
+
:present_weather => ['pw'],
|
169
|
+
:sky_conditions => [ sky1, sky2 ],
|
170
|
+
:temperature => 'temp' )
|
171
|
+
expected = <<EOT
|
172
|
+
name: Airport 1
|
173
|
+
country: Wwwwww
|
174
|
+
time: #{@metar_time}
|
175
|
+
wind: wind
|
176
|
+
visibility: visibility
|
177
|
+
weather: pw
|
178
|
+
sky: sky2
|
179
|
+
temperature: temp
|
180
|
+
EOT
|
181
|
+
subject.to_s. should == expected
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
load File.expand_path( '../spec_helper.rb', File.dirname(__FILE__) )
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
RSpec::Matchers.define :be_runway_visible_range do | designator, visibility1, visibility2, tendency |
|
5
|
+
match do | rvr |
|
6
|
+
if rvr.nil? && designator.nil?
|
7
|
+
true
|
8
|
+
elsif rvr.nil? != designator.nil?
|
9
|
+
false
|
10
|
+
elsif rvr.visibility1.nil? != visibility1.nil?
|
11
|
+
false
|
12
|
+
elsif rvr.visibility2.nil? != visibility2.nil?
|
13
|
+
false
|
14
|
+
elsif rvr.tendency.nil? != tendency.nil?
|
15
|
+
false
|
16
|
+
elsif ! visibility1.nil? &&
|
17
|
+
( ( rvr.visibility1.distance.value - visibility1[0] ).abs > 0.01 ||
|
18
|
+
rvr.visibility1.comparator != visibility1[ 2 ] )
|
19
|
+
false
|
20
|
+
elsif ! visibility2.nil? &&
|
21
|
+
( ( rvr.visibility2.distance.value - visibility2[0] ).abs > 0.02 ||
|
22
|
+
rvr.visibility2.comparator != visibility2[2] )
|
23
|
+
false
|
24
|
+
elsif tendency != rvr.tendency
|
25
|
+
false
|
26
|
+
else
|
27
|
+
true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe Metar::RunwayVisibleRange do
|
33
|
+
|
34
|
+
context '.parse' do
|
35
|
+
|
36
|
+
[
|
37
|
+
[ 'understands R + nn + / + nnnn', 'R12/3400', [ '12', [3400.00, nil, nil], nil, nil ] ],
|
38
|
+
[ 'understands runway positions: RLC', 'R12L/3400', [ '12L', [3400.00, nil, nil], nil, nil ] ],
|
39
|
+
[ 'understands comparators: PM', 'R12/P3400', [ '12', [3400.00, nil, :more_than], nil, nil ] ],
|
40
|
+
[ 'understands tendencies: NUD', 'R12/3400U', [ '12', [3400.00, nil, nil], nil, :improving ] ],
|
41
|
+
[ 'understands feet', 'R12/3400FT', [ '12', [1036.32, nil, nil], nil, nil ] ],
|
42
|
+
[ 'understands second visibilties', 'R12/3400V1800FT', [ '12', [1036.32, nil, nil], [548.64, nil, nil], nil ] ],
|
43
|
+
[ 'returns nil for nil', nil, [ nil, nil, nil, nil ] ],
|
44
|
+
].each do | docstring, raw, expected |
|
45
|
+
example docstring do
|
46
|
+
Metar::RunwayVisibleRange.parse( raw ).should be_runway_visible_range( *expected )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
context '#to_s' do
|
53
|
+
|
54
|
+
before :all do
|
55
|
+
@locale = I18n.locale
|
56
|
+
I18n.locale = :it
|
57
|
+
end
|
58
|
+
|
59
|
+
after :all do
|
60
|
+
I18n.locale = @locale
|
61
|
+
end
|
62
|
+
|
63
|
+
[
|
64
|
+
[ 'v1', :en, [ [3400.00, nil, nil], nil, nil ], 'runway 14: 3400m' ],
|
65
|
+
[ 'v1 and v2', :en, [ [3400.00, nil, nil], [1900.00, nil, nil], nil ], 'runway 14: from 3400m to 1900m' ],
|
66
|
+
[ 'v1 and tendency', :en, [ [3400.00, nil, nil], nil, :improving ], 'runway 14: 3400m improving' ],
|
67
|
+
].each do | docstring, locale, ( visibility1, visibility2, tendency ), expected |
|
68
|
+
d1 = Metar::Distance.new( visibility1[0] )
|
69
|
+
v1 = Metar::Visibility.new( d1, visibility1[1], visibility1[2] )
|
70
|
+
v2 =
|
71
|
+
if ! visibility2.nil?
|
72
|
+
d2 = Metar::Distance.new( visibility2[0] )
|
73
|
+
Metar::Visibility.new( d2, visibility2[1], visibility2[2] )
|
74
|
+
else
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
|
78
|
+
example docstring + " (#{locale})" do
|
79
|
+
I18n.locale = locale
|
80
|
+
Metar::RunwayVisibleRange.new( '14', v1, v2, tendency ).to_s.
|
81
|
+
should == expected
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|