bogo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9b544e3d18be2de80a851293f139ef363d3e2460
4
+ data.tar.gz: 2634b067067e337a84006f5583263cbb18399930
5
+ SHA512:
6
+ metadata.gz: 7c2f47cfac15a3cbcb72bd8e4d74dc2e2954512f7c8b4afff66624133eb5dfc93afaf2c16db37278cb38e4ff375fd632c8f2ec63fba83403f924f17139133f4d
7
+ data.tar.gz: d5df748c23547d30bf5db35672c94bb489cfabbaf35125870a451e3c5dc6eb4e038358e7831deb0a7623d3706884e9ea312329acb281be7393bf36f86cf23bba
@@ -0,0 +1,2 @@
1
+ ## v0.1.0
2
+ * Initial release
@@ -0,0 +1,25 @@
1
+ # Contributing
2
+
3
+ ## Branches
4
+
5
+ ### `master` branch
6
+
7
+ The master branch is the current stable released version.
8
+
9
+ ### `develop` branch
10
+
11
+ The develop branch is the current edge of development.
12
+
13
+ ## Pull requests
14
+
15
+ * https://github.com/spox/bogo/pulls
16
+
17
+ Please base all pull requests of the `develop` branch. Merges to
18
+ `master` only occur through the `develop` branch. Pull requests
19
+ based on `master` will likely be cherry picked.
20
+
21
+ ## Issues
22
+
23
+ Need to report an issue? Use the github issues:
24
+
25
+ * https://github.com/spox/bogo/issues
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2014 Chris Roberts
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,6 @@
1
+ # Bogo
2
+
3
+ A collection of libraries.
4
+
5
+ ## Info
6
+ * Repository: https://github.com/spox/bogo
@@ -0,0 +1,15 @@
1
+ $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
2
+ require 'bogo/version'
3
+ Gem::Specification.new do |s|
4
+ s.name = 'bogo'
5
+ s.version = Bogo::VERSION.version
6
+ s.summary = 'Helper libraries'
7
+ s.author = 'Chris Roberts'
8
+ s.email = 'code@chrisroberts.org'
9
+ s.homepage = 'https://github.com/spox/bogo'
10
+ s.description = 'Helper libraries'
11
+ s.require_path = 'lib'
12
+ s.license = 'Apache 2.0'
13
+ s.add_dependency 'hashie'
14
+ s.files = Dir['lib/**/*'] + %w(bogo.gemspec README.md CHANGELOG.md CONTRIBUTING.md LICENSE)
15
+ end
@@ -0,0 +1,10 @@
1
+ require 'bogo/version'
2
+
3
+ module Bogo
4
+ autoload :AnimalStrings, 'bogo/animal_strings'
5
+ autoload :Lazy, 'bogo/lazy'
6
+ autoload :Memoization, 'bogo/memoization'
7
+ autoload :Smash, 'bogo/smash'
8
+ end
9
+
10
+ autoload :Smash, 'bogo/smash'
@@ -0,0 +1,24 @@
1
+ require 'bogo'
2
+
3
+ module Bogo
4
+ # Animal stylings on strings
5
+ module AnimalStrings
6
+
7
+ # Camel case string
8
+ # @param string [String]
9
+ # @return [String]
10
+ def camel(string)
11
+ string.to_s.split('_').map{|k| "#{k.slice(0,1).upcase}#{k.slice(1,k.length)}"}.join
12
+ end
13
+
14
+ # Snake case (underscore) string
15
+ #
16
+ # @param string [String]
17
+ # @return [String]
18
+ def snake(string)
19
+ string.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').gsub('-', '_').downcase
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,238 @@
1
+ require 'bogo'
2
+ require 'digest/sha2'
3
+
4
+ module Bogo
5
+ # Adds functionality to facilitate laziness
6
+ module Lazy
7
+
8
+ # Instance methods for laziness
9
+ module InstanceMethods
10
+
11
+ # @return [Smash] argument hash
12
+ def data
13
+ unless(@data)
14
+ @data = Smash.new
15
+ end
16
+ @data
17
+ end
18
+
19
+ # @return [Smash] updated data
20
+ def dirty
21
+ unless(@dirty)
22
+ @dirty = Smash.new
23
+ end
24
+ @dirty
25
+ end
26
+
27
+ # @return [Smash] current data state
28
+ def attributes
29
+ data.merge(dirty)
30
+ end
31
+
32
+ # Create new instance
33
+ #
34
+ # @param args [Hash]
35
+ # @return [self]
36
+ def load_data(args={})
37
+ args = args.to_smash
38
+ @data = Smash.new
39
+ self.class.attributes.each do |name, options|
40
+ val = args[name]
41
+ if(options[:required] && !args.has_key?(name) && !options.has_key?(:default))
42
+ raise ArgumentError.new("Missing required option: `#{name}`")
43
+ end
44
+ if(val.nil? && !args.has_key?(name) && options[:default])
45
+ if(options[:default])
46
+ val = options[:default].respond_to?(:call) ? options[:default].call : options[:default]
47
+ end
48
+ end
49
+ if(args.has_key?(name) || val)
50
+ self.send("#{name}=", val)
51
+ end
52
+ end
53
+ self
54
+ end
55
+
56
+ # Identifies valid state and automatically
57
+ # merges dirty attributes into data, clears
58
+ # dirty attributes
59
+ #
60
+ # @return [self]
61
+ def valid_state
62
+ data.merge!(dirty)
63
+ dirty.clear
64
+ @_checksum = Digest::SHA256.hexdigest(MultiJson.dump(data))
65
+ self
66
+ end
67
+
68
+ # Model is dirty or specific attribute is dirty
69
+ #
70
+ # @param attr [String, Symbol] name of attribute
71
+ # @return [TrueClass, FalseClass] model or attribute is dirty
72
+ def dirty?(attr=nil)
73
+ if(attr)
74
+ dirty.has_key?(attr)
75
+ else
76
+ if(@_checksum)
77
+ !dirty.empty? ||
78
+ @_checksum != Digest::SHA256.hexdigest(MultiJson.dump(data))
79
+ else
80
+ true
81
+ end
82
+ end
83
+ end
84
+
85
+ # @return [String]
86
+ def to_s
87
+ "<#{self.class.name}:#{object_id}>"
88
+ end
89
+
90
+ # @return [String]
91
+ def inspect
92
+ "<#{self.class.name}:#{object_id} [#{data.inspect}]>"
93
+ end
94
+
95
+ end
96
+
97
+ # Class methods for laziness
98
+ module ClassMethods
99
+
100
+ # Add new attributes to class
101
+ #
102
+ # @param name [String]
103
+ # @param type [Class, Array<Class>]
104
+ # @param options [Hash]
105
+ # @option options [TrueClass, FalseClass] :required must be provided on initialization
106
+ # @option options [Object, Proc] :default default value
107
+ # @option options [Proc] :coerce
108
+ # @return [nil]
109
+ def attribute(name, type, options={})
110
+ name = name.to_sym
111
+ options = options.to_smash
112
+ attributes[name] = Smash.new(:type => type).merge(options)
113
+ coerce = attributes[name][:coerce]
114
+ valid_types = [attributes[name][:type], NilClass].flatten.compact
115
+ allowed_values = attributes[name][:allowed]
116
+ multiple_values = attributes[name][:multiple]
117
+ depends_on = attributes[name][:depends]
118
+ define_method(name) do
119
+ send(depends_on) if depends_on
120
+ self.class.on_missing(self) unless data.has_key?(name) || dirty.has_key?(name)
121
+ dirty[name] || data[name]
122
+ end
123
+ define_method("#{name}=") do |val|
124
+ values = multiple_values && val.is_a?(Array) ? val : [val]
125
+ values.map! do |item|
126
+ valid_type = valid_types.detect do |klass|
127
+ item.is_a?(klass)
128
+ end
129
+ if(coerce && !valid_type)
130
+ item = coerce.arity == 2 ? coerce.call(item, self) : coerce.call(item)
131
+ end
132
+ valid_type = valid_types.detect do |klass|
133
+ item.is_a?(klass)
134
+ end
135
+ unless(valid_type)
136
+ raise TypeError.new("Invalid type for `#{name}` (#{item} <#{item.class}>). Valid - #{valid_types.map(&:to_s).join(',')}")
137
+ end
138
+ if(allowed_values)
139
+ unless(allowed_values.include?(item))
140
+ raise ArgumentError.new("Invalid value provided for `#{name}` (#{item.inspect}). Allowed - #{allowed_values.map(&:inspect).join(', ')}")
141
+ end
142
+ end
143
+ item
144
+ end
145
+ if(!multiple_values && !val.is_a?(Array))
146
+ dirty[name] = values.first
147
+ else
148
+ dirty[name] = values
149
+ end
150
+ end
151
+ define_method("#{name}?") do
152
+ send(depends_on) if depends_on
153
+ self.class.on_missing(self) unless data.has_key?(name)
154
+ !!data[name]
155
+ end
156
+ nil
157
+ end
158
+
159
+ # Return attributes
160
+ #
161
+ # @param args [Symbol] :required or :optional
162
+ # @return [Array<Hash>]
163
+ def attributes(*args)
164
+ @attributes ||= Smash.new
165
+ if(args.include?(:required))
166
+ Smash[@attributes.find_all{|k,v| v[:required]}]
167
+ elsif(args.include?(:optional))
168
+ Smash[@attributes.find_all{|k,v| !v[:required]}]
169
+ else
170
+ @attributes
171
+ end
172
+ end
173
+
174
+ # Instance method to call on missing attribute or
175
+ # object to call method on if set
176
+ #
177
+ # @param param [Symbol, Object]
178
+ # @return [Symbol]
179
+ def on_missing(param=nil)
180
+ if(param)
181
+ if(param.is_a?(Symbol))
182
+ @missing_method = param
183
+ else
184
+ if(@missing_method && !@calling_on_missing)
185
+ @calling_on_missing = true
186
+ begin
187
+ param.send(@missing_method)
188
+ ensure
189
+ @calling_on_missing = false
190
+ end
191
+ end
192
+ @missing_method
193
+ end
194
+ else
195
+ @missing_method
196
+ end
197
+ end
198
+
199
+ # Directly set attribute hash
200
+ #
201
+ # @param attrs [Hash]
202
+ # @return [TrueClass]
203
+ # @todo need deep dup here
204
+ def set_attributes(attrs)
205
+ @attributes = attrs.to_smash
206
+ true
207
+ end
208
+
209
+ end
210
+
211
+ class << self
212
+
213
+ # Injects laziness into class
214
+ #
215
+ # @param klass [Class]
216
+ def included(klass)
217
+ klass.class_eval do
218
+ include InstanceMethods
219
+ extend ClassMethods
220
+
221
+ class << self
222
+
223
+ def inherited(klass)
224
+ klass.set_attributes(
225
+ MultiJson.load(
226
+ MultiJson.dump(self.attributes)
227
+ ).to_smash
228
+ )
229
+ end
230
+
231
+ end
232
+ end
233
+ end
234
+
235
+ end
236
+
237
+ end
238
+ end
@@ -0,0 +1,53 @@
1
+ require 'bogo'
2
+
3
+ module Bogo
4
+ # Memoization helpers
5
+ module Memoization
6
+
7
+ # Memoize data
8
+ #
9
+ # @param key [String, Symbol] identifier for data
10
+ # @param direct [Truthy, Falsey] direct skips key prepend of object id
11
+ # @yield block to create data
12
+ # @yieldreturn data to memoize
13
+ # @return [Object] data
14
+ def memoize(key, direct=false)
15
+ unless(direct)
16
+ key = "#{self.object_id}_#{key}"
17
+ end
18
+ unless(_memo.has_key?(key))
19
+ _memo[key] = yield
20
+ end
21
+ _memo[key]
22
+ end
23
+
24
+ def _memo
25
+ Thread.current[:bogo_memoization] ||= Smash.new
26
+ end
27
+
28
+ # Remove memoized value
29
+ #
30
+ # @param key [String, Symbol] identifier for data
31
+ # @param direct [Truthy, Falsey] direct skips key prepend of object id
32
+ # @return [NilClass]
33
+ def unmemoize(key, direct=false)
34
+ unless(direct)
35
+ key = "#{self.object_id}_#{key}"
36
+ end
37
+ _memo.delete(key)
38
+ end
39
+
40
+ # Remove all memoized values
41
+ #
42
+ # @return [TrueClass]
43
+ def clear_memoizations!
44
+ _memo.keys.find_all do |key|
45
+ key.to_s.start_with?("#{self.object_id}_")
46
+ end.each do |key|
47
+ _memo.delete(key)
48
+ end
49
+ true
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,151 @@
1
+ require 'hashie'
2
+ require 'digest/sha2'
3
+ require 'bogo'
4
+
5
+ module Bogo
6
+
7
+ # Customized Hash
8
+ class Smash < Hash
9
+ include Hashie::Extensions::IndifferentAccess
10
+ include Hashie::Extensions::MergeInitializer
11
+ include Hashie::Extensions::DeepMerge
12
+ include Hashie::Extensions::Coercion
13
+
14
+ coerce_value Hash, Smash
15
+
16
+ # Create new instance
17
+ #
18
+ # @param args [Object] argument list
19
+ def initialize(*args)
20
+ base = nil
21
+ if(args.first.is_a?(::Hash))
22
+ base = args.shift
23
+ end
24
+ super *args
25
+ if(base)
26
+ self.replace(base.to_smash)
27
+ end
28
+ end
29
+
30
+ def merge!(hash)
31
+ hash = hash.to_smash
32
+ super(hash)
33
+ end
34
+
35
+ # Get value at given path
36
+ #
37
+ # @param args [String, Symbol] key path to walk
38
+ # @return [Object, NilClass]
39
+ def retrieve(*args)
40
+ args.inject(self) do |memo, key|
41
+ if(memo.is_a?(Hash))
42
+ memo.to_smash[key]
43
+ else
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ alias_method :get, :retrieve
49
+
50
+ # Fetch value at given path or return a default value
51
+ #
52
+ # @param args [String, Symbol, Object] key path to walk. last value default to return
53
+ # @return [Object] value at key or default value
54
+ def fetch(*args)
55
+ default_value = args.pop
56
+ retrieve(*args) || default_value
57
+ end
58
+
59
+ # Set value at given path
60
+ #
61
+ # @param args [String, Symbol, Object] key path to walk. set last value to given path
62
+ # @return [Object] value set
63
+ def set(*args)
64
+ unless(args.size > 1)
65
+ raise ArgumentError.new 'Set requires at least one key and a value'
66
+ end
67
+ value = args.pop
68
+ set_key = args.pop
69
+ leaf = args.inject(self) do |memo, key|
70
+ unless(memo[key].is_a?(Hash))
71
+ memo[key] = Smash.new
72
+ end
73
+ memo[key]
74
+ end
75
+ leaf[set_key] = value
76
+ value
77
+ end
78
+
79
+ # Convert to Hash
80
+ #
81
+ # @return [Hash]
82
+ def to_hash(*args)
83
+ self.to_type_converter(::Hash, :to_hash, *args)
84
+ end
85
+
86
+ # Calculate checksum of hash (sha256)
87
+ #
88
+ # @return [String] checksum
89
+ def checksum
90
+ Digest::SHA256.hexdigest(self.to_smash(:sorted).to_s)
91
+ end
92
+
93
+ end
94
+ end
95
+
96
+ # Hook helper into toplevel `Hash`
97
+ class Hash
98
+
99
+ # Convert to Smash
100
+ #
101
+ # @return [Smash]
102
+ def to_smash(*args)
103
+ self.to_type_converter(::Smash, :to_smash, *args)
104
+ end
105
+ alias_method :hulk_smash, :to_smash
106
+
107
+ protected
108
+
109
+ # Convert to type
110
+ #
111
+ # @param type [Class] hash type
112
+ # @param convert_call [Symbol] builtin hash convert
113
+ # @return [Smash]
114
+ def to_type_converter(type, convert_call, *args)
115
+ type.new.tap do |smash|
116
+ if(args.include?(:sorted))
117
+ process = self.sort_by do |entry|
118
+ entry.first.to_s
119
+ end
120
+ else
121
+ process = self
122
+ end
123
+ process.each do |k,v|
124
+ smash[k.is_a?(Symbol) ? k.to_s : k] = smash_conversion(v, convert_call, *args)
125
+ end
126
+ end
127
+ end
128
+
129
+ # Convert object to smash if applicable
130
+ #
131
+ # @param obj [Object]
132
+ # @param convert_call [Symbol] builtin hash convert
133
+ # @return [Smash, Object]
134
+ def smash_conversion(obj, convert_call, *args)
135
+ case obj
136
+ when Hash
137
+ obj.send(convert_call, *args)
138
+ when Array
139
+ obj.map do |i|
140
+ smash_conversion(i, convert_call, *args)
141
+ end
142
+ else
143
+ obj
144
+ end
145
+ end
146
+
147
+ end
148
+
149
+ unless(defined?(Smash))
150
+ Smash = Bogo::Smash
151
+ end
@@ -0,0 +1,4 @@
1
+ module Bogo
2
+ # Current library version
3
+ VERSION = Gem::Version.new('0.1.0')
4
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bogo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Roberts
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: hashie
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description: Helper libraries
28
+ email: code@chrisroberts.org
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - CHANGELOG.md
34
+ - CONTRIBUTING.md
35
+ - LICENSE
36
+ - README.md
37
+ - bogo.gemspec
38
+ - lib/bogo.rb
39
+ - lib/bogo/animal_strings.rb
40
+ - lib/bogo/lazy.rb
41
+ - lib/bogo/memoization.rb
42
+ - lib/bogo/smash.rb
43
+ - lib/bogo/version.rb
44
+ homepage: https://github.com/spox/bogo
45
+ licenses:
46
+ - Apache 2.0
47
+ metadata: {}
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubyforge_project:
64
+ rubygems_version: 2.2.2
65
+ signing_key:
66
+ specification_version: 4
67
+ summary: Helper libraries
68
+ test_files: []
69
+ has_rdoc: