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,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
|
data/lib/html/bbhtml.rb
ADDED
data/lib/html/builder.rb
ADDED
@@ -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
|
data/lib/html/tag.rb
ADDED
@@ -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('"', '"')}\"" }.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
|
data/lib/mixins/attrs.rb
ADDED
@@ -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
|