tins 1.31.1 → 1.54.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 (152) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +1 -0
  3. data/CHANGES.md +356 -0
  4. data/Gemfile +0 -5
  5. data/README.md +161 -90
  6. data/Rakefile +34 -17
  7. data/examples/let.rb +8 -40
  8. data/examples/mail.rb +2 -1
  9. data/examples/null_pattern.rb +2 -0
  10. data/examples/ones_difference.stm +0 -1
  11. data/examples/turing.rb +5 -1
  12. data/lib/tins/alias.rb +1 -0
  13. data/lib/tins/annotate.rb +37 -27
  14. data/lib/tins/ask_and_send.rb +41 -0
  15. data/lib/tins/attempt.rb +43 -2
  16. data/lib/tins/bijection.rb +34 -0
  17. data/lib/tins/case_predicate.rb +21 -0
  18. data/lib/tins/complete.rb +16 -0
  19. data/lib/tins/concern.rb +100 -0
  20. data/lib/tins/date_dummy.rb +36 -4
  21. data/lib/tins/date_time_dummy.rb +34 -2
  22. data/lib/tins/deep_dup.rb +9 -2
  23. data/lib/tins/deprecate.rb +27 -0
  24. data/lib/tins/dslkit.rb +628 -59
  25. data/lib/tins/duration.rb +160 -3
  26. data/lib/tins/expose.rb +54 -5
  27. data/lib/tins/extract_last_argument_options.rb +9 -0
  28. data/lib/tins/file_binary.rb +108 -25
  29. data/lib/tins/find.rb +114 -11
  30. data/lib/tins/generator.rb +10 -2
  31. data/lib/tins/go.rb +106 -18
  32. data/lib/tins/hash_bfs.rb +66 -0
  33. data/lib/tins/hash_dfs.rb +81 -0
  34. data/lib/tins/hash_symbolize_keys_recursive.rb +97 -8
  35. data/lib/tins/hash_union.rb +47 -2
  36. data/lib/tins/if_predicate.rb +31 -0
  37. data/lib/tins/implement.rb +50 -0
  38. data/lib/tins/limited.rb +105 -29
  39. data/lib/tins/lines_file.rb +81 -2
  40. data/lib/tins/lru_cache.rb +53 -24
  41. data/lib/tins/memoize.rb +86 -58
  42. data/lib/tins/method_description.rb +87 -4
  43. data/lib/tins/minimize.rb +39 -11
  44. data/lib/tins/module_group.rb +27 -2
  45. data/lib/tins/named_set.rb +20 -0
  46. data/lib/tins/null.rb +86 -15
  47. data/lib/tins/once.rb +61 -4
  48. data/lib/tins/p.rb +44 -8
  49. data/lib/tins/partial_application.rb +66 -7
  50. data/lib/tins/proc_compose.rb +58 -1
  51. data/lib/tins/proc_prelude.rb +97 -10
  52. data/lib/tins/range_plus.rb +30 -2
  53. data/lib/tins/require_maybe.rb +36 -0
  54. data/lib/tins/responding.rb +39 -0
  55. data/lib/tins/secure_write.rb +25 -5
  56. data/lib/tins/sexy_singleton.rb +46 -48
  57. data/lib/tins/string_byte_order_mark.rb +33 -2
  58. data/lib/tins/string_camelize.rb +31 -2
  59. data/lib/tins/string_named_placeholders.rb +70 -0
  60. data/lib/tins/string_underscore.rb +33 -2
  61. data/lib/tins/string_version.rb +198 -12
  62. data/lib/tins/subhash.rb +35 -10
  63. data/lib/tins/temp_io.rb +7 -0
  64. data/lib/tins/temp_io_enum.rb +19 -0
  65. data/lib/tins/terminal.rb +34 -12
  66. data/lib/tins/thread_local.rb +69 -11
  67. data/lib/tins/time_dummy.rb +47 -21
  68. data/lib/tins/to.rb +15 -0
  69. data/lib/tins/to_proc.rb +17 -4
  70. data/lib/tins/token.rb +87 -5
  71. data/lib/tins/unit.rb +304 -149
  72. data/lib/tins/version.rb +1 -1
  73. data/lib/tins/write.rb +14 -3
  74. data/lib/tins/xt/blank.rb +81 -2
  75. data/lib/tins/xt/concern.rb +51 -0
  76. data/lib/tins/xt/deep_dup.rb +4 -2
  77. data/lib/tins/xt/deprecate.rb +5 -0
  78. data/lib/tins/xt/full.rb +56 -11
  79. data/lib/tins/xt/hash_bfs.rb +7 -0
  80. data/lib/tins/xt/hash_dfs.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 +4 -3
  95. data/lib/tins.rb +20 -3
  96. data/tests/annotate_test.rb +0 -1
  97. data/tests/attempt_test.rb +12 -0
  98. data/tests/bijection_test.rb +0 -1
  99. data/tests/concern_test.rb +63 -4
  100. data/tests/date_dummy_test.rb +0 -1
  101. data/tests/date_time_dummy_test.rb +0 -1
  102. data/tests/delegate_test.rb +0 -1
  103. data/tests/deprecate_test.rb +41 -0
  104. data/tests/dslkit_test.rb +15 -1
  105. data/tests/duration_test.rb +23 -2
  106. data/tests/dynamic_scope_test.rb +0 -1
  107. data/tests/extract_last_argument_options_test.rb +0 -1
  108. data/tests/find_test.rb +0 -1
  109. data/tests/from_module_test.rb +30 -3
  110. data/tests/generator_test.rb +0 -1
  111. data/tests/go_test.rb +31 -3
  112. data/tests/hash_bfs_test.rb +34 -0
  113. data/tests/hash_dfs_test.rb +41 -0
  114. data/tests/hash_symbolize_keys_recursive_test.rb +57 -2
  115. data/tests/implement_test.rb +6 -9
  116. data/tests/limited_test.rb +12 -12
  117. data/tests/lines_file_test.rb +2 -1
  118. data/tests/lru_cache_test.rb +13 -1
  119. data/tests/memoize_test.rb +0 -1
  120. data/tests/method_description_test.rb +14 -20
  121. data/tests/minimize_test.rb +0 -1
  122. data/tests/module_group_test.rb +0 -1
  123. data/tests/named_set_test.rb +0 -1
  124. data/tests/null_test.rb +0 -1
  125. data/tests/partial_application_test.rb +4 -0
  126. data/tests/proc_prelude_test.rb +1 -1
  127. data/tests/require_maybe_test.rb +0 -1
  128. data/tests/scope_test.rb +1 -2
  129. data/tests/secure_write_test.rb +6 -1
  130. data/tests/sexy_singleton_test.rb +1 -1
  131. data/tests/string_named_placeholders.rb +109 -0
  132. data/tests/string_version_test.rb +16 -1
  133. data/tests/subhash_test.rb +0 -1
  134. data/tests/test_helper.rb +6 -6
  135. data/tests/time_dummy_test.rb +0 -1
  136. data/tests/time_freezer_test.rb +1 -1
  137. data/tests/to_test.rb +6 -6
  138. data/tests/token_test.rb +32 -2
  139. data/tests/unit_test.rb +18 -1
  140. data/tins.gemspec +19 -24
  141. metadata +113 -38
  142. data/lib/tins/count_by.rb +0 -8
  143. data/lib/tins/deep_const_get.rb +0 -42
  144. data/lib/tins/timed_cache.rb +0 -51
  145. data/lib/tins/uniq_by.rb +0 -10
  146. data/lib/tins/xt/count_by.rb +0 -11
  147. data/lib/tins/xt/deep_const_get.rb +0 -7
  148. data/lib/tins/xt/uniq_by.rb +0 -15
  149. data/tests/count_by_test.rb +0 -17
  150. data/tests/deep_const_get_test.rb +0 -37
  151. data/tests/uniq_by_test.rb +0 -31
  152. /data/{COPYING → LICENSE} +0 -0
