tins 1.43.0 → 1.44.0

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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/.contexts/code_comment.rb +5 -8
  3. data/.contexts/lib.rb +0 -2
  4. data/CHANGES.md +12 -0
  5. data/README.md +158 -6
  6. data/Rakefile +19 -16
  7. data/examples/let.rb +8 -40
  8. data/examples/mail.rb +0 -1
  9. data/examples/turing.rb +3 -1
  10. data/lib/tins/alias.rb +1 -0
  11. data/lib/tins/annotate.rb +37 -27
  12. data/lib/tins/ask_and_send.rb +41 -0
  13. data/lib/tins/attempt.rb +39 -0
  14. data/lib/tins/bijection.rb +34 -0
  15. data/lib/tins/case_predicate.rb +21 -0
  16. data/lib/tins/complete.rb +16 -0
  17. data/lib/tins/concern.rb +64 -0
  18. data/lib/tins/date_dummy.rb +36 -4
  19. data/lib/tins/date_time_dummy.rb +34 -2
  20. data/lib/tins/deep_dup.rb +9 -2
  21. data/lib/tins/deprecate.rb +12 -0
  22. data/lib/tins/dslkit.rb +559 -83
  23. data/lib/tins/duration.rb +120 -5
  24. data/lib/tins/expose.rb +54 -5
  25. data/lib/tins/extract_last_argument_options.rb +9 -0
  26. data/lib/tins/file_binary.rb +104 -21
  27. data/lib/tins/find.rb +114 -11
  28. data/lib/tins/generator.rb +10 -2
  29. data/lib/tins/go.rb +81 -4
  30. data/lib/tins/hash_bfs.rb +4 -2
  31. data/lib/tins/hash_symbolize_keys_recursive.rb +62 -4
  32. data/lib/tins/hash_union.rb +47 -2
  33. data/lib/tins/if_predicate.rb +31 -0
  34. data/lib/tins/implement.rb +50 -0
  35. data/lib/tins/limited.rb +54 -5
  36. data/lib/tins/lines_file.rb +81 -2
  37. data/lib/tins/lru_cache.rb +54 -17
  38. data/lib/tins/memoize.rb +86 -58
  39. data/lib/tins/method_description.rb +87 -4
  40. data/lib/tins/minimize.rb +39 -11
  41. data/lib/tins/module_group.rb +27 -2
  42. data/lib/tins/named_set.rb +20 -0
  43. data/lib/tins/null.rb +86 -15
  44. data/lib/tins/once.rb +61 -4
  45. data/lib/tins/p.rb +44 -8
  46. data/lib/tins/partial_application.rb +66 -7
  47. data/lib/tins/proc_compose.rb +58 -1
  48. data/lib/tins/proc_prelude.rb +97 -10
  49. data/lib/tins/range_plus.rb +30 -2
  50. data/lib/tins/require_maybe.rb +36 -0
  51. data/lib/tins/responding.rb +39 -0
  52. data/lib/tins/secure_write.rb +24 -4
  53. data/lib/tins/sexy_singleton.rb +45 -48
  54. data/lib/tins/string_byte_order_mark.rb +33 -2
  55. data/lib/tins/string_camelize.rb +31 -2
  56. data/lib/tins/string_underscore.rb +33 -2
  57. data/lib/tins/string_version.rb +179 -10
  58. data/lib/tins/subhash.rb +35 -10
  59. data/lib/tins/temp_io.rb +7 -0
  60. data/lib/tins/temp_io_enum.rb +19 -0
  61. data/lib/tins/terminal.rb +31 -9
  62. data/lib/tins/thread_local.rb +67 -5
  63. data/lib/tins/time_dummy.rb +46 -21
  64. data/lib/tins/to.rb +15 -0
  65. data/lib/tins/to_proc.rb +17 -4
  66. data/lib/tins/token.rb +56 -1
  67. data/lib/tins/unit.rb +288 -149
  68. data/lib/tins/version.rb +1 -1
  69. data/lib/tins/write.rb +14 -3
  70. data/lib/tins/xt/blank.rb +81 -2
  71. data/lib/tins/xt/concern.rb +51 -0
  72. data/lib/tins/xt/full.rb +56 -11
  73. data/lib/tins/xt/irb.rb +46 -2
  74. data/lib/tins/xt/method_description.rb +0 -12
  75. data/lib/tins/xt/minimize.rb +7 -0
  76. data/lib/tins/xt/named.rb +71 -16
  77. data/lib/tins/xt/proc_compose.rb +4 -0
  78. data/lib/tins/xt/subhash.rb +11 -0
  79. data/lib/tins/xt/time_freezer.rb +43 -6
  80. data/lib/tins/xt.rb +1 -3
  81. data/lib/tins.rb +16 -3
  82. data/tests/duration_test.rb +4 -0
  83. data/tests/from_module_test.rb +30 -2
  84. data/tests/implement_test.rb +6 -8
  85. data/tests/lines_file_test.rb +2 -0
  86. data/tests/lru_cache_test.rb +12 -0
  87. data/tests/method_description_test.rb +14 -20
  88. data/tests/partial_application_test.rb +4 -0
  89. data/tests/proc_prelude_test.rb +1 -1
  90. data/tests/scope_test.rb +1 -1
  91. data/tests/string_version_test.rb +2 -0
  92. data/tests/to_test.rb +6 -6
  93. data/tins.gemspec +9 -9
  94. metadata +23 -41
  95. data/lib/tins/count_by.rb +0 -21
  96. data/lib/tins/deep_const_get.rb +0 -64
  97. data/lib/tins/timed_cache.rb +0 -51
  98. data/lib/tins/uniq_by.rb +0 -23
  99. data/lib/tins/xt/count_by.rb +0 -7
  100. data/lib/tins/xt/deep_const_get.rb +0 -7
  101. data/lib/tins/xt/uniq_by.rb +0 -25
  102. data/tests/count_by_test.rb +0 -17
  103. data/tests/deep_const_get_test.rb +0 -37
  104. data/tests/uniq_by_test.rb +0 -31
  105. /data/{COPYING → LICENSE} +0 -0
