junkfood 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +11 -0
- data/.gitignore +6 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +76 -0
- data/LICENSE +202 -0
- data/NOTICE +4 -0
- data/README.md +375 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/junkfood.gemspec +147 -0
- data/lib/junkfood/adler32.rb +102 -0
- data/lib/junkfood/adler32_pure.rb +112 -0
- data/lib/junkfood/assert.rb +75 -0
- data/lib/junkfood/base32.rb +198 -0
- data/lib/junkfood/ceb/base_command.rb +62 -0
- data/lib/junkfood/ceb/base_event.rb +42 -0
- data/lib/junkfood/ceb/bus.rb +152 -0
- data/lib/junkfood/ceb/executors/command_executor.rb +44 -0
- data/lib/junkfood/ceb/executors/delayed_job_command_executor.rb +61 -0
- data/lib/junkfood/ceb/executors/event_executor.rb +35 -0
- data/lib/junkfood/ceb/executors.rb +25 -0
- data/lib/junkfood/ceb.rb +27 -0
- data/lib/junkfood/one_time.rb +247 -0
- data/lib/junkfood/paperclip_string_io.rb +66 -0
- data/lib/junkfood/settings.rb +67 -0
- data/lib/junkfood.rb +29 -0
- data/spec/.rspec +1 -0
- data/spec/junkfood/adler32_pure_spec.rb +16 -0
- data/spec/junkfood/adler32_spec.rb +16 -0
- data/spec/junkfood/assert_spec.rb +84 -0
- data/spec/junkfood/base32_spec.rb +39 -0
- data/spec/junkfood/ceb/base_command_spec.rb +73 -0
- data/spec/junkfood/ceb/base_event_spec.rb +67 -0
- data/spec/junkfood/ceb/bus_spec.rb +153 -0
- data/spec/junkfood/ceb/executors/command_executor_spec.rb +24 -0
- data/spec/junkfood/ceb/executors/delayed_job_command_executor_spec.rb +5 -0
- data/spec/junkfood/ceb/executors/event_executor_spec.rb +18 -0
- data/spec/junkfood/one_time_spec.rb +167 -0
- data/spec/junkfood/paperclip_string_io_spec.rb +40 -0
- data/spec/junkfood/settings_spec.rb +7 -0
- data/spec/junkfood_spec.rb +4 -0
- data/spec/spec_helper.rb +20 -0
- metadata +372 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'delayed_job'
|
17
|
+
require 'json'
|
18
|
+
|
19
|
+
module Junkfood
|
20
|
+
module Ceb
|
21
|
+
module Executors
|
22
|
+
|
23
|
+
##
|
24
|
+
# The actual job class that is serialized for DelayedJob runs. which
|
25
|
+
# is tasked to perform the commands.
|
26
|
+
#
|
27
|
+
# EXPERIMENTAL: This is untested, unfinished code.
|
28
|
+
#
|
29
|
+
class DelayedJobCommandExecutorJob < Struct(:message)
|
30
|
+
|
31
|
+
##
|
32
|
+
# This method JSON parses the command data, instantiating the
|
33
|
+
# referenced Command class, and finally executes the command.
|
34
|
+
#
|
35
|
+
def perform
|
36
|
+
params = JSON.parse message
|
37
|
+
command_class = params['_type'].constantize
|
38
|
+
raise 'err' unless message_class.kind_of? ::Junkfood::Ceb::BaseCommand
|
39
|
+
command = command_class.new params
|
40
|
+
command.perform
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# An executor that queues up a DelayedJob to execute the command at
|
46
|
+
# a later point in time.
|
47
|
+
#
|
48
|
+
# EXPERIMENTAL: This is untested, unfinished code.
|
49
|
+
#
|
50
|
+
class DelayedJobExecutor
|
51
|
+
|
52
|
+
##
|
53
|
+
# @param command (BaseCommand) the command to queue up.
|
54
|
+
#
|
55
|
+
def call(command)
|
56
|
+
Delayed::Job.enqueue DelayedJobExecutorJob.new(command.to_json)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
module Junkfood
|
17
|
+
module Ceb
|
18
|
+
module Executors
|
19
|
+
|
20
|
+
##
|
21
|
+
# Processes the event from the event_bus. The event is saved.
|
22
|
+
# TODO: emit json serialized event to other listeners.
|
23
|
+
#
|
24
|
+
class EventExecutor
|
25
|
+
|
26
|
+
##
|
27
|
+
# @param event (BaseEvent) the event to save and perform.
|
28
|
+
#
|
29
|
+
def call(event)
|
30
|
+
event.save! if event.new_record?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
module Junkfood
|
17
|
+
module Ceb
|
18
|
+
|
19
|
+
##
|
20
|
+
# Namespace for the Ceb executors.
|
21
|
+
#
|
22
|
+
module Executors
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/junkfood/ceb.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
module Junkfood
|
17
|
+
|
18
|
+
##
|
19
|
+
# The Junkfood::Ceb module is the namespace for the Command-Query
|
20
|
+
# Responsibility Separation component of Junkfood.
|
21
|
+
module Ceb
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'junkfood/ceb/base_command'
|
26
|
+
require 'junkfood/ceb/base_event'
|
27
|
+
require 'junkfood/ceb/bus'
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'openssl'
|
17
|
+
require 'wrong'
|
18
|
+
|
19
|
+
module Junkfood
|
20
|
+
|
21
|
+
##
|
22
|
+
# Implements HMAC One Time Passwords using SHA-1 digests.
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
#
|
26
|
+
# # Using the class methods to get OTP one at a time.
|
27
|
+
# key = '12345678901234567890'
|
28
|
+
# puts Junkfood::OneTime.hotp(key, 0) #=> 755224
|
29
|
+
# puts Junkfood::OneTime.hotp(key, 1) #=> 287082
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
#
|
33
|
+
# # Changing the length of the OTP
|
34
|
+
# key = '12345678901234567890'
|
35
|
+
# puts Junkfood::OneTime.hotp(key, 0, :digits => 8) #=> 84755224
|
36
|
+
#
|
37
|
+
# @example
|
38
|
+
#
|
39
|
+
# # Using the class methods to get multiple OTP at a time.
|
40
|
+
# key = '12345678901234567890'
|
41
|
+
# puts Junkfood::OneTime.hotp_multi(key, 0..1) #=> [755224, 287082]
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
#
|
45
|
+
# # Create a new OTP generator starting at counter 0.
|
46
|
+
# key = '12345678901234567890'
|
47
|
+
# one_time = Junkfood::OneTime.new key
|
48
|
+
# # Get an OTP, but don't advance the counter
|
49
|
+
# puts one_time.otp #=> 755224
|
50
|
+
# puts one_time.otp #=> 755224
|
51
|
+
# # Get a range of OTP
|
52
|
+
# puts one_time.otp :range => 2 #=> [755224, 287082]
|
53
|
+
# # Get an OTP, and advance the counter
|
54
|
+
# puts one_time.otp! #=> 755224
|
55
|
+
# puts one_time.otp! #=> 287082
|
56
|
+
# puts one_time.counter #=> 2
|
57
|
+
# puts one_time.otp! :range => 2 #=> [359152, 969429]
|
58
|
+
# puts one_time.counter #=> 4
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
#
|
62
|
+
# # The current Time based OTP for the current epoch step.
|
63
|
+
# key = '12345678901234567890'
|
64
|
+
# one_time = Junkfood::OneTime.new key
|
65
|
+
# puts one_time.totp
|
66
|
+
# # A bunch of OTPs preceding and following the current epoch step OTP.
|
67
|
+
# puts one_time.totp :radius => 2
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
#
|
71
|
+
# # Setting the length and counter of the OTP on a OneTime instance
|
72
|
+
# one_time = Junkfood::OneTime.new key, :digits => 8, :counter => 2
|
73
|
+
# puts one_time.otp! #=> 37359152
|
74
|
+
# puts one_time.otp! #=> 26969429
|
75
|
+
#
|
76
|
+
# @see http://tools.ietf.org/html/rfc4226
|
77
|
+
# @see http://tools.ietf.org/html/draft-mraihi-totp-timebased-06
|
78
|
+
#
|
79
|
+
class OneTime
|
80
|
+
include ::Wrong::Assert
|
81
|
+
|
82
|
+
attr_reader :counter
|
83
|
+
|
84
|
+
# Default number of digits for each OTP.
|
85
|
+
DEFAULT_DIGITS = 6
|
86
|
+
# Default number of seconds for each step in the Time Epoch calculation.
|
87
|
+
DEFAULT_STEP_SIZE = 30
|
88
|
+
# Max number of OTPs preceding and following the current Time based OTP
|
89
|
+
# allowed in the Time based OTP method.
|
90
|
+
MAX_RADIUS = 10
|
91
|
+
|
92
|
+
##
|
93
|
+
# @param secret (String) the secret key used for the HMAC calculation.
|
94
|
+
# @option options [Fixnum] :counter (0) the htop counter to start at.
|
95
|
+
# @option options [Fixnum] :digits (6) size of each OTP.
|
96
|
+
# @option options [Fixnum] :time_digits (6) size of each Time Based OTP.
|
97
|
+
# @option options [Fixnum] :time_step_size (30) number of seconds for
|
98
|
+
# each block in calculating current counter in Time Based OTP.
|
99
|
+
def initialize(secret, options={})
|
100
|
+
@secret = secret
|
101
|
+
@counter = options[:counter] || 0
|
102
|
+
@digits = options[:digits] || DEFAULT_DIGITS
|
103
|
+
@time_digits = options[:time_digits] || @digits
|
104
|
+
@time_step_size = options[:time_step_size] || DEFAULT_STEP_SIZE
|
105
|
+
end
|
106
|
+
|
107
|
+
##
|
108
|
+
# Generate counter based OTPs and advance the counter.
|
109
|
+
#
|
110
|
+
# @option options [Fixnum] :range (1) number of OTPs to generate.
|
111
|
+
# @return [Array<String>,String] the generated OTPs.
|
112
|
+
def otp!(options={})
|
113
|
+
range = options[:range] || 1
|
114
|
+
result = otp :range => range
|
115
|
+
@counter += range
|
116
|
+
return result
|
117
|
+
end
|
118
|
+
|
119
|
+
##
|
120
|
+
# Generate counter based OTPs without advancing the counter.
|
121
|
+
#
|
122
|
+
# @option options [Fixnum] :range (1) number of OTPs to generate.
|
123
|
+
# @return [Array<String>,String] the generated OTPs.
|
124
|
+
def otp(options={})
|
125
|
+
range = options[:range] || 1
|
126
|
+
if range <= 1
|
127
|
+
return self.class.hotp(@secret, @counter, :digits => @digits)
|
128
|
+
else
|
129
|
+
return self.class.hotp_multi(
|
130
|
+
@secret,
|
131
|
+
@counter...(@counter + range),
|
132
|
+
:digits => @digits)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# Generate Time Based OTPs based on the current time and time steps.
|
138
|
+
#
|
139
|
+
# @option options [Fixnum] :radius (0) number of additional OTPs
|
140
|
+
# preceding and following the current Time OTP to generate.
|
141
|
+
# @return [Array<String>,String] the generated OTPs.
|
142
|
+
def totp(options={})
|
143
|
+
radius = options[:radius] || 0
|
144
|
+
assert{radius.kind_of?(Fixnum) && radius >= 0 && radius <= MAX_RADIUS}
|
145
|
+
c = self.class.epoch_counter(:step_size => @time_step_size)
|
146
|
+
start_counter = max(c - radius, 0)
|
147
|
+
range = start_counter..(c+radius)
|
148
|
+
results = self.class.hotp_multi @secret, range, :digits => @time_digits
|
149
|
+
return results.size <= 1 ? results.first : results
|
150
|
+
end
|
151
|
+
|
152
|
+
##
|
153
|
+
# Generate an individual OTP.
|
154
|
+
#
|
155
|
+
# @param secret (String) the secret key used for the HMAC.
|
156
|
+
# @param counter (Fixnum) the counter used to generate the OTP.
|
157
|
+
# @option options [Fixnum] :digits (6) size of each OTP.
|
158
|
+
# @return [String] the generated OTP.
|
159
|
+
def self.hotp(secret, counter=0, options={})
|
160
|
+
results = hotp_raw secret, counter, (options[:digits] || DEFAULT_DIGITS)
|
161
|
+
return results.first
|
162
|
+
end
|
163
|
+
|
164
|
+
##
|
165
|
+
# Generate a set of OTPs.
|
166
|
+
#
|
167
|
+
# @param secret (String) the secret key used for the HMAC calculation.
|
168
|
+
# @param range (Fixnum,Range) counters for which to generate OTPs
|
169
|
+
# @option options [Fixnum] :digits (6) size of each OTP.
|
170
|
+
# @return [String] the generated OTPs.
|
171
|
+
def self.hotp_multi(secret, range, options={})
|
172
|
+
digits = options[:digits] || DEFAULT_DIGITS
|
173
|
+
range = range..range if range.kind_of? Fixnum
|
174
|
+
range.map do |c|
|
175
|
+
(hotp_raw secret, c, digits).first
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
##
|
180
|
+
# Generate the OTP along with additional debug information.
|
181
|
+
#
|
182
|
+
# @param secret (String) the secret key used for the HMAC calculation.
|
183
|
+
# @param counter (Fixnum) the htop counter to use at.
|
184
|
+
# @param digits (Fixnum) size of each OTP.
|
185
|
+
# @return [Array<String,Fixnum,String>] The generated OTP, the Dynamic
|
186
|
+
# Binary Code, and the calculated HMAC digest for the OTP.
|
187
|
+
#
|
188
|
+
def self.hotp_raw(secret, counter=0, digits=DEFAULT_DIGITS)
|
189
|
+
# TODO: figure out a better way to turn fixnum into an 8byte buffer string
|
190
|
+
counter_bytes = []
|
191
|
+
x = counter
|
192
|
+
for i in 0..7
|
193
|
+
byte = x & 0xff
|
194
|
+
x >>= 8
|
195
|
+
counter_bytes.unshift byte
|
196
|
+
end
|
197
|
+
digest_data = counter_bytes.pack('C8')
|
198
|
+
|
199
|
+
# SHA1 digest is guaranteed to produce a 20 byte binary string.
|
200
|
+
# We unpack the string into an array of 8-bit bytes.
|
201
|
+
digest = OpenSSL::HMAC.digest(
|
202
|
+
OpenSSL::Digest::Digest.new('sha1'),
|
203
|
+
secret,
|
204
|
+
digest_data)
|
205
|
+
digest_array = digest.unpack('C20')
|
206
|
+
|
207
|
+
# Based on the HMAC OTP algorithm, we use the last 4 bits of the binary
|
208
|
+
# string to find the 'dbc' value. The 4 bits is the offset of the
|
209
|
+
# hmac bytes array. From which, we extract 4 bytes for the 'dbc'.
|
210
|
+
# This is the "Dynamic Truncation".
|
211
|
+
# We zero the most significant bit of the 'dbc' to get a 31-bit
|
212
|
+
# unsigned big-endian integer. This dbc (converted to a Ruby Fixnum).
|
213
|
+
# From the fix num, we modulo 10^digits to get the digits for the HOTP.
|
214
|
+
# This is the "Compute HOTP value" step
|
215
|
+
offset = digest_array.last & 0x0f
|
216
|
+
dbc = digest_array[offset..(offset+3)]
|
217
|
+
dbc[0] &= 0x7f
|
218
|
+
dbc = dbc.pack('C4').unpack('N').first
|
219
|
+
otp = (dbc % 10**digits).to_s.rjust(digits,'0')
|
220
|
+
return otp, dbc, digest
|
221
|
+
end
|
222
|
+
|
223
|
+
##
|
224
|
+
# Generate the counter based on the time and step_size.
|
225
|
+
#
|
226
|
+
# @option options [Time] :time (Time.now) the time to use.
|
227
|
+
# @option options [Fixnum] :step_size (30) the step size.
|
228
|
+
# @return the time based counter.
|
229
|
+
def self.epoch_counter(options={})
|
230
|
+
time = options[:time] || Time.now.to_i
|
231
|
+
step_size = options[:step_size] || DEFAULT_STEP_SIZE
|
232
|
+
return time / step_size
|
233
|
+
end
|
234
|
+
|
235
|
+
private
|
236
|
+
|
237
|
+
##
|
238
|
+
# Helper method to return the larger of two values.
|
239
|
+
#
|
240
|
+
# @param a (Object)
|
241
|
+
# @param b (Object)
|
242
|
+
# @return [Boolean] a >= b ? a : b
|
243
|
+
def max(a, b)
|
244
|
+
return a >= b ? a : b
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'stringio'
|
17
|
+
|
18
|
+
module Junkfood
|
19
|
+
|
20
|
+
##
|
21
|
+
# Adapter to save blobs of in-memory data into paperclip enabled
|
22
|
+
# ActiveRecord models without requiring the use of temporary files.
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
#
|
26
|
+
# class FaxDocument < ActiveRecord::Base
|
27
|
+
# has_attached_file :pdf
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# def incoming_fax_handler()
|
31
|
+
# attachment = 'Blob of Faxed Data in PDF form'
|
32
|
+
# fax_number = '555-1234'
|
33
|
+
#
|
34
|
+
# fax_document = FaxDocument.create(
|
35
|
+
# :caption => 'Look at this Document!',
|
36
|
+
# :pdf => PaperclipStringIo.new(
|
37
|
+
# attachment,
|
38
|
+
# :filename => "#{fax_number}.pdf",
|
39
|
+
# :content_type => 'application/pdf'))
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# @see http://rubygems.org/gems/paperclip
|
43
|
+
#
|
44
|
+
class PaperclipStringIo < StringIO
|
45
|
+
attr_reader :original_filename, :content_type
|
46
|
+
|
47
|
+
# Default filename
|
48
|
+
DEFAULT_FILENAME = 'unnamed'
|
49
|
+
|
50
|
+
# Default file content_type
|
51
|
+
DEFAULT_CONTENT_TYPE = 'application/octet-stream'
|
52
|
+
|
53
|
+
##
|
54
|
+
# @param string (String) blob of to save into paperclip.
|
55
|
+
# @option options [String] :filename ('unnamed') set the filename
|
56
|
+
# component for the paperclip object.
|
57
|
+
# @option options [String] :content_type ('application/octet-stream')
|
58
|
+
# set the paperclip object's mime content type.
|
59
|
+
#
|
60
|
+
def initialize(string, options={})
|
61
|
+
super(string)
|
62
|
+
@original_filename = options[:filename] || DEFAULT_FILENAME
|
63
|
+
@content_type = options[:content_type] || DEFAULT_CONTENT_TYPE
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require 'active_support/core_ext/hash'
|
17
|
+
require 'singleton'
|
18
|
+
require 'yaml'
|
19
|
+
|
20
|
+
module Junkfood
|
21
|
+
|
22
|
+
##
|
23
|
+
# A Global Settings Singleton class for Rails applications.
|
24
|
+
#
|
25
|
+
class Settings
|
26
|
+
include Singleton
|
27
|
+
|
28
|
+
attr_reader :config
|
29
|
+
|
30
|
+
##
|
31
|
+
# Reads the settings.yml and settings_<env>.yml file for configuration
|
32
|
+
# options and merges them together. The env is the Rails.env,
|
33
|
+
# and the settings files are assumed to be in the Rails.root's config
|
34
|
+
# subdirectory.
|
35
|
+
#
|
36
|
+
def initialize
|
37
|
+
file = "#{Rails.root}/config/settings.yml"
|
38
|
+
env_file = "#{Rails.root}/config/settings_#{Rails.env}.yml"
|
39
|
+
@config = {}
|
40
|
+
if File.exist? file
|
41
|
+
base_cfg = YAML.load File.read(file)
|
42
|
+
@config.deep_merge! base_cfg if base_cfg.kind_of? Hash
|
43
|
+
end
|
44
|
+
if File.exist? env_file
|
45
|
+
env_cfg = YAML.load File.read(env_file)
|
46
|
+
@config.deep_merge! env_cfg if env_cfg.kind_of? Hash
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# @return (Hash) the singleton's configuration.
|
52
|
+
#
|
53
|
+
def self.config
|
54
|
+
instance.config
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Looks up the configuration option.
|
59
|
+
#
|
60
|
+
# @param key (Object) the configuration option key.
|
61
|
+
# @return (Object) the configuration value.
|
62
|
+
#
|
63
|
+
def self.[](key)
|
64
|
+
config[key]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/junkfood.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2010 Benjamin Yu <http://benjaminyu.org/>
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
##
|
17
|
+
# The Junkfood module is the namespace for all Junkfood components.
|
18
|
+
#
|
19
|
+
module Junkfood
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'junkfood/adler32'
|
23
|
+
require 'junkfood/adler32_pure'
|
24
|
+
require 'junkfood/assert'
|
25
|
+
require 'junkfood/base32'
|
26
|
+
require 'junkfood/ceb'
|
27
|
+
require 'junkfood/one_time'
|
28
|
+
require 'junkfood/paperclip_string_io'
|
29
|
+
require 'junkfood/settings'
|
data/spec/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe 'Adler32' do
|
4
|
+
|
5
|
+
it 'should checksum Wikipedia example' do
|
6
|
+
adler32 = Junkfood::Adler32Pure.new('Wikipedia')
|
7
|
+
adler32.digest.should eql(300286872)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should checksum Wikipedia example using update' do
|
11
|
+
adler32 = Junkfood::Adler32Pure.new
|
12
|
+
adler32.update('Wikipedia').should eql(300286872)
|
13
|
+
adler32.digest.should eql(300286872)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe 'Adler32' do
|
4
|
+
|
5
|
+
it 'should checksum Wikipedia example' do
|
6
|
+
adler32 = Junkfood::Adler32.new('Wikipedia')
|
7
|
+
adler32.digest.should eql(300286872)
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should checksum Wikipedia example using update' do
|
11
|
+
adler32 = Junkfood::Adler32Pure.new
|
12
|
+
adler32.update('Wikipedia').should eql(300286872)
|
13
|
+
adler32.digest.should eql(300286872)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe "Assert" do
|
4
|
+
|
5
|
+
it 'should pass with various yielded results' do
|
6
|
+
x = Class.new do
|
7
|
+
include Junkfood::Assert
|
8
|
+
def test
|
9
|
+
assert { true }
|
10
|
+
assert { 1 }
|
11
|
+
assert { Array.new }
|
12
|
+
assert { Hash.new }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
x.new.test
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should raise Assertion Error with false' do
|
19
|
+
x = Class.new do
|
20
|
+
include Junkfood::Assert
|
21
|
+
def test
|
22
|
+
assert { false }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
lambda {
|
26
|
+
x.new.test
|
27
|
+
}.should raise_error(Junkfood::Assert::AssertionFailedError)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should raise Assertion Error with nil' do
|
31
|
+
x = Class.new do
|
32
|
+
include Junkfood::Assert
|
33
|
+
def test
|
34
|
+
assert { nil }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
lambda {
|
38
|
+
x.new.test
|
39
|
+
}.should raise_error(Junkfood::Assert::AssertionFailedError)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should raise Assertion Error with custom message' do
|
43
|
+
x = Class.new do
|
44
|
+
include Junkfood::Assert
|
45
|
+
def test
|
46
|
+
assert('my custom error message') {
|
47
|
+
false
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
lambda {
|
52
|
+
x.new.test
|
53
|
+
}.should raise_error(
|
54
|
+
Junkfood::Assert::AssertionFailedError,
|
55
|
+
'my custom error message')
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should call the parent class assert method when no block is given' do
|
59
|
+
x = Class.new do
|
60
|
+
def assert(*args)
|
61
|
+
return args.first
|
62
|
+
end
|
63
|
+
end
|
64
|
+
y = Class.new(x) do
|
65
|
+
include Junkfood::Assert
|
66
|
+
def test
|
67
|
+
result = assert true
|
68
|
+
assert { result }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
y.new.test
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should raise an error on missing block and undefined parent assert' do
|
75
|
+
x = Class.new
|
76
|
+
y = Class.new(x) do
|
77
|
+
include Junkfood::Assert
|
78
|
+
def test
|
79
|
+
assert
|
80
|
+
end
|
81
|
+
end
|
82
|
+
lambda { y.new.test }.should raise_error
|
83
|
+
end
|
84
|
+
end
|