data/Rakefile CHANGED
@@ -3,25 +3,42 @@
3
3
  require 'gem_hadar'
4
4
 
5
5
  GemHadar do
6
- name 'tins'
7
- author 'Florian Frank'
8
- email 'flori@ping.de'
9
- homepage "https://github.com/flori/#{name}"
10
- summary 'Useful stuff.'
11
- description 'All the stuff that isn\'t good/big enough for a real library.'
12
- test_dir 'tests'
6
+ name 'tins'
7
+ author 'Florian Frank'
8
+ email 'flori@ping.de'
9
+ homepage "https://github.com/flori/#{name}"
10
+ summary 'Useful stuff.'
11
+ description 'All the stuff that isn\'t good/big enough for a real library.'
12
+ test_dir 'tests'
13
13
  test_files.concat Dir["#{test_dir}/*_test.rb"]
14
- ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', '.rvmrc', 'coverage', '.rbx',
15
- '.AppleDouble', '.DS_Store', 'tags', '.bundle', '.byebug_history'
16
- package_ignore '.all_images.yml', '.tool-versions', '.gitignore', 'VERSION',
17
- 'TODO', *Dir.glob('.github/**/*', File::FNM_DOTMATCH)
14
+ doc_code_files files.grep(%r(\Alib/))
15
+ ignore '.*.sw[pon]', 'pkg', 'Gemfile.lock', '.rvmrc', 'coverage',
16
+ '.rbx', '.AppleDouble', '.DS_Store', 'tags', 'cscope.out', '.bundle',
17
+ '.yardoc', 'doc', 'TODO.md'
18
+ package_ignore '.all_images.yml', '.tool-versions', '.gitignore',
19
+ 'VERSION', '.utilsrc', '.github', '.contexts'
18
20
 
