rack-si 0.0.2 → 0.1.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.
data/Gemfile CHANGED
@@ -2,4 +2,4 @@ source :rubygems
2
2
 
3
3
  gemspec :path => '.'
4
4
 
5
- gem 'herbalist', :path => '~/herbalist'
5
+ gem 'herbalist', :path => '~/herbalist' #:git => 'git://github.com/dkastner/herbalist.git', :branch => 'basic'
data/README.markdown CHANGED
@@ -0,0 +1,26 @@
1
+ # Rack::SI
2
+
3
+ Convert parameters into SI (metric) base units.
4
+
5
+ We all know the metric system is far superior to the imperial/customary system (except when performing mental math on lengths and volumes) so now there is a Rack middleware that will normalize all of your measurement input into SI!
6
+
7
+ # Installation
8
+
9
+ $ gem install rack-si
10
+
11
+
12
+ # config.ru
13
+ require 'rack/si'
14
+
15
+ use Rack::SI, options
16
+ run MyApp
17
+
18
+ # Configuration
19
+
20
+ Rack::SI accepts several configuration options:
21
+
22
+ * *env*: If true, the converted params will appear in the env['si.params'] hash. If set to a string, the converted params will appear in the env[custom\_string] hash.
23
+ * *basic*: If true, and dkastner-herbalist is installed, keep Herbalist from matching spelled-out numbers and fractions. This helps performance.
24
+ * *path*: A single path or array of paths defined as strings and/or regexes. If set, params are translated only for specified paths.
25
+ * *whitelist*: A list of params that should be converted. All others are ignored. If left blank, all params are converted (unless blacklisted).
26
+ * *blacklist*: A list of params that should *not* be converted. All others are converted (if whitelisted). If left blank, all params are converted.
data/lib/rack/si.rb CHANGED
@@ -1,33 +1,109 @@
1
1
  require 'herbalist'
2
- Herbalist.basic = true
3
2
 
4
3
  module Rack
5
4
 
6
5
  # A Rack middleware for converting params to base SI units
7
6
  #
8
- class Rack::SI
7
+ class SI
8
+ attr_accessor :app, :options
9
+
10
+ # These are the base SI units to which measurement params will be converted
9
11
  BASE_UNITS = [:metre, :metres, :meter, :meters, :litre, :litres, :liter, :liters, :joule, :joules, :gram, :grams, :watt, :watts]
10
12
 
11
- def initialize(app)
12
- @app = app
13
+ DEFAULT_OPTIONS = {
14
+ :env => false,
15
+ :basic => false,
16
+ :whitelist => [],
17
+ :blacklist => [],
18
+ :path => []
19
+ }
20
+
21
+ # use Rack::SI my_options
22
+ def initialize(app, options = {})
23
+ self.app = app
24
+ self.options = DEFAULT_OPTIONS.merge(options)
25
+
26
+ normalize_options
27
+ Herbalist.basic = true if Herbalist.respond_to?(:basic) && options[:basic]
28
+ end
29
+
30
+ # Make sure options are in the right format
31
+ def normalize_options
32
+ self.options[:whitelist] = self.options[:whitelist].map(&:to_s)
33
+ self.options[:blacklist] = self.options[:blacklist].map(&:to_s)
34
+ self.options[:path] = [self.options[:path]] unless self.options[:path].is_a?(Array)
35
+ end
36
+
37
+ # Called on each request
38
+ def call(env, options = {})
39
+ req = Request.new(env)
40
+ convert_params(env, req) if path_matches?(req)
41
+ app.call(env)
42
+ end
43
+
44
+ def path_matches?(req)
45
+ options[:path].empty? || options[:path].find do |path|
46
+ (path.is_a?(String) && req.path == path) ||
47
+ (path.is_a?(Regexp) && req.path =~ path)
48
+ end
13
49
  end
14
50
 
15
- def call(env)
16
- convert_params(env)
17
- @app.call(env)
51
+ def convert_params(env, req)
52
+ if options[:env]
53
+ convert_params_in_hash(env, req)
54
+ else
55
+ convert_params_in_situ(env, req)
56
+ end
18
57
  end
