regentanz 0.3.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +268 -0
  3. data/bin/regentanz +16 -0
  4. data/lib/regentanz.rb +10 -11
  5. data/lib/regentanz/cli/common.rb +35 -0
  6. data/lib/regentanz/cli/compare.rb +85 -0
  7. data/lib/regentanz/cli/compile.rb +27 -0
  8. data/lib/regentanz/template_compiler.rb +263 -0
  9. data/lib/regentanz/version.rb +1 -2
  10. data/lib/regentanz/yaml-ext.rb +18 -0
  11. data/spec/regentanz/resources/test/unloaded.rb +11 -0
  12. data/spec/regentanz/template_compiler_spec.rb +692 -0
  13. data/spec/spec_helper.rb +2 -0
  14. metadata +45 -152
  15. data/.gitignore +0 -5
  16. data/.rvmrc +0 -4
  17. data/CHANGELOG.rdoc +0 -26
  18. data/Gemfile +0 -4
  19. data/LICENSE +0 -24
  20. data/README.rdoc +0 -54
  21. data/Rakefile +0 -23
  22. data/lib/regentanz/astronomy.rb +0 -69
  23. data/lib/regentanz/cache.rb +0 -2
  24. data/lib/regentanz/cache/base.rb +0 -51
  25. data/lib/regentanz/cache/file.rb +0 -86
  26. data/lib/regentanz/callbacks.rb +0 -18
  27. data/lib/regentanz/conditions.rb +0 -3
  28. data/lib/regentanz/conditions/base.rb +0 -16
  29. data/lib/regentanz/conditions/current.rb +0 -14
  30. data/lib/regentanz/conditions/forecast.rb +0 -14
  31. data/lib/regentanz/configuration.rb +0 -55
  32. data/lib/regentanz/configurator.rb +0 -22
  33. data/lib/regentanz/google_weather.rb +0 -151
  34. data/lib/regentanz/parser.rb +0 -1
  35. data/lib/regentanz/parser/google_weather.rb +0 -100
  36. data/lib/regentanz/test_helper.rb +0 -52
  37. data/regentanz.gemspec +0 -30
  38. data/test/factories.rb +0 -6
  39. data/test/support/tmp/.gitignore +0 -1
  40. data/test/support/valid_response.xml.erb +0 -26
  41. data/test/test_helper.rb +0 -14
  42. data/test/unit/astronomy_test.rb +0 -26
  43. data/test/unit/cache/base_test.rb +0 -53
  44. data/test/unit/cache/file_test.rb +0 -141
  45. data/test/unit/callbacks_test.rb +0 -27
  46. data/test/unit/configuration_test.rb +0 -57
  47. data/test/unit/current_condition_test.rb +0 -33
  48. data/test/unit/forecast_condition_test.rb +0 -35
  49. data/test/unit/google_weather_test.rb +0 -125
  50. data/test/unit/parser/google_weather_parser_test.rb +0 -71
@@ -1,2 +0,0 @@
1
- require 'regentanz/cache/base'
2
- require 'regentanz/cache/file'
@@ -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
@@ -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
@@ -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
@@ -1,3 +0,0 @@
1
- require 'regentanz/conditions/base'
2
- require 'regentanz/conditions/current'
3
- require 'regentanz/conditions/forecast'
@@ -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,14 +0,0 @@
1
- module Regentanz
2
- module Conditions
3
-
4
- class Forecast < Regentanz::Conditions::Base
5
-
6
- attr_reader :high, :low
7
- attr_accessor :day_of_week
8
-
9
- def high=temp; @high = temp.to_i; end
10
- def low=temp; @low = 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