carbon 1.1.3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -22
- data/CHANGELOG +11 -0
- data/Gemfile +10 -1
- data/README.markdown +185 -0
- data/Rakefile +13 -26
- data/bin/carbon +3 -3
- data/carbon.gemspec +17 -23
- data/developer_notes/MULTI.markdown +25 -0
- data/developer_notes/REDUCE_HTTP_CONNECTIONS.markdown +46 -0
- data/features/shell.feature +1 -1
- data/features/support/env.rb +3 -4
- data/lib/carbon.rb +242 -96
- data/lib/carbon/registry.rb +50 -0
- data/lib/carbon/shell.rb +14 -8
- data/lib/carbon/shell/emitter.rb +33 -29
- data/lib/carbon/version.rb +1 -1
- data/test/carbon_test.rb +167 -0
- metadata +128 -182
- data/MIT-LICENSE.txt +0 -19
- data/README.rdoc +0 -266
- data/doc/INTEGRATION_GUIDE.rdoc +0 -1002
- data/doc/examining-response-with-jsonview.png +0 -0
- data/doc/shell_example +0 -43
- data/doc/timeout-error.png +0 -0
- data/doc/with-committee-reports.png +0 -0
- data/doc/without-committee-reports.png +0 -0
- data/lib/carbon/base.rb +0 -62
- data/lib/carbon/emission_estimate.rb +0 -165
- data/lib/carbon/emission_estimate/request.rb +0 -100
- data/lib/carbon/emission_estimate/response.rb +0 -61
- data/lib/carbon/emission_estimate/storage.rb +0 -33
- data/spec/fixtures/vcr_cassettes/flight.yml +0 -47
- data/spec/fixtures/vcr_cassettes/residence.yml +0 -44
- data/spec/lib/carbon/emission_estimate/request_spec.rb +0 -41
- data/spec/lib/carbon/emission_estimate/response_spec.rb +0 -33
- data/spec/lib/carbon/emission_estimate_spec.rb +0 -32
- data/spec/lib/carbon_spec.rb +0 -384
- data/spec/spec_helper.rb +0 -60
- data/spec/specwatchr +0 -60
data/.gitignore
CHANGED
@@ -1,23 +1,5 @@
|
|
1
|
-
## MAC OS
|
2
|
-
.DS_Store
|
3
|
-
|
4
|
-
## TEXTMATE
|
5
|
-
*.tmproj
|
6
|
-
tmtags
|
7
|
-
|
8
|
-
## EMACS
|
9
|
-
*~
|
10
|
-
\#*
|
11
|
-
.\#*
|
12
|
-
|
13
|
-
## VIM
|
14
|
-
*.swp
|
15
|
-
|
16
|
-
## PROJECT::GENERAL
|
17
|
-
coverage
|
18
|
-
rdoc
|
19
|
-
pkg
|
20
|
-
|
21
|
-
## PROJECT::SPECIFIC
|
22
1
|
Gemfile.lock
|
23
|
-
|
2
|
+
.DS_Store
|
3
|
+
.rvmrc
|
4
|
+
.yardoc/
|
5
|
+
doc/
|
data/CHANGELOG
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
2.0.0 / 2012-03-09
|
2
|
+
|
3
|
+
* Breaking changes
|
4
|
+
|
5
|
+
* #emission_estimate has been removed in favor of #impact
|
6
|
+
* Response structure now mirrors what you get from http://impact.brighterplanet.com
|
7
|
+
|
8
|
+
* Enhancements
|
9
|
+
|
10
|
+
* Carbon.multi method for parallelizing requests
|
11
|
+
* Tested with MRI 1.8 and MRI 1.9
|
data/Gemfile
CHANGED
data/README.markdown
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
# Carbon
|
2
|
+
|
3
|
+
Carbon is a Ruby API client and command-line console for the [Brighter Planet impact estimate web service](http://impact.brighterplanet.com), which is located at http://impact.brighterplanet.com. By querying the web service, it can estimate the carbon emissions, energy usage, and other environmental impacts of many real-life objects, such as cars and houses, based on particular characteristics that they may have.
|
4
|
+
|
5
|
+
Full documentation: [RDoc](http://rdoc.info/projects/brighterplanet/carbon)
|
6
|
+
|
7
|
+
## Quick start 1: experimenting with the console
|
8
|
+
|
9
|
+
<b>You'll need a Brighter Planet API key. See the "API keys" section below for details.</b>
|
10
|
+
|
11
|
+
First get the gem:
|
12
|
+
|
13
|
+
$ gem install carbon
|
14
|
+
|
15
|
+
Then start the console:
|
16
|
+
|
17
|
+
$ carbon
|
18
|
+
carbon->
|
19
|
+
|
20
|
+
Provide your key:
|
21
|
+
|
22
|
+
carbon-> key '123ABC'
|
23
|
+
=> Using key 123ABC
|
24
|
+
|
25
|
+
Start a flight calculation:
|
26
|
+
|
27
|
+
carbon-> flight
|
28
|
+
=> 1210.66889895298 kg CO2e
|
29
|
+
flight*>
|
30
|
+
|
31
|
+
Start providing characteristics:
|
32
|
+
|
33
|
+
flight*> origin_airport 'jfk'
|
34
|
+
=> 1593.46008200024 kg CO2e
|
35
|
+
flight*> destination_airport 'lax'
|
36
|
+
=> 1766.55536727522 kg CO2e
|
37
|
+
|
38
|
+
Review what you've entered:
|
39
|
+
|
40
|
+
flight*> characteristics
|
41
|
+
=> origin_airport: jfk
|
42
|
+
destination_airport: lax
|
43
|
+
|
44
|
+
See how the calculation's being made:
|
45
|
+
|
46
|
+
flight*> methodology
|
47
|
+
=> emission: from fuel and passengers with coefficients
|
48
|
+
[ ... ]
|
49
|
+
cohort: from t100
|
50
|
+
|
51
|
+
See intermediate calculations:
|
52
|
+
|
53
|
+
flight*> reports
|
54
|
+
=> emission: 1766.55536727522
|
55
|
+
[ ... ]
|
56
|
+
cohort: {"members"=>262}
|
57
|
+
|
58
|
+
Generate a methodology URL:
|
59
|
+
|
60
|
+
flight*> url
|
61
|
+
=> http://impact.brighterplanet.com/flights.json?origin_airport=jfk&destination_airport=lax&key=123ABC
|
62
|
+
|
63
|
+
And when you're done:
|
64
|
+
|
65
|
+
flight*> done
|
66
|
+
=> Saved as flight #0
|
67
|
+
carbon->
|
68
|
+
|
69
|
+
You can recall this flight anytime during this same session:
|
70
|
+
|
71
|
+
carbon-> flight 0
|
72
|
+
=> 1766.55536727522 kg CO2e
|
73
|
+
flight*> characteristics
|
74
|
+
=> origin_airport: jfk
|
75
|
+
destination_airport: lax
|
76
|
+
|
77
|
+
For more, see the "Console" section below.
|
78
|
+
|
79
|
+
## Quick start 2: using the library in your application
|
80
|
+
|
81
|
+
<b>You'll need a Brighter Planet API key. See the "API keys" section below for details.</b>
|
82
|
+
|
83
|
+
Carbon works by extending any Ruby class to be an emission source. You `include Carbon` and then use the `emit_as` DSL...
|
84
|
+
|
85
|
+
{render:Carbon::ClassMethods#emit_as}
|
86
|
+
|
87
|
+
The final URL will be something like
|
88
|
+
|
89
|
+
http://impact.brighterplanet.com/flights.json?segments_per_trip=1&trips=1&origin_airport[iata_code]=MSN&destination_airport[iata_code]=ORD&airline[iata_code]=UA&aircraft[icao_code]=B737
|
90
|
+
|
91
|
+
When you want to calculate impacts, simply call `MyFlight#impact`.
|
92
|
+
|
93
|
+
{render:Carbon#impact}
|
94
|
+
|
95
|
+
## API keys
|
96
|
+
|
97
|
+
You should get an API key from http://keys.brighterplanet.com and set it globally:
|
98
|
+
|
99
|
+
Carbon.key = '12903019230128310293'
|
100
|
+
|
101
|
+
Now all of your queries will use that key.
|
102
|
+
|
103
|
+
## Gotcha: timeframes and 0.0kg results
|
104
|
+
|
105
|
+
You submit this query about a flight in 2009, but the result is 0.0 kilograms. Why?
|
106
|
+
|
107
|
+
$ carbon
|
108
|
+
carbon-> flight
|
109
|
+
[...]
|
110
|
+
flight*> date '2009-05-03'
|
111
|
+
=> 0.0 kg CO2e
|
112
|
+
flight*> url
|
113
|
+
=> http://impact.brighterplanet.com/flights?date=2009-05-03
|
114
|
+
|
115
|
+
It's telling you that a flight in 2009 did not result in any 2011 emissions (the default timeframe is the current year).
|
116
|
+
|
117
|
+
flight*> timeframe '2009'
|
118
|
+
=> 847.542137647608 kg CO2e
|
119
|
+
flight*> url
|
120
|
+
=> http://impact.brighterplanet.com/flights?date=2009-05-03&timeframe=2009-01-01/2010-01-01
|
121
|
+
|
122
|
+
So, 850 kilograms emitted in 2009.
|
123
|
+
|
124
|
+
## Console
|
125
|
+
|
126
|
+
This library includes a special console for performing calculations interactively. Quick Start #1 provides an example session. Here is a command reference:
|
127
|
+
|
128
|
+
### Shell mode
|
129
|
+
|
130
|
+
`help`
|
131
|
+
: Displays a list of emitter types.
|
132
|
+
|
133
|
+
`key` _yourkey_
|
134
|
+
: Set the [developer key](http://keys.brighterplanet.com) that should be used for this session. Alternatively, put this key in `~/.brighter_planet` and it will be auto-selected on console startup.
|
135
|
+
|
136
|
+
_emitter_
|
137
|
+
: (e.g. `Flight`) Enters emitter mode using this emitter type.
|
138
|
+
|
139
|
+
_emitter num_
|
140
|
+
: (e.g. `Flight 0`) Recalls a previous emitter from this session.
|
141
|
+
|
142
|
+
`exit`
|
143
|
+
: Quits.
|
144
|
+
|
145
|
+
### Emitter mode
|
146
|
+
|
147
|
+
In Emitter mode, the prompt displays the emitter type in use. If a timeframe has been set, the timeframe is also included in the prompt.
|
148
|
+
|
149
|
+
`help`
|
150
|
+
: Displays a list of characteristics for this emitter type.
|
151
|
+
|
152
|
+
_characteristic value_
|
153
|
+
: (e.g. `origin_airport 'lax'`)
|
154
|
+
: Provide a characteristic. Remember, this is Ruby we're dealing with, so strings must be quoted.
|
155
|
+
|
156
|
+
`timeframe`
|
157
|
+
: Display the current timeframe in effect on the emission estimate.
|
158
|
+
|
159
|
+
`timeframe` _timeframe_
|
160
|
+
: (e.g. `timeframe '2009-01-01/2010-01-01'` or just `timeframe '2009'`) Set a timeframe on the emission estimate.
|
161
|
+
|
162
|
+
`emission`
|
163
|
+
: Displays the current emission in kilograms CO2e for this emitter.
|
164
|
+
|
165
|
+
`lbs`, `pounds`, or `tons`
|
166
|
+
: Display the emission using different units.
|
167
|
+
|
168
|
+
`characteristics`
|
169
|
+
: Lists the characteristics you have provided so far.
|
170
|
+
|
171
|
+
`methodology`
|
172
|
+
: Summarizes how the calculation is being made.
|
173
|
+
|
174
|
+
`reports`
|
175
|
+
: Displays intermediate calculations that were made in pursuit of the emission estimate.
|
176
|
+
|
177
|
+
`url`
|
178
|
+
: Generates a methodology URL suitable for pasting into your browser for further inspection.
|
179
|
+
|
180
|
+
`done`
|
181
|
+
: Saves this emitter and returns to shell mode.
|
182
|
+
|
183
|
+
## Copyright
|
184
|
+
|
185
|
+
Copyright (c) 2012 Brighter Planet.
|
data/Rakefile
CHANGED
@@ -1,33 +1,20 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
require 'rspec/core/rake_task'
|
5
|
-
|
6
|
-
desc "Run all examples"
|
7
|
-
RSpec::Core::RakeTask.new('examples') do |c|
|
8
|
-
c.rspec_opts = '-Ispec'
|
9
|
-
end
|
10
|
-
|
11
|
-
task :test => [:examples, :cucumber]
|
12
|
-
task :default => :test
|
13
|
-
|
14
|
-
desc "Run specs with RCov"
|
15
|
-
RSpec::Core::RakeTask.new(:examples_with_coverage) do |t|
|
16
|
-
t.rcov = true
|
17
|
-
t.rcov_opts = ['--exclude', 'spec']
|
18
|
-
t.rspec_opts = '-Ispec'
|
19
|
-
end
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require "bundler/gem_tasks"
|
20
3
|
|
21
4
|
require 'rake'
|
22
|
-
require 'rake/
|
23
|
-
Rake::
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
rdoc.rdoc_files.include('README*')
|
28
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
5
|
+
require 'rake/testtask'
|
6
|
+
Rake::TestTask.new(:test) do |test|
|
7
|
+
test.libs << 'lib'
|
8
|
+
test.pattern = 'test/**/*_test.rb'
|
9
|
+
test.verbose = true
|
29
10
|
end
|
30
11
|
|
31
12
|
require 'cucumber/rake/task'
|
32
13
|
Cucumber::Rake::Task.new
|
33
14
|
|
15
|
+
require 'yard'
|
16
|
+
YARD::Rake::YardocTask.new do |y|
|
17
|
+
y.options << '--no-private' << '--title' << "Brighter Planet CM1 client for Ruby"
|
18
|
+
end
|
19
|
+
|
20
|
+
task :default => [:test, :cucumber]
|
data/bin/carbon
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
$:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib')
|
3
|
-
|
4
2
|
require 'rubygems'
|
5
|
-
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__)) unless $LOAD_PATH.include?(File.expand_path('../../lib', __FILE__))
|
6
5
|
require 'carbon/shell'
|
6
|
+
|
7
7
|
Bombshell.launch(Carbon::Shell)
|
data/carbon.gemspec
CHANGED
@@ -1,33 +1,27 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
require "carbon/version"
|
2
|
+
require File.expand_path('../lib/carbon/version', __FILE__)
|
4
3
|
|
5
4
|
Gem::Specification.new do |s|
|
6
|
-
s.name =
|
5
|
+
s.name = 'carbon'
|
7
6
|
s.version = Carbon::VERSION
|
8
|
-
s.
|
9
|
-
s.
|
10
|
-
s.
|
7
|
+
s.author = 'Seamus Abshere'
|
8
|
+
s.email = ['seamus@abshere.net', 'dkastner@gmail.com', 'andy@rossmeissl.net']
|
9
|
+
s.summary = 'Brighter Planet API client for Ruby'
|
10
|
+
s.description = 'Brighter Planet API client for Ruby'
|
11
11
|
s.homepage = 'https://github.com/brighterplanet/carbon'
|
12
|
-
s.summary = %q{Carbon is a Ruby API wrapper for the Brighter Planet emission estimate web service (http://carbon.brighterplanet.com).}
|
13
|
-
s.description = %q{Carbon is a Ruby API wrapper for the Brighter Planet emission estimate web service (http://carbon.brighterplanet.com). By querying the web service, it can estimate the carbon emissions of many real-life objects, such as cars and houses, based on particular attributes that they may have.}
|
14
12
|
|
13
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
15
14
|
s.files = `git ls-files`.split("\n")
|
16
15
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
16
|
s.require_paths = ["lib"]
|
19
|
-
|
20
|
-
s.
|
21
|
-
s.
|
22
|
-
s.
|
23
|
-
s.
|
24
|
-
|
25
|
-
|
26
|
-
s.
|
27
|
-
s.
|
28
|
-
s.
|
29
|
-
s.add_development_dependency 'rspec'
|
30
|
-
s.add_development_dependency 'aruba'
|
31
|
-
s.add_development_dependency 'rake'
|
32
|
-
s.add_development_dependency 'vcr'
|
17
|
+
|
18
|
+
s.add_runtime_dependency 'em-http-request'
|
19
|
+
s.add_runtime_dependency 'activesupport'
|
20
|
+
s.add_runtime_dependency 'multi_json'
|
21
|
+
s.add_runtime_dependency 'hashie'
|
22
|
+
|
23
|
+
# CLI
|
24
|
+
s.add_runtime_dependency 'bombshell'
|
25
|
+
s.add_runtime_dependency 'conversions'
|
26
|
+
s.add_runtime_dependency 'brighter_planet_metadata'
|
33
27
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Had this for a while
|
2
|
+
|
3
|
+
def self.impacts(enumerable)
|
4
|
+
queries = enumerable.map do |instance|
|
5
|
+
[ Registry.instance[instance.class.name].emitter, instance.impact_params ]
|
6
|
+
end
|
7
|
+
multi queries
|
8
|
+
end
|
9
|
+
|
10
|
+
Tested like this
|
11
|
+
|
12
|
+
describe :impacts do
|
13
|
+
it "works" do
|
14
|
+
impacts = Carbon.impacts(MyNissanAltima.all(:order => :year))
|
15
|
+
impacts.length.must_equal 5
|
16
|
+
impacts.map do |impact|
|
17
|
+
impact.decisions.carbon.object.value.round
|
18
|
+
end.uniq.length.must_be :>, 3
|
19
|
+
impacts.each_with_index do |impact, idx|
|
20
|
+
impact.decisions.carbon.object.value.must_be :>, 0
|
21
|
+
impact.characteristics.make.description.must_match %r{Nissan}i
|
22
|
+
impact.characteristics.year.description.to_i.must_equal(2000+idx)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
One way to reduce the number of connections by a constant... but it makes it slower (because requests are serialized) and less reliable (because there is a 30s heroku limit)
|
2
|
+
|
3
|
+
def self.multi(queries)
|
4
|
+
unsorted = {}
|
5
|
+
pool_size = (queries.length.to_f / 3).ceil
|
6
|
+
$stderr.puts "Starting #{pool_size} workers"
|
7
|
+
::EventMachine.run do
|
8
|
+
multi = ::EventMachine::MultiRequest.new
|
9
|
+
pool = 0.upto(pool_size).map do
|
10
|
+
::EventMachine::HttpRequest.new("http://#{domain}")
|
11
|
+
end
|
12
|
+
pool_idx = 0
|
13
|
+
queries.each_with_index do |(emitter, params), query_idx|
|
14
|
+
params ||= {}
|
15
|
+
multi.add query_idx, pool[pool_idx].post(:path => "/#{emitter.underscore.pluralize}.json", :body => params, :keepalive => true)
|
16
|
+
pool_idx = (pool_idx + 1) % pool_size
|
17
|
+
end
|
18
|
+
multi.callback do
|
19
|
+
multi.responses[:callback].each do |query_idx, http|
|
20
|
+
response = ::Hashie::Mash.new
|
21
|
+
response.status = http.response_header.status
|
22
|
+
if (200..299).include?(response.status)
|
23
|
+
response.success = true
|
24
|
+
response.merge! ::MultiJson.decode(http.response)
|
25
|
+
else
|
26
|
+
response.success = false
|
27
|
+
response.errors = [http.response]
|
28
|
+
end
|
29
|
+
unsorted[query_idx] = response
|
30
|
+
end
|
31
|
+
multi.responses[:errback].each do |query_idx, http|
|
32
|
+
response = ::Hashie::Mash.new
|
33
|
+
response.status = http.response_header.status
|
34
|
+
response.success = false
|
35
|
+
response.errors = ['Timeout or other network error.']
|
36
|
+
unsorted[query_idx] = response
|
37
|
+
end
|
38
|
+
::EventMachine.stop
|
39
|
+
end
|
40
|
+
end
|
41
|
+
unsorted.sort_by do |query_idx, _|
|
42
|
+
query_idx
|
43
|
+
end.map do |_, response|
|
44
|
+
response
|
45
|
+
end
|
46
|
+
end
|
data/features/shell.feature
CHANGED
@@ -169,7 +169,7 @@ Feature: Shell
|
|
169
169
|
And I type "exit"
|
170
170
|
Then the output should contain:
|
171
171
|
"""
|
172
|
-
http://
|
172
|
+
http://impact.brighterplanet.com/computations?duration=10
|
173
173
|
"""
|
174
174
|
|
175
175
|
Scenario: Retrieving stored emitter
|
data/features/support/env.rb
CHANGED
@@ -1,9 +1,8 @@
|
|
1
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__) + '/../../lib')
|
2
1
|
require 'aruba/cucumber'
|
3
2
|
require 'fileutils'
|
4
|
-
|
5
|
-
|
6
|
-
require '
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift File.expand_path('../../../lib', __FILE__)
|
5
|
+
require 'carbon/shell'
|
7
6
|
|
8
7
|
Before do
|
9
8
|
@aruba_io_wait_seconds = 2
|