data/lib/tins/unit.rb CHANGED
@@ -1,179 +1,318 @@
1
1
  require 'strscan'
2
2
  require 'bigdecimal'
3
3
 
4
- module Tins::Unit
5
- Prefix = Struct.new(:name, :step, :multiplier, :fraction)
6
-
7
- PREFIX_LC = [
8
- '', 'k', 'm', 'g', 't', 'p', 'e', 'z', 'y',
9
- ].each_with_index.map { |n, i| Prefix.new(n.freeze, 1000, 1000 ** i, false) }.freeze
10
-
11
- PREFIX_UC = [
12
- '', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y',
13
- ].each_with_index.map { |n, i| Prefix.new(n.freeze, 1024, 1024 ** i, false) }.freeze
14
-
15
- PREFIX_F = [
16
- '', 'm', 'µ', 'n', 'p', 'f', 'a', 'z', 'y',
17
- ].each_with_index.map { |n, i| Prefix.new(n.freeze, 1000, 1000 ** -i, true) }.freeze
18
-
19
- class ParserError < ArgumentError; end
20
-
21
- module_function
22
-
23
- def prefixes(identifier)
24
- case identifier
25
- when :uppercase, :uc, 1024
26
- PREFIX_UC
27
- when :lowercase, :lc, 1000
28
- PREFIX_LC
29
- when :fraction, :f, 0.001
30
- PREFIX_F
31
- when Array
32
- identifier
33
- end
34
- end
4
+ module Tins
5
+ # A module for parsing and formatting unit specifications with support for
6
+ # various prefix types and unit formats.
7
+ #
8
+ # @example Basic usage
9
+ # Tins::Unit.parse('1.5 GB').round # => 1610612736
10
+ # Tins::Unit.format(1610612736, unit: 'B') # => "1.500000 GB"
11
+ # Tins::Unit.parse('1000 mb', prefix: 1000).round # => 1000000000
12
+ module Unit
13
+ # A simple data structure representing a unit prefix with name, scaling step,
14
+ # multiplier, and fraction flag.
15
+ Prefix = Struct.new(:name, :step, :multiplier, :fraction)
35
16
 
