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