19
58
 
20
- def convert_params(env)
21
- params = ::Rack::Request.new(env).params
22
- env['si.params'] = params.inject({}) do |hsh, (name, value)|
23
- if measurement = Herbalist.parse(value)
24
- value = normalize(measurement)
59
+ # Convert parameters, but put them in a special hash and
60
+ # leave the regular Rack ENV params alone.
61
+ # You can specify the name of the env[] key that stores
62
+ # the params by configuring the app with the :env option
63
+ def convert_params_in_hash(env, req)
64
+ params = req.params
65
+ hash_name = options[:env].is_a?(String) ? options[:env] : 'si.params'
66
+ env[hash_name] = params.inject({}) do |hsh, (name, value)|
67
+ herbalize(hsh, name, value)
68
+ end
69
+ end
70
+
71
+ # Convert parameters "in place" - that is, modify all the
72
+ # params in env so that every other middleware sees
73
+ # the changes
74
+ def convert_params_in_situ(env, req)
75
+ env['si.original_params'] = req.params.dup
76
+ [:GET, :POST].each do |method|
77
+ hsh = req.send(method)
78
+ hsh.each do |name, value|
79
+ herbalize(hsh, name, value)
25
80
  end
26
- hsh[name] = value
27
- hsh
28
81
  end
29
82
  end
30
83
 
84
+ # Convert a parameter with Herbalist
85
+ def herbalize(hsh, name, value)
86
+ if herbalizable?(name) && measurement = Herbalist.parse(value)
87
+ hsh[name] = normalize(measurement)
88
+ end
89
+ hsh
90
+ end
91
+
92
+ # Decides if a param should be converted with Herbalist
93
+ def herbalizable?(param)
94
+ whitelisted?(param) && !blacklisted?(param)
95
+ end
96
+
97
+ def whitelisted?(param)
98
+ options[:whitelist].empty? || options[:whitelist].include?(param)
99
+ end
100
+
101
+ def blacklisted?(param)
102
+ !options[:blacklist].empty? && options[:blacklist].include?(param)
103
+ end
104
+
105
+ # Convert a param to a base unit using Alchemist's
106
+ # conversion table
31
107
  def normalize(measurement)
32
108
  group_name = Alchemist.conversion_table.keys.find do |k|
33
109
  Alchemist.conversion_table[k].include?(measurement.unit_name)
@@ -1,5 +1,5 @@
1
1
  module Rack
2
2
  class SI
3
- VERSION = '0.0.2'
3
+ VERSION = '0.1.0'
4
4
  end
5
5
  end
data/spec/si_spec.rb CHANGED
@@ -5,50 +5,216 @@ describe Rack::SI do
5
5
  include Rack::Test::Methods
6
6
 
7
7
  def app
8
- Rack::SI.new lambda { |env| [200, {}, []] }
8
+ Rack::SI.new lambda { |env| [200, {}, []] }, options
9
9
  end
10
10
 
11
11
  describe '#call' do
12
- it 'returns control to the app' do
13
- get '/foo'
14
- last_response.should be_ok
12
+ context 'with default options' do
13
+ let(:options) { {} }
14
+
15
+ it 'returns control to the app' do
16
+ get '/foo'
17
+ last_response.should be_ok
18
+ end
19
+
20
+ it 'ignores missing parameters' do
21
+ post '/bar'
22
+ last_request.params.should == {}
23
+ end
24
+
25
+ it 'does not convert non-unit params' do
26
+ post '/automobile_trips', {
27
+ 'destination' => '2,3',
28
+ 'distance' => 233
29
+ }
30
+ last_request.params.should ==({
31
+ 'destination' => '2,3',
32
+ 'distance' => '233'
33
+ })
34
+ end
35
+
36
+ it 'converts any unit-ed params to base SI units' do
37
+ post '/automobile_trips', {
38
+ 'destination' => '2,3',
39
+ 'distance' => '233 miles'
40
+ }
41
+ last_request.params['destination'].should == '2,3'
42
+ last_request.params['distance'].to_f.should == 374977.152
43
+ end
44
+
45
+ it 'keeps the original params close by' do
46
+ post '/automobile_trips', {
47
+ 'destination' => '2,3',
48
+ 'distance' => '233 miles'
49
+ }
50
+ last_request.env['si.original_params']['destination'].should == '2,3'
51
+ last_request.env['si.original_params']['distance'].should == '233 miles'
52
+ end
53
+
54
+ it 'converts compound measurements' do
55
+ pending
56
+ #post '/automobile_trips', {
57
+ #'fuel_efficiency' => '54 miles per gallon'
58
+ #}
59
+ #last_request.env['si.params']['fuel_efficiency'].to_f.should == 86904.576
60
+ end
61
+
62
+ it 'converts dates' do
63
+ pending
64
+ end
15
65
  end
