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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ce24174fda71fe6649f2d1f02eab03a2d9de0a59da6dbb581d581fe2dc2df4c
4
- data.tar.gz: 7643f1ec9c399e244ddbc13ef9a3a5df31eaf63e78c197a55c55838a0536d8ad
3
+ metadata.gz: 279a7722a7ee0070603236b477cdd96bb1babfea88e8e70cce7088069683e783
4
+ data.tar.gz: cb9453b4aca266eafc8aa7ae8d949d402c42f748d2193796b160fc17c9280068
5
5
  SHA512:
6
- metadata.gz: d4029172c77e70998f89c10eb19c6bb827d042fd7a9c9190538bb422e319c0387c85534de28e8d0357b0c676f99e7a222479419bbc34d00cc34ca3c918636987
7
- data.tar.gz: 3a294be41d8eb5067dc286b60ed304db554607694e094b7fa92a114ce9bac663261ba4246d45d4da133b753689535f2a708d09dd47dc06204dd5f86c5dd982f6
6
+ metadata.gz: 4102f1a2c77ea39f2b3200c61d49c7dd194768944688f42d4cb0740707d431b2dc290e35a81215de0c646284a59b8ed9fee4dff15a6cb65616e70e3cf979868a
7
+ data.tar.gz: c8087248c414c41e415c1361370f5ec84116e6a11d6aa23336eceb353f9af13f72de7b8459b7d4504a44ff4bb9206c042734e18da15a02010bd8f8074ff830d5
data/.gitignore CHANGED
@@ -3,4 +3,6 @@ Gemfile.lock
3
3
  pkg/*
4
4
  *.gem
5
5
  .bundle
6
+ .yardoc
6
7
  *.aixm
8
+ *.ofmx
@@ -1 +1 @@
1
- ruby-2.5.0
1
+ ruby-2.5.3
@@ -0,0 +1,3 @@
1
+ --no-private
2
+ lib/**/*.rb -
3
+ README.md CHANGELOG.md LICENSE.txt
@@ -1,22 +1,37 @@
1
+ ## 0.2.0
2
+
3
+ #### Changes
4
+ * Complete rewrite of the framework in order to allow cross-AIP parsing made necessary due to recent changes in LF AIP.
5
+
6
+ #### Additions
7
+ * LF/ENR-2.1
8
+ * Handling of errors and warnings optimized for parser development
9
+
10
+ #### Removals
11
+ * LF/AD-1.5
12
+
1
13
  ## 0.1.3
2
14
 
3
- * Additons:
4
- LF/AD-1.5
5
- * Minor changes:
6
- * Summary at end of run
15
+ #### Changes
16
+ * Summary at end of run
17
+
18
+ #### Additions
19
+ * LF/AD-1.5
20
+ * Source file line number evaluation
7
21
 
8
22
  ## 0.1.2
9
23
 
10
- * Additions:
11
- * LF/ENR-4.3
24
+ #### Additions:
25
+ * LF/ENR-4.3
12
26
 
13
27
  ## 0.1.1
14
28
 
15
- * Additions:
16
- * Helper modules
17
- * LF/ENR-4.1
29
+ #### Additions:
30
+ * LF/ENR-4.1
31
+ * Helper modules
18
32
 
19
33
  ## 0.1.0
20
34
 
21
- * Initial implementation of aip2aixm executable and infrastructure
35
+ #### Initial Implementation
36
+ * Framework and aip2aixm executable
22
37
  * LF/ENR-5.1
data/README.md CHANGED
@@ -8,9 +8,7 @@
8
8
 
9
9
  Parser for Aeronautical Information Publication (AIP) available online.
10
10
 
