tins 1.32.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 (150) hide show
  1. checksums.yaml +4 -4
  2. data/.contexts/code_comment.rb +23 -0
  3. data/.contexts/full.rb +31 -0
  4. data/.contexts/lib.rb +24 -0
  5. data/.contexts/yard.md +92 -0
  6. data/.github/workflows/codeql-analysis.yml +72 -0
  7. data/CHANGES.md +194 -0
  8. data/README.md +161 -90
  9. data/Rakefile +23 -19
  10. data/examples/let.rb +8 -40
  11. data/examples/mail.rb +0 -1
  12. data/examples/ones_difference.stm +0 -1
  13. data/examples/turing.rb +3 -1
  14. data/lib/tins/alias.rb +1 -0
  15. data/lib/tins/annotate.rb +37 -27
  16. data/lib/tins/ask_and_send.rb +41 -0
  17. data/lib/tins/attempt.rb +39 -0
  18. data/lib/tins/bijection.rb +34 -0
  19. data/lib/tins/case_predicate.rb +21 -0
  20. data/lib/tins/complete.rb +16 -0
  21. data/lib/tins/concern.rb +100 -0
  22. data/lib/tins/date_dummy.rb +36 -4
  23. data/lib/tins/date_time_dummy.rb +34 -2
  24. data/lib/tins/deep_dup.rb +9 -2
  25. data/lib/tins/deprecate.rb +27 -0
  26. data/lib/tins/dslkit.rb +563 -59
  27. data/lib/tins/duration.rb +160 -3
  28. data/lib/tins/expose.rb +54 -5
  29. data/lib/tins/extract_last_argument_options.rb +9 -0
  30. data/lib/tins/file_binary.rb +108 -25
  31. data/lib/tins/find.rb +114 -11
  32. data/lib/tins/generator.rb +10 -2
  33. data/lib/tins/go.rb +81 -4
  34. data/lib/tins/hash_bfs.rb +69 -0
  35. data/lib/tins/hash_symbolize_keys_recursive.rb +62 -4
  36. data/lib/tins/hash_union.rb +47 -2
  37. data/lib/tins/if_predicate.rb +31 -0
  38. data/lib/tins/implement.rb +50 -0
  39. data/lib/tins/limited.rb +105 -29
  40. data/lib/tins/lines_file.rb +81 -2
  41. data/lib/tins/lru_cache.rb +54 -17
  42. data/lib/tins/memoize.rb +86 -58
  43. data/lib/tins/method_description.rb +87 -4
  44. data/lib/tins/minimize.rb +39 -11
  45. data/lib/tins/module_group.rb +27 -2
  46. data/lib/tins/named_set.rb +20 -0
  47. data/lib/tins/null.rb +86 -15
  48. data/lib/tins/once.rb +61 -4
  49. data/lib/tins/p.rb +44 -8
  50. data/lib/tins/partial_application.rb +66 -7
  51. data/lib/tins/proc_compose.rb +58 -1
  52. data/lib/tins/proc_prelude.rb +97 -10
  53. data/lib/tins/range_plus.rb +30 -2
  54. data/lib/tins/require_maybe.rb +36 -0
  55. data/lib/tins/responding.rb +39 -0
  56. data/lib/tins/secure_write.rb +25 -5
  57. data/lib/tins/sexy_singleton.rb +46 -48
  58. data/lib/tins/string_byte_order_mark.rb +33 -2
  59. data/lib/tins/string_camelize.rb +31 -2
  60. data/lib/tins/string_named_placeholders.rb +70 -0
  61. data/lib/tins/string_underscore.rb +33 -2
  62. data/lib/tins/string_version.rb +183 -10
  63. data/lib/tins/subhash.rb +35 -10
  64. data/lib/tins/temp_io.rb +7 -0
  65. data/lib/tins/temp_io_enum.rb +19 -0
  66. data/lib/tins/terminal.rb +34 -12
  67. data/lib/tins/thread_local.rb +69 -11
  68. data/lib/tins/time_dummy.rb +47 -21
  69. data/lib/tins/to.rb +15 -0
  70. data/lib/tins/to_proc.rb +17 -4
  71. data/lib/tins/token.rb +61 -2
  72. data/lib/tins/unit.rb +288 -149
  73. data/lib/tins/version.rb +1 -1
  74. data/lib/tins/write.rb +14 -3
  75. data/lib/tins/xt/blank.rb +81 -2
  76. data/lib/tins/xt/concern.rb +51 -0
  77. data/lib/tins/xt/deep_dup.rb +4 -2
  78. data/lib/tins/xt/deprecate.rb +5 -0
  79. data/lib/tins/xt/full.rb +56 -11
  80. data/lib/tins/xt/hash_bfs.rb +7 -0
  81. data/lib/tins/xt/irb.rb +46 -2
  82. data/lib/tins/xt/method_description.rb +0 -12
  83. data/lib/tins/xt/minimize.rb +7 -0
  84. data/lib/tins/xt/named.rb +71 -16
  85. data/lib/tins/xt/proc_compose.rb +4 -0
  86. data/lib/tins/xt/secure_write.rb +0 -4
  87. data/lib/tins/xt/string.rb +1 -0
  88. data/lib/tins/xt/string_camelize.rb +4 -2
  89. data/lib/tins/xt/string_named_placeholders.rb +7 -0
  90. data/lib/tins/xt/string_underscore.rb +4 -2
  91. data/lib/tins/xt/subhash.rb +11 -0
  92. data/lib/tins/xt/time_freezer.rb +43 -6
  93. data/lib/tins/xt/write.rb +0 -4
  94. data/lib/tins/xt.rb +3 -3
  95. data/lib/tins.rb +19 -3
  96. data/tests/annotate_test.rb +0 -1
  97. data/tests/bijection_test.rb +0 -1
  98. data/tests/concern_test.rb +63 -4
  99. data/tests/date_dummy_test.rb +0 -1
  100. data/tests/date_time_dummy_test.rb +0 -1
  101. data/tests/delegate_test.rb +0 -1
  102. data/tests/deprecate_test.rb +41 -0
  103. data/tests/dslkit_test.rb +15 -1
  104. data/tests/duration_test.rb +23 -2
  105. data/tests/dynamic_scope_test.rb +0 -1
  106. data/tests/extract_last_argument_options_test.rb +0 -1
  107. data/tests/find_test.rb +0 -1
  108. data/tests/from_module_test.rb +30 -3
  109. data/tests/generator_test.rb +0 -1
  110. data/tests/go_test.rb +0 -1
  111. data/tests/hash_bfs_test.rb +34 -0
  112. data/tests/hash_symbolize_keys_recursive_test.rb +0 -1
  113. data/tests/implement_test.rb +6 -9
  114. data/tests/limited_test.rb +12 -12
  115. data/tests/lines_file_test.rb +2 -1
  116. data/tests/lru_cache_test.rb +12 -1
  117. data/tests/memoize_test.rb +0 -1
  118. data/tests/method_description_test.rb +14 -20
  119. data/tests/minimize_test.rb +0 -1
  120. data/tests/module_group_test.rb +0 -1
  121. data/tests/named_set_test.rb +0 -1
  122. data/tests/null_test.rb +0 -1
  123. data/tests/partial_application_test.rb +4 -0
  124. data/tests/proc_prelude_test.rb +1 -1
  125. data/tests/require_maybe_test.rb +0 -1
  126. data/tests/scope_test.rb +1 -2
  127. data/tests/secure_write_test.rb +6 -1
  128. data/tests/sexy_singleton_test.rb +1 -1
  129. data/tests/string_named_placeholders.rb +109 -0
  130. data/tests/string_version_test.rb +3 -1
  131. data/tests/subhash_test.rb +0 -1
  132. data/tests/test_helper.rb +4 -7
  133. data/tests/time_dummy_test.rb +0 -1
  134. data/tests/time_freezer_test.rb +1 -1
  135. data/tests/to_test.rb +6 -6
  136. data/tests/token_test.rb +0 -1
  137. data/tests/unit_test.rb +0 -1
  138. data/tins.gemspec +18 -30
  139. metadata +55 -38
  140. data/lib/tins/count_by.rb +0 -8
  141. data/lib/tins/deep_const_get.rb +0 -50
  142. data/lib/tins/timed_cache.rb +0 -51
  143. data/lib/tins/uniq_by.rb +0 -10
  144. data/lib/tins/xt/count_by.rb +0 -11
  145. data/lib/tins/xt/deep_const_get.rb +0 -7
  146. data/lib/tins/xt/uniq_by.rb +0 -15
  147. data/tests/count_by_test.rb +0 -17
  148. data/tests/deep_const_get_test.rb +0 -37
  149. data/tests/uniq_by_test.rb +0 -31
  150. /data/{COPYING → LICENSE} +0 -0
