hash_params 0.0.2 → 2.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.
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