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,210 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'part'
|
3
|
+
require_relative 'proc'
|
4
|
+
require_relative 'path_hash'
|
5
|
+
|
6
|
+
# This classes parses dot delimited hash path strings and wraps the corresponding parts. Then hashes or arrays can be
|
7
|
+
# passed to the find method to find all matching elements for the path.
|
8
|
+
class HashPath
|
9
|
+
include BBLib::Effortless
|
10
|
+
attr_ary_of Part, :parts, default: []
|
11
|
+
|
12
|
+
def append(path)
|
13
|
+
insert(path, parts.size)
|
14
|
+
end
|
15
|
+
|
16
|
+
def prepend(path)
|
17
|
+
insert(path, 0)
|
18
|
+
end
|
19
|
+
|
20
|
+
def insert(path, index)
|
21
|
+
parse_path(path).each do |part|
|
22
|
+
parts[index] = part
|
23
|
+
index += 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def find(hash)
|
28
|
+
hash = TreeHash.new unless hash.is_a?(TreeHash)
|
29
|
+
hash.find(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def simple_init(*args)
|
35
|
+
args.find_all { |arg| arg.is_a?(String) || arg.is_a?(Symbol) }.each do |path|
|
36
|
+
append(path.to_s)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_path(path)
|
41
|
+
path.to_s.gsub('..', '.[[:recursive:]]').scan(/(?:[\(|\[|\/].*?[\)|\]|\/]|\\\.|[^\.])+/).map do |part|
|
42
|
+
Part.new(part)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module BBLib
|
48
|
+
def self.hash_path(hash, *paths, multi_path: false, multi_join: false, multi_join_hash: false)
|
49
|
+
tree = TreeHash.new(hash)
|
50
|
+
if multi_path
|
51
|
+
tree.find_multi(*paths).map { |r| r.map { |sr| sr&.value } }
|
52
|
+
elsif multi_join
|
53
|
+
tree.find_join(*paths).map { |r| r.map { |sr| sr&.value } }
|
54
|
+
elsif multi_join_hash
|
55
|
+
tree.find_join(*paths).map { |r| r.map { |sr| sr&.value } }.to_h
|
56
|
+
else
|
57
|
+
tree.find(paths).map(&:value)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.hash_path_keys(hash)
|
62
|
+
hash.to_tree_hash.absolute_paths
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.hash_path_key_for(hash, value)
|
66
|
+
hash.squish.find_all { |_k, v| value.is_a?(Regexp) ? v =~ value : v == value }.to_h.keys
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.hash_path_set(hash, *paths)
|
70
|
+
tree = hash.is_a?(TreeHash) ? hash : TreeHash.new(hash)
|
71
|
+
tree.bridge(*paths)
|
72
|
+
hash.replace(tree.value)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.hash_path_copy(hash, *paths)
|
76
|
+
tree = hash.is_a?(TreeHash) ? hash : TreeHash.new(hash)
|
77
|
+
tree.copy(*paths)
|
78
|
+
hash.replace(tree.value)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.hash_path_copy_to(from, to, *paths)
|
82
|
+
tree = from.is_a?(TreeHash) ? from : TreeHash.new(from)
|
83
|
+
tree.hash_path_copy_to(to, *paths)
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.hash_path_delete(hash, *paths)
|
87
|
+
tree = hash.is_a?(TreeHash) ? hash : TreeHash.new(hash)
|
88
|
+
tree.delete(*paths)
|
89
|
+
hash.replace(tree.value)
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.hash_path_move(hash, *paths)
|
93
|
+
tree = hash.is_a?(TreeHash) ? hash : TreeHash.new(hash)
|
94
|
+
tree.move(*paths)
|
95
|
+
hash.replace(tree.value)
|
96
|
+
end
|
97
|
+
|
98
|
+
def self.hash_path_move_to(from, to, *paths)
|
99
|
+
tree = hash.is_a?(TreeHash) ? hash : TreeHash.new(hash)
|
100
|
+
tree.hash_path_copy_to(to, *paths).tap do |res|
|
101
|
+
from.replace(tree.value)
|
102
|
+
to.replace(res.value)
|
103
|
+
end
|
104
|
+
to
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
# Monkey patches
|
110
|
+
class Hash
|
111
|
+
def hash_path(*path)
|
112
|
+
BBLib.hash_path self, *path
|
113
|
+
end
|
114
|
+
|
115
|
+
def hash_path_set(*paths)
|
116
|
+
BBLib.hash_path_set self, *paths
|
117
|
+
end
|
118
|
+
|
119
|
+
def hash_path_copy(*paths)
|
120
|
+
BBLib.hash_path_copy self, *paths
|
121
|
+
end
|
122
|
+
|
123
|
+
def hash_path_copy_to(to, *paths)
|
124
|
+
BBLib.hash_path_copy_to self, to, *paths
|
125
|
+
end
|
126
|
+
|
127
|
+
def hash_path_delete(*paths)
|
128
|
+
BBLib.hash_path_delete self, *paths
|
129
|
+
end
|
130
|
+
|
131
|
+
def hash_path_move(*paths)
|
132
|
+
BBLib.hash_path_move self, *paths
|
133
|
+
end
|
134
|
+
|
135
|
+
def hash_path_move_to(to, *paths)
|
136
|
+
BBLib.hash_path_move_to self, to, *paths
|
137
|
+
end
|
138
|
+
|
139
|
+
def hash_paths
|
140
|
+
BBLib.hash_path_keys self
|
141
|
+
end
|
142
|
+
|
143
|
+
def hash_path_for(value)
|
144
|
+
BBLib.hash_path_key_for self, value
|
145
|
+
end
|
146
|
+
|
147
|
+
alias hpath hash_path
|
148
|
+
alias hpath_set hash_path_set
|
149
|
+
alias hpath_move hash_path_move
|
150
|
+
alias hpath_move_to hash_path_move_to
|
151
|
+
alias hpath_delete hash_path_delete
|
152
|
+
alias hpath_copy hash_path_copy
|
153
|
+
alias hpath_copy_to hash_path_copy_to
|
154
|
+
alias hpaths hash_paths
|
155
|
+
alias hpath_for hash_path_for
|
156
|
+
end
|
157
|
+
|
158
|
+
# Monkey Patches
|
159
|
+
class Array
|
160
|
+
def hash_path(*path)
|
161
|
+
BBLib.hash_path(self, *path)
|
162
|
+
end
|
163
|
+
|
164
|
+
def hash_path_set(*paths)
|
165
|
+
BBLib.hash_path_set(self, *paths)
|
166
|
+
end
|
167
|
+
|
168
|
+
def hash_path_copy(*paths)
|
169
|
+
BBLib.hash_path_copy(self, *paths)
|
170
|
+
end
|
171
|
+
|
172
|
+
def hash_path_copy_to(to, *paths)
|
173
|
+
BBLib.hash_path_copy_to(self, to, *paths)
|
174
|
+
end
|
175
|
+
|
176
|
+
def hash_path_delete(*paths)
|
177
|
+
BBLib.hash_path_delete(self, *paths)
|
178
|
+
end
|
179
|
+
|
180
|
+
def hash_path_move(*paths)
|
181
|
+
BBLib.hash_path_move(self, *paths)
|
182
|
+
end
|
183
|
+
|
184
|
+
def hash_path_move_to(to, *paths)
|
185
|
+
BBLib.hash_path_move_to(self, to, *paths)
|
186
|
+
end
|
187
|
+
|
188
|
+
def hash_paths
|
189
|
+
BBLib.hash_path_keys(self)
|
190
|
+
end
|
191
|
+
|
192
|
+
def hash_path_for(value)
|
193
|
+
BBLib.hash_path_key_for(self, value)
|
194
|
+
end
|
195
|
+
|
196
|
+
alias hpath hash_path
|
197
|
+
alias hpath_set hash_path_set
|
198
|
+
alias hpath_move hash_path_move
|
199
|
+
alias hpath_move_to hash_path_move_to
|
200
|
+
alias hpath_delete hash_path_delete
|
201
|
+
alias hpath_copy hash_path_copy
|
202
|
+
alias hpath_copy_to hash_path_copy_to
|
203
|
+
alias hpaths hash_paths
|
204
|
+
alias hpath_for hash_path_for
|
205
|
+
|
206
|
+
# Add a hash path to a hash
|
207
|
+
def bridge(*paths)
|
208
|
+
replace(to_tree_hash.bridge(*paths))
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
class HashPath
|
2
|
+
# This class encapsulates a single portion of a hash path path
|
3
|
+
class Part
|
4
|
+
include BBLib::Effortless
|
5
|
+
|
6
|
+
attr_of [String, Regexp, Integer, Range], :selector, default: nil, allow_nil: true, serialize: true
|
7
|
+
attr_str :evaluation, default: nil, allow_nil: true, serialize: true
|
8
|
+
attr_bool :recursive, default: false, serialize: true
|
9
|
+
|
10
|
+
def parse(path)
|
11
|
+
evl = path.scan(/\(.*\)$/).first
|
12
|
+
self.evaluation = evl ? evl.uncapsulate('(', limit: 1) : evl
|
13
|
+
self.recursive = path.start_with?('[[:recursive:]]')
|
14
|
+
self.selector = parse_selector(evl ? path.sub(evl, '') : path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def key_match?(key, object)
|
18
|
+
case selector
|
19
|
+
when String
|
20
|
+
key.to_s == selector
|
21
|
+
when Integer
|
22
|
+
key.to_i == selector
|
23
|
+
when Range
|
24
|
+
selector === key || object.size.times.to_a.include?(key)
|
25
|
+
else
|
26
|
+
selector === key
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def special_selector?
|
31
|
+
selector.is_a?(String) && /^\{.*\}$/ =~ selector
|
32
|
+
end
|
33
|
+
|
34
|
+
def matches(object)
|
35
|
+
matches = []
|
36
|
+
if special_selector?
|
37
|
+
begin
|
38
|
+
[object.send(*selector.uncapsulate('{').split(':'))].flatten(1).compact.each do |match|
|
39
|
+
matches << match if evaluates?(match)
|
40
|
+
end
|
41
|
+
rescue StandardError => e
|
42
|
+
# Nothing, the special selector failed
|
43
|
+
# puts e
|
44
|
+
end
|
45
|
+
elsif object.children?
|
46
|
+
object.children.each do |k, v|
|
47
|
+
matches << v if key_match?(k, object) && evaluates?(v)
|
48
|
+
matches += matches(v) if recursive? && v.children?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
matches
|
52
|
+
end
|
53
|
+
|
54
|
+
def evaluates?(object)
|
55
|
+
return true unless evaluation
|
56
|
+
eval(evaluation.gsub('$', 'object.value'))
|
57
|
+
rescue => e
|
58
|
+
# The eval resulted in an error so we return false
|
59
|
+
false
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
def simple_init(*args)
|
65
|
+
parse(args.first) if args.first.is_a?(String)
|
66
|
+
end
|
67
|
+
|
68
|
+
def parse_selector(str)
|
69
|
+
str = str.gsub('.[[:recursive:]]', '..') if str =~ /^\[\d+.*\d+\]$/
|
70
|
+
str = str.gsub('[[:recursive:]]', '') if recursive?
|
71
|
+
if str =~ /^\/.*\/[imx]?$/
|
72
|
+
str.to_regex
|
73
|
+
elsif str =~ /^\[\d+\]$/
|
74
|
+
str.uncapsulate('[').to_i
|
75
|
+
elsif str =~ /\[\-?\d+\.{2,3}\-?\d+\]/
|
76
|
+
Range.new(*str.scan(/\-?\d+/).map(&:to_i))
|
77
|
+
else
|
78
|
+
str
|
79
|
+
end.gsub('\\.', '.')
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# This module provides similar functionality as hash path, but instead
|
4
|
+
# generates a PathHash object which wraps a Hash or Array. Elements may
|
5
|
+
# be accessed via method calls rather than path strings.
|
6
|
+
|
7
|
+
module BBLib
|
8
|
+
def self.path_hash(hash)
|
9
|
+
PathHash.new(hash)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Wraps a hash in a PathHash object which allows ActiveRecord-like access to hash parameters.
|
13
|
+
# For example, methods are treated as keys passed in to Hash's [] method and ._ can be used to
|
14
|
+
# indicate that the next element should be searched for recursively.
|
15
|
+
class PathHash < BasicObject
|
16
|
+
attr_reader :hash, :recursive
|
17
|
+
|
18
|
+
def initialize(hash)
|
19
|
+
@hash = hash
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](val)
|
23
|
+
PathHash.new(@hash.map { |h| h[val] })
|
24
|
+
end
|
25
|
+
|
26
|
+
def _val
|
27
|
+
@hash
|
28
|
+
end
|
29
|
+
|
30
|
+
alias _v _val
|
31
|
+
|
32
|
+
def _fval
|
33
|
+
@hash.first
|
34
|
+
rescue
|
35
|
+
@hash
|
36
|
+
end
|
37
|
+
|
38
|
+
alias _f _fval
|
39
|
+
|
40
|
+
def _
|
41
|
+
@recursive = true
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def _path(arg, formula = nil)
|
46
|
+
method_missing arg, formula
|
47
|
+
end
|
48
|
+
|
49
|
+
# Does not fall back on super as ALL input is accepted
|
50
|
+
def method_missing(arg, formula = nil)
|
51
|
+
arg = (@recursive ? "..#{arg}" : arg.to_s) +
|
52
|
+
(formula ? "(#{formula})" : '')
|
53
|
+
if @hash.is_a?(::Array)
|
54
|
+
PathHash.new @hash.flat_map { |h| if h.is_a?(::Array) || h.is_a?(::Hash) then h.hash_path(arg) end }
|
55
|
+
else
|
56
|
+
PathHash.new @hash.hpath(arg)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def respond_to_missing?(*args)
|
61
|
+
true || super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Monkey Patches
|
67
|
+
class Hash
|
68
|
+
def path_hash
|
69
|
+
BBLib.path_hash(self)
|
70
|
+
end
|
71
|
+
|
72
|
+
alias phash path_hash
|
73
|
+
alias _ph path_hash
|
74
|
+
end
|
75
|
+
|
76
|
+
# Monkey Patches
|
77
|
+
class Array
|
78
|
+
def path_hash
|
79
|
+
BBLib.path_hash(self)
|
80
|
+
end
|
81
|
+
|
82
|
+
alias phash path_hash
|
83
|
+
alias _ph path_hash
|
84
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative 'processors'
|
3
|
+
|
4
|
+
module BBLib
|
5
|
+
# This class wraps around a hash path or set of paths and maps a set of actions for modifying elements at the matching
|
6
|
+
# path.
|
7
|
+
class HashPathProc
|
8
|
+
include BBLib::Effortless
|
9
|
+
|
10
|
+
attr_ary_of String, :paths, default: [''], serialize: true, uniq: true
|
11
|
+
attr_of [String, Symbol], :action, default: nil, allow_nil: true, serialize: true, pre_proc: proc { |arg| HashPathProc.map_action(arg.to_s.to_sym) }
|
12
|
+
attr_ary :args, default: [], serialize: true
|
13
|
+
attr_hash :options, default: {}, serialize: true
|
14
|
+
attr_of [String, Proc], :condition, default: nil, allow_nil: true, serialize: true
|
15
|
+
attr_bool :recursive, default: false, serialize: true
|
16
|
+
attr_bool :class_based, default: true, serialize: true
|
17
|
+
|
18
|
+
def process(hash)
|
19
|
+
return hash unless @action && hash
|
20
|
+
tree = hash.to_tree_hash
|
21
|
+
paths.each do |path|
|
22
|
+
children = recursive ? tree.find(path).flat_map(&:descendants) : tree.find(path)
|
23
|
+
children.each do |child|
|
24
|
+
next unless check_condition(child.value)
|
25
|
+
HashPathProcs.send(find_action(action), child, *full_args, class_based: class_based)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
hash.replace(tree.value) rescue tree.value
|
29
|
+
end
|
30
|
+
|
31
|
+
def check_condition(value)
|
32
|
+
return true unless condition
|
33
|
+
if condition.is_a?(String)
|
34
|
+
eval(condition)
|
35
|
+
else
|
36
|
+
condition.call(value)
|
37
|
+
end
|
38
|
+
rescue => e
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
USED_KEYWORDS = [:action, :args, :paths, :recursive, :condition].freeze
|
45
|
+
|
46
|
+
def find_action(action)
|
47
|
+
(HashPathProcs.respond_to?(action) ? action : :custom)
|
48
|
+
end
|
49
|
+
|
50
|
+
def full_args
|
51
|
+
(HASH_PATH_PROC_TYPES.include?(action) ? [] : [action]) +
|
52
|
+
args +
|
53
|
+
(options.empty? || options.nil? ? [] : [options])
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.map_action(action)
|
57
|
+
clean = HASH_PATH_PROC_TYPES.find { |k, v| action == k || v.include?(action) }
|
58
|
+
clean ? clean.first : action
|
59
|
+
end
|
60
|
+
|
61
|
+
def simple_init(*args)
|
62
|
+
options = BBLib.named_args(*args)
|
63
|
+
options.merge(options.delete(:options)) if options[:options]
|
64
|
+
USED_KEYWORDS.each { |k| options.delete(k) }
|
65
|
+
self.options = options
|
66
|
+
if args.first.is_a?(Symbol) && @action.nil?
|
67
|
+
self.action = args.shift
|
68
|
+
self.paths = args.shift if args.first.is_a?(String)
|
69
|
+
elsif action && args.first.is_a?(String)
|
70
|
+
self.paths = args.first
|
71
|
+
end
|
72
|
+
self.args += args.find_all { |arg| !arg.is_a?(Hash) } unless args.empty?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Monkey patches
|
78
|
+
class Hash
|
79
|
+
def hash_path_proc(*args)
|
80
|
+
BBLib::HashPathProc.new(*args).process(self)
|
81
|
+
end
|
82
|
+
|
83
|
+
alias hpath_proc hash_path_proc
|
84
|
+
end
|
85
|
+
|
86
|
+
# Monkey patches
|
87
|
+
class Array
|
88
|
+
def hash_path_proc(*args)
|
89
|
+
BBLib::HashPathProc.new(*args).process(self)
|
90
|
+
end
|
91
|
+
|
92
|
+
alias hpath_proc hash_path_proc
|
93
|
+
end
|