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
@@ -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
- # If this module is included into a Proc (or similar object), it tampers
4
- # with its Proc#arity method.
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__) or old_arity.bind(self).call
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
- # Create a partial application of this Proc (or similar object) using
16
- # _args_ as the already applied arguments.
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'
@@ -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
- self.class.new do |*args|
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
@@ -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.call(*list) }
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.call(x, y.__send__(my_method, *args)) }
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
- memoize_function :array, freeze: true
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
- memoize_function :first, freeze: true
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
- memoize_function :second, freeze: true
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
- memoize_function :tail, freeze: true
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
- memoize_function :last, freeze: true
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
- memoize_function :id1, freeze: true
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.call
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.call, block.binding
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
@@ -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'
@@ -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
@@ -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
@@ -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
- !temp.closed? and temp.close
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'
@@ -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
- module SexySingleton
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
- if Tins::StringVersion.compare(RUBY_VERSION, :<, "2.7")
17
- def included(klass)
18
- __old_singleton_included__(klass)
19
- (class << klass; self; end).class_eval do
20
- if Object.method_defined?(:respond_to_missing?)
21
- def respond_to_missing?(name, *args)
22
- instance.respond_to?(name) || super
23
- end
24
- else
25
- def respond_to?(name, *args)
26
- instance.respond_to?(name) || super
27
- end
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 method_missing(name, *args, &block)
31
- if instance.respond_to?(name)
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
- def method_missing(name, *args, **kwargs, &block)
55
- if instance.respond_to?(name)
56
- instance.__send__(name, *args, **kwargs, &block)
57
- else
58
- super
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'