bogo 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.
@@ -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: