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.
- checksums.yaml +5 -5
- data/.gitignore +11 -10
- data/.rspec +2 -2
- data/.travis.yml +4 -4
- data/CODE_OF_CONDUCT.md +13 -13
- data/Gemfile +4 -4
- data/LICENSE.txt +21 -21
- data/README.md +247 -757
- data/Rakefile +6 -6
- data/bblib.gemspec +34 -34
- data/bin/console +14 -14
- data/bin/setup +7 -7
- data/lib/array/bbarray.rb +71 -29
- data/lib/bblib.rb +12 -12
- data/lib/bblib/version.rb +3 -3
- data/lib/class/effortless.rb +23 -0
- data/lib/error/abstract.rb +3 -0
- data/lib/file/bbfile.rb +93 -52
- data/lib/hash/bbhash.rb +130 -46
- data/lib/hash/hash_struct.rb +24 -0
- data/lib/hash/tree_hash.rb +364 -0
- data/lib/hash_path/hash_path.rb +210 -0
- data/lib/hash_path/part.rb +83 -0
- data/lib/hash_path/path_hash.rb +84 -0
- data/lib/hash_path/proc.rb +93 -0
- data/lib/hash_path/processors.rb +239 -0
- data/lib/html/bbhtml.rb +2 -0
- data/lib/html/builder.rb +34 -0
- data/lib/html/tag.rb +49 -0
- data/lib/logging/bblogging.rb +42 -0
- data/lib/mixins/attrs.rb +422 -0
- data/lib/mixins/bbmixins.rb +7 -0
- data/lib/mixins/bridge.rb +17 -0
- data/lib/mixins/family_tree.rb +41 -0
- data/lib/mixins/hooks.rb +139 -0
- data/lib/mixins/logger.rb +31 -0
- data/lib/mixins/serializer.rb +71 -0
- data/lib/mixins/simple_init.rb +160 -0
- data/lib/number/bbnumber.rb +15 -7
- data/lib/object/bbobject.rb +46 -19
- data/lib/opal/bbopal.rb +0 -4
- data/lib/os/bbos.rb +24 -16
- data/lib/os/bbsys.rb +60 -43
- data/lib/string/bbstring.rb +165 -66
- data/lib/string/cases.rb +37 -29
- data/lib/string/fuzzy_matcher.rb +48 -50
- data/lib/string/matching.rb +43 -30
- data/lib/string/pluralization.rb +156 -0
- data/lib/string/regexp.rb +45 -0
- data/lib/string/roman.rb +17 -30
- data/lib/system/bbsystem.rb +42 -0
- data/lib/time/bbtime.rb +79 -58
- data/lib/time/cron.rb +174 -132
- data/lib/time/task_timer.rb +86 -70
- metadata +27 -10
- data/lib/gem/bbgem.rb +0 -28
- data/lib/hash/hash_path.rb +0 -344
- data/lib/hash/hash_path_proc.rb +0 -256
- data/lib/hash/path_hash.rb +0 -81
- data/lib/object/attr.rb +0 -182
- data/lib/object/hooks.rb +0 -69
- data/lib/object/lazy_class.rb +0 -73
@@ -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
|
data/lib/mixins/hooks.rb
ADDED
@@ -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
|