aipp 0.2.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +21 -0
  4. data/README.md +147 -91
  5. data/exe/aip2aixm +2 -2
  6. data/exe/aip2ofmx +2 -2
  7. data/lib/aipp/aip.rb +96 -11
  8. data/lib/aipp/border.rb +77 -46
  9. data/lib/aipp/debugger.rb +101 -0
  10. data/lib/aipp/downloader.rb +18 -5
  11. data/lib/aipp/executable.rb +33 -20
  12. data/lib/aipp/parser.rb +42 -37
  13. data/lib/aipp/patcher.rb +5 -2
  14. data/lib/aipp/regions/LF/README.md +49 -0
  15. data/lib/aipp/regions/LF/aerodromes.rb +223 -0
  16. data/lib/aipp/regions/LF/d_p_r_airspaces.rb +56 -0
  17. data/lib/aipp/regions/LF/dangerous_activities.rb +49 -0
  18. data/lib/aipp/regions/LF/designated_points.rb +47 -0
  19. data/lib/aipp/regions/LF/fixtures/aerodromes.yml +608 -0
  20. data/lib/aipp/regions/LF/helipads.rb +122 -0
  21. data/lib/aipp/regions/LF/helpers/base.rb +167 -174
  22. data/lib/aipp/regions/LF/helpers/surface.rb +49 -0
  23. data/lib/aipp/regions/LF/helpers/usage_limitation.rb +20 -0
  24. data/lib/aipp/regions/LF/navigational_aids.rb +85 -0
  25. data/lib/aipp/regions/LF/obstacles.rb +153 -0
  26. data/lib/aipp/regions/LF/serviced_airspaces.rb +70 -0
  27. data/lib/aipp/regions/LF/services.rb +172 -0
  28. data/lib/aipp/t_hash.rb +3 -4
  29. data/lib/aipp/version.rb +1 -1
  30. data/lib/aipp.rb +7 -5
  31. data/lib/core_ext/enumerable.rb +2 -2
  32. data/lib/core_ext/hash.rb +21 -5
  33. data/lib/core_ext/nokogiri.rb +54 -0
  34. data/lib/core_ext/string.rb +32 -65
  35. data.tar.gz.sig +0 -0
  36. metadata +70 -81
  37. metadata.gz.sig +0 -0
  38. data/lib/aipp/airac.rb +0 -55
  39. data/lib/aipp/regions/LF/AD-1.3.rb +0 -177
  40. data/lib/aipp/regions/LF/AD-1.6.rb +0 -33
  41. data/lib/aipp/regions/LF/AD-2.rb +0 -344
  42. data/lib/aipp/regions/LF/AD-3.1.rb +0 -185
  43. data/lib/aipp/regions/LF/ENR-2.1.rb +0 -167
  44. data/lib/aipp/regions/LF/ENR-4.1.rb +0 -41
  45. data/lib/aipp/regions/LF/ENR-4.3.rb +0 -27
  46. data/lib/aipp/regions/LF/ENR-5.1.rb +0 -106
  47. data/lib/aipp/regions/LF/ENR-5.4.rb +0 -90
  48. data/lib/aipp/regions/LF/ENR-5.5.rb +0 -55
  49. data/lib/aipp/regions/LF/fixtures/AD-1.3.yml +0 -511
  50. data/lib/aipp/regions/LF/fixtures/AD-2.yml +0 -185
  51. data/lib/aipp/regions/LF/fixtures/AD-3.1.yml +0 -10
  52. data/lib/aipp/regions/LF/helpers/URL.rb +0 -26
  53. data/lib/aipp/regions/LF/helpers/navigational_aid.rb +0 -104
  54. data/lib/aipp/regions/LF/helpers/radio_AD.rb +0 -110
  55. data/lib/core_ext/object.rb +0 -43
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11e3df230b0b8dcafdef31e97ed97e2bbf7d6c45f75cb86949ee92a3980e07f4
4
- data.tar.gz: 9fe425721151ec0c99677813ac283de61f274f97a6d103cbce8d4d178e8f794d
3
+ metadata.gz: '09151e277b537c1529951cd69b38bb5d6fc47f855e6041ca3537d36462f93907'
4
+ data.tar.gz: f0b9d43b940ced7998a079cf6dce602386d52ad90995b7e77a6b6429f09f798e
5
5
  SHA512:
