regentanz 0.3.3 → 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.
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