19
- readme 'README.md'
20
- licenses << 'MIT'
21
+ readme 'README.md'
22
+ licenses << 'MIT'
23
+ clean << 'coverage'
24
+
25
+ changelog do
26
+ filename 'CHANGES.md'
27
+ end
28
+
29
+ github_workflows(
30
+ 'static.yml' => {}
31
+ )
32
+
33
+ required_ruby_version '>= 3.1'
21
34
 
22
- required_ruby_version '>= 2.0'
23
- development_dependency 'utils'
24
- development_dependency 'test-unit', '~>3.1'
25
- development_dependency 'simplecov'
26
35
  dependency 'sync'
36
+ dependency 'bigdecimal'
37
+ dependency 'readline'
38
+ dependency 'mize', '~> 0.6'
39
+ development_dependency 'all_images'
40
+ development_dependency 'debug'
41
+ development_dependency 'simplecov'
42
+ development_dependency 'term-ansicolor'
43
+ development_dependency 'test-unit', '~> 3.7'
27
44
  end
data/examples/let.rb CHANGED
@@ -2,48 +2,16 @@
2
2
 
3
3
  require 'tins'
4
4
 
5
- LetScope = Tins::BlankSlate.with :instance_eval, :to_s, :inspect, :extend
6
- class LetScope
7
- include Tins::MethodMissingDelegator::DelegatorModule
8
- include Tins::BlockSelf
9
-
10
- def initialize(my_self, bindings = {}, outer_scope = nil)
11
- super(my_self)
12
- @outer_scope = outer_scope
13
- @bindings = bindings
14
- extend Tins::Eigenclass
15
- eigenclass_eval { extend Tins::Constant }
16
- each_binding do |name, value|
17
- eigenclass_eval { constant name, value }
18
- end
19
- end
20
-
21
- def each_binding(&block)
22
- if @outer_scope
23
- @outer_scope.each_binding(&block)
24
- end
25
- @bindings.each(&block)
26
- end
27
-
28
- def let(bindings = {}, &block)
29
- ls = LetScope.new(block_self(&block), bindings, self)
30
- ls.instance_eval(&block)
31
- end
32
-
33
- # Including this module into your current namespace defines the let method.
34
- module Include
35
- include Tins::BlockSelf
36
-
37
- def let(bindings = {}, &block)
38
- ls = LetScope.new(block_self(&block), bindings)
39
- ls.instance_eval(&block)
40
- end
41
- end
42
- end
43
-
44
5
  if $0 == __FILE__
45
6
  class Foo
46
- include LetScope::Include
7
+ include Tins::DynamicScope
8
+
9
+ def let(bindings = {})
10
+ dynamic_scope do
11
+ bindings.each { |name, value| send("#{name}=", value) }
12
+ yield
13
+ end
14
+ end
47
15
 
