buff-extensions 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/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/CONTRIBUTING.md +24 -0
- data/Gemfile +3 -0
- data/Guardfile +15 -0
- data/LICENSE +15 -0
- data/README.md +35 -0
- data/Thorfile +43 -0
- data/buff-extensions.gemspec +32 -0
- data/lib/buff/extensions/boolean.rb +9 -0
- data/lib/buff/extensions/hash/dotted_paths.rb +124 -0
- data/lib/buff/extensions/hash/key_transforms.rb +144 -0
- data/lib/buff/extensions/hash/reverse_merge.rb +19 -0
- data/lib/buff/extensions/hash.rb +9 -0
- data/lib/buff/extensions/object/blank.rb +106 -0
- data/lib/buff/extensions/object.rb +1 -0
- data/lib/buff/extensions/version.rb +5 -0
- data/lib/buff/extensions.rb +9 -0
- data/lib/buff-extensions.rb +1 -0
- data/spec/buff/extensions/hash/dotted_paths_spec.rb +151 -0
- data/spec/spec_helper.rb +27 -0
- metadata +233 -0
data/.gitignore
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.9.3-p429
|
data/.travis.yml
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
## Running tests
|
4
|
+
|
5
|
+
### Install prerequisites
|
6
|
+
|
7
|
+
Install the latest version of [Bundler](http://gembundler.com)
|
8
|
+
|
9
|
+
$ gem install bundler
|
10
|
+
|
11
|
+
Clone the project
|
12
|
+
|
13
|
+
$ git clone git://github.com/RiotGames/buff-extensions.git
|
14
|
+
|
15
|
+
and run:
|
16
|
+
|
17
|
+
$ cd buff-extensions
|
18
|
+
$ bundle install
|
19
|
+
|
20
|
+
Bundler will install all gems and their dependencies required for testing and developing.
|
21
|
+
|
22
|
+
### Running unit (RSpec) tests
|
23
|
+
|
24
|
+
$ bundle exec guard start
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
notification :off
|
2
|
+
|
3
|
+
guard "spork" do
|
4
|
+
watch('Gemfile')
|
5
|
+
watch('spec/spec_helper.rb') { :rspec }
|
6
|
+
watch(%r{^spec/support/.+\.rb$}) { :rspec }
|
7
|
+
end
|
8
|
+
|
9
|
+
guard "rspec", cli: "--color --drb --format Fuubar", all_on_start: false, all_after_pass: false do
|
10
|
+
watch(%r{^spec/.+_spec\.rb$})
|
11
|
+
|
12
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
13
|
+
watch('spec/spec_helper.rb') { "spec" }
|
14
|
+
watch(%r{^spec/support/.+\.rb$}) { "spec" }
|
15
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Copyright 2012-2013 Riot Games
|
2
|
+
|
3
|
+
Jamie Winsor (<reset@riotgames.com>)
|
4
|
+
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
you may not use this file except in compliance with the License.
|
7
|
+
You may obtain a copy of the License at
|
8
|
+
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
See the License for the specific language governing permissions and
|
15
|
+
limitations under the License.
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Buff::Extensions
|
2
|
+
[](http://badge.fury.io/rb/buff-extensions)
|
3
|
+
[](https://travis-ci.org/RiotGames/buff-extensions)
|
4
|
+
|
5
|
+
Extensions to Core Ruby classes
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'buff-extensions'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install buff-extensions
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Using it as a mixin
|
24
|
+
|
25
|
+
require 'buff/extensions'
|
26
|
+
|
27
|
+
class PowerHash
|
28
|
+
include Buff::Extensions::DottedPaths
|
29
|
+
end
|
30
|
+
|
31
|
+
# Authors and Contributors
|
32
|
+
|
33
|
+
* Jamie Winsor (<reset@riotgames.com>)
|
34
|
+
|
35
|
+
Thank you to all of our [Contributors](https://github.com/RiotGames/buff-extensions/graphs/contributors), testers, and users.
|
data/Thorfile
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
require 'bundler'
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'buff/ruby_engine'
|
7
|
+
require 'buff/extensions/version'
|
8
|
+
|
9
|
+
class Default < Thor
|
10
|
+
extend Buff::RubyEngine
|
11
|
+
|
12
|
+
unless jruby?
|
13
|
+
require 'thor/rake_compat'
|
14
|
+
|
15
|
+
include Thor::RakeCompat
|
16
|
+
Bundler::GemHelper.install_tasks
|
17
|
+
|
18
|
+
desc "build", "Build buff-extensions-#{Buff::Extensions::VERSION}.gem into the pkg directory"
|
19
|
+
def build
|
20
|
+
Rake::Task["build"].execute
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "install", "Build and install buff-extensions-#{Buff::Extensions::VERSION}.gem into system gems"
|
24
|
+
def install
|
25
|
+
Rake::Task["install"].execute
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "release", "Create tag v#{Buff::Extensions::VERSION} and build and push buff-extensions-#{Buff::Extensions::VERSION}.gem to Rubygems"
|
29
|
+
def release
|
30
|
+
Rake::Task["release"].execute
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Spec < Thor
|
35
|
+
namespace :spec
|
36
|
+
default_task :unit
|
37
|
+
|
38
|
+
desc "unit", "run the project's unit tests"
|
39
|
+
def unit
|
40
|
+
exec "rspec --color --format=documentation spec"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'buff/extensions/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "buff-extensions"
|
8
|
+
spec.version = Buff::Extensions::VERSION
|
9
|
+
spec.authors = ["Jamie Winsor"]
|
10
|
+
spec.email = ["reset@riotgames.com"]
|
11
|
+
spec.description = %q{Extensions to Core Ruby classes}
|
12
|
+
spec.summary = spec.description
|
13
|
+
spec.homepage = "https://github.com/RiotGames/buff-extensions"
|
14
|
+
spec.license = "Apache 2.0"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^spec/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
spec.required_ruby_version = ">= 1.9.2"
|
21
|
+
|
22
|
+
spec.add_development_dependency "buff-ruby_engine", "~> 0.1"
|
23
|
+
spec.add_development_dependency "thor", "~> 0.18.0"
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
spec.add_development_dependency "fuubar"
|
28
|
+
spec.add_development_dependency "guard"
|
29
|
+
spec.add_development_dependency "guard-rspec"
|
30
|
+
spec.add_development_dependency "guard-spork"
|
31
|
+
spec.add_development_dependency "spork"
|
32
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'buff/extensions/object'
|
2
|
+
|
3
|
+
module Buff
|
4
|
+
module Extensions
|
5
|
+
module DottedPaths
|
6
|
+
class << self
|
7
|
+
def included(base)
|
8
|
+
base.send(:extend, ClassMethods)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
# Create a new Hash containing other nested Hashes from a string containing
|
14
|
+
# a dotted path. A Hash will be created and assigned to a key of another Hash
|
15
|
+
# for each entry in the dotted path.
|
16
|
+
#
|
17
|
+
# If a value is provided for the optional seed argument then the value of the
|
18
|
+
# deepest nested key will be set to the given value. If no value is provided
|
19
|
+
# the value of the key will be nil.
|
20
|
+
#
|
21
|
+
# @example creating a nested hash from a dotted path
|
22
|
+
#
|
23
|
+
# Hash.from_dotted_path("deep.nested.hash") =>
|
24
|
+
# {
|
25
|
+
# "deep" => {
|
26
|
+
# "nested" => {
|
27
|
+
# "hash" => nil
|
28
|
+
# }
|
29
|
+
# }
|
30
|
+
# }
|
31
|
+
#
|
32
|
+
#
|
33
|
+
# @example specifying a seed value
|
34
|
+
#
|
35
|
+
# Hash.from_dotted_path("deep.nested.hash", :seed_value) =>
|
36
|
+
# {
|
37
|
+
# "deep" => {
|
38
|
+
# "nested" => {
|
39
|
+
# "hash" => :seed_value
|
40
|
+
# }
|
41
|
+
# }
|
42
|
+
# }
|
43
|
+
#
|
44
|
+
# @param [String, Symbol, Array] dotpath
|
45
|
+
# @param [Object] seed (nil)
|
46
|
+
# @param [Hash] target (self.new)
|
47
|
+
#
|
48
|
+
# @return [Hash]
|
49
|
+
def from_dotted_path(dotpath, seed = nil, target = self.new)
|
50
|
+
case dotpath
|
51
|
+
when String, Symbol
|
52
|
+
from_dotted_path(dotpath.to_s.split("."), seed)
|
53
|
+
when Array
|
54
|
+
if dotpath.empty?
|
55
|
+
return target
|
56
|
+
end
|
57
|
+
|
58
|
+
key = dotpath.pop
|
59
|
+
|
60
|
+
if target.empty?
|
61
|
+
target[key] = seed
|
62
|
+
from_dotted_path(dotpath, seed, target)
|
63
|
+
else
|
64
|
+
new_target = self.new
|
65
|
+
new_target[key] = target
|
66
|
+
from_dotted_path(dotpath, seed, new_target)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Return the value of the nested hash key from the given dotted path
|
73
|
+
#
|
74
|
+
# @example
|
75
|
+
#
|
76
|
+
# nested_hash = {
|
77
|
+
# "deep" => {
|
78
|
+
# "nested" => {
|
79
|
+
# "hash" => :seed_value
|
80
|
+
# }
|
81
|
+
# }
|
82
|
+
# }
|
83
|
+
#
|
84
|
+
# nested_hash.dig('deep.nested.hash') => :seed_value
|
85
|
+
#
|
86
|
+
# @param [String] path
|
87
|
+
#
|
88
|
+
# @return [Object, nil]
|
89
|
+
def dig(path)
|
90
|
+
return nil unless path.present?
|
91
|
+
|
92
|
+
parts = path.split('.', 2)
|
93
|
+
match = self[parts[0].to_s].nil? ? self[parts[0].to_sym] : self[parts[0].to_s]
|
94
|
+
if !parts[1] or match.nil?
|
95
|
+
match
|
96
|
+
else
|
97
|
+
match.dig(parts[1])
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Returns an array of dotted paths from the keys, values of this Hash. Values which are
|
102
|
+
# nested Hashes will also recurred into and their paths will be added properly.
|
103
|
+
#
|
104
|
+
# @param [Hash] source
|
105
|
+
# @param [Array] acc
|
106
|
+
# @param [Array] namespace
|
107
|
+
#
|
108
|
+
# @return [Array<String>]
|
109
|
+
def dotted_paths(source = self, acc = Array.new, namespace = Array.new)
|
110
|
+
if source.is_a?(Hash) && !source.empty?
|
111
|
+
source.each do |key, value|
|
112
|
+
branch = namespace.dup
|
113
|
+
branch << key
|
114
|
+
dotted_paths(value, acc, branch)
|
115
|
+
end
|
116
|
+
else
|
117
|
+
acc << namespace.join('.')
|
118
|
+
end
|
119
|
+
|
120
|
+
acc
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module Buff
|
2
|
+
module Extensions
|
3
|
+
# Borrowd and modified from
|
4
|
+
# {https://raw.github.com/rails/rails/master/activesupport/lib/active_support/core_ext/hash/keys.rb}
|
5
|
+
module KeyTransforms
|
6
|
+
# Return a new hash with all keys converted using the block operation.
|
7
|
+
#
|
8
|
+
# hash = { name: 'Rob', age: '28' }
|
9
|
+
#
|
10
|
+
# hash.transform_keys{ |key| key.to_s.upcase }
|
11
|
+
# # => { "NAME" => "Rob", "AGE" => "28" }
|
12
|
+
def transform_keys
|
13
|
+
result = {}
|
14
|
+
each_key do |key|
|
15
|
+
result[yield(key)] = self[key]
|
16
|
+
end
|
17
|
+
result
|
18
|
+
end
|
19
|
+
|
20
|
+
# Destructively convert all keys using the block operations.
|
21
|
+
# Same as transform_keys but modifies +self+.
|
22
|
+
def transform_keys!
|
23
|
+
keys.each do |key|
|
24
|
+
self[yield(key)] = delete(key)
|
25
|
+
end
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# Return a new hash with all keys converted to strings.
|
30
|
+
#
|
31
|
+
# hash = { name: 'Rob', age: '28' }
|
32
|
+
#
|
33
|
+
# hash.stringify_keys
|
34
|
+
# #=> { "name" => "Rob", "age" => "28" }
|
35
|
+
def stringify_keys
|
36
|
+
transform_keys { |key| key.to_s }
|
37
|
+
end
|
38
|
+
|
39
|
+
# Destructively convert all keys to strings. Same as
|
40
|
+
# +stringify_keys+, but modifies +self+.
|
41
|
+
def stringify_keys!
|
42
|
+
transform_keys! { |key| key.to_s }
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return a new hash with all keys converted to symbols, as long as
|
46
|
+
# they respond to +to_sym+.
|
47
|
+
#
|
48
|
+
# hash = { 'name' => 'Rob', 'age' => '28' }
|
49
|
+
#
|
50
|
+
# hash.symbolize_keys
|
51
|
+
# #=> { name: "Rob", age: "28" }
|
52
|
+
def symbolize_keys
|
53
|
+
transform_keys { |key| key.to_sym rescue key }
|
54
|
+
end
|
55
|
+
alias_method :to_options, :symbolize_keys
|
56
|
+
|
57
|
+
# Destructively convert all keys to symbols, as long as they respond
|
58
|
+
# to +to_sym+. Same as +symbolize_keys+, but modifies +self+.
|
59
|
+
def symbolize_keys!
|
60
|
+
transform_keys! { |key| key.to_sym rescue key }
|
61
|
+
end
|
62
|
+
alias_method :to_options!, :symbolize_keys!
|
63
|
+
|
64
|
+
# Validate all keys in a hash match <tt>*valid_keys</tt>, raising ArgumentError
|
65
|
+
# on a mismatch. Note that keys are NOT treated indifferently, meaning if you
|
66
|
+
# use strings for keys but assert symbols as keys, this will fail.
|
67
|
+
#
|
68
|
+
# { name: 'Rob', years: '28' }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key: years"
|
69
|
+
# { name: 'Rob', age: '28' }.assert_valid_keys('name', 'age') # => raises "ArgumentError: Unknown key: name"
|
70
|
+
# { name: 'Rob', age: '28' }.assert_valid_keys(:name, :age) # => passes, raises nothing
|
71
|
+
def assert_valid_keys(*valid_keys)
|
72
|
+
valid_keys.flatten!
|
73
|
+
each_key do |k|
|
74
|
+
raise ArgumentError.new("Unknown key: #{k}") unless valid_keys.include?(k)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Return a new hash with all keys converted by the block operation.
|
79
|
+
# This includes the keys from the root hash and from all
|
80
|
+
# nested hashes.
|
81
|
+
#
|
82
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
83
|
+
#
|
84
|
+
# hash.deep_transform_keys{ |key| key.to_s.upcase }
|
85
|
+
# # => { "PERSON" => { "NAME" => "Rob", "AGE" => "28" } }
|
86
|
+
def deep_transform_keys(&block)
|
87
|
+
result = {}
|
88
|
+
each do |key, value|
|
89
|
+
result[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys(&block) : value
|
90
|
+
end
|
91
|
+
result
|
92
|
+
end
|
93
|
+
|
94
|
+
# Destructively convert all keys by using the block operation.
|
95
|
+
# This includes the keys from the root hash and from all
|
96
|
+
# nested hashes.
|
97
|
+
def deep_transform_keys!(&block)
|
98
|
+
keys.each do |key|
|
99
|
+
value = delete(key)
|
100
|
+
self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value
|
101
|
+
end
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
# Return a new hash with all keys converted to strings.
|
106
|
+
# This includes the keys from the root hash and from all
|
107
|
+
# nested hashes.
|
108
|
+
#
|
109
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
110
|
+
#
|
111
|
+
# hash.deep_stringify_keys
|
112
|
+
# # => { "person" => { "name" => "Rob", "age" => "28" } }
|
113
|
+
def deep_stringify_keys
|
114
|
+
deep_transform_keys { |key| key.to_s }
|
115
|
+
end
|
116
|
+
|
117
|
+
# Destructively convert all keys to strings.
|
118
|
+
# This includes the keys from the root hash and from all
|
119
|
+
# nested hashes.
|
120
|
+
def deep_stringify_keys!
|
121
|
+
deep_transform_keys! { |key| key.to_s }
|
122
|
+
end
|
123
|
+
|
124
|
+
# Return a new hash with all keys converted to symbols, as long as
|
125
|
+
# they respond to +to_sym+. This includes the keys from the root hash
|
126
|
+
# and from all nested hashes.
|
127
|
+
#
|
128
|
+
# hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } }
|
129
|
+
#
|
130
|
+
# hash.deep_symbolize_keys
|
131
|
+
# # => { person: { name: "Rob", age: "28" } }
|
132
|
+
def deep_symbolize_keys
|
133
|
+
deep_transform_keys { |key| key.to_sym rescue key }
|
134
|
+
end
|
135
|
+
|
136
|
+
# Destructively convert all keys to symbols, as long as they respond
|
137
|
+
# to +to_sym+. This includes the keys from the root hash and from all
|
138
|
+
# nested hashes.
|
139
|
+
def deep_symbolize_keys!
|
140
|
+
deep_transform_keys! { |key| key.to_sym rescue key }
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Buff
|
2
|
+
module Extensions
|
3
|
+
module ReverseMerge
|
4
|
+
# @param [Hash] other
|
5
|
+
#
|
6
|
+
# @return [Hash]
|
7
|
+
def reverse_merge(other)
|
8
|
+
other.merge(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param [Hash] other
|
12
|
+
#
|
13
|
+
# @return [Hash]
|
14
|
+
def reverse_merge!(other)
|
15
|
+
merge!(other) { |key, old, new| old }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# Borrowed and modified from
|
2
|
+
# {https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/blank.rb}
|
3
|
+
|
4
|
+
class Object
|
5
|
+
# An object is blank if it's false, empty, or a whitespace string.
|
6
|
+
# For example, '', ' ', +nil+, [], and {} are all blank.
|
7
|
+
#
|
8
|
+
# This simplifies:
|
9
|
+
#
|
10
|
+
# if address.nil? || address.empty?
|
11
|
+
#
|
12
|
+
# ...to:
|
13
|
+
#
|
14
|
+
# if address.blank?
|
15
|
+
def blank?
|
16
|
+
respond_to?(:empty?) ? empty? : !self
|
17
|
+
end
|
18
|
+
|
19
|
+
# An object is present if it's not <tt>blank?</tt>.
|
20
|
+
def present?
|
21
|
+
!blank?
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns object if it's <tt>present?</tt> otherwise returns +nil+.
|
25
|
+
# <tt>object.presence</tt> is equivalent to <tt>object.present? ? object : nil</tt>.
|
26
|
+
#
|
27
|
+
# This is handy for any representation of objects where blank is the same
|
28
|
+
# as not present at all. For example, this simplifies a common check for
|
29
|
+
# HTTP POST/query parameters:
|
30
|
+
#
|
31
|
+
# state = params[:state] if params[:state].present?
|
32
|
+
# country = params[:country] if params[:country].present?
|
33
|
+
# region = state || country || 'US'
|
34
|
+
#
|
35
|
+
# ...becomes:
|
36
|
+
#
|
37
|
+
# region = params[:state].presence || params[:country].presence || 'US'
|
38
|
+
def presence
|
39
|
+
self if present?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class NilClass
|
44
|
+
# +nil+ is blank:
|
45
|
+
#
|
46
|
+
# nil.blank? # => true
|
47
|
+
def blank?
|
48
|
+
true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class FalseClass
|
53
|
+
# +false+ is blank:
|
54
|
+
#
|
55
|
+
# false.blank? # => true
|
56
|
+
def blank?
|
57
|
+
true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class TrueClass
|
62
|
+
# +true+ is not blank:
|
63
|
+
#
|
64
|
+
# true.blank? # => false
|
65
|
+
def blank?
|
66
|
+
false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Array
|
71
|
+
# An array is blank if it's empty:
|
72
|
+
#
|
73
|
+
# [].blank? # => true
|
74
|
+
# [1,2,3].blank? # => false
|
75
|
+
alias_method :blank?, :empty?
|
76
|
+
end
|
77
|
+
|
78
|
+
class Hash
|
79
|
+
# A hash is blank if it's empty:
|
80
|
+
#
|
81
|
+
# {}.blank? # => true
|
82
|
+
# { key: 'value' }.blank? # => false
|
83
|
+
alias_method :blank?, :empty?
|
84
|
+
end
|
85
|
+
|
86
|
+
class String
|
87
|
+
# A string is blank if it's empty or contains whitespaces only:
|
88
|
+
#
|
89
|
+
# ''.blank? # => true
|
90
|
+
# ' '.blank? # => true
|
91
|
+
# ' '.blank? # => true
|
92
|
+
# ' something here '.blank? # => false
|
93
|
+
def blank?
|
94
|
+
self !~ /[^[:space:]]/
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class Numeric #:nodoc:
|
99
|
+
# No number is blank:
|
100
|
+
#
|
101
|
+
# 1.blank? # => false
|
102
|
+
# 0.blank? # => false
|
103
|
+
def blank?
|
104
|
+
false
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'object/blank'
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'buff/extensions'
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Buff::Extensions::DottedPaths do
|
4
|
+
describe "ClassMethods" do
|
5
|
+
subject { Hash }
|
6
|
+
|
7
|
+
describe "::from_dotted_path" do
|
8
|
+
it "returns a new Hash" do
|
9
|
+
expect(subject.from_dotted_path("deep.nested.item")).to be_a(Hash)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "returns a hash containing the nested keys" do
|
13
|
+
obj = subject.from_dotted_path("deep.nested.item")
|
14
|
+
|
15
|
+
expect(obj).to have_key("deep")
|
16
|
+
expect(obj["deep"]).to have_key("nested")
|
17
|
+
expect(obj["deep"]["nested"]).to have_key("item")
|
18
|
+
end
|
19
|
+
|
20
|
+
it "sets a nil value for the deepest nested item" do
|
21
|
+
obj = subject.from_dotted_path("deep.nested.item")
|
22
|
+
|
23
|
+
expect(obj["deep"]["nested"]["item"]).to be_nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it "handles a symbol as the dotted path" do
|
27
|
+
obj = subject.from_dotted_path(:"a.b.c", "value")
|
28
|
+
|
29
|
+
expect(obj["a"]["b"]["c"]).to eql("value")
|
30
|
+
end
|
31
|
+
|
32
|
+
context "when given a seed value" do
|
33
|
+
it "sets the value of the deepest nested item to the seed" do
|
34
|
+
obj = subject.from_dotted_path("deep.nested.item", "seeded_value")
|
35
|
+
|
36
|
+
expect(obj["deep"]["nested"]["item"]).to eql("seeded_value")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
subject { Hash.new }
|
43
|
+
|
44
|
+
describe "#dig" do
|
45
|
+
context "when the Hash contains the nested path" do
|
46
|
+
subject do
|
47
|
+
{
|
48
|
+
"we" => {
|
49
|
+
"found" => {
|
50
|
+
"something" => true
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
it "returns the value at the dotted path" do
|
57
|
+
expect(subject.dig("we.found.something")).to eql(true)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "when the Hash does not contain the nested path" do
|
62
|
+
it "returns a nil value" do
|
63
|
+
expect(subject.dig("nothing.is.here")).to be_nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context "when the Hash contains symbols for keys" do
|
68
|
+
subject do
|
69
|
+
{
|
70
|
+
we: {
|
71
|
+
found: {
|
72
|
+
something: :symbol_value
|
73
|
+
}
|
74
|
+
}
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
it "returns the value at the dotted path" do
|
79
|
+
expect(subject.dig("we.found.something")).to eql(:symbol_value)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns nil if given a blank string" do
|
84
|
+
expect(subject.dig("")).to be_nil
|
85
|
+
end
|
86
|
+
|
87
|
+
it "returns 'false' nested values as 'false' and not 'nil'" do
|
88
|
+
hash = {
|
89
|
+
"ssl" => {
|
90
|
+
"verify" => false
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
expect(hash.dig('ssl.verify')).to eql(false)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "#dotted_paths" do
|
99
|
+
it "returns an array" do
|
100
|
+
expect(subject.dotted_paths).to be_a(Array)
|
101
|
+
end
|
102
|
+
|
103
|
+
context "given a hash with only top level keys" do
|
104
|
+
subject do
|
105
|
+
{
|
106
|
+
"one" => "val",
|
107
|
+
"two" => "val"
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
111
|
+
it "returns an array of the top level keys as strings" do
|
112
|
+
expect(subject.dotted_paths).to eql(["one", "two"])
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context "given a hash with empty hashes as values" do
|
117
|
+
subject do
|
118
|
+
{
|
119
|
+
"one" => Hash.new,
|
120
|
+
"two" => Hash.new
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
it "returns an array of the top level keys as strings" do
|
125
|
+
expect(subject.dotted_paths).to eql(["one", "two"])
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context "given a hash with nested keys" do
|
130
|
+
subject do
|
131
|
+
{
|
132
|
+
"one" => {
|
133
|
+
"nested" => {
|
134
|
+
"attribute" => "hello"
|
135
|
+
}
|
136
|
+
},
|
137
|
+
"two" => {
|
138
|
+
"nested" => {
|
139
|
+
"attribute" => "other_hello",
|
140
|
+
"other_attr" => "world"
|
141
|
+
}
|
142
|
+
}
|
143
|
+
}
|
144
|
+
end
|
145
|
+
|
146
|
+
it "returns an array of dotted paths including the nested Hash keys" do
|
147
|
+
expect(subject.dotted_paths).to eql(["one.nested.attribute", "two.nested.attribute", "two.nested.other_attr"])
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'buff/ruby_engine'
|
5
|
+
|
6
|
+
def setup_rspec
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.expect_with :rspec do |c|
|
9
|
+
c.syntax = :expect
|
10
|
+
end
|
11
|
+
|
12
|
+
config.mock_with :rspec
|
13
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
14
|
+
config.filter_run focus: true
|
15
|
+
config.run_all_when_everything_filtered = true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if Buff::RubyEngine.jruby?
|
20
|
+
require 'buff/extensions'
|
21
|
+
setup_rspec
|
22
|
+
else
|
23
|
+
require 'spork'
|
24
|
+
|
25
|
+
Spork.prefork { setup_rspec }
|
26
|
+
Spork.each_run { require 'buff/extensions' }
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: buff-extensions
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jamie Winsor
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: buff-ruby_engine
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0.1'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.1'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: thor
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.18.0
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.18.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.3'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: fuubar
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: guard
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: guard-rspec
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: guard-spork
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ! '>='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ! '>='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: spork
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
description: Extensions to Core Ruby classes
|
175
|
+
email:
|
176
|
+
- reset@riotgames.com
|
177
|
+
executables: []
|
178
|
+
extensions: []
|
179
|
+
extra_rdoc_files: []
|
180
|
+
files:
|
181
|
+
- .gitignore
|
182
|
+
- .ruby-version
|
183
|
+
- .travis.yml
|
184
|
+
- CONTRIBUTING.md
|
185
|
+
- Gemfile
|
186
|
+
- Guardfile
|
187
|
+
- LICENSE
|
188
|
+
- README.md
|
189
|
+
- Thorfile
|
190
|
+
- buff-extensions.gemspec
|
191
|
+
- lib/buff-extensions.rb
|
192
|
+
- lib/buff/extensions.rb
|
193
|
+
- lib/buff/extensions/boolean.rb
|
194
|
+
- lib/buff/extensions/hash.rb
|
195
|
+
- lib/buff/extensions/hash/dotted_paths.rb
|
196
|
+
- lib/buff/extensions/hash/key_transforms.rb
|
197
|
+
- lib/buff/extensions/hash/reverse_merge.rb
|
198
|
+
- lib/buff/extensions/object.rb
|
199
|
+
- lib/buff/extensions/object/blank.rb
|
200
|
+
- lib/buff/extensions/version.rb
|
201
|
+
- spec/buff/extensions/hash/dotted_paths_spec.rb
|
202
|
+
- spec/spec_helper.rb
|
203
|
+
homepage: https://github.com/RiotGames/buff-extensions
|
204
|
+
licenses:
|
205
|
+
- Apache 2.0
|
206
|
+
post_install_message:
|
207
|
+
rdoc_options: []
|
208
|
+
require_paths:
|
209
|
+
- lib
|
210
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
211
|
+
none: false
|
212
|
+
requirements:
|
213
|
+
- - ! '>='
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: 1.9.2
|
216
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
217
|
+
none: false
|
218
|
+
requirements:
|
219
|
+
- - ! '>='
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '0'
|
222
|
+
segments:
|
223
|
+
- 0
|
224
|
+
hash: -4518863559578760213
|
225
|
+
requirements: []
|
226
|
+
rubyforge_project:
|
227
|
+
rubygems_version: 1.8.23
|
228
|
+
signing_key:
|
229
|
+
specification_version: 3
|
230
|
+
summary: Extensions to Core Ruby classes
|
231
|
+
test_files:
|
232
|
+
- spec/buff/extensions/hash/dotted_paths_spec.rb
|
233
|
+
- spec/spec_helper.rb
|