hash_params 0.0.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +8 -8
  3. data/README.md +167 -109
  4. data/coverage/assets/0.9.0/application.css +799 -0
  5. data/coverage/assets/0.9.0/application.js +1707 -0
  6. data/coverage/assets/0.9.0/colorbox/border.png +0 -0
  7. data/coverage/assets/0.9.0/colorbox/controls.png +0 -0
  8. data/coverage/assets/0.9.0/colorbox/loading.gif +0 -0
  9. data/coverage/assets/0.9.0/colorbox/loading_background.png +0 -0
  10. data/coverage/assets/0.9.0/favicon_green.png +0 -0
  11. data/coverage/assets/0.9.0/favicon_red.png +0 -0
  12. data/coverage/assets/0.9.0/favicon_yellow.png +0 -0
  13. data/coverage/assets/0.9.0/loading.gif +0 -0
  14. data/coverage/assets/0.9.0/magnify.png +0 -0
  15. data/coverage/assets/0.9.0/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  16. data/coverage/assets/0.9.0/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  17. data/coverage/assets/0.9.0/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  18. data/coverage/assets/0.9.0/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  19. data/coverage/assets/0.9.0/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  20. data/coverage/assets/0.9.0/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  21. data/coverage/assets/0.9.0/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  22. data/coverage/assets/0.9.0/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  23. data/coverage/assets/0.9.0/smoothness/images/ui-icons_222222_256x240.png +0 -0
  24. data/coverage/assets/0.9.0/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  25. data/coverage/assets/0.9.0/smoothness/images/ui-icons_454545_256x240.png +0 -0
  26. data/coverage/assets/0.9.0/smoothness/images/ui-icons_888888_256x240.png +0 -0
  27. data/coverage/assets/0.9.0/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  28. data/coverage/index.html +1597 -369
  29. data/hash_params.gemspec +14 -5
  30. data/lib/hash_params/binding_validator.rb +26 -0
  31. data/lib/hash_params/hash_validator.rb +71 -0
  32. data/lib/hash_params/validator.rb +119 -0
  33. data/lib/hash_params/yaml_params.rb +93 -0
  34. data/lib/hash_params.rb +15 -142
  35. data/spec/hash_params_spec.rb +100 -63
  36. data/spec/spec_helper.rb +0 -11
  37. data/tmp/hash_params.rb +181 -0
  38. data/tmp/hash_params_spec.rb +102 -0
  39. data/tmp/module_spec.rb +27 -0
  40. data/tmp/var_spec.rb +9 -0
  41. data/tmp/yaml_params.rb +109 -0
  42. metadata +36 -3
data/hash_params.gemspec CHANGED
@@ -1,5 +1,5 @@
1
1
  # -*- encoding: utf-8 -*-
2
- require File.expand_path('../lib/hash_params', __FILE__)
2
+ require File.dirname(__FILE__) + '/lib/hash_params'
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = 'hash_params'
@@ -13,6 +13,17 @@ Gem::Specification.new do |s|
13
13
  s.description = 'hash-param allows you to declare, validate, and transform endpoint parameters as you would in frameworks like ActiveModel or DataMapper without the overhead.
14
14
  This gem is a variation of the sinatra-param gem https://github.com/mattt/sinatra-param modified to be more generic and with some additional features'
15
15
 
16
+ s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # gem.require_paths = ['lib']
22
+ # gem.files = %w(.yardopts CHANGELOG.md CONTRIBUTING.md LICENSE README.md UPGRADING.md Rakefile hashie.gemspec)
23
+ # gem.files += Dir['lib/**/*.rb']
24
+ # gem.files += Dir['spec/**/*.rb']
25
+ # gem.test_files = Dir['spec/**/*.rb']
26
+
16
27
 
17
28
  s.add_development_dependency 'rake'
18
29
  s.add_development_dependency 'minitest'
@@ -20,8 +31,6 @@ Gem::Specification.new do |s|
20
31
  s.add_development_dependency 'simplecov'
21
32
  s.add_development_dependency 'pry'
22
33
 
23
- s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
24
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
- s.require_paths = ["lib"]
34
+
35
+
27
36
  end
