tins 1.38.0 → 1.51.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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +159 -0
  3. data/README.md +158 -6
  4. data/Rakefile +28 -18
  5. data/examples/let.rb +8 -40
  6. data/examples/mail.rb +0 -1
  7. data/examples/turing.rb +3 -1
  8. data/lib/tins/alias.rb +1 -0
  9. data/lib/tins/annotate.rb +37 -27
  10. data/lib/tins/ask_and_send.rb +41 -0
  11. data/lib/tins/attempt.rb +39 -0
  12. data/lib/tins/bijection.rb +34 -0
  13. data/lib/tins/case_predicate.rb +21 -0
  14. data/lib/tins/complete.rb +16 -0
  15. data/lib/tins/concern.rb +64 -0
  16. data/lib/tins/date_dummy.rb +36 -4
  17. data/lib/tins/date_time_dummy.rb +34 -2
  18. data/lib/tins/deep_dup.rb +9 -2
  19. data/lib/tins/deprecate.rb +12 -0
  20. data/lib/tins/dslkit.rb +563 -59
  21. data/lib/tins/duration.rb +120 -5
  22. data/lib/tins/expose.rb +54 -5
  23. data/lib/tins/extract_last_argument_options.rb +9 -0
  24. data/lib/tins/file_binary.rb +104 -21
  25. data/lib/tins/find.rb +114 -11
  26. data/lib/tins/generator.rb +10 -2
  27. data/lib/tins/go.rb +78 -14
  28. data/lib/tins/hash_bfs.rb +4 -2
  29. data/lib/tins/hash_symbolize_keys_recursive.rb +62 -4
  30. data/lib/tins/hash_union.rb +47 -2
  31. data/lib/tins/if_predicate.rb +31 -0
  32. data/lib/tins/implement.rb +50 -0
  33. data/lib/tins/limited.rb +63 -12
  34. data/lib/tins/lines_file.rb +81 -2
  35. data/lib/tins/lru_cache.rb +54 -17
  36. data/lib/tins/memoize.rb +86 -58
  37. data/lib/tins/method_description.rb +87 -4
  38. data/lib/tins/minimize.rb +39 -11
  39. data/lib/tins/module_group.rb +27 -2
  40. data/lib/tins/named_set.rb +20 -0
  41. data/lib/tins/null.rb +86 -15
  42. data/lib/tins/once.rb +61 -4
  43. data/lib/tins/p.rb +44 -8
  44. data/lib/tins/partial_application.rb +66 -7
  45. data/lib/tins/proc_compose.rb +58 -1
  46. data/lib/tins/proc_prelude.rb +97 -10
  47. data/lib/tins/range_plus.rb +30 -2
  48. data/lib/tins/require_maybe.rb +36 -0
  49. data/lib/tins/responding.rb +39 -0
  50. data/lib/tins/secure_write.rb +24 -4
  51. data/lib/tins/sexy_singleton.rb +45 -48
  52. data/lib/tins/string_byte_order_mark.rb +33 -2
  53. data/lib/tins/string_camelize.rb +31 -2
  54. data/lib/tins/string_named_placeholders.rb +70 -0
  55. data/lib/tins/string_underscore.rb +33 -2
  56. data/lib/tins/string_version.rb +190 -12
  57. data/lib/tins/subhash.rb +35 -10
  58. data/lib/tins/temp_io.rb +7 -0
  59. data/lib/tins/temp_io_enum.rb +19 -0
  60. data/lib/tins/terminal.rb +31 -9
  61. data/lib/tins/thread_local.rb +67 -5
  62. data/lib/tins/time_dummy.rb +46 -21
  63. data/lib/tins/to.rb +15 -0
  64. data/lib/tins/to_proc.rb +17 -4
  65. data/lib/tins/token.rb +74 -2
  66. data/lib/tins/unit.rb +304 -149
  67. data/lib/tins/version.rb +1 -1
  68. data/lib/tins/write.rb +14 -3
  69. data/lib/tins/xt/blank.rb +81 -2
  70. data/lib/tins/xt/concern.rb +51 -0
  71. data/lib/tins/xt/deep_dup.rb +4 -2
  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/secure_write.rb +0 -4
  79. data/lib/tins/xt/string.rb +1 -0
  80. data/lib/tins/xt/string_camelize.rb +4 -2
  81. data/lib/tins/xt/string_named_placeholders.rb +7 -0
  82. data/lib/tins/xt/string_underscore.rb +4 -2
  83. data/lib/tins/xt/subhash.rb +11 -0
  84. data/lib/tins/xt/time_freezer.rb +43 -6
  85. data/lib/tins/xt/write.rb +0 -4
  86. data/lib/tins/xt.rb +1 -3
  87. data/lib/tins.rb +17 -3
  88. data/tests/dslkit_test.rb +15 -0
  89. data/tests/duration_test.rb +4 -0
  90. data/tests/from_module_test.rb +30 -2
  91. data/tests/go_test.rb +4 -2
  92. data/tests/implement_test.rb +6 -8
  93. data/tests/limited_test.rb +1 -1
  94. data/tests/lines_file_test.rb +2 -0
  95. data/tests/lru_cache_test.rb +12 -0
  96. data/tests/method_description_test.rb +14 -20
  97. data/tests/partial_application_test.rb +4 -0
  98. data/tests/proc_prelude_test.rb +1 -1
  99. data/tests/scope_test.rb +1 -1
  100. data/tests/string_named_placeholders.rb +109 -0
  101. data/tests/string_version_test.rb +15 -0
  102. data/tests/test_helper.rb +4 -5
  103. data/tests/to_test.rb +6 -6
  104. data/tests/token_test.rb +32 -1
  105. data/tests/unit_test.rb +18 -0
  106. data/tins.gemspec +13 -11
  107. metadata +49 -33
  108. data/lib/tins/count_by.rb +0 -21
  109. data/lib/tins/deep_const_get.rb +0 -64
  110. data/lib/tins/timed_cache.rb +0 -51
  111. data/lib/tins/uniq_by.rb +0 -23
  112. data/lib/tins/xt/count_by.rb +0 -7
  113. data/lib/tins/xt/deep_const_get.rb +0 -7
  114. data/lib/tins/xt/uniq_by.rb +0 -25
  115. data/tests/count_by_test.rb +0 -17
  116. data/tests/deep_const_get_test.rb +0 -37
  117. data/tests/uniq_by_test.rb +0 -31
  118. /data/{COPYING → LICENSE} +0 -0
