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.
- checksums.yaml +4 -4
- data/.contexts/code_comment.rb +23 -0
- data/.contexts/full.rb +31 -0
- data/.contexts/lib.rb +24 -0
- data/.contexts/yard.md +92 -0
- data/.github/workflows/codeql-analysis.yml +72 -0
- data/CHANGES.md +194 -0
- data/README.md +161 -90
- data/Rakefile +23 -19
- data/examples/let.rb +8 -40
- data/examples/mail.rb +0 -1
- data/examples/ones_difference.stm +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 +100 -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 +27 -0
- data/lib/tins/dslkit.rb +563 -59
- data/lib/tins/duration.rb +160 -3
- data/lib/tins/expose.rb +54 -5
- data/lib/tins/extract_last_argument_options.rb +9 -0
- data/lib/tins/file_binary.rb +108 -25
- 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 +69 -0
- 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 +105 -29
- 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 +25 -5
- data/lib/tins/sexy_singleton.rb +46 -48
- data/lib/tins/string_byte_order_mark.rb +33 -2
- data/lib/tins/string_camelize.rb +31 -2
- data/lib/tins/string_named_placeholders.rb +70 -0
- data/lib/tins/string_underscore.rb +33 -2
- data/lib/tins/string_version.rb +183 -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 +34 -12
- data/lib/tins/thread_local.rb +69 -11
- data/lib/tins/time_dummy.rb +47 -21
- data/lib/tins/to.rb +15 -0
- data/lib/tins/to_proc.rb +17 -4
- data/lib/tins/token.rb +61 -2
- 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/deep_dup.rb +4 -2
- data/lib/tins/xt/deprecate.rb +5 -0
- data/lib/tins/xt/full.rb +56 -11
- data/lib/tins/xt/hash_bfs.rb +7 -0
- 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/secure_write.rb +0 -4
- data/lib/tins/xt/string.rb +1 -0
- data/lib/tins/xt/string_camelize.rb +4 -2
- data/lib/tins/xt/string_named_placeholders.rb +7 -0
- data/lib/tins/xt/string_underscore.rb +4 -2
- data/lib/tins/xt/subhash.rb +11 -0
- data/lib/tins/xt/time_freezer.rb +43 -6
- data/lib/tins/xt/write.rb +0 -4
- data/lib/tins/xt.rb +3 -3
- data/lib/tins.rb +19 -3
- data/tests/annotate_test.rb +0 -1
- data/tests/bijection_test.rb +0 -1
- data/tests/concern_test.rb +63 -4
- data/tests/date_dummy_test.rb +0 -1
- data/tests/date_time_dummy_test.rb +0 -1
- data/tests/delegate_test.rb +0 -1
- data/tests/deprecate_test.rb +41 -0
- data/tests/dslkit_test.rb +15 -1
- data/tests/duration_test.rb +23 -2
- data/tests/dynamic_scope_test.rb +0 -1
- data/tests/extract_last_argument_options_test.rb +0 -1
- data/tests/find_test.rb +0 -1
- data/tests/from_module_test.rb +30 -3
- data/tests/generator_test.rb +0 -1
- data/tests/go_test.rb +0 -1
- data/tests/hash_bfs_test.rb +34 -0
- data/tests/hash_symbolize_keys_recursive_test.rb +0 -1
- data/tests/implement_test.rb +6 -9
- data/tests/limited_test.rb +12 -12
- data/tests/lines_file_test.rb +2 -1
- data/tests/lru_cache_test.rb +12 -1
- data/tests/memoize_test.rb +0 -1
- data/tests/method_description_test.rb +14 -20
- data/tests/minimize_test.rb +0 -1
- data/tests/module_group_test.rb +0 -1
- data/tests/named_set_test.rb +0 -1
- data/tests/null_test.rb +0 -1
- data/tests/partial_application_test.rb +4 -0
- data/tests/proc_prelude_test.rb +1 -1
- data/tests/require_maybe_test.rb +0 -1
- data/tests/scope_test.rb +1 -2
- data/tests/secure_write_test.rb +6 -1
- data/tests/sexy_singleton_test.rb +1 -1
- data/tests/string_named_placeholders.rb +109 -0
- data/tests/string_version_test.rb +3 -1
- data/tests/subhash_test.rb +0 -1
- data/tests/test_helper.rb +4 -7
- data/tests/time_dummy_test.rb +0 -1
- data/tests/time_freezer_test.rb +1 -1
- data/tests/to_test.rb +6 -6
- data/tests/token_test.rb +0 -1
- data/tests/unit_test.rb +0 -1
- data/tins.gemspec +18 -30
- metadata +55 -38
- data/lib/tins/count_by.rb +0 -8
- data/lib/tins/deep_const_get.rb +0 -50
- data/lib/tins/timed_cache.rb +0 -51
- data/lib/tins/uniq_by.rb +0 -10
- data/lib/tins/xt/count_by.rb +0 -11
- data/lib/tins/xt/deep_const_get.rb +0 -7
- data/lib/tins/xt/uniq_by.rb +0 -15
- 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/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,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
|
-
|
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
@@ -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
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
30
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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'
|
data/lib/tins/string_camelize.rb
CHANGED
@@ -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'
|