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,239 @@
1
+
2
+ module BBLib
3
+ def self.hash_path_proc(hash, *args)
4
+ HashPathProc.new(*args).process(hash)
5
+ end
6
+
7
+ HASH_PATH_PROC_TYPES = {
8
+ evaluate: [:eval, :equation, :equate],
9
+ debug: [:puts],
10
+ append: [:suffix],
11
+ prepend: [:prefix],
12
+ split: [:delimit, :delim, :separate, :msplit],
13
+ replace: [:swap],
14
+ replace_with: [],
15
+ extract: [:grab, :scan],
16
+ extract_first: [:grab_first, :scan_first],
17
+ extract_last: [:grab_last, :scan_last],
18
+ parse_date: [:date, :parse_time, :time],
19
+ parse_date_unix: [:unix_time, :unix_date],
20
+ parse_duration: [:duration],
21
+ parse_file_size: [:file_size],
22
+ to_string: [:to_s, :stringify],
23
+ downcase: [:lower, :lowercase, :to_lower],
24
+ upcase: [:upper, :uppercase, :to_upper],
25
+ roman: [:convert_roman, :roman_numeral, :parse_roman],
26
+ remove_symbols: [:chop_symbols, :drop_symbols],
27
+ format_articles: [:articles],
28
+ reverse: [:invert],
29
+ delete: [:del],
30
+ remove: [:rem],
31
+ custom: [:send],
32
+ encapsulate: [],
33
+ uncapsulate: [],
34
+ extract_integers: [:extract_ints, :extract_i],
35
+ extract_floats: [:extract_f],
36
+ extract_numbers: [:extract_nums],
37
+ max_number: [:max, :maximum, :maximum_number],
38
+ min_number: [:min, :minimum, :minimum_number],
39
+ avg_number: [:avg, :average, :average_number],
40
+ sum_number: [:sum],
41
+ strip: [:trim],
42
+ concat: [:join, :concat_with],
43
+ reverse_concat: [:reverse_join, :reverse_concat_with]
44
+ }.freeze
45
+
46
+ module HashPathProcs
47
+ def self.class_based_proc(child, class_based, *methods)
48
+ child.replace_with(
49
+ if class_based && child.node_class == Hash
50
+ child.value.map { |k, v| [BBLib.recursive_send(k, *methods), v] }.to_h
51
+ elsif class_based && child.node_class == Array
52
+ child.children.values.flat_map { |v| class_based_proc(v, true, *methods) }
53
+ else
54
+ BBLib.recursive_send(child.value, *methods)
55
+ end
56
+ )
57
+ end
58
+
59
+ def self.evaluate(child, *args, class_based: true)
60
+ value = child.value
61
+ if args.first.is_a?(String)
62
+ child.replace_with(eval(args.first))
63
+ else
64
+ child.replace_with(args.first.call(value))
65
+ end
66
+ end
67
+
68
+ def self.debug(child, *args, class_based: true)
69
+ puts "DEBUG: #{child.value}" + (args.empty? ? '' : "(#{args.join(', ')})")
70
+ child
71
+ end
72
+
73
+ def self.append(child, *args, class_based: true)
74
+ class_based_proc(child, class_based, :to_s, :dup, [:concat, args.first])
75
+ end
76
+
77
+ def self.prepend(child, *args, class_based: true)
78
+ class_based_proc(child, class_based, :to_s, :dup, [:prepend, args.first])
79
+ end
80
+
81
+ def self.split(child, *args, class_based: true)
82
+ class_based_proc(child, class_based, [:msplit, args])
83
+ rescue => e
84
+ class_based_proc(child, class_based, :to_s, [:msplit, args])
85
+ end
86
+
87
+ def self.replace(child, *args, class_based: true)
88
+ methods = [:to_s]
89
+ BBLib.hash_args(*args).each { |k, v| methods << [:gsub, (k ? k : k.to_s), v.to_s] }
90
+ class_based_proc(child, class_based, *methods)
91
+ end
92
+
93
+ def self.replace_with(child, *args, class_based: true)
94
+ child.replace_with(args.first)
95
+ end
96
+
97
+ def self.extract(child, *args, class_based: true)
98
+ class_based_proc(child, class_based, [:scan, args.first], [:[], BBLib.named_args(*args)[:slice] || (0..-1)])
99
+ end
100
+
101
+ def self.extract_first(child, *args, class_based: true)
102
+ class_based_proc(child, class_based, [:scan, args.first], :first)
103
+ end
104
+
105
+ def self.extract_last(child, *args, class_based: true)
106
+ class_based_proc(child, class_based, [:scan, args.first], :last)
107
+ end
108
+
109
+ def self.parse_date(child, *args, class_based: true)
110
+ format = BBLib.named_args(*args)[:format]
111
+ child.replace_with(
112
+ if class_based && child.node_class == Hash
113
+ child.value.map do |k, v|
114
+ [_parse_date(k, args, format), v]
115
+ end.to_h
116
+ elsif class_based && child.node_class == Array
117
+ child.value.map do |v|
118
+ _parse_date(v, args, format)
119
+ end
120
+ else
121
+ _parse_date(child.value, args, format)
122
+ end
123
+ )
124
+ end
125
+
126
+ def self._parse_date(value, patterns, format)
127
+ formatted = nil
128
+ patterns.each do |pattern|
129
+ next unless formatted.nil?
130
+ formatted = Time.strptime(value.to_s, pattern.to_s) rescue nil
131
+ formatted = formatted.strftime(format) if format
132
+ end
133
+ formatted
134
+ end
135
+
136
+ def self.parse_date_unix(child, *args, class_based: true)
137
+ child.replace_with(parse_date(child, *args, class_based: class_based).to_f)
138
+ end
139
+
140
+ def self.parse_duration(child, *args, class_based: true)
141
+ class_based_proc(child, class_based, :to_s, [:parse_duration, BBLib.named_args(*args)])
142
+ end
143
+
144
+ def self.parse_file_size(child, *args, class_based: true)
145
+ class_based_proc(child, class_based, :to_s, [:parse_file_size, BBLib.named_args(*args)])
146
+ end
147
+
148
+ def self.to_string(child, *args, class_based: true)
149
+ class_based_proc(child, class_based, :to_s)
150
+ end
151
+
152
+ def self.downcase(child, *args, class_based: true)
153
+ class_based_proc(child, class_based, :to_s, :downcase)
154
+ end
155
+
156
+ def self.upcase(child, *args, class_based: true)
157
+ class_based_proc(child, class_based, :to_s, :upcase)
158
+ end
159
+
160
+ def self.roman(child, *args, class_based: true)
161
+ class_based_proc(child, class_based, :to_s, (args.first == :to ? :to_roman : :from_roman))
162
+ end
163
+
164
+ def self.remove_symbols(child, *args, class_based: true)
165
+ class_based_proc(child, class_based, :to_s, :drop_symbols)
166
+ end
167
+
168
+ def self.format_articles(child, *args, class_based: true)
169
+ class_based_proc(child, class_based, :to_s, [:format_articles, args.first || :front])
170
+ end
171
+
172
+ def self.reverse(child, *args, class_based: true)
173
+ class_based_proc(child, class_based, :reverse)
174
+ end
175
+
176
+ def self.delete(child, *args, class_based: true)
177
+ child.kill
178
+ end
179
+
180
+ def self.remove(child, *args, class_based: true)
181
+ methods = args.map { |a| [:gsub, a, ''] }
182
+ methods.shift(:to_s)
183
+ class_based_proc(child, class_based, *methods )
184
+ end
185
+
186
+ def self.custom(child, *args, class_based: true)
187
+ class_based_proc(child, class_based, *args)
188
+ end
189
+
190
+ def self.encapsulate(child, *args, class_based: true)
191
+ class_based_proc(child, class_based, :to_s, [:concat, args.first.to_s], [:prepend, args.first.to_s])
192
+ end
193
+
194
+ def self.uncapsulate(child, *args, class_based: true)
195
+ class_based_proc(child, class_based, [:uncapsulate, args.first])
196
+ end
197
+
198
+ def self.max(child, *args, class_based: true)
199
+ class_based_proc(child, class_based, :max)
200
+ end
201
+
202
+ def self.min(child, *args, class_based: true)
203
+ class_based_proc(child, class_based, :min)
204
+ end
205
+
206
+ def self.avg(child, *args, class_based: true)
207
+ nums = child.value.to_s.extract_numbers
208
+ child.replace_with(nums.inject { |s, x| s + x }.to_f / nums.size.to_f)
209
+ end
210
+
211
+ def self.sum(child, *args, class_based: true)
212
+ child.replace_with(value.to_s.extract_numbers.inject { |s, x| s + x })
213
+ end
214
+
215
+ def self.strip(child, *args, class_based: true)
216
+ class_based_proc(child, class_based, :to_s, :strip)
217
+ end
218
+
219
+ def self.extract_integers(child, *args, class_based: true)
220
+ class_based_proc(child, class_based, :to_s, :extract_integers)
221
+ end
222
+
223
+ def self.extract_floats(child, *args, class_based: true)
224
+ class_based_proc(child, class_based, :to_s, :extract_floats)
225
+ end
226
+
227
+ def self.extract_numbers(child, *args, class_based: true)
228
+ class_based_proc(child, class_based, :to_s, :extract_numbers)
229
+ end
230
+
231
+ def self.concat(child, *args, class_based: true)
232
+ child.replace_with(([child.value] + child.root.find(args.first)).map { |v| v.to_s }.join)
233
+ end
234
+
235
+ def self.reverse_concat(child, *args, class_based: true)
236
+ child.replace_with(([child.value] + child.root.find(args.first)).map { |v| v.to_s }.reverse.join)
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'builder'
2
+ require_relative 'tag'
@@ -0,0 +1,34 @@
1
+ module BBLib
2
+ module HTML
3
+ TAGS = %w{a abbr address area article aside audio b base bdi bdo blockquote body br button canvas caption cite code col colgroup command datalist dd del details dfn div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins kbd keygen label legend li link map mark menu meta meter nav noscript object ol optgroup option output p param pre progress q rp rt ruby s samp script section select small source span strong style sub summary sup table tbody td textarea tfoot th thead time title tr track u ul var video wbr}
4
+
5
+ SELF_CLOSING_TAGS = %w{area base br col command embed hr img input keygen link meta param source track wbr}
6
+
7
+ def self.self_close?(tag)
8
+ SELF_CLOSING_TAGS.include?(tag.to_s.downcase)
9
+ end
10
+
11
+ module Builder
12
+
13
+ BBLib::HTML::TAGS.each do |tag|
14
+ define_method(tag) do |content = nil, **attributes, &block|
15
+ context = attributes.delete(:context) || self.context
16
+ t = Tag.new(type: tag, attributes: attributes, content: content, context: context)
17
+ children << t
18
+ t.instance_eval(&block) if block
19
+ end
20
+ end
21
+
22
+ def build(&block)
23
+ instance_eval(&block)
24
+ self
25
+ end
26
+
27
+ def self.build(type, content = nil, **attributes, &block)
28
+ raise ArgumentError, "Unknown element type '#{type}'." unless TAGS.include?(type.to_s.downcase)
29
+ context = attributes.delete(:context)
30
+ Tag.new(type: type, content: content, attributes: attributes, context: context, &block)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,49 @@
1
+ module BBLib
2
+ module HTML
3
+ class Tag
4
+ include BBLib::Effortless
5
+ include Builder
6
+
7
+ attr_str :type, required: true, arg_at: 0, arg_at_accept: [String, Symbol]
8
+ attr_str :content, arg_at: 1, arg_at_accept: [String, Symbol]
9
+ attr_hash :attributes, default: {}
10
+ attr_ary_of Tag, :children, default: []
11
+ attr_of Object, :context, default: nil, allow_nil: true
12
+
13
+ def render(pretty: false, tabs: 0)
14
+ cont = render_content(pretty: pretty, tabs: tabs)
15
+ tabbing = pretty ? ("\n" + ("\t" * tabs)) : ''
16
+ "#{tabbing}<#{type}#{render_attributes}" +
17
+ if cont || !BBLib::HTML.self_close?(type)
18
+ ">#{cont}#{tabbing}</#{type}>"
19
+ else
20
+ "/>"
21
+ end
22
+ end
23
+
24
+ def to_s
25
+ render
26
+ end
27
+
28
+ def to_str
29
+ to_s
30
+ end
31
+
32
+ def render_attributes
33
+ return nil if attributes.empty?
34
+ attributes[:style] = attributes[:style].map { |k, v| "#{k}: #{v}" }.join('; ') if attributes[:style] && attributes[:style].is_a?(Hash)
35
+ ' ' + attributes.map { | k, v| "#{k}=\"#{v.to_s.gsub('"', '&#34;')}\"" }.join(' ')
36
+ end
37
+
38
+ def render_content(pretty: false, tabs: 0)
39
+ return nil if (content.nil? || content.empty?) && children.empty?
40
+ tabbing = pretty ? ("\n" + ("\t" * (tabs + 1))) : ''
41
+ text = if content && !content.empty?
42
+ "#{tabbing}#{content.gsub("\n", tabbing)}"
43
+ end
44
+ html = children.map { |tag| tag.render(pretty: pretty, tabs: tabs + 1) }.join
45
+ [text, html].compact.join
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,42 @@
1
+
2
+ module BBLib
3
+
4
+ def self.logger
5
+ @logger ||= default_logger
6
+ end
7
+
8
+ def self.default_logger
9
+ log = ::Logger.new(STDOUT)
10
+ log.level = ::Logger::INFO
11
+ log.formatter = proc do |severity, datetime, progname, msg|
12
+ if msg.is_a?(Exception)
13
+ msg = msg.inspect + "\n\t" + msg.backtrace.join("\n\t")
14
+ end
15
+ "[#{datetime}] #{severity} - #{msg}\n"
16
+ end
17
+ log.datetime_format = '%Y-%m-%d %H:%M:%S'
18
+ log
19
+ end
20
+
21
+ def self.logger=(logger)
22
+ raise ArgumentError, 'Must be set to a valid logger' unless logger.is_a?(Logger)
23
+ @logger = logger
24
+ end
25
+
26
+ def self.enable_logger(enable = true)
27
+ @logger_on = enable
28
+ end
29
+
30
+ def self.log_enabled?
31
+ @logger_on
32
+ end
33
+
34
+ class << self
35
+ [:fatal, :error, :warn, :info, :debug].each do |sev|
36
+ define_method(sev) do |*args|
37
+ logger.send(sev, *args)
38
+ end
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,422 @@
1
+
2
+
3
+ module BBLib
4
+ # Adds type casted attr getters and setters with all kinds of other goodness to ensure
5
+ # classes accept, set and return data the correct way without requiring piles or boiler plate
6
+ # code.
7
+ module Attrs
8
+
9
+ def _attrs
10
+ @_attrs ||= _ancestor_attrs
11
+ end
12
+
13
+ def _ancestor_attrs
14
+ hash = {}
15
+ ancestors.reverse.each do |ancestor|
16
+ next if ancestor == self
17
+ hash = hash.merge(ancestor._attrs) if ancestor.respond_to?(:_attrs)
18
+ end
19
+ # Need to dup to avoid subclasses modifying parents
20
+ hash.hmap do |k, v|
21
+ v[:options] = v[:options].dup
22
+ [k, v.dup]
23
+ end
24
+ end
25
+
26
+ def attr_set(name, opts = {})
27
+ return false unless _attrs[name]
28
+ opts.each do |k, v|
29
+ _attrs[name][:options][k] = v
30
+ end
31
+ end
32
+
33
+ # Lists all attr_* getter methods that were created on this class.
34
+ def instance_readers
35
+ _attrs.map { |k, v| [:attr_writer].any? { |t| v[:type] == t } ? nil : k }.compact
36
+ end
37
+
38
+ # Lists all attr_* setter methods that were created on this class.
39
+ def instance_writers
40
+ _attrs.keys.map { |m| "#{m}=".to_sym }.select { |m| method_defined?(m) }
41
+ end
42
+
43
+ def attr_reader(*args, **opts)
44
+ args.each do |arg|
45
+ _register_attr(arg, :attr_reader)
46
+ serialize_method(arg, opts[:serialize_method], opts[:serialize_opts] || {}) if _attr_serialize?(opts)
47
+ end
48
+ super(*args)
49
+ end
50
+
51
+ def attr_writer(*args, **opts)
52
+ args.each { |arg| _register_attr(arg, :attr_writer) }
53
+ super(*args)
54
+ end
55
+
56
+ def attr_accessor(*args, **opts)
57
+ args.each do |arg|
58
+ _register_attr(arg, :attr_accessor)
59
+ serialize_method(arg, opts[:serialize_method], opts[:serialize_opts] || {}) if _attr_serialize?(opts)
60
+ end
61
+ super(*args)
62
+ end
63
+
64
+ def attr_custom(method, opts = {}, &block)
65
+ called_by = caller_locations(1, 1)[0].label.gsub('block in ', '') rescue :attr_custom
66
+ type = (called_by =~ /^attr_/ ? called_by.to_sym : (opts[:attr_type] || :custom))
67
+ opts = opts.dup
68
+ ivar = "@#{method}".to_sym
69
+ mthd_type = opts[:singleton] ? :define_singleton_method : :define_method
70
+
71
+ self.send(mthd_type, "#{method}=") do |*args|
72
+ args = opts[:pre_proc].call(*args) if opts[:pre_proc]
73
+ instance_variable_set(ivar, (args.is_a?(Array) ? yield(*args) : yield(args)))
74
+ end
75
+
76
+ self.send(mthd_type, method) do
77
+ if opts[:getter] && opts[:getter].is_a?(Proc)
78
+ opts[:getter].arity == 0 ? opts[:getter].call : opts[:getter].call(self)
79
+ elsif instance_variable_defined?(ivar) && !(var = instance_variable_get(ivar)).nil?
80
+ var
81
+ elsif opts.include?(:default) || opts.include?(:default_proc)
82
+ default_value =
83
+ if opts[:default].respond_to?(:dup) && BBLib.is_a?(opts[:default], Array, Hash)
84
+ opts[:default].dup rescue opts[:default]
85
+ elsif opts[:default_proc].is_a?(Proc)
86
+ prc = opts[:default_proc]
87
+ prc.arity == 0 ? prc.call : prc.call(self)
88
+ elsif opts[:default_proc].is_a?(Symbol)
89
+ send(opts[:default_proc])
90
+ else
91
+ opts[:default]
92
+ end
93
+ send("#{method}=", default_value)
94
+ end
95
+ end
96
+
97
+ unless opts[:singleton]
98
+ protected method if opts[:protected] || opts[:protected_reader]
99
+ protected "#{method}=".to_sym if opts[:protected] || opts[:protected_writer]
100
+ private method if opts[:private] || opts[:private_reader]
101
+ private "#{method}=".to_sym if opts[:private] || opts[:private_writer]
102
+
103
+ serialize_method(method, opts[:serialize_method], (opts[:serialize_opts] || {}).merge(default: opts[:default])) if _attr_serialize?(opts)
104
+ _register_attr(method, type, opts)
105
+ end
106
+ end
107
+
108
+ def _attr_serialize?(opts)
109
+ return false unless respond_to?(:serialize_method)
110
+ (opts[:private] || opts[:protected]) && opts[:serialize] ||
111
+ (opts.include?(:serialize) && opts[:serialize]) || !opts.include?(:serialize)
112
+ end
113
+
114
+ def attr_of(klasses, *methods, **opts)
115
+ allowed = [klasses].flatten
116
+ methods.each do |method|
117
+ attr_custom(method, opts.merge(_attr_type: :of, classes: klasses)) do |arg|
118
+ if BBLib.is_a?(arg, *allowed) || (arg.nil? && opts[:allow_nil])
119
+ arg
120
+ elsif arg && (!opts.include?(:pack) || opts[:pack]) && arg = _attr_pack(arg, klasses, opts)
121
+ arg
122
+ else
123
+ raise ArgumentError, "#{method} must be set to a class of #{allowed.join_terms(:or)}, NOT #{arg.class}" unless opts[:suppress]
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ def attr_string(*methods, **opts)
130
+ methods.each do |method|
131
+ attr_custom(method, opts) { |arg| arg.nil? && opts[:allow_nil] ? arg : arg.to_s }
132
+ end
133
+ end
134
+
135
+ alias attr_str attr_string
136
+
137
+ def attr_integer(*methods, **opts)
138
+ methods.each do |method|
139
+ attr_custom(method, opts) { |arg| arg.nil? && opts[:allow_nil] ? arg : arg.to_i }
140
+ end
141
+ end
142
+
143
+ alias attr_int attr_integer
144
+
145
+ def attr_float(*methods, **opts)
146
+ methods.each do |method|
147
+ attr_custom(method, opts) { |arg| arg.nil? && opts[:allow_nil] ? arg : arg.to_f }
148
+ end
149
+ end
150
+
151
+ def attr_symbol(*methods, **opts)
152
+ methods.each do |method|
153
+ attr_custom(method, opts) do |arg|
154
+ if arg.nil?
155
+ opts[:allow_nil] ? arg : raise(ArgumentError, "#{method} cannot be set to nil.")
156
+ else
157
+ arg.to_s.to_sym
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ alias attr_sym attr_symbol
164
+
165
+ def attr_boolean(*methods, **opts)
166
+ methods.each do |method|
167
+ attr_custom(method, opts) { |arg| arg ? true : false }
168
+ next if opts[:no_?]
169
+ if opts[:singleton]
170
+ singleton_class.send(:alias_method, "#{method}?", method)
171
+ else
172
+ alias_method "#{method}?", method
173
+ end
174
+ end
175
+ end
176
+
177
+ alias attr_bool attr_boolean
178
+
179
+ def attr_integer_between(min, max, *methods, **opts)
180
+ methods.each do |method|
181
+ attr_custom(method, opts) { |arg| arg.nil? && opts[:allow_nil] ? arg : BBLib.keep_between(arg, min, max) }
182
+ end
183
+ end
184
+
185
+ alias attr_int_between attr_integer_between
186
+ alias attr_float_between attr_integer_between
187
+
188
+ def attr_integer_loop(min, max, *methods, **opts)
189
+ methods.each do |method|
190
+ attr_custom(method, opts) { |arg| arg.nil? && opts[:allow_nil] ? arg : BBLib.loop_between(arg, min, max) }
191
+ end
192
+ end
193
+
194
+ alias attr_int_loop attr_integer_loop
195
+ alias attr_float_loop attr_integer_loop
196
+
197
+ def attr_element_of(list, *methods, **opts)
198
+ methods.each do |method|
199
+ attr_custom(method, opts.merge(list: list)) do |arg|
200
+ ls = list.is_a?(Proc) ? list.call : list
201
+ if ls.include?(arg) || (opts[:allow_nil] && arg.nil?)
202
+ arg
203
+ else
204
+ raise ArgumentError, "Invalid option '#{arg}' for #{method}." unless opts.include?(:raise) && !opts[:raise]
205
+ end
206
+ end
207
+ end
208
+ end
209
+
210
+ def attr_elements_of(list, *methods, **opts)
211
+ methods.each do |method|
212
+ attr_custom(method, opts.merge(list: list)) do |args|
213
+ ls = list.is_a?(Proc) ? list.call : list
214
+ [].tap do |final|
215
+ [args].flatten(1).each do |arg|
216
+ if ls.include?(arg) || (opts[:allow_nil] && arg.nil?)
217
+ final << arg
218
+ else
219
+ raise ArgumentError, "Invalid option '#{arg}' for #{method}." unless opts.include?(:raise) && !opts[:raise]
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+
227
+ def attr_array(*methods, **opts)
228
+ methods.each do |method|
229
+ attr_custom(method, opts) do |arg|
230
+ args = arg.is_a?(Array) ? arg : [arg]
231
+ args = args.uniq if opts[:uniq]
232
+ args
233
+ end
234
+ attr_array_adder(method, opts[:adder_name], singleton: opts[:singleton]) if opts[:add_rem] || opts[:adder]
235
+ attr_array_remover(method, opts[:remover_name], singleton: opts[:singleton]) if opts[:add_rem] || opts[:remover]
236
+ end
237
+ end
238
+
239
+ alias attr_ary attr_array
240
+
241
+ def attr_array_of(klasses, *methods, **opts)
242
+ klasses = [klasses].flatten
243
+ methods.each do |method|
244
+ attr_custom(method, opts.merge(classes: klasses)) do |args|
245
+ array = []
246
+ if args.nil?
247
+ if opts[:allow_nil]
248
+ array = nil
249
+ else
250
+ raise ArgumentError, "#{method} cannot be set to nil."
251
+ end
252
+ else
253
+ args = [args] unless args.is_a?(Array)
254
+ args.each do |arg|
255
+ match = BBLib.is_a?(arg, *klasses)
256
+ if match
257
+ array.push(arg) if match
258
+ elsif arg && (!opts.include?(:pack) || opts[:pack]) && arg = _attr_pack(arg, klasses, opts)
259
+ array.push(arg)
260
+ else
261
+ raise ArgumentError, "Invalid class passed to #{method}: #{arg.class}. Must be a #{klasses.join_terms(:or)}." unless opts[:raise] == false
262
+ end
263
+ end
264
+ end
265
+ array
266
+ end
267
+ attr_array_adder(method, opts[:adder_name], singleton: opts[:singleton]) if opts[:add_rem] || opts[:adder]
268
+ attr_array_remover(method, opts[:remover_name], singleton: opts[:singleton]) if opts[:add_rem] || opts[:remover]
269
+ end
270
+ end
271
+
272
+ alias attr_ary_of attr_array_of
273
+
274
+ def attr_array_adder(method, name = nil, singleton: false, &block)
275
+ name = "add_#{method}" unless name
276
+ mthd_type = singleton ? :define_singleton_method : :define_method
277
+ send(mthd_type, name) do |*args|
278
+ array = send(method)
279
+ args.each do |arg|
280
+ arg = yield(arg) if block_given?
281
+ array.push(arg)
282
+ end
283
+ send("#{method}=", array)
284
+ end
285
+ end
286
+
287
+ def attr_array_remover(method, name = nil, singleton: false)
288
+ name = "remove_#{method}" unless name
289
+ define_method(name) do |*args|
290
+ array = instance_variable_get("@#{method}")
291
+ args.map do |arg|
292
+ next unless array && !array.empty?
293
+ array.delete(arg)
294
+ end
295
+ end
296
+ end
297
+
298
+ def attr_file(*methods, **opts)
299
+ methods.each do |method|
300
+ attr_custom(method, opts) do |arg|
301
+ exists = File.exist?(arg.to_s)
302
+ if !exists && opts[:mkfile] && arg
303
+ FileUtils.touch(arg.to_s)
304
+ exists = File.exist?(arg.to_s)
305
+ end
306
+ raise ArgumentError, "#{method} must be set to a valid file. '#{arg}' cannot be found." unless exists || (opts[:allow_nil] && arg.nil?)
307
+ arg
308
+ end
309
+ end
310
+ end
311
+
312
+ def attr_dir(*methods, **opts)
313
+ methods.each do |method|
314
+ attr_custom(method, opts) do |arg|
315
+ exists = Dir.exist?(arg.to_s)
316
+ if !exists && (opts[:mkdir] || opts[:mkpath]) && arg
317
+ FileUtils.mkpath(arg.to_s)
318
+ exists = Dir.exist?(arg.to_s)
319
+ end
320
+ raise ArgumentError, "#{method} must be set to a valid directory. '#{arg}' cannot be found." unless exists || (opts[:allow_nil] && arg.nil?)
321
+ arg
322
+ end
323
+ end
324
+ end
325
+
326
+ def attr_time(*methods, **opts)
327
+ methods.each do |method|
328
+ attr_custom(method, **opts) do |arg|
329
+ if opts[:formats]
330
+ arg = arg.to_s
331
+ opts[:format].each do |format|
332
+ arg = Time.strftime(arg, format) rescue arg
333
+ end
334
+ end
335
+ if arg.is_a?(Time) || arg.nil? && opts[:allow_nil]
336
+ arg
337
+ elsif arg.is_a?(Numeric)
338
+ Time.at(arg)
339
+ else
340
+ begin
341
+ Time.parse(arg.to_s)
342
+ rescue => e
343
+ nil
344
+ end
345
+ end
346
+ end
347
+ end
348
+ end
349
+
350
+ def attr_date(*methods, **opts)
351
+ methods.each do |method|
352
+ attr_custom(method, **opts) do |arg|
353
+ if opts[:formats]
354
+ arg = arg.to_s
355
+ opts[:format].each do |format|
356
+ arg = Date.strftime(arg, format) rescue arg
357
+ end
358
+ end
359
+ if arg.is_a?(Date) || arg.nil? && opts[:allow_nil]
360
+ arg
361
+ elsif arg.is_a?(Numeric)
362
+ Date.parse(Time.at(arg).to_s)
363
+ else
364
+ begin
365
+ Date.parse(arg.to_s)
366
+ rescue => e
367
+ nil
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end
373
+
374
+ def attr_hash(*methods, **opts)
375
+ methods.each do |method|
376
+ attr_custom(method, **opts) do |arg|
377
+ raise ArgumentError, "#{method} must be set to a hash, not a #{arg.class} (for #{self})." unless arg.is_a?(Hash) || arg.nil? && opts[:allow_nil]
378
+ if opts[:keys] && arg
379
+ arg.keys.each do |key|
380
+ if BBLib.is_a?(key, *opts[:keys])
381
+ next
382
+ elsif (opts.include?(:pack_key) && opts[:pack_key]) && new_key = _attr_pack(key, klasses, opts)
383
+ arg[new_key] = arg.delete(key)
384
+ else
385
+ raise ArgumentError, "Invalid key type for #{method}: #{key.class}. Must be #{opts[:keys].join_terms(:or)}."
386
+ end
387
+ end
388
+ end
389
+ if opts[:values] && arg
390
+ arg.each do |key, value|
391
+ if BBLib.is_a?(value, *opts[:values])
392
+ next
393
+ elsif (!opts.include?(:pack_value) || opts[:pack_value]) && value = _attr_pack(value, klasses, opts)
394
+ arg[key] = arg.delete(value)
395
+ else
396
+ raise ArgumentError, "Invalid value type for #{method}: #{value.class}. Must be #{opts[:values].join_terms(:or)}."
397
+ end
398
+ end
399
+ end
400
+ arg = arg.keys_to_sym if opts[:symbol_keys] && arg
401
+ arg
402
+ end
403
+ end
404
+ end
405
+
406
+ def _attr_pack(arg, klasses, opts = {})
407
+ klasses = [klasses].flatten
408
+ unless BBLib.is_a?(arg, *klasses)
409
+ return klasses.first.new(*[arg].flatten(1))
410
+ end
411
+ nil
412
+ end
413
+
414
+ protected
415
+
416
+ def _register_attr(method, type, opts = {})
417
+ _attrs[method] = { type: type.to_s.sub(/^attr_/, '').to_sym, options: opts }
418
+ method
419
+ end
420
+
421
+ end
422
+ end