aipp 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +2 -2
  3. data/CHANGELOG.md +17 -1
  4. data/README.md +269 -150
  5. data/exe/aip2aixm +2 -8
  6. data/exe/aip2ofmx +2 -8
  7. data/exe/notam2aixm +5 -0
  8. data/exe/notam2ofmx +5 -0
  9. data/lib/aipp/aip/README.md +10 -0
  10. data/lib/aipp/aip/executable.rb +40 -0
  11. data/lib/aipp/aip/parser.rb +9 -0
  12. data/lib/aipp/aip/runner.rb +85 -0
  13. data/lib/aipp/border.rb +2 -2
  14. data/lib/aipp/debugger.rb +14 -19
  15. data/lib/aipp/downloader/file.rb +57 -0
  16. data/lib/aipp/downloader/graphql.rb +29 -0
  17. data/lib/aipp/downloader/http.rb +48 -0
  18. data/lib/aipp/downloader.rb +78 -29
  19. data/lib/aipp/environment.rb +88 -0
  20. data/lib/aipp/executable.rb +36 -53
  21. data/lib/aipp/notam/README.md +25 -0
  22. data/lib/aipp/notam/executable.rb +27 -0
  23. data/lib/aipp/notam/parser.rb +9 -0
  24. data/lib/aipp/notam/runner.rb +28 -0
  25. data/lib/aipp/parser.rb +133 -160
  26. data/lib/aipp/patcher.rb +4 -5
  27. data/lib/aipp/regions/LF/README.md +6 -2
  28. data/lib/aipp/regions/LF/aip/aerodromes.rb +220 -0
  29. data/lib/aipp/regions/LF/aip/d_p_r_airspaces.rb +53 -0
  30. data/lib/aipp/regions/LF/aip/dangerous_activities.rb +48 -0
  31. data/lib/aipp/regions/LF/aip/designated_points.rb +44 -0
  32. data/lib/aipp/regions/LF/aip/helipads.rb +119 -0
  33. data/lib/aipp/regions/LF/aip/navigational_aids.rb +82 -0
  34. data/lib/aipp/regions/LF/aip/obstacles.rb +150 -0
  35. data/lib/aipp/regions/LF/aip/serviced_airspaces.rb +67 -0
  36. data/lib/aipp/regions/LF/aip/services.rb +169 -0
  37. data/lib/aipp/regions/LF/fixtures/aerodromes.yml +2 -2
  38. data/lib/aipp/regions/LF/helpers/base.rb +32 -32
  39. data/lib/aipp/regions/LS/README.md +59 -0
  40. data/lib/aipp/regions/LS/helpers/base.rb +111 -0
  41. data/lib/aipp/regions/LS/notam/ENR.rb +173 -0
  42. data/lib/aipp/runner.rb +152 -0
  43. data/lib/aipp/version.rb +1 -1
  44. data/lib/aipp.rb +30 -11
  45. data/lib/core_ext/array.rb +13 -0
  46. data/lib/core_ext/nokogiri.rb +56 -8
  47. data/lib/core_ext/string.rb +63 -1
  48. data.tar.gz.sig +0 -0
  49. metadata +115 -64
  50. metadata.gz.sig +0 -0
  51. data/lib/aipp/aip.rb +0 -166
  52. data/lib/aipp/regions/LF/aerodromes.rb +0 -223
  53. data/lib/aipp/regions/LF/d_p_r_airspaces.rb +0 -56
  54. data/lib/aipp/regions/LF/dangerous_activities.rb +0 -49
  55. data/lib/aipp/regions/LF/designated_points.rb +0 -47
  56. data/lib/aipp/regions/LF/helipads.rb +0 -122
  57. data/lib/aipp/regions/LF/navigational_aids.rb +0 -85
  58. data/lib/aipp/regions/LF/obstacles.rb +0 -153
  59. data/lib/aipp/regions/LF/serviced_airspaces.rb +0 -70
  60. data/lib/aipp/regions/LF/services.rb +0 -172
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09151e277b537c1529951cd69b38bb5d6fc47f855e6041ca3537d36462f93907'
4
- data.tar.gz: f0b9d43b940ced7998a079cf6dce602386d52ad90995b7e77a6b6429f09f798e
3
+ metadata.gz: aead3f21ec1e78d23e1c273e6d19f3db92834bb3dccd8ec77f85bf3c279a7a74
4
+ data.tar.gz: 8c1203247d9a8714fabb704fabbd972364f6c1544b2294b6ae004a59ebf94c74
5
5
  SHA512:
