formeze 1.0.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/README.md ADDED
@@ -0,0 +1,133 @@
1
+ Formeze: A little library for defining classes to handle form data/input
2
+ ========================================================================
3
+
4
+
5
+ Motivation
6
+ ----------
7
+
8
+ Most web apps built for end users will need to process urlencoded form data.
9
+ Registration forms, profile forms, checkout forms, contact forms, and forms
10
+ for adding/editing application specific data. As developers we would like to
11
+ process this data safely, to minimise the possibility of security holes
12
+ within our application that could be exploited. Formeze adopts the approach
13
+ of being "strict by default", forcing the application code to be explicit in
14
+ what it accepts as input.
15
+
16
+
17
+ Example usage
18
+ -------------
19
+
20
+ Forms are just "plain old ruby objects". Calling `Formeze.setup` will include
21
+ some class methods and instance methods, but will otherwise leave the object
22
+ untouched (i.e. you can define your own initialization). Here is a minimal
23
+ example, which defines a form with a single "title" field:
24
+
25
+ class ExampleForm
26
+ Formeze.setup(self)
27
+
28
+ field :title
29
+ end
30
+
31
+
32
+ This form can then be used to parse and validate form/input data as follows:
33
+
34
+ form = ExampleForm.new
35
+
36
+ form.parse('title=Title')
37
+
38
+ form.title # => "Title"
39
+
40
+
41
+ Detecting errors
42
+ ----------------
43
+
44
+ Formeze distinguishes between user errors (which are expected in the normal
45
+ running of your application), and key/value errors (which most likely indicate
46
+ either developer error, or form tampering).
47
+
48
+ For the latter case, the `parse` method that formeze provides will raise a
49
+ Formeze::KeyError or a Formeze::ValueError exception if the structure of the
50
+ form data does not match the field definitions.
51
+
52
+ After calling `parse` you can check that the form is valid by calling the
53
+ `#valid?` method. If it isn't you can call the `errors` method which will
54
+ return an array of error messages to display to the user.
55
+
56
+
57
+ Field options
58
+ -------------
59
+
60
+ By default fields cannot be blank, they are limited to 64 characters,
61
+ and they cannot contain newlines. These restrictions can be overrided
62
+ by setting various field options.
63
+
64
+ Defining a field without any options works well for a simple text input.
65
+ If the default character limit is too big or too small you can override
66
+ it by setting the `char_limit` option. For example:
67
+
68
+ field :title, char_limit: 200
69
+
70
+
71
+ If you are dealing with textareas (i.e. multiple lines of text) then you can
72
+ set the `multiline` option to allow newlines. For example:
73
+
74
+ field :description, char_limit: 500, multiline: true
75
+
76
+
77
+ Error messages will include the field label, which by default is set to the
78
+ field name, capitalized, and with underscores replace by spaces. If you want
79
+ to override this, set the `label` option. For example:
80
+
81
+ field :twitter, label: 'Twitter Username'
82
+
83
+
84
+ If you want to validate that the field value matches a specific pattern you
85
+ can specify the `pattern` option. This is useful for validating things with
86
+ well defined formats, like numbers. For example:
87
+
88
+ field :number, pattern: /\A[1-9]\d*\z/
89
+
90
+ field :card_security_code, char_limit: 5, value: /\A\d+\z/
91
+
92
+
93
+ If you want to validate that the field value belongs to a set of predefined
94
+ values then you can specify the `values` option. This is useful for dealing
95
+ with input from select boxes, where the values are known upfront. For example:
96
+
97
+ field :card_expiry_month, values: (1..12).map(&:to_s)
98
+
99
+
100
+ The `values` option is also useful for checkboxes. Specify the `key_required`
101
+ option to handle the case where the checkbox is unchecked. For example:
102
+
103
+ field :accept_terms, values: %w(true), key_required: false
104
+
105
+
106
+ Sometimes you'll have a field with multiple values. A multiple select input,
107
+ a set of checkboxes. For this case you can specify the `multiple` option to
108
+ allow multiple values. For example:
109
+
110
+ field :colour, multiple: true, values: Colour.keys
111
+
112
+
113
+ Unlike all the other examples so far, reading the attribute that corresponds
114
+ to this field will return an array of strings instead of a single string.
115
+
116
+
117
+ Rails usage
118
+ -----------
119
+
120
+ This is the basic pattern for using a formeze form in a rails controller:
121
+
122
+ form = SomeForm.new
123
+ form.parse(request.raw_post)
124
+
125
+ if form.valid?
126
+ # do something with form data
127
+ else
128
+ # display form.errors to user
129
+ end
130
+
131
+
132
+ Formeze will automatically define optional "utf8" and "authenticity_token"
133
+ fields on every form so that you don't have to specify those manually.
data/Rakefile.rb ADDED
@@ -0,0 +1,7 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.test_files = FileList['spec/*_spec.rb']
7
+ end
data/formeze.gemspec ADDED
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'formeze'
3
+ s.version = '1.0.0'
4
+ s.platform = Gem::Platform::RUBY
5
+ s.authors = ['Tim Craft']
6
+ s.email = ['mail@timcraft.com']
7
+ s.homepage = 'http://github.com/timcraft/formeze'
8
+ s.description = 'A little library for defining classes to handle form data/input'
9
+ s.summary = 'See description'
10
+ s.files = Dir.glob('{lib,spec}/**/*') + %w(README.md Rakefile.rb formeze.gemspec)
11
+ s.require_path = 'lib'
12
+ end
data/lib/formeze.rb ADDED
@@ -0,0 +1,232 @@
1
+ require 'cgi'
2
+
3
+ module Formeze
4
+ class Label
5
+ def initialize(name)
6
+ @name = name
7
+ end
8
+
9
+ def to_s
10
+ @name.to_s.tr('_', ' ').capitalize
11
+ end
12
+ end
13
+
14
+ class Field
15
+ attr_reader :name
16
+
17
+ def initialize(name, options = {})
18
+ @name, @options = name, options
19
+ end
20
+
21
+ def validate(value, &error)
22
+ error.call(self, 'is required') if required? && value !~ /\S/
23
+
24
+ error.call(self, 'has too many lines') if !multiline? && value.lines.count > 1
25
+
26
+ error.call(self, 'has too many characters') if value.chars.count > char_limit
27
+
28
+ error.call(self, 'has too many words') if word_limit? && value.scan(/\w+/).length > word_limit
29
+
30
+ error.call(self, 'is invalid') if pattern? && value !~ pattern
31
+
32
+ error.call(self, 'is invalid') if values? && !values.include?(value)
33
+ end
34
+
35
+ def key
36
+ @key ||= @name.to_s
37
+ end
38
+
39
+ def key_required?
40
+ @options.fetch(:key_required) { true }
41
+ end
42
+
43
+ def label
44
+ @label ||= @options.fetch(:label) { Label.new(name) }
45
+ end
46
+
47
+ def required?
48
+ @options.fetch(:required) { true }
49
+ end
50
+
51
+ def multiline?
52
+ @options.fetch(:multiline) { false }
53
+ end
54
+
55
+ def multiple?
56
+ @options.fetch(:multiple) { false }
57
+ end
58
+
59
+ def char_limit
60
+ @options.fetch(:char_limit) { 64 }
61
+ end
62
+
63
+ def word_limit?
64
+ @options.has_key?(:word_limit)
65
+ end
66
+
67
+ def word_limit
68
+ @options.fetch(:word_limit)
69
+ end
70
+
71
+ def pattern?
72
+ @options.has_key?(:pattern)
73
+ end
74
+
75
+ def pattern
76
+ @options.fetch(:pattern)
77
+ end
78
+
79
+ def values?
80
+ @options.has_key?(:values)
81
+ end
82
+
83
+ def values
84
+ @options.fetch(:values)
85
+ end
86
+ end
87
+
88
+ module ArrayAttrAccessor
89
+ def array_attr_reader(name)
90
+ define_method(name) do
91
+ ivar = :"@#{name}"
92
+
93
+ values = instance_variable_get(ivar)
94
+
95
+ if values.nil?
96
+ values = []
97
+
98
+ instance_variable_set(ivar, values)
99
+ end
100
+
101
+ values
102
+ end
103
+ end
104
+
105
+ def array_attr_writer(name)
106
+ define_method(:"#{name}=") do |value|
107
+ ivar = :"@#{name}"
108
+
109
+ values = instance_variable_get(ivar)
110
+
111
+ if values.nil?
112
+ instance_variable_set(ivar, [value])
113
+ else
114
+ values << value
115
+ end
116
+ end
117
+ end
118
+
119
+ def array_attr_accessor(name)
120
+ array_attr_reader(name)
121
+ array_attr_writer(name)
122
+ end
123
+ end
124
+
125
+ module ClassMethods
126
+ include ArrayAttrAccessor
127
+
128
+ def fields
129
+ @fields ||= []
130
+ end
131
+
132
+ def field(*args)
133
+ field = Field.new(*args)
134
+
135
+ fields << field
136
+
137
+ if field.multiple?
138
+ array_attr_accessor field.name
139
+ else
140
+ attr_accessor field.name
141
+ end
142
+ end
143
+
144
+ def guard(&block)
145
+ fields << block
146
+ end
147
+
148
+ def checks
149
+ @checks ||= []
150
+ end
151
+
152
+ def check(&block)
153
+ checks << block
154
+ end
155
+
156
+ def errors
157
+ @errors ||= []
158
+ end
159
+
160
+ def error(message)
161
+ errors << message
162
+ end
163
+ end
164
+
165
+ class KeyError < StandardError; end
166
+
167
+ class ValueError < StandardError; end
168
+
169
+ class UserError < StandardError; end
170
+
171
+ module InstanceMethods
172
+ def parse(encoded_form_data)
173
+ form_data = CGI.parse(encoded_form_data)
174
+
175
+ self.class.fields.each do |field|
176
+ unless field.respond_to?(:key)
177
+ instance_eval(&field) ? return : next
178
+ end
179
+
180
+ unless form_data.has_key?(field.key)
181
+ next if field.multiple? || !field.key_required?
182
+
183
+ raise KeyError
184
+ end
185
+
186
+ values = form_data.delete(field.key)
187
+
188
+ if values.length > 1
189
+ raise ValueError unless field.multiple?
190
+ end
191
+
192
+ values.each do |value|
193
+ field.validate(value) do |error|
194
+ errors << UserError.new("#{field.label} #{error}")
195
+ end
196
+
197
+ send(:"#{field.name}=", value)
198
+ end
199
+ end
200
+
201
+ raise KeyError unless form_data.empty?
202
+
203
+ self.class.checks.zip(self.class.errors) do |check, error|
204
+ instance_eval(&check) ? next : errors << UserError.new(error)
205
+ end
206
+ end
207
+
208
+ def errors
209
+ @errors ||= []
210
+ end
211
+
212
+ def valid?
213
+ errors.empty?
214
+ end
215
+ end
216
+
217
+ def self.setup(form)
218
+ form.send :include, InstanceMethods
219
+
220
+ form.extend ClassMethods
221
+
222
+ if on_rails?
223
+ form.field(:utf8, key_required: false)
224
+
225
+ form.field(:authenticity_token, key_required: false)
226
+ end
227
+ end
228
+
229
+ def self.on_rails?
230
+ defined?(Rails)
231
+ end
232
+ end
@@ -0,0 +1,399 @@
1
+ require 'minitest/autorun'
2
+
3
+ require 'formeze'
4
+
5
+ class FormWithField
6
+ Formeze.setup(self)
7
+
8
+ field :title
9
+ end
10
+
11
+ describe 'FormWithField' do
12
+ before do
13
+ @form = FormWithField.new
14
+ end
15
+
16
+ describe 'title method' do
17
+ it 'should return nil' do
18
+ @form.title.must_be_nil
19
+ end
20
+ end
21
+
22
+ describe 'title equals method' do
23
+ it 'should set the value of the title attribute' do
24
+ @form.title = 'Untitled'
25
+ @form.title.must_equal('Untitled')
26
+ end
27
+ end
28
+
29
+ describe 'parse method' do
30
+ it 'should set the value of the title attribute' do
31
+ @form.parse('title=Untitled')
32
+ @form.title.must_equal('Untitled')
33
+ end
34
+
35
+ it 'should raise an exception when the key is missing' do
36
+ proc { @form.parse('') }.must_raise(Formeze::KeyError)
37
+ end
38
+
39
+ it 'should raise an exception when there are multiple values for the key' do
40
+ proc { @form.parse('title=foo&title=bar') }.must_raise(Formeze::ValueError)
41
+ end
42
+
43
+ it 'should raise an exception when there is an unexpected key' do
44
+ proc { @form.parse('title=Untitled&foo=bar') }.must_raise(Formeze::KeyError)
45
+ end
46
+ end
47
+ end
48
+
49
+ describe 'FormWithField after parsing valid input' do
50
+ before do
51
+ @form = FormWithField.new
52
+ @form.parse('title=Untitled')
53
+ end
54
+
55
+ describe 'valid query method' do
56
+ it 'should return true' do
57
+ @form.valid?.must_equal(true)
58
+ end
59
+ end
60
+
61
+ describe 'errors method' do
62
+ it 'should return an empty array' do
63
+ @form.errors.must_be_instance_of(Array)
64
+ @form.errors.must_be_empty
65
+ end
66
+ end
67
+ end
68
+
69
+ describe 'FormWithField after parsing blank input' do
70
+ before do
71
+ @form = FormWithField.new
72
+ @form.parse('title=')
73
+ end
74
+
75
+ describe 'valid query method' do
76
+ it 'should return false' do
77
+ @form.valid?.must_equal(false)
78
+ end
79
+ end
80
+ end
81
+
82
+ describe 'FormWithField after parsing input containing newlines' do
83
+ before do
84
+ @form = FormWithField.new
85
+ @form.parse('title=This+is+a+product.%0AIt+is+very+lovely.')
86
+ end
87
+
88
+ describe 'valid query method' do
89
+ it 'should return false' do
90
+ @form.valid?.must_equal(false)
91
+ end
92
+ end
93
+ end
94
+
95
+ class FormWithOptionalField
96
+ Formeze.setup(self)
97
+
98
+ field :title, required: false
99
+ end
100
+
101
+ describe 'FormWithOptionalField after parsing blank input' do
102
+ before do
103
+ @form = FormWithOptionalField.new
104
+ @form.parse('title=')
105
+ end
106
+
107
+ describe 'valid query method' do
108
+ it 'should return true' do
109
+ @form.valid?.must_equal(true)
110
+ end
111
+ end
112
+ end
113
+
114
+ class FormWithFieldThatCanHaveMultipleLines
115
+ Formeze.setup(self)
116
+
117
+ field :description, multiline: true
118
+ end
119
+
120
+ describe 'FormWithFieldThatCanHaveMultipleLines after parsing input containing newlines' do
121
+ before do
122
+ @form = FormWithFieldThatCanHaveMultipleLines.new
123
+ @form.parse('description=This+is+a+product.%0AIt+is+very+lovely.')
124
+ end
125
+
126
+ describe 'valid query method' do
127
+ it 'should return true' do
128
+ @form.valid?.must_equal(true)
129
+ end
130
+ end
131
+ end
132
+
133
+ class FormWithCharacterLimitedField
134
+ Formeze.setup(self)
135
+
136
+ field :title, char_limit: 16
137
+ end
138
+
139
+ describe 'FormWithCharacterLimitedField after parsing input with too many characters' do
140
+ before do
141
+ @form = FormWithCharacterLimitedField.new
142
+ @form.parse('title=This+Title+Will+Be+Too+Long')
143
+ end
144
+
145
+ describe 'valid query method' do
146
+ it 'should return false' do
147
+ @form.valid?.must_equal(false)
148
+ end
149
+ end
150
+ end
151
+
152
+ class FormWithWordLimitedField
153
+ Formeze.setup(self)
154
+
155
+ field :title, word_limit: 2
156
+ end
157
+
158
+ describe 'FormWithWordLimitedField after parsing input with too many words' do
159
+ before do
160
+ @form = FormWithWordLimitedField.new
161
+ @form.parse('title=This+Title+Will+Be+Too+Long')
162
+ end
163
+
164
+ describe 'valid query method' do
165
+ it 'should return false' do
166
+ @form.valid?.must_equal(false)
167
+ end
168
+ end
169
+ end
170
+
171
+ class FormWithFieldThatMustMatchPattern
172
+ Formeze.setup(self)
173
+
174
+ field :number, pattern: /\A\d+\z/
175
+ end
176
+
177
+ describe 'FormWithFieldThatMustMatchPattern after parsing input that matches the pattern' do
178
+ before do
179
+ @form = FormWithFieldThatMustMatchPattern.new
180
+ @form.parse('number=12345')
181
+ end
182
+
183
+ describe 'valid query method' do
184
+ it 'should return true' do
185
+ @form.valid?.must_equal(true)
186
+ end
187
+ end
188
+ end
189
+
190
+ describe 'FormWithFieldThatMustMatchPattern after parsing input that does not match the pattern' do
191
+ before do
192
+ @form = FormWithFieldThatMustMatchPattern.new
193
+ @form.parse('number=notanumber')
194
+ end
195
+
196
+ describe 'valid query method' do
197
+ it 'should return false' do
198
+ @form.valid?.must_equal(false)
199
+ end
200
+ end
201
+ end
202
+
203
+ class FormWithFieldThatCanHaveMultipleValues
204
+ Formeze.setup(self)
205
+
206
+ field :colour, multiple: true
207
+ end
208
+
209
+ describe 'FormWithFieldThatCanHaveMultipleValues' do
210
+ before do
211
+ @form = FormWithFieldThatCanHaveMultipleValues.new
212
+ end
213
+
214
+ describe 'colour method' do
215
+ it 'should return an empty array' do
216
+ @form.colour.must_be_instance_of(Array)
217
+ @form.colour.must_be_empty
218
+ end
219
+ end
220
+
221
+ describe 'colour equals method' do
222
+ it 'should add the argument to the colour attribute array' do
223
+ @form.colour = 'black'
224
+ @form.colour.must_include('black')
225
+ end
226
+ end
227
+
228
+ describe 'parse method' do
229
+ it 'should add the value to the colour attribute array' do
230
+ @form.parse('colour=black')
231
+ @form.colour.must_include('black')
232
+ end
233
+
234
+ it 'should not raise an exception when there are multiple values for the key' do
235
+ @form.parse('colour=black&colour=white')
236
+ end
237
+
238
+ it 'should not raise an exception when the key is missing' do
239
+ @form.parse('')
240
+ end
241
+ end
242
+ end
243
+
244
+ describe 'FormWithFieldThatCanHaveMultipleValues after parsing input with multiple values' do
245
+ before do
246
+ @form = FormWithFieldThatCanHaveMultipleValues.new
247
+ @form.parse('colour=black&colour=white')
248
+ end
249
+
250
+ describe 'colour method' do
251
+ it 'should return an array containing the values' do
252
+ @form.colour.must_be_instance_of(Array)
253
+ @form.colour.must_include('black')
254
+ @form.colour.must_include('white')
255
+ end
256
+ end
257
+
258
+ describe 'valid query method' do
259
+ it 'should return true' do
260
+ @form.valid?.must_equal(true)
261
+ end
262
+ end
263
+ end
264
+
265
+ describe 'FormWithFieldThatCanHaveMultipleValues after parsing input with no values' do
266
+ before do
267
+ @form = FormWithFieldThatCanHaveMultipleValues.new
268
+ @form.parse('')
269
+ end
270
+
271
+ describe 'colour method' do
272
+ it 'should return an empty array' do
273
+ @form.colour.must_be_instance_of(Array)
274
+ @form.colour.must_be_empty
275
+ end
276
+ end
277
+
278
+ describe 'valid query method' do
279
+ it 'should return true' do
280
+ @form.valid?.must_equal(true)
281
+ end
282
+ end
283
+ end
284
+
285
+ class FormWithFieldThatCanOnlyHaveSpecifiedValues
286
+ Formeze.setup(self)
287
+
288
+ field :answer, values: %w(yes no)
289
+ end
290
+
291
+ describe 'FormWithFieldThatCanOnlyHaveSpecifiedValues after parsing input with an invalid value' do
292
+ before do
293
+ @form = FormWithFieldThatCanOnlyHaveSpecifiedValues.new
294
+ @form.parse('answer=maybe')
295
+ end
296
+
297
+ describe 'valid query method' do
298
+ it 'should return false' do
299
+ @form.valid?.must_equal(false)
300
+ end
301
+ end
302
+ end
303
+
304
+ class FormWithGuard
305
+ Formeze.setup(self)
306
+
307
+ field :delivery_address
308
+
309
+ field :same_address, values: %w(yes no)
310
+
311
+ guard { same_address? }
312
+
313
+ field :billing_address
314
+
315
+ def same_address?
316
+ same_address == 'yes'
317
+ end
318
+ end
319
+
320
+ describe 'FormWithGuard after parsing input with same_address set and no billing address' do
321
+ before do
322
+ @form = FormWithGuard.new
323
+ @form.parse('delivery_address=123+Main+St&same_address=yes')
324
+ end
325
+
326
+ describe 'valid query method' do
327
+ it 'should return true' do
328
+ @form.valid?.must_equal(true)
329
+ end
330
+ end
331
+ end
332
+
333
+ class FormWithCustomValidation
334
+ Formeze.setup(self)
335
+
336
+ field :email
337
+
338
+ check { email.include?(?@) }
339
+ error 'Email is invalid'
340
+ end
341
+
342
+ describe 'FormWithCustomValidation after parsing invalid input' do
343
+ before do
344
+ @form = FormWithCustomValidation.new
345
+ @form.parse('email=alice')
346
+ end
347
+
348
+ describe 'valid query method' do
349
+ it 'should return false' do
350
+ @form.valid?.must_equal(false)
351
+ end
352
+ end
353
+ end
354
+
355
+ class FormWithOptionalKey
356
+ Formeze.setup(self)
357
+
358
+ field :accept_terms, values: %w(true), key_required: false
359
+ end
360
+
361
+ describe 'FormWithOptionalKey after parsing input without the key' do
362
+ before do
363
+ @form = FormWithOptionalKey.new
364
+ @form.parse('')
365
+ end
366
+
367
+ describe 'valid query method' do
368
+ it 'should return true' do
369
+ @form.valid?.must_equal(true)
370
+ end
371
+ end
372
+ end
373
+
374
+ Rails = Object.new
375
+
376
+ class RailsForm
377
+ Formeze.setup(self)
378
+
379
+ field :title
380
+ end
381
+
382
+ describe 'RailsForm' do
383
+ before do
384
+ @form = RailsForm.new
385
+ end
386
+
387
+ describe 'parse method' do
388
+ it 'should automatically process the utf8 and authenticity_token parameters' do
389
+ @form.parse('utf8=%E2%9C%93&authenticity_token=5RMc3sPZdR%2BZz4onNS8NfK&title=Test')
390
+ @form.authenticity_token.wont_be_empty
391
+ @form.utf8.wont_be_empty
392
+ end
393
+
394
+ it 'should not complain if the utf8 or authenticity_token parameters are missing' do
395
+ @form.parse('utf8=%E2%9C%93&title=Test')
396
+ @form.parse('authenticity_token=5RMc3sPZdR%2BZz4onNS8NfK&title=Test')
397
+ end
398
+ end
399
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: formeze
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Tim Craft
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-09 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: A little library for defining classes to handle form data/input
15
+ email:
16
+ - mail@timcraft.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/formeze.rb
22
+ - spec/formeze_spec.rb
23
+ - README.md
24
+ - Rakefile.rb
25
+ - formeze.gemspec
26
+ homepage: http://github.com/timcraft/formeze
27
+ licenses: []
28
+ post_install_message:
29
+ rdoc_options: []
30
+ require_paths:
31
+ - lib
32
+ required_ruby_version: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubyforge_project:
46
+ rubygems_version: 1.8.10
47
+ signing_key:
48
+ specification_version: 3
49
+ summary: See description
50
+ test_files: []