6
- metadata.gz: 000f611816b7d7b7112649830b3cd1ae383fd9b2a6defcc554715bfbabb9c3008c1f90f3f8062d3c43630c06f7642cd8e8f3c559a99e6bb7ccc4e045062333b5
7
- data.tar.gz: c29e73e68318fe197e06d07a08c79fd5cfcb6be9aff3c5fbb96150f0385682e5a51284caf311026b8623d342a77b73485db1509a844de3217161656b52e8818c
6
+ metadata.gz: 205b609c8aad3999495447f7c55aba98282b5b6347a4f89caef3e06e3edc8f8e28c4e523cc48737522f895813309fe9d4f7b8d3d2d0db37a12d5808c41509137
7
+ data.tar.gz: 9a228dd7a152dfd5e0bbbd22b8a068e8de404ba9a4d48f4a30f7b3f0b352c694f12b4a45b543880b20884645fbd52fa475dd738f7385541113462dcadce1fc29
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## Main
2
+
3
+ Nothing so far
4
+
5
+ ## 1.0.0
6
+
7
+ #### Breaking Changes
8
+ * Switch from individual AIP HTML section files to the comprehensive AIP XML
9
+ database dump for the LF region reference implementation.
10
+ * Drop the mandatory `URL` helper in favour of a mandatory `url_for` method.
11
+ * Renamed default git branch to `main`
12
+ * Improve calculation of short feature hash in manifest in order to include
13
+ e.g. geometries of airspaces.
14
+
15
+ #### Changes
16
+ * Switch from `pry` to `debug`
17
+
18
+ #### Additions
19
+ * Unsevere warnings
20
+ * Support for .xlsx, .ods and .csv files
21
+
1
22
  ## 0.2.6
2
23
 
3
24
  #### Additions
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  Parser for Aeronautical Information Publication (AIP) available online.
8
8
 