@@ -0,0 +1,26 @@
1
+ module HashParams
2
+ class BindingValidator
3
+
4
+ def with_binding (&code)
5
+ @binding = code.binding
6
+ instance_eval(&code)
7
+ end
8
+
9
+
10
+ def var(var_name, type, opts={})
11
+ raise 'Variable name must be a string or symbol' unless (var_name.is_a?(String) || var_name.is_a?(Symbol))
12
+ value = @binding.local_variable_get var_name
13
+ new_value = if value.is_a?(Hash)
14
+ if block_given?
15
+ #if the param is a hash then the validations are actually options
16
+ HashParams::HashValidator.new.validate_hash(value, opts, &Proc.new)
17
+ else
18
+ HashParams::HashValidator.new.validate_hash(value, opts)
19
+ end
20
+ else
21
+ HashParams.validate value, type, opts
22
+ end
23
+ @binding.local_variable_set var_name, new_value
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,71 @@
1
+ module HashParams
2
+
3
+
4
+ class HPHash < Hash
5
+ attr_accessor :validation_errors
6
+
7
+ def initialize(args=nil)
8
+ @validation_errors=[]
9
+ super(args)
10
+ end
11
+
12
+ def valid?
13
+ @validation_errors.empty?
14
+ end
15
+
16
+ def set_key_value(key, value, symbolize_key, make_method)
17
+ key = key.to_s.to_sym if symbolize_key
18
+ if make_method
19
+ singleton_class.module_eval do
20
+ define_method key.to_s.to_sym do
21
+ value
22
+ end
23
+ end
24
+ end
25
+ self[key]=value
26
+ end
27
+ end
28
+
29
+ class HashValidator
30
+
31
+ def validate_hash(h, options={})
32
+ #Hash Validation has to be stateful
33
+
34
+ @incoming = h
35
+ @outgoing = HPHash.new
36
+ @options = options
37
+
38
+ if block_given?
39
+ instance_eval(&Proc.new)
40
+ else
41
+ #no proc was given this means just pass the hash back as is
42
+ @outgoing = @incoming
43
+ end
44
+ @outgoing
45
+ end
46
+
47
+ def key(hash_key, type, opts={})
48
+ value = @incoming[hash_key] || @incoming[hash_key.to_s]
49
+ # if a block is given to the param then it's a recursive call
50
+ # recursive calls can only be done with a hash
51
+ new_value = if value.is_a?(Hash)
52
+ if block_given?
53
+ #if the param is a hash then the validations are actually options
54
+ HashParams::HashValidator.new.validate_hash(value, @options, &Proc.new)
55
+ else
56
+ HashParams::HashValidator.new.validate_hash(value, opts)
57
+ end
58
+ else
59
+ HashParams.validate value, type, opts
60
+ end
61
+ hash_key = opts[:as] if opts[:as]
62
+ @outgoing.set_key_value(hash_key, new_value, @options[:symbolize_keys], @options[:make_methods])
63
+ new_value
64
+ rescue => e
65
+ @outgoing.validation_errors << "Error processing key '#{hash_key}': #{e}" # [e.to_s, e.backtrace].join("\n")
66
+ raise e if @options[:raise_errors]
67
+ nil
68
+ end
69
+ alias_method :param, :key
70
+ end
71
+ end
@@ -0,0 +1,119 @@
1
+ module HashParams
2
+ module Validator
3
+
4
+ class ValidationError < StandardError
5
+ end
6
+
7
+ class CoercionError < StandardError
8
+ end
9
+
10
+ def with_binding(&code)
11
+ BindingValidator.new.with_binding(&code)
12
+ end
13
+
14
+ def validate(param, type, validations={})
15
+
16
+ coercions = Array(validations[:coerce]) << type
17
+
18
+ if param.nil? && validations[:default]
19
+ param = validations[:default].respond_to?(:call) ? validations[:default].call() : validations[:default]
20
+ end
21
+
22
+ #if there is a :validate lambda run that too
23
+ if validations[:validate] && validations[:validate].respond_to?(:call)
24
+ param = validations.delete(:validate).call(param)
25
+ end
26
+
27
+ if block_given? && !param.is_a?(Hash)
28
+ param = yield(param, validations)
29
+ end
30
+
31
+ # do all coercion and transformation first there could be an array of coersions they will be run in order
32
+ coercions.each do |c|
33
+ param = coerce(param, c, validations)
34
+ end
35
+
36
+ if param.is_a?(Hash)
37
+ param = if block_given?
38
+ HashParams::HashValidator.new.validate_hash(param, validations, &Proc.new)
39
+ else
40
+ HashParams::HashValidator.new.validate_hash(param, validations)
41
+ end
42
+ end
43
+
44
+ #don't bother with the rest if required parameter is missing
45
+ if validations[:required] && param.nil?
46
+ raise ValidationError.new('Required Parameter missing and has no default specified')
47
+ end
48
+
49
+ error = nil
50
+ validations.each do |key, value|
51
+ error = case key
52
+ when :blank
53
+ 'Parameter cannot be blank' if !value && (param.nil? || (param.respond_to?(:empty) && param.empty)) #)!value && blank?(value)
54
+ when :format
55
+ "#{param} must be a string if using the format validation" && next unless param.kind_of?(String)
56
+ "#{param} must match format #{value}" unless param =~ value
57
+ when :is
58
+ "#{param} must be #{value}" unless param === value
59
+ when :in, :within, :range
60
+ "#{param} must be within #{value}" unless value.respond_to?(:include) ? value.include?(param) : Array(value).include?(param)
61
+ when :min
62
+ "#{param} cannot be less than #{value}" unless value <= param
63
+ when :max
64
+ "#{param} cannot be greater than #{value}" unless value >= param
65
+ when :min_length
66
+ "#{param} cannot have length less than #{value}" unless value <= param.length
67
+ when :max_length
68
+ "#{param} cannot have length greater than #{value}" unless value >= param.length
69
+ else
70
+ nil
71
+ end
72
+ end
73
+
74
+ raise ValidationError.new(error) if error
75
+ param
76
+ end
77
+
78
+ private
79
+
80
+ def coerce(val, type, opts={})
81
+
82
+ # exceptions bubble up
83
+ #order is important
84
+
85
+ #if type is nil just return the object
86
+ return val if type.nil?
87
+
88
+ #two special types of transforms
89
+ #There is no Boolean type so we handle them special
90
+ if type.to_s == 'boolean'
91
+ return val if (val == true || val == false)
92
+ return false if /(false|f|no|n|0)$/i === val.to_s.downcase
93
+ return true if /(true|t|yes|y|1)$/i === val.to_s.downcase
94
+
95
+ # if we can't parse we return a nil
96
+ # maybe !!val is a better return?
97
+ raise CoercionError.new("Unable to coerce #{val} to boolean")
98
+ end
99
+ #they have given us a coercion which is a string or symbol which is not "boolean", we are going to cast this into a proc and run it
100
+
101
+ return type.to_proc.call(val) if type.is_a?(Symbol) || type.is_a?(String)
102
+ #could be a proc
103
+
104
+ return type.call(val) if type.respond_to?(:call)
105
+ #nothing but simple types left
106
+ return val if val.is_a?(type)
107
+ return Integer(val) if type == Integer
108
+ return Float(val) if type == Float
109
+ return String(val) if type == String
110
+ return Date.parse(val) if type == Date
111
+ return Time.parse(val) if type == Time
112
+ return DateTime.parse(val) if type == DateTime
113
+ return Array(val.split(opts[:delimiter] || ',')) if type == Array
114
+ return Hash[val.gsub(/[{}]/, '').gsub('}', '').split(opts[:delimiter] || ',').map { |c| c.split(opts[:separator] ||':').map { |i| i.strip } }] if type == Hash
115
+
116
+ raise CoercionError("Unable to coerce #{val} to #{type}")
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,93 @@
1
+ module YamlParams
2
+
3
+ ENVIRONMENT = ENV['YAML_PARAMS_ENV'] || (defined?(HashParams) && HashParams::ENVIRONMENT) || 'development'
4
+
5
+ def self.autoconfig(opts={})
6
+
7
+ script_name = File.basename($0)
8
+ script_dir = File.dirname($0)
9
+ home_dir = File.expand_path('~')
10
+ host_name = Socket.gethostname
11
+ special_file_names = opts.delete(:files)
12
+ special_file_names = Array(special_file_names && special_file_names.is_a?(String) && special_file_names.split(','))
13
+ special_roots = opts.delete(:roots)
14
+ special_roots = Array(special_roots && special_roots.is_a?(String) && special_roots.split(','))
15
+ app_name = opts.delete(:app_name) || script_name
16
+ env = opts.delete(:env) || opts.delete(:environment) || ENVIRONMENT
17
+ generated_hash = {}
18
+ all_file_names = []
19
+
20
+
21
+ #Sequence is important when constructing this list as later files will override the earlier ones
22
+ generic_file_names = %W(
23
+ settings.yml
24
+ config.yml
25
+ default.yml
26
+ #{env}.yml
27
+ #{host_name}.yml
28
+ #{host_name}_#{env}.yml
29
+ local.yml
30
+ local_#{env}.yml
31
+ settings.local.yml
32
+ settings.local_#{env}.yml
33
+ config.local.yml
34
+ config.local_#{env}.yml
35
+ )
36
+ #prepend the app name to the default file names
37
+ app_file_names = generic_file_names.map { |f| "#{app_name}_#{f}" }
38
+
39
+ default_roots = [
40
+ script_dir,
41
+ File.join('/etc', app_name.to_s),
42
+ File.join('/usr', 'local', 'etc', app_name.to_s),
43
+ File.join(home_dir, 'etc', app_name.to_s),
44
+ File.join(home_dir, ".#{app_name}"),
45
+ File.join(home_dir, '.hash_params', app_name.to_s),
46
+ File.join(script_dir, 'config'),
47
+ File.join(script_dir, 'settings')
48
+ ]
49
+ if defined?(Rails)
50
+ default_roots << Rails.root.join('config')
51
+ end
52
+
53
+
54
+ #process the /etc/app_name* files
55
+ app_file_names.each do |fname|
56
+ all_file_names << File.join('/etc', fname)
57
+ end
58
+ #now process the default roots which will override the above
59
+ (default_roots + special_roots).each do |root|
60
+ (generic_file_names + app_file_names + special_file_names).each do |fname|
61
+ all_file_names << File.join(root, fname)
62
+ end
63
+ end
64
+
65
+ all_file_names.each do |file|
66
+ generated_hash = deep_merge(generated_hash, hash_from_yaml_file(file)) if File.exists?(file)
67
+ end
68
+
69
+ if block_given?
70
+ HashParams::HashValidator.new.validate_hash(generated_hash, opts, &Proc.new)
71
+ else
72
+ HashParams::HashValidator.new.validate_hash(generated_hash, opts)
73
+ end
74
+ end
75
+
76
+ def self.hash_from_yaml_file(filename, env=nil)
77
+ env ||= ENVIRONMENT
78
+ r = File.exists?(filename) ? YAML::load(ERB.new(File.read(filename)).result) : {}
79
+ r[env] || r
80
+ end
81
+
82
+ def self.deep_merge(hash, other_hash)
83
+ if other_hash.is_a?(::Hash) && hash.is_a?(::Hash)
84
+ other_hash.each do |k, v|
85
+ hash[k] = hash.key?(k) ? deep_merge(hash[k], v) : v
86
+ end
87
+ hash
88
+ else
89
+ other_hash
90
+ end
91
+ end
92
+
93
+ end
data/lib/hash_params.rb CHANGED
@@ -1,148 +1,21 @@
1
- class HashParams < Hash
2
- VERSION = '0.0.2'
3
- attr :valid, :errors
4
-
5
- def initialize(opts={}, injection_target =nil, &code)
6
- @incoming_hash = opts
7
- @errors =[]
8
- # @parent = code.binding.eval 'self'
9
- @target =injection_target
10
- instance_eval(&code)
11
- @valid = (@errors.size == 0)
12
- end
13
-
14
- def param(key, h = {})
15
-
16
- #What happens if value is FalseClass ? Need something a little better
17
- val = @incoming_hash[key] || @incoming_hash[key.to_sym] || @incoming_hash[key.to_s]
18
- if val.nil? && h[:default]
19
- val = h[:default].respond_to?(:call) ? h[:default].call(self) : h[:default]
20
- end
21
-
22
- #don't bother with the rest if required parameter is missing
23
- return @errors << "Parameter #{key} is required and missing" if h[:required] && val.nil?
24
- #do all coercion and transformation first there could be an array of coersions they will be run in order
25
-
26
- Array(h[:coerce]).each do |c|
27
- val = coerce(val, c, h)
28
- end
29
-
30
- #coersion could return a nil which won't validate, it could return a false which will attempt to validate
31
- if validate!(val, h)
32
- #The value is valid add it
33
- var_name = h[:as] ? h[:as] : key
34
- self[var_name]=val
35
- inject_into_target(@target, var_name, val)
36
- end
37
-
38
- #after all that see if a block is given and process that
39
- if block_given? && val.is_a?(Hash)
40
- #Proc.new references the implict block
41
- val = HashParams.new(val, nil, &Proc.new)
42
- end
43
-
44
- val
45
- rescue => e
46
- @errors << e.to_s
47
- end
48
-
49
- def inject_into_target(target, var_name, val)
50
- if target
51
- #for read write methods
52
- target.singleton_class.class_eval do
53
- attr_accessor var_name;
54
- end
55
- target.send("#{var_name}=", val)
56
- end
57
- end
58
-
59
- def validate!(param, options ={})
60
- return false if param.nil?
61
- is_valid = true
62
-
63
- options.each do |key, value|
64
-
65
- error = case key
66
- when :validate
67
- "#{param.to_s} failed validation using proc" if value.respond_to?(:call) && !value.call(param)
68
- when :blank
69
- 'Parameter cannot be blank' if !value && blank?(param)
70
- when :format
71
- 'Parameter must be a string if using the format validation' && next unless param.kind_of?(String)
72
- "Parameter must match format #{value}" unless param =~ value
73
- when :is
74
- "Parameter must be #{value}" unless param === value
75
- when :in, :within, :range
76
- "Parameter must be within #{value}" unless value.respond_to?(:include) ? value.include?(param) : Array(value).include?(param)
77
- when :min
78
- "Parameter cannot be less than #{value}" unless value <= param
79
- when :max
80
- "Parameter cannot be greater than #{value}" unless value >= param
81
- when :min_length
82
- "Parameter cannot have length less than #{value}" unless value <= param.length
83
- when :max_length
84
- "Parameter cannot have length greater than #{value}" unless value >= param.length
85
- else
86
- nil
87
- end
88
- if error
89
- @errors << error
90
- is_valid = false
91
- end
92
- end
93
-
94
- #return true or false depending on if it validated
95
- is_valid
96
- end
97
-
98
-
99
- def coerce(val, type, h)
100
-
101
- # exceptions bubble up
102
- #order is important
103
- return val if type.nil? || val.nil?
104
-
105
- #two special types of transforms
106
- #There is no Boolean type so we handle them special
107
- if type == :boolean || type =='boolean'
108
- return val if (val == true || val == false)
109
- return false if /(false|f|no|n|0)$/i === val.to_s.downcase
110
- return true if /(true|t|yes|y|1)$/i === val.to_s.downcase
1
+ require 'yaml'
2
+ require 'erb'
3
+ require 'socket'
4
+
5
+ require_relative 'hash_params/hash_validator'
6
+ require_relative 'hash_params/validator'
7
+ require_relative 'hash_params/binding_validator'
8
+
9
+ module HashParams
10
+ ENVIRONMENT = ENV['HASH_PARAMS_ENV'] || (defined?(Rails) && Rails.env) || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || 'development'
11
+ VERSION = '2.0.0'
12
+ extend HashParams::Validator
13
+ end
111
14
 
