bblib 0.3.0 → 0.4.1

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 (62) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +11 -10
  3. data/.rspec +2 -2
  4. data/.travis.yml +4 -4
  5. data/CODE_OF_CONDUCT.md +13 -13
  6. data/Gemfile +4 -4
  7. data/LICENSE.txt +21 -21
  8. data/README.md +247 -757
  9. data/Rakefile +6 -6
  10. data/bblib.gemspec +34 -34
  11. data/bin/console +14 -14
  12. data/bin/setup +7 -7
  13. data/lib/array/bbarray.rb +71 -29
  14. data/lib/bblib.rb +12 -12
  15. data/lib/bblib/version.rb +3 -3
  16. data/lib/class/effortless.rb +23 -0
  17. data/lib/error/abstract.rb +3 -0
  18. data/lib/file/bbfile.rb +93 -52
  19. data/lib/hash/bbhash.rb +130 -46
  20. data/lib/hash/hash_struct.rb +24 -0
  21. data/lib/hash/tree_hash.rb +364 -0
  22. data/lib/hash_path/hash_path.rb +210 -0
  23. data/lib/hash_path/part.rb +83 -0
  24. data/lib/hash_path/path_hash.rb +84 -0
  25. data/lib/hash_path/proc.rb +93 -0
  26. data/lib/hash_path/processors.rb +239 -0
  27. data/lib/html/bbhtml.rb +2 -0
  28. data/lib/html/builder.rb +34 -0
  29. data/lib/html/tag.rb +49 -0
  30. data/lib/logging/bblogging.rb +42 -0
  31. data/lib/mixins/attrs.rb +422 -0
  32. data/lib/mixins/bbmixins.rb +7 -0
  33. data/lib/mixins/bridge.rb +17 -0
  34. data/lib/mixins/family_tree.rb +41 -0
  35. data/lib/mixins/hooks.rb +139 -0
  36. data/lib/mixins/logger.rb +31 -0
  37. data/lib/mixins/serializer.rb +71 -0
  38. data/lib/mixins/simple_init.rb +160 -0
  39. data/lib/number/bbnumber.rb +15 -7
  40. data/lib/object/bbobject.rb +46 -19
  41. data/lib/opal/bbopal.rb +0 -4
  42. data/lib/os/bbos.rb +24 -16
  43. data/lib/os/bbsys.rb +60 -43
  44. data/lib/string/bbstring.rb +165 -66
  45. data/lib/string/cases.rb +37 -29
  46. data/lib/string/fuzzy_matcher.rb +48 -50
  47. data/lib/string/matching.rb +43 -30
  48. data/lib/string/pluralization.rb +156 -0
  49. data/lib/string/regexp.rb +45 -0
  50. data/lib/string/roman.rb +17 -30
  51. data/lib/system/bbsystem.rb +42 -0
  52. data/lib/time/bbtime.rb +79 -58
  53. data/lib/time/cron.rb +174 -132
  54. data/lib/time/task_timer.rb +86 -70
  55. metadata +27 -10
  56. data/lib/gem/bbgem.rb +0 -28
  57. data/lib/hash/hash_path.rb +0 -344
  58. data/lib/hash/hash_path_proc.rb +0 -256
  59. data/lib/hash/path_hash.rb +0 -81
  60. data/lib/object/attr.rb +0 -182
  61. data/lib/object/hooks.rb +0 -69
  62. data/lib/object/lazy_class.rb +0 -73