9
- This gem incluces two executables to download and parse aeronautical data as HTML or PDF, then build and export is as [AIXM](https://github.com/svoop/aixm) or [OFMX](https://github.com/openflightmaps/ofmx/wiki).
9
+ This gem incluces two executables to download and parse aeronautical data as HTML, PDF, XSLX, ODS and CSV, then build and export is as [AIXM](https://github.com/svoop/aixm) or [OFMX](https://github.com/openflightmaps/ofmx/wiki).
10
10
 
11
11
  * [Homepage](https://github.com/svoop/aipp)
12
12
  * [Rubydoc](https://www.rubydoc.info/gems/aipp/AIPP)
@@ -18,9 +18,8 @@ This gem incluces two executables to download and parse aeronautical data as HTM
18
18
  [Usage](#usage)<br>
19
19
  [Storage](#storage)<br>
20
20
  [Regions](#regions)<br>
21
- [AIP Parsers](#aip-parsers)<br>
22
- [References](#references)<br>
23
21
  [AIRAC Date Calculations](#airac-date-calculations)<br>
22
+ [References](#references)<br>
24
23
  [Development](#development)
25
24
 
26
25
  ## Install
@@ -30,7 +29,7 @@ This gem incluces two executables to download and parse aeronautical data as HTM
30
29
  This gem is [cryptographically signed](https://guides.rubygems.org/security/#using-gems) in order to assure it hasn't been tampered with. Unless already done, please add the author's public key as a trusted certificate now:
31
30
 
32
31
  ```
33
- gem cert --add <(curl -Ls https://raw.github.com/svoop/aipp/master/certs/svoop.pem)
32
+ gem cert --add <(curl -Ls https://raw.github.com/svoop/aipp/main/certs/svoop.pem)
34
33
  ```
35
34
 
36
35
  ### Standalone
@@ -124,6 +123,24 @@ module AIPP
124
123
 
125
124
  DEPENDS = %w(ENR-2.1 ENR-2.2) # declare dependencies to other AIPs
126
125
 
126
+ end
127
+ end
128
+ end
129
+ ```
130
+
131
+ The class has to implement some methods either in the class itself or in a [helper](#Helpers) included by the class.
132
+
133
+ ⚠️ Parser files usually follow AIP naming conventions such as `ENR-4.3`. However, you're free to use arbitrary naming for parser files e.g. if you're working with one big data source which contains the full AIP dataset.
134
+
135
+ #### Mandatory `parse` Method
136
+
137
+ The class must implement the `parse` method which contains the code to read, parse and write the data:
138
+
139
+ ```ruby
140
+ module AIPP
141
+ module LF
142
+ class ENR43 < AIP
143
+
127
144
  def parse
128
145
  html = read # read the Nokogiri::HTML5 document
129
146
  feature = (...) # build the feature
@@ -144,8 +161,8 @@ module AIPP
144
161
 
145
162
  def parse
146
163
  %i(one two three).each do |part|
147
- html = read(aip_file: "#{aip}.#{part}") # read with a non-standard name
148
- support_html = read(aip_file: 'AD-0.6') # maybe read necessary support documents
164
+ html = read("#{aip}.#{part}") # read with a non-standard name
165
+ support_html = read('AD-0.6') # maybe read necessary support documents
149
166
  (...)
150
167
  end
151
168
  end
@@ -157,26 +174,85 @@ end
157
174
 
158
175
  Inside the `parse` method, you have access to the following methods:
159
176
 
160
- * [`read`](https://www.rubydoc.info/gems/aipp/AIPP/AIP#read-instance_method) – download and read an AIP file
161
- * [`add`](https://www.rubydoc.info/gems/aipp/AIPP/AIP#add-instance_method) – add a [`AIXM::Feature`](https://www.rubydoc.info/gems/aixm/AIXM/Feature)
162
- * [`find`](https://www.rubydoc.info/gems/aipp/AIPP/AIP#find-instance_method) find previously written [`AIXM::Feature`s](https://www.rubydoc.info/gems/aixm/AIXM/Feature) by object
163
- * [`find_by`](https://www.rubydoc.info/gems/aipp/AIPP/AIP#find_by-instance_method) find previously written [`AIXM::Feature`s](https://www.rubydoc.info/gems/aixm/AIXM/Feature) by class and attribute values
164
- * [`unique`](https://www.rubydoc.info/gems/aipp/AIPP/AIP#unique-instance_method) prevent duplicate [`AIXM::Feature`s](https://www.rubydoc.info/gems/aixm/AIXM/Feature)
165
- * some core extensions from ActiveSupport – [`Object#blank`](https://www.rubydoc.info/gems/activesupport/Object#blank%3F-instance_method) and [`String`](https://www.rubydoc.info/gems/activesupport/String)
166
- * core extensions from this gem – [`Object`](https://www.rubydoc.info/gems/aipp/Object), [`NilClass`](https://www.rubydoc.info/gems/aipp/NilClass), [`Integer`](https://www.rubydoc.info/gems/aipp/Integer), [`String`](https://www.rubydoc.info/gems/aipp/String), [`Hash`](https://www.rubydoc.info/gems/aipp/Hash) and [`Enumerable`](https://www.rubydoc.info/gems/aipp/Enumerable)
177
+ Method | Description
178
+ -------|------------
179
+ [`read`](https://www.rubydoc.info/gems/aipp/AIPP/AIP#read-instance_method) | download and read an AIP file
180
+ [`add`](https://www.rubydoc.info/gems/aipp/AIPP/AIP#add-instance_method) | add a [`AIXM::Feature`](https://www.rubydoc.info/gems/aixm/AIXM/Feature)
181
+ [`find`](https://www.rubydoc.info/gems/aipp/AIPP/AIP#find-instance_method) | find previously written [`AIXM::Feature`](https://www.rubydoc.info/gems/aixm/AIXM/Feature)s by object
182
+ [`find_by`](https://www.rubydoc.info/gems/aipp/AIPP/AIP#find_by-instance_method) | find previously written [`AIXM::Feature`](https://www.rubydoc.info/gems/aixm/AIXM/Feature)s by class and attribute values
183
+ [`unique`](https://www.rubydoc.info/gems/aipp/AIPP/AIP#unique-instance_method) | prevent duplicate [`AIXM::Feature`](https://www.rubydoc.info/gems/aixm/AIXM/Feature)s
184
+ [`given`](https://www.rubydoc.info/gems/aipp/AIPP/AIP#given-instance_method) | inline condition for assignments
185
+ [`link_to`](https://www.rubydoc.info/gems/aipp/AIPP/AIP#link_to-instance_method) | optionally checked Markdown link
186
+ [`Object#blank`](https://www.rubydoc.info/gems/activesupport/Object#blank%3F-instance_method) and [`String`](https://www.rubydoc.info/gems/activesupport/String) | some core extensions from ActiveSupport
187
+ [`aip`](https://www.rubydoc.info/gems/aipp/AIPP%2FAIP:aip) | AIP name (equal to the parser file name without its file extension such as "ENR-2.1" implemented in the file "ENR-2.1.rb")
188
+ [`aip_file`](https://www.rubydoc.info/gems/aipp/AIPP%2FAIP:aip_file) | AIP file as passed and possibly renamed by `url_for`
189
+ [`options`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#options-instance_method) | arguments read from <tt>aip2aixm</tt> or <tt>aip2ofmx</tt> respectively
190
+ [`config`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#config-instance_method) | configuration read from <tt>config.yml</tt>
191
+ [`borders`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#borders-instance_method) | borders defined as GeoJSON read from the region (see below)
192
+ [`cache`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#cache-instance_method) | `OStruct` instance to make objects available across AIPs
193
+
194
+ To make the parser code more readable, this gem provides a few useful core extensions as well:
195
+
196
+ * [`NilClass`](https://www.rubydoc.info/gems/aipp/NilClass)
197
+ * [`Integer`](https://www.rubydoc.info/gems/aipp/Integer)
198
+ * [`String`](https://www.rubydoc.info/gems/aipp/String)
199
+ * [`Hash`](https://www.rubydoc.info/gems/aipp/Hash)
200
+ * [`Enumerable`](https://www.rubydoc.info/gems/aipp/Enumerable)
201
+ * [`Nokogiri`](https://www.rubydoc.info/gems/aipp/Nokogiri)
202
+
203
+ #### Mandatory `url_for` Method
204
+
205
+ The class must implement the `url_for` method which returns the URL from where to download the AIP file:
206
+
207
+ ```ruby
208
+ module AIPP
209
+ module LF
210
+ class AD2 < AIP
211
+
212
+ def url_for(aip_file)
213
+ # build and return the download URL for the aip file
214
+ end
215
+
216
+ end
217
+ end
218
+ end
219
+ ```
220
+
221
+ There are a few things to note about `url_for`:
222
+
223
+ * If the returned string begins with a protocol like `https:`, the downloader will fetch the file from there.
224
+ * If the returned string is just a file name, the downloader will look for this exact file in the current local directory.
225
+ * The passed `aip_file` will be used as the file name for the local copy in the sources directory. You can rename it on the fly by assigning a new value to this variable.
226
+
227
+ #### Optional `setup` Method
228
+
229
+ The class may implement the `setup` method. If present, it will be called when this parser is instantiated:
167
230
 
168
- As well as the following methods:
169
231
 
170
- * [`options`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#options-instance_method) – arguments read from <tt>aip2aixm</tt> or <tt>aip2ofmx</tt> respectively
171
- * [`config`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#config-instance_method) – configuration read from <tt>config.yml</tt>
172
- * [`borders`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#borders-instance_method) – borders defined as GeoJSON read from the region (see below)
173
- * [`cache`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#cache-instance_method) virgin `OStruct` instance to make objects available across AIPs
232
+ ```ruby
233
+ module AIPP
234
+ module LF
235
+ class AD2 < AIP
236
+
237
+ def setup
238
+ AIXM.config.voice_channel_separation = :any
239
+ cache.setup_at ||= Time.now
240
+ end
241
+
242
+ end
243
+ end
244
+ end
245
+ ```
174
246
 
175
247
  ### Borders
176
248
 
177
- AIXM knows named borders for country boundaries. However, you might need additional borders which don't exist as named boarders.
249
+ AIXM knows named borders for country boundaries. However, you might need additional borders which don't exist as named borders.
250
+
251
+ You can define additional borders as [`AIPP::Border`](https://www.rubydoc.info/gems/aipp/AIPP/Border) objects in two ways.
178
252
 
179
- To define additional borders, create simple GeoJSON files in the <tt>lib/aipp/regions/{REGION}/borders/</tt> directory, for example this `custom_border.geojson`:
253
+ #### From GeoJSON
254
+
255
+ Create simple GeoJSON files in the <tt>lib/aipp/regions/{REGION}/borders/</tt> directory, for example this `my_border_1.geojson`:
180
256
 
181
257
  ```json
182
258
  {
@@ -188,6 +264,13 @@ To define additional borders, create simple GeoJSON files in the <tt>lib/aipp/re
188
264
  [6.009531650000042, 45.12013319700009],
189
265
  [6.015747738000073, 45.12006702600007]
190
266
  ]
267
+ },
268
+ {
269
+ "type": "LineString",
270
+ "coordinates": [
271
+ [4.896732957592112, 43.95662950764992],
272
+ [4.005739165537195, 44.10769266295027]
273
+ ]
191
274
  }
192
275
  ]
193
276
  }
@@ -195,73 +278,44 @@ To define additional borders, create simple GeoJSON files in the <tt>lib/aipp/re
195
278
 
196
279
  ⚠️ The GeoJSON file must consist of exactly one `GeometryCollection` which may contain any number of `LineString` geometries. Only `LineString` geometries are recognized! To define a closed polygon, the first coordinates of a `LineString` must be identical to the last coordinates.
197
280
 
198
- The [`borders`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#borders-instance_method) method gives you access to a map from the border name (upcased file name) to the corresponding [`AIPP::Border`](https://www.rubydoc.info/gems/aipp/AIPP/Border) object:
281
+ #### From Coordinates
282
+
283
+ It's also possible to create a [`AIPP::Border`](https://www.rubydoc.info/gems/aipp/AIPP/Border) objects on the fly:
199
284
 
200
285
  ```ruby
201
- borders # => { "CUSTOM_BORDER" => #<AIPP::Border file=custom_border.geojson> }
286
+ my_border_2 = AIPP::Border.from_array(
287
+ [
288
+ ["6.009531650000042 45.12013319700009", "6.015747738000073 45.12006702600007"],
289
+ ["4.896732957592112 43.95662950764992", "4.005739165537195 44.10769266295027"]
290
+ ]
291
+ )
202
292
  ```
203
293
 
204
- The border object implements simple nearest point and segment calculations to create arrays of [`AIXM::XY`](https://www.rubydoc.info/gems/aixm/AIXM/XY) which can be used with [`AIXM::Component::Geometry`](https://www.rubydoc.info/gems/aixm/AIXM/Component/Geometry).
205
-
206
- See [`AIPP::Border`](https://www.rubydoc.info/gems/aipp/AIPP/Border) for more on this.
207
-
208
- ### Helpers
209
-
210
- Helpers are modules defined in the <tt>lib/aipp/regions/{REGION}/helpers/</tt> directory. All helper modules are required automatically in alphabetic order.
211
-
212
- There is one mandatory helper called `URL.rb` which must define the following method to build URLs from which to download AIPs:
294
+ The coordinate pairs must be separated with whitespaces and/or commas. If you want to use this border everywhere, make sure you add it to the others:
213
295
 
214
296
  ```ruby
215
- module AIPP
216
- module LF
217
- module Helpers
218
- module URL
219
-
220
- def url_for(aip_file)
221
- # build and return the download URL for the aip file
222
- end
223
-
224
- end
225
- end
226
- end
227
- end
297
+ borders["my_border_2"] = my_border_2
228
298
  ```
229
299
 
230
- Feel free to add more helpers to DRY code which is shared by multiple AIP parsers. Say you want to extract methods which are used by all AIP parsers:
231
-
232
- ```ruby
233
- module AIPP
234
- module LF
235
- module Helpers
236
- module Base
300
+ #### Usage in Parsers
237
301
 
238
- def source(position:, aip_file: nil)
239
- (...)
240
- end
302
+ In the parser, the [`borders`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#borders-instance_method) method gives you access to all borders read from GeoJSON files:
241
303
 
242
- end
243
- end
244
- end
245
- end
304
+ ```ruby
305
+ borders # => { "my_border_1" => #<AIPP::Border>, "my_border_2" => #<AIPP::Border> }
246
306
  ```
247
307
 
248
- To use this `source` method, simply include the helper module in the AIP parser:
308
+ The border object implements simple nearest point and segment calculations to create arrays of [`AIXM::XY`](https://www.rubydoc.info/gems/aixm/AIXM/XY) which can be used with [`AIXM::Component::Geometry`](https://www.rubydoc.info/gems/aixm/AIXM/Component/Geometry).
249
309
 
250
- ```ruby
251
- module AIPP
252
- module LF
253
- class AD2 < AIP
310
+ See [`AIPP::Border`](https://www.rubydoc.info/gems/aipp/AIPP/Border) for more on this.
254
311
 
255
- include AIPP::LF::Helpers::Base
312
+ ### Helpers
256
313
 
257
- end
258
- end
259
- end
260
- ```
314
+ Helpers are modules defined in the <tt>lib/aipp/regions/{REGION}/helpers/</tt> directory. All helper modules are required automatically in alphabetic order.
261
315
 
262
316
  ### Fixtures and Patches
263
317
 
264
- Fixtures is static data defined as YAML in the <tt>lib/aipp/regions/{REGION}/fixtures/</tt> directory. All fixtures are read automatically. Please note that the name of the AIP parser (e.g. `AD-1.3.rb`) must match the name of the corresponding fixture (e.g. `fixtures/AD-1.3.yml`).
318
+ Fixtures are static YAML data files in the <tt>lib/aipp/regions/{REGION}/fixtures/</tt> directory. All fixtures are read automatically. Please note that the name of the AIP parser (e.g. `AD-1.3.rb`) must match the name of the corresponding fixture (e.g. `fixtures/AD-1.3.yml`).
265
319
 
266
320
  When parsed data is faulty or missing, you may fall back to such static data instead. This is where patches come in. You can patch any AIXM attribute setter by defining a patch block inside the AIP parser and accessing the static data via `parser.fixture`:
267
321
 
@@ -270,14 +324,10 @@ module AIPP
270
324
  module LF
271
325
  class AD2 < AIP
272
326
 
273
- patch AIXM::Component::Runway::Direction, :xy do |parser, object, value|
274
- throw :abort unless value.nil?
275
- @fixtures ||= YAML.load_file(Pathname(__FILE__).dirname.join('AD-1.3.yml'))
276
- airport_id = parser.instance_variable_get(:@airport).id
277
- direction_name = object.name.to_s
278
- throw :abort if (xy = parser.fixture.dig('runways', airport_id, direction_name, 'xy')).nil?
279
- lat, long = xy.split(/\s+/)
280
- AIXM.xy(lat: lat, long: long)
327
+ patch AIXM::Feature::Airport, :z do |parser, object, value|
328
+ throw(:abort) unless value.nil?
329
+ throw(:abort, 'fixture missing') unless z = parser.fixture.dig(object.id, 'z')
330
+ AIXM.z(z, :qnh)
281
331
  end
282
332
 
283
333
  end
@@ -285,7 +335,17 @@ module AIPP
285
335
  end
286
336
  ```
287
337
 
288
- The patch block receives the object and the current value. If this value is okay, `throw :abort` to leave the patch block without touching anything. Otherwise, have the patch block return a new value which will be used instead.
338
+ The patch receives the object and the value which is about to be assigned. It should implement something along these lines:
339
+
340
+ * If the value is okay, `throw(:abort)` to leave the patch block without touching anything.
341
+ * Otherwise, try to fetch a better value e.g. from the fixtures. If no better value can be found (e.g. outdated fixtures), `throw(:abort, "reason")` to leave the patch block and fail with a useful error message which contains the reason thrown.
342
+ * At last, build and return the value object which will be assigned instead of the original value.
343
+
344
+ In case the `object` does not carry enough details, you can access instance variables of the parser like so:
345
+
346
+ ```ruby
347
+ parser.instance_variable_get(:@instance_variable)
348
+ ```
289
349
 
290
350
  ### Source File Line Numbers
291
351
 
@@ -295,11 +355,9 @@ In order to reference the source of an AIXM/OFMX feature, it's necessary to know
295
355
  tr.line
296
356
  ```
297
357
 
298
- ⚠️ Make sure you have build Nokogumbo `--with-libxml2`. Otherwise, all elements will report line number `0` and therefore render OFMX documents invalid. See the [Nokogumbo README](https://github.com/rubys/nokogumbo/blob/master/README.md#flavors-of-nokogumbo) for more on this.
299
-
300
358
  ### Errors
301
359
 
302
- You should `fail` on fatal problems. The `-e` command line argument will open a Pry session when such an error occurs. Issue errors as usual:
360
+ You should `fail` on fatal problems which must be fixed. The `--debug-on-error` command line argument will open a debug session when such an error occurs. Issue errors as usual:
303
361
 
304
362
  ```ruby
305
363
  fail "my message"
@@ -307,11 +365,11 @@ fail "my message"
307
365
 
308
366
  ### Warnings
309
367
 
310
- You should `warn` on non-fatal problems. The `-w ID` command line argument will open a Pry session when then warning with the given ID occurs. To issue a warning:
368
+ You should `warn` on non-fatal problems which should be fixed (default) or might be ignored (`severe: false`). The `--debug-on-warning ID` command line argument will open a debug session when then warning with the given ID occurs. To issue a warning:
311
369
 
312
370
  ```ruby
313
- warn("my message", pry: binding) # open Pry attached to the binding
314
- warn("my message", pry: error) # open Pry attached to the error
371
+ warn("my message")
372
+ warn("my message", severe: false) # show warning only when --unsevere-warn argument is set
315
373
  ```
316
374
 
317
375
  ### Messages
@@ -333,11 +391,13 @@ Use `verbose_info` for in-depth info messages which are only shown if the `--ver
333
391
  verbose_info("my message") # displays "my message" in blue
334
392
  ```
335
393
 
336
- ### Pry
394
+ #### debug
337
395
 
338
- Pry is loaded with stack explorer support. Type `help` in the Pry console to see all available commands. The most useful command in the context of this gem is `up` which beams you one frame up in the caller stack.
396
+ The [default Ruby debugger](https://github.com/ruby/debug#debug-command-on-the-debug-console) is enabled by default, you can add a breakpoint as usual with:
339
397
 
340
- Note: It's not currently possible to use pry-byebug at this time since it [interferes with pry-rescue](https://github.com/ConradIrwin/pry-rescue/issues/71).
398
+ ```ruby
399
+ debugger
400
+ ```
341
401
 
342
402
  ## AIRAC Date Calculations
343
403
 
@@ -353,10 +413,6 @@ airac.next_id # => 1801
353
413
 
354
414
  ## References
355
415
 
356
- * LF - France
357
- * [SIA – AIP publisher](https://www.sia.aviation-civile.gouv.fr)
358
- * [OpenData – public data files](https://www.data.gouv.fr)
359
- * [Protected Planet – protected area data files](https://www.protectedplanet.net)
360
416
  * [Geo Maps – programmatically generated GeoJSON maps](https://github.com/simonepri/geo-maps)
361
417
  * [open flightmaps – open-source aeronautical maps](https://openflightmaps.org)
362
418
  * [AIXM Rubygem – AIXM/OFMX generator for Ruby](https://github.com/svoop/aixm)
data/exe/aip2aixm CHANGED
@@ -4,8 +4,8 @@ require 'bundler/inline'
4
4
 
5
5
  gemfile do
6
6
  source 'https://rubygems.org'
7
- ruby '>= 2.7'
8
- gem 'aipp', '~> 0'
7
+ ruby '>= 3.0.0'
8
+ gem 'aipp', '~> 1'
9
9
  end
10
10
 
11
11
  AIPP::Executable.new(schema: File.basename($0)[4..-1].to_sym).run
data/exe/aip2ofmx CHANGED
@@ -4,8 +4,8 @@ require 'bundler/inline'
4
4
 
5
5
  gemfile do
6
6
  source 'https://rubygems.org'
7
- ruby '>= 2.7'
8
- gem 'aipp', '~> 0'
7
+ ruby '>= 3.0.0'
8
+ gem 'aipp', '~> 1'
9
9
  end
10
10
 
11
11
  AIPP::Executable.new(schema: File.basename($0)[4..-1].to_sym).run
data/lib/aipp/aip.rb CHANGED
@@ -3,13 +3,18 @@ module AIPP
3
3
  # @abstract
4
4
  class AIP
5
5
  extend Forwardable
6
+ include AIPP::Debugger
6
7
  include AIPP::Patcher
7
8
 
8
9
  DEPENDS = []
9
10
 
10
- # @return [String] AIP name (e.g. "ENR-2.1")
11
+ # @return [String] AIP name (equal to the parser file name without its
12
+ # file extension such as "ENR-2.1" implemented in the file "ENR-2.1.rb")
11
13
  attr_reader :aip
12
14
 
15
+ # @return [String] AIP file as passed and possibly updated by `url_for`
16
+ attr_reader :aip_file
17
+
13
18
  # @return [Object] Fixture read from YAML file
14
19
  attr_reader :fixture
15
20
 
@@ -30,7 +35,12 @@ module AIPP
30
35
 
31
36
  def initialize(aip:, downloader:, fixture:, parser:)
32
37
  @aip, @downloader, @fixture, @parser = aip, downloader, fixture, parser
33
- self.class.include ("AIPP::%s::Helpers::URL" % options[:region]).constantize
38
+ setup if respond_to? :setup
39
+ end
40
+
41
+ # @return [String]
42
+ def inspect
43
+ "#<AIPP::AIP #{aip}>"
34
44
  end
35
45
 
36
46
  # Read an AIP source file
@@ -38,15 +48,18 @@ module AIPP
38
48
  # Read the cached source file if it exists in the source archive. Otherwise,
39
49
  # download it from URL and cache it.
40
50
  #
41
- # An URL builder method +url_for(aip_file)+ must be defined either in
42
- # +helper.rb+ or in the AIP parser definition (e.g. +ENR-2.1.rb+).
51
+ # An URL builder method +url_for(aip_file)+ must be implemented by the AIP
52
+ # parser definition (e.g. +ENR-2.1.rb+).
43
53
  #
44
- # @param aip_file [String] e.g. "ENR-2.1" or "AD-2.LFMV" (default: +aip+)
45
- # @return [Nokogiri::HTML5::Document, String] HTML as Nokogiri document,
46
- # PDF or TXT as String
54
+ # @param aip_file [String] e.g. "ENR-2.1" or "AD-2.LFMV" (default: +aip+
55
+ # with section stripped e.g. "AD-1.3-2" -> "AD-1.3")
56
+ # @return [Nokogiri::XML::Document, Nokogiri::HTML5::Document,
57
+ # Roo::Spreadsheet, String] XML/HTML as Nokogiri document, XLSX/ODS/CSV
58
+ # as Roo document, PDF and TXT as String
47
59
  def read(aip_file=nil)
48
- aip_file ||= aip
49
- @downloader.read(document: aip_file, url: url_for(aip_file))
60
+ @aip_file = aip_file || aip.remove(/(?<![A-Z])-\d+$/)
61
+ url = url_for(@aip_file) # may update aip_file string
62
+ @downloader.read(document: @aip_file, url: url)
50
63
  end
51
64
 
52
65
  # Add feature to AIXM
@@ -54,7 +67,7 @@ module AIPP
54
67
  # @param feature [AIXM::Feature] e.g. airport or airspace
55
68
  # @return [AIXM::Feature] added feature
56
69
  def add(feature)
57
- verbose_info "Adding #{feature.inspect}"
70
+ verbose_info "adding #{feature.inspect}"
58
71
  aixm.add_feature feature
59
72
  feature
60
73
  end
@@ -76,6 +89,78 @@ module AIPP
76
89
  aixm.features.send(method, *args)
77
90
  end
78
91
  end
79
- end
80
92
 
93
+ # @overload given(*objects)
94
+ # Return +objects+ unless at least one of them equals nil
95
+ #
96
+ # @example
97
+ # # Instead of this:
98
+ # first, last = unless ((first = expensive_first).nil? || (last = expensive_last).nil?)
99
+ # [first, last]
100
+ # end
101
+ #
102
+ # # Use the following:
103
+ # first, last = given(expensive_first, expensive_last)
104
+ #
105
+ # @param *objects [Array<Object>] any objects really
106
+ # @return [Object] nil if at least one of the objects is nil, given
107
+ # objects otherwise
108
+ #
109
+ # @overload given(*objects)
110
+ # Yield +objects+ unless at least one of them equals nil
111
+ #
112
+ # @example
113
+ # # Instead of this:
114
+ # name = unless ((first = expensive_first.nil? || (last = expensive_last.nil?)
115
+ # "#{first} #{last}"
116
+ # end
117
+ #
118
+ # # Use any of the following:
119
+ # name = given(expensive_first, expensive_last) { |f, l| "#{f} #{l}" }
120
+ # name = given(expensive_first, expensive_last) { "#{_1} #{_2}" }
121
+ #
122
+ # @param *objects [Array<Object>] any objects really
123
+ # @yield [Array<Object>] objects passed as parameter
124
+ # @return [Object] nil if at least one of the objects is nil, return of
125
+ # block otherwise
126
+ def given(*objects)
127
+ if objects.none?(&:nil?)
128
+ block_given? ? yield(*objects) : objects
129
+ end
130
+ end
131
+
132
+ # Build and optionally check a Markdown link
133
+ #
134
+ # @example
135
+ # options[:check_links] = false
136
+ # link_to('foo', 'https://bar.com/exists') # => "[foo](https://bar.com/exists)"
137
+ # link_to('foo', 'https://bar.com/not-found') # => "[foo](https://bar.com/not-found)"
138
+ # options[:check_links] = true
139
+ # link_to('foo', 'https://bar.com/exists') # => "[foo](https://bar.com/exists)"
140
+ # link_to('foo', 'https://bar.com/not-found') # => nil
141
+ #
142
+ # @params body [String] body text of the link
143
+ # @params url [String] URL of the link
144
+ # @return [String, nil] Markdown link
145
+ def link_to(body, url)
146
+ "[#{body}](#{url})" if !options[:check_links] || url_exists?(url)
147
+ end
148
+
149
+ private
150
+
151
+ def url_exists?(url)
152
+ uri = URI.parse(url)
153
+ Net::HTTP.new(uri.host, uri.port).tap do |request|
154
+ request.use_ssl = (uri.scheme == 'https')
155
+ path = uri.path.present? ? uri.path : '/'
156
+ result = request.request_head(path)
157
+ if result.kind_of? Net::HTTPRedirection
158
+ url_exist?(result['location'])
159
+ else
160
+ result.code == '200'
161
+ end
162
+ end
163
+ end
164
+
165
+ end
81
166
  end