carbon 1.1.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/.gitignore +4 -22
  2. data/CHANGELOG +11 -0
  3. data/Gemfile +10 -1
  4. data/README.markdown +185 -0
  5. data/Rakefile +13 -26
  6. data/bin/carbon +3 -3
  7. data/carbon.gemspec +17 -23
  8. data/developer_notes/MULTI.markdown +25 -0
  9. data/developer_notes/REDUCE_HTTP_CONNECTIONS.markdown +46 -0
  10. data/features/shell.feature +1 -1
  11. data/features/support/env.rb +3 -4
  12. data/lib/carbon.rb +242 -96
  13. data/lib/carbon/registry.rb +50 -0
  14. data/lib/carbon/shell.rb +14 -8
  15. data/lib/carbon/shell/emitter.rb +33 -29
  16. data/lib/carbon/version.rb +1 -1
  17. data/test/carbon_test.rb +167 -0
  18. metadata +128 -182
  19. data/MIT-LICENSE.txt +0 -19
  20. data/README.rdoc +0 -266
  21. data/doc/INTEGRATION_GUIDE.rdoc +0 -1002
  22. data/doc/examining-response-with-jsonview.png +0 -0
  23. data/doc/shell_example +0 -43
  24. data/doc/timeout-error.png +0 -0
  25. data/doc/with-committee-reports.png +0 -0
  26. data/doc/without-committee-reports.png +0 -0
  27. data/lib/carbon/base.rb +0 -62
  28. data/lib/carbon/emission_estimate.rb +0 -165
  29. data/lib/carbon/emission_estimate/request.rb +0 -100
  30. data/lib/carbon/emission_estimate/response.rb +0 -61
  31. data/lib/carbon/emission_estimate/storage.rb +0 -33
  32. data/spec/fixtures/vcr_cassettes/flight.yml +0 -47
  33. data/spec/fixtures/vcr_cassettes/residence.yml +0 -44
  34. data/spec/lib/carbon/emission_estimate/request_spec.rb +0 -41
  35. data/spec/lib/carbon/emission_estimate/response_spec.rb +0 -33
  36. data/spec/lib/carbon/emission_estimate_spec.rb +0 -32
  37. data/spec/lib/carbon_spec.rb +0 -384
  38. data/spec/spec_helper.rb +0 -60
  39. data/spec/specwatchr +0 -60
