regentanz 0.3.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +268 -0
- data/bin/regentanz +16 -0
- data/lib/regentanz.rb +10 -11
- data/lib/regentanz/cli/common.rb +35 -0
- data/lib/regentanz/cli/compare.rb +85 -0
- data/lib/regentanz/cli/compile.rb +27 -0
- data/lib/regentanz/template_compiler.rb +263 -0
- data/lib/regentanz/version.rb +1 -2
- data/lib/regentanz/yaml-ext.rb +18 -0
- data/spec/regentanz/resources/test/unloaded.rb +11 -0
- data/spec/regentanz/template_compiler_spec.rb +692 -0
- data/spec/spec_helper.rb +2 -0
- metadata +45 -152
- data/.gitignore +0 -5
- data/.rvmrc +0 -4
- data/CHANGELOG.rdoc +0 -26
- data/Gemfile +0 -4
- data/LICENSE +0 -24
- data/README.rdoc +0 -54
- data/Rakefile +0 -23
- data/lib/regentanz/astronomy.rb +0 -69
- data/lib/regentanz/cache.rb +0 -2
- data/lib/regentanz/cache/base.rb +0 -51
- data/lib/regentanz/cache/file.rb +0 -86
- data/lib/regentanz/callbacks.rb +0 -18
- data/lib/regentanz/conditions.rb +0 -3
- data/lib/regentanz/conditions/base.rb +0 -16
- data/lib/regentanz/conditions/current.rb +0 -14
- data/lib/regentanz/conditions/forecast.rb +0 -14
- data/lib/regentanz/configuration.rb +0 -55
- data/lib/regentanz/configurator.rb +0 -22
- data/lib/regentanz/google_weather.rb +0 -151
- data/lib/regentanz/parser.rb +0 -1
- data/lib/regentanz/parser/google_weather.rb +0 -100
- data/lib/regentanz/test_helper.rb +0 -52
- data/regentanz.gemspec +0 -30
- data/test/factories.rb +0 -6
- data/test/support/tmp/.gitignore +0 -1
- data/test/support/valid_response.xml.erb +0 -26
- data/test/test_helper.rb +0 -14
- data/test/unit/astronomy_test.rb +0 -26
- data/test/unit/cache/base_test.rb +0 -53
- data/test/unit/cache/file_test.rb +0 -141
- data/test/unit/callbacks_test.rb +0 -27
- data/test/unit/configuration_test.rb +0 -57
- data/test/unit/current_condition_test.rb +0 -33
- data/test/unit/forecast_condition_test.rb +0 -35
- data/test/unit/google_weather_test.rb +0 -125
- data/test/unit/parser/google_weather_parser_test.rb +0 -71
data/lib/regentanz/cache.rb
DELETED
data/lib/regentanz/cache/base.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
module Regentanz
|
2
|
-
|
3
|
-
module Cache
|
4
|
-
|
5
|
-
class Base
|
6
|
-
|
7
|
-
# Checks if +instance_or_class+ complies to cache backend duck type,
|
8
|
-
# ie. if it reponds to all mandatory methods
|
9
|
-
def self.lint(instance_or_class)
|
10
|
-
instance = instance_or_class.is_a?(Class) ? instance_or_class.new : instance_or_class
|
11
|
-
[:set, :get, :available?, :expire!, :valid?].inject(true) do |memo, method|
|
12
|
-
memo && instance.respond_to?(method)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
# Returns a alpha-numeric cache key
|
17
|
-
def self.sanitize_key(key)
|
18
|
-
Digest::SHA1.hexdigest(key.to_s)
|
19
|
-
end
|
20
|
-
|
21
|
-
# Stores cache +value+ as +key+.
|
22
|
-
def set(key, value); end
|
23
|
-
|
24
|
-
# Retrieves cached value from +key+.
|
25
|
-
def get(key); end
|
26
|
-
|
27
|
-
# Checks if cache under +key+ is available.
|
28
|
-
def available?(key); end
|
29
|
-
|
30
|
-
# Deletes cache under +key+.
|
31
|
-
def expire!(key); end
|
32
|
-
|
33
|
-
# Checks if cache under +key+ is still valid.
|
34
|
-
def valid?(key); end
|
35
|
-
|
36
|
-
# Returns whether or not weather retrieval from the API
|
37
|
-
# is currently waiting for a timeout to expire
|
38
|
-
def waiting_for_retry?; end
|
39
|
-
|
40
|
-
# Checks if we've waited enough. Unsets a possible retry
|
41
|
-
# state (and returns true) if so or returns false if not
|
42
|
-
def unset_retry_state!; end
|
43
|
-
|
44
|
-
# Persists a timeout state
|
45
|
-
def set_retry_state!; end
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
end
|
data/lib/regentanz/cache/file.rb
DELETED
@@ -1,86 +0,0 @@
|
|
1
|
-
module Regentanz
|
2
|
-
|
3
|
-
module Cache
|
4
|
-
|
5
|
-
# Implements file-based caching. Cache files are stored in
|
6
|
-
# +Regentanz.configuration.cache_dir+ and are prefixed with
|
7
|
-
# +Regentanz.configuration.cache_prefix+.
|
8
|
-
class File < Regentanz::Cache::Base
|
9
|
-
|
10
|
-
# Cache is available (n.b. not necessarily #valid?) if a file
|
11
|
-
# exists for +key+
|
12
|
-
def available?(key)
|
13
|
-
::File.exists?(filename(key)) rescue nil
|
14
|
-
end
|
15
|
-
|
16
|
-
# Unlinks the cache file for +key+
|
17
|
-
def expire!(key)
|
18
|
-
return false unless available?(key)
|
19
|
-
begin
|
20
|
-
::File.delete(filename(key))
|
21
|
-
true
|
22
|
-
rescue
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def filename(key)
|
27
|
-
::File.join(Regentanz.configuration.cache_dir, "#{Regentanz.configuration.cache_prefix}_#{key}.xml")
|
28
|
-
end
|
29
|
-
|
30
|
-
# Retrieves content of #filename for +key+
|
31
|
-
def get(key)
|
32
|
-
if available?(key)
|
33
|
-
::File.open(filename(key), "r") { |file| file.read } rescue nil
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
# Stores +value+ in #filename for +key+
|
38
|
-
def set(key, value)
|
39
|
-
begin
|
40
|
-
::File.open(filename(key), "w") { |file| file.puts value }
|
41
|
-
filename(key)
|
42
|
-
rescue
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def valid?(key)
|
47
|
-
return false unless available?(key)
|
48
|
-
begin
|
49
|
-
# TODO delegate XML parsing and verification
|
50
|
-
doc = REXML::Document.new(get(key))
|
51
|
-
node = doc.elements["xml_api_reply/weather/forecast_information/current_date_time"]
|
52
|
-
time = node.attribute("data").to_s.to_time if node
|
53
|
-
time > Regentanz.configuration.cache_ttl.seconds.ago
|
54
|
-
rescue
|
55
|
-
# TODO pass exception upstream until properly delegated in the first place?
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# Returns whether or not weather retrieval from the API
|
60
|
-
# is currently waiting for a timeout to expire; here: existence of
|
61
|
-
# a retry marker file
|
62
|
-
def waiting_for_retry?
|
63
|
-
::File.exists?(Regentanz.configuration.retry_marker)
|
64
|
-
end
|
65
|
-
|
66
|
-
# Checks if we've waited long enough. Deletes a possible retry
|
67
|
-
# marker file (and returns true) if so or returns false if not
|
68
|
-
def unset_retry_state!
|
69
|
-
marker = Regentanz.configuration.retry_marker
|
70
|
-
if waiting_for_retry? and ::File.new(marker).mtime < Regentanz.configuration.retry_ttl.seconds.ago
|
71
|
-
::File.delete(marker) if ::File.exists?(marker)
|
72
|
-
true
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
# Persists the timeout state by writing a retry_marker file
|
77
|
-
def set_retry_state!
|
78
|
-
::File.open(Regentanz.configuration.retry_marker, "w+").close
|
79
|
-
waiting_for_retry?
|
80
|
-
end
|
81
|
-
|
82
|
-
end
|
83
|
-
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
data/lib/regentanz/callbacks.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
module Regentanz
|
2
|
-
module Callbacks
|
3
|
-
|
4
|
-
CALLBACKS = [:api_failure_detected, :api_failure_resumed]
|
5
|
-
|
6
|
-
CALLBACKS.each do |callback_method|
|
7
|
-
# Define no-op stubs for all CALLBACKS
|
8
|
-
define_method(callback_method) {}
|
9
|
-
private callback_method
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.included(base)
|
13
|
-
base.send :include, ActiveSupport::Callbacks
|
14
|
-
base.define_callbacks *CALLBACKS
|
15
|
-
end
|
16
|
-
|
17
|
-
end
|
18
|
-
end
|
data/lib/regentanz/conditions.rb
DELETED
@@ -1,16 +0,0 @@
|
|
1
|
-
module Regentanz
|
2
|
-
module Conditions
|
3
|
-
class Base
|
4
|
-
|
5
|
-
attr_accessor :condition, :style, :icon
|
6
|
-
|
7
|
-
def initialize(attributes = {})
|
8
|
-
attributes.symbolize_keys!
|
9
|
-
attributes.keys.each do |attr|
|
10
|
-
self.send(:"#{attr}=", attributes[attr]) if respond_to?(:"#{attr}=")
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
module Regentanz
|
2
|
-
module Conditions
|
3
|
-
|
4
|
-
class Current < Regentanz::Conditions::Base
|
5
|
-
|
6
|
-
attr_reader :temp_c, :temp_f
|
7
|
-
attr_accessor :humidity, :wind_condition
|
8
|
-
|
9
|
-
def temp_c=temp; @temp_c = temp.to_i; end
|
10
|
-
def temp_f=temp; @temp_f = temp.to_i; end
|
11
|
-
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,55 +0,0 @@
|
|
1
|
-
module Regentanz
|
2
|
-
require "tmpdir"
|
3
|
-
|
4
|
-
class Configuration
|
5
|
-
|
6
|
-
DEFAULT_OPTIONS = [
|
7
|
-
:base_url,
|
8
|
-
:cache_backend,
|
9
|
-
:cache_dir,
|
10
|
-
:cache_prefix,
|
11
|
-
:cache_ttl,
|
12
|
-
:retry_marker,
|
13
|
-
:retry_ttl
|
14
|
-
]
|
15
|
-
|
16
|
-
OPTIONS = DEFAULT_OPTIONS + [
|
17
|
-
:do_not_get_weather,
|
18
|
-
:suppress_stderr_output
|
19
|
-
]
|
20
|
-
|
21
|
-
# Define default values
|
22
|
-
@@default_base_url = "http://www.google.com/ig/api"
|
23
|
-
@@default_cache_backend = Regentanz::Cache::File
|
24
|
-
@@default_cache_dir = Dir.tmpdir
|
25
|
-
@@default_cache_prefix = "regentanz"
|
26
|
-
@@default_cache_ttl = 14400 # 4 hours
|
27
|
-
@@default_retry_ttl = 3600 # 1 hour
|
28
|
-
@@default_retry_marker = File.join(@@default_cache_dir, "#{@@default_cache_prefix}_api_retry.txt")
|
29
|
-
|
30
|
-
OPTIONS.each { |opt| attr_accessor(opt) }
|
31
|
-
DEFAULT_OPTIONS.each { |cvar| cattr_reader(:"default_#{cvar}", :instance_reader => false) } # class getter for all DEFAULT_OPTION cvars
|
32
|
-
|
33
|
-
# Stores global configuration information for +Regentanz+.
|
34
|
-
#
|
35
|
-
# == Default Options
|
36
|
-
# * +base_url+: HTTP API, request-specific calls will be appended (default: +http://www.google.com/ig/api+)
|
37
|
-
# * +cache_backend+: defaults to +Regentanz::Cache::File+
|
38
|
-
# * +cache_dir+: defaults to +Dir.tmpdir+
|
39
|
-
# * +cache_prefix+: String to prefix both cache file and retry_marker (if not specified otherwise) with
|
40
|
-
# * +cache_ttl+: time in seconds for which cache data is considered valid. Default: 14400 (4 hours).
|
41
|
-
# * +retry_ttl+: time in seconds Regentanz should wait until it tries to call the API again when it failed before. Default: 3600 (1 hour).
|
42
|
-
# * +retry_marker+: persist a marker-file here to indicate a failed API state. Supply a full pathname.
|
43
|
-
#
|
44
|
-
# == Options
|
45
|
-
# * +do_not_get_weather+: don't try to retrieve weather data, neither from cache nor remote API. Intended for testing.
|
46
|
-
# * +suppress_stderr_output+: called from GoogleWeather#error_output, silences Regentanz' output. Intended for testing.
|
47
|
-
def initialize(*args)
|
48
|
-
DEFAULT_OPTIONS.each do |option|
|
49
|
-
self.send(:"#{option}=", self.class.send(:class_variable_get, :"@@default_#{option}") )
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module Regentanz
|
2
|
-
class << self
|
3
|
-
|
4
|
-
attr_writer :configuration
|
5
|
-
def configuration #:nodoc:
|
6
|
-
@configuration ||= Configuration.new
|
7
|
-
end
|
8
|
-
|
9
|
-
# Call this method to modify defaults in your initializers.
|
10
|
-
# See Regentanz::Configuration for supported config options.
|
11
|
-
#
|
12
|
-
# === Example usage
|
13
|
-
# Regentanz.configure do |config|
|
14
|
-
# config.<supported_config_option> = :bar
|
15
|
-
# end
|
16
|
-
def configure
|
17
|
-
self.configuration ||= Configuration.new
|
18
|
-
yield(configuration)
|
19
|
-
end
|
20
|
-
|
21
|
-
end
|
22
|
-
end
|
@@ -1,151 +0,0 @@
|
|
1
|
-
require 'cgi'
|
2
|
-
require 'net/http'
|
3
|
-
require 'rexml/document'
|
4
|
-
require 'ostruct'
|
5
|
-
|
6
|
-
module Regentanz
|
7
|
-
class GoogleWeather
|
8
|
-
include Astronomy
|
9
|
-
include Callbacks
|
10
|
-
|
11
|
-
attr_accessor :location, :cache_id
|
12
|
-
attr_reader :cache, :current, :forecast, :lang, :parser, :xml
|
13
|
-
|
14
|
-
# Creates an object and queries the weather API.
|
15
|
-
#
|
16
|
-
# === Parameters
|
17
|
-
# * +options+ A hash
|
18
|
-
#
|
19
|
-
# === Available options
|
20
|
-
# * +location+ String to pass to Google's weather API (mandatory)
|
21
|
-
# * +cache_id+ enables caching with a unique identifier to locate a cached file
|
22
|
-
# * +geodata+ a hash with keys :lat and :lng, required for sunset/-rise calculations
|
23
|
-
# * +lang+ Desired language for the returned results (Defaults to "de")
|
24
|
-
def initialize(*args)
|
25
|
-
options = args.extract_options!
|
26
|
-
@options = options.symbolize_keys
|
27
|
-
@parser = Parser::GoogleWeather.new
|
28
|
-
self.location = args.first || options[:location]
|
29
|
-
self.lang = options[:lang]
|
30
|
-
|
31
|
-
# Activate caching
|
32
|
-
if Regentanz.configuration.cache_backend
|
33
|
-
@cache = Regentanz.configuration.cache_backend.new if Regentanz.configuration.cache_backend
|
34
|
-
@cache_id = options[:cache_id] || Regentanz.configuration.cache_backend.sanitize_key(@location)
|
35
|
-
end
|
36
|
-
|
37
|
-
@geodata = options[:geodata] if options[:geodata] and options[:geodata][:lat] and options[:geodata][:lng]
|
38
|
-
get_weather() unless Regentanz.configuration.do_not_get_weather
|
39
|
-
end
|
40
|
-
|
41
|
-
# Loads weather data from known data sources (ie. cache or external API)
|
42
|
-
def get_weather!; get_weather(); end
|
43
|
-
|
44
|
-
# Input sanitizer-setter, defaults to "de"
|
45
|
-
def lang=(lang)
|
46
|
-
@lang = lang.present? ? lang.to_s : "de"
|
47
|
-
end
|
48
|
-
|
49
|
-
# Provide an accessor to see if we actually got weather info at all.
|
50
|
-
def present?
|
51
|
-
current.present? or forecast.present?
|
52
|
-
end
|
53
|
-
|
54
|
-
def sunrise
|
55
|
-
@sunrise ||= @geodata.blank? ? nil : sun_rise_set(:sunrise, @geodata[:lat], @geodata[:lng])
|
56
|
-
end
|
57
|
-
|
58
|
-
def sunset
|
59
|
-
@sunset ||= @geodata.blank? ? nil : sun_rise_set(:sunset, @geodata[:lat], @geodata[:lng])
|
60
|
-
end
|
61
|
-
|
62
|
-
def waiting_for_retry?
|
63
|
-
@cache && @cache.waiting_for_retry?
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
# Encapsulate output of error messages. Will output to $stderr unless
|
69
|
-
# Regentanz.configuration.suppress_stderr_output is set
|
70
|
-
#
|
71
|
-
# === Parameters
|
72
|
-
# * +output+: String to output
|
73
|
-
def error_output(output)
|
74
|
-
$stderr.puts output unless Regentanz.configuration.suppress_stderr_output
|
75
|
-
end
|
76
|
-
|
77
|
-
# Proxies +do_request+ and +parse_request+
|
78
|
-
def get_weather
|
79
|
-
if cache_valid?
|
80
|
-
@xml = @parser.convert_encoding(@cache.get(@cache_id))
|
81
|
-
else
|
82
|
-
@xml = @parser.convert_encoding(do_request(Regentanz.configuration.base_url + "?weather=#{CGI::escape(@location)}&hl=#{@lang}"))
|
83
|
-
if @cache
|
84
|
-
@cache.expire!(@cache_id)
|
85
|
-
@cache.set(@cache_id, @xml)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
parse_xml() if @xml
|
89
|
-
end
|
90
|
-
|
91
|
-
# Makes an outbound HTTP-request and returns the request body (ie. the XML)
|
92
|
-
#
|
93
|
-
# === Parameters
|
94
|
-
# * +url+ API-URL with protocol, fqdn and URI
|
95
|
-
def do_request(url)
|
96
|
-
begin
|
97
|
-
Net::HTTP.get_response(URI.parse(url)).body
|
98
|
-
rescue => e
|
99
|
-
error_output(e.message)
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
# Parses the raw data and Sets the +current+ and +forecast+ variables.
|
104
|
-
#
|
105
|
-
# Assumes the instance var +xml+ is set.
|
106
|
-
def parse_xml
|
107
|
-
begin
|
108
|
-
@current = @parser.parse_current!(@xml)
|
109
|
-
@forecast = @parser.parse_forecast!(@xml)
|
110
|
-
rescue REXML::ParseException => e
|
111
|
-
error_output(e.message)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
# Returns +true+ if a given cache file contains data not older than Regentanz::Configuration#cache_ttl seconds
|
116
|
-
# TODO properly use cache backend's #valid?
|
117
|
-
def cache_valid?
|
118
|
-
validity = false
|
119
|
-
if @cache and @cache.available?(@cache_id)
|
120
|
-
begin
|
121
|
-
doc = REXML::Document.new(@cache.get(@cache_id))
|
122
|
-
node = doc.elements["xml_api_reply/weather/forecast_information/current_date_time"]
|
123
|
-
time = node.attribute("data").to_s.to_time if node
|
124
|
-
validity = time ? time > Regentanz.configuration.cache_ttl.seconds.ago : false
|
125
|
-
rescue REXML::ParseException
|
126
|
-
retry_after_incorrect_api_reply
|
127
|
-
validity = waiting_for_retry? # not really valid, but we need to wait a bit.
|
128
|
-
end
|
129
|
-
end
|
130
|
-
validity
|
131
|
-
end
|
132
|
-
|
133
|
-
# Brings the outbound API-calls to a halt for Regentanz::Configuration#retry_ttl seconds by creating
|
134
|
-
# a marker file. Flushes the incorrect cached API response after Regentanz::Configuration#retry_ttl
|
135
|
-
# seconds.
|
136
|
-
def retry_after_incorrect_api_reply
|
137
|
-
if !waiting_for_retry? and @cache
|
138
|
-
# We are run for the first time, create the marker file
|
139
|
-
api_failure_detected # callback
|
140
|
-
# TODO execute custom callback
|
141
|
-
@cache.set_retry_state!
|
142
|
-
elsif @cache and @cache.unset_retry_state!
|
143
|
-
# Marker file is old enough, delete the (invalid) cache file and remove the marker_file
|
144
|
-
@cache.expire!(@cache_id)
|
145
|
-
api_failure_resumed # callback
|
146
|
-
# TODO execute custom callback
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
end
|
151
|
-
end
|