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
@@ -1,19 +1,80 @@
|
|
1
1
|
module Tins
|
2
|
+
# A module that provides partial application functionality.
|
3
|
+
#
|
4
|
+
# This module is designed to be included in classes that respond to `call` and have
|
5
|
+
# an `arity` method. It's commonly used with Proc and Method objects, but can be
|
6
|
+
# included in any class that implements the required interface.
|
7
|
+
#
|
8
|
+
# Partial application allows you to create new callables by fixing some arguments
|
9
|
+
# of an existing callable, resulting in a callable with fewer parameters.
|
10
|
+
#
|
11
|
+
# @example Using partial application with Proc
|
12
|
+
# add = proc { |x, y| x + y }
|
13
|
+
# add_five = add.partial(5) # Fixes first argument to 5
|
14
|
+
# result = add_five.call(3) # Returns 8 (5 + 3)
|
15
|
+
#
|
16
|
+
# @example Using partial application with Method
|
17
|
+
# class Calculator
|
18
|
+
# def calculate(x, y, z)
|
19
|
+
# x + y * z
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# calc = Calculator.new
|
24
|
+
# method_obj = calc.method(:calculate)
|
25
|
+
# partial_calc = method_obj.partial(1, 2) # Fixes first two arguments
|
26
|
+
# result = partial_calc.call(3) # Returns 7 (1 + 2 * 3)
|
2
27
|
module PartialApplication
|
3
|
-
#
|
4
|
-
#
|
28
|
+
# Callback invoked when this module is included in a class. Overrides the
|
29
|
+
# `arity` method to support custom arity values for partial applications.
|
30
|
+
#
|
31
|
+
# This is particularly useful for Proc and Method objects where the arity
|
32
|
+
# needs to be adjusted after partial application.
|
33
|
+
#
|
34
|
+
# @param modul [Module] The module that included this module
|
5
35
|
def self.included(modul)
|
6
36
|
modul.module_eval do
|
7
37
|
old_arity = instance_method(:arity)
|
8
38
|
define_method(:arity) do
|
9
|
-
defined?(@__arity__)
|
39
|
+
if defined?(@__arity__)
|
40
|
+
@__arity__
|
41
|
+
else
|
42
|
+
old_arity.bind(self).call
|
43
|
+
end
|
10
44
|
end
|
11
45
|
end
|
12
46
|
super
|
13
47
|
end
|
14
48
|
|
15
|
-
#
|
16
|
-
#
|
49
|
+
# Creates a partial application of the current object.
|
50
|
+
#
|
51
|
+
# If no arguments are provided, returns a duplicate of the current object.
|
52
|
+
# If more arguments are provided than the object's arity, raises an
|
53
|
+
# ArgumentError. Otherwise, creates a new lambda that combines the provided
|
54
|
+
# arguments with additional arguments when called.
|
55
|
+
#
|
56
|
+
# This method is particularly useful for creating curried functions or
|
57
|
+
# partially applied methods where some parameters are pre-filled.
|
58
|
+
#
|
59
|
+
# @param args [Array] Arguments to partially apply to the callable
|
60
|
+
# @return [Proc] A partial application of this object with adjusted arity
|
61
|
+
# @raise [ArgumentError] If too many arguments are provided for the arity
|
62
|
+
# @example
|
63
|
+
# add = proc { |x, y| x + y }
|
64
|
+
# add_five = add.partial(5)
|
65
|
+
# add_five.call(3) # => 8
|
66
|
+
#
|
67
|
+
# @example With Method objects
|
68
|
+
# class MathOps
|
69
|
+
# def multiply(a, b, c)
|
70
|
+
# a * b * c
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# ops = MathOps.new
|
75
|
+
# method_obj = ops.method(:multiply)
|
76
|
+
# partial_mult = method_obj.partial(2, 3)
|
77
|
+
# partial_mult.call(4) # => 24 (2 * 3 * 4)
|
17
78
|
def partial(*args)
|
18
79
|
if args.empty?
|
19
80
|
dup
|
@@ -27,5 +88,3 @@ module Tins
|
|
27
88
|
end
|
28
89
|
end
|
29
90
|
end
|
30
|
-
|
31
|
-
require 'tins/alias'
|
data/lib/tins/proc_compose.rb
CHANGED
@@ -1,15 +1,72 @@
|
|
1
1
|
module Tins
|
2
|
+
# A module that provides function composition functionality for Proc objects.
|
3
|
+
#
|
4
|
+
# This module enables the composition of two functions (procs) such that the
|
5
|
+
# result is a new proc that applies the second function to the input, then
|
6
|
+
# applies the first function to the result. This follows the mathematical
|
7
|
+
# concept of function composition: (f ∘ g)(x) = f(g(x))
|
8
|
+
#
|
9
|
+
# @example Basic composition
|
10
|
+
# add_one = proc { |x| x + 1 }
|
11
|
+
# multiply_by_two = proc { |x| x * 2 }
|
12
|
+
#
|
13
|
+
# composed = multiply_by_two.compose(add_one)
|
14
|
+
# composed.call(5) # => 12 (2 * (5 + 1))
|
15
|
+
#
|
16
|
+
# @example Using the alias operator
|
17
|
+
# add_one = proc { |x| x + 1 }
|
18
|
+
# multiply_by_two = proc { |x| x * 2 }
|
19
|
+
#
|
20
|
+
# composed = multiply_by_two * add_one
|
21
|
+
# composed.call(5) # => 12
|
2
22
|
module ProcCompose
|
23
|
+
# Composes this proc with another callable, creating a new proc that
|
24
|
+
# applies the other callable first, then applies this proc to its result.
|
25
|
+
#
|
26
|
+
# The composition follows the mathematical convention:
|
27
|
+
#
|
28
|
+
# (self ∘ other)(args) = self(other(args))
|
29
|
+
#
|
30
|
+
# @param other [Proc, Method, Object] A callable object that responds to `call`
|
31
|
+
# or can be converted to a proc via `to_proc`
|
32
|
+
# @return [Proc] A new proc representing the composition
|
33
|
+
# @example
|
34
|
+
# square = proc { |x| x * x }
|
35
|
+
# add_one = proc { |x| x + 1 }
|
36
|
+
#
|
37
|
+
# composed = square.compose(add_one)
|
38
|
+
# composed.call(3) # => 16 ((3 + 1)²)
|
39
|
+
#
|
40
|
+
# @example With Method objects
|
41
|
+
# class MathOps
|
42
|
+
# def square(x)
|
43
|
+
# x * x
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# math = MathOps.new
|
48
|
+
# add_one = proc { |x| x + 1 }
|
49
|
+
# composed = math.method(:square).compose(add_one)
|
50
|
+
# composed.call(3) # => 16 ((3 + 1)²)
|
3
51
|
def compose(other)
|
4
|
-
|
52
|
+
block = -> *args {
|
5
53
|
if other.respond_to?(:call)
|
6
54
|
call(*other.call(*args))
|
7
55
|
else
|
8
56
|
call(*other.to_proc.call(*args))
|
9
57
|
end
|
58
|
+
}
|
59
|
+
if self.class.respond_to?(:new)
|
60
|
+
self.class.new(&block)
|
61
|
+
else
|
62
|
+
Proc.new(&block)
|
10
63
|
end
|
11
64
|
end
|
12
65
|
|
66
|
+
# Alias for {compose} method, enabling the use of the * operator for
|
67
|
+
# composition.
|
68
|
+
#
|
69
|
+
# @see compose
|
13
70
|
alias * compose
|
14
71
|
end
|
15
72
|
end
|
data/lib/tins/proc_prelude.rb
CHANGED
@@ -1,71 +1,158 @@
|
|
1
1
|
require 'tins/memoize'
|
2
2
|
|
3
3
|
module Tins
|
4
|
+
# Tins::ProcPrelude provides a set of utility methods for creating and composing
|
5
|
+
# Proc objects with common functional programming patterns.
|
6
|
+
#
|
7
|
+
# This module contains various helper methods that return lambda functions,
|
8
|
+
# making it easier to build complex processing pipelines and functional
|
9
|
+
# transformations. These are particularly useful in functional programming
|
10
|
+
# contexts or when working with higher-order functions.
|
11
|
+
#
|
12
|
+
# The methods are typically accessed through the singleton interface:
|
13
|
+
# Proc.array.(1, 2, 3) # => [1, 2, 3]
|
14
|
+
#
|
15
|
+
# @example Basic usage with map_apply in reduce context
|
16
|
+
# # Create a proc that applies a method to each element and accumulates results
|
17
|
+
# proc = Proc.map_apply(:upcase) { |s, upcased| s << upcased }
|
18
|
+
#
|
19
|
+
# # Use it with Array#reduce
|
20
|
+
# ['hello', 'world'].reduce([], &proc)
|
21
|
+
# # => ['HELLO', 'WORLD']
|
22
|
+
#
|
23
|
+
# @example Using array to convert arguments to list
|
24
|
+
# proc = Proc.array
|
25
|
+
# proc.(1, 2, 3)
|
26
|
+
# # => [1, 2, 3]
|
4
27
|
module ProcPrelude
|
28
|
+
# Create a proc that applies the given block to a list of arguments.
|
29
|
+
#
|
30
|
+
# @yield [list] The block to apply to the arguments
|
31
|
+
# @return [Proc] A proc that takes arguments and calls the block with them unpacked
|
5
32
|
def apply(&my_proc)
|
6
33
|
my_proc or raise ArgumentError, 'a block argument is required'
|
7
|
-
lambda { |list| my_proc.
|
34
|
+
lambda { |list| my_proc.(*list) }
|
8
35
|
end
|
9
36
|
|
37
|
+
# Create a proc that applies a method to an object and then applies the block.
|
38
|
+
#
|
39
|
+
# @param my_method [Symbol] The method name to call on each element
|
40
|
+
# @param args [Array] Additional arguments to pass to the method
|
41
|
+
# @yield [x, y] The block to apply after calling the method
|
42
|
+
# @return [Proc] A proc that takes two arguments and applies the method + block
|
43
|
+
# @raise [ArgumentError] if no block is provided
|
10
44
|
def map_apply(my_method, *args, &my_proc)
|
11
45
|
my_proc or raise ArgumentError, 'a block argument is required'
|
12
|
-
lambda { |x, y| my_proc.
|
46
|
+
lambda { |x, y| my_proc.(x, y.__send__(my_method, *args)) }
|
13
47
|
end
|
14
48
|
|
49
|
+
# Create a proc that evaluates a block in the context of an object.
|
50
|
+
#
|
51
|
+
# @param obj [Object] The object to evaluate the block against
|
52
|
+
# @yield [obj] The block to evaluate in the object's context
|
53
|
+
# @return [Proc] A proc that takes an object and evaluates the block
|
15
54
|
def call(obj, &my_proc)
|
16
55
|
my_proc or raise ArgumentError, 'a block argument is required'
|
17
56
|
obj.instance_eval(&my_proc)
|
18
57
|
end
|
19
58
|
|
59
|
+
# Create a proc that converts all arguments to a list array.
|
60
|
+
#
|
61
|
+
# @return [Proc] A proc that takes any number of arguments and returns them as a list
|
20
62
|
def array
|
21
63
|
lambda { |*list| list }
|
22
64
|
end
|
23
|
-
|
65
|
+
memoize function: :array, freeze: true
|
24
66
|
|
67
|
+
# Create a proc that returns the first element (the head) of a list.
|
68
|
+
#
|
69
|
+
# @return [Proc] A proc that takes a list and returns its first element
|
25
70
|
def first
|
26
71
|
lambda { |*list| list.first }
|
27
72
|
end
|
28
|
-
|
73
|
+
memoize function: :first, freeze: true
|
29
74
|
|
30
75
|
alias head first
|
31
76
|
|
77
|
+
# Create a proc that returns the second element of a list.
|
78
|
+
#
|
79
|
+
# @return [Proc] A proc that takes a list and returns its second element
|
32
80
|
def second
|
33
81
|
lambda { |*list| list[1] }
|
34
82
|
end
|
35
|
-
|
83
|
+
memoize function: :second, freeze: true
|
36
84
|
|
85
|
+
# Create a proc that returns all elements except the first (the tail) from
|
86
|
+
# a list.
|
87
|
+
#
|
88
|
+
# @return [Proc] A proc that takes a list and returns all but the first element
|
37
89
|
def tail
|
38
90
|
lambda { |*list| list[1..-1] }
|
39
91
|
end
|
40
|
-
|
92
|
+
memoize function: :tail, freeze: true
|
41
93
|
|
94
|
+
# Create a proc that returns the last element of a list.
|
95
|
+
#
|
96
|
+
# @return [Proc] A proc that takes a list and returns its last element
|
42
97
|
def last
|
43
98
|
lambda { |*list| list.last }
|
44
99
|
end
|
45
|
-
|
100
|
+
memoize function: :last, freeze: true
|
46
101
|
|
102
|
+
# Create a proc that rotates a list by n positions.
|
103
|
+
#
|
104
|
+
# @param n [Integer] Number of positions to rotate (default: 1)
|
105
|
+
# @return [Proc] A proc that takes a list and returns it rotated
|
47
106
|
def rotate(n = 1)
|
48
107
|
lambda { |*list| list.rotate(n) }
|
49
108
|
end
|
50
109
|
|
51
110
|
alias swap rotate
|
52
111
|
|
112
|
+
# Create a proc that returns its argument unchanged.
|
113
|
+
#
|
114
|
+
# @return [Proc] A proc that takes an element and returns it unchanged
|
53
115
|
def id1
|
54
116
|
lambda { |obj| obj }
|
55
117
|
end
|
56
|
-
|
118
|
+
memoize function: :id1, freeze: true
|
57
119
|
|
120
|
+
# Create a proc that returns a constant value.
|
121
|
+
#
|
122
|
+
# @param konst [Object] The constant value to return (optional)
|
123
|
+
# @yield [konst] Block to compute the constant value if not provided
|
124
|
+
# @return [Proc] A proc that always returns the same value
|
58
125
|
def const(konst = nil, &my_proc)
|
59
|
-
konst ||= my_proc.
|
126
|
+
konst ||= my_proc.()
|
60
127
|
lambda { |*_| konst }
|
61
128
|
end
|
62
129
|
|
130
|
+
# Create a proc that returns the nth element of a list.
|
131
|
+
#
|
132
|
+
# @param n [Integer] The index of the element to return
|
133
|
+
# @return [Proc] A proc that takes a list and returns element at index n
|
63
134
|
def nth(n)
|
64
135
|
lambda { |*list| list[n] }
|
65
136
|
end
|
66
137
|
|
138
|
+
# Create a proc that calls a method on self with given arguments.
|
139
|
+
#
|
140
|
+
# This method uses binding introspection to dynamically determine which
|
141
|
+
# method to call, making it useful for creating flexible function
|
142
|
+
# references.
|
143
|
+
#
|
144
|
+
# @yield [] The block that should return a method name (symbol)
|
145
|
+
# @return [Proc] A proc that takes arguments and calls the specified method
|
146
|
+
# @example Dynamic method invocation
|
147
|
+
# def square(x)
|
148
|
+
# x ** 2
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# proc = Proc.from { :square }
|
152
|
+
# [1, 2, 3].map(&proc)
|
153
|
+
# # => [1, 4, 9]
|
67
154
|
def from(&block)
|
68
|
-
my_method, binding = block.
|
155
|
+
my_method, binding = block.(), block.binding
|
69
156
|
my_self = eval 'self', binding
|
70
157
|
lambda { |*list| my_self.__send__(my_method, *list) }
|
71
158
|
end
|
data/lib/tins/range_plus.rb
CHANGED
@@ -1,9 +1,37 @@
|
|
1
1
|
module Tins
|
2
|
+
# RangePlus extends the Range class with additional functionality.
|
3
|
+
#
|
4
|
+
# This module adds a `+` method to Range objects that concatenates the
|
5
|
+
# elements of two ranges into a single array.
|
6
|
+
#
|
7
|
+
# @example Basic usage
|
8
|
+
# range1 = (1..3)
|
9
|
+
# range2 = (4..6)
|
10
|
+
# result = range1 + range2
|
11
|
+
# # => [1, 2, 3, 4, 5, 6]
|
12
|
+
#
|
13
|
+
# @example With different range types
|
14
|
+
# range1 = ('a'..'c')
|
15
|
+
# range2 = ('d'..'f')
|
16
|
+
# result = range1 + range2
|
17
|
+
# # => ["a", "b", "c", "d", "e", "f"]
|
18
|
+
#
|
19
|
+
# @note This implementation converts both ranges to arrays and concatenates
|
20
|
+
# them. This means it will materialize the entire range into memory, which
|
21
|
+
# could be problematic for very large ranges.
|
2
22
|
module RangePlus
|
23
|
+
# Concatenates two ranges by converting them to arrays and merging them.
|
24
|
+
#
|
25
|
+
# This method allows you to combine the elements of two ranges into a
|
26
|
+
# single array. Both ranges are converted to arrays using their `to_a`
|
27
|
+
# method, then concatenated together.
|
28
|
+
#
|
29
|
+
# @param other [Range] Another range to concatenate with this range
|
30
|
+
# @return [Array] A new array containing all elements from both ranges
|
31
|
+
# @example
|
32
|
+
# (1..3) + (4..6) # => [1, 2, 3, 4, 5, 6]
|
3
33
|
def +(other)
|
4
34
|
to_a + other.to_a
|
5
35
|
end
|
6
36
|
end
|
7
37
|
end
|
8
|
-
|
9
|
-
require 'tins/alias'
|
data/lib/tins/require_maybe.rb
CHANGED
@@ -1,5 +1,41 @@
|
|
1
1
|
module Tins
|
2
|
+
# A module that provides a safe require mechanism with optional error handling.
|
3
|
+
#
|
4
|
+
# This module is included in Object, making the `require_maybe` method globally
|
5
|
+
# available throughout your Ruby application. It enables conditional loading of
|
6
|
+
# libraries where the failure to load a dependency is not necessarily an error
|
7
|
+
# condition, making it ideal for optional dependencies and feature detection.
|
8
|
+
#
|
9
|
+
# @example Basic usage anywhere in your codebase
|
10
|
+
# # Available globally:
|
11
|
+
# require_maybe 'foo' # Returns true/false
|
12
|
+
#
|
13
|
+
# @example Providing fallback behavior
|
14
|
+
# class MyParser
|
15
|
+
# def parse_data(data)
|
16
|
+
# if require_maybe('foo')
|
17
|
+
# Foo::Parser.parse(data)
|
18
|
+
# else
|
19
|
+
# Bar.parse(data) # Fallback
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @example With error handling block
|
25
|
+
# require_maybe 'some_gem' do |error|
|
26
|
+
# puts "Optional gem 'some_gem' not available: #{error.message}"
|
27
|
+
# end
|
2
28
|
module RequireMaybe
|
29
|
+
# Attempts to require a library, gracefully handling LoadError exceptions.
|
30
|
+
#
|
31
|
+
# This method is globally available because the module is included in
|
32
|
+
# Object. It's particularly useful for optional dependencies and feature
|
33
|
+
# detection.
|
34
|
+
#
|
35
|
+
# @param library [String] The name of the library to require
|
36
|
+
# @yield [LoadError] Optional block to handle the LoadError
|
37
|
+
# @yieldparam error [LoadError] The rescued LoadError exception
|
38
|
+
# @return [Boolean] Returns true if library was loaded successfully, false otherwise
|
3
39
|
def require_maybe(library)
|
4
40
|
require library
|
5
41
|
rescue LoadError => e
|
data/lib/tins/responding.rb
CHANGED
@@ -1,5 +1,44 @@
|
|
1
1
|
module Tins
|
2
|
+
# A module that provides a convenient way to check if objects respond to
|
3
|
+
# specific methods.
|
4
|
+
#
|
5
|
+
# The `responding?` method returns an object that can be used with the
|
6
|
+
# case/when construct to test if an object responds to certain methods. This is
|
7
|
+
# particularly useful for duck typing and implementing polymorphic behavior.
|
8
|
+
#
|
9
|
+
# @example Check duck types
|
10
|
+
# if responding?(:eof?, :gets) === obj
|
11
|
+
# until obj.eof?
|
12
|
+
# puts obj.gets
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# @example Easy interface checks
|
17
|
+
# case obj
|
18
|
+
# when responding?(:length, :keys)
|
19
|
+
# puts " → Hash-like object (has size and keys)"
|
20
|
+
# when responding?(:size, :begin)
|
21
|
+
# puts " → Range-like object (has size and begin)"
|
22
|
+
# when responding?(:length, :push)
|
23
|
+
# puts " → Array-like object (has size and push)"
|
24
|
+
# when responding?(:length, :upcase)
|
25
|
+
# puts " → String-like object (has length and upcase)"
|
26
|
+
# when responding?(:read, :write)
|
27
|
+
# puts " → IO-like object (has read and write)"
|
28
|
+
# else
|
29
|
+
# puts " → Unknown interface"
|
30
|
+
# end
|
2
31
|
module Responding
|
32
|
+
# Returns a special object that can be used to test if objects respond to
|
33
|
+
# specific methods. The returned object implements `===` to perform the
|
34
|
+
# check.
|
35
|
+
#
|
36
|
+
# This is particularly useful for duck typing and case/when constructs
|
37
|
+
# where you want to match against objects based on their interface rather
|
38
|
+
# than their class.
|
39
|
+
#
|
40
|
+
# @param method_names [Array<Symbol>] One or more method names to check for
|
41
|
+
# @return [Object] A special object that responds to `===` for method checking
|
3
42
|
def responding?(*method_names)
|
4
43
|
Class.new do
|
5
44
|
define_method(:to_s) do
|
data/lib/tins/secure_write.rb
CHANGED
@@ -1,6 +1,28 @@
|
|
1
1
|
module Tins
|
2
|
+
# A module that provides secure file writing capabilities.
|
3
|
+
#
|
4
|
+
# This module extends objects with a method to write data to files in a way
|
5
|
+
# that ensures atomicity and prevents partial writes, making it safer for
|
6
|
+
# concurrent access.
|
2
7
|
module SecureWrite
|
3
|
-
# Write to a file atomically
|
8
|
+
# Write to a file atomically by creating a temporary file and renaming it.
|
9
|
+
# This ensures that readers will either see the complete old content or
|
10
|
+
# the complete new content, never partial writes.
|
11
|
+
#
|
12
|
+
# @param filename [String, #to_s] The target filename
|
13
|
+
# @param content [String, nil] The content to write (optional)
|
14
|
+
# @param mode [String] File open mode (default: 'w')
|
15
|
+
# @yield [File] If a block is given, yields the temporary file handle
|
16
|
+
# @return [Integer] The number of bytes written
|
17
|
+
# @raise [ArgumentError] If neither content nor block is provided
|
18
|
+
#
|
19
|
+
# @example With content
|
20
|
+
# File.secure_write('config.json', '{"timeout": 30}')
|
21
|
+
#
|
22
|
+
# @example With block
|
23
|
+
# File.secure_write('output.txt') do |f|
|
24
|
+
# f.write("Hello, World!")
|
25
|
+
# end
|
4
26
|
def secure_write(filename, content = nil, mode = 'w')
|
5
27
|
temp = File.new(filename.to_s + ".tmp.#$$.#{Time.now.to_f}", mode)
|
6
28
|
if content.nil? and block_given?
|
@@ -17,11 +39,9 @@ module Tins
|
|
17
39
|
size
|
18
40
|
ensure
|
19
41
|
if temp
|
20
|
-
|
42
|
+
temp.closed? or temp.close
|
21
43
|
File.file?(temp.path) and File.unlink temp.path
|
22
44
|
end
|
23
45
|
end
|
24
46
|
end
|
25
47
|
end
|
26
|
-
|
27
|
-
require 'tins/alias'
|
data/lib/tins/sexy_singleton.rb
CHANGED
@@ -2,65 +2,62 @@ require 'tins/string_version'
|
|
2
2
|
require 'singleton'
|
3
3
|
|
4
4
|
module Tins
|
5
|
-
|
5
|
+
# Enhanced singleton implementation that forwards method calls to the
|
6
|
+
# instance.
|
7
|
+
#
|
8
|
+
# This module provides a "sexy" singleton implementation that extends the
|
9
|
+
# standard Ruby singleton pattern by making the singleton class itself
|
10
|
+
# respond to methods defined on the instance. This allows for more intuitive
|
11
|
+
# usage where you can call singleton methods directly on the class without
|
12
|
+
# needing to access the instance explicitly.
|
13
|
+
#
|
14
|
+
# @example Basic usage
|
15
|
+
# class MySingleton
|
16
|
+
# include Tins::SexySingleton
|
17
|
+
#
|
18
|
+
# def hello
|
19
|
+
# "Hello World"
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# # You can now call methods directly on the class
|
24
|
+
# MySingleton.hello # => "Hello World"
|
6
25
|
SexySingleton = Singleton.dup
|
7
26
|
|
8
|
-
|
9
|
-
module SingletonClassMethods
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
class << SexySingleton
|
27
|
+
SexySingleton.singleton_class.class_eval do
|
14
28
|
alias __old_singleton_included__ included
|
15
29
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
30
|
+
# Extends the standard singleton inclusion to forward method calls from the
|
31
|
+
# singleton class to the instance.
|
32
|
+
#
|
33
|
+
# This method is automatically called when including {SexySingleton} in a
|
34
|
+
# class. It sets up the singleton class to delegate method calls to the
|
35
|
+
# instance, making it possible to call singleton methods directly on the
|
36
|
+
# class.
|
37
|
+
#
|
38
|
+
# @param klass [Class] The class that includes this module
|
39
|
+
def included(klass)
|
40
|
+
__old_singleton_included__(klass)
|
41
|
+
klass.singleton_class.class_eval do
|
42
|
+
if Object.method_defined?(:respond_to_missing?)
|
43
|
+
def respond_to_missing?(name, *args, **kwargs)
|
44
|
+
instance.respond_to?(name) || super
|
28
45
|
end
|
29
|
-
|
30
|
-
def
|
31
|
-
|
32
|
-
instance.__send__(name, *args, &block)
|
33
|
-
else
|
34
|
-
super
|
35
|
-
end
|
46
|
+
else
|
47
|
+
def respond_to?(name, *args, **kwargs)
|
48
|
+
instance.respond_to?(name) || super
|
36
49
|
end
|
37
50
|
end
|
38
|
-
super
|
39
|
-
end
|
40
|
-
else
|
41
|
-
def included(klass)
|
42
|
-
__old_singleton_included__(klass)
|
43
|
-
(class << klass; self; end).class_eval do
|
44
|
-
if Object.method_defined?(:respond_to_missing?)
|
45
|
-
def respond_to_missing?(name, *args, **kwargs)
|
46
|
-
instance.respond_to?(name) || super
|
47
|
-
end
|
48
|
-
else
|
49
|
-
def respond_to?(name, *args, **kwargs)
|
50
|
-
instance.respond_to?(name) || super
|
51
|
-
end
|
52
|
-
end
|
53
51
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
52
|
+
def method_missing(name, *args, **kwargs, &block)
|
53
|
+
if instance.respond_to?(name)
|
54
|
+
instance.__send__(name, *args, **kwargs, &block)
|
55
|
+
else
|
56
|
+
super
|
60
57
|
end
|
61
58
|
end
|
62
|
-
super
|
63
59
|
end
|
60
|
+
super
|
64
61
|
end
|
65
62
|
end
|
66
63
|
end
|
@@ -1,7 +1,40 @@
|
|
1
1
|
require 'tins/concern'
|
2
2
|
|
3
3
|
module Tins
|
4
|
+
# Tins::StringByteOrderMark provides methods for detecting and identifying
|
5
|
+
# byte order marks (BOMs) in strings.
|
6
|
+
#
|
7
|
+
# This module contains the `bom_encoding` method which analyzes the beginning
|
8
|
+
# of a string to determine its encoding based on the presence of BOM bytes.
|
9
|
+
# This is particularly useful when working with text files that may have
|
10
|
+
# different encodings and BOM markers.
|
11
|
+
#
|
12
|
+
# @example Detecting UTF-8 BOM
|
13
|
+
# "\xef\xbb\xbfhello".bom_encoding
|
14
|
+
# # => Encoding::UTF_8
|
15
|
+
#
|
16
|
+
# @example Detecting UTF-16BE BOM
|
17
|
+
# "\xfe\xffhello".bom_encoding
|
18
|
+
# # => Encoding::UTF_16BE
|
19
|
+
#
|
20
|
+
# @example No BOM detected
|
21
|
+
# "hello".bom_encoding
|
22
|
+
# # => nil
|
4
23
|
module StringByteOrderMark
|
24
|
+
# Detect the encoding of a string based on its byte order mark (BOM).
|
25
|
+
#
|
26
|
+
# This method examines the first 4 bytes of the string to identify
|
27
|
+
# common Unicode BOM patterns and returns the corresponding Encoding object.
|
28
|
+
# The method handles various Unicode encodings that use BOM markers:
|
29
|
+
# - UTF-8 (EF BB BF)
|
30
|
+
# - UTF-16BE (FE FF)
|
31
|
+
# - UTF-16LE (FF FE)
|
32
|
+
# - UTF-32BE (00 00 FF FE)
|
33
|
+
# - UTF-32LE (FF FE 00 00)
|
34
|
+
# - UTF-7 (2B 2F 76 followed by specific bytes)
|
35
|
+
# - GB18030 (84 31 95 33)
|
36
|
+
#
|
37
|
+
# @return [Encoding, nil] The detected encoding if a BOM is found, otherwise nil
|
5
38
|
def bom_encoding
|
6
39
|
prefix = self[0, 4].force_encoding(Encoding::ASCII_8BIT)
|
7
40
|
case prefix
|
@@ -16,5 +49,3 @@ module Tins
|
|
16
49
|
end
|
17
50
|
end
|
18
51
|
end
|
19
|
-
|
20
|
-
require 'tins/alias'
|