vfr_utils 0.0.3

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ad057bae66be0a8f641c375a0ed7f0c4e6413b37
4
+ data.tar.gz: 2592fd98b47284abb45bfee57bf003bc69c832fa
5
+ SHA512:
6
+ metadata.gz: 7997372764a5dbb062fe6450860fb33fbfeceaeffea6ee34c8f9ef7d35b16edc9987109a92d981350e356847c67065dbf3b2e45480e2a6dac70c53bed6ecfd71
7
+ data.tar.gz: 2142d275478481899105ec253def50ffbb425c3650042a1776cfab9df6d61da356b91c725a9136ec4451da05e89cd3521be56c6a5c18fa96bfdb28be90b401ad
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/vfr_utils/cli'
4
+
5
+ VfrUtils::CLI.run ARGV
@@ -0,0 +1,23 @@
1
+ require_relative 'vfr_utils/version'
2
+ require_relative 'vfr_utils/configuration'
3
+ require_relative 'vfr_utils/master_configuration'
4
+ require_relative 'vfr_utils/cache'
5
+ require_relative 'vfr_utils/base_service'
6
+ require_relative 'vfr_utils/taf'
7
+ require_relative 'vfr_utils/notam'
8
+ require_relative 'vfr_utils/metar'
9
+
10
+ module VfrUtils
11
+ class << self
12
+ attr_accessor :master_configuration
13
+ end
14
+
15
+ self.master_configuration ||= MasterConfiguration.new
16
+
17
+ class << self
18
+ def configure
19
+ self.master_configuration ||= MasterConfiguration.new
20
+ yield master_configuration
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ require_relative '../vfr_utils'
2
+ require 'faraday'
3
+ require 'nokogiri'
4
+ require 'date'
5
+
6
+ module VfrUtils
7
+ class BaseService
8
+
9
+ class << self
10
+
11
+ def inherited(child_class)
12
+ def child_class.cache
13
+ unless @cache
14
+ config_key = self.name.split('::').last.downcase.to_sym
15
+ @cache = Cache.new(VfrUtils.master_configuration.send(config_key))
16
+ end
17
+ @cache
18
+ end
19
+ end
20
+
21
+ def get(icao_codes)
22
+ icao_codes = [*icao_codes]
23
+ icao_codes.map!(&:upcase)
24
+
25
+ ret = {}
26
+ icao_codes.each do |icao_code|
27
+ ret[icao_code] = get_one(icao_code)
28
+ end
29
+ ret
30
+ end
31
+
32
+ def get_one(icao_code)
33
+ raise "Implement me!"
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ module VfrUtils
2
+ class Cache
3
+
4
+ def initialize(config)
5
+ @config = config
6
+ end
7
+
8
+ def get(k, &blk)
9
+ return get_files(k, &blk) if config.cache_backend == :files
10
+ raise "Unsupported cache backend: #{config.cache_backend}"
11
+ end
12
+
13
+ private
14
+
15
+ def config
16
+ @config
17
+ end
18
+
19
+ def get_files(k)
20
+ cache_file_path = "#{config.cache_directory}/#{k}.tmp"
21
+ if File.exists?(cache_file_path)
22
+ if Time.now.to_i - File.ctime(cache_file_path).to_i > config.cache_lifetime
23
+ FileUtils.rm_rf(cache_file_path)
24
+ else
25
+ return Marshal.load(File.read(cache_file_path))
26
+ end
27
+ end
28
+ v = yield
29
+ File.write(cache_file_path, Marshal.dump(v))
30
+ v
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,49 @@
1
+ require_relative '../vfr_utils'
2
+
3
+ module VfrUtils
4
+ module CLI
5
+
6
+ ALLOWED_ACTIONS = [
7
+ 'metar',
8
+ 'taf',
9
+ 'notam'
10
+ ]
11
+
12
+ def self.run(argv)
13
+ return usage if argv.empty?
14
+ action = argv[0].downcase
15
+ params = argv[1..-1]
16
+ return usage unless ALLOWED_ACTIONS.include? action
17
+ send(action.to_sym, params)
18
+ 0
19
+ end
20
+
21
+ def self.usage
22
+ STDERR.puts "Version: #{VfrUtils::VERSION} Usage: vfr-utils <action> <params>"
23
+ STDERR.puts ""
24
+ STDERR.puts "Allowed actions:"
25
+ ALLOWED_ACTIONS.each { |action| STDERR.puts " #{action} <space delimited list of ICAO codes>" }
26
+ STDERR.puts ""
27
+ 1
28
+ end
29
+
30
+ def self.notam(icao_codes)
31
+ require_relative 'notam'
32
+ require_relative 'formatter/notam'
33
+ VfrUtils::Formatter::NOTAM.pretty_display(VfrUtils::NOTAM.get(icao_codes))
34
+ end
35
+
36
+ def self.metar(icao_codes)
37
+ require_relative 'metar'
38
+ require_relative 'formatter/weather'
39
+ VfrUtils::Formatter::Weather.pretty_display(VfrUtils::METAR.get(icao_codes))
40
+ end
41
+
42
+ def self.taf(icao_codes)
43
+ require_relative 'taf'
44
+ require_relative 'formatter/weather'
45
+ VfrUtils::Formatter::Weather.pretty_display(VfrUtils::TAF.get(icao_codes))
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,29 @@
1
+ require 'tmpdir'
2
+
3
+ module VfrUtils
4
+ class Configuration
5
+ DEFAULTS = {
6
+ cache_lifetime: 900, # 900 secs = 15 minutes
7
+ cache_directory: "#{Dir.tmpdir}/vfr-utils",
8
+ cache_backend: :files,
9
+ redis_url: nil,
10
+ }
11
+ attr_accessor :cache_lifetime
12
+ attr_accessor :cache_directory
13
+ attr_accessor :cache_backend
14
+ attr_accessor :redis_url
15
+
16
+ def initialize(defaults=DEFAULTS)
17
+ @cache_lifetime = defaults[:cache_lifetime] || DEFAULTS[:cache_lifetime]
18
+ @cache_directory = defaults[:cache_directory] || DEFAULTS[:cache_directory]
19
+ @cache_backend = defaults[:cache_backend] || DEFAULTS[:cache_backend]
20
+ @redis_url = defaults[:redis_url] || DEFAULTS[:redis_url]
21
+ apply
22
+ end
23
+
24
+ def apply
25
+ FileUtils.mkdir_p @cache_directory if @cache_backend == :files
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,25 @@
1
+ require 'pp'
2
+
3
+ module VfrUtils
4
+ module Formatter
5
+ module NOTAM
6
+ def self.pretty_display(notams)
7
+ notams.each_pair do |icao_code, notams_for_aerodrome|
8
+ puts "=================================================="
9
+ puts "===================== #{icao_code} ====================="
10
+ notams_for_aerodrome.each do |notam_data|
11
+ puts "=================================================="
12
+ puts "Signature: #{notam_data[:signature]}" if notam_data[:signature]
13
+ puts "Valid from: #{notam_data[:valid_from].strftime("%F %H:%M (%A)")}" if notam_data[:valid_from]
14
+ puts "Valid to: #{notam_data[:valid_to].strftime("%F %H:%M (%A)")}" if notam_data[:valid_to]
15
+ puts ""
16
+ puts notam_data[:message]
17
+ puts ""
18
+ puts "Created at #{notam_data[:created_at].strftime("%F %H:%M")} by #{notam_data[:source]}" if notam_data[:created_at]
19
+ puts "" if notam_data[:created_at]
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ require 'pp'
2
+
3
+ module VfrUtils
4
+ module Formatter
5
+ module Weather
6
+ def self.pretty_display(data)
7
+ data.each_pair do |icao_code, data_for_aerodrome|
8
+ puts ""
9
+ #puts "=================================================="
10
+ puts "===================== #{icao_code} ====================="
11
+ puts data_for_aerodrome[:data]
12
+ puts "=================================================="
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,30 @@
1
+ module VfrUtils
2
+ class MasterConfiguration
3
+
4
+ def initialize(defaults={})
5
+ @defaults = defaults
6
+ @modules = {}
7
+ end
8
+
9
+ def method_missing(method_name, *args, &blk)
10
+ unless @modules[method_name]
11
+ @modules[method_name] = Configuration.new @defaults
12
+ self.class.class_exec do
13
+ define_method(method_name) { @modules[method_name] }
14
+ end
15
+ send(method_name, *args, &blk)
16
+ end
17
+ end
18
+
19
+ def global(values)
20
+ @defaults.merge! values
21
+ @modules.each_value do |m|
22
+ values.each_pair do |k,v|
23
+ key = "#{k}=".to_sym
24
+ m.send(key, v)
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,47 @@
1
+ require_relative '../vfr_utils'
2
+ require 'faraday'
3
+ require 'nokogiri'
4
+ require 'date'
5
+
6
+ module VfrUtils
7
+ class METAR < BaseService
8
+
9
+ URL='http://aviationweather.gov/metar/data'
10
+
11
+ class << self
12
+
13
+ def get_one(icao_code)
14
+ return cache.get("metar_#{icao_code}") do
15
+ html = fetch_from_web(icao_code)
16
+ parse(html)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def fetch_from_web(icao_code)
23
+ request_params = {
24
+ ids: icao_code,
25
+ format: 'raw',
26
+ hours: 0,
27
+ taf: 'off',
28
+ layout: 'off',
29
+ date: 0
30
+ }
31
+ response = Faraday.get URL, request_params
32
+ response.body
33
+ end
34
+
35
+ def parse(html)
36
+ html_doc = Nokogiri::HTML(html)
37
+ begin
38
+ data = html_doc.xpath("//div[@id='awc_main_content']//p").first.next_sibling.next_sibling.next_sibling.text
39
+ rescue
40
+ data = nil
41
+ end
42
+ { data: data }
43
+ end
44
+
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,88 @@
1
+ require_relative '../vfr_utils'
2
+ require 'faraday'
3
+ require 'nokogiri'
4
+ require 'date'
5
+
6
+ module VfrUtils
7
+ class NOTAM < BaseService
8
+
9
+ URL='https://www.notams.faa.gov/dinsQueryWeb/queryRetrievalMapAction.do'
10
+
11
+ class << self
12
+
13
+ def get_one(icao_code)
14
+ ret = []
15
+ return cache.get("notam_#{icao_code}") do
16
+ html = fetch_from_web(icao_code)
17
+ html_doc = Nokogiri::HTML(html)
18
+ html_doc.xpath("//td[@class='textBlack12']/pre").map(&:text).each do |raw_notam|
19
+ ret << parse(raw_notam)
20
+ end
21
+ ret
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def fetch_from_web(icao_code)
28
+ request_params = {
29
+ reportType: 'Raw',
30
+ retrieveLocId: icao_code,
31
+ actionType: 'notamRetrievalByICAOs',
32
+ submit: 'View NOTAMs'
33
+ }
34
+ response = Faraday.post URL, request_params
35
+ response.body
36
+ end
37
+
38
+ def parse(raw_notam)
39
+ ret = {
40
+ raw: raw_notam,
41
+ header: nil,
42
+ signature: nil,
43
+ icao_code: nil,
44
+ valid_from: nil,
45
+ valid_to: nil,
46
+ created_at: nil,
47
+ source: nil,
48
+ message: nil
49
+ }
50
+
51
+ lines = raw_notam.split("\n")
52
+ ret[:header] = lines[0]
53
+ ret[:created_at] = lines[-2].split(':', 2).last.strip # "17 Nov 2016 10:03:00"
54
+ ret[:created_at] = DateTime.parse(ret[:created_at])
55
+ ret[:source] = lines[-1].split(':', 2).last.strip
56
+ body = lines[1..-3].join(' ').gsub("\n", ' ')
57
+
58
+ if matches = body.match(/\AQ\)\s+(?<signature>.+)\s+A\)\s+(?<icao_code>.+)\s+B\)\s+(?<valid_from>.+)\s+C\)\s+(?<valid_to>.+)\s+E\)\s+(?<message>.+)\s*\z/)
59
+ ret[:signature] = matches[:signature]
60
+ ret[:icao_code] = matches[:icao_code]
61
+ ret[:valid_from] = parse_validity_time(matches[:valid_from])
62
+ ret[:valid_to] = parse_validity_time(matches[:valid_to])
63
+ ret[:message] = matches[:message]
64
+ else
65
+ body = ret[:header]+body
66
+ if matches = body.match(/\D(?<valid_from>\d{10})-(?<valid_to>\d{10}([A-Z]{3-5})?|PERM)/)
67
+ ret[:valid_from] = parse_validity_time(matches[:valid_from])
68
+ ret[:valid_to] = parse_validity_time(matches[:valid_to])
69
+ body.gsub!(matches[0], '')
70
+ end
71
+ ret[:message] = body
72
+ end
73
+ ret
74
+ end
75
+
76
+ def parse_validity_time(text)
77
+ return DateTime.new(2100) if text == 'PERM'
78
+ year = text[0..1].to_i+2000
79
+ month = text[2..3].to_i
80
+ day = text[4..5].to_i
81
+ hour = text[6..7].to_i
82
+ minute = text[8..9].to_i
83
+ zone = text[10..15]
84
+ DateTime.parse("#{year}-#{month}-#{day} #{hour}:#{minute}:00#{zone}")
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,46 @@
1
+ require_relative '../vfr_utils'
2
+ require 'faraday'
3
+ require 'nokogiri'
4
+ require 'date'
5
+
6
+ module VfrUtils
7
+ class TAF < BaseService
8
+
9
+ URL='http://aviationweather.gov/taf/data'
10
+
11
+ class << self
12
+
13
+ def get_one(icao_code)
14
+ return cache.get("taf_#{icao_code}") do
15
+ html = fetch_from_web(icao_code)
16
+ parse(html)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def fetch_from_web(icao_code)
23
+ # http://aviationweather.gov/taf/data?ids=EPWR&format=raw&metars=off&layout=off
24
+ request_params = {
25
+ ids: icao_code,
26
+ format: 'raw',
27
+ metars: 'off',
28
+ layout: 'off',
29
+ }
30
+ response = Faraday.get URL, request_params
31
+ response.body
32
+ end
33
+
34
+ def parse(html)
35
+ html_doc = Nokogiri::HTML(html)
36
+ begin
37
+ data = html_doc.xpath("//code").first.inner_html.gsub('<br>', "\n")
38
+ rescue
39
+ data = nil
40
+ end
41
+ { data: data }
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module VfrUtils
2
+ VERSION='0.0.3'
3
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vfr_utils
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Bartek Wilczek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-12-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.9'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.9'
41
+ description: This gem provides set of classes and a CLI for obtaining information
42
+ like NOTAM, METAR and TAF
43
+ email:
44
+ - bwilczek@gmail.com
45
+ executables:
46
+ - vfr_utils
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - bin/vfr_utils
51
+ - lib/vfr_utils.rb
52
+ - lib/vfr_utils/base_service.rb
53
+ - lib/vfr_utils/cache.rb
54
+ - lib/vfr_utils/cli.rb
55
+ - lib/vfr_utils/configuration.rb
56
+ - lib/vfr_utils/formatter/notam.rb
57
+ - lib/vfr_utils/formatter/weather.rb
58
+ - lib/vfr_utils/master_configuration.rb
59
+ - lib/vfr_utils/metar.rb
60
+ - lib/vfr_utils/notam.rb
61
+ - lib/vfr_utils/taf.rb
62
+ - lib/vfr_utils/version.rb
63
+ homepage: https://github.com/bwilczek/vfr-utils
64
+ licenses:
65
+ - MIT
66
+ metadata: {}
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 2.5.1
84
+ signing_key:
85
+ specification_version: 4
86
+ summary: Basic toolkit for obtaining aeronautical data
87
+ test_files: []