48
16
  def twice(x)
49
17
  2 * x
data/examples/mail.rb CHANGED
@@ -1,10 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
1
3
  require 'tins'
2
4
  require 'net/smtp'
3
5
  require 'time'
4
6
 
5
7
  class Mail
6
8
  extend Tins::DSLAccessor
7
- include Tins::InstanceExec
8
9
  include Tins::MethodMissingDelegator::DelegatorModule
9
10
  include Tins::BlockSelf
10
11
 
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
1
3
  require 'tins'
2
4
  $:.unshift 'examples'
3
5
 
@@ -22,4 +22,3 @@
22
22
  18. right goto: 19
23
23
  19. cond if: '1', then: 18, else: 21
24
24
  20. halt
25
- 21. halt
data/examples/turing.rb CHANGED
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
1
3
  require 'term/ansicolor'
2
4
  require 'tins'
3
5
 
@@ -305,6 +307,8 @@ if $0 == __FILE__ and ARGV.any?
305
307
  else
306
308
  raise "unknown turing machine suffix: #{ext}, use .stm or .mtm"
307
309
  end
308
- tm = machine_type.new(File.read(filename))
310
+ tm = File.open(filename) do |file|
311
+ machine_type.new(file)
312
+ end
309
313
  $DEBUG ? tm.step(*tapes) : tm.run(*tapes)
310
314
  end
data/lib/tins/alias.rb CHANGED
@@ -1 +1,2 @@
1
+ # This Is Not Spruz
1
2
  Spruz = Tins
data/lib/tins/annotate.rb CHANGED
@@ -1,38 +1,48 @@
1
- module Tins::Annotate
2
- def annotate(name)
3
- singleton_class.class_eval do
4
- define_method(name) do |annotation = :annotated|
5
- instance_variable_set "@__annotation_#{name}__", annotation
6
- end
1
+ module Tins
2
+ # A module for adding annotations to classes and methods
3
+ #
4
+ # This module provides functionality to add metadata annotations to classes and
5
+ # methods, allowing for enhanced documentation and introspection capabilities
6
+ module Annotate
7
+ # The annotate method sets up annotation functionality for a given name
8
+ # by defining methods to set and retrieve annotations on class methods
9
+ #
10
+ # @param name [ Symbol ] the name of the annotation to define
11
+ def annotate(name)
12
+ singleton_class.class_eval do
13
+ define_method(name) do |annotation = :annotated|
14
+ instance_variable_set "@__annotation_#{name}__", annotation
15
+ end
7
16
 
8
- define_method("#{name}_of") do |method_name|
9
- __send__("#{name}_annotations")[method_name]
10
- end
17
+ define_method("#{name}_of") do |method_name|
18
+ __send__("#{name}_annotations")[method_name]
19
+ end
11
20
 
12
- define_method("#{name}_annotations") do
13
- if instance_variable_defined?("@__annotation_#{name}_annotations__")
14
- instance_variable_get "@__annotation_#{name}_annotations__"
15
- else
16
- instance_variable_set "@__annotation_#{name}_annotations__", {}
21
+ define_method("#{name}_annotations") do
22
+ if instance_variable_defined?("@__annotation_#{name}_annotations__")
23
+ instance_variable_get "@__annotation_#{name}_annotations__"
24
+ else
25
+ instance_variable_set "@__annotation_#{name}_annotations__", {}
26
+ end
17
27
  end
18
- end
19
28
 
20
- old_method_added = instance_method(:method_added)
21
- define_method(:method_added) do |method_name|
22
- old_method_added.bind(self).call method_name
23
- if annotation = instance_variable_get("@__annotation_#{name}__")
24
- __send__("#{name}_annotations")[method_name] = annotation
29
+ old_method_added = instance_method(:method_added)
30
+ define_method(:method_added) do |method_name|
31
+ old_method_added.bind(self).call method_name
32
+ if annotation = instance_variable_get("@__annotation_#{name}__")
33
+ __send__("#{name}_annotations")[method_name] = annotation
34
+ end
35
+ instance_variable_set "@__annotation_#{name}__", nil
25
36
  end
26
- instance_variable_set "@__annotation_#{name}__", nil
27
37
  end
