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