@@ -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,8 +1,30 @@
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
- temp = File.new(filename + ".tmp.#$$.#{Time.now.to_f}", mode)
27
+ temp = File.new(filename.to_s + ".tmp.#$$.#{Time.now.to_f}", mode)
6
28
  if content.nil? and block_given?
7
29
  yield temp
8
30
  elsif !content.nil?
@@ -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'
@@ -1,65 +1,63 @@
1
+ require 'tins/string_version'
1
2
  require 'singleton'
2
3
 
3
4
  module Tins
4
-
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"
5
25
  SexySingleton = Singleton.dup
6
26
 
7
- module SexySingleton
8
- module SingletonClassMethods
9
- end
10
- end
11
-
12
- class << SexySingleton
27
+ SexySingleton.singleton_class.class_eval do
13
28
  alias __old_singleton_included__ included
14
29
 
15
- if RUBY_VERSION < "2.7"
16
- def included(klass)
17
- __old_singleton_included__(klass)
18
- (class << klass; self; end).class_eval do
19
- if Object.method_defined?(:respond_to_missing?)
20
- def respond_to_missing?(name, *args)
21
- instance.respond_to?(name) || super
22
- end
23
- else
24
- def respond_to?(name, *args)
25
- instance.respond_to?(name) || super
26
- 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
27
45
  end
