nacofetch 1.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.
- data/README.md +1 -0
- data/bin/nacofetch +3 -0
- data/lib/nacofetch.rb +207 -0
- data/lib/nacofetch/config.rb +14 -0
- data/lib/nacofetch/cycle.rb +21 -0
- data/lib/nacofetch/date_util.rb +22 -0
- data/lib/nacofetch/naco_interface.rb +142 -0
- metadata +115 -0
data/README.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# This is my README
|
data/bin/nacofetch
ADDED
data/lib/nacofetch.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'cri'
|
5
|
+
|
6
|
+
require 'nacofetch/naco_interface'
|
7
|
+
require 'nacofetch/config'
|
8
|
+
|
9
|
+
#initialize here
|
10
|
+
|
11
|
+
@default_config_dir = File.expand_path("~/.naco")
|
12
|
+
@config_file_name = "config.rb"
|
13
|
+
|
14
|
+
begin
|
15
|
+
NacoConfig.from_file(File.expand_path('~/.naco/config.rb'))
|
16
|
+
rescue
|
17
|
+
config_filename = File.expand_path(@config_file_name, @default_config_dir)
|
18
|
+
puts "No config file found... creating #{config_filename}"
|
19
|
+
|
20
|
+
# ensure directory exists
|
21
|
+
begin
|
22
|
+
Dir::mkdir(@default_config_dir)
|
23
|
+
puts "Creating #{@default_config_dir}"
|
24
|
+
rescue
|
25
|
+
puts "#{@default_config_dir} already exists"
|
26
|
+
end
|
27
|
+
|
28
|
+
File.new(File.expand_path(@config_file_name, @default_config_dir), "w") do |f|
|
29
|
+
# file closes on block termination
|
30
|
+
# write into f if I want to pre-configure the file. For now it's just empty.
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def print_cycles(cycles)
|
35
|
+
puts "\tCycle\tEffective\tEnding"
|
36
|
+
puts "-------------------------------------------"
|
37
|
+
cycles.each do |cycle|
|
38
|
+
puts "\t#{cycle.number}\t#{cycle.effective.strftime("%b %d %Y")}\t#{cycle.ending.strftime("%b %d %Y")}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def select_cycle(opts)
|
43
|
+
ni = NacoInterface.new
|
44
|
+
cycles = ni.cycles
|
45
|
+
|
46
|
+
cycle = nil
|
47
|
+
|
48
|
+
unless opts[:current] or opts[:pending] or opts[:cycle]
|
49
|
+
cycle = ni.current_cycle
|
50
|
+
if cycles.size > 1
|
51
|
+
puts "WARN: no cycle specified, and multiple cycles are available. Defaulting to current cycle (#{cycle.number})"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if(opts[:current])
|
56
|
+
cycle = ni.current_cycle
|
57
|
+
end
|
58
|
+
|
59
|
+
if(opts[:pending])
|
60
|
+
cycle = ni.pending_cycle
|
61
|
+
end
|
62
|
+
|
63
|
+
if(opts[:cycle])
|
64
|
+
cycle = cycles.select { |c| c.number == opts[:cycle] }.first
|
65
|
+
if cycle.nil?
|
66
|
+
puts "ERROR: cycle #{opts[:cycle]} not found on dTPP. Available cycles:\n\n"
|
67
|
+
print_cycles(cycles)
|
68
|
+
exit 0
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if cycle.nil?
|
73
|
+
raise "Ended up here with a nil cycle, and I can't handle that"
|
74
|
+
end
|
75
|
+
|
76
|
+
status = cycle.status == "PENDING" ? "(effective #{cycle.effective.strftime("%b %d %Y")})" : "(expires #{cycle.ending.strftime("%b %d %Y")})"
|
77
|
+
puts "Desired cycle is #{cycle.number} #{status}"
|
78
|
+
|
79
|
+
cycle
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
super_cmd = Cri::Command.define do
|
84
|
+
name 'nacofetch'
|
85
|
+
usage 'nacofetch fetches and packages naco/faa plates from the dTPP'
|
86
|
+
summary 'naco digital terminal procedure fetch tool'
|
87
|
+
description 'coming soon'
|
88
|
+
|
89
|
+
run do |opts, args, cmd|
|
90
|
+
puts cmd.help
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
super_cmd.define_command do
|
95
|
+
name 'cycle'
|
96
|
+
summary 'list currently available dTPP cycles'
|
97
|
+
|
98
|
+
run do |opts, args|
|
99
|
+
print_cycles(NacoInterface.new.cycles)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
super_cmd.define_command do
|
105
|
+
name 'metafile'
|
106
|
+
summary 'prefetch metafile for cycle'
|
107
|
+
usage 'metafile <-p|-c|-y cycle>'
|
108
|
+
|
109
|
+
flag :h, :help, 'show help for this command' do |value, cmd|
|
110
|
+
puts cmd.help
|
111
|
+
exit 0
|
112
|
+
end
|
113
|
+
|
114
|
+
flag :c, :current, 'Fetch current cycle (default)'
|
115
|
+
flag :p, :pending, 'Fetch pending cycle if available, otherwise fall back to current cycle'
|
116
|
+
option :y, :cycle, 'Fetch given cycle (-y 1207 will fetch cycle 1207)', :argument => :required
|
117
|
+
|
118
|
+
run do |opts, args, cmd|
|
119
|
+
cycle = select_cycle(opts)
|
120
|
+
|
121
|
+
metafile = NacoInterface.new.metafile_for_cycle(cycle)
|
122
|
+
|
123
|
+
puts "Metafile fetched: #{metafile}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
super_cmd.define_command do
|
129
|
+
name 'airport'
|
130
|
+
summary 'fetch plates for one or more airports'
|
131
|
+
|
132
|
+
flag :h, :help, 'show help for this command' do |value, cmd|
|
133
|
+
puts cmd.help
|
134
|
+
exit 0
|
135
|
+
end
|
136
|
+
|
137
|
+
flag :c, :current, 'Fetch current cycle (default)'
|
138
|
+
flag :p, :pending, 'Fetch pending cycle if available, otherwise fall back to current cycle'
|
139
|
+
option :y, :cycle, 'Fetch given cycle (-y 1207 will fetch cycle 1207)', :argument => :required
|
140
|
+
|
141
|
+
run do |opts, args|
|
142
|
+
ni = NacoInterface.new
|
143
|
+
cycle = select_cycle(opts)
|
144
|
+
metafile = ni.metafile_for_cycle(cycle)
|
145
|
+
|
146
|
+
puts "Fetching cycle #{cycle.number} plates for: " + args.map{|a| a.upcase}.join(", ")
|
147
|
+
|
148
|
+
args.each do |a|
|
149
|
+
ni.fetch_plates_for_airport(metafile,a)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
super_cmd.define_command do
|
155
|
+
name 'state'
|
156
|
+
summary 'fetch plates for all airports in one or more states'
|
157
|
+
|
158
|
+
flag :h, :help, 'show help for this command' do |value, cmd|
|
159
|
+
puts cmd.help
|
160
|
+
exit 0
|
161
|
+
end
|
162
|
+
|
163
|
+
flag :c, :current, 'Fetch current cycle (default)'
|
164
|
+
flag :p, :pending, 'Fetch pending cycle if available, otherwise fall back to current cycle'
|
165
|
+
option :y, :cycle, 'Fetch given cycle (-y 1207 will fetch cycle 1207)', :argument => :required
|
166
|
+
|
167
|
+
run do |opts, args|
|
168
|
+
ni = NacoInterface.new
|
169
|
+
cycle = select_cycle(opts)
|
170
|
+
metafile = ni.metafile_for_cycle(cycle)
|
171
|
+
|
172
|
+
puts "Fetching cycle #{cycle.number} plates for: " + args.map{|a| a.upcase}.join(", ")
|
173
|
+
|
174
|
+
args.each do |a|
|
175
|
+
ni.fetch_plates_for_state(metafile,a)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
super_cmd.define_command do
|
181
|
+
name 'volume'
|
182
|
+
summary 'fetch plates for an entire NACO volume'
|
183
|
+
|
184
|
+
flag :h, :help, 'show help for this command' do |value, cmd|
|
185
|
+
puts cmd.help
|
186
|
+
exit 0
|
187
|
+
end
|
188
|
+
|
189
|
+
flag :c, :current, 'Fetch current cycle (default)'
|
190
|
+
flag :p, :pending, 'Fetch pending cycle if available, otherwise fall back to current cycle'
|
191
|
+
option :y, :cycle, 'Fetch given cycle (-y 1207 will fetch cycle 1207)', :argument => :required
|
192
|
+
|
193
|
+
run do |opts, args|
|
194
|
+
ni = NacoInterface.new
|
195
|
+
cycle = select_cycle(opts)
|
196
|
+
metafile = ni.metafile_for_cycle(cycle)
|
197
|
+
|
198
|
+
puts "Fetching cycle #{cycle.number} plates for: " + args.map{|a| a.upcase}.join(", ")
|
199
|
+
|
200
|
+
args.each do |a|
|
201
|
+
ni.fetch_plates_for_volume(metafile,a)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
super_cmd.add_command Cri::Command.new_basic_help
|
207
|
+
super_cmd.run(ARGV)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'mixlib/config'
|
3
|
+
|
4
|
+
class NacoConfig
|
5
|
+
extend(Mixlib::Config)
|
6
|
+
|
7
|
+
configure do |c|
|
8
|
+
c[:config_dir] = File.expand_path('~/.naco')
|
9
|
+
c[:plate_storage_dir] = File.expand_path('plates', c[:config_dir])
|
10
|
+
c[:metafile_storage_dir] = File.expand_path('metafiles', c[:config_dir])
|
11
|
+
c[:cache_storage_dir] = File.expand_path('cache', c[:config_dir])
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'nacofetch/date_util'
|
2
|
+
|
3
|
+
class Cycle
|
4
|
+
attr_accessor :url, :number, :effective, :ending
|
5
|
+
|
6
|
+
def initialize(url, number, effective, ending)
|
7
|
+
@url = url
|
8
|
+
@number = number
|
9
|
+
@effective = effective
|
10
|
+
@ending = ending
|
11
|
+
end
|
12
|
+
|
13
|
+
def status
|
14
|
+
now = Time.new
|
15
|
+
|
16
|
+
return "PENDING" if now.before?@effective
|
17
|
+
return "CURRENT" if now.before?@ending and now.after?@effective
|
18
|
+
"EXPIRED" if now.after?@ending
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
def string_to_time time_, format_
|
4
|
+
time = Date._strptime(time_, format_)
|
5
|
+
return Time.local(time[:year], time[:mon], time[:mday], time[:hour], time[:min], time[:sec], time[:sec_fraction], time[:zone])
|
6
|
+
end
|
7
|
+
|
8
|
+
module DateUtil
|
9
|
+
|
10
|
+
LEFT_SIDE_LATER = 1
|
11
|
+
RIGHT_SIDE_LATER = -1
|
12
|
+
|
13
|
+
def before?(input_time)
|
14
|
+
(self <=> input_time) == RIGHT_SIDE_LATER
|
15
|
+
end
|
16
|
+
|
17
|
+
def after?(input_time)
|
18
|
+
(self <=> input_time) == LEFT_SIDE_LATER
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Time.send :include , DateUtil
|
@@ -0,0 +1,142 @@
|
|
1
|
+
|
2
|
+
require 'rubygems'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'nokogiri'
|
5
|
+
require 'httpclient'
|
6
|
+
|
7
|
+
require 'nacofetch/config'
|
8
|
+
require 'nacofetch/cycle'
|
9
|
+
|
10
|
+
|
11
|
+
class NacoInterface
|
12
|
+
def initialize
|
13
|
+
@base_url = "http://aeronav.faa.gov"
|
14
|
+
end
|
15
|
+
|
16
|
+
def cycles
|
17
|
+
unless @cycles_var
|
18
|
+
url = "/index.asp?xml=aeronav/applications/d_tpp"
|
19
|
+
doc = Nokogiri::HTML(open(@base_url + url))
|
20
|
+
@cycles_var = []
|
21
|
+
|
22
|
+
tables = doc.xpath('//table[@title="Digital Terminal Procedures Publication"]')
|
23
|
+
|
24
|
+
tables.xpath('//td[@headers="header1"]').each do |cycle|
|
25
|
+
url = cycle.xpath('./a/@href').to_s()
|
26
|
+
@cycles_var << Cycle.new(
|
27
|
+
url.sub(/digital_tpp/, "digital_tpp_search"), # todo: there's gotta be a less hacky way to do this'
|
28
|
+
/ver=(\d+)/.match(url)[1],
|
29
|
+
string_to_time(/eff=(\d+-\d+-\d+)/.match(url)[1], '%m-%d-%Y'),
|
30
|
+
string_to_time(/end=(\d+-\d+-\d+)/.match(url)[1], '%m-%d-%Y')
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
@cycles_var
|
36
|
+
end
|
37
|
+
|
38
|
+
def current_cycle
|
39
|
+
self.cycles.select { |cycle| cycle.status == "CURRENT" }.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def pending_cycle
|
43
|
+
self.cycles.select { |cycle| cycle.status == "PENDING" }.first
|
44
|
+
end
|
45
|
+
|
46
|
+
def metafile_for_cycle(cycle)
|
47
|
+
fetch_metafile(cycle) unless File.exists?(metafilename(cycle))
|
48
|
+
metafilename(cycle)
|
49
|
+
end
|
50
|
+
|
51
|
+
def fetch_plates_for_airport(metafile_url, ident)
|
52
|
+
xpath = "/digital_tpp/state_code/city_name/airport_name[@icao_ident = '#{ident}' or @apt_ident = '#{ident}']"
|
53
|
+
#xpath = "/digital_tpp/state_code[@ID = 'GA']"
|
54
|
+
|
55
|
+
fetch_plates_for_xpath(metafile_url, xpath)
|
56
|
+
end
|
57
|
+
|
58
|
+
def fetch_plates_for_state(metafile_url, statecode)
|
59
|
+
xpath = "/digital_tpp/state_code[@ID = '#{statecode}']"
|
60
|
+
|
61
|
+
fetch_plates_for_xpath(metafile_url, xpath)
|
62
|
+
end
|
63
|
+
|
64
|
+
def fetch_plates_for_volume(metafile_url, volumecode)
|
65
|
+
xpath = "/digital_tpp/state_code[city_name/@volume = '#{volumecode}']"
|
66
|
+
|
67
|
+
fetch_plates_for_xpath(metafile_url, xpath)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def fetch_plates_for_xpath(metafile_url, xpath)
|
73
|
+
doc = Nokogiri::XML(open(metafile_url))
|
74
|
+
|
75
|
+
cycle = doc.xpath('/digital_tpp/@cycle')
|
76
|
+
base_path = "#{@base_url}/d-tpp/#{cycle}/"
|
77
|
+
|
78
|
+
plate_dir = NacoConfig.plate_storage_dir
|
79
|
+
|
80
|
+
unless File.directory?plate_dir
|
81
|
+
Dir::mkdir(File.expand_path(plate_dir))
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
doc.xpath(xpath).each do |node|
|
86
|
+
node.xpath(".//record").each do |chart|
|
87
|
+
url = chart.xpath("pdf_name").inner_html
|
88
|
+
name = chart.xpath("chart_name").inner_html.gsub(/\//, "-")
|
89
|
+
type = chart.xpath("chart_code").inner_html
|
90
|
+
|
91
|
+
apt_ident = determine_ident(chart.xpath(".."))
|
92
|
+
|
93
|
+
full_url = base_path + url
|
94
|
+
|
95
|
+
unless File.directory?(File.expand_path(apt_ident, plate_dir))
|
96
|
+
Dir::mkdir(File.expand_path(apt_ident, plate_dir))
|
97
|
+
end
|
98
|
+
|
99
|
+
download(full_url, File.expand_path("#{apt_ident}/#{name}.pdf", plate_dir))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def determine_ident(apt_node)
|
105
|
+
icao_ident = apt_node.xpath("@icao_ident").inner_html
|
106
|
+
|
107
|
+
if icao_ident.empty?
|
108
|
+
return apt_node.xpath("@apt_ident").inner_html
|
109
|
+
end
|
110
|
+
|
111
|
+
icao_ident
|
112
|
+
end
|
113
|
+
|
114
|
+
def metafilename(cycle)
|
115
|
+
File.expand_path("#{cycle.number}-metafile.xml", NacoConfig.metafile_storage_dir)
|
116
|
+
end
|
117
|
+
|
118
|
+
def fetch_metafile(cycle)
|
119
|
+
url = "/d-tpp/#{cycle.number}/xml_data/d-TPP_Metafile.xml"
|
120
|
+
download(@base_url+url, metafilename(cycle))
|
121
|
+
end
|
122
|
+
|
123
|
+
def download(url, to_file)
|
124
|
+
client = HTTPClient.new(nil)
|
125
|
+
|
126
|
+
begin
|
127
|
+
Dir::mkdir(File.dirname(File.expand_path(to_file)))
|
128
|
+
rescue
|
129
|
+
end
|
130
|
+
|
131
|
+
file = File.new(to_file, "wb")
|
132
|
+
puts "Downloading #{url} to #{to_file}"
|
133
|
+
begin
|
134
|
+
file.write(client.get_content(url))
|
135
|
+
rescue
|
136
|
+
puts "ERROR: problem fetching #{url} (skipping)"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
|
metadata
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nacofetch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
version: 1.0.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Ian McMahon
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2012-07-03 00:00:00 -04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: httpclient
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
version_requirements: *id001
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: nokogiri
|
34
|
+
prerelease: false
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
segments:
|
40
|
+
- 0
|
41
|
+
version: "0"
|
42
|
+
type: :runtime
|
43
|
+
version_requirements: *id002
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: cri
|
46
|
+
prerelease: false
|
47
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
segments:
|
52
|
+
- 0
|
53
|
+
version: "0"
|
54
|
+
type: :runtime
|
55
|
+
version_requirements: *id003
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: mixlib-config
|
58
|
+
prerelease: false
|
59
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
type: :runtime
|
67
|
+
version_requirements: *id004
|
68
|
+
description: A tool to fetch and manage naco plates from the FAA aeronav digital terminal plates website
|
69
|
+
email: imcmahon@prototechnical.com
|
70
|
+
executables:
|
71
|
+
- nacofetch
|
72
|
+
extensions: []
|
73
|
+
|
74
|
+
extra_rdoc_files: []
|
75
|
+
|
76
|
+
files:
|
77
|
+
- lib/nacofetch/config.rb
|
78
|
+
- lib/nacofetch/cycle.rb
|
79
|
+
- lib/nacofetch/date_util.rb
|
80
|
+
- lib/nacofetch/naco_interface.rb
|
81
|
+
- lib/nacofetch.rb
|
82
|
+
- bin/nacofetch
|
83
|
+
- README.md
|
84
|
+
has_rdoc: true
|
85
|
+
homepage: http://nacofetch.prototechnical.com/
|
86
|
+
licenses: []
|
87
|
+
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
|
91
|
+
require_paths:
|
92
|
+
- lib
|
93
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
segments:
|
98
|
+
- 0
|
99
|
+
version: "0"
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
segments:
|
105
|
+
- 0
|
106
|
+
version: "0"
|
107
|
+
requirements: []
|
108
|
+
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 1.3.6
|
111
|
+
signing_key:
|
112
|
+
specification_version: 3
|
113
|
+
summary: NacoFetch
|
114
|
+
test_files: []
|
115
|
+
|