rack-si 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/README.markdown +26 -0
- data/lib/rack/si.rb +90 -14
- data/lib/rack/si/version.rb +1 -1
- data/spec/si_spec.rb +198 -32
- metadata +10 -10
data/Gemfile
CHANGED
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
|
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
|
-
|
12
|
-
|
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
|
16
|
-
|
17
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
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)
|
data/lib/rack/si/version.rb
CHANGED
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
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
-
|
135
|
+
context 'with path option' do
|
136
|
+
let(:options) { { :path => ['/accepted', /also_accepted/] } }
|
50
137
|
|
51
|
-
|
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
|
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-
|
12
|
+
date: 2012-05-01 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: herbalist
|
16
|
-
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: *
|
24
|
+
version_requirements: *2165760380
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rack
|
27
|
-
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: *
|
35
|
+
version_requirements: *2165759960
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rack-test
|
38
|
-
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: *
|
46
|
+
version_requirements: *2165759540
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rspec
|
49
|
-
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: *
|
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:
|