28
- end
29
38
 
30
- define_method("#{name}_annotations") do
31
- self.class.__send__("#{name}_annotations")
32
- end
39
+ define_method("#{name}_annotations") do
40
+ self.class.__send__("#{name}_annotations")
41
+ end
33
42
 
34
- define_method("#{name}_of") do |method_name|
35
- self.class.__send__("#{name}_of", method_name)
43
+ define_method("#{name}_of") do |method_name|
44
+ self.class.__send__("#{name}_of", method_name)
45
+ end
36
46
  end
37
47
  end
38
48
  end
@@ -1,17 +1,47 @@
1
1
  module Tins
2
+ # A module that provides methods to call private and protected methods on
3
+ # objects.
2
4
  module AskAndSend
5
+ # The ask_and_send method attempts to invoke a given method on the object
6
+ # if that method is available.
7
+ #
8
+ # @param method_name [ Symbol ] the name of the method to invoke
9
+ # @param args [ Array ] arguments to pass to the method
10
+ # @yield [ block ] optional block to pass to the method
11
+ # @return [ Object, nil ] the result of the method call or nil if the
12
+ # method doesn't exist
3
13
  def ask_and_send(method_name, *args, &block)
4
14
  if respond_to?(method_name)
5
15
  __send__(method_name, *args, &block)
6
16
  end
7
17
  end
8
18
 
19
+ # The ask_and_send! method attempts to invoke a private or protected method
20
+ # on the object.
21
+ #
22
+ # @param method_name [ Symbol ] the name of the method to call
23
+ # @param args [ Array ] arguments to pass to the method
24
+ # @param block [ Proc ] optional block to pass to the method
25
+ #
26
+ # @return [ Object, nil ] the result of the method call or nil if the
27
+ # method doesn't exist
9
28
  def ask_and_send!(method_name, *args, &block)
10
29
  if respond_to?(method_name, true)
11
30
  __send__(method_name, *args, &block)
12
31
  end
13
32
  end
14
33
 
34
+ # The ask_and_send_or_self method attempts to invoke the specified method
35
+ # on the object If the method exists, it calls the method with the provided
36
+ # arguments and block If the method does not exist, it returns the object
37
+ # itself
38
+ #
39
+ # @param method_name [ Symbol ] the name of the method to call
40
+ # @param args [ Array ] the arguments to pass to the method
41
+ # @param block [ Proc ] the block to pass to the method
42
+ #
43
+ # @return [ Object ] the result of the method call or self if the method
44
+ # doesn't exist
15
45
  def ask_and_send_or_self(method_name, *args, &block)
16
46
  if respond_to?(method_name)
17
47
  __send__(method_name, *args, &block)
@@ -20,6 +50,17 @@ module Tins
20
50
  end
21
51
  end
22
52
 
53
+ # The ask_and_send_or_self! method attempts to send a message to the object
54
+ # with the given method name and arguments. If the object responds to the
55
+ # method, it executes the method and returns the result. If the object does
56
+ # not respond to the method, it returns the object itself.
57
+ #
58
+ # @param method_name [ Symbol ] the name of the method to send
59
+ # @param args [ Array ] the arguments to pass to the method
60
+ # @param block [ Proc ] the block to pass to the method
61
+ #
62
+ # @return [ Object ] the result of the method call or the object itself if
63
+ # the method is not found
23
64
  def ask_and_send_or_self!(method_name, *args, &block)
24
65
  if respond_to?(method_name, true)
25
66
  __send__(method_name, *args, &block)
data/lib/tins/attempt.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  module Tins
2
+ # A module that provides functionality for attempting operations with error
3
+ # handling and retry logic.
2
4
  module Attempt
3
5
  # Attempts code in block *attempts* times, sleeping according to *sleep*
4
6
  # between attempts and catching the exception(s) in *exception_class*.
@@ -14,6 +16,7 @@ module Tins
14
16
  def attempt(opts = {}, &block)
15
17
  sleep = nil
16
18
  exception_class = StandardError
19
+ prev_exception = nil
17
20
  if Numeric === opts
18
21
  attempts = opts
19
22
  else
@@ -28,7 +31,7 @@ module Tins
28
31
  if exception_class.nil?