36
- def format(value, format: '%f %U', prefix: 1024, unit: ?b)
37
- prefixes = prefixes(prefix)
38
- first_prefix = prefixes.first or
39
- raise ArgumentError, 'a non-empty array of prefixes is required'
40
- if value.zero?
41
- result = format.sub('%U', unit)
42
- result %= value
43
- else
44
- prefix = prefixes[
45
- (first_prefix.fraction ? -1 : 1) * Math.log(value.abs) / Math.log(first_prefix.step)
46
- ]
47
- result = format.sub('%U', "#{prefix.name}#{unit}")
48
- result %= (value / prefix.multiplier.to_f)
49
- end
50
- end
17
+ # An array of prefix objects for lowercase decimal prefixes (k, M, G...)
18
+ # based on 1000-step increments.
19
+ PREFIX_LC = [
20
+ '', 'k', 'm', 'g', 't', 'p', 'e', 'z', 'y',
21
+ ].each_with_index.map { |n, i| Prefix.new(n.freeze, 1000, 1000 ** i, false) }.freeze
51
22
 
52
- class UnitParser < StringScanner
53
- NUMBER = /([+-]?
54
- (?:0|[1-9]\d*)
55
- (?:
56
- \.\d+(?i:e[+-]?\d+) |
57
- \.\d+ |
58
- (?i:e[+-]?\d+)
59
- )?
60
- )/x
61
-
62
- def initialize(source, unit, prefixes = nil)
63
- super source
64
- if prefixes
65
- @unit_re = unit_re(Tins::Unit.prefixes(prefixes), unit)
66
- @unit_lc_re = @unit_uc_re = nil
67
- else
68
- @unit_lc_re = unit_re(Tins::Unit.prefixes(:lc), unit)
69
- @unit_uc_re = unit_re(Tins::Unit.prefixes(:uc), unit)
70
- @unit_re = nil
23
+ # An array of prefix objects for uppercase binary prefixes (K, M, G...) based
24
+ # on 1024-step increments.
25
+ PREFIX_UC = [
26
+ '', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y',
27
+ ].each_with_index.map { |n, i| Prefix.new(n.freeze, 1024, 1024 ** i, false) }.freeze
28
+
29
+ # An array of prefix objects for fractional prefixes (m, µ, n...) based on
30
+ # 1000-step decrements.
31
+ PREFIX_F = [
32
+ '', 'm', 'µ', 'n', 'p', 'f', 'a', 'z', 'y',
33
+ ].each_with_index.map { |n, i| Prefix.new(n.freeze, 1000, 1000 ** -i, true) }.freeze
34
+
35
+ # A custom exception class for parser errors that inherits from ArgumentError
36
+ class ParserError < ArgumentError; end
37
+
38
+ module_function
39
+
40
+ # The prefixes method returns an array of prefix objects based on the given
41
+ # identifier.
42
+ #
43
+ # This method maps different identifier symbols and values to predefined
44
+ # arrays of prefix objects, allowing for flexible configuration of unit
45
+ # prefixes.
46
+ #
47
+ # @param identifier [Symbol, Integer, Array] the identifier specifying which
48
+ # prefix set to return
49
+ # @return [Array] an array of prefix objects corresponding to the identifier
50
+ def prefixes(identifier)
51
+ case identifier
52
+ when :uppercase, :uc, 1024
53
+ PREFIX_UC
54
+ when :lowercase, :lc, 1000
55
+ PREFIX_LC
56
+ when :fraction, :f, 0.001
57
+ PREFIX_F
58
+ when Array
59
+ identifier
71
60
  end
72
- @number = 1.0
73
61
  end
74
62
 
