carbon 1.1.3 → 2.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 (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