bblib 0.3.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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