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 +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:
|