@@ -1,5 +1,39 @@
1
1
  module Tins
2
+ # Provides thread-local storage capabilities for classes and modules.
3
+ # Thread-local variables are scoped to individual threads, allowing each
4
+ # thread to maintain its own copy of the variable value.
5
+ #
6
+ # @example Basic usage with a class
7
+ # class MyClass
8
+ # extend Tins::ThreadLocal
9
+ #
10
+ # thread_local :counter, 0
11
+ # thread_local :name, "default"
12
+ # end
13
+ #
14
+ # # Each thread gets its own copy of the variables
15
+ # t1 = Thread.new { puts MyClass.new.counter } # => 0
16
+ # t2 = Thread.new { puts MyClass.new.counter } # => 0
17
+ # t1.join; t2.join
18
+ #
19
+ # @example Usage with default blocks
20
+ # class Config
21
+ # extend Tins::ThreadLocal
22
+ #
23
+ # thread_local :database_url do
24
+ # ENV['DATABASE_URL'] || 'sqlite3://default.db'
25
+ # end
26
+ # end
27
+ #
28
+ # @example Instance-level thread local variables
29
+ # class MyClass
30
+ # include Tins::ThreadLocal
31
+ #
32
+ # instance_thread_local :user_id, 0
33
+ # end
2
34
  module ThreadLocal
35
+ # Cleanup lambda that removes thread-local data when objects are garbage
36
+ # collected
3
37
  @@cleanup = lambda do |my_object_id|
4
38
  my_id = "__thread_local_#{my_object_id}__"
5
39
  for t in Thread.list
@@ -7,13 +41,28 @@ module Tins
7
41
  end
8
42
  end
9
43
 