29
32
  begin
30
33
  count += 1
31
- if block.call(count)
34
+ if block.call(count, prev_exception)
32
35
  return true
33
36
  elsif count < attempts
34
37
  sleep_duration(sleep, count)
@@ -38,10 +41,11 @@ module Tins
38
41
  else
39
42
  begin
40
43
  count += 1
41
- block.call(count)
44
+ block.call(count, prev_exception)
42
45
  true
43
46
  rescue *exception_class
44
47
  if count < attempts
48
+ prev_exception = $!
45
49
  sleep_duration(sleep, count)
46
50
  retry
47
51
  end
@@ -61,6 +65,11 @@ module Tins
61
65
 
62
66
  private
63
67
 
68
+ # The sleep_duration method handles sleeping for a specified duration or
69
+ # based on a proc call.
70
+ #
71
+ # @param duration [ Numeric, Proc ] the duration to sleep or a proc that
72
+ # returns the duration
64
73
  def sleep_duration(duration, count)
65
74
  case duration
66
75
  when Numeric
@@ -70,6 +79,26 @@ module Tins
70
79
  end
71
80
  end
72
81
 
82
+ # Computes the base for exponential backoff that results in a specific
83
+ # total sleep duration.
84
+ #
85
+ # This method solves for the base `x` in the geometric series:
86
+ # x^0 + x^1 + x^2 + ... + x^(attempts-1) = sleep
87
+ #
88
+ # The solution ensures that when using exponential delays with base `x`,
89
+ # the total time spent across all attempts equals approximately the
90
+ # specified sleep duration. This method of computation is used if a
91
+ # negative number of secondes was requested in the attempt method.
92
+ #
93
+ # @param sleep [Numeric] The total number of seconds to distribute across
94
+ # all attempts
95
+ # @param attempts [Integer] The number of attempts (must be > 2)
96
+ #
97
+ # @return [Float] The base for exponential backoff delays
98
+ # @raise [ArgumentError] If attempts <= 2, or if the sleep parameters are
99
+ # invalid
100
+ # @raise [ArgumentError] If the algorithm fails to converge after maximum
101
+ # iterations
73
102
  def compute_duration_base(sleep, attempts)
74
103
  x1, x2 = 1, sleep
75
104
  attempts <= sleep or raise ArgumentError,
@@ -96,6 +125,18 @@ module Tins
96
125
  result
97
126
  end
98
127
 
128
+ # The interpret_sleep method determines the sleep behavior for retry attempts.
129
+ #
130
+ # @param sleep [Numeric, Proc, nil] the sleep duration or proc to use
131
+ # between retries, nil if no sleep was requested
132
+ # @param attempts [Integer] the number of retry attempts
133
+ #
134
+ # @return [Proc, nil] a proc that calculates the sleep duration or nil if
135
+ # no sleep is needed
136
+ #
137
+ # @raise [ArgumentError] if a negative sleep value is provided with less
138
+ # than 3 attempts
139
+ # @raise [TypeError] if the sleep argument is not Numeric, Proc, or nil
99
140
  def interpret_sleep(sleep, attempts)
100
141
  case sleep
101
142
  when nil
@@ -1,5 +1,12 @@
1
1
  module Tins
2
+ # A hash subclass that ensures bijection between keys and values
2
3
  class Bijection < Hash
4
+ # Creates a new Bijection instance with key-value pairs.
5
+ #
6
+ # @param pairs [Array] an array of key-value pairs to populate the
7
+ # bijection
8
+ #
9
+ # @return [Bijection] a new bijection populated with the provided pairs
3
10
  def self.[](*pairs)
4
11
  pairs.size % 2 == 0 or
5
12
  raise ArgumentError, "odd number of arguments for #{self}"
@@ -15,10 +22,20 @@ module Tins
15
22
  end
16
23
  end
17
24
 
25
+ # The initialize method sets up a new instance with an inverted bijection.
26
+ #
27
+ # @param inverted [ Bijection ] the inverted bijection object, defaults to
28
+ # a new Bijection instance
18
29
  def initialize(inverted = Bijection.new(self))
19
30
  @inverted = inverted
20
31
  end
21
32
 