28
-
29
- def method_missing(name, *args, &block)
30
- if instance.respond_to?(name)
31
- instance.__send__(name, *args, &block)
32
- else
33
- super
34
- end
46
+ else
47
+ def respond_to?(name, *args, **kwargs)
48
+ instance.respond_to?(name) || super
35
49
  end
36
50
  end
37
- super
38
- end
39
- else
40
- def included(klass)
41
- __old_singleton_included__(klass)
42
- (class << klass; self; end).class_eval do
43
- if Object.method_defined?(:respond_to_missing?)
44
- def respond_to_missing?(name, *args, **kwargs)
45
- instance.respond_to?(name) || super
46
- end
47
- else
48
- def respond_to?(name, *args, **kwargs)
49
- instance.respond_to?(name) || super
50
- end
51
- end
52
51
 
53
- def method_missing(name, *args, **kwargs, &block)
54
- if instance.respond_to?(name)
55
- instance.__send__(name, *args, **kwargs, &block)
56
- else
57
- super
58
- 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
59
57
  end
60
58
  end
61
- super
62
59
  end
60
+ super
63
61
  end
64
62
  end
65
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'
@@ -1,5 +1,36 @@
1
1
  module Tins
2
+ # Tins::StringCamelize provides methods for converting snake_case strings
3
+ # to camelCase or PascalCase format.
4
+ #
5
+ # This module contains the `camelize` method which is commonly used in Ruby
6
+ # applications, particularly in Rails-style naming conventions where developers
7
+ # need to convert between different naming styles.
8
+ #
9
+ # @example Converting snake_case to PascalCase (default)
10
+ # "snake_case_string".camelize
11
+ # # => "SnakeCaseString"
12
+ #
13
+ # @example Converting snake_case to camelCase
14
+ # "snake_case_string".camelize(:lower)
15
+ # # => "snakeCaseString"
16
+ #
17
+ # @example Handling nested module paths
18
+ # "my/module/class_name".camelize
19
+ # # => "My::Module::ClassName"
2
20
  module StringCamelize
21
+ # Convert a snake_case string to camelCase or PascalCase format.
22
+ #
23
+ # This method handles various naming conventions:
24
+ # - Converts snake_case to PascalCase (default) or camelCase
25
+ # - Handles nested module paths with '/' separators
26
+ # - Supports different first letter cases
27
+ #
28
+ # @param first_letter [Symbol, Boolean] Controls capitalization of first letter
29
+ # @option first_letter :upper (default) Convert first letter to uppercase (PascalCase)
30
+ # @option first_letter :lower Convert first letter to lowercase (camelCase)
31
+ # @option first_letter true Same as :upper
32
+ # @option first_letter false Same as :lower
33
+ # @return [String] A new string in camelCase or PascalCase format
3
34
  def camelize(first_letter = :upper)
4
35
  case first_letter
5
36
  when :upper, true
@@ -12,5 +43,3 @@ module Tins
12
43
  alias camelcase camelize
13
44
  end
14
45
  end