10
- # Define a thread local variable named _name_ in this module/class. If the
11
- # value _value_ is given, it is used to initialize the variable.
44
+ # Define a thread local variable named +name+ in this module/class.
45
+ # If the value +value+ is given, it is used to initialize the variable.
46
+ #
47
+ # @param name [Symbol, String] The name of the thread-local variable
48
+ # @param default_value [Object] Optional default value for the variable
49
+ # @yield [void] Optional block that returns the default value
50
+ # @return [self]
51
+ # @raise [TypeError] If receiver is not a Module
52
+ # @raise [ArgumentError] If both default_value and default block are provided
53
+ #
54
+ # @example With static default value
55
+ # thread_local :counter, 0
56
+ #
57
+ # @example With dynamic default value via block
58
+ # thread_local :timestamp do
59
+ # Time.now
60
+ # end
12
61
  def thread_local(name, default_value = nil, &default)
13
62
  is_a?(Module) or raise TypeError, "receiver has to be a Module"
14
63
 
15
64
  default_value && default and raise ArgumentError,
16
- "require either default_falue or default block"
65
+ "require either default_value or default block"
17
66
 
18
67
  if default_value
19
68
  default = -> * { default_value }
@@ -40,13 +89,26 @@ module Tins
40
89
  self
41
90
  end
42
91
 
43
- # Define a thread local variable for the current instance with name _name_.
44
- # If the value _value_ is given, it is used to initialize the variable.
92
+ # Define a thread local variable for the current instance with name +name+.
93
+ # If the value +value+ is given, it is used to initialize the variable.
94
+ #
95
+ # @param name [Symbol, String] The name of the thread-local variable
96
+ # @param default_value [Object] Optional default value for the variable
97
+ # @yield [void] Optional block that returns the default value
98
+ # @return [self]
99
+ #
100
+ # @example Basic usage
101
+ # class MyClass
102
+ # include Tins::ThreadLocal
103
+ #
104
+ # instance_thread_local :user_id, 0
105
+ # end
45
106
  def instance_thread_local(name, default_value = nil, &default)
46
107
  class << self
47
108
  extend Tins::ThreadLocal
48
109
  self
49
110
  end.thread_local name, default_value, &default
111
+
50
112
  self
51
113
  end
52
114
  end
@@ -2,7 +2,22 @@ require 'tins/string_version'
2
2
  require 'time'
3
3
 
4
4
  module Tins
5
+ # A module that provides time dummy functionality for testing and development
6
+ # purposes.
7
+ #
8
+ # This module allows setting a fake current time that can be used in tests or
9
+ # development environments where you want to control the time returned by
10
+ # Time.now.
5
11
  module TimeDummy
12
+ # The included method is a hook that gets called when this module is
13
+ # included in another class or module.
14
+ #
15
+ # It sets up time freezing functionality by extending the including
16
+ # class/module with special time handling methods. The method modifies the
17
+ # including class/module's singleton class to provide dummy time
18
+ # capabilities.
19
+ #
20
+ # @param modul [Object] the class or module that includes this module
6
21
  def self.included(modul)
7
22
  class << modul
8
23
  alias really_new new
@@ -11,6 +26,12 @@ module Tins
11
26
  remove_method :now rescue nil
12
27
  remove_method :new rescue nil
13
28
 
29
+ # Sets the dummy time value for time freezing functionality.
30
+ #
31
+ # This method allows setting a specific time value that will be used
32
+ # as the frozen time when time freezing is enabled.
33
+ #
34
+ # @param value [Time, String] the time value to set as dummy
14
35
  def dummy=(value)
15
36
  if value.respond_to?(:to_str)
16
37
  value = Time.parse(value.to_str)
@@ -20,6 +41,12 @@ module Tins
20
41
  @dummy = value
21
42
  end
22
43
 
44
+ # The dummy method manages a dummy value for testing purposes.
45
+ #
46
+ # @param value [Object] the dummy value to set, or nil to get the current value
47
+ # @return [Object] the current dummy value when value is nil
48
+ # @yield [] executes the block with the dummy value set
49
+ # @return [Object] the return value of the block if a block is given
23
50
  def dummy(value = nil)
24
51
  if value.nil?
25
52
  if defined?(@dummy)
@@ -36,28 +63,28 @@ module Tins
36
63
  end
37
64
  end
38
65
 
