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