75
- def unit_re(prefixes, unit)
76
- re = Regexp.new(
77
- "(#{prefixes.reverse.map { |pre| Regexp.quote(pre.name) } * ?|})(#{unit})"
78
- )
79
- re.singleton_class.class_eval do
80
- define_method(:prefixes) { prefixes }
63
+ # Format a value using unit prefixes and a specified format template.
64
+ #
65
+ # This method takes a numerical value and formats it according to the given
66
+ # format string, inserting the appropriate unit prefix based on the specified
67
+ # prefix type and unit identifier.
68
+ #
69
+ # @param value [Numeric] the numerical value to format
70
+ # @param format [String] the format template to use for output, default is '%f %U'
71
+ # @param prefix [Object] the prefix configuration to use, default is 1024 (binary prefixes)
72
+ # @param unit [String, Symbol] the unit identifier to append, default is ?b (bytes)
73
+ def format(value, format: '%f %U', prefix: 1024, unit: ?b)
74
+ prefixes = prefixes(prefix)
75
+ first_prefix = prefixes.first or
76
+ raise ArgumentError, 'a non-empty array of prefixes is required'
77
+ if value.zero?
78
+ result = format.sub('%U', unit)
79
+ result %= value
80
+ else
81
+ prefix = prefixes[
82
+ (first_prefix.fraction ? -1 : 1) * Math.log(value.abs) / Math.log(first_prefix.step)
83
+ ]
84
+ result = format.sub('%U', "#{prefix.name}#{unit}")
85
+ result %= (value / prefix.multiplier.to_f)
81
86
  end
82
- re
83
87
  end
84
88
 
85
- private :unit_re
89
+ # A parser for unit specifications that extends StringScanner
90
+ #
91
+ # This class is responsible for parsing strings that contain numerical values
92
+ # followed by unit specifications, supporting various prefix types and unit
93
+ # formats for flexible unit parsing.
94
+ class UnitParser < StringScanner
95
+ # A regular expression matching a number.
96
+ NUMBER = /([+-]?
97
+ (?:0|[1-9]\d*)
98
+ (?:
99
+ \.\d+(?i:e[+-]?\d+) |
100
+ \.\d+ |
101
+ (?i:e[+-]?\d+)
102
+ )?
103
+ )/x
86
104
 
87
- attr_reader :number
105
+ # The initialize method sets up a new UnitParser instance with the given
106
+ # source string, unit identifier, and optional prefixes configuration.
107
+ #
108
+ # @param source [String] the input string to parse for units
109
+ # @param unit [String, Symbol] the unit identifier to look for in the source
110
+ # @param prefixes [Object, nil] optional prefixes configuration (can be array or symbol)
111
+ # @return [UnitParser] a new UnitParser instance configured with the provided parameters
112
+ def initialize(source, unit, prefixes = nil)
113
+ super source
114
+ if prefixes
115
+ @unit_re = unit_re(Tins::Unit.prefixes(prefixes), unit)
116
+ @unit_lc_re = @unit_uc_re = nil
117
+ else
118
+ @unit_lc_re = unit_re(Tins::Unit.prefixes(:lc), unit)
119
+ @unit_uc_re = unit_re(Tins::Unit.prefixes(:uc), unit)
120
+ @unit_re = nil
121
+ end
122
+ @number = 1.0
123
+ end
88
124
 
89
- def scan(re)
90
- re.nil? and return
91
- super
92
- end
125
+ # The unit_re method creates a regular expression for matching units with
126
+ # prefixes
127
+ #
128
+ # This method constructs a regular expression pattern that can match unit
129
+ # strings including their associated prefixes, which is useful for parsing
130
+ # formatted unit specifications in text.
131
+ #
132
+ # @param prefixes [Array] an array of prefix objects with name attributes
133
+ # @param unit [String] the base unit string to match against
134
+ #
135
+ # @return [Regexp] a regular expression object that can match prefixed units
136
+ def unit_re(prefixes, unit)
137
+ re = Regexp.new(
138
+ "(#{prefixes.reverse.map { |pre| Regexp.quote(pre.name) } * ?|})(#{unit})"
139
+ )
140
+ re.singleton_class.class_eval do
141
+ define_method(:prefixes) { prefixes }
142
+ end
143
+ re
144
+ end
93
145
 
94
- def scan_number
95
- scan(NUMBER) or return
96
- @number *= BigDecimal(self[1])
97
- end
146
+ private :unit_re
98
147
 