16
66
 
17
- it 'ignores missing parameters' do
18
- post '/bar'
19
- last_request.env['si.params'].should == {}
67
+ context 'with env option' do
68
+ let(:options) { { :env => true } }
69
+
70
+ it 'saves SI params to a separate ENV hash if desired' do
71
+ post '/automobile_trips', {
72
+ 'destination' => '2,3',
73
+ 'distance' => '233 miles'
74
+ }
75
+ last_request.params.should == {
76
+ 'destination' => '2,3',
77
+ 'distance' => '233 miles'
78
+ }
79
+ last_request.env['si.params']['destination'].should be_nil
80
+ last_request.env['si.params']['distance'].to_f.should == 374977.152
81
+ end
20
82
  end
21
83
 
22
- it 'does not convert non-unit params' do
23
- post '/automobile_trips', {
24
- 'destination' => '2,3',
25
- 'distance' => 233
26
- }
27
- last_request.env['si.params'].should ==({
28
- 'destination' => '2,3',
29
- 'distance' => '233'
30
- })
84
+ context 'with custom env option' do
85
+ let(:options) { { :env => 'my.hash' } }
86
+
87
+ it 'saves SI params to a custom ENV hash if desired' do
88
+ post '/automobile_trips', {
89
+ 'destination' => '2,3',
90
+ 'distance' => '233 miles'
91
+ }
92
+ last_request.params.should == {
93
+ 'destination' => '2,3',
94
+ 'distance' => '233 miles'
95
+ }
96
+ last_request.env['my.hash']['destination'].should be_nil
97
+ last_request.env['my.hash']['distance'].to_f.should == 374977.152
98
+ end
31
99
  end
32
100
 
33
- it 'converts any unit-ed params to base SI units' do
34
- post '/automobile_trips', {
35
- 'destination' => '2,3',
36
- 'distance' => '233 miles'
37
- }
38
- last_request.env['si.params']['destination'].should == '2,3'
39
- last_request.env['si.params']['distance'].to_f.should == 374977.152
101
+ context 'with whitelist' do
102
+ let(:options) { { :whitelist => %w{distance} } }
103
+
104
+ it 'converts only whitelisted SI params' do
105
+ post '/automobile_trips', {
106
+ 'destination' => '2,3',
107
+ 'distance' => '233 miles',
108
+ 'weight' => '2013 lbs'
109
+ }
110
+ last_request.params.should == {
111
+ 'destination' => '2,3',
112
+ 'distance' => '374977.152',
113
+ 'weight' => '2013 lbs'
114
+ }
115
+ end
40
116
  end
41
117
 
42
- #it 'converts compound measurements' do
43
- #post '/automobile_trips', {
44
- #'fuel_efficiency' => '54 miles per gallon'
45
- #}
46
- #last_request.env['si.params']['fuel_efficiency'].to_f.should == 86904.576
47
- #end
118
+ context 'with blacklist' do
119
+ let(:options) { { :blacklist => %w{distance} } }
120
+
121
+ it 'does not convert blacklisted SI params' do
122
+ post '/automobile_trips', {
123
+ 'destination' => '2,3',
124
+ 'distance' => '233 miles',
125
+ 'weight' => '2013 lbs'
126
+ }
127
+ last_request.params.should == {
128
+ 'destination' => '2,3',
129
+ 'distance' => '233 miles',
130
+ 'weight' => '913081.4408100001'
131
+ }
132
+ end
133
+ end
48
134
 
