aipp 1.0.0 → 2.0.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
- checksums.yaml.gz.sig +2 -2
- data/CHANGELOG.md +17 -1
- data/README.md +269 -150
- data/exe/aip2aixm +2 -8
- data/exe/aip2ofmx +2 -8
- data/exe/notam2aixm +5 -0
- data/exe/notam2ofmx +5 -0
- data/lib/aipp/aip/README.md +10 -0
- data/lib/aipp/aip/executable.rb +40 -0
- data/lib/aipp/aip/parser.rb +9 -0
- data/lib/aipp/aip/runner.rb +85 -0
- data/lib/aipp/border.rb +2 -2
- data/lib/aipp/debugger.rb +14 -19
- data/lib/aipp/downloader/file.rb +57 -0
- data/lib/aipp/downloader/graphql.rb +29 -0
- data/lib/aipp/downloader/http.rb +48 -0
- data/lib/aipp/downloader.rb +78 -29
- data/lib/aipp/environment.rb +88 -0
- data/lib/aipp/executable.rb +36 -53
- data/lib/aipp/notam/README.md +25 -0
- data/lib/aipp/notam/executable.rb +27 -0
- data/lib/aipp/notam/parser.rb +9 -0
- data/lib/aipp/notam/runner.rb +28 -0
- data/lib/aipp/parser.rb +133 -160
- data/lib/aipp/patcher.rb +4 -5
- data/lib/aipp/regions/LF/README.md +6 -2
- data/lib/aipp/regions/LF/aip/aerodromes.rb +220 -0
- data/lib/aipp/regions/LF/aip/d_p_r_airspaces.rb +53 -0
- data/lib/aipp/regions/LF/aip/dangerous_activities.rb +48 -0
- data/lib/aipp/regions/LF/aip/designated_points.rb +44 -0
- data/lib/aipp/regions/LF/aip/helipads.rb +119 -0
- data/lib/aipp/regions/LF/aip/navigational_aids.rb +82 -0
- data/lib/aipp/regions/LF/aip/obstacles.rb +150 -0
- data/lib/aipp/regions/LF/aip/serviced_airspaces.rb +67 -0
- data/lib/aipp/regions/LF/aip/services.rb +169 -0
- data/lib/aipp/regions/LF/fixtures/aerodromes.yml +2 -2
- data/lib/aipp/regions/LF/helpers/base.rb +32 -32
- data/lib/aipp/regions/LS/README.md +59 -0
- data/lib/aipp/regions/LS/helpers/base.rb +111 -0
- data/lib/aipp/regions/LS/notam/ENR.rb +173 -0
- data/lib/aipp/runner.rb +152 -0
- data/lib/aipp/version.rb +1 -1
- data/lib/aipp.rb +30 -11
- data/lib/core_ext/array.rb +13 -0
- data/lib/core_ext/nokogiri.rb +56 -8
- data/lib/core_ext/string.rb +63 -1
- data.tar.gz.sig +0 -0
- metadata +115 -64
- metadata.gz.sig +0 -0
- data/lib/aipp/aip.rb +0 -166
- data/lib/aipp/regions/LF/aerodromes.rb +0 -223
- data/lib/aipp/regions/LF/d_p_r_airspaces.rb +0 -56
- data/lib/aipp/regions/LF/dangerous_activities.rb +0 -49
- data/lib/aipp/regions/LF/designated_points.rb +0 -47
- data/lib/aipp/regions/LF/helipads.rb +0 -122
- data/lib/aipp/regions/LF/navigational_aids.rb +0 -85
- data/lib/aipp/regions/LF/obstacles.rb +0 -153
- data/lib/aipp/regions/LF/serviced_airspaces.rb +0 -70
- data/lib/aipp/regions/LF/services.rb +0 -172
data/lib/aipp/executable.rb
CHANGED
@@ -1,60 +1,28 @@
|
|
1
1
|
module AIPP
|
2
2
|
|
3
|
-
#
|
3
|
+
# @abstract
|
4
4
|
class Executable
|
5
5
|
include AIPP::Debugger
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
@options = options.merge(
|
11
|
-
airac: AIRAC::Cycle.new,
|
12
|
-
region_options: [],
|
7
|
+
def initialize(exe_file)
|
8
|
+
AIPP.options.replace(
|
9
|
+
schema: exe_file.split('2').last.to_sym,
|
13
10
|
storage: Pathname(Dir.home).join('.aipp'),
|
11
|
+
clean: false,
|
14
12
|
force: false,
|
15
13
|
mid: false,
|
14
|
+
quiet: false,
|
16
15
|
verbose: false,
|
17
16
|
debug_on_warning: false,
|
18
17
|
debug_on_error: false
|
19
18
|
)
|
20
|
-
OptionParser.new do |o|
|
21
|
-
o.banner = <<~END
|
22
|
-
Download online AIP and convert it to #{options[:schema].upcase}.
|
23
|
-
Usage: #{File.basename($0)} [options]
|
24
|
-
END
|
25
|
-
o.on('-d', '--airac (DATE|INTEGER)', String, %Q[AIRAC date or delta e.g. "+1" (default: "#{@options[:airac].date.xmlschema}")]) { @options[:airac] = airac_for(_1) }
|
26
|
-
o.on('-r', '--region STRING', String, 'region (e.g. "LF")') { @options[:region] = _1.upcase }
|
27
|
-
o.on('-a', '--aip STRING', String, 'process this AIP only (e.g. "ENR-5.1")') { @options[:aip] = _1 }
|
28
|
-
if options[:schema] == :ofmx
|
29
|
-
o.on('-g', '--[no-]grouped-obstacles', 'group obstacles (default: false)') { @options[:grouped_obstacles] = _1 }
|
30
|
-
o.on('-m', '--[no-]mid', 'insert mid attributes into all Uid elements (default: false)') { @options[:mid] = _1 }
|
31
|
-
end
|
32
|
-
o.on('-o', '--region-options STRING', String, %Q[comma separated region specific options]) { @options[:region_options] = _1.split(',') }
|
33
|
-
o.on('-s', '--storage DIR', String, 'storage directory (default: "~/.aipp")') { @options[:storage] = Pathname(_1) }
|
34
|
-
o.on('-h', '--[no-]check-links', 'check all links with HEAD requests') { @options[:check_links] = _1 }
|
35
|
-
o.on('-f', '--[no-]force', 'ignore XML schema validation (default: false)') { @options[:force] = _1 }
|
36
|
-
o.on('-v', '--[no-]verbose', 'verbose output including unsevere warnings (default: false)') { @options[:verbose] = _1 }
|
37
|
-
o.on('-w', '--debug-on-warning [ID]', Integer, 'open debug session on warning with ID (default: false)') { @options[:debug_on_warning] = _1 || true }
|
38
|
-
o.on('-e', '--[no-]debug-on-error', 'open debug session on error (default: false)') { @options[:debug_on_error] = _1 }
|
39
|
-
o.on('-A', '--about', 'show author/license information and exit') { about }
|
40
|
-
o.on('-R', '--readme', 'show README and exit') { readme }
|
41
|
-
o.on('-L', '--list', 'list implemented regions and AIPs') { list }
|
42
|
-
o.on('-V', '--version', 'show version and exit') { version }
|
43
|
-
end.parse!
|
44
19
|
end
|
45
20
|
|
46
21
|
def run
|
47
|
-
with_debugger
|
22
|
+
with_debugger do
|
23
|
+
String.disable_colorization = !STDOUT.tty?
|
48
24
|
starting = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
49
|
-
AIPP
|
50
|
-
parser.read_config
|
51
|
-
parser.read_region
|
52
|
-
parser.parse_aip
|
53
|
-
parser.validate_aixm
|
54
|
-
parser.write_build
|
55
|
-
parser.write_aixm
|
56
|
-
parser.write_config
|
57
|
-
end
|
25
|
+
[:AIPP, AIPP.options.module, :Runner].constantize.new.run
|
58
26
|
ending = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
59
27
|
info("finished after %s" % Time.at(ending - starting).utc.strftime("%H:%M:%S"))
|
60
28
|
end
|
@@ -62,12 +30,28 @@ module AIPP
|
|
62
30
|
|
63
31
|
private
|
64
32
|
|
65
|
-
def
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
33
|
+
def common_options(o)
|
34
|
+
o.on('-r', '--region STRING', String, 'region (e.g. "LF")') { AIPP.options.region = _1.upcase }
|
35
|
+
o.on('-s', '--section STRING', String, 'process this section only') { AIPP.options.section = _1.classify }
|
36
|
+
o.on('-d', '--storage DIR', String, 'storage directory (default: "~/.aipp")') { AIPP.options.storage = Pathname(_1) }
|
37
|
+
o.on('-o', '--output FILE', String, 'output file') { AIPP.options.output_file = _1 }
|
38
|
+
end
|
39
|
+
|
40
|
+
def developer_options(o)
|
41
|
+
if AIPP.options.schema == :ofmx
|
42
|
+
o.on('-m', '--[no-]mid', 'insert mid attributes into all Uid elements (default: false)') { AIPP.options.mid = _1 }
|
70
43
|
end
|
44
|
+
o.on('-h', '--[no-]check-links', 'check all links with HEAD requests') { AIPP.options.check_links = _1 }
|
45
|
+
o.on('-c', '--[no-]clean', 'clean cache and download from sources anew (default: false)') { AIPP.options.clean = _1 }
|
46
|
+
o.on('-f', '--[no-]force', 'continue on non-fatal errors (default: false)') { AIPP.options.force = _1 }
|
47
|
+
o.on('-q', '--[no-]quiet', 'suppress all informational output (default: false)') { AIPP.options.quiet = _1 }
|
48
|
+
o.on('-v', '--[no-]verbose', 'verbose output including unsevere warnings (default: false)') { AIPP.options.verbose = _1 }
|
49
|
+
o.on('-w', '--debug-on-warning [ID]', Integer, 'open debug session on warning with ID (default: false)') { AIPP.options.debug_on_warning = _1 || true }
|
50
|
+
o.on('-e', '--[no-]debug-on-error', 'open debug session on error (default: false)') { AIPP.options.debug_on_error = _1 }
|
51
|
+
o.on('-A', '--about', 'show author/license information and exit') { about }
|
52
|
+
o.on('-R', '--readme', 'show README and exit') { readme }
|
53
|
+
o.on('-L', '--list', 'list implemented regions') { list }
|
54
|
+
o.on('-V', '--version', 'show version and exit') { version }
|
71
55
|
end
|
72
56
|
|
73
57
|
def about
|
@@ -76,19 +60,18 @@ module AIPP
|
|
76
60
|
end
|
77
61
|
|
78
62
|
def readme
|
79
|
-
|
80
|
-
puts IO.read(readme_path)
|
63
|
+
puts IO.read(Pathname(__dir__).join('README.md'))
|
81
64
|
exit
|
82
65
|
end
|
83
66
|
|
84
67
|
def list
|
85
|
-
|
86
|
-
|
87
|
-
hash[region] =
|
88
|
-
File.basename(
|
68
|
+
hash = Pathname(__dir__).join('regions').glob('*').each.with_object({}) do |dir, hash|
|
69
|
+
region = "Sections for region #{dir.basename}"
|
70
|
+
hash[region] = dir.join(AIPP.options.module.downcase).glob('*.rb').map do |file|
|
71
|
+
File.basename(file, '.rb')
|
89
72
|
end.compact
|
90
73
|
end
|
91
|
-
puts hash.to_yaml.
|
74
|
+
puts hash.to_yaml.lines[1..]
|
92
75
|
exit
|
93
76
|
end
|
94
77
|
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# AIPP NOTAM Module
|
2
|
+
|
3
|
+
## Cache Time Window
|
4
|
+
|
5
|
+
The default time window for NOTAM is the hour of day. This means:
|
6
|
+
|
7
|
+
* Source data is downloaded and cached based on the hour of the day.
|
8
|
+
* The effective date and time is rounded down to the previous full hour.
|
9
|
+
|
10
|
+
To force a rebuild within this time window, you have to clean the cache using the `-c` command line argument.
|
11
|
+
|
12
|
+
### Soft Fail and Crossload
|
13
|
+
|
14
|
+
Malformed NOTAM which cannot be processed normally cause the build to fail. The `-f` command line argument changes this behaviour to skip the malformed NOTAM, issue a warning and continue.
|
15
|
+
|
16
|
+
To fix broken NOTAM, you can set a crossload directory with `-x`. The contents of this directory must adhere to the following convention:
|
17
|
+
|
18
|
+
```
|
19
|
+
/ ⬅︎ custom crossload directory
|
20
|
+
├── LS ⬅︎ region
|
21
|
+
│ └── W2479_22.txt ⬅︎ NOTAM ID (replace "/" with "_")
|
22
|
+
└── ED ⬅︎ other region
|
23
|
+
```
|
24
|
+
|
25
|
+
If a matching file is found in the crossload directory, it will be used in place of the original, malformed NOTAM.
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module AIPP
|
2
|
+
module NOTAM
|
3
|
+
|
4
|
+
class Executable < AIPP::Executable
|
5
|
+
|
6
|
+
def initialize(exe_file)
|
7
|
+
super
|
8
|
+
now = Time.now.utc.round
|
9
|
+
AIPP.options.merge(
|
10
|
+
module: 'NOTAM',
|
11
|
+
effective_at: now - now.sec - (now.min * 60) # previous full hour
|
12
|
+
)
|
13
|
+
OptionParser.new do |o|
|
14
|
+
o.banner = <<~END
|
15
|
+
Download online NOTAM and convert it to #{AIPP.options.schema.upcase}.
|
16
|
+
Usage: #{File.basename($0)} [options]
|
17
|
+
END
|
18
|
+
common_options(o)
|
19
|
+
o.on('-t', '--effective (TIME)', String, %Q[effective after this point in time (default: #{AIPP.options.effective_at})]) { AIPP.options.effective_at = Time.parse(_1) }
|
20
|
+
o.on('-x', '--crossload DIR', String, 'crossload directory') { AIPP.options.crossload = Pathname(_1) }
|
21
|
+
developer_options(o)
|
22
|
+
end.parse!
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module AIPP
|
2
|
+
module NOTAM
|
3
|
+
|
4
|
+
class Runner < AIPP::Runner
|
5
|
+
|
6
|
+
def effective_at
|
7
|
+
AIPP.options.effective_at
|
8
|
+
end
|
9
|
+
|
10
|
+
def expiration_at
|
11
|
+
effective_at.end_of_day.round - 1
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
info("NOTAM effective #{effective_at}", color: :green)
|
16
|
+
read_config
|
17
|
+
read_region
|
18
|
+
read_parsers
|
19
|
+
parse_sections
|
20
|
+
validate_aixm
|
21
|
+
write_aixm(AIPP.options.output_file || output_file)
|
22
|
+
write_config
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
data/lib/aipp/parser.rb
CHANGED
@@ -1,196 +1,169 @@
|
|
1
1
|
module AIPP
|
2
2
|
|
3
|
-
#
|
3
|
+
# @abstract
|
4
4
|
class Parser
|
5
|
-
extend Forwardable
|
6
5
|
include AIPP::Debugger
|
7
|
-
|
6
|
+
include AIPP::Patcher
|
8
7
|
|
9
|
-
# @return [
|
10
|
-
attr_reader :
|
8
|
+
# @return [AIXM::Document] AIXM document instance
|
9
|
+
attr_reader :aixm
|
11
10
|
|
12
|
-
|
13
|
-
|
11
|
+
class << self
|
12
|
+
# Declare a dependency
|
13
|
+
#
|
14
|
+
# @param dependencies [Array<String>] class names of other parsers this
|
15
|
+
# parser depends on
|
16
|
+
def depends_on(*dependencies)
|
17
|
+
@dependencies = dependencies.map(&:to_s)
|
18
|
+
end
|
14
19
|
|
15
|
-
|
16
|
-
|
20
|
+
# Declared dependencies
|
21
|
+
#
|
22
|
+
# @return [Array<String>] class names of other parsers this parser
|
23
|
+
# depends on
|
24
|
+
def dependencies
|
25
|
+
@dependencies || []
|
26
|
+
end
|
27
|
+
end
|
17
28
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
# @return [Hash] map from border names to border objects
|
22
|
-
attr_reader :borders
|
23
|
-
|
24
|
-
# @return [OpenStruct] object cache
|
25
|
-
attr_reader :cache
|
26
|
-
|
27
|
-
def initialize(options:)
|
28
|
-
@options = options
|
29
|
-
@options[:storage] = options[:storage].join(options[:region])
|
30
|
-
@options[:storage].mkpath
|
31
|
-
@config = {}
|
32
|
-
@aixm = AIXM.document(effective_at: @options[:airac].date)
|
33
|
-
@dependencies = THash.new
|
34
|
-
@fixtures = {}
|
35
|
-
@borders = {}
|
36
|
-
@cache = OpenStruct.new
|
37
|
-
AIXM.send("#{options[:schema]}!")
|
38
|
-
AIXM.config.region = options[:region]
|
29
|
+
def initialize(downloader:, aixm:)
|
30
|
+
@downloader, @aixm = downloader, aixm
|
31
|
+
setup if respond_to? :setup
|
39
32
|
end
|
40
33
|
|
41
34
|
# @return [String]
|
42
35
|
def inspect
|
43
|
-
"#<AIPP::Parser>"
|
36
|
+
"#<AIPP::Parser #{section}>"
|
44
37
|
end
|
45
38
|
|
46
|
-
#
|
47
|
-
def
|
48
|
-
|
49
|
-
@config = YAML.load_file(config_file, symbolize_names: true, fallback: {}) if config_file.exist?
|
50
|
-
@config[:namespace] ||= SecureRandom.uuid
|
51
|
-
@aixm.namespace = @config[:namespace]
|
39
|
+
# @return [String]
|
40
|
+
def section
|
41
|
+
self.class.to_s.sectionize
|
52
42
|
end
|
53
43
|
|
54
|
-
#
|
55
|
-
def
|
56
|
-
|
57
|
-
dir = Pathname(__FILE__).dirname.join('regions', options[:region])
|
58
|
-
fail("unknown region `#{options[:region]}'") unless dir.exist?
|
59
|
-
# Fixtures
|
60
|
-
dir.glob('fixtures/*.yml').each do |file|
|
61
|
-
verbose_info "reading fixture fixtures/#{file.basename}"
|
62
|
-
fixture = YAML.load_file(file)
|
63
|
-
@fixtures[file.basename('.yml').to_s] = fixture
|
64
|
-
end
|
65
|
-
# Borders
|
66
|
-
dir.glob('borders/*.geojson').each do |file|
|
67
|
-
verbose_info "reading border borders/#{file.basename}"
|
68
|
-
border = AIPP::Border.from_file(file)
|
69
|
-
@borders[file.basename] = border
|
70
|
-
end
|
71
|
-
# Helpers
|
72
|
-
dir.glob('helpers/*.rb').each do |file|
|
73
|
-
verbose_info "reading helper helpers/#{file.basename}"
|
74
|
-
require file
|
75
|
-
end
|
76
|
-
# Parsers
|
77
|
-
dir.glob('*.rb').each do |file|
|
78
|
-
verbose_info "requiring #{file.basename}"
|
79
|
-
require file
|
80
|
-
aip = file.basename('.*').to_s
|
81
|
-
@dependencies[aip] = ("AIPP::%s::%s::DEPENDS" % [options[:region], aip.remove(/\W/).camelcase]).constantize
|
82
|
-
end
|
44
|
+
# @abstract
|
45
|
+
def origin_for(*)
|
46
|
+
fail "origin_for method must be implemented in parser"
|
83
47
|
end
|
84
48
|
|
85
|
-
#
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
end
|
103
|
-
info("counting #{aixm.features.count} features")
|
49
|
+
# Read a source document
|
50
|
+
#
|
51
|
+
# Read the cached document if it exists in the source archive. Otherwise,
|
52
|
+
# download and cache it.
|
53
|
+
#
|
54
|
+
# An origin builder method +origin_for+ must be implemented by the parser
|
55
|
+
# definition.
|
56
|
+
#
|
57
|
+
# @param document [String] e.g. "ENR-2.1" or "aerodromes" (default: current
|
58
|
+
# +section+)
|
59
|
+
# @return [Nokogiri::XML::Document, Nokogiri::HTML5::Document,
|
60
|
+
# Roo::Spreadsheet, String] document
|
61
|
+
def read(document=section)
|
62
|
+
@downloader.read(
|
63
|
+
document: document,
|
64
|
+
origin: origin_for(document)
|
65
|
+
)
|
104
66
|
end
|
105
67
|
|
106
|
-
#
|
68
|
+
# Add feature to AIXM
|
107
69
|
#
|
108
|
-
# @
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
end
|
115
|
-
info("validating #{options[:schema].upcase}")
|
116
|
-
unless aixm.valid?
|
117
|
-
message = "invalid #{options[:schema].upcase} document:\n" + aixm.errors.map(&:message).join("\n")
|
118
|
-
@options[:force] ? warn(message) : fail(message)
|
119
|
-
end
|
70
|
+
# @param feature [AIXM::Feature] e.g. airport or airspace
|
71
|
+
# @return [AIXM::Feature] added feature
|
72
|
+
def add(feature)
|
73
|
+
verbose_info "adding #{feature.inspect}"
|
74
|
+
aixm.add_feature feature
|
75
|
+
feature
|
120
76
|
end
|
121
77
|
|
122
|
-
#
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
tmp_dir.join('build.yaml'), {
|
138
|
-
version: AIPP::VERSION,
|
139
|
-
config: @config,
|
140
|
-
options: @options
|
141
|
-
}.to_yaml
|
142
|
-
)
|
143
|
-
# Manifest
|
144
|
-
manifest = ['AIP','Feature', 'Comment', 'Short Uid Hash', 'Short Feature Hash'].to_csv
|
145
|
-
manifest += aixm.features.map do |feature|
|
146
|
-
xml = feature.to_xml
|
147
|
-
element = xml.first_match(/<(\w{3})\s/)
|
148
|
-
[
|
149
|
-
feature.source.split('|')[2],
|
150
|
-
element,
|
151
|
-
xml.match(/<!-- (.*?) -->/)[1],
|
152
|
-
AIXM::PayloadHash.new(xml.match(%r(<#{element}Uid\s.*?</#{element}Uid>)m).to_s).to_uuid[0,8],
|
153
|
-
AIXM::PayloadHash.new(xml).to_uuid[0,8]
|
154
|
-
].to_csv
|
155
|
-
end.sort.join
|
156
|
-
File.write(tmp_dir.join('manifest.csv'), manifest)
|
157
|
-
# Zip it
|
158
|
-
build_file.delete if build_file.exist?
|
159
|
-
Zip::File.open(build_file, Zip::File::CREATE) do |zip|
|
160
|
-
tmp_dir.children.each do |entry|
|
161
|
-
zip.add(entry.basename.to_s, entry) unless entry.basename.to_s[0] == '.'
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
78
|
+
# @!method find_by(klass, attributes={})
|
79
|
+
# Find objects of the given class and optionally with the given attribute
|
80
|
+
# values previously written to AIXM.
|
81
|
+
#
|
82
|
+
# @note This method is delegated to +AIXM::Association::Array+.
|
83
|
+
# @see https://www.rubydoc.info/gems/aixm/AIXM/Association/Array#find_by-instance_method
|
84
|
+
#
|
85
|
+
# @!method find(object)
|
86
|
+
# Find equal objects previously written to AIXM.
|
87
|
+
#
|
88
|
+
# @note This method is delegated to +AIXM::Association::Array+.
|
89
|
+
# @see https://www.rubydoc.info/gems/aixm/AIXM/Association/Array#find-instance_method
|
90
|
+
%i(find_by find).each do |method|
|
91
|
+
define_method method do |*args|
|
92
|
+
aixm.features.send(method, *args)
|
165
93
|
end
|
166
94
|
end
|
167
95
|
|
168
|
-
#
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
96
|
+
# @overload given(*objects)
|
97
|
+
# Return +objects+ unless at least one of them equals nil
|
98
|
+
#
|
99
|
+
# @example
|
100
|
+
# # Instead of this:
|
101
|
+
# first, last = unless ((first = expensive_first).nil? || (last = expensive_last).nil?)
|
102
|
+
# [first, last]
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# # Use the following:
|
106
|
+
# first, last = given(expensive_first, expensive_last)
|
107
|
+
#
|
108
|
+
# @param *objects [Array<Object>] any objects really
|
109
|
+
# @return [Object] nil if at least one of the objects is nil, given
|
110
|
+
# objects otherwise
|
111
|
+
#
|
112
|
+
# @overload given(*objects)
|
113
|
+
# Yield +objects+ unless at least one of them equals nil
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
# # Instead of this:
|
117
|
+
# name = unless ((first = expensive_first.nil? || (last = expensive_last.nil?)
|
118
|
+
# "#{first} #{last}"
|
119
|
+
# end
|
120
|
+
#
|
121
|
+
# # Use any of the following:
|
122
|
+
# name = given(expensive_first, expensive_last) { |f, l| "#{f} #{l}" }
|
123
|
+
# name = given(expensive_first, expensive_last) { "#{_1} #{_2}" }
|
124
|
+
#
|
125
|
+
# @param *objects [Array<Object>] any objects really
|
126
|
+
# @yield [Array<Object>] objects passed as parameter
|
127
|
+
# @return [Object] nil if at least one of the objects is nil, return of
|
128
|
+
# block otherwise
|
129
|
+
def given(*objects)
|
130
|
+
if objects.none?(&:nil?)
|
131
|
+
block_given? ? yield(*objects) : objects
|
132
|
+
end
|
173
133
|
end
|
174
134
|
|
175
|
-
#
|
176
|
-
|
177
|
-
|
178
|
-
|
135
|
+
# Build and optionally check a Markdown link
|
136
|
+
#
|
137
|
+
# @example
|
138
|
+
# AIPP.options.check_links = false
|
139
|
+
# link_to('foo', 'https://bar.com/exists') # => "[foo](https://bar.com/exists)"
|
140
|
+
# link_to('foo', 'https://bar.com/not-found') # => "[foo](https://bar.com/not-found)"
|
141
|
+
# AIPP.options.check_links = true
|
142
|
+
# link_to('foo', 'https://bar.com/exists') # => "[foo](https://bar.com/exists)"
|
143
|
+
# link_to('foo', 'https://bar.com/not-found') # => nil
|
144
|
+
#
|
145
|
+
# @param body [String] body text of the link
|
146
|
+
# @param url [String] URL of the link
|
147
|
+
# @return [String, nil] Markdown link
|
148
|
+
def link_to(body, url)
|
149
|
+
"[#{body}](#{url})" if !AIPP.options.check_links || url_exists?(url)
|
179
150
|
end
|
180
151
|
|
181
152
|
private
|
182
153
|
|
183
|
-
def
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
154
|
+
def url_exists?(url)
|
155
|
+
uri = URI.parse(url)
|
156
|
+
Net::HTTP.new(uri.host, uri.port).tap do |request|
|
157
|
+
request.use_ssl = (uri.scheme == 'https')
|
158
|
+
path = uri.path.present? ? uri.path : '/'
|
159
|
+
result = request.request_head(path)
|
160
|
+
if result.kind_of? Net::HTTPRedirection
|
161
|
+
url_exist?(result['location'])
|
162
|
+
else
|
163
|
+
result.code == '200'
|
164
|
+
end
|
165
|
+
end
|
189
166
|
end
|
190
167
|
|
191
|
-
def config_file
|
192
|
-
options[:storage].join('config.yml')
|
193
|
-
end
|
194
168
|
end
|
195
|
-
|
196
169
|
end
|
data/lib/aipp/patcher.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module AIPP
|
2
2
|
module Patcher
|
3
3
|
|
4
|
-
def self.included(
|
5
|
-
|
6
|
-
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
base.class_variable_set(:@@patches, {})
|
7
7
|
end
|
8
8
|
|
9
9
|
module ClassMethods
|
@@ -17,14 +17,13 @@ module AIPP
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def attach_patches
|
20
|
-
parser = self
|
21
20
|
verbose_info_method = method(:verbose_info)
|
22
21
|
self.class.patches[self.class]&.each do |(klass, attribute, block)|
|
23
22
|
klass.instance_eval do
|
24
23
|
alias_method :"original_#{attribute}=", :"#{attribute}="
|
25
24
|
define_method(:"#{attribute}=") do |value|
|
26
25
|
error = catch :abort do
|
27
|
-
value = block.call(
|
26
|
+
value = block.call(self, value)
|
28
27
|
verbose_info_method.call("Patching #{self.inspect} with #{attribute}=#{value.inspect}", color: :magenta)
|
29
28
|
end
|
30
29
|
fail "patching #{self.inspect} with #{attribute}=#{value.inspect} failed: #{error}" if error
|
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# LF – France Mainland
|
2
2
|
|
3
3
|
## Prerequisites
|
4
4
|
|
@@ -7,7 +7,7 @@ This parser requires the XML data dump from SIA. It is available free of charge,
|
|
7
7
|
1. Browse to the [SIA web shop](https://www.sia.aviation-civile.gouv.fr/produits-numeriques-en-libre-disposition/les-bases-de-donnees-sia.html).
|
8
8
|
2. Shop the desired dump named «données aéronautiques XML AIRAC ii/yy».
|
9
9
|
3. Browse to [your customer page](https://www.sia.aviation-civile.gouv.fr/customer/account/#orders-and-proposals).
|
10
|
-
4.
|
10
|
+
4. On page «mes produits téléchargeables» download the desired dump.
|
11
11
|
5. Unzip the downloaded ZIP archive.
|
12
12
|
6. Move the file «XML_SIA_yyyy-mm-dd.xml» to the directory in which you will execute the parser.
|
13
13
|
|
@@ -41,6 +41,10 @@ grep "<Revetement>" XML_SIA_2021-12-02_UTF.xml | sort | uniq
|
|
41
41
|
(...)
|
42
42
|
```
|
43
43
|
|
44
|
+
## Error Reports
|
45
|
+
|
46
|
+
Feedback and error reports should be sent to SIA. However, their [contact form](https://www.sia.aviation-civile.gouv.fr/contact) is sometimes broken. If you don't receive an automatic reception confirmation, you should send it directly to sia-qualite@aviation-civile.gouv.fr instead.
|
47
|
+
|
44
48
|
## References
|
45
49
|
|
46
50
|
* [SIA – AIP publisher](https://www.sia.aviation-civile.gouv.fr)
|