99
- def scan_unit
100
- case
101
- when scan(@unit_re)
102
- prefix = @unit_re.prefixes.find { |pre| pre.name == self[1] } or return
103
- @number *= prefix.multiplier
104
- when scan(@unit_lc_re)
105
- prefix = @unit_lc_re.prefixes.find { |pre| pre.name == self[1] } or return
106
- @number *= prefix.multiplier
107
- when scan(@unit_uc_re)
108
- prefix = @unit_uc_re.prefixes.find { |pre| pre.name == self[1] } or return
109
- @number *= prefix.multiplier
110
- end
111
- end
148
+ # The number reader method returns the value of the number attribute.
149
+ #
150
+ # @return [Object] the current value of the number attribute
151
+ attr_reader :number
112
152
 
113
- def scan_char(char)
114
- scan(/#{char}/) or return
115
- end
153
+ # The scan method scans a regular expression against the current string
154
+ # scanner state.
155
+ #
156
+ # This method delegates to the parent StringScanner's scan method, but
157
+ # includes a guard clause to return early if the provided regular
158
+ # expression is nil.
159
+ #
160
+ # @param re [Regexp, nil] the regular expression to scan for
161
+ # @return [String, nil] the matched string if found, or nil if no match
162
+ def scan(re)
163
+ re.nil? and return
164
+ super
165
+ end
116
166
 
117
- def parse
118
- raise NotImplementedError
119
- end
120
- end
167
+ # The scan_number method parses a number from the current string scanner
168
+ # state and multiplies the internal number value by it.
169
+ def scan_number
170
+ scan(NUMBER) or return
171
+ @number *= BigDecimal(self[1])
172
+ end
121
173
 
122
- class FormatParser < StringScanner
123
- def initialize(format, unit_parser)
124
- super format
125
- @unit_parser = unit_parser
126
- end
174
+ # The scan_unit method attempts to match unit patterns against the current
175
+ # string and updates the internal number value by multiplying it with the
176
+ # corresponding prefix multiplier
177
+ def scan_unit
178
+ case
179
+ when scan(@unit_re)
180
+ prefix = @unit_re.prefixes.find { |pre| pre.name == self[1] } or return
181
+ @number *= prefix.multiplier
182
+ when scan(@unit_lc_re)
183
+ prefix = @unit_lc_re.prefixes.find { |pre| pre.name == self[1] } or return
184
+ @number *= prefix.multiplier
185
+ when scan(@unit_uc_re)
186
+ prefix = @unit_uc_re.prefixes.find { |pre| pre.name == self[1] } or return
187
+ @number *= prefix.multiplier
188
+ end
189
+ end
127
190
 
128
- def reset
129
- super
130
- @unit_parser.reset
131
- end
191
+ # The scan_char method attempts to match a character pattern against the
192
+ # current string scanner state.
193
+ #
194
+ # @param char [String] the character to scan for
195
+ # @return [String, nil] the matched string if found, or nil if no match
196
+ def scan_char(char)
197
+ scan(/#{char}/) or return
198
+ end
132
199
 
133
- def location
134
- @unit_parser.peek(10).inspect
200
+ # The parse method is intended to be overridden by subclasses to provide
201
+ # specific parsing functionality.
202
+ #
203
+ # @return [Object] the parsed result
204
+ def parse
205
+ raise NotImplementedError
206
+ end
135
207
  end
136
208
 
137
- private :location
138
-
139
- def parse
140
- reset
141
- until eos? || @unit_parser.eos?
142
- case
143
- when scan(/%f/)
144
- @unit_parser.scan_number or
145
- raise ParserError, "\"%f\" expected at #{location}"
146
- when scan(/%U/)
147
- @unit_parser.scan_unit or
148
- raise ParserError, "\"%U\" expected at #{location}"
149
- when scan(/%%/)
150
- @unit_parser.scan_char(?%) or
151
- raise ParserError, "#{?%.inspect} expected at #{location}"
152
- else
153
- char = scan(/./)
154
- @unit_parser.scan_char(char) or
155
- raise ParserError, "#{char.inspect} expected at #{location}"
156
- end
209
+ # A parser for unit specifications that extends StringScanner
210
+ #
211
+ # This class is responsible for parsing strings that contain numerical values
212
+ # followed by unit specifications, supporting various prefix types and unit
213
+ # formats for flexible unit parsing.
214
+ class FormatParser < StringScanner
215
+
216
+ # The initialize method sets up a new UnitParser instance with the given
217
+ # format and unit parser.
218
+ #
219
+ # @param format [String] the format string to use for parsing
220
+ # @param unit_parser [Tins::Unit::UnitParser] the unit parser to use for
221
+ # parsing units
222
+ # @return [Tins::Unit::FormatParser] a new FormatParser instance configured
223
+ # with the provided parameters
224
+ def initialize(format, unit_parser)
225
+ super format
226
+ @unit_parser = unit_parser
227
+ end
228
+
229
+ # The reset method resets the unit parser state.
230
+ #
231
+ # This method calls the superclass reset implementation and then resets
232
+ # the internal unit parser instance to its initial state.
233
+ def reset
234
+ super
235
+ @unit_parser.reset
236
+ end
237
+
238
+ # The location method returns a string representation of the current
239
+ # parsing position by peeking at the next 10 characters from the unit
240
+ # parser and inspecting them
241
+ # @return [String] the inspected representation of the next 10 characters from the parser
242
+ # @private
243
+ def location
244
+ @unit_parser.peek(10).inspect
157
245
  end
158
- unless eos? && @unit_parser.eos?
159
- raise ParserError,
160
- "format #{string.inspect} and string "\
246
+
247
+ private :location
248
+
249
+ # The parse method parses a format string using a unit parser and returns
250
+ # the parsed number.
251
+ #
252
+ # This method processes a format template by scanning for specific pattern directives
253
+ # (%f for numbers, %U for units, %% for literal percent signs) and validates that
254
+ # the input string matches the expected format. It handles parsing errors by raising
255
+ # ParserError exceptions with descriptive messages about mismatches.
256
+ #
257
+ # @return [Float] the parsed numerical value with units applied
258
+ # @raise [ParserError] if the format string or input string doesn't match the expected pattern
259
+ # @raise [ParserError] if a required number or unit is missing at a specific location
260
+ # @raise [ParserError] if literal percent signs don't match expected positions
261
+ def parse
262
+ reset
263
+ until eos? || @unit_parser.eos?
264
+ case
265
+ when scan(/%f/)
266
+ @unit_parser.scan_number or
267
+ raise ParserError, "\"%f\" expected at #{location}"
268
+ when scan(/%U/)
269
+ @unit_parser.scan_unit or
270
+ raise ParserError, "\"%U\" expected at #{location}"
271
+ when scan(/%%/)
272
+ @unit_parser.scan_char(?%) or
273
+ raise ParserError, "#{?%.inspect} expected at #{location}"
274
+ else
275
+ char = scan(/./)
276
+ @unit_parser.scan_char(char) or
277
+ raise ParserError, "#{char.inspect} expected at #{location}"
278
+ end
279
+ end
280
+ unless eos? && @unit_parser.eos?
281
+ raise ParserError,
282
+ "format #{string.inspect} and string "\
161
283
  "#{@unit_parser.string.inspect} do not match"
284
+ end
285
+ @unit_parser.number
162
286
  end
163
- @unit_parser.number
164
287
  end
165
- end
166
288
 
167
- # Parse the string +string+ if it matches +format+ with the unit +unit+ and
168
- # the prefixes specified by +prefix+.
169
- def parse(string, format: '%f %U', unit: ?b, prefix: nil)
170
- prefixes = prefixes(prefix)
171
- FormatParser.new(format, UnitParser.new(string, unit, prefixes)).parse
172
- end
289
+ # Parse the string using the specified format and unit information
290
+ #
291
+ # This method takes a string and parses it according to a given format template,
292
+ # extracting numerical values and their associated units. It supports various
293
+ # prefix types and unit specifications for flexible parsing.
294
+ #
295
+ # @param string [String] the input string to parse
296
+ # @param format [String] the format template to use for parsing (default: '%f %U')
297
+ # @param unit [String, Symbol] the unit identifier to use (default: ?b)
298
+ # @param prefix [Object] the prefix configuration to use (default: nil)
299
+ #
300
+ # @return [Object] the parsed result based on the format and unit specifications
301
+ def parse(string, format: '%f %U', unit: ?b, prefix: nil)
302
+ prefixes = prefixes(prefix)
303
+ FormatParser.new(format, UnitParser.new(string, unit, prefixes)).parse
304
+ end
173
305
 
174
- def parse?(string, **options)
175
- parse(string, **options)
176
- rescue ParserError
177
- nil
306
+ # The parse? method attempts to parse a string using the specified options
307
+ # and returns nil if parsing fails.
308
+ #
309
+ # @param string [String] the string to parse
310
+ # @param options [Hash] the options to pass to the parse method
311
+ # @return [Object, nil] the parsed result or nil if parsing fails
312
+ def parse?(string, **options)
313
+ parse(string, **options)
314
+ rescue ParserError
315
+ nil
316
+ end
178
317
  end
179
318
  end
data/lib/tins/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Tins
2
2
  # Tins version
3
- VERSION = '1.43.0'
3
+ VERSION = '1.44.0'
4
4
  VERSION_ARRAY = VERSION.split('.').map(&:to_i) # :nodoc:
5
5
  VERSION_MAJOR = VERSION_ARRAY[0] # :nodoc:
6
6
  VERSION_MINOR = VERSION_ARRAY[1] # :nodoc:
data/lib/tins/write.rb CHANGED
@@ -1,11 +1,24 @@
1
1
  require 'tins/secure_write'
2
2
 
3
3
  module Tins
4
+ # Tins::Write provides secure write functionality that can be extended onto
5
+ # modules/classes to add a `write` method.
6
+ #
7
+ # When a module is extended with Tins::Write, it will:
8
+ # - Extend the module with SecureWrite methods
9
+ # - Conditionally alias `secure_write` to `write` if no existing `write` method exists
10
+ # - Issue a warning if `write` already exists (when $DEBUG is enabled)
4
11
  module Write
12
+ # Called when Tins::Write is extended onto a module.
13
+ # Extends the receiving module with SecureWrite functionality
14
+ # and conditionally aliases secure_write to write.
15
+ #
16
+ # @param modul [Module] The module being extended
5
17
  def self.extended(modul)
6
18
  modul.extend SecureWrite
7
19
  if modul.respond_to?(:write)
8
- $DEBUG and warn "Skipping inclusion of Tins::Write#write method, include Tins::Write::SecureWrite#secure_write instead"
20
+ $DEBUG and warn "Skipping inclusion of Tins::Write#write method, "\
21
+ "include Tins::Write::SecureWrite#secure_write instead"
9
22
  else
10
23
  class << modul; self; end.instance_eval do
11
24
  alias_method :write, :secure_write
@@ -15,5 +28,3 @@ module Tins
15
28
  end
16
29
  end
17
30
  end
18
-
19
- require 'tins/alias'
data/lib/tins/xt/blank.rb CHANGED
@@ -1,34 +1,83 @@
1
1
  module Tins
2
+ # The Tins::Blank module provides a consistent way to check if objects are
3
+ # "blank" (empty, nil, or contain only whitespace). This follows Rails'
4
+ # convention.
5
+ #
6
+ # @example Basic usage
7
+ # "".blank? # => true
8
+ # " ".blank? # => true
9
+ # "foo".blank? # => false
10
+ # [].blank? # => true
11
+ # [1, 2].blank? # => false
12
+ # nil.blank? # => true
13
+ # false.blank? # => true
14
+ # true.blank? # => false
15
+ #
16
+ # @example Using present?
17
+ # "".present? # => false
18
+ # "foo".present? # => true
2
19
  module Blank
20
+ # Blank behavior for Object instances
3
21
  module Object
22
+ # Provides a fallback implementation that checks for `empty?` method,
23
+ # falling back to negation of truthiness if not defined.
24
+ #
25
+ # @return [Boolean] true if the object is considered blank, false otherwise
4
26
  def blank?
5
27
  respond_to?(:empty?) ? empty? : !self
6
28
  end
7
29
 
30
+ # Checks if the object is not blank
31
+ #
32
+ # @return [Boolean] true if the object is present, false otherwise
8
33
  def present?
9
34
  !blank?
10
35
  end
11
36
  end
12
37
 
38
+ # Blank behavior for NilClass instances
13
39
  module NilClass
40
+ #
41
+ # Nil values are always considered blank.
42
+ #
43
+ # @return [Boolean] true (always)
14
44
  def blank?
15
45
  true
16
46
  end
17
47
  end
18
48
 
49
+ # Blank behavior for FalseClass instances
19
50
  module FalseClass
51
+ # False values are always considered blank.
52
+ #
53
+ # @return [Boolean] true (always)
20
54
  def blank?
21
55
  true
22
56
  end
23
57
  end
24
58
 
59
+ # Blank behavior for TrueClass instances
25
60
  module TrueClass
61
+ # True values are never considered blank.
62
+ #
63
+ # @return [Boolean] false (always)
26
64
  def blank?
27
65
  false
28
66
  end
29
67
  end
30
68
 
69
+ # Blank behavior for Array instances
70
+ #
71
+ # Arrays are blank if they are empty.
72
+ # This implementation aliases the `empty?` method to `blank?`.
31
73
  module Array
74
+ # The included method is a hook that gets called when this module is
75
+ # included in another class or module.
76
+ #
77
+ # It sets up blank? behavior by aliasing the empty? method to blank? in
78
+ # the including class/module.
79
+ #
80
+ # @param modul [Object] the class or module that includes this module
32
81
  def self.included(modul)
33
82
  modul.module_eval do
34
83
  alias_method :blank?, :empty?
@@ -36,7 +85,18 @@ module Tins
36
85
  end
37
86
  end
38
87
 
88
+ # Blank behavior for Hash instances
89
+ #
90
+ # Hashes are blank if they are empty.
91
+ # This implementation aliases the `empty?` method to `blank?`.
39
92
  module Hash
93
+ # The included method is a hook that gets called when this module is
94
+ # included in another class or module.
95
+ #
96
+ # It sets up blank? behavior by aliasing the empty? method to blank? in
97
+ # the including class/module.
98
+ #
99
+ # @param modul [Object] the class or module that includes this module
40
100
  def self.included(modul)
41
101
  modul.module_eval do
42
102
  alias_method :blank?, :empty?
@@ -44,20 +104,39 @@ module Tins
44
104
  end
45
105
  end
46
106
 
107
+ # Blank behavior for String instances
47
108
  module String
109
+ # Strings are blank if they contain only whitespace characters.
110
+ # This uses a regex match against non-whitespace characters.
111
+ #
112
+ # @return [Boolean] true if the string contains only whitespace, false otherwise
48
113
  def blank?
49
114
  self !~ /\S/
50
115
  end
51
116
  end
52
117
 
118
+ # Blank behavior for Numeric instances
119
+ #
120
+ # Numbers are considered blank only if they are zero.
121
+ # This implementation aliases the `zero?` method to `blank?`.
53
122
  module Numeric
54
- def blank?
55
- false
123
+ # The included method is a hook that gets called when this module is
124
+ # included in another class or module.
125
+ #
126
+ # It sets up blank? behavior by aliasing the zero? method to blank? in
127
+ # the including class/module.
128
+ #
129
+ # @param modul [Object] the class or module that includes this module
130
+ def self.included(modul)
131
+ modul.module_eval do
132
+ alias_method :blank?, :zero?
133
+ end
56
134
  end
57
135
  end
58
136
  end
59
137
  end
60
138
 
139
+ # Extend constant classes with Blank behavior unless blank? is already defined
61
140
  unless Object.respond_to?(:blank?)
62
141
  for k in Tins::Blank.constants
63
142
  Object.const_get(k).class_eval do