49
- #it 'converts dates' do
135
+ context 'with path option' do
136
+ let(:options) { { :path => ['/accepted', /also_accepted/] } }
50
137
 
51
- #end
138
+ it 'converts params for a path matching a string' do
139
+ post '/accepted', {
140
+ 'distance' => '233 miles'
141
+ }
142
+ last_request.params.should == {
143
+ 'distance' => '374977.152'
144
+ }
145
+ end
146
+ it 'converts params for a path matching a regex' do
147
+ post '/also_accepted', {
148
+ 'distance' => '233 miles'
149
+ }
150
+ last_request.params.should == {
151
+ 'distance' => '374977.152'
152
+ }
153
+ end
154
+ it 'skips params conversion for a non-matching path' do
155
+ post '/fogettaboutit', {
156
+ 'distance' => '233 miles'
157
+ }
158
+ last_request.params.should == {
159
+ 'distance' => '233 miles'
160
+ }
161
+ end
162
+ end
163
+ end
164
+
165
+ describe '#herbalizable?' do
166
+ let(:options) { {} }
167
+ let(:si) { app }
168
+
169
+ it 'returns true if param is whitelisted and not blacklisted' do
170
+ si.stub!(:whitelisted?).and_return true
171
+ si.stub!(:blacklisted?).and_return false
172
+ si.herbalizable?('foo').should be_true
173
+ end
174
+ it 'returns false if param is not whitelisted' do
175
+ si.stub!(:whitelisted?).and_return false
176
+ si.herbalizable?('foo').should be_false
177
+ end
178
+ it 'returns false if param is blacklisted' do
179
+ si.stub!(:whitelisted?).and_return true
180
+ si.stub!(:blacklisted?).and_return true
181
+ si.herbalizable?('foo').should be_false
182
+ end
183
+ end
184
+
185
+ describe '#whitelisted?' do
186
+ let(:options) { {} }
187
+ let(:si) { app }
188
+
189
+ it 'returns true if the whitelist is empty' do
190
+ si.options[:whitelist] = []
191
+ si.whitelisted?('foo').should be_true
192
+ end
193
+ it 'returns true if the param is in the whitelist' do
194
+ si.options[:whitelist] = ['foo']
195
+ si.whitelisted?('foo').should be_true
196
+ end
197
+ it 'returns false if the param is not in the whitelist' do
198
+ si.options[:whitelist] = ['foo']
199
+ si.whitelisted?('bar').should be_false
200
+ end
52
201
  end
53
- end
54
202
 
203
+ describe '#blacklisted?' do
204
+ let(:options) { {} }
205
+ let(:si) { app }
206
+
207
+ it 'returns false if the blacklist is empty' do
208
+ si.options[:blacklist] = []
209
+ si.blacklisted?('foo').should be_false
210
+ end
211
+ it 'returns true if the param is in the blacklist' do
212
+ si.options[:blacklist] = ['foo']
213
+ si.blacklisted?('foo').should be_true
214
+ end
215
+ it 'returns false if the param is not in the blacklist' do
216
+ si.options[:blacklist] = ['foo']
217
+ si.blacklisted?('bar').should be_false
218
+ end
219
+ end
220
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-si
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-30 00:00:00.000000000Z
12
+ date: 2012-05-01 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: herbalist
16
- requirement: &2158550580 !ruby/object:Gem::Requirement
16
+ requirement: &2165760380 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2158550580
24
+ version_requirements: *2165760380
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rack
27
- requirement: &2166031240 !ruby/object:Gem::Requirement
27
+ requirement: &2165759960 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *2166031240
35
+ version_requirements: *2165759960
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rack-test
38
- requirement: &2166030820 !ruby/object:Gem::Requirement
38
+ requirement: &2165759540 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *2166030820
46
+ version_requirements: *2165759540
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rspec
49
- requirement: &2166030400 !ruby/object:Gem::Requirement
49
+ requirement: &2163226240 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *2166030400
57
+ version_requirements: *2163226240
58
58
  description: Choose params that are converted with Herbalist to base SI units (meters,
59
59
  grams, etc)
60
60
  email: