junkfood 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/.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
|