6
- metadata.gz: 205b609c8aad3999495447f7c55aba98282b5b6347a4f89caef3e06e3edc8f8e28c4e523cc48737522f895813309fe9d4f7b8d3d2d0db37a12d5808c41509137
7
- data.tar.gz: 9a228dd7a152dfd5e0bbbd22b8a068e8de404ba9a4d48f4a30f7b3f0b352c694f12b4a45b543880b20884645fbd52fa475dd738f7385541113462dcadce1fc29
6
+ metadata.gz: '09e058cee67ad5456af31c5cbc93462207303f89857e4bc709d9c09fb9f82212f24db2c59fe1e99a26b0153449f5b347aedd731114b2c549e246a23f270c558a'
7
+ data.tar.gz: 00c2fdf2386236e31f965317d1d743522adf70a10e80fbf04371ed5c350dcc22f490592ab06d05665a6d8acc55257b458a81cc5c151c031e6b4fc7b4bb04548f
checksums.yaml.gz.sig CHANGED
@@ -1,2 +1,2 @@
1
- `��˼��e����zt��pR3�^±�ܛ�Q�.ܾ�`�ާz|�/����rH��.c JS�@�,W��&v����C18�"�u��Mʡ��2| �&5tp[q�MD^�T'�C��:���2#]Zu}�oP�~ ����� �@��nE�NÞJ4~��kW
2
- �}�T�v����/;��E��P�_"��KR���lWz��^�d/�l9KY��-+'��wY�����1R�Z[_���bv���*V��(�;�
1
+ ��~@f���x�ih*���=�L���b0��'�Ђ2zUh���7H�4~�]ӧ^� �\�+�0����W-��`������ۡMn&��*^�Gm��h��킡.�LRi�&��*,�꛿
2
+ Oʼn�7������E��F���H��;[fp�=���3���%�bS��TP̥��)���,s����Q���d#�Ku�� ����[N���byN{�'TR�i��p��/+S�b�U͂���� d:V8��zw
data/CHANGELOG.md CHANGED
@@ -2,10 +2,26 @@
2
2
 
3
3
  Nothing so far
4
4
 
5
+ ## 2.0.0
6
+
7
+ #### Additions
8
+ * Region LS NOTAM
9
+ * CLI option to set a custom output file
10
+ * `--quiet` option
11
+
12
+ #### Breaking Changes
13
+ * Drop support for Ruby 3.0
14
+ * Rename `url_for` to `origin_for` and introduce origin structures which allow
15
+ for more complex download scenarios such as HTTPS with session or GraphQL.
16
+ * Overhaul file/class layout to accommodate other than AIP, implement NOTAM.
17
+ * Cache, borders, fixtures, options and config are now dedicated objects
18
+ accessible on `AIPP`.
19
+ * Patches are no longer passed the parser instance.
20
+
5
21
  ## 1.0.0
6
22
 
7
23
  #### Breaking Changes
8
- * Switch from individual AIP HTML section files to the comprehensive AIP XML
24
+ * Switch from individual AIP HTML files to the comprehensive AIP XML
9
25
  database dump for the LF region reference implementation.
10
26
  * Drop the mandatory `URL` helper in favour of a mandatory `url_for` method.
11
27
  * Renamed default git branch to `main`
data/README.md CHANGED
@@ -4,23 +4,22 @@
4
4
 
5
5
  # AIPP
6
6
 
