not_naughty 0.3

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/CHANGELOG ADDED
@@ -0,0 +1,22 @@
1
+ === 0.3 (2008-02-09)
2
+
3
+ * renamed to NotNaughty - The Validation Framework
4
+ * made predefined Validation optional, you can load them with:
5
+
6
+ ::NotNaughty::Validation.load '*'
7
+
8
+ or ordered (should be preferred):
9
+
10
+ ::NotNaughty::Validation.load :acceptance, :confirmation, :format, :length,
11
+ :numericality, :presence
12
+
13
+ * specs pass, 100% code coverage, 2 not complete, so pending specs
14
+ * docs added
15
+
16
+ === 0.2 (2008-02-09)
17
+
18
+ * complete rewrite
19
+
20
+ === 0.1 (2008-02-06)
21
+
22
+ * initial release
data/COPYING ADDED
@@ -0,0 +1,18 @@
1
+ Copyright (c) 2007-2008 Florian Aßmann
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to
5
+ deal in the Software without restriction, including without limitation the
6
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7
+ sell copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
16
+ THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
17
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
18
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,62 @@
1
+ = NotNaughty - The Validation Framework
2
+
3
+ <b>Features:</b>
4
+ * API compatible to Sequel as plugin (see Sequel::Plugins::NotNaughty for
5
+ details):
6
+
7
+ class User < Sequel::Model
8
+ is :not_naughty
9
+ validates { presence_of :username and length_of :username, :within => 4..16 }
10
+ has_validations? # => true
11
+
12
+ # ...
13
+ end
14
+
15
+ * Syntactical Sugar with Builder methods:
16
+
17
+ validates(:username, :password) {length :minimum => 6}
18
+ validates(:password) {confirmed and complexity :level => :high}
19
+ validates(:if => :necessary?) {bunchyness_of :crap}
20
+
21
+ * Beautiful error messages:
22
+
23
+ validates_presence_of :person,
24
+ :message => '#{"%s".humanize} is gone missing.'
25
+
26
+ * Conditional Validations:
27
+
28
+ validates(:if => :necessary?) {...}
29
+ validates(:unless => proc {|obj| obj.vip?}) {...}
30
+
31
+ * Easy to adapt and to extent:
32
+
33
+ _adapt_
34
+ require 'rubygems'
35
+ require 'not_naughty'
36
+ Person = Struct.new(:name) do
37
+ extend NotNaughty
38
+ validates(:name) { presence and length :minimum => 4 }
39
+ validated_before :clone
40
+ validated_before :dup, :without => :exception
41
+ end
42
+ Person.new('Horst').valid? # => true
43
+ Person.new('Foo').valid? # => false
44
+ Person.new('Foo').clone # goes *boom* with NotNaughty::ValidationException
45
+ Person.new('Foo').dup # => false
46
+
47
+ _extend_
48
+ class BunchynessValidation < NotNaughty::Validation
49
+ def initialize(opts, attributes)
50
+ __message = opts[:message] || '#{"%s".humanize} is not bunchy.'
51
+ super opts, attributes do |o, a, v|
52
+ o.errors.add(a, __message) unless v.respond_to? :to_bunchy
53
+ end
54
+ end
55
+ end
56
+
57
+ Thingy = Struct.new(:bunchy_item) do
58
+ extend NotNaughty
59
+ validates_bunchyness_of :bunchy_item
60
+ end
61
+
62
+ :include: COPYING
data/Rakefile ADDED
@@ -0,0 +1,130 @@
1
+ require "rake"
2
+ require "rake/clean"
3
+ require "rake/gempackagetask"
4
+ require "rake/rdoctask"
5
+ require "fileutils"
6
+ include FileUtils
7
+
8
+ ##############################################################################
9
+ # Configuration
10
+ ##############################################################################
11
+ NAME = "not_naughty"
12
+ VERS = "0.3"
13
+ CLEAN.include ["**/.*.sw?", "pkg/*", ".config", "doc/*", "coverage/*"]
14
+ RDOC_OPTS = [
15
+ "--quiet",
16
+ "--title", "NotNaughty: The Validation Framework",
17
+ "--opname", "index.html",
18
+ "--inline-source",
19
+ "--line-numbers",
20
+ "--main", "README",
21
+ "--inline-source",
22
+ "--charset", "utf-8"
23
+ ]
24
+
25
+ ##############################################################################
26
+ # RDoc
27
+ ##############################################################################
28
+ task :doc => [:rdoc]
29
+
30
+ Rake::RDocTask.new do |rdoc|
31
+ rdoc.rdoc_dir = "doc/rdoc"
32
+ rdoc.options += RDOC_OPTS
33
+ rdoc.main = "README"
34
+ rdoc.title = "NotNaughty: The Validation Framework"
35
+ rdoc.rdoc_files.add %w[README COPYING lib/**/*.rb]
36
+ end
37
+
38
+ task :doc_rforge => [:doc]
39
+
40
+ desc "Update docs and upload to rubyforge.org"
41
+ task :doc_rforge do
42
+ # sh %{scp -r doc/rdoc/* boof@rubyforge.org:/var/www/gforge-projects/not-naughty}
43
+ end
44
+
45
+ ##############################################################################
46
+ # specs
47
+ ##############################################################################
48
+ require "spec/rake/spectask"
49
+
50
+ desc "Run specs with coverage"
51
+ Spec::Rake::SpecTask.new("spec") do |t|
52
+ t.spec_files = FileList["spec/**/*_spec.rb"]
53
+ t.spec_opts = File.read("spec/spec.opts").split("\n")
54
+ t.rcov_opts = File.read("spec/rcov.opts").split("\n")
55
+ t.rcov = true
56
+ end
57
+
58
+ desc "Run specs without coverage"
59
+ Spec::Rake::SpecTask.new("spec_no_cov") do |t|
60
+ t.spec_files = FileList["spec/**/*_spec.rb"]
61
+ t.spec_opts = File.read("spec/spec.opts").split("\n")
62
+ end
63
+
64
+ desc "check documentation coverage"
65
+ task :dcov do
66
+ sh "find lib -name '*.rb' | xargs dcov"
67
+ end
68
+
69
+ ##############################################################################
70
+ # Gem packaging
71
+ ##############################################################################
72
+ desc "Packages up NotNaughty."
73
+ task :default => [:package]
74
+ task :package => [:clean]
75
+
76
+ spec = Gem::Specification.new do |s|
77
+ s.name = NAME
78
+ s.rubyforge_project = NAME
79
+ s.version = VERS
80
+ s.platform = Gem::Platform::RUBY
81
+ s.has_rdoc = true
82
+ s.extra_rdoc_files = ["README", "CHANGELOG", "COPYING"]
83
+ s.summary = "Heavily armed validation framework."
84
+ s.description = s.summary
85
+ s.author = "Florian Aßmann"
86
+ s.email = "florian.assmann@oniversus.de"
87
+ s.homepage = "http://not-naughty.rubyforge.org"
88
+ s.required_ruby_version = ">= 1.8.4"
89
+
90
+ s.files = %w(COPYING README Rakefile) + Dir.glob("{doc,spec,lib}/**/*")
91
+
92
+ s.require_path = "lib"
93
+ end
94
+
95
+ Rake::GemPackageTask.new(spec) do |p|
96
+ p.need_tar = true
97
+ p.gem_spec = spec
98
+ end
99
+
100
+ ##############################################################################
101
+ # installation & removal
102
+ ##############################################################################
103
+ task :install do
104
+ sh %{rake package}
105
+ sh %{sudo gem install pkg/#{NAME}-#{VERS}}
106
+ end
107
+
108
+ task :install_no_docs do
109
+ sh %{rake package}
110
+ sh %{sudo gem install pkg/#{NAME}-#{VERS} --no-rdoc --no-ri}
111
+ end
112
+
113
+ task :uninstall => [:clean] do
114
+ sh %{sudo gem uninstall #{NAME}}
115
+ end
116
+
117
+ task :tag do
118
+ cwd = FileUtils.pwd
119
+ sh %{cd .. && svn copy #{cwd} tags/#{NAME}-#{VERS} && svn commit -m "#{NAME}-#{VERS} tag." tags}
120
+ end
121
+
122
+ ##############################################################################
123
+ # gem and rdoc release
124
+ ##############################################################################
125
+ task :release => [:package] do
126
+ sh %{rubyforge login}
127
+ sh %{rubyforge add_release #{NAME} #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.tgz}
128
+ sh %{rubyforge add_file #{NAME} #{NAME} #{VERS} pkg/#{NAME}-#{VERS}.gem}
129
+ end
130
+
@@ -0,0 +1,68 @@
1
+ module NotNaughty
2
+
3
+ # == Builder that builds
4
+ #
5
+ # With this you get syntactical sugar for all descendants of Validation, see
6
+ # validates for examples.
7
+ module Builder
8
+
9
+ # Observer method that creates Validation builder methods.
10
+ #
11
+ # A Validation with the class name TestValidation will get the
12
+ # builder method <tt>validate_test_of</tt> that adds a validation via
13
+ # add_validation in the current <tt>validator</tt>.
14
+ def self.update(validation)
15
+ if basename = validation.name[/([^:]+)Validation$/, 1]
16
+ define_method 'validates_%s_of' % basename.downcase do |*params|
17
+ validator.add_validation(validation, *params)
18
+ end
19
+ end
20
+ end
21
+
22
+ # == Syntactic sugar.
23
+ #
24
+ # <b>Example:</b>
25
+ # validates { presence_of :example, :if => :reading? }
26
+ # # => validate_presence_of :example, :if => :reading?
27
+ # validates(:name) { presence and length :minimum => 6 }
28
+ # # => validates_presence_of :name
29
+ # validates_length_of :name, :minimum => 6
30
+ # validates(:temp, :if => water?) { value :lt => 100 }
31
+ # # => validates_value_of :temp, :lt => 100, :if => water?
32
+ def validates(*params, &block)
33
+ ValidationDelegator.new(self, *params).instance_eval(&block)
34
+ end
35
+
36
+ # Allows adding validations the legacy way.
37
+ def validates_each(*attributes, &block)
38
+ validator.add_validation(*attributes, &block)
39
+ end
40
+
41
+ class ValidationDelegator < SimpleDelegator #:nodoc:all
42
+ def initialize(receiver, *params)
43
+ @_sd_obj_opts = params.extract_options!
44
+ @_sd_obj_params = params
45
+
46
+ super receiver
47
+ end
48
+ def method_missing(method_sym, *params) #:nodoc:
49
+ method_sym, params = if @_sd_obj_params.any?
50
+ opts = @_sd_obj_opts.update params.extract_options!
51
+ [:"validates_#{method_sym}_of", @_sd_obj_params + [opts]]
52
+ else
53
+ opts = @_sd_obj_opts.update params.extract_options!
54
+ [:"validates_#{method_sym}", params + [opts]]
55
+ end
56
+
57
+ if @_sd_obj.respond_to? method_sym
58
+ @_sd_obj.send!(method_sym, *params)
59
+ return true
60
+ else
61
+ raise NoMethodError,
62
+ "unable to evaluate ´#{method_sym}(*#{params.inspect})'"
63
+ end
64
+ end
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,61 @@
1
+ module NotNaughty
2
+
3
+ # == Container for failed validations.
4
+ #
5
+ # ...
6
+ class Errors
7
+ extend Forwardable
8
+
9
+ def_delegators :@errors, :empty?, :clear, :[], :each, :to_yaml
10
+
11
+ include Enumerable
12
+
13
+ def initialize() #:nodoc:
14
+ @errors = Hash.new {|h, k| h[k] = []}
15
+ end
16
+ # Adds an error for the given attribute.
17
+ def add(k, msg) @errors[k] << msg end
18
+
19
+ # Returns an array of fully-formatted error messages.
20
+ def full_messages
21
+ @errors.inject([]) do |messages, k_errors| k, errors = *k_errors
22
+ errors.each {|e| messages << eval(e.inspect.delete('\\') % k) }
23
+ messages
24
+ end
25
+ end
26
+
27
+ # Returns an array of evaluated error messages for given attribute.
28
+ def on(attribute)
29
+ @errors[attribute].map do |message|
30
+ eval(message.inspect.delete('\\') % attribute)
31
+ end
32
+ end
33
+
34
+ # Returns a ValidationException with <tt>self</tt> in <tt>:errors</tt>.
35
+ def to_exception
36
+ ValidationException.new self
37
+ end
38
+ end
39
+
40
+ # == Exception class for NotNaughty
41
+ #
42
+ # Includes the instance of Errors that caused the Exception.
43
+ class ValidationException < RuntimeError
44
+ extend Forwardable
45
+
46
+ attr_reader :errors
47
+ def_delegators :@errors, :on, :full_messages, :each
48
+
49
+ # Returns instance of ValidationError with errors set
50
+ def initialize(errors)
51
+ @errors = errors
52
+
53
+ if errors.any?
54
+ super 'validation errors'
55
+ else
56
+ super 'no validation errors'
57
+ end
58
+ end
59
+ end
60
+
61
+ end
@@ -0,0 +1,20 @@
1
+ module NotNaughty
2
+ module InstanceMethods
3
+
4
+ # Returns instance of Errors.
5
+ def errors() @errors ||= ::NotNaughty::Errors.new end
6
+
7
+ # Returns true if all validations passed
8
+ def valid?
9
+ validate
10
+ errors.empty?
11
+ end
12
+
13
+ # Clears errors and validates.
14
+ def validate
15
+ errors.clear
16
+ self.class.validator.invoke self
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,126 @@
1
+ module NotNaughty
2
+
3
+ # == The superclass for Validations.
4
+ #
5
+ # See new for more information.
6
+ class Validation
7
+
8
+ BASEDIR = File.dirname __FILE__
9
+ # Loader pattern
10
+ PATTERN = File.join BASEDIR, %w[validations %s_validation.rb]
11
+
12
+ # Loads validations.
13
+ def self.load(*validations)
14
+ validations.each do |validation|
15
+ Dir.glob(PATTERN % validation).each { |path| require path }
16
+ end
17
+ end
18
+
19
+ extend Observable
20
+ def self.inherited(descendant) #:nodoc:
21
+ changed and notify_observers(descendant)
22
+
23
+ descendant.
24
+ instance_variable_set :@observer_peers, @observer_peers.clone
25
+ end
26
+
27
+ # Builds validations.
28
+ #
29
+ # <b>Example:</b>
30
+ # NotNaughty::Validation.new :temp, :if => :water? do |obj, attr, val|
31
+ # obj.errors.add attr, 'too hot' unless val < 100
32
+ # end
33
+ #
34
+ # <b>Like:</b>
35
+ # class TempValidation < NotNaughty::Validation
36
+ # def initialize(opts, attributes)
37
+ # super opts, attributes method(:temp_validation)
38
+ # end
39
+ # def temp_validation(obj, attr, val)
40
+ # obj.errors.add attr, 'too hot' unless val < 100
41
+ # end
42
+ # end
43
+ #
44
+ # Validation.new TempValidation, :temp, :if => :water?
45
+ #
46
+ # The last one also notifies all Observers of Validation (see
47
+ # Builder#update). If Builder#update is called because <Name>Validation
48
+ # is inherited from Validation the ValidationBuilder gets the method
49
+ # validates_<name>_of and so does the classes that included the Builder.
50
+ def self.new(*params, &block)
51
+ attributes = if params.first.is_a? Class and params.first < self
52
+ klass = params.shift
53
+ klass.new(*params, &block)
54
+ else
55
+ options = params.extract_options!
56
+ instance = allocate
57
+ instance.send! :initialize, options, params.map {|p|p.to_sym}, &block
58
+ instance
59
+ end
60
+ end
61
+
62
+ attr_reader :attributes
63
+
64
+ def initialize(opts, attributes, &block) #:nodoc:
65
+ build_conditions opts[:if], opts[:unless]
66
+ @attributes, @block, @opts = attributes, block, opts
67
+ end
68
+
69
+ def call_without_conditions(obj, attr, value) #:nodoc:
70
+ @block.call obj, attr, value
71
+ end
72
+ alias_method :call, :call_without_conditions
73
+
74
+ def call_with_conditions(obj, attr, value) #:nodoc:
75
+ if @conditions.all? { |c| c.evaluate obj }
76
+ call_without_conditions obj, attr, value
77
+ end
78
+ end
79
+
80
+ protected
81
+ def build_conditions(p, n) #:nodoc:
82
+ @conditions = []
83
+ [p].flatten.each {|c| @conditions << Condition.new(c) if c }
84
+ [n].flatten.each {|c| @conditions << Condition.new(c, false) if c }
85
+
86
+ (class << self; self; end).module_eval do
87
+ alias_method :call, :call_with_conditions
88
+ end if @conditions.any?
89
+ end
90
+
91
+ # Conditions for use in Validations are usually used with Validations.
92
+ class Condition
93
+
94
+ # An instance of Condition accepts Symbols, UnboundMethods or anything
95
+ # that responds to :call.
96
+ #
97
+ # The following examples are similiar to each other:
98
+ #
99
+ # NotNaughty::Validation::Condition.new proc {|o| o.nil?}
100
+ # NotNaughty::Validation::Condition.new :nil?
101
+ # NotNaughty::Validation::Condition.new Object.instance_method(:nil?)
102
+ def self.new(condition, positive = true)
103
+ instance = allocate
104
+ instance.instance_variable_set :@condition, condition
105
+
106
+ block = case condition
107
+ when Symbol then positive ?
108
+ proc { |o| o.send! @condition } :
109
+ proc { |o| not o.send! @condition }
110
+ when UnboundMethod then positive ?
111
+ proc { |o| @condition.bind(o).call } :
112
+ proc { |o| not @condition.bind(o).call }
113
+ else positive ?
114
+ proc { |o| @condition.call o } :
115
+ proc { |o| not @condition.call o }
116
+ end
117
+
118
+ (class << instance; self; end).
119
+ module_eval { define_method(:evaluate, &block) }
120
+
121
+ instance
122
+ end
123
+
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,39 @@
1
+ module NotNaughty
2
+
3
+ # == Validates acceptance of obj's attribute against a fixed value.
4
+ #
5
+ # Unless the validation succeeds an error hash (:attribute => :message)
6
+ # is added to the obj's instance of Errors.
7
+ #
8
+ # <b>Options:</b>
9
+ # <tt>:accept</tt>:: object that that'll check via a <tt>:match</tt> call
10
+ # <tt>:message</tt>:: see NotNaughty::Errors for details
11
+ # <tt>:if</tt>:: see NotNaughty::Validation::Condition for details
12
+ # <tt>:unless</tt>:: see NotNaughty::Validation::Condition for details
13
+ #
14
+ # <b>Example:</b>
15
+ #
16
+ # obj = 'abc'
17
+ # def obj.errors() @errors ||= NotNauthy::Errors.new end
18
+ #
19
+ class AcceptanceValidation < Validation
20
+
21
+ def initialize(opts, attributes)
22
+ __message, __accept =
23
+ opts[:message] || '#{"%s".humanize} not accepted.',
24
+ opts[:accept] || '1'
25
+
26
+ if opts[:allow_blank] or opts.fetch(:allow_nil, true)
27
+ __allow = if opts[:allow_blank] then :blank? else :nil? end
28
+ super opts, attributes do |o, a, v|
29
+ o.errors.add a, __message unless v.send! __allow or __accept.eql? v
30
+ end
31
+ else
32
+ super opts, attributes do |o, a, v|
33
+ o.errors.add a, __message unless __accept.eql? v
34
+ end
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ module NotNaughty
2
+
3
+ # == Validates confirmaton of obj's attribute via <tt>:eql?</tt> method against the _appropiate_ confirmation attribute.
4
+ #
5
+ # Unless the validation succeeds an error hash (:attribute => :message)
6
+ # is added to the obj's instance of Errors.
7
+ #
8
+ # <b>Options:</b>
9
+ # <tt>:message</tt>:: see NotNaughty::Errors for details
10
+ # <tt>:if</tt>:: see NotNaughty::Validation::Condition for details
11
+ # <tt>:unless</tt>:: see NotNaughty::Validation::Condition for details
12
+ #
13
+ # <b>Example:</b>
14
+ #
15
+ # obj = 'abc'
16
+ # def obj.errors() @errors ||= NotNauthy::Errors.new end
17
+ # def obj.to_s_confirmation() '123 end
18
+ #
19
+ # ConfirmationValidation.new({}, :to_s).call obj, :to_s, 'abc'
20
+ # obj.errors.on(:to_s).any? # => true
21
+ class ConfirmationValidation < Validation
22
+
23
+ def initialize(opts, attributes) #:nodoc:
24
+ __message = opts[:message] || '#{"%s".humanize} could not be confirmed.'
25
+
26
+ if opts[:allow_blank] or opts[:allow_nil]
27
+ __allow = if opts[:allow_blank] then :blank? else :nil? end
28
+ super opts, attributes do |o, a, v|
29
+ o.errors.add a, __message unless
30
+ v.send! __allow or o.send!(:"#{a}_confirmation").eql? v
31
+ end
32
+ else
33
+ super opts, attributes do |o, a, v|
34
+ o.errors.add a, __message unless
35
+ o.send!(:"#{a}_confirmation").eql? v
36
+ end
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,45 @@
1
+ module NotNaughty
2
+
3
+ # == Validates format of obj's attribute via the <tt>:match</tt> method.
4
+ #
5
+ # Unless the validation succeeds an error hash (:attribute => :message)
6
+ # is added to the obj's instance of Errors.
7
+ #
8
+ # <b>Options:</b>
9
+ # <tt>:with</tt>:: object that that'll check via a <tt>:match</tt> call
10
+ # <tt>:message</tt>:: see NotNaughty::Errors for details
11
+ # <tt>:if</tt>:: see NotNaughty::Validation::Condition for details
12
+ # <tt>:unless</tt>:: see NotNaughty::Validation::Condition for details
13
+ #
14
+ # <b>Example:</b>
15
+ #
16
+ # obj = 'abc'
17
+ # def obj.errors() @errors ||= NotNauthy::Errors.new end
18
+ #
19
+ # FormatValidation.new({:with => /[a-z]/}, :to_s).call obj, :to_s, 'abc'
20
+ # obj.errors.on(:to_s).any? # => false
21
+ #
22
+ # FormatValidation.new({:with => /[A-Z]/}, :to_s).call obj, :to_s, 'abc'
23
+ # obj.errors.on(:to_s) # => ["Format of to_s does not match."]
24
+ class FormatValidation < Validation
25
+
26
+ def initialize(opts, attributes) #:nodoc:
27
+ (__format = opts[:with]).respond_to? :match or
28
+ raise ArgumentError, "#{__format.inspect} doesn't respond to :match"
29
+
30
+ __message = opts[:message] || 'Format of %s does not match.'
31
+
32
+ if opts[:allow_blank] or opts[:allow_nil]
33
+ __allow = if opts[:allow_blank] then :blank? else :nil? end
34
+ super opts, attributes do |o, a, v|
35
+ o.errors.add a, __message unless v.send! __allow or __format.match v
36
+ end
37
+ else
38
+ super opts, attributes do |o, a, v|
39
+ o.errors.add a, __message unless __format.match v
40
+ end
41
+ end
42
+ end
43
+
44
+ end
45
+ end