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.
- checksums.yaml +4 -4
- data/.contexts/code_comment.rb +5 -8
- data/.contexts/lib.rb +0 -2
- data/CHANGES.md +12 -0
- data/README.md +158 -6
- data/Rakefile +19 -16
- data/examples/let.rb +8 -40
- data/examples/mail.rb +0 -1
- data/examples/turing.rb +3 -1
- data/lib/tins/alias.rb +1 -0
- data/lib/tins/annotate.rb +37 -27
- data/lib/tins/ask_and_send.rb +41 -0
- data/lib/tins/attempt.rb +39 -0
- data/lib/tins/bijection.rb +34 -0
- data/lib/tins/case_predicate.rb +21 -0
- data/lib/tins/complete.rb +16 -0
- data/lib/tins/concern.rb +64 -0
- data/lib/tins/date_dummy.rb +36 -4
- data/lib/tins/date_time_dummy.rb +34 -2
- data/lib/tins/deep_dup.rb +9 -2
- data/lib/tins/deprecate.rb +12 -0
- data/lib/tins/dslkit.rb +559 -83
- data/lib/tins/duration.rb +120 -5
- data/lib/tins/expose.rb +54 -5
- data/lib/tins/extract_last_argument_options.rb +9 -0
- data/lib/tins/file_binary.rb +104 -21
- data/lib/tins/find.rb +114 -11
- data/lib/tins/generator.rb +10 -2
- data/lib/tins/go.rb +81 -4
- data/lib/tins/hash_bfs.rb +4 -2
- data/lib/tins/hash_symbolize_keys_recursive.rb +62 -4
- data/lib/tins/hash_union.rb +47 -2
- data/lib/tins/if_predicate.rb +31 -0
- data/lib/tins/implement.rb +50 -0
- data/lib/tins/limited.rb +54 -5
- data/lib/tins/lines_file.rb +81 -2
- data/lib/tins/lru_cache.rb +54 -17
- data/lib/tins/memoize.rb +86 -58
- data/lib/tins/method_description.rb +87 -4
- data/lib/tins/minimize.rb +39 -11
- data/lib/tins/module_group.rb +27 -2
- data/lib/tins/named_set.rb +20 -0
- data/lib/tins/null.rb +86 -15
- data/lib/tins/once.rb +61 -4
- data/lib/tins/p.rb +44 -8
- data/lib/tins/partial_application.rb +66 -7
- data/lib/tins/proc_compose.rb +58 -1
- data/lib/tins/proc_prelude.rb +97 -10
- data/lib/tins/range_plus.rb +30 -2
- data/lib/tins/require_maybe.rb +36 -0
- data/lib/tins/responding.rb +39 -0
- data/lib/tins/secure_write.rb +24 -4
- data/lib/tins/sexy_singleton.rb +45 -48
- data/lib/tins/string_byte_order_mark.rb +33 -2
- data/lib/tins/string_camelize.rb +31 -2
- data/lib/tins/string_underscore.rb +33 -2
- data/lib/tins/string_version.rb +179 -10
- data/lib/tins/subhash.rb +35 -10
- data/lib/tins/temp_io.rb +7 -0
- data/lib/tins/temp_io_enum.rb +19 -0
- data/lib/tins/terminal.rb +31 -9
- data/lib/tins/thread_local.rb +67 -5
- data/lib/tins/time_dummy.rb +46 -21
- data/lib/tins/to.rb +15 -0
- data/lib/tins/to_proc.rb +17 -4
- data/lib/tins/token.rb +56 -1
- data/lib/tins/unit.rb +288 -149
- data/lib/tins/version.rb +1 -1
- data/lib/tins/write.rb +14 -3
- data/lib/tins/xt/blank.rb +81 -2
- data/lib/tins/xt/concern.rb +51 -0
- data/lib/tins/xt/full.rb +56 -11
- data/lib/tins/xt/irb.rb +46 -2
- data/lib/tins/xt/method_description.rb +0 -12
- data/lib/tins/xt/minimize.rb +7 -0
- data/lib/tins/xt/named.rb +71 -16
- data/lib/tins/xt/proc_compose.rb +4 -0
- data/lib/tins/xt/subhash.rb +11 -0
- data/lib/tins/xt/time_freezer.rb +43 -6
- data/lib/tins/xt.rb +1 -3
- data/lib/tins.rb +16 -3
- data/tests/duration_test.rb +4 -0
- data/tests/from_module_test.rb +30 -2
- data/tests/implement_test.rb +6 -8
- data/tests/lines_file_test.rb +2 -0
- data/tests/lru_cache_test.rb +12 -0
- data/tests/method_description_test.rb +14 -20
- data/tests/partial_application_test.rb +4 -0
- data/tests/proc_prelude_test.rb +1 -1
- data/tests/scope_test.rb +1 -1
- data/tests/string_version_test.rb +2 -0
- data/tests/to_test.rb +6 -6
- data/tins.gemspec +9 -9
- metadata +23 -41
- data/lib/tins/count_by.rb +0 -21
- data/lib/tins/deep_const_get.rb +0 -64
- data/lib/tins/timed_cache.rb +0 -51
- data/lib/tins/uniq_by.rb +0 -23
- data/lib/tins/xt/count_by.rb +0 -7
- data/lib/tins/xt/deep_const_get.rb +0 -7
- data/lib/tins/xt/uniq_by.rb +0 -25
- data/tests/count_by_test.rb +0 -17
- data/tests/deep_const_get_test.rb +0 -37
- data/tests/uniq_by_test.rb +0 -31
- /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
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
95
|
-
scan(NUMBER) or return
|
96
|
-
@number *= BigDecimal(self[1])
|
97
|
-
end
|
146
|
+
private :unit_re
|
98
147
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
134
|
-
|
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
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
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
|
-
|
159
|
-
|
160
|
-
|
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
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
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
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
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,
|
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
|
-
|
55
|
-
|
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
|