33
+ # The fill method populates the object with content from a block if it is
34
+ # empty.
35
+ #
36
+ # @return [ self ] returns the object itself after filling or if it was not
37
+ # empty
38
+ # @yield [ self ] yields self to the block for population
22
39
  def fill
23
40
  if empty?
24
41
  yield self
@@ -27,6 +44,9 @@ module Tins
27
44
  self
28
45
  end
29
46
 
47
+ # The freeze method freezes the current object and its inverted attribute.
48
+ #
49
+ # @return [Object] the frozen object
30
50
  def freeze
31
51
  r = super
32
52
  unless @inverted.frozen?
@@ -35,12 +55,26 @@ module Tins
35
55
  r
36
56
  end
37
57
 
58
+ # The []= method assigns a value to a key in the hash and maintains an
59
+ # inverted index.
60
+ #
61
+ # @param key [ Object ] the key to assign
62
+ # @param value [ Object ] the value to assign
63
+ #
64
+ # @return [ Object ] the assigned value
65
+ #
66
+ # @note This method will not overwrite existing keys, it will return early
67
+ # if the key already exists.
38
68
  def []=(key, value)
39
69
  key?(key) and return
40
70
  super
41
71
  @inverted[value] = key
42
72
  end
43
73
 
74
+ # The inverted attribute returns the inverted state of the object.
75
+ # Creates a new Bijection instance filled with key-value pairs.
76
+ #
77
+ # @return [Bijection] a new bijection instance with the specified key-value pairs
44
78
  attr_reader :inverted
45
79
  end
46
80
  end
@@ -1,5 +1,26 @@
1
1
  module Tins
2
+ # A module that provides a predicate method for checking if a value matches
3
+ # any of the given cases.
2
4
  module CasePredicate
5
+ # Checks if the object matches any of the given arguments using the ===
6
+ # operator.
7
+ #
8
+ # This method provides pattern matching functionality similar to Ruby's
9
+ # case/when statements, using the === operator for semantic equality
10
+ # checking.
11
+ #
12
+ # @example Basic type matching
13
+ # "hello".case?(String, Integer) # => String (matches first argument)
14
+ # 42.case?(String, Integer) # => Integer (matches first argument)
15
+ # nil.case?(String, Integer) # => nil (no matches)
16
+ #
17
+ # @example Range and pattern matching
18
+ # 15.case?(1..10, 11..20, 21..30) # => 11..20 (matches range)
19
+ # "hello world".case?(/foo/, /hello/) # => /hello/ (matches regex)
20
+ #
21
+ # @param args [Array] the arguments to check against using === operator
22
+ # @return [Object, nil] the first matching argument, or nil if no match is
23
+ # found
3
24
  def case?(*args)
4
25
  args.find { |a| a === self }
5
26
  end
data/lib/tins/complete.rb CHANGED
@@ -1,11 +1,27 @@
1
1
  require 'readline'
2
2
 
3
3
  module Tins
4
+ # A module that provides completion functionality for objects.
4
5
  module Complete
5
6
  module_function
6
7
 
7
8
  @@sync = Sync.new
8
9
 
10
+ # The complete method reads a line of input from the user with optional
11
+ # prompt and history support.
12
+ #
13
+ # @param prompt [ String ] the prompt string to display to the user
14
+ # @param add_hist [ Boolean ] whether to add the input to the command
15
+ # history
16
+ #
17
+ # @yield [ String ] the completion procedure to use for tab completion
18
+ #
19
+ # @return [ String ] the line of input entered by the user
20
+ #
21
+ # @example Prompt with easy tab completion
22
+ # complete(prompt: 'Pick a ruby file! ') {
23
+ # Dir['**/*.rb'].grep(/#{it}/)
24
+ # }.then { '%u lines' % File.new(it).each_line.count }
9
25
  def complete(prompt: '', add_hist: false, &block)
10
26
  @@sync.synchronize do
11
27
  Readline.completion_proc = block
data/lib/tins/concern.rb CHANGED
@@ -1,9 +1,49 @@
1
1
  module Tins