39
- if Tins::StringVersion.compare(RUBY_VERSION, :>=, "3.0")
40
- def new(*a, **kw)
41
- if dummy
42
- dummy.dup
43
- elsif caller.first =~ /`now`/
44
- really_now(**kw)
45
- else
46
- really_new(*a, **kw)
47
- end
48
- end
49
- else
50
- def new(*a)
51
- if dummy
52
- dummy.dup
53
- elsif caller.first =~ /`now`/
54
- really_now
55
- else
56
- really_new(*a)
57
- end
66
+ # The new method creates a new time instance, either by duplicating
67
+ # the dummy time or calling the real creation method.
68
+ #
69
+ # @param a [ Array ] the arguments to pass to the real creation
70
+ # method
71
+ # @param kw [ Hash ] the keyword arguments to pass to the real
72
+ # creation method
73
+ #
74
+ # @return [ Time ] the newly created time instance
75
+ def new(*a, **kw)
76
+ if dummy
77
+ dummy.dup
78
+ elsif caller.first =~ /`now`/
79
+ really_now(**kw)
80
+ else
81
+ really_new(*a, **kw)
58
82
  end
59
83
  end
60
84
 
85
+ # The now method returns a new instance of the current class.
86
+ #
87
+ # @return [Object] a new instance of the class this method is called on
61
88
  def now
62
89
  new
63
90
  end
@@ -66,5 +93,3 @@ module Tins
66
93
  end
67
94
  end
68
95
  end
69
-
70
- require 'tins/alias'
data/lib/tins/to.rb CHANGED
@@ -1,5 +1,20 @@
1
1
  module Tins
2
+ # Provides a simple way to remove common leading whitespace from multi-line
3
+ # strings, mostly for to(<<-EOT), if require "tins/xt/to". Today you would
4
+ # probably use <<~EOT in this case.
5
+ #
6
+ # Example usage:
7
+ # doc = to(<<-EOT)
8
+ # hello
9
+ # world
10
+ # end
11
+ # EOT
12
+ # # => "hello\n world\nend"
2
13
  module To
14
+ # Remove common leading whitespace from multi-line strings
15
+ #
16
+ # @param string [String] The multi-line string to deindent
17
+ # @return [String] String with common leading whitespace removed
3
18
  def to(string)
4
19
  shift_width = (string[/\A\s*/]).size