112
- # if we can't parse we return a nil
113
- # maybe !!val is a better return?
114
- return nil
115
- end
116
- #they have given us a coercion which is a string or symbol which is not "boolean", we are going to cast this into a proc and run it
15
+ require_relative 'hash_params/yaml_params'
16
+ require 'pry' if HashParams::ENVIRONMENT == 'development'
117
17
 
118
- return type.to_proc.call(val) if type.is_a?(Symbol) || type.is_a?(String)
119
- #could be a proc
120
18
 
121
- return type.call(val) if type.respond_to?(:call)
122
- #nothing but simple types left
123
- return val if val.is_a?(type)
124
- return Integer(val) if type == Integer
125
- return Float(val) if type == Float
126
- return String(val) if type == String
127
- return Date.parse(val) if type == Date
128
- return Time.parse(val) if type == Time
129
- return DateTime.parse(val) if type == DateTime
130
- return Array(val.split(h[:delimiter] || ',')) if type == Array
131
- return Hash[val.gsub(/[{}]/,'').gsub('}','').split(h[:delimiter] || ',').map { |c| c.split(h[:separator] ||':').map{|i| i.strip} }] if type == Hash
132
19
 
133
- nil
134
- end
135
20
 
136
- def valid?
137
- @valid
138
- end
139
- def present?(object)
140
- !blank?(object)
141
- end
142
21
 
143
- def blank?(object)
144
- return true if object.nil?
145
- return true if object.respond_to?(:empty) && object.empty
146
- return false
147
- end
148
- end