aipp 0.1.3 → 0.2.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.
- 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
|
|