5
20
  string.gsub(/^[^\S\n]{0,#{shift_width}}/, '')
data/lib/tins/to_proc.rb CHANGED
@@ -1,12 +1,25 @@
1
1
  module Tins
2
+ # This module provides a way to convert symbols into procs using the
3
+ # __send__ method for dynamic method invocation. It was necessary before
4
+ # Ruby 1.9's built-in Symbol#to_proc functionality. You still can use
5
+ # it for strings, though.
6
+ #
7
+ # Example Usage:
8
+ # class String
9
+ # include Tins::ToProc
10
+ # end
11
+ #
12
+ # ["hello", "world"].map(&'upcase') # => ["HELLO", "WORLD"]
2
13
  module ToProc
3
- # :nocov:
14
+ # Converts a Symbol into a Proc that sends the symbol's name to its
15
+ # argument
16
+ #
17
+ # @return [ Proc ] a Proc that when called will send the symbol's name as a
18
+ # message to obj with args
4
19
  def to_proc
5
20
  lambda do |obj, *args|
6
- obj.__send__(self, *args[0..-1])
21
+ obj.__send__(self, *args)
7
22
  end
8
23
  end
9
24
  end
10
25
  end
11
-
12
- require 'tins/alias'
data/lib/tins/token.rb CHANGED
@@ -1,26 +1,78 @@
1
1
  require 'securerandom'
2
2
 
3
3
  module Tins
4
+ # A secure token generator that creates cryptographically safe strings
5
+ # using customizable alphabets and random number generators.
6
+ #
7
+ # @example Basic usage
8
+ # token = Tins::Token.new
9
+ # # => "aB3xK9mN2pQ8rS4tU6vW7yZ1"
10
+ #
11
+ # @example Custom length
12
+ # token = Tins::Token.new(length: 20)
13
+ # # => "xYz123AbC456DeF789GhI0jK"
14
+ #
15
+ # @example Custom alphabet
16
+ # token = Tins::Token.new(
17
+ # alphabet: Tins::Token::BASE16_UPPERCASE_ALPHABET,
18
+ # length: 16
19
+ # )
20
+ # # => "A1B2C3D4E5F6G7H8"
21
+ #
22
+ # @example Custom random number generator
23
+ # token = Tins::Token.new(random: Random.new(42))
24
+ # # => "xYz123AbC456DeF789GhI0jK"
4
25
  class Token < String
26
+ # Default alphabet for token generation
5
27
  DEFAULT_ALPHABET =
6
28
  "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".freeze
7
29
 
30
+ # Base64 alphabet
8
31
  BASE64_ALPHABET =
9
32
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".freeze
10
33
 
34
+ # URL-safe base64 alphabet
11
35
  BASE64_URL_FILENAME_SAFE_ALPHABET =
12
36
  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_".freeze
13
37
 
38
+ # Base32 alphabet
14
39
  BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567".freeze
15
40
 
41
+ # Extended hex base32 alphabet
16
42
  BASE32_EXTENDED_HEX_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUV".freeze
17
43
 
44
+ # Base16 uppercase alphabet
18
45
  BASE16_UPPERCASE_ALPHABET = "0123456789ABCDEF".freeze
19
46
 
47
+ # Base16 lowercase alphabet
20
48
  BASE16_LOWERCASE_ALPHABET = "0123456789abcdef".freeze
21
49
 
50
+ # Base16 default alphabet (uppercase)
22
51
  BASE16_ALPHABET = BASE16_UPPERCASE_ALPHABET
23
52
 
53
+ # Initializes a new Token instance with specified bit length, length,
54
+ # alphabet, and random number generator.
55
+ #
56
+ # @example Basic initialization
57
+ # token = Tins::Token.new
58
+ # # => "aB3xK9mN2pQ8rS4tU6vW7yZ1"
59
+ #
60
+ # @example Custom parameters
61
+ # token = Tins::Token.new(
62
+ # bits: 256,
63
+ # alphabet: Tins::Token::BASE32_ALPHABET
64
+ # )
65
+ #
66
+ # @param bits [Integer] the number of bits for the token (default: 128)
67
+ # @param length [Integer] the length of the token (optional, mutually exclusive with bits)
68
+ # @param alphabet [String] the alphabet to use for token generation (default: DEFAULT_ALPHABET)
69
+ # @param random [Object] the random number generator to use (default: SecureRandom)
70
+ #
71
+ # @return [Tins::Token] a new Token instance
72
+ #
73
+ # @raise [ArgumentError] if alphabet has fewer than 2 symbols
74
+ # @raise [ArgumentError] if bits is not positive when length is not specified
75
+ # @raise [ArgumentError] if length is not positive when specified
24
76
  def initialize(bits: 128, length: nil, alphabet: DEFAULT_ALPHABET, random: SecureRandom)
25
77
  alphabet.size > 1 or raise ArgumentError, 'need at least 2 symbols in alphabet'
26
78
  if length
@@ -29,12 +81,32 @@ module Tins
29
81
  bits > 0 or raise ArgumentError, 'bits has to be positive'
30
82
  length = (Math.log(1 << bits) / Math.log(alphabet.size)).ceil
31
83
  end
32
- self.bits = (Math.log(alphabet.size ** length) / Math.log(2)).floor
33
- token = ''
84
+ self.bits = self.class.analyze(alphabet:, length:)
85
+ token = +''
34
86
  length.times { token << alphabet[random.random_number(alphabet.size)] }
35
87
  super token
36
88
  end
37
89
 
90
+ # The bit length of the token.
91
+ #
92
+ # @return [Integer] the number of bits of entropy in the token
38
93
  attr_accessor :bits
94
+
95
+ # The analyze method calculates the bit length of a token based on its
96
+ # alphabet and length.
97
+ #
98
+ # @param alphabet [String] the alphabet used for token generation, defaults
99
+ # to Tins::Token::DEFAULT_ALPHABET
100
+ # @param token [String, nil] the token string to analyze, optional
101
+ # @param length [Integer, nil] the length of the token, optional
102
+ #
103
+ # @return [Integer] the calculated bit length of the token
104
+ #
105
+ # @raise [ArgumentError] if neither token nor length is provided, or if both are provided
106
+ def self.analyze(alphabet: Tins::Token::DEFAULT_ALPHABET, token: nil, length: nil)
107
+ token.nil? ^ length.nil? or raise ArgumentError, 'either token or length is required'
108
+ length ||= token.length
109
+ (Math.log(alphabet.size ** length) / Math.log(2)).floor
110
+ end
39
111
  end
40
112
  end