11
- This gem incluces an executable to download and parse aeronautical data, then
12
- export is as [AIXM](https://github.com/svoop/aixm) which can be consumed by
13
- [Open Flightmaps](https://openflightmaps.org).
11
+ This gem incluces two executables to download and parse aeronautical data, then export is as [AIXM](https://github.com/svoop/aixm) or [OFMX](https://github.com/openflightmaps/ofmx/wiki).
14
12
 
15
13
  * [Homepage](https://github.com/svoop/aipp)
16
14
  * Author: [Sven Schwyn - Bitcetera](http://www.bitcetera.com)
@@ -27,38 +25,126 @@ gem aipp
27
25
 
28
26
  ```
29
27
  aip2aixm --help
28
+ aip2ofmx --help
30
29
  ```
31
30
 
32
- ## Parsers
31
+ ## Storage
33
32
 
34
- Parsers are defined as modules and named <tt>lib/aipp/parser/{FIR}/{AIP}.rb</tt>. For
35
- them to plug in, you have to define the following public methods:
33
+ 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.
36
34
 
37
- * `url`<br>Must return the download URL of the AIP HTML as a string.
38
- * `convert!`<br>Takes `html` ([Nokogiri document](https://github.com/sparklemotion/nokogiri)) to parse and populate `aixm` ([AIXM document](https://github.com/svoop/aixm))
35
+ ## Regions
39
36
 
40
- You should read and honor the following attributes passed in from `aip2aixm`
41
- arguments:
37
+ The reference implementation is region "LF" (France).
42
38
 
43
- * `@fir`
44
- * `@aip`
45
- * `@airac`
46
- * `@limit`
39
+ To implement a region, you have to create a new directory <tt>lib/aipp/regions/{REGION}</tt> and place the following files there:
47
40
 
48
- You should `fail` on fatal and `warn` on non-fatal problems. If `$DEBUG` is
49
- +true+ (e.g. by use of the `-D` option), a Pry session will open if you use
50
- `warn` as follows:
41
+ ### helper.rb
42
+
43
+ Create the file <tt>helper.rb</tt> which defines the module `AIPP::LF::Helper` and usually contains the URL builder method `url_for` used by all AIP parsers:
44
+
45
+ ```ruby
46
+ module AIPP
47
+ module LF
48
+ module Helper
49
+
50
+ def url_for(aip_file)
51
+ # build and return the download URL for the aip file
52
+ end
53
+
54
+ end
55
+ end
56
+ end
57
+ ```
58
+
59
+ ### AIP Parsers
60
+
61
+ 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:
62
+
63
+ ```ruby
64
+ module AIPP
65
+ module LF
66
+ class ENR43 < AIP
67
+
68
+ DEPENDS = %w(ENR-2.1 ENR-2.2) # declare dependencies to other AIPs
69
+
70
+ def parse
71
+ html = load_html
72
+ # read from "html" (Nokogiri::HTML::Document) and write to "aixm"
73
+ end
74
+
75
+ end
76
+ end
77
+ end
78
+ ```
79
+
80
+ Some AIP may be split over several files which require a little more code to load the individual HTML source files:
81
+
82
+ ```ruby
83
+ module AIPP
84
+ module LF
85
+ class AD2 < AIP
86
+
87
+ def parse
88
+ %i(one two three).each do |part|
89
+ html = load_html(aip_file: "#{aip}.#{part}")
90
+ # read from "html" (Nokogiri::HTML::Document) and write to "aixm"
91
+ end
92
+ end
93
+
94
+ end
95
+ end
96
+ end
97
+ ```
98
+
99
+ Inside the `parse` method, you have access to the following objects:
100
+
101
+ * `aixm` – target: instance of `AIXM::Document` (see [AIXM Rubygem](https://github.com/svoop/aixm))
102
+ * `options` – arguments read from <tt>aip2aixm</tt> or <tt>aip2ofmx</tt> respectively
103
+ * `config` – configuration read from <tt>config.yml</tt>
104
+ * `cache` – virgin `OStruct` instance to make objects available across AIPs
105
+
106
+ Furthermore, you have access to any method defined in <tt>helper.rb</tt> and you can overwrite any of them if need be (most notably `url_for`).
107
+
108
+ ### Source File Line Numbers
109
+
110
+ 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:
111
+
112
+ ```ruby
113
+ tr.line
114
+ ```
115
+
116
+ :warning: 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.
117
+
118
+ ### Errors
119
+
120
+ 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:
51
121
 
52
122
  ```ruby
53
- warn("my message", binding)
123
+ fail "my message"
54
124
  ```
55
125
 
56
- ## Helpers
126
+ ### Warnings
127
+
128
+ 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:
129
+
130
+ ```ruby
131
+ warn("my message", context: binding) # open Pry with binding context
132
+ warn("my message", context: error) # open Pry with error context
133
+ ```
134
+
135
+ ### Informational Messages
136
+
137
+ You may `info` any other useful information:
138
+
139
+ ```ruby
140
+ info("my message") # show info only in verbose mode (-V)
141
+ info("my message", force: true) # always show info
142
+ info("my message", color: :green) # show info with this color
143
+ ```
57
144
 
58
- Any modules in <tt>lib/aipp/parser/{FIR}/helpers</tt> are required automatically and
59
- can be included and used in the parsers.
145
+ ## AIRAC Date Calculations
60
146
 
61
- ## AIRAC date calculations
147
+ The `AIPP::AIRAC` class is used to calculate AIRAC cycles:
62
148
 
63
149
  ```ruby
64
150
  airac = AIPP::AIRAC.new(Date.parse('2017-12-24'))
@@ -70,9 +156,13 @@ airac.next_id # => 1801
70
156
 
71
157
  ## References
72
158
 
73
- * AIP authorities
74
- * [LF: SIA](https://www.sia.aviation-civile.gouv.fr)
75
- * [Open Flightmaps](https://openflightmaps.org)
159
+ * LF - France
160
+ * [SIA – AIP publisher](https://www.sia.aviation-civile.gouv.fr)
161
+ * [OpenData – public data files](https://www.data.gouv.fr)
162
+ * [Protected Planet – protected area data files](https://www.protectedplanet.net)
163
+ * [Geo Maps – programmatically generated GeoJSON maps](https://github.com/simonepri/geo-maps)
164
+ * [Open Flightmaps – open-source aeronautical maps](https://openflightmaps.org)
165
+ * [AIXM Rubygem – AIXM/OFMX generator for Ruby](https://github.com/svoop/aixm)
76
166
 
77
167
  ## Development
78
168
 
@@ -88,8 +178,7 @@ Please submit issues on:
88
178
 
89
179
  https://github.com/svoop/aipp/issues
90
180
 
91
- To contribute code, fork the project on Github, add your code and submit a
92
- pull request:
181
+ To contribute code, fork the project on Github, add your code and submit a pull request:
93
182
 
94
183
  https://help.github.com/articles/fork-a-repo
95
184
 
@@ -30,8 +30,10 @@ Gem::Specification.new do |spec|
30
30
  spec.add_development_dependency 'guard'
31
31
  spec.add_development_dependency 'guard-minitest'
32
32
 
33
- spec.add_runtime_dependency 'aixm', '~> 0', '>= 0.2.3'
33
+ spec.add_runtime_dependency 'aixm', '~> 0', '>= 0.3.2'
34
34
  spec.add_runtime_dependency 'nokogiri', '~> 1'
35
- spec.add_runtime_dependency 'nokogumbo', '~> 1'
35
+ spec.add_runtime_dependency 'nokogumbo', '~> 2'
36
+ spec.add_runtime_dependency 'colorize', '~> 0'
36
37
  spec.add_runtime_dependency 'pry', '~> 0'
38
+ spec.add_runtime_dependency 'pry-rescue', '~> 1'
37
39
  end
@@ -1,8 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'bundler/inline'
4
- require 'optparse'
5
- require 'yaml'
6
4
 
7
5
  gemfile do
8
6
  source 'https://rubygems.org'
@@ -10,51 +8,4 @@ gemfile do
10
8
  gem 'aipp', '~> 0'
11
9
  end
12
10
 
13
- class Executable
14
- attr_reader :fir, :aip, :airac, :ofm, :limit, :verbose
15
-
16
- def initialize
17
- @ogn = false
18
- @airac = AIPP::AIRAC.new(Date.today).date
19
- @limit = Float::INFINITY
20
- @verbose = false
21
- OptionParser.new do |o|
22
- o.banner = <<~END
23
- Download online AIP and convert it to AIXM for VFR.
24
- Usage: #{File.basename($0)} [options]
25
- END
26
- o.on('-A', '--about', 'author and license information') { puts 'Written by Sven Schwyn (bitcetera.com) and distributed under MIT license.'; exit }
27
- o.on('-l', '--list', "list available FIR/AIP and exit") { puts AIPP::Loader.list.to_yaml; exit }
28
- o.on('-f', '--fir STRING', String, 'FIR (flight information region, e.g. "LF")') { |v| @fir = v }
29
- o.on('-a', '--aip STRING', String, 'AIP (aeronautical information publication, e.g. "ENR-5.1")') { |v| @aip = v }
30
- o.on('-d', '--airac DATE', String, 'AIRAC date (e.g. "2018-01-04", default: current)') { |v| @airac = v }
31
- o.on('-o', '--[no-]ofm', 'Use OFM extensions (default: false)') { |v| @ofm = v }
32
- o.on('-L', '--limit INTEGER', Integer, 'Stop conversion after this many features') { |v| @limit = v }
33
- o.on('-D', '--[no-]debug', 'Enable debug mode (default: false)') { |v| $DEBUG = v }
34
- o.on('-v', '--[no-]verbose', 'Verbose error reporting (default: false)') { |v| @verbose = v }
35
- end.parse!
36
- fail(ArgumentError, "FIR must be supplied") unless @fir
37
- fail(ArgumentError, "AIP must be supplied") unless @aip
38
- fail(ArgumentError, "AIRAC date must be supplied") unless @airac
39
- end
40
-
41
- def run
42
- extensions = [(:ofm if ofm)].compact
43
- filename = [fir, aip, airac].join('_') + '.aixm'
44
- loader = AIPP::Loader.new(fir: fir, aip: aip, airac: airac, limit: limit)
45
- File.write(Pathname.new(Dir.pwd).join(filename), loader.aixm.to_aixm(*extensions))
46
- puts "#{loader.aixm.features.count} feature(s) written to #{filename}"
47
- end
48
- end
49
-
50
- begin
51
- executable = Executable.new
52
- executable.run
53
- rescue => exception
54
- if executable&.verbose
55
- raise exception
56
- else
57
- puts "#{File.basename($0)}: #{exception.message}"
58
- exit 1
59
- end
60
- end
11
+ AIPP::Executable.new(schema: File.basename($0)[4..-1].to_sym).run
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/inline'
4
+
5
+ gemfile do
6
+ source 'https://rubygems.org'
7
+ ruby '>= 2.5'
8
+ gem 'aipp', '~> 0'
9
+ end
10
+
11
+ AIPP::Executable.new(schema: File.basename($0)[4..-1].to_sym).run
@@ -1,12 +1,29 @@
1
- require 'tmpdir'
1
+ require 'forwardable'
2
+ require 'colorize'
3
+ require 'pry'
4
+ require 'pry-rescue'
5
+ require 'optparse'
6
+ require 'yaml'
2
7
  require 'pathname'
3
8
  require 'open-uri'
9
+ require 'securerandom'
10
+ require 'tsort'
11
+ require 'ostruct'
4
12
  require 'date'
5
13
  require 'nokogiri'
6
14
  require 'nokogumbo'
7
15
  require 'aixm'
8
16
 
9
17
  require_relative 'aipp/version'
10
- require_relative 'aipp/airac'
11
18
  require_relative 'aipp/refinements'
12
- require_relative 'aipp/loader'
19
+ require_relative 'aipp/t_hash'
20
+ require_relative 'aipp/executable'
21
+ require_relative 'aipp/progress'
22
+ require_relative 'aipp/airac'
23
+ require_relative 'aipp/aip'
24
+ require_relative 'aipp/parser'
25
+
26
+ # Disable "did you mean?" suggestions
27
+ module DidYouMean::Correctable
28
+ remove_method :to_s
29
+ end
@@ -0,0 +1,63 @@
1
+ module AIPP
2
+
3
+ # @abstract
4
+ class AIP
5
+ using AIPP::Refinements
6
+ include AIPP::Progress
7
+ extend Forwardable
8
+
9
+ DEPENDS = []
10
+
11
+ # @return [String] AIP name (e.g. "ENR-2.1")
12
+ attr_reader :aip
13
+
14
+ # @return [AIXM::Document] target document
15
+ attr_reader :aixm
16
+
17
+ # @!method aixm
18
+ # @return (see AIPP::Parser#aixm)
19
+ # @!method config
20
+ # @return (see AIPP::Parser#config)
21
+ # @!method options
22
+ # @return (see AIPP::Parser#options)
23
+ # @!method cache
24
+ # @return (see AIPP::Parser#cache)
25
+ def_delegators :@parser, :aixm, :config, :options, :cache
26
+
27
+ def initialize(aip:, parser:)
28
+ @aip, @parser = aip, parser
29
+ self.class.include [:AIPP, options[:region], :Helper].constantize
30
+ end
31
+
32
+ # Load an AIP source file
33
+ #
34
+ # Depending on whether a local copy of the file exists, either:
35
+ # * download from URL to local storage and read from local storage
36
+ # * read from local storage
37
+ #
38
+ # An URL builder method +url_for(aip_file)+ must be defined either in
39
+ # +helper.rb+ or in the AIP parser definition (e.g. +ENR-2.1.rb+).
40
+ #
41
+ # @param aip_file [String] e.g. "ENR-2.1" or "AD-2.LFMV" (default: +aip+)
42
+ # @return [Nokogiri::HTML5] HTML document
43
+ def load_html(aip_file: nil)
44
+ aip_file ||= aip
45
+ unless (storage_path = storage_path(aip_file)).exist?
46
+ info("Downloading #{storage_file}", force: true)
47
+ storage_path.mkpath
48
+ IO.copy_stream(open(url_for(aip_file)), storage_path)
49
+ end
50
+ Nokogiri::HTML5(storage_path)
51
+ end
52
+
53
+ private
54
+
55
+ def storage_path(aip_file=nil)
56
+ options[:storage].
57
+ join(options[:airac].date.xmlschema).
58
+ join(("#{aip_file}.html" if aip_file).to_s)
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -1,43 +1,51 @@
1
1
  module AIPP
2
2
 
3
- ##
4
- # Calculate the AIRAC date and AIRAC ID for the given +any_date+
3
+ # AIRAC cycle date calculations
4
+ #
5
+ # @example
6
+ # airac = AIPP::AIRAC.new('2018-01-01')
7
+ # airac.date # => #<Date: 2017-12-07 ((2458095j,0s,0n),+0s,2299161j)>
8
+ # airac.id # => 1713
9
+ # airac.next_date # => #<Date: 2018-01-04 ((2458123j,0s,0n),+0s,2299161j)>
10
+ # airac.next_id # => 1801
5
11
  class AIRAC
6
- ##
7
12
  # First AIRAC date following the last cycle length modification
8
13
  ROOT_DATE = Date.parse('2015-06-25').freeze
9
14
 
10
- ##
11
15
  # Length of one AIRAC cycle
12
16
  DAYS_PER_CYCLE = 28
13
17
 
14
- attr_reader :date, :id
18
+ # @return [Date] AIRAC effective on date
19
+ attr_reader :date
15
20
 
21
+ # @return [Integer] AIRAC cycle ID
22
+ attr_reader :id
23
+
24
+ # @param any_date [Date] any date within the AIRAC cycle (default: today)
16
25
  def initialize(any_date = nil)
17
- any_date ||= Date.today
18
- fail(ArgumentError, "argument must be of class Date") unless any_date.is_a? Date
26
+ any_date = any_date ? Date.parse(any_date.to_s) : Date.today
19
27
  fail(ArgumentError, "cannot calculate dates before #{ROOT_DATE}") if any_date < ROOT_DATE
20
28
  @date = date_for(any_date)
21
29
  @id = id_for(@date)
22
30
  end
23
31
 
32
+ # @return [Date] next AIRAC effective on date
24
33
  def next_date
25
34
  date + DAYS_PER_CYCLE
26
35
  end
27
36
 
37
+ # @return [Integer] next AIRAC cycle ID
28
38
  def next_id
29
39
  id_for next_date
30
40
  end
31
41
 
32
42
  private
33
43
 
34
- ##
35
44
  # Find the AIRAC date for +any_date+
36
45
  def date_for(any_date)
37
46
  ROOT_DATE + (any_date - ROOT_DATE).to_i / DAYS_PER_CYCLE * DAYS_PER_CYCLE
38
47
  end
39
48
 
40
- ##
41
49
  # Find the AIRAC ID for the AIRAC +date+
42
50
  def id_for(date)
43
51
  (date.year % 100) * 100 + ((date.yday - 1) / 28) + 1