data/doc/shell_example DELETED
@@ -1,43 +0,0 @@
1
- carbon> key '123abc'
2
- => Using key 123abc
3
- carbon> help
4
- => flight, automobile, ...
5
- carbon> flight
6
- => 974 kg CO2e
7
- flight> lbs
8
- => 123 lbs CO2e
9
- flight> tons
10
- => 1 ton CO2e
11
- flight> help
12
- => origin_airport, destination_airport . . .
13
- flight> origin_airport 'lax'
14
- => 1123 kg CO2e
15
- flight> destination_airport 'jfk'
16
- => 1375 kg CO2e
17
- flight> characteristics
18
- => Origin airport: JFK
19
- Destination airport: JFK
20
- flight> methodology
21
- => Emission: from foo and bar
22
- Foo: from baz
23
- Bar: default
24
- flight> url
25
- => http://carbon.brighterplanet.com/flights?key=123abc&origin_airport[iata_code]=lax&destination_airport[iata_code]=jfk
26
- flight> done
27
- => Origin airport: JFK
28
- Destination airport: JFK
29
- 1375 kg CO2e
30
- http://carbon.brighterplanet.com/flights?key=123abc&origin_airport[iata_code]=lax&destination_airport[iata_code]=jfk
31
- Saved as flight #1
32
- carbon> flight 1
33
- => Origin airport: JFK
34
- Destination airport: JFK
35
- 1375 kg CO2e
36
- flight> done
37
- => Origin airport: JFK
38
- Destination airport: JFK
39
- 1375 kg CO2e
40
- http://carbon.brighterplanet.com/flights?key=123abc&origin_airport[iata_code]=lax&destination_airport[iata_code]=jfk
41
- Saved as flight #1
42
- carbon> exit
43
- $
Binary file
Binary file
Binary file
data/lib/carbon/base.rb DELETED
@@ -1,62 +0,0 @@
1
- module Carbon
2
- # You will probably never access this class directly. Instead, you'll touch it through the DSL.
3
- #
4
- # An instance of this appears on any class that includes <tt>Carbon</tt>.
5
- class Base
6
- include Blockenspiel::DSL
7
- attr_reader :emitter_common_name
8
-
9
- def initialize(emitter_common_name)
10
- @emitter_common_name = emitter_common_name.to_s
11
- end
12
-
13
- # A completed translation table will look like:
14
- # {[:mixer, :size]=>"mixer_size",
15
- # :personnel=>:employees,
16
- # :smokestack_size=>"smokestack_size",
17
- # :oven_count=>"oven_count",
18
- # [:mixer, :wattage]=>"mixer_wattage"}
19
- def translation_table # :nodoc:
20
- @translation_table ||= Hash.new
21
- end
22
-
23
- def reset_translation_table! #:nodoc:
24
- @translation_table = Hash.new
25
- end
26
-
27
- # Indicate that you will send in a piece of data about the emitter.
28
- #
29
- # Two general rules:
30
- # * Take note of what Brighter Planet expects to receive. If you send <tt>fuel_economy</tt> and we're expecting <tt>fuel_efficiency</tt>, we won't understand! (Hint: use the <tt>:as</tt> option.)
31
- # * Make sure <tt>#to_characteristic</tt> or <tt>#to_param</tt> is set up. The gem always calls one of these (it will try <tt>#to_characteristic</tt> first) before sending, so that's your change to key things by "EPA code" (fictional) or whatever else you want. (Hint: use the <tt>:key</tt> option.)
32
- #
33
- # There are two optional parameters:
34
- # * <tt>:as</tt> - if Brighter Planet expects <tt>fuel_efficiency</tt>, and you say <tt>mpg</tt>, you can write <tt>provide :mpg, :as => :fuel_efficiency</tt>. This will result in a query like <tt>?fuel_efficiency=XYZ</tt>.
35
- # * <tt>:key</tt> - if Brighter Planet expects a make's name ("Nissan") and you want to look things up by (fictional) "EPA code", you could say <tt>provide :make, :key => :epa_code</tt>. This will result in a query like <tt>?make[epa_code]=ABC</tt>.
36
- #
37
- # # What's sent to Brighter Planet
38
- # emit_as :automobile do
39
- # provide :mpg, :as => :fuel_efficiency # fuel_efficiency=my_car.mpg.to_param
40
- # provide :make, :key => :epa_code # make[epa_code]=my_car.make.to_param
41
- # # or really shaking things up...
42
- # provide :manufacturer, :as => :make, :key => :epa_code # make[epa_code]=my_car.manufacturer.to_param
43
- # end
44
- #
45
- # Note that no matter what you send to us, the gem always calls <b><tt>#to_characteristic</tt></b> (or, failing that, <tt>#to_param</tt>) on the emitter. In this example, it's up to you to make sure my_car.manufacturer.to_param returns an epa_code.
46
- def provide(attr_name, options = {})
47
- options = options.symbolize_keys
48
- characteristic = if options.has_key? :as
49
- # [ :mpg, :fuel_efficiency ]
50
- [attr_name, options[:as]]
51
- else
52
- # :make
53
- attr_name
54
- end
55
- # translation_table[:make] = 'epa_code'
56
- translation_table[characteristic] = options[:key]
57
- end
58
-
59
- # Third-person singular preferred.
60
- alias :provides :provide
61
- end
62
- end
@@ -1,165 +0,0 @@
1
- module Carbon
2
- # Let's start off by saying that realtime <tt>EmissionEstimate</tt> objects quack like numbers.
3
- #
4
- # If you ask for a callback, on the other hand, you can't use them as numbers.
5
- #
6
- # So, you can just say <tt>my_car.emission_estimate.to_s</tt> and you'll get something like <tt>"4308.29"</tt>.
7
- #
8
- # At the same time, they contain all the data you get back from the emission estimate web service. For example, you could say <tt>puts my_donut_factor.emission_estimate.oven_count</tt> (see the tests) and you'd get back the oven count used in the calculation, if any.
9
- class EmissionEstimate
10
- autoload :Response, 'carbon/emission_estimate/response'
11
- autoload :Request, 'carbon/emission_estimate/request'
12
- autoload :Storage, 'carbon/emission_estimate/storage'
13
-
14
- def self.parse(str) #:nodoc:
15
- data = ::ActiveSupport::JSON.decode str
16
- if data.has_key? 'active_subtimeframe'
17
- interval = "#{data['active_subtimeframe']['startDate']}/#{data['active_subtimeframe']['endDate']}"
18
- data['active_subtimeframe'] = Timeframe.interval(interval)
19
- end
20
- data['updated_at'] = ::Time.parse(data['updated_at']) if data.has_key?('updated_at') and data['updated_at'].is_a?(::String)
21
- data
22
- end
23
-
24
- attr_writer :callback_content_type
25
- attr_writer :key
26
- attr_writer :timeout
27
- attr_writer :defer
28
- attr_accessor :callback
29
- attr_accessor :timeframe
30
- attr_accessor :certified
31
- attr_accessor :guid
32
- attr_accessor :comply
33
- attr_reader :emitter
34
-
35
- def initialize(emitter, options = {})
36
- @emitter = emitter
37
- take_options options unless options.empty?
38
- end
39
-
40
- VALID_OPTIONS = [:callback_content_type, :key, :callback, :timeframe, :guid, :timeout, :defer, :certified, :comply]
41
-
42
- def take_options(options) #:nodoc:
43
- return if options.blank?
44
- options.slice(*VALID_OPTIONS).each do |k, v|
45
- instance_variable_set "@#{k}", v
46
- end
47
- end
48
-
49
- # I can be compared directly to a number, unless I'm an async request.
50
- def ==(other)
51
- if other.is_a? ::Numeric and mode == :realtime
52
- other == number
53
- else
54
- super
55
- end
56
- end
57
-
58
- # You can ask an EmissionEstimate object for any of the response data provided.
59
- # This is useful for characteristics that are unique to an emitter.
60
- #
61
- # For example:
62
- # > my_car.emission_estimate.model
63
- # => 'Ford Taurus'
64
- def method_missing(method_id, *args, &blk)
65
- if !block_given? and args.empty? and data.has_key? method_id.to_s
66
- data[method_id.to_s]
67
- elsif ::Float.method_defined? method_id
68
- raise TriedToUseAsyncResponseAsNumber if mode == :async
69
- number.send method_id, *args, &blk
70
- else
71
- super
72
- end
73
- end
74
-
75
- def data #:nodoc:
76
- if storage.present?
77
- storage.data
78
- else
79
- response.data
80
- end
81
- end
82
-
83
- def storage #:nodoc:
84
- @storage ||= {}
85
- return @storage[guid] if @storage.has_key? guid
86
- @storage[guid] = Storage.new self
87
- end
88
-
89
- def request #:nodoc:
90
- @request ||= Request.new self
91
- end
92
-
93
- # Here's where caching takes place.
94
- def response #:nodoc:
95
- current_params = request.params
96
- @response ||= {}
97
- return @response[current_params] if @response.has_key? current_params
98
- response_object = Response.new self
99
- response_object.load_data
100
- @response[current_params] = response_object
101
- end
102
-
103
- def certified? #:nodoc:
104
- !!certified
105
- end
106
-
107
- def defer? #:nodoc:
108
- @defer == true
109
- end
110
-
111
- def async? #:nodoc:
112
- callback or defer?
113
- end
114
-
115
- def mode #:nodoc:
116
- async? ? :async : :realtime
117
- end
118
-
119
- # Timeout on realtime requests in seconds, if desired.
120
- def timeout
121
- @timeout
122
- end
123
-
124
- def callback_content_type
125
- @callback_content_type || 'application/json'
126
- end
127
-
128
- def key
129
- @key || ::Carbon.key
130
- end
131
-
132
- # The timeframe being looked at in the emission calculation.
133
- def active_subtimeframe
134
- data['active_subtimeframe']
135
- end
136
-
137
- # Another way to access the emission value.
138
- # Useful if you don't like treating <tt>EmissionEstimate</tt> objects like <tt>Numeric</tt> objects (even though they do quack like numbers...)
139
- def number
140
- async? ? nil : data['emission'].to_f.freeze
141
- end
142
-
143
- # The units of the emission.
144
- def emission_units
145
- data['emission_units']
146
- end
147
-
148
- # Errors (and warnings) as reported in the response.
149
- # Note: may contain HTML tags like KBD or A
150
- def errors
151
- data['errors']
152
- end
153
-
154
- # The URL of the methodology report indicating how this estimate was calculated.
155
- # > my_car.emission_estimate.methodology
156
- # => 'http://carbon.brighterplanet.com/automobiles.html?[...]'
157
- def methodology
158
- data['methodology']
159
- end
160
-
161
- def reports
162
- data['reports']
163
- end
164
- end
165
- end
@@ -1,100 +0,0 @@
1
- module Carbon
2
- class EmissionEstimate
3
- class Request #:nodoc:all
4
- attr_reader :parent
5
-
6
- def initialize(parent)
7
- @parent = parent
8
- end
9
-
10
- def body
11
- params.to_query
12
- end
13
-
14
- def params
15
- params = send "#{parent.mode}_params"
16
- validate params
17
- params
18
- end
19
-
20
- def validate(params_hash)
21
- unless params_hash.key? :key
22
- Carbon.warn 'You have not specified an API key. Please obtain a key from http://keys.brighterplanet.com.'
23
- end
24
- end
25
-
26
- def async_params
27
- raise ::ArgumentError, "When using :callback you cannot specify :defer" if parent.defer? and parent.callback
28
- raise ::ArgumentError, "When using :defer => true you must specify :guid" if parent.defer? and parent.guid.blank?
29
- hash = _params
30
- hash[:emitter] = parent.emitter.class.carbon_base.emitter_common_name
31
- hash[:callback] = parent.callback if parent.callback
32
- hash[:callback_content_type] = parent.callback_content_type if parent.callback
33
- hash[:guid] = parent.guid if parent.defer?
34
- {
35
- :Action => 'SendMessage',
36
- :Version => '2009-02-01',
37
- :MessageBody => hash.to_query
38
- }
39
- end
40
-
41
- def realtime_params
42
- _params
43
- end
44
-
45
- # Used internally, but you can look if you want.
46
- #
47
- # Returns the params hash that will be send to the emission estimate server.
48
- def _params
49
- hash = parent.emitter.class.carbon_base.translation_table.inject({}) do |memo, translation|
50
- characteristic, key = translation
51
- if characteristic.is_a? ::Array
52
- current_value = parent.emitter.send characteristic[0]
53
- as = characteristic[1]
54
- else
55
- current_value = parent.emitter.send characteristic
56
- as = characteristic
57
- end
58
- current_value = begin
59
- current_value.to_characteristic
60
- rescue NoMethodError
61
- current_value.to_param
62
- end
63
- if current_value.is_a?(FalseClass) or current_value.present?
64
- if key
65
- memo[as] ||= {}
66
- memo[as][key] = current_value
67
- else
68
- memo[as] = current_value
69
- end
70
- end
71
- memo
72
- end
73
- hash[:timeframe] = parent.timeframe if parent.timeframe
74
- hash[:key] = parent.key if parent.key
75
- hash[:comply] = parent.comply if parent.comply
76
- hash
77
- end
78
-
79
- def realtime_url
80
- uri = ::URI.parse ''
81
- uri.scheme = 'http'
82
- uri.host = parent.certified? ? 'certified.carbon.brighterplanet.com' : 'carbon.brighterplanet.com'
83
- uri.path = "/#{parent.emitter.class.carbon_base.emitter_common_name.pluralize}.json"
84
- uri.to_s
85
- end
86
-
87
- def async_url
88
- uri = ::URI.parse ''
89
- uri.scheme = 'https'
90
- uri.host = 'queue.amazonaws.com'
91
- uri.path = parent.certified? ? '/121562143717/cm1_production_incoming_certified' : '/121562143717/cm1_production_incoming'
92
- uri.to_s
93
- end
94
-
95
- def url
96
- send "#{parent.mode}_url"
97
- end
98
- end
99
- end
100
- end
@@ -1,61 +0,0 @@
1
- module Carbon
2
- class EmissionEstimate
3
- class Response #:nodoc:all
4
- attr_reader :parent
5
- attr_reader :data
6
- attr_reader :raw_request
7
- attr_reader :raw_response
8
-
9
- def initialize(parent)
10
- @parent = parent
11
- end
12
-
13
- def load_data
14
- send "load_#{parent.mode}_data"
15
- end
16
-
17
- private
18
- def load_realtime_data
19
- attempts = 0
20
- response = nil
21
- begin
22
- response = perform
23
- raise ::Carbon::RateLimited if response.status_code == 403 and response.body =~ /Rate Limit/i #TODO: Should we expect an HTTP 402: payment required, instead?
24
- rescue ::Carbon::RateLimited
25
- if attempts < 4
26
- attempts += 1
27
- sleep 0.2 * attempts
28
- retry
29
- else
30
- raise $!
31
- end
32
- end
33
- raise ::Carbon::RealtimeEstimateFailed unless response.success? #TODO: should we expect 300s as well as 200s? Also, we may want to include response code and body in our exception.
34
- @data = ::Carbon::EmissionEstimate.parse response.body
35
- end
36
-
37
- def load_async_data
38
- response = perform
39
- raise ::Carbon::QueueingFailed unless response.success? #TODO: should we expect 300s as well as 200s? Also, we may want to include response code and body in our exception.
40
- @data = {}
41
- end
42
-
43
- def perform
44
- response = nil
45
- if parent.timeout
46
- Timeout.timeout(parent.timeout) do
47
- response = perform_request
48
- end
49
- else
50
- response = perform_request
51
- end
52
- response
53
- end
54
-
55
- def perform_request
56
- @raw_request = ::REST::Request.new :post, ::URI.parse(parent.request.url), parent.request.body, {'Content-Type' => 'application/x-www-form-urlencoded; charset=utf-8'}
57
- @raw_response = raw_request.perform
58
- end
59
- end
60
- end
61
- end
@@ -1,33 +0,0 @@
1
- module Carbon
2
- class EmissionEstimate
3
- class Storage #:nodoc:all
4
- attr_accessor :parent
5
- attr_reader :raw_request
6
- attr_reader :raw_response
7
-
8
- def initialize(parent)
9
- @parent = parent
10
- end
11
-
12
- def url
13
- "http://storage.carbon.brighterplanet.com/#{::Digest::SHA1.hexdigest(parent.key+parent.guid)}"
14
- end
15
-
16
- def present?
17
- parent.guid.present? and data.present?
18
- end
19
-
20
- def data
21
- return @data[0] if @data.is_a? ::Array
22
- @raw_request = ::REST::Request.new :get, ::URI.parse(url)
23
- @raw_response = raw_request.perform
24
- if raw_response.success?
25
- @data = [::Carbon::EmissionEstimate.parse(raw_response.body)]
26
- else
27
- @data = []
28
- end
29
- @data[0]
30
- end
31
- end
32
- end
33
- end