15
-
16
- require 'tins/alias'
@@ -0,0 +1,70 @@
1
+ module Tins
2
+ # A module that provides methods for working with named placeholders in strings.
3
+ #
4
+ # This module adds functionality to extract named placeholders from strings
5
+ # and assign values to them, making it easier to work with template-style
6
+ # strings that contain named substitution points.
7
+ #
8
+ # @example Extracting named placeholders from a string
9
+ # "Hello %{name}, you have %{count} messages".named_placeholders
10
+ # # => [:name, :count]
11
+ #
12
+ # @example Assigning values to named placeholders
13
+ # template = "Welcome %{user}, your balance is %{amount}"
14
+ # template.named_placeholders_assign(user: "Alice", amount: "$100")
15
+ # # => {:user=>"Alice", :amount=>"$100"}
16
+ module StringNamedPlaceholders
17
+ # Returns an array of symbols representing the named placeholders found in
18
+ # the string.
19
+ #
20
+ # This method scans the string for patterns matching named placeholders in
21
+ # the format %{name} and extracts the placeholder names, returning them as
22
+ # symbols in an array.
23
+ #
24
+ # @return [Array<Symbol>] An array of unique symbol representations of the
25
+ # named placeholders found in the string.
26
+ def named_placeholders
27
+ scan(/%\{([^}]+)\}/).inject([], &:concat).uniq.map(&:to_sym)
28
+ end
29
+
30
+ # Assign values to named placeholders from a hash, using a default value
31
+ # for unspecified placeholders.
32
+ #
33
+ # This method takes a hash of placeholder values and assigns them to the
34
+ # named placeholders found in the string. If a placeholder is not present
35
+ # in the input hash, the provided default value is used instead. The
36
+ # default can be a static value or a proc that receives the placeholder
37
+ # symbol as an argument.
38
+ #
39
+ # @param hash [Hash] A hash mapping placeholder names to their corresponding values.
40
+ # @param default [Object, Proc] The default value to use for placeholders not present in the hash.
41
+ # If a proc is provided, it will be called with the placeholder symbol.
42
+ #
43
+ # @return [Hash] A new hash containing the assigned values for each named
44
+ # placeholder.
45
+ def named_placeholders_assign(hash, default: nil)
46
+ hash = hash.transform_keys(&:to_sym)
47
+ named_placeholders.each_with_object({}) do |placeholder, h|
48
+ h[placeholder] = hash[placeholder] ||
49
+ (default.is_a?(Proc) ? default.(placeholder) : default)
50
+ end
51
+ end
52
+
53
+ # Interpolate named placeholders in the string with values from a hash.
54
+ #
55
+ # This method takes a hash of placeholder values and substitutes the named
56
+ # placeholders found in the string with their corresponding values.
57
+ # Placeholders that are not present in the input hash will be replaced with
58
+ # the provided default value.
59
+ #
60
+ # @param hash [Hash] A hash mapping placeholder names to their corresponding values
61
+ # @param default [Object, Proc] The default value to use for placeholders not present in the hash
62
+ # If a proc is provided, it will be called with the placeholder symbol
63
+ #
64
+ # @return [String] A new string with named placeholders replaced by their values
65
+ def named_placeholders_interpolate(hash, default: nil)
66
+ values = named_placeholders_assign(hash, default:)
67
+ self % values
68
+ end
69
+ end
70
+ end
@@ -1,5 +1,38 @@
1
1
  module Tins
2
+ # Tins::StringUnderscore provides methods for converting camelCase and
3
+ # PascalCase strings to snake_case format.
4
+ #
5
+ # This module contains the `underscore` method which is commonly used in Ruby
6
+ # applications, particularly in Rails-style naming conventions where developers
7
+ # need to convert between different naming styles.
8
+ #
9
+ # @example Converting camelCase to snake_case
10
+ # "camelCaseString".underscore
11
+ # # => "camel_case_string"
12
+ #
13
+ # @example Converting PascalCase to snake_case
14
+ # "PascalCaseString".underscore
15
+ # # => "pascal_case_string"
16
+ #
17
+ # @example Handling nested modules
18
+ # "My::Module::ClassName".underscore
19
+ # # => "my/module/class_name"
20
+ #
21
+ # @example Mixed case with dashes
22
+ # "camel-case-string".underscore
23
+ # # => "camel_case_string"
2
24
  module StringUnderscore
25
+ # Convert a camelCase or PascalCase string to snake_case format.
26
+ #
27
+ # This method handles various naming conventions:
28
+ # - Converts camelCase to snake_case (e.g., "camelCase" → "camel_case")
29
+ # - Converts PascalCase to snake_case (e.g., "PascalCase" → "pascal_case")
30
+ # - Handles consecutive uppercase letters (e.g., "XMLParser" → "xml_parser")
31
+ # - Replaces dashes with underscores
32
+ # - Converts to lowercase
33
+ # - Handles nested module paths with '::' separators
34
+ #
35
+ # @return [String] A new string in snake_case format
3
36
  def underscore
4
37
  word = dup
5
38
  word.gsub!(/::/, '/')
@@ -11,5 +44,3 @@ module Tins
11
44
  end
12
45
  end
13
46
  end
14
-
15
- require 'tins/alias'