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,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