2
+ # A module concern implementation that supports dependency tracking, class
3
+ # method inclusion, and block execution hooks.
4
+ #
5
+ # This module provides a way to define reusable module functionality with
6
+ # automatic dependency management, class method injection, and lifecycle
7
+ # callbacks similar to ActiveSupport::Concern but with more control.
8
+ #
9
+ # @example Basic usage
10
+ # module MyConcern
11
+ # extend Tins::Concern
12
+ #
13
+ # included do
14
+ # attr_accessor :logger
15
+ # end
16
+ #
17
+ # def log(message)
18
+ # logger&.info message
19
+ # end
20
+ #
21
+ # class_methods do
22
+ # def my_class_method
23
+ # # ...
24
+ # end
25
+ # end
26
+ # end
27
+ #
28
+ # class MyClass
29
+ # include MyConcern
30
+ # end
2
31
  module Concern
32
+ # Extends the base object with dependency tracking capabilities.
33
+ #
34
+ # This method initializes an instance variable on the base object to store
35
+ # dependency information.
36
+ #
37
+ # @param base [ Object ] the object being extended
3
38
  def self.extended(base)
4
39
  base.instance_variable_set("@_dependencies", [])
5
40
  end
6
41
 
42
+ # The append_features method includes dependencies and class methods in the
43
+ # base class.
44
+ #
45
+ # @param base [ Object ] the base class to include features in
46
+ # @return [ Boolean ] true if features were appended, false otherwise
7
47
  def append_features(base)
8
48
  if base.instance_variable_defined?("@_dependencies")
9
49
  base.instance_variable_get("@_dependencies") << self
@@ -19,12 +59,72 @@ module Tins
19
59
  end
20
60
  end
21
61
 
62
+ # Prepends the features of this module to the base class.
63
+ #
64
+ # This method handles the inclusion of dependencies and class methods when
65
+ # a module is included in a class. It manages dependency tracking and
66
+ # ensures proper extension of the base class with ClassMethods.
67
+ #
68
+ # @param base [Class] the class that includes this module
69
+ # @return [Boolean] true if the module was successfully prepended, false otherwise
70
+ def prepend_features(base)
71
+ if base.instance_variable_defined?("@_dependencies")
72
+ base.instance_variable_get("@_dependencies") << self
73
+ false
74
+ else
75
+ return false if base < self
76
+ @_dependencies.each { |dep| base.send(:include, dep) }
77
+ super
78
+ base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
79
+ base.class_eval(&@_prepended_block) if instance_variable_defined?("@_prepended_block")
80
+ Thread.current[:tin_concern_args] = nil
81
+ true
82
+ end
83
+ end
84
+
85
+ # The included method is called when the module is included in a class or
86
+ # module.
87
+ #
88
+ # @param base [ Object ] the class or module that includes this module
89
+ # @param block [ Proc ] optional block to be executed when included
22
90
  def included(base = nil, &block)
23
91
  if base.nil?
92
+ instance_variable_defined?(:@_included_block) and
93
+ raise StandardError, "included block already defined"
24
94
  @_included_block = block
25
95
  else
26
96
  super
27
97
  end
28
98
  end
99
+
100
+ # The prepended method handles the setup of a block for prepend
101
+ # functionality or delegates to the superclass implementation
102
+ #
103
+ # @param base [ Object ] the base object to prepend to, or nil to set up a block
104
+ # @param block [ Proc ] the block to be used for prepend functionality
105
+ def prepended(base = nil, &block)
106
+ if base.nil?
107
+ instance_variable_defined?(:@_prepended_block) and
108
+ raise StandardError, "prepended block already defined"
109
+ @_prepended_block = block
110
+ else
111
+ super
112
+ end
113
+ end
114
+
115
+ # Defines a ClassMethods module for the current class and evaluates the
116
+ # given block within it.
117
+ #
118
+ # @param block [Proc] The block to be evaluated in the context of the
119
+ # ClassMethods module
120
+ # @return [Module] The ClassMethods module that was defined or retrieved
121
+ def class_methods(&block)
122
+ modul = const_get(:ClassMethods) if const_defined?(:ClassMethods, false)
123
+ unless modul
124
+ modul = Module.new
125
+ const_set(:ClassMethods, modul)
126
+ end
127
+ modul.module_eval(&block)
128
+ end
29
129
  end
30
130
  end