7
- Parser for Aeronautical Information Publication (AIP) available online.
7
+ Parser for aeronautical information available online.
8
8
 
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).
9
+ This gem incluces executables to download and parse aeronautical information (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)
13
- * Author: [Sven Schwyn - Bitcetera](http://www.bitcetera.com)
13
+ * Author: [Sven Schwyn - Bitcetera](https://bitcetera.com)
14
14
 
15
15
  ## Table of Contents
16
16
 
17
- [Install](#install)<br>
18
- [Usage](#usage)<br>
19
- [Storage](#storage)<br>
20
- [Regions](#regions)<br>
21
- [AIRAC Date Calculations](#airac-date-calculations)<br>
22
- [References](#references)<br>
23
- [Development](#development)
17
+ [Install](#label-Install) <br>
18
+ [Usage](#label-Usage) <br>
19
+ [Regions](#label-Regions) <br>
20
+ [Storage](#label-Storage) <br>
21
+ [References](#label-References) <br>
22
+ [Development](#label-Development)
24
23
 
25
24
  ## Install
26
25
 
@@ -42,10 +41,10 @@ gem install aipp --trust-policy MediumSecurity
42
41
 
43
42
  ### Bundler
44
43
 
45
- If you're familiar with [Bundler](https://bundler.io) powered Ruby projects, you might prefer to add the following to your <tt>Gemfile</tt> or <tt>gems.rb</tt>:
44
+ If you're familiar with [Bundler](https://bundler.io) powered Ruby projects, you might prefer to add the following to your <samp>Gemfile</samp> or <samp>gems.rb</samp>:
46
45
 
47
46
  ```ruby
48
- gem aipp
47
+ gem 'aipp'
49
48
  ```
50
49
 
51
50
  And then install the bundle:
@@ -56,98 +55,117 @@ bundle install --trust-policy MediumSecurity
56
55
 
57
56
  ## Usage
58
57
 
59
- See the built-in help for all options:
58
+ AIPP parses different kind of information sources. The parsers are organized in three levels:
60
59
 
61
60
  ```
62
- aip2aixm --help
63
- aip2ofmx --help
61
+ region ⬅︎ aeronautical region such as "LF" (France)
62
+ └── module ⬅︎ subject area such as "AIP" or "NOTAM"
63
+ └── section ⬅︎ part of the subject area such as "ENR-2.1" or "aerodromes"
64
64
  ```
65
65
 
66
- Say, you with to build the complete OFMX file for the current AIRAC cycle of the region LF:
66
+ The following modules are currently available:
67
67
 
68
- ```
69
- aip2ofmx -r LF
70
- ```
68
+ Module | Content | Executables | Cache
69
+ -------|---------|-------------|------
70
+ [AIP](lib/aipp/aip/README.md) | aeronautical information publication | `aip2aixm` and `aip2ofmx` | by AIRAC cycle
71
+ [NOTAM](lib/aipp/notam/README.md) | notice to airmen | `notam2aixm` and `notam2ofmx` | by effective date and hour
71
72
 
72
- You'll find the OFMX file in the current directory if the binary exits successfully.
73
-
74
- ## Storage
75
-
76
- AIPP uses a storage directory for configuration, caching and in order to keep the results of previous runs. The default location is `~/.aipp`, however, you can pass a different directory with the `--storage` argument.
73
+ To list all available regions and sections for a given module:
77
74
 
78
- You'll find a directory for each region which contains the following items:
75
+ ```
76
+ aip2aixm --list
77
+ ```
79
78
 
80
- * `sources/`<br>This directory contains one ZIP archive per AIRAC cycle which incrementially caches all source files used to build the AIXM/OFMX file. Therefore, to make sure it contains all source files for a region, you have to build at least one complete AIXM/OFMX file for that region.
81
- * `builds/`<br>This directory contains one ZIP archive per AIRAC cycle which is overwritten on every run. Therefore, to make sure it contains the complete build for a region, you have to make sure that your last run builds the complete AIXM/OFMX for that region. This archive contains:
82
- * the built AIXM/OFMX file
83
- * `build.yaml` – context of the build process
84
- * `manifest.csv` – diffable manifest (see below)
85
- * `config.yml`<br>This file contains configuration which will be read on subsequent runs, most notably the namespace UUID used to identify the creator of OFMX files.
79
+ See the built-in help for all options:
86
80
 
87
- The manifest is a CSV which lists every feature on a separate line along with its hashes, AIP and comment. You can `diff` or `git diff` two manifests:
81
+ ```
82
+ notam2aixm --help
83
+ ```
88
84
 
89
- ```diff
90
- $ git diff -U0 2019-09-12/manifest.csv 2019-10-10/manifest.csv
85
+ Example: You wish to build the complete OFMX file for the current AIRAC cycle AIP of the region LF:
91
86
 
92
- --- a/2019-09-12/manifest.csv
93
- +++ b/2019-10-10/manifest.csv
94
- @@ -204 +204 @@ AD-1.3,Ahp,9e9f031e,d6f22057,Airport: LFLJ COURCHEVEL
95
- -AD-1.3,Ahp,9f1eed18,37ddbbde,Airport: LFQD ARRAS ROCLINCOURT
96
- +AD-1.3,Ahp,9f1eed18,f0e60105,Airport: LFQD ARRAS ROCLINCOURT
97
- @@ -312 +312 @@ AD-2,Aha,4250c9ee,04d49dc7,Address: RADIO for LFHV
98
- -AD-2,Aha,6b381b32,fb947716,Address: RADIO for LFPO
99
- +AD-2,Aha,6b381b32,b9723b7e,Address: RADIO for LFPO
100
- @@ -664 +663,0 @@ AD-2,Ser,3920a7fd,4545c5eb,Service: AFIS by LFGA TWR
101
- -AD-2,Ser,39215774,1f13f2cf,Service: APP by LFCR APP
102
- @@ -878 +876,0 @@ AD-2,Ser,bb5228d7,7cfb4572,Service: TWR by LFMH TWR
103
- -AD-2,Ser,bc72caf2,0a15b39c,Service: FIS by LFCR FIC
104
- (...)
87
+ ```
88
+ aip2ofmx -r LF
105
89
  ```
106
90
 
107
- The advantage of `git diff` is it's ability to hightlight exactly which part of a line has changed. [Check out this post to learn how](https://www.viget.com/articles/dress-up-your-git-diffs-with-word-level-highlights/).
91
+ You'll find the OFMX file in the current directory if the binary exits successfully.
108
92
 
109
93
  ## Regions
110
94
 
111
- The reference implementation is region "LF" (France).
95
+ To implement a region, you have to create a directory <samp>lib/aipp/regions/{REGION}/</samp> off the gem root and then subdirectories for each module as well as for support files. Here's a simplified overview for the region "LF" (France):
112
96
 
113
- To implement a region, you have to create a new directory <tt>lib/aipp/regions/{REGION}/</tt> and place the following files there:
97
+ ```
98
+ LF/ ⬅︎ region "LF"
99
+ ├── README.md
100
+ ├── aip ⬅︎ module "AIP"
101
+ │   ├── AD-2.rb ⬅︎ section "AD-2"
102
+ │   └── ENR-4.3.rb ⬅︎ section "ENR-4.3"
103
+ ├── notam ⬅︎ module "NOTAM"
104
+ │   ├── AD.rb ⬅︎ section "AD"
105
+ │   └── ENR.rb ⬅︎ section "ENR"
106
+ ├── borders
107
+ │   ├── france_atlantic_coast.geojson
108
+ │   └── france_atlantic_territorial_sea.geojson
109
+ ├── fixtures
110
+ │   └── aerodromes.yml
111
+ └── helpers
112
+    ├── base.rb
113
+    └── surface.rb
114
+ ```
114
115
 
115
- ### AIP Parsers
116
+ <table>
117
+ <tr>
118
+ <td>⚠️</td>
119
+ <td>All paths from here on forward are relative to the region directory.</td>
120
+ </tr>
121
+ </table>
116
122
 
117
- Say, you want to parse ENR-4.3, you have to create the file <tt>ENR-4.3.rb</tt> which defines the class `AIPP::LF::ENR43` as follows:
123
+ ### Parsers
124
+
125
+ Say, you want to parse AIP ENR-4.1. You have to create the file <samp>aip/ENR-4.1.rb</samp> which defines the class `ENR41` as follows:
118
126
 
119
127
  ```ruby
120
- module AIPP
121
- module LF
122
- class ENR43 < AIP
128
+ module AIPP::LF::AIP
129
+ class ENR41 < AIPP::AIP::Parser
130
+ depends_on :ENR21, :ENR22 # declare dependencies to other parsers
131
+ (...)
132
+ end
133
+ end
134
+ ```
123
135
 
124
- DEPENDS = %w(ENR-2.1 ENR-2.2) # declare dependencies to other AIPs
136
+ Another parser might target en-route NOTAM and therefore has to go to <samp>notam/ENR.rb</samp> like so:
125
137
 
126
- end
138
+ ```ruby
139
+ module AIPP::LF::NOTAM
140
+ class ENR < AIPP::NOTAM::Parser
141
+ (...)
127
142
  end
128
143
  end
129
144
  ```
130
145
 
131
- The class has to implement some methods either in the class itself or in a [helper](#Helpers) included by the class.
146
+ <table>
147
+ <tr>
148
+ <td>⚠️</td>
149
+ <td>Parser files and classes may follow AIP naming conventions such as <samp>ENR-4.1.rb</samp>. However, you're free to use arbitrary naming for parser files like <samp>navaids.rb</samp> (e.g. if you're working with one big data source which contains the full AIP dataset you'd like to split into smaller parts).</td>
150
+ </tr>
151
+ </table>
132
152
 
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.
153
+ The class has to implement some methods either in the class itself or in a [helper](#Helpers) included by the class.
134
154
 
135
155
  #### Mandatory `parse` Method
136
156
 
137
157
  The class must implement the `parse` method which contains the code to read, parse and write the data:
138
158
 
139
159
  ```ruby
140
- module AIPP
141
- module LF
142
- class ENR43 < AIP
143
-
144
- def parse
145
- html = read # read the Nokogiri::HTML5 document
146
- feature = (...) # build the feature
147
- add(feature: feature) # add the feature to AIXM::Document
148
- end
160
+ module AIPP::LF::AIP
161
+ class ENR41 < AIPP::AIP::Parser
149
162
 
163
+ def parse
164
+ html = read # read the Nokogiri::HTML5 document
165
+ feature = (...) # build the feature
166
+ add(feature: feature) # add the feature to AIXM::Document
150
167
  end
168
+
151
169
  end
152
170
  end
153
171
  ```
@@ -155,95 +173,171 @@ end
155
173
  Some AIP may be split over several files which require a little more code to load the individual HTML source files:
156
174
 
157
175
  ```ruby
158
- module AIPP
159
- module LF
160
- class AD2 < AIP
161
-
162
- def parse
163
- %i(one two three).each do |part|
164
- html = read("#{aip}.#{part}") # read with a non-standard name
165
- support_html = read('AD-0.6') # maybe read necessary support documents
166
- (...)
167
- end
176
+ module AIPP::LF::AIP
177
+ class AD2 < AIPP::AIP::Parser
178
+
179
+ def parse
180
+ %i(one two three).each do |part|
181
+ html = read("#{aip}.#{part}") # read with a non-standard name
182
+ support_html = read('AD-0.6') # maybe read necessary support documents
183
+ (...)
168
184
  end
169
-
170
185
  end
186
+
171
187
  end
172
188
  end
173
189
  ```
174
190
 
175
- Inside the `parse` method, you have access to the following methods:
191
+ The parser has access to the following methods:
176
192
 
177
193
  Method | Description
178
194
  -------|------------
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
+ [`section`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#section-instance_method) | current section (e.g. `ENR-2.1` or `aerodromes`)
196
+ [`read`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#read-instance_method) | download, cache and read a document from source
197
+ [`add`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#add-instance_method) | add a [`AIXM::Feature`](https://www.rubydoc.info/gems/aixm/AIXM/Feature)
198
+ [`find`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#find-instance_method) | find previously written [`AIXM::Feature`](https://www.rubydoc.info/gems/aixm/AIXM/Feature)s by object
199
+ [`find_by`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#find_by-instance_method) | find previously written [`AIXM::Feature`](https://www.rubydoc.info/gems/aixm/AIXM/Feature)s by class and attribute values
200
+ [`unique`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#unique-instance_method) | prevent duplicate [`AIXM::Feature`](https://www.rubydoc.info/gems/aixm/AIXM/Feature)s
201
+ [`given`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#given-instance_method) | inline condition for assignments
202
+ [`link_to`](https://www.rubydoc.info/gems/aipp/AIPP/Parser#link_to-instance_method) | optionally checked Markdown link
195
203
 
204
+ Equally available is the current runtime environment. All of the following objects behave like `OpenStruct`:
205
+
206
+ Method | Description
207
+ -------|------------
208
+ [`AIPP.cache`](https://www.rubydoc.info/gems/aipp/AIPP/Enrivonment/Cache) | cache to make transient objects available across AIPs
209
+ [`AIPP.borders`](https://www.rubydoc.info/gems/aipp/AIPP/Enrivonment/Borders) | [borders](#Borders) of the current region
210
+ [`AIPP.fixtures`](https://www.rubydoc.info/gems/aipp/AIPP/Enrivonment/Fixtures) | [fixtures](#Fixtures) of the current region
211
+ [`AIPP.options`](https://www.rubydoc.info/gems/aipp/AIPP/Environment/Options) | arguments read from <samp>aip2aixm</samp> or <samp>aip2ofmx</samp> respectively
212
+ [`AIPP.config`](https://www.rubydoc.info/gems/aipp/AIPP/Environment/Config) | configuration read from <samp>config.yml</samp>
213
+
214
+ To make the parser code more readable, a few core extensions are provided:
215
+
216
+ * [`Object#blank` (ActiveSupport)](https://www.rubydoc.info/gems/activesupport/Object#blank%3F-instance_method)
196
217
  * [`NilClass`](https://www.rubydoc.info/gems/aipp/NilClass)
197
218
  * [`Integer`](https://www.rubydoc.info/gems/aipp/Integer)
219
+ * [`String` (ActiveSupport)](https://www.rubydoc.info/gems/activesupport/String)
198
220
  * [`String`](https://www.rubydoc.info/gems/aipp/String)
221
+ * [`Array`](https://www.rubydoc.info/gems/aipp/Array)
199
222
  * [`Hash`](https://www.rubydoc.info/gems/aipp/Hash)
200
223
  * [`Enumerable`](https://www.rubydoc.info/gems/aipp/Enumerable)
224
+ * [`DateTime` (ActiveSupport)](https://www.rubydoc.info/gems/activesupport/DateTime)
201
225
  * [`Nokogiri`](https://www.rubydoc.info/gems/aipp/Nokogiri)
202
226
 
203
- #### Mandatory `url_for` Method
227
+ #### Mandatory `origin_for` Method
204
228
 
205
- The class must implement the `url_for` method which returns the URL from where to download the AIP file:
229
+ The class must implement the `origin_for` method which returns an origin object describing how to download the source data (e.g. an AIP file or NOTAM message):
206
230
 
207
231
  ```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
232
+ module AIPP::LF::AIP
233
+ class AD2 < AIPP::AIP::Parser
215
234
 
235
+ def origin_for(document)
236
+ # build and return the origin object
216
237
  end
238
+
217
239
  end
218
240
  end
219
241
  ```
220
242
 
221
- There are a few things to note about `url_for`:
243
+ Return any of the following origin objects best explained by example:
222
244
 
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.
245
+ ```
246
+ AIPP::Downloader::File.new(
247
+ file: "file.dat", # relative path to file
248
+ type: :pdf # optional: file type if different from extension
249
+ )
250
+ ```
226
251
 
227
- #### Optional `setup` Method
252
+ ```
253
+ AIPP::Downloader::File.new(
254
+ archive: "foobar.zip", # relative path to archive
255
+ file: "subdir/file.dat", # file to extract from archive
256
+ type: :pdf # optional: file type if different from extension
257
+ )
258
+ ```
228
259
 
229
- The class may implement the `setup` method. If present, it will be called when this parser is instantiated:
260
+ See [Downloader](https://www.rubydoc.info/gems/aipp/AIPP/Downloader) for more on recognised file and archive types.
230
261
 
262
+ ```
263
+ AIPP::Downloader::HTTP.new(
264
+ file: "https://example.com/foobar.zip", # URL where the file is located
265
+ type: :pdf, # optional: file type if different from extension
266
+ headers: "Cookie: name=value", # optional: additional headers e.g. for session
267
+ )
268
+ ```
269
+
270
+ ```
271
+ AIPP::Downloader::HTTP.new(
272
+ archive: "https://example.com/foobar.zip", # URL where the archive is located
273
+ file: "subdir/file.dat", # file to extract from archive
274
+ type: :pdf, # optional: file type if different from extension
275
+ headers: "Cookie: name=value", # optional: additional headers e.g. for session
276
+ )
277
+ ```
278
+
279
+ The [excon gem](https://www.rubydoc.info/gems/excon) is used to perform HTTP requests.
280
+
281
+ ```
282
+ AIPP::Downloader::GraphQL.new(
283
+ client: MyAPI::Client, # GraphQL client class
284
+ query: MyAPI::Name::Query, # GraphQL query class
285
+ variables: { # dynamic query parameters
286
+ first_name: 'Geronimo',
287
+ age: 50
288
+ }
289
+ )
290
+ ```
291
+
292
+ For this GraphQL downloader to work, you have to declare a GraphQL client class beforehand. See the [graphql-client gem documentation](https://www.rubydoc.info/gems/graphql-client) for details, the following example fits the downloader above:
231
293
 
232
294
  ```ruby
233
- module AIPP
234
- module LF
235
- class AD2 < AIP
295
+ module MyAPI
296
+ HttpAdapter = GraphQL::Client::HTTP.new(ENV['MY_API_URL']) do
297
+ def headers(context)
298
+ { "Authorization": "Bearer #{ENV['MY_API_AUTHORIZATION']}" }
299
+ end
300
+ end
301
+ Schema = GraphQL::Client.load_schema(HttpAdapter)
302
+ Client = GraphQL::Client.new(schema: Schema, execute: HttpAdapter)
303
+
304
+ class Name
305
+ Query = Client.parse <<~END
306
+ query ($first_name: String!, $age: Int!) {
307
+ queryNOTAMs(
308
+ filter: {first_name: $first_name, age: $age}
309
+ ) {
310
+ name
311
+ }
312
+ }
313
+ END
314
+ end
315
+ end
316
+ ```
236
317
 
237
- def setup
238
- AIXM.config.voice_channel_separation = :any
239
- cache.setup_at ||= Time.now
240
- end
318
+ For performance, all downloads are cached and subsequent runs will use the cached data rather than fetching the sources anew. Each module defines a cache time window, see the [table of modules above](#label-Usage). You can discard existing and rebuild caches by use of the `--clean` command line argument.
319
+
320
+ #### Optional `setup` Method
321
+
322
+ The class may implement the `setup` method. If present, it will be called when this parser is instantiated:
241
323
 
324
+ ```ruby
325
+ module AIPP::LF::AIP
326
+ class AD2 < AIPP::AIP::Parser
327
+
328
+ def setup
329
+ AIXM.config.voice_channel_separation = :any
330
+ AIPP.cache.setup_at ||= Time.now
242
331
  end
332
+
243
333
  end
244
334
  end
245
335
  ```
246
336
 
337
+ ### Helpers
338
+
339
+ Helpers are mixins defined in the <samp>helpers/</samp> subdirectory. All helpers are required automatically in alphabetic order.
340
+
247
341
  ### Borders
248
342
 
249
343
  AIXM knows named borders for country boundaries. However, you might need additional borders which don't exist as named borders.
@@ -252,7 +346,7 @@ You can define additional borders as [`AIPP::Border`](https://www.rubydoc.info/g
252
346
 
253
347
  #### From GeoJSON
254
348
 
255
- Create simple GeoJSON files in the <tt>lib/aipp/regions/{REGION}/borders/</tt> directory, for example this `my_border_1.geojson`:
349
+ Create simple GeoJSON files in the <samp>borders/</samp> subdirectory, for example this `my_border_1.geojson`:
256
350
 
257
351
  ```json
258
352
  {
@@ -276,7 +370,12 @@ Create simple GeoJSON files in the <tt>lib/aipp/regions/{REGION}/borders/</tt> d
276
370
  }
277
371
  ```
278
372
 
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.
373
+ <table>
374
+ <tr>
375
+ <td>⚠️</td>
376
+ <td>The GeoJSON file must consist of exactly one `GeometryCollection` which may contain any number of `LineString` geometries. Only `LineString` geometries are recognised! To define a closed polygon, the first coordinates of a `LineString` must be identical to the last coordinates.</td
377
+ </tr>
378
+ </table>
280
379
 
281
380
  #### From Coordinates
282
381
 
@@ -309,28 +408,26 @@ The border object implements simple nearest point and segment calculations to cr
309
408
 
310
409
  See [`AIPP::Border`](https://www.rubydoc.info/gems/aipp/AIPP/Border) for more on this.
311
410
 
312
- ### Helpers
411
+ ### Fixtures
313
412
 
314
- Helpers are modules defined in the <tt>lib/aipp/regions/{REGION}/helpers/</tt> directory. All helper modules are required automatically in alphabetic order.
413
+ Fixtures are static YAML data files in the <samp>fixtures/</samp> subdirectory. All fixtures are read automatically, e.g. the contents of the <samp>lib/aipp/regions/{REGION}/fixtures/aerodromes.yml</samp> will be available from `AIPP.fixtures.aerodromes`.
315
414
 
316
- ### Fixtures and Patches
415
+ Read on for how to best use fixtures.
317
416
 
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`).
417
+ ### Patches
319
418
 
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`:
419
+ When parsed data is faulty or missing, you may fall back to fixtures 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`:
321
420
 
322
421
  ```ruby
323
- module AIPP
324
- module LF
325
- class AD2 < AIP
326
-
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)
331
- end
422
+ module AIPP::LF::AIP
423
+ class AD2 < AIP
332
424
 
425
+ patch AIXM::Feature::Airport, :z do |object, value|
426
+ throw(:abort) unless value.nil?
427
+ throw(:abort, 'fixture missing') unless z = AIPP.fixtures.aerodromes.dig(object.id, 'z')
428
+ AIXM.z(z, :qnh)
333
429
  end
430
+
334
431
  end
335
432
  end
336
433
  ```
@@ -341,12 +438,6 @@ The patch receives the object and the value which is about to be assigned. It sh
341
438
  * 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
439
  * At last, build and return the value object which will be assigned instead of the original value.
343
440
 
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
- ```
349
-
350
441
  ### Source File Line Numbers
351
442
 
352
443
  In order to reference the source of an AIXM/OFMX feature, it's necessary to know the line number where a particular node occurs in the HTML source file. You can ask any HTML element as follows:
@@ -369,7 +460,12 @@ You should `warn` on non-fatal problems which should be fixed (default) or might
369
460
 
370
461
  ```ruby
371
462
  warn("my message")
372
- warn("my message", severe: false) # show warning only when --unsevere-warn argument is set
463
+ ```
464
+
465
+ Less important warnings are only shown if the `--verbose` mode is set:
466
+
467
+ ```ruby
468
+ warn("my message", severe: false)
373
469
  ```
374
470
 
375
471
  ### Messages
@@ -399,18 +495,41 @@ The [default Ruby debugger](https://github.com/ruby/debug#debug-command-on-the-d
399
495
  debugger
400
496
  ```
401
497
 
402
- ## AIRAC Date Calculations
498
+ ## Storage
499
+
500
+ AIPP uses a storage directory for configuration, caching and in order to keep the results of previous runs. The default location is `~/.aipp`, however, you can pass a different directory with the `--storage` argument.
403
501
 
404
- The `AIPP::AIRAC` class is used to calculate AIRAC cycles:
502
+ You'll find a directory for each region and module which contains the following items:
405
503
 
406
- ```ruby
407
- airac = AIPP::AIRAC.new(Date.parse('2017-12-24'))
408
- airac.date # => 2018-12-07
409
- airac.id # => 1713
410
- airac.next_date # => 2018-01-04
411
- airac.next_id # => 1801
504
+ * `sources/`<br>ZIP archives which cache all source files used to build.
505
+ * `builds/`<br>ZIP archives of successful builds containing:
506
+ * the built AIXM/OFMX file
507
+ * `build.yaml` context of the build process
508
+ * `manifest.csv` diffable manifest (see below)
509
+ * `config.yml`<br>This file contains configuration which will be read on subsequent runs, most notably the namespace UUID used to identify the creator of OFMX files.
510
+
511
+ The manifest is a CSV which lists every feature on a separate line along with its hashes, AIP and comment. You can `diff` or `git diff` two manifests:
512
+
513
+ ```diff
514
+ $ git diff -U0 2019-09-12/manifest.csv 2019-10-10/manifest.csv
515
+
516
+ --- a/2019-09-12/manifest.csv
517
+ +++ b/2019-10-10/manifest.csv
518
+ @@ -204 +204 @@ AD-1.3,Ahp,9e9f031e,d6f22057,Airport: LFLJ COURCHEVEL
519
+ -AD-1.3,Ahp,9f1eed18,37ddbbde,Airport: LFQD ARRAS ROCLINCOURT
520
+ +AD-1.3,Ahp,9f1eed18,f0e60105,Airport: LFQD ARRAS ROCLINCOURT
521
+ @@ -312 +312 @@ AD-2,Aha,4250c9ee,04d49dc7,Address: RADIO for LFHV
522
+ -AD-2,Aha,6b381b32,fb947716,Address: RADIO for LFPO
523
+ +AD-2,Aha,6b381b32,b9723b7e,Address: RADIO for LFPO
524
+ @@ -664 +663,0 @@ AD-2,Ser,3920a7fd,4545c5eb,Service: AFIS by LFGA TWR
525
+ -AD-2,Ser,39215774,1f13f2cf,Service: APP by LFCR APP
526
+ @@ -878 +876,0 @@ AD-2,Ser,bb5228d7,7cfb4572,Service: TWR by LFMH TWR
527
+ -AD-2,Ser,bc72caf2,0a15b39c,Service: FIS by LFCR FIC
528
+ (...)
412
529
  ```
413
530
 
531
+ The advantage of `git diff` is its ability to highlight exactly which part of a line has changed. [Check out this post to learn how](https://www.viget.com/articles/dress-up-your-git-diffs-with-word-level-highlights/).
532
+
414
533
  ## References
415
534
 
416
535
  * [Geo Maps – programmatically generated GeoJSON maps](https://github.com/simonepri/geo-maps)
@@ -431,7 +550,7 @@ Please submit issues on:
431
550
 
432
551
  https://github.com/svoop/aipp/issues
433
552
 
434
- To contribute code, fork the project on Github, add your code and submit a pull request:
553
+ To contribute code, fork the project on GitHub, add your code and submit a pull request:
435
554
 
436
555
  https://help.github.com/articles/fork-a-repo
437
556