aipp 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/.yardopts +3 -0
- data/CHANGELOG.md +25 -10
- data/README.md +116 -27
- data/aipp.gemspec +4 -2
- data/exe/aip2aixm +1 -50
- data/exe/aip2ofmx +11 -0
- data/lib/aipp.rb +20 -3
- data/lib/aipp/aip.rb +63 -0
- data/lib/aipp/airac.rb +17 -9
- data/lib/aipp/executable.rb +82 -0
- data/lib/aipp/parser.rb +97 -0
- data/lib/aipp/progress.rb +40 -0
- data/lib/aipp/refinements.rb +91 -28
- data/lib/aipp/regions/LF/ENR-2.1.rb +89 -0
- data/lib/aipp/regions/LF/ENR-4.1.rb +101 -0
- data/lib/aipp/regions/LF/ENR-4.3.rb +26 -0
- data/lib/aipp/regions/LF/ENR-5.1.rb +65 -0
- data/lib/aipp/regions/LF/helper.rb +177 -0
- data/lib/aipp/t_hash.rb +46 -0
- data/lib/aipp/version.rb +1 -1
- data/spec/lib/aipp/airac_spec.rb +3 -3
- data/spec/lib/aipp/refinements_spec.rb +97 -27
- data/spec/lib/aipp/t_hash_spec.rb +44 -0
- metadata +50 -14
- data/lib/aipp/loader.rb +0 -52
- data/lib/aipp/parsers/LF/AD-1.5.rb +0 -128
- data/lib/aipp/parsers/LF/ENR-4.1.rb +0 -105
- data/lib/aipp/parsers/LF/ENR-4.3.rb +0 -32
- data/lib/aipp/parsers/LF/ENR-5.1.rb +0 -134
- data/lib/aipp/parsers/LF/helpers/html.rb +0 -11
- data/lib/aipp/parsers/LF/helpers/url.rb +0 -15
@@ -0,0 +1,101 @@
|
|
1
|
+
module AIPP
|
2
|
+
module LF
|
3
|
+
|
4
|
+
# ENR Navaids
|
5
|
+
class ENR41 < AIP
|
6
|
+
using AIPP::Refinements
|
7
|
+
|
8
|
+
def parse
|
9
|
+
load_html.css('tbody').each do |tbody|
|
10
|
+
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
11
|
+
tds = cleanup(node: tr).css('td')
|
12
|
+
master, slave = tds[1].text.strip.gsub(/[^\w-]/, '').downcase.split('-')
|
13
|
+
navaid = AIXM.send(master, base_from(tds).merge(send("#{master}_from", tds)))
|
14
|
+
navaid.source = source_for(tr)
|
15
|
+
navaid.timetable = timetable_from(tds[4])
|
16
|
+
navaid.remarks = remarks_from(tds[5], tds[7], tds[9])
|
17
|
+
navaid.send("associate_#{slave}", channel: channel_from(tds[3])) if slave
|
18
|
+
aixm.features << navaid
|
19
|
+
rescue => error
|
20
|
+
warn("error parsing navigational aid at ##{index}: #{error.message}", context: error)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def base_from(tds)
|
28
|
+
{
|
29
|
+
organisation: organisation_lf,
|
30
|
+
id: tds[2].text.strip,
|
31
|
+
name: tds[0].text.strip,
|
32
|
+
xy: xy_from(tds[5]),
|
33
|
+
z: z_from(tds[6])
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
def vor_from(tds)
|
38
|
+
{
|
39
|
+
type: :conventional,
|
40
|
+
f: frequency_from(tds[3]),
|
41
|
+
north: :magnetic,
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def dme_from(tds)
|
46
|
+
{
|
47
|
+
channel: channel_from(tds[3])
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def ndb_from(tds)
|
52
|
+
{
|
53
|
+
type: :en_route,
|
54
|
+
f: frequency_from(tds[3])
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def tacan_from(tds)
|
59
|
+
{
|
60
|
+
channel: channel_from(tds[3])
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def z_from(td)
|
65
|
+
parts = td.text.strip.split(/\s+/)
|
66
|
+
AIXM.z(parts[0].to_i, :qnh) if parts[1] == 'ft'
|
67
|
+
end
|
68
|
+
|
69
|
+
def frequency_from(td)
|
70
|
+
parts = td.text.strip.split(/\s+/)
|
71
|
+
AIXM.f(parts[0].to_f, parts[1]) if parts[1] =~ /hz$/i
|
72
|
+
end
|
73
|
+
|
74
|
+
def channel_from(td)
|
75
|
+
parts = td.text.strip.split(/\s+/)
|
76
|
+
parts.last if parts[-2].downcase == 'ch'
|
77
|
+
end
|
78
|
+
|
79
|
+
def timetable_from(td)
|
80
|
+
code = td.text.strip
|
81
|
+
AIXM.timetable(code: code) unless code.empty?
|
82
|
+
end
|
83
|
+
|
84
|
+
def remarks_from(*parts)
|
85
|
+
part_titles = ['RANGE', 'SITUATION', 'OBSERVATIONS']
|
86
|
+
[].tap do |remarks|
|
87
|
+
parts.each.with_index do |part, index|
|
88
|
+
text = if index == 0
|
89
|
+
part = part.text.strip.split(/\s+/)
|
90
|
+
part.shift(2)
|
91
|
+
part.join(' ').blank_to_nil
|
92
|
+
else
|
93
|
+
part.text.strip.blank_to_nil
|
94
|
+
end
|
95
|
+
remarks << "#{part_titles[index]}:\n#{text}" if text
|
96
|
+
end
|
97
|
+
end.join("\n\n").blank_to_nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module AIPP
|
2
|
+
module LF
|
3
|
+
|
4
|
+
# Designated Points
|
5
|
+
class ENR43 < AIP
|
6
|
+
|
7
|
+
def parse
|
8
|
+
load_html.css('tbody').each do |tbody|
|
9
|
+
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
10
|
+
tds = cleanup(node: tr).css('td')
|
11
|
+
designated_point = AIXM.designated_point(
|
12
|
+
type: :icao,
|
13
|
+
id: tds[0].text.strip,
|
14
|
+
xy: xy_from(tds[1])
|
15
|
+
)
|
16
|
+
designated_point.source = source_for(tr)
|
17
|
+
aixm.features << designated_point
|
18
|
+
rescue => error
|
19
|
+
warn("error parsing designated point at ##{index}: #{error.message}", context: error)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module AIPP
|
2
|
+
module LF
|
3
|
+
|
4
|
+
# D/P/R Zones
|
5
|
+
class ENR51 < AIP
|
6
|
+
using AIPP::Refinements
|
7
|
+
|
8
|
+
TYPES = {
|
9
|
+
'D' => 'D',
|
10
|
+
'P' => 'P',
|
11
|
+
'R' => 'R',
|
12
|
+
'ZIT' => 'P'
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
def parse
|
16
|
+
load_html.css('tbody:has(tr[id^=mid])').each do |tbody|
|
17
|
+
airspace = nil
|
18
|
+
tbody.css('tr').to_enum.with_index(1).each do |tr, index|
|
19
|
+
if tr.attr(:class) =~ /keep-with-next-row/
|
20
|
+
airspace = airspace_from cleanup(node: tr)
|
21
|
+
else
|
22
|
+
begin
|
23
|
+
tds = cleanup(node: tr).css('td')
|
24
|
+
airspace.geometry = geometry_from tds[0]
|
25
|
+
fail("geometry is not closed") unless airspace.geometry.closed?
|
26
|
+
airspace.layers << layer_from(tds[1])
|
27
|
+
airspace.layers.first.timetable = timetable_from tds[2]
|
28
|
+
airspace.layers.first.remarks = remarks_from(tds[2], tds[3], tds[4])
|
29
|
+
aixm.features << airspace
|
30
|
+
rescue => error
|
31
|
+
warn("error parsing airspace `#{airspace.name}' at ##{index}: #{error.message}", context: error)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def airspace_from(tr)
|
41
|
+
spans = tr.css(:span)
|
42
|
+
AIXM.airspace(
|
43
|
+
name: [spans[1], spans[2], spans[3], spans[5].text.blank_to_nil].compact.join(' '),
|
44
|
+
local_type: [spans[1], spans[2], spans[3]].compact.join(' '),
|
45
|
+
type: TYPES.fetch(spans[2].text)
|
46
|
+
).tap do |airspace|
|
47
|
+
airspace.source = source_for(tr)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def remarks_from(*parts)
|
52
|
+
part_titles = ['TIMETABLE', 'RESTRICTION', 'AUTHORITY/CONDITIONS']
|
53
|
+
[].tap do |remarks|
|
54
|
+
parts.each.with_index do |part, index|
|
55
|
+
if part = part.text.gsub(/ +/, ' ').gsub(/(\n ?)+/, "\n").strip.blank_to_nil
|
56
|
+
unless index.zero? && part == 'H24'
|
57
|
+
remarks << "#{part_titles[index]}:\n#{part}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end.join("\n\n").blank_to_nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module AIPP
|
2
|
+
module LF
|
3
|
+
module Helper
|
4
|
+
using AIPP::Refinements
|
5
|
+
using AIXM::Refinements
|
6
|
+
|
7
|
+
BORDERS = {
|
8
|
+
'franco-allemande' => 'FRANCE_GERMANY',
|
9
|
+
'franco-espagnole' => 'FRANCE_SPAIN',
|
10
|
+
'franco-italienne' => 'FRANCE_ITALY',
|
11
|
+
'franco-suisse' => 'FRANCE_SWITZERLAND',
|
12
|
+
'franco-luxembourgeoise' => 'FRANCE_LUXEMBOURG',
|
13
|
+
'franco-belge' => 'BELGIUM_FRANCE',
|
14
|
+
'germano-suisse' => 'GERMANY_SWITZERLAND',
|
15
|
+
'hispano-andorrane' => 'ANDORRA_SPAIN',
|
16
|
+
'la côte atlantique française' => 'FRANCE_ATLANTIC_COAST',
|
17
|
+
'côte méditérrannéenne' => 'FRANCE_MEDITERRANEAN_COAST',
|
18
|
+
'limite des eaux territoriales atlantique françaises' => 'FRANCE_ATLANTIC_TERRITORIAL_SEA',
|
19
|
+
'parc national des écrins' => 'FRANCE_ECRINS_NATIONAL_PARK'
|
20
|
+
}.freeze
|
21
|
+
|
22
|
+
INTERSECTIONS = {
|
23
|
+
'FRANCE_SPAIN|ANDORRA_SPAIN' => AIXM.xy(lat: 42.502720, long: 1.725965),
|
24
|
+
'ANDORRA_SPAIN|FRANCE_SPAIN' => AIXM.xy(lat: 42.603571, long: 1.442681),
|
25
|
+
'FRANCE_SWITZERLAND|FRANCE_ITALY' => AIXM.xy(lat: 45.922701, long: 7.044125),
|
26
|
+
'BELGIUM_FRANCE|FRANCE_LUXEMBOURG' => AIXM.xy(lat: 49.546428, long: 5.818415),
|
27
|
+
'FRANCE_LUXEMBOURG|FRANCE_GERMANY' => AIXM.xy(lat: 49.469438, long: 6.367516),
|
28
|
+
'FRANCE_GERMANY|FRANCE_SWITZERLAND' => AIXM.xy(lat: 47.589831, long: 7.589049),
|
29
|
+
'GERMANY_SWITZERLAND|FRANCE_GERMANY' => AIXM.xy(lat: 47.589831, long: 7.589049)
|
30
|
+
}
|
31
|
+
|
32
|
+
ANGLICISE_MAP = {
|
33
|
+
/[^A-Z0-9 .\-]/ => '',
|
34
|
+
/ 0(\d)/ => ' \1',
|
35
|
+
/(\d)-(\d)/ => '\1.\2',
|
36
|
+
/PARTIE/ => '',
|
37
|
+
/DELEG\./ => 'DELEG ',
|
38
|
+
/FRANCAISE?/ => 'FR',
|
39
|
+
/ANGLAISE?/ => 'UK',
|
40
|
+
/BELGE/ => 'BE',
|
41
|
+
/LUXEMBOURGEOISE?/ => 'LU',
|
42
|
+
/ALLEMANDE?/ => 'DE',
|
43
|
+
/SUISSE/ => 'CH',
|
44
|
+
/ITALIEN(?:NE)?/ => 'IT',
|
45
|
+
/ESPAGNOLE?/ => 'ES',
|
46
|
+
/ANDORRANE?/ => 'AD',
|
47
|
+
/NORD/ => 'N',
|
48
|
+
/EST/ => 'E',
|
49
|
+
/SUD/ => 'S',
|
50
|
+
/OEST/ => 'W',
|
51
|
+
/ANGLO NORMANDES/ => 'ANGLO-NORMANDES',
|
52
|
+
/ +/ => ' '
|
53
|
+
}.freeze
|
54
|
+
|
55
|
+
# Download URL
|
56
|
+
|
57
|
+
def url_for(aip_file)
|
58
|
+
"https://www.sia.aviation-civile.gouv.fr/dvd/eAIP_%s/FRANCE/AIRAC-%s/html/eAIP/FR-%s-fr-FR.html" % [
|
59
|
+
options[:airac].date.strftime('%d_%^b_%Y'), # 04_JAN_2018
|
60
|
+
options[:airac].date.xmlschema, # 2018-01-04
|
61
|
+
aip_file # ENR-5.1 or AD-2.LFMV
|
62
|
+
]
|
63
|
+
end
|
64
|
+
|
65
|
+
# Templates
|
66
|
+
|
67
|
+
def organisation_lf
|
68
|
+
@organisation_lf ||= AIXM.organisation(
|
69
|
+
name: 'FRANCE',
|
70
|
+
type: 'S'
|
71
|
+
).tap do |organisation|
|
72
|
+
organisation.id = 'LF'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Transformations
|
77
|
+
|
78
|
+
def cleanup(node:)
|
79
|
+
node.tap do |root|
|
80
|
+
root.css('del').each { |n| n.remove } # remove deleted entries
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def anglicise(name:)
|
85
|
+
name.uptrans.tap do |string|
|
86
|
+
ANGLICISE_MAP.each do |regexp, replacement|
|
87
|
+
string.gsub!(regexp, replacement)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Parsers
|
93
|
+
|
94
|
+
def source_for(element)
|
95
|
+
[
|
96
|
+
options[:region],
|
97
|
+
@aip.split('-').first,
|
98
|
+
@aip,
|
99
|
+
options[:airac].date.xmlschema,
|
100
|
+
element.line
|
101
|
+
].join('|')
|
102
|
+
end
|
103
|
+
|
104
|
+
def xy_from(td)
|
105
|
+
parts = td.text.strip.split(/\s+/)
|
106
|
+
AIXM.xy(lat: parts[0], long: parts[1])
|
107
|
+
end
|
108
|
+
|
109
|
+
def z_from(limit)
|
110
|
+
case limit
|
111
|
+
when nil then nil
|
112
|
+
when 'SFC' then AIXM::GROUND
|
113
|
+
when 'UNL' then AIXM::UNLIMITED
|
114
|
+
when /(\d+)ftASFC/ then AIXM.z($1.to_i, :qfe)
|
115
|
+
when /(\d+)ftAMSL/ then AIXM.z($1.to_i, :qnh)
|
116
|
+
when /FL(\d+)/ then AIXM.z($1.to_i, :qne)
|
117
|
+
else fail "z `#{limit}' not recognized"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def layer_from(td)
|
122
|
+
above, below = td.text.gsub(/ /, '').split(/\n+/).select(&:blank_to_nil).split { |e| e.match? '---+' }
|
123
|
+
above.reverse!
|
124
|
+
AIXM.layer(
|
125
|
+
vertical_limits: AIXM.vertical_limits(
|
126
|
+
max_z: z_from(above[1]),
|
127
|
+
upper_z: z_from(above[0]),
|
128
|
+
lower_z: z_from(below[0]),
|
129
|
+
min_z: z_from(below[1])
|
130
|
+
)
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
def geometry_from(td)
|
135
|
+
AIXM.geometry.tap do |geometry|
|
136
|
+
buffer = {}
|
137
|
+
td.text.gsub(/\s+/, ' ').strip.split(/ - /).append('end').each do |element|
|
138
|
+
case element
|
139
|
+
when /arc (anti-)?horaire .+ sur (\S+) , (\S+)/i
|
140
|
+
geometry << AIXM.arc(
|
141
|
+
xy: buffer.delete(:xy),
|
142
|
+
center_xy: AIXM.xy(lat: $2, long: $3),
|
143
|
+
clockwise: $1.nil?
|
144
|
+
)
|
145
|
+
when /cercle de ([\d\.]+) (NM|km|m) .+ sur (\S+) , (\S+)/i
|
146
|
+
geometry << AIXM.circle(
|
147
|
+
center_xy: AIXM.xy(lat: $3, long: $4),
|
148
|
+
radius: AIXM.d($1.to_f, $2)
|
149
|
+
)
|
150
|
+
when /end|(\S+) , (\S+)/
|
151
|
+
geometry << AIXM.point(xy: buffer[:xy]) if buffer.has_key?(:xy)
|
152
|
+
buffer[:xy] = AIXM.xy(lat: $1, long: $2) if $1
|
153
|
+
when /^frontière ([\w-]+)/i, /^(\D[^(]+)/i
|
154
|
+
border_name = BORDERS.fetch($1.downcase.strip)
|
155
|
+
buffer[:xy] ||= INTERSECTIONS.fetch("#{buffer[:border_name]}|#{border_name}")
|
156
|
+
buffer[:border_name] = border_name
|
157
|
+
if border_name == 'FRANCE_SPAIN' # specify which part of this split border
|
158
|
+
border_name += buffer[:xy].lat < 42.55 ? '_EAST' : '_WEST'
|
159
|
+
end
|
160
|
+
geometry << AIXM.border(
|
161
|
+
xy: buffer.delete(:xy),
|
162
|
+
name: border_name
|
163
|
+
)
|
164
|
+
else
|
165
|
+
fail "geometry `#{element}' not recognized"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def timetable_from(td)
|
172
|
+
AIXM::H24 if td.text.gsub(/\W/, '') == 'H24'
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
data/lib/aipp/t_hash.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module AIPP
|
2
|
+
|
3
|
+
# Topologically sortable hash for dealing with dependencies
|
4
|
+
#
|
5
|
+
# Example:
|
6
|
+
# dependency_hash = THash[
|
7
|
+
# dns: %i(net),
|
8
|
+
# webserver: %i(dns logger),
|
9
|
+
# net: [],
|
10
|
+
# logger: []
|
11
|
+
# ]
|
12
|
+
# # Sort to resolve dependencies of the entire hash
|
13
|
+
# dependency_hash.tsort # => [:net, :dns, :logger, :webserver]
|
14
|
+
# # Sort to resolve dependencies of one node only
|
15
|
+
# dependency_hash.tsort(:dns) # => [:net, :dns]
|
16
|
+
class THash < Hash
|
17
|
+
include TSort
|
18
|
+
|
19
|
+
alias_method :tsort_each_node, :each_key
|
20
|
+
|
21
|
+
def tsort_each_child(node, &block)
|
22
|
+
fetch(node).each(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
def tsort(node=nil)
|
26
|
+
if node
|
27
|
+
subhash = subhash_for node
|
28
|
+
super().select { |n| subhash.include? n }
|
29
|
+
else
|
30
|
+
super()
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def subhash_for(node, memo=[])
|
37
|
+
memo.tap do |m|
|
38
|
+
fail TSort::Cyclic if m.include? node
|
39
|
+
m << node
|
40
|
+
fetch(node).each { |n| subhash_for(n, m) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/lib/aipp/version.rb
CHANGED
data/spec/lib/aipp/airac_spec.rb
CHANGED
@@ -8,7 +8,7 @@ describe AIPP::AIRAC do
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
context "on AIRAC date" do
|
11
|
+
context "on AIRAC date (as Date)" do
|
12
12
|
subject do
|
13
13
|
AIPP::AIRAC.new(Date.parse('2018-01-04'))
|
14
14
|
end
|
@@ -30,9 +30,9 @@ describe AIPP::AIRAC do
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
context "one day before AIRAC date" do
|
33
|
+
context "one day before AIRAC date (as String)" do
|
34
34
|
subject do
|
35
|
-
AIPP::AIRAC.new(
|
35
|
+
AIPP::AIRAC.new('2018-01-03')
|
36
36
|
end
|
37
37
|
|
38
38
|
it "must calculate correct #date" do
|
@@ -4,49 +4,119 @@ using AIPP::Refinements
|
|
4
4
|
|
5
5
|
describe AIPP::Refinements do
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
"
|
10
|
-
|
7
|
+
context String do
|
8
|
+
describe :blank_to_nil do
|
9
|
+
it "must convert blank to nil" do
|
10
|
+
"\n \n ".blank_to_nil.must_be :nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
it "must leave non-blank untouched" do
|
14
|
+
"foobar".blank_to_nil.must_equal "foobar"
|
15
|
+
end
|
11
16
|
|
12
|
-
|
13
|
-
|
17
|
+
it "must leave non-blank with whitespace untouched" do
|
18
|
+
"\nfoo bar\n".blank_to_nil.must_equal "\nfoo bar\n"
|
19
|
+
end
|
14
20
|
end
|
15
21
|
|
16
|
-
|
17
|
-
|
22
|
+
describe :blank? do
|
23
|
+
it "all whitespace must return true" do
|
24
|
+
"\n \n ".blank?.must_equal true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "not all whitespace must return false" do
|
28
|
+
"\nfoo bar\n".blank?.must_equal false
|
29
|
+
end
|
18
30
|
end
|
19
|
-
end
|
20
31
|
|
21
|
-
|
22
|
-
|
23
|
-
|
32
|
+
describe :classify do
|
33
|
+
it "must convert file name to class name" do
|
34
|
+
"ENR-5.1".classify.must_equal "ENR51"
|
35
|
+
"helper".classify.must_equal "Helper"
|
36
|
+
"foo_bar".classify.must_equal "FooBar"
|
37
|
+
end
|
24
38
|
end
|
25
39
|
end
|
26
40
|
|
27
|
-
|
28
|
-
|
29
|
-
|
41
|
+
context NilClass do
|
42
|
+
describe :blank_to_nil do
|
43
|
+
it "must return self" do
|
44
|
+
nil.blank_to_nil.must_be :nil?
|
45
|
+
end
|
30
46
|
end
|
31
47
|
|
32
|
-
|
33
|
-
|
48
|
+
describe :blank? do
|
49
|
+
it "must return true" do
|
50
|
+
nil.blank?.must_equal true
|
51
|
+
end
|
34
52
|
end
|
53
|
+
end
|
35
54
|
|
36
|
-
|
37
|
-
|
38
|
-
|
55
|
+
context Array do
|
56
|
+
describe :constantize do
|
57
|
+
it "must convert to constant" do
|
58
|
+
%w(AIPP Refinements).constantize.must_equal AIPP::Refinements
|
59
|
+
end
|
39
60
|
|
40
|
-
|
41
|
-
|
61
|
+
it "fails to convert to inexistant constant" do
|
62
|
+
-> { %w(Foo Bar).constantize }.must_raise NameError
|
63
|
+
end
|
42
64
|
end
|
65
|
+
end
|
43
66
|
|
44
|
-
|
45
|
-
|
46
|
-
|
67
|
+
context Enumerable do
|
68
|
+
describe :split do
|
69
|
+
context "by object" do
|
70
|
+
it "must split at matching element" do
|
71
|
+
[1, 2, 0, 3, 4].split(0).must_equal [[1, 2], [3, 4]]
|
72
|
+
end
|
73
|
+
|
74
|
+
it "won't split when no element matches" do
|
75
|
+
[1, 2, 3].split(0).must_equal [[1, 2, 3]]
|
76
|
+
end
|
77
|
+
|
78
|
+
it "won't split zero length enumerable" do
|
79
|
+
[].split(0).must_equal []
|
80
|
+
end
|
81
|
+
|
82
|
+
it "must keep leading empty subarrays" do
|
83
|
+
[0, 1, 2, 0, 3, 4].split(0).must_equal [[], [1, 2], [3, 4]]
|
84
|
+
end
|
85
|
+
|
86
|
+
it "must keep empty subarrays in the middle" do
|
87
|
+
[1, 2, 0, 0, 3, 4].split(0).must_equal [[1, 2], [], [3, 4]]
|
88
|
+
end
|
89
|
+
|
90
|
+
it "must drop trailing empty subarrays" do
|
91
|
+
[1, 2, 0, 3, 4, 0].split(0).must_equal [[1, 2], [3, 4]]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context "by block" do
|
96
|
+
it "must split at matching element" do
|
97
|
+
[1, 2, 0, 3, 4].split { |e| e.zero? }.must_equal [[1, 2], [3, 4]]
|
98
|
+
end
|
99
|
+
|
100
|
+
it "won't split when no element matches" do
|
101
|
+
[1, 2, 3].split { |e| e.zero? }.must_equal [[1, 2, 3]]
|
102
|
+
end
|
103
|
+
|
104
|
+
it "won't split zero length enumerable" do
|
105
|
+
[].split { |e| e.zero? }.must_equal []
|
106
|
+
end
|
107
|
+
|
108
|
+
it "must keep leading empty subarrays" do
|
109
|
+
[0, 1, 2, 0, 3, 4].split { |e| e.zero? }.must_equal [[], [1, 2], [3, 4]]
|
110
|
+
end
|
111
|
+
|
112
|
+
it "must keep empty subarrays in the middle" do
|
113
|
+
[1, 2, 0, 0, 3, 4].split { |e| e.zero? }.must_equal [[1, 2], [], [3, 4]]
|
114
|
+
end
|
47
115
|
|
48
|
-
|
49
|
-
|
116
|
+
it "must drop trailing empty subarrays" do
|
117
|
+
[1, 2, 0, 3, 4, 0].split { |e| e.zero? }.must_equal [[1, 2], [3, 4]]
|
118
|
+
end
|
119
|
+
end
|
50
120
|
end
|
51
121
|
end
|
52
122
|
|