@@ -0,0 +1,7 @@
1
+ require_relative 'simple_init'
2
+ require_relative 'attrs'
3
+ require_relative 'family_tree'
4
+ require_relative 'hooks'
5
+ require_relative 'serializer'
6
+ require_relative 'logger'
7
+ require_relative 'bridge'
@@ -0,0 +1,17 @@
1
+
2
+ module BBLib
3
+ # Adds basic convenience methods to a class to extend getters or setters from
4
+ # class methods to instances.
5
+ module Bridge
6
+
7
+ def bridge_method(*class_methods)
8
+ class_methods.each do |class_method|
9
+ define_method(class_method) do |*args|
10
+ self.class.send(class_method, *args)
11
+ end
12
+ end
13
+ true
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+
2
+
3
+ module BBLib
4
+ # Various methods for finding descendants and subclasses of a class. Intended as an
5
+ # extend mixin for any class.
6
+ module FamilyTree
7
+ # Return all classes that inherit from this class
8
+ def descendants(include_singletons = false)
9
+ ObjectSpace.each_object(Class).select do |c|
10
+ (include_singletons || !c.singleton_class?) && c < self
11
+ end
12
+ end
13
+
14
+ alias subclasses descendants
15
+
16
+ # Return all classes that directly inherit from this class
17
+ def direct_descendants(include_singletons = false)
18
+ ObjectSpace.each_object(Class).select do |c|
19
+ (include_singletons || !c.singleton_class?) && c.ancestors[1] == self
20
+ end
21
+ end
22
+
23
+ # Return all live instances of the class
24
+ # Passing false will not include instances of sub classes
25
+ def instances(descendants = true)
26
+ inst = ObjectSpace.each_object(self).to_a
27
+ descendants ? inst : inst.select { |i| i.class == self }
28
+ end
29
+
30
+ def namespace
31
+ BBLib.namespace_of(self)
32
+ end
33
+
34
+ def root_namespace
35
+ BBLib.root_namespace_of(self)
36
+ end
37
+
38
+ alias direct_subclasses direct_descendants
39
+
40
+ end
41
+ end
@@ -0,0 +1,139 @@
1
+
2
+
3
+ module BBLib
4
+ # Adds method hooking capability to a class. Intended to be used as a mixin.
5
+ module Hooks
6
+
7
+ [:before, :after].each do |hook_type|
8
+ define_method(hook_type) do |*methods, **opts|
9
+ raise ArgumentError, 'You must pass in at least one method followed by the name of the hook method.' if methods.size < 2
10
+ hooks = _hooks[hook_type][methods.pop] ||= { methods: [], opts: {} }
11
+ hooks[:methods] += methods
12
+ hooks[:opts] = hooks[:opts].deep_merge(opts)
13
+ methods.each { |method| _hook_method(method) if method_defined?(method) }
14
+ true
15
+ end
16
+ end
17
+
18
+ def method_added(method)
19
+ if _defining_hook?
20
+ @_defining_hook = false
21
+ else
22
+ _hook_method(method)
23
+ end
24
+ end
25
+
26
+ def singleton_method_added(method)
27
+ if _defining_hook?
28
+ @_defining_hook = false
29
+ else
30
+ self.singleton_class.send(:_hook_method, method, force: true) if self.singleton_class.respond_to?(:_hook_method)
31
+ end
32
+ end
33
+
34
+ def _hook_method(method, force: false)
35
+ return false if _defining_hook?
36
+ [:before, :after].each do |hook_type|
37
+ _hooks[hook_type].find_all { |hook, data| data[:methods].include?(method) }.to_h.each do |hook, data|
38
+ next if !force && _hooked_methods[hook_type] && _hooked_methods[hook_type][hook] && _hooked_methods[hook_type][hook].include?(method)
39
+ send("_hook_#{hook_type}_method", method, hook, data[:opts])
40
+ end
41
+ end
42
+ end
43
+
44
+ # def _hook_all
45
+ # _hooks.each do |type, hooks|
46
+ # hooks.each do |hook, data|
47
+ # data[:methods].each do |method|
48
+ # _hook_method(method)
49
+ # end
50
+ # end
51
+ # end
52
+ # end
53
+
54
+ def _superclass_hooks
55
+ hooks = { before: {}, after: {} }
56
+ ancestors.reverse.each do |ancestor|
57
+ next if ancestor == self
58
+ hooks = hooks.deep_merge(ancestor.send(:_hooks)) if ancestor.respond_to?(:_hooks)
59
+ end
60
+ hooks
61
+ end
62
+
63
+ def _hooks
64
+ @_hooks ||= _superclass_hooks
65
+ end
66
+
67
+ def _hooked_methods
68
+ @_hooked_methods ||= { before: {}, after: {} }
69
+ end
70
+
71
+ def _add_hooked_method(type, hook, method)
72
+ history = _hooked_methods[type]
73
+ history[hook] = {} unless history[hook]
74
+ history[hook][method] = instance_method(method)
75
+ end
76
+
77
+ def _defining_hook?
78
+ @_defining_hook ||= false
79
+ end
80
+
81
+ # Current opts:
82
+ # send_args - Sends the arguments of the method to the before hook.
83
+ # modify_args - Replaces the original args with the returned value of the
84
+ # send_method - Sends the method name as an argument to the hooked method.
85
+ # before hook method.
86
+ def _hook_before_method(method, hook, opts = {})
87
+ return false if method == hook
88
+ _add_hooked_method(:before, hook, method)
89
+ original = instance_method(method)
90
+ @_defining_hook = true
91
+ define_method(method) do |*args, &block|
92
+ if opts[:send_args] || opts[:send_arg] || opts[:modify_args] || opts[:send_method]
93
+ margs = args
94
+ margs = [method] + args if opts[:send_method]
95
+ margs = args + [opts[:add_args]].flatten(1) if opts[:add_args]
96
+ result = method(hook).call(*margs)
97
+ args = result if opts[:modify_args]
98
+ else
99
+ method(hook).call
100
+ end
101
+ original.bind(self).call(*args, &block)
102
+ end
103
+ @_defining_hook = false
104
+ true
105
+ end
106
+
107
+ # Current opts:
108
+ # send_args - Sends the arguments of the method to the after method.
109
+ # send_value - Sends the return value of the method to the hook method.
110
+ # send_value_ary - Sends the return value of the method to the hook method
111
+ # => with the splat operator.
112
+ # modify_value - Opts must also include one of the two above. Passes the returned
113
+ # => value of the method to the hook and returns the hooks value
114
+ # => rather than the original methods value.
115
+ def _hook_after_method(method, hook, opts = {})
116
+ return false if method == hook
117
+ _add_hooked_method(:after, hook, method)
118
+ original = instance_method(method)
119
+ @_defining_hook = true
120
+ define_method(method) do |*args, &block|
121
+ rtr = original.bind(self).call(*args, &block)
122
+ if opts[:send_args]
123
+ method(hook).call(*args)
124
+ elsif opts[:send_return] || opts[:send_value]
125
+ result = method(hook).call(rtr)
126
+ rtr = result if opts[:modify_value] || opts[:modify_return]
127
+ elsif opts[:send_return_ary] || opts[:send_value_ary]
128
+ result = method(hook).call(*rtr)
129
+ rtr = result if opts[:modify_value] || opts[:modify_return]
130
+ else
131
+ method(hook).call
132
+ end
133
+ rtr
134
+ end
135
+ @_defining_hook = false
136
+ true
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,31 @@
1
+ module BBLib
2
+ module Logger
3
+
4
+ def logger
5
+ self.class.logger
6
+ end
7
+
8
+ [:debug, :info, :warn, :error, :fatal, :unknown].each do |sev|
9
+ define_method(sev) do |msg = nil, &block|
10
+ logger.send(sev) { "[#{self.class}] #{msg ? msg : block.call}" }
11
+ end
12
+ end
13
+
14
+ def self.included(base)
15
+ base.extend ClassMethods
16
+ end
17
+
18
+ module ClassMethods
19
+ def logger
20
+ BBLib.logger
21
+ end
22
+
23
+ [:debug, :info, :warn, :error, :fatal, :unknown].each do |sev|
24
+ define_method(sev) do |msg = nil, &block|
25
+ logger.send(sev) { "[#{self}] #{msg ? msg : block.call}" }
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,71 @@
1
+
2
+
3
+ module BBLib
4
+
5
+ module Serializer
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def _serialize_fields
12
+ @_serialize_fields ||= _ancestor_serialize_fields
13
+ end
14
+
15
+ def _ancestor_serialize_fields
16
+ hash = {}
17
+ ancestors.reverse.map do |ancestor|
18
+ next unless ancestor.respond_to?(:_serialize_fields) && ancestor != self
19
+ hash = hash.deep_merge(ancestor._serialize_fields)
20
+ end
21
+ hash
22
+ end
23
+
24
+ def _dont_serialize_fields
25
+ @_dont_serialize_fields ||= _ancestor_dont_serialize_fields
26
+ end
27
+
28
+ def _ancestor_dont_serialize_fields
29
+ ancestors.reverse.flat_map do |ancestor|
30
+ next unless ancestor.respond_to?(:_dont_serialize_fields) && ancestor != self
31
+ ancestor._dont_serialize_fields
32
+ end.compact.uniq
33
+ end
34
+
35
+ def dont_serialize_method(method)
36
+ _dont_serialize_fields.push(method) unless _dont_serialize_fields.include?(method)
37
+ end
38
+
39
+ def serialize_method(name, method = nil, **opts)
40
+ return false if method == :serialize || name == :serialize && method.nil?
41
+ _serialize_fields[name.to_sym] = {
42
+ method: (method || name).to_sym
43
+ }.merge(opts)
44
+ end
45
+ end
46
+
47
+ def serialize(ignore_defaults = false)
48
+ self.class._serialize_fields.map do |name, opts|
49
+ next if self.class._dont_serialize_fields.include?(name)
50
+ args = [opts[:method]] + (opts.include?(:args) ? [opts[:args]].flatten(1) : [])
51
+ value = send(*args)
52
+ next if value == self
53
+ unless opts[:flat]
54
+ if value.is_a?(Hash)
55
+ value = value.map { |k, v| [k, v != self && v.respond_to?(:serialize) ? v.serialize(ignore_defaults) : v] }.to_h
56
+ elsif value.is_a?(Array)
57
+ value = value.map { |v| v != self && v.respond_to?(:serialize) ? v.serialize(ignore_defaults) : v }
58
+ elsif value.respond_to?(:serialize)
59
+ value = value.serialize(ignore_defaults)
60
+ end
61
+ end
62
+ if !opts[:always] && (ignore_defaults && value == opts[:default] || opts.include?(:ignore) && value == opts[:ignore])
63
+ nil
64
+ else
65
+ [name, value]
66
+ end
67
+ end.compact.to_h
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,160 @@
1
+
2
+
3
+ module BBLib
4
+ # Allows any public setter method to be called during initialization using keyword arguments.
5
+ # Add include BBLib::SimpleInit or prepend BBLib::SimpleInit to classes to add this behavior.
6
+ module SimpleInit
7
+ attr_reader :_init_type
8
+
9
+ INIT_TYPES = [:strict, :loose].freeze
10
+
11
+ def self.included(base)
12
+ base.extend ClassMethods
13
+ base.class_eval do
14
+ define_method(:initialize) do |*args, &block|
15
+ send(:simple_setup) if respond_to?(:simple_setup, true)
16
+ send(:simple_preinit, *args, &block) if respond_to?(:simple_preinit, true)
17
+ _initialize(*args)
18
+ send(:simple_init, *args, &block) if respond_to?(:simple_init, true)
19
+ instance_eval(&block) if block
20
+ end
21
+ end
22
+ end
23
+
24
+ module ClassMethods
25
+
26
+ # TODO: Currently init foundation breaks when running in opal.
27
+ unless BBLib.in_opal?
28
+ # Overriden new method that allows parent classes to dynamically generate
29
+ # instantiations of descendants by using the named :_class argument.
30
+ # :_class needs to be the fully qualified name of the descendant.
31
+ def new(*args, &block)
32
+ named = BBLib.named_args(*args)
33
+ if init_foundation && named[init_foundation_method] && ((named[init_foundation_method] != self.send(init_foundation_method)) rescue false)
34
+ klass = descendants.find do |k|
35
+ if init_foundation_compare
36
+ init_foundation_compare.call(k.send(init_foundation_method), named[init_foundation_method])
37
+ else
38
+ k.send(init_foundation_method).to_s == named[init_foundation_method].to_s
39
+ end
40
+ end
41
+ raise ArgumentError, "Unknown class type #{named[init_foundation_method]}" unless klass
42
+ klass.new(*args, &block)
43
+ else
44
+ super
45
+ end
46
+ end
47
+ end
48
+
49
+ # If true, this allows the overriden new method to generate descendants from
50
+ # its constructors.
51
+ def init_foundation
52
+ @init_foundation ||= false
53
+ end
54
+
55
+ # Sets the init_foundation variable to true of false. When false, the new
56
+ # method behaves like any other class. If true, the new method can instantiate
57
+ # child classes using the :_class named parameter.
58
+ def init_foundation=(toggle)
59
+ @init_foundation = toggle
60
+ end
61
+
62
+ def init_foundation_method(method = nil)
63
+ @init_foundation_method = method if method
64
+ @init_foundation_method ||= ancestor_init_foundation_method
65
+ end
66
+
67
+ def init_foundation_compare(&block)
68
+ @init_foundation_compare = block if block_given?
69
+ @init_foundation_compare
70
+ end
71
+
72
+ def setup_init_foundation(method, &block)
73
+ self.init_foundation = true
74
+ self.init_foundation_method(method)
75
+ self.init_foundation_compare(&block) if block_given?
76
+ end
77
+
78
+ def ancestor_init_foundation_method
79
+ anc = ancestors.find do |a|
80
+ next if a == self
81
+ a.respond_to?(:init_foundation_method)
82
+ end
83
+ anc ? anc.init_foundation_method : :_class
84
+ end
85
+
86
+ # Sets or returns the current init type for this class.
87
+ # Available types are:
88
+ #=> :strict = Unknown named arguments will raise an error.
89
+ #=> :loose = Unknown named arguments are ignored.
90
+ def init_type(type = nil)
91
+ return @init_type ||= _super_init_type unless type
92
+ raise ArgumentError, "Unknown init type '#{type}'. Must be #{INIT_TYPES.join_terms('or', encapsulate: "'")}." unless INIT_TYPES.include?(type)
93
+ @init_type = type
94
+ end
95
+
96
+ # Used to load the init type of the nearest ancestor for inheritance.
97
+ def _super_init_type
98
+ ancestors.each do |ancestor|
99
+ next if ancestor == self
100
+ return ancestor.init_type if ancestor.respond_to?(:init_type)
101
+ end
102
+ :strict
103
+ end
104
+
105
+ # Dynamically create a new class based on this one. By default this class
106
+ # is generated in the same namespace as the parent class. A custom namespace
107
+ # can be passed in using the named argument :namespace.
108
+ def build_descendant(name, namespace: parent_namespace)
109
+ namespace.const_set(name, Class.new(self))
110
+ end
111
+
112
+ # Returns the nearest parent namespace to thi current class. Object is
113
+ # returned if this class is not in a namespace.
114
+ def parent_namespace
115
+ parent = self.to_s.split('::')[0..-2].join('::')
116
+ if parent.empty?
117
+ return Object
118
+ else
119
+ Object.const_get(parent)
120
+ end
121
+ end
122
+
123
+ def _class
124
+ self.to_s
125
+ end
126
+ end
127
+
128
+ protected
129
+
130
+ def _initialize(*args)
131
+ named = BBLib.named_args(*args)
132
+ if self.class.respond_to?(:_attrs)
133
+ set_v_arg = self.class._attrs.map do |method, details|
134
+ next unless details[:options][:arg_at] && details[:options][:arg_at].is_a?(Integer)
135
+ index = details[:options][:arg_at]
136
+ if args.size > index
137
+ accept = details[:options][:arg_at_accept]
138
+ if accept.nil? || [accept].flatten.any? { |a| a >= args[index].class }
139
+ send("#{method}=", args[index])
140
+ method
141
+ end
142
+ end
143
+ end.compact
144
+ missing = self.class._attrs.map do |method, details|
145
+ next unless !set_v_arg.include?(method) && details[:options][:required] && !named.include?(method) && !send(method)
146
+ method
147
+ end.compact
148
+ raise ArgumentError, "You are missing the following required #{BBLib.pluralize('argument', missing.size)}: #{missing.join_terms}" unless missing.empty?
149
+ end
150
+ named.each do |method, value|
151
+ next if method == self.class.init_foundation_method
152
+ setter = "#{method}="
153
+ exists = respond_to?(setter)
154
+ raise ArgumentError, "Undefined attribute #{setter} for class #{self.class}." if !exists && self.class.init_type == :strict
155
+ next unless exists
156
+ send(setter, value)
157
+ end
158
+ end
159
+ end
160
+ end