cli-kit 4.0.0 → 5.0.1

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +3 -0
  3. data/.github/workflows/cla.yml +22 -0
  4. data/.github/workflows/ruby.yml +16 -2
  5. data/.gitignore +2 -0
  6. data/.rubocop.sorbet.yml +47 -0
  7. data/.rubocop.yml +32 -1
  8. data/.ruby-version +1 -0
  9. data/Gemfile +10 -1
  10. data/Gemfile.lock +102 -29
  11. data/README.md +46 -3
  12. data/Rakefile +1 -0
  13. data/bin/onchange +30 -0
  14. data/bin/tapioca +28 -0
  15. data/bin/testunit +1 -0
  16. data/cli-kit.gemspec +9 -4
  17. data/dev.yml +38 -3
  18. data/examples/minimal/example.rb +11 -6
  19. data/examples/single-file/example.rb +25 -35
  20. data/gen/lib/gen/commands/help.rb +8 -10
  21. data/gen/lib/gen/commands/new.rb +23 -9
  22. data/gen/lib/gen/commands.rb +21 -9
  23. data/gen/lib/gen/entry_point.rb +12 -3
  24. data/gen/lib/gen/generator.rb +32 -11
  25. data/gen/lib/gen/help.rb +63 -0
  26. data/gen/lib/gen.rb +18 -23
  27. data/gen/template/bin/update-deps +2 -2
  28. data/gen/template/dev-gems.yml +1 -1
  29. data/gen/template/dev-vendor.yml +1 -1
  30. data/gen/template/lib/__app__/commands.rb +1 -4
  31. data/gen/template/lib/__app__.rb +8 -17
  32. data/gen/template/test/example_test.rb +1 -1
  33. data/lib/cli/kit/args/definition.rb +344 -0
  34. data/lib/cli/kit/args/evaluation.rb +234 -0
  35. data/lib/cli/kit/args/parser/node.rb +132 -0
  36. data/lib/cli/kit/args/parser.rb +129 -0
  37. data/lib/cli/kit/args/tokenizer.rb +133 -0
  38. data/lib/cli/kit/args.rb +16 -0
  39. data/lib/cli/kit/base_command.rb +17 -32
  40. data/lib/cli/kit/command_help.rb +271 -0
  41. data/lib/cli/kit/command_registry.rb +72 -20
  42. data/lib/cli/kit/config.rb +25 -22
  43. data/lib/cli/kit/core_ext.rb +30 -0
  44. data/lib/cli/kit/error_handler.rb +131 -70
  45. data/lib/cli/kit/executor.rb +20 -3
  46. data/lib/cli/kit/ini.rb +31 -38
  47. data/lib/cli/kit/levenshtein.rb +12 -4
  48. data/lib/cli/kit/logger.rb +16 -2
  49. data/lib/cli/kit/opts.rb +301 -0
  50. data/lib/cli/kit/parse_args.rb +55 -0
  51. data/lib/cli/kit/resolver.rb +8 -0
  52. data/lib/cli/kit/sorbet_runtime_stub.rb +154 -0
  53. data/lib/cli/kit/support/test_helper.rb +27 -16
  54. data/lib/cli/kit/support.rb +2 -0
  55. data/lib/cli/kit/system.rb +194 -57
  56. data/lib/cli/kit/util.rb +48 -103
  57. data/lib/cli/kit/version.rb +3 -1
  58. data/lib/cli/kit.rb +104 -7
  59. metadata +30 -14
  60. data/.github/probots.yml +0 -2
  61. data/lib/cli/kit/autocall.rb +0 -21
  62. data/lib/cli/kit/ruby_backports/enumerable.rb +0 -6
data/lib/cli/kit/util.rb CHANGED
@@ -1,81 +1,26 @@
1
+ # typed: true
2
+
3
+ require 'cli/kit'
4
+
1
5
  module CLI
2
6
  module Kit
3
7
  module Util
4
8
  class << self
5
- def snake_case(camel_case, seperator = '_')
6
- camel_case.to_s # MyCoolThing::MyAPIModule
7
- .gsub(/::/, '/') # MyCoolThing/MyAPIModule
8
- .gsub(/([A-Z]+)([A-Z][a-z])/, "\\1#{seperator}\\2") # MyCoolThing::MyAPI_Module
9
- .gsub(/([a-z\d])([A-Z])/, "\\1#{seperator}\\2") # My_Cool_Thing::My_API_Module
10
- .downcase # my_cool_thing/my_api_module
11
- end
12
-
13
- def dash_case(camel_case)
14
- snake_case(camel_case, '-')
15
- end
16
-
17
- # The following methods is taken from activesupport
18
- # All credit for this method goes to the original authors.
19
- # https://github.com/rails/rails/blob/d66e7835bea9505f7003e5038aa19b6ea95ceea1/activesupport/lib/active_support/core_ext/string/strip.rb
20
- #
21
- # Copyright (c) 2005-2018 David Heinemeier Hansson
22
- #
23
- # Permission is hereby granted, free of charge, to any person obtaining
24
- # a copy of this software and associated documentation files (the
25
- # "Software"), to deal in the Software without restriction, including
26
- # without limitation the rights to use, copy, modify, merge, publish,
27
- # distribute, sublicense, and/or sell copies of the Software, and to
28
- # permit persons to whom the Software is furnished to do so, subject to
29
- # the following conditions:
30
- #
31
- # The above copyright notice and this permission notice shall be
32
- # included in all copies or substantial portions of the Software.
33
- #
34
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
35
- # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
36
- # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
37
- # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
38
- # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
39
- # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
40
- # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
41
- #
42
- # Strips indentation by removing the amount of leading whitespace in the least indented
43
- # non-empty line in the whole string
9
+ extend T::Sig
44
10
  #
45
- def strip_heredoc(str)
46
- str.gsub(/^#{str.scan(/^[ \t]*(?=\S)/).min}/, ''.freeze)
47
- end
48
-
49
- # Joins an array with commas and "and", using the Oxford comma.
50
- def english_join(array)
51
- return '' if array.nil?
52
- return array.join(' and ') if array.length < 3
53
-
54
- "#{array[0..-2].join(", ")}, and #{array[-1]}"
55
- end
56
-
57
- # Execute a block within the context of a variable enviroment
58
- #
59
- def with_environment(environment, value)
60
- return yield unless environment
61
-
62
- old_env = ENV[environment]
63
- begin
64
- ENV[environment] = value
65
- yield
66
- ensure
67
- old_env ? ENV[environment] = old_env : ENV.delete(environment)
68
- end
69
- end
70
-
71
11
  # Converts an integer representing bytes into a human readable format
72
12
  #
13
+ sig { params(bytes: Integer, precision: Integer, space: T::Boolean).returns(String) }
73
14
  def to_filesize(bytes, precision: 2, space: false)
74
15
  to_si_scale(bytes, 'B', precision: precision, space: space, factor: 1024)
75
16
  end
76
17
 
77
18
  # Converts a number to a human readable format on the SI scale
78
19
  #
20
+ sig do
21
+ params(number: Numeric, unit: String, factor: Integer, precision: Integer, space: T::Boolean)
22
+ .returns(String)
23
+ end
79
24
  def to_si_scale(number, unit = '', factor: 1000, precision: 2, space: false)
80
25
  raise ArgumentError, 'factor should only be 1000 or 1024' unless [1000, 1024].include?(factor)
81
26
 
@@ -84,7 +29,7 @@ module CLI
84
29
  negative = number < 0
85
30
  number = number.abs.to_f
86
31
 
87
- if number == 0 || number.between?(1, factor)
32
+ if number == 0.0 || number.between?(1, factor)
88
33
  prefix = ''
89
34
  scale = 0
90
35
  else
@@ -92,22 +37,24 @@ module CLI
92
37
  if number < 1
93
38
  index = [-scale - 1, small_scale.length].min
94
39
  scale = -(index + 1)
95
- prefix = small_scale[index]
40
+ prefix = T.must(small_scale[index])
96
41
  else
97
42
  index = [scale - 1, big_scale.length].min
98
43
  scale = index + 1
99
- prefix = big_scale[index]
44
+ prefix = T.must(big_scale[index])
100
45
  end
101
46
  end
102
47
 
103
48
  divider = (factor**scale)
104
- fnum = (number / divider).round(precision)
49
+ fnum = (number / divider.to_f).round(precision)
105
50
 
106
51
  # Trim useless decimal
107
- fnum = fnum.to_i if (fnum.to_i.to_f * divider) == number
52
+ fnum = fnum.to_i if (fnum.to_i.to_f * divider.to_f) == number
108
53
 
109
54
  fnum = -fnum if negative
110
- prefix = ' ' + prefix if space
55
+ if space
56
+ prefix = ' ' + prefix
57
+ end
111
58
 
112
59
  "#{fnum}#{prefix}#{unit}"
113
60
  end
@@ -115,38 +62,18 @@ module CLI
115
62
  # Dir.chdir, when invoked in block form, complains when we call chdir
116
63
  # again recursively. There's no apparent good reason for this, so we
117
64
  # simply implement our own block form of Dir.chdir here.
118
- def with_dir(dir)
119
- prev = Dir.pwd
120
- Dir.chdir(dir)
121
- yield
122
- ensure
123
- Dir.chdir(prev)
65
+ sig do
66
+ type_parameters(:T).params(dir: String, block: T.proc.returns(T.type_parameter(:T)))
67
+ .returns(T.type_parameter(:T))
124
68
  end
125
-
126
- def with_tmp_dir
127
- require 'fileutils'
128
- dir = Dir.mktmpdir
129
- with_dir(dir) do
130
- yield(dir)
69
+ def with_dir(dir, &block)
70
+ prev = Dir.pwd
71
+ begin
72
+ Dir.chdir(dir)
73
+ yield
74
+ ensure
75
+ Dir.chdir(prev)
131
76
  end
132
- ensure
133
- FileUtils.remove_entry(dir)
134
- end
135
-
136
- # Standard way of checking for CI / Tests
137
- def testing?
138
- ci? || ENV['TEST']
139
- end
140
-
141
- # Set only in IntegrationTest#session; indicates that the process was
142
- # called by `session.execute` from an IntegrationTest subclass.
143
- def integration_test_session?
144
- ENV['INTEGRATION_TEST_SESSION']
145
- end
146
-
147
- # Standard way of checking for CI
148
- def ci?
149
- ENV['CI']
150
77
  end
151
78
 
152
79
  # Must call retry_after on the result in order to execute the block
@@ -158,25 +85,43 @@ module CLI
158
85
  # end.retry_after(ExpectedError) do
159
86
  # costly_prep()
160
87
  # end
88
+ sig do
89
+ type_parameters(:T).params(block_that_might_raise: T.proc.returns(T.type_parameter(:T)))
90
+ .returns(Retrier[T.type_parameter(:T)])
91
+ end
161
92
  def begin(&block_that_might_raise)
162
93
  Retrier.new(block_that_might_raise)
163
94
  end
164
95
  end
165
96
 
166
97
  class Retrier
98
+ extend T::Sig
99
+ extend T::Generic
100
+
101
+ BlockReturnType = type_member
102
+
103
+ sig { params(block_that_might_raise: T.proc.returns(BlockReturnType)).void }
167
104
  def initialize(block_that_might_raise)
168
105
  @block_that_might_raise = block_that_might_raise
169
106
  end
170
107
 
108
+ sig do
109
+ params(
110
+ exception: T.class_of(Exception),
111
+ retries: Integer,
112
+ before_retry: T.nilable(T.proc.params(e: Exception).void),
113
+ ).returns(BlockReturnType)
114
+ end
171
115
  def retry_after(exception = StandardError, retries: 1, &before_retry)
172
116
  @block_that_might_raise.call
173
117
  rescue exception => e
174
118
  raise if (retries -= 1) < 0
119
+
175
120
  if before_retry
176
121
  if before_retry.arity == 0
177
- yield
122
+ T.cast(before_retry, T.proc.void).call
178
123
  else
179
- yield e
124
+ T.cast(before_retry, T.proc.params(e: Exception).void).call(e)
180
125
  end
181
126
  end
182
127
  retry
@@ -1,5 +1,7 @@
1
+ # typed: true
2
+
1
3
  module CLI
2
4
  module Kit
3
- VERSION = '4.0.0'
5
+ VERSION = '5.0.1'
4
6
  end
5
7
  end
data/lib/cli/kit.rb CHANGED
@@ -1,17 +1,29 @@
1
+ # typed: true
2
+
1
3
  require 'cli/ui'
2
- require 'cli/kit/ruby_backports/enumerable'
4
+
5
+ unless defined?(T)
6
+ require('cli/kit/sorbet_runtime_stub')
7
+ end
8
+
9
+ require 'cli/kit/core_ext'
3
10
 
4
11
  module CLI
5
12
  module Kit
6
- autoload :Autocall, 'cli/kit/autocall'
13
+ extend T::Sig
14
+
15
+ autoload :Args, 'cli/kit/args'
7
16
  autoload :BaseCommand, 'cli/kit/base_command'
8
17
  autoload :CommandRegistry, 'cli/kit/command_registry'
18
+ autoload :CommandHelp, 'cli/kit/command_help'
9
19
  autoload :Config, 'cli/kit/config'
10
20
  autoload :ErrorHandler, 'cli/kit/error_handler'
11
21
  autoload :Executor, 'cli/kit/executor'
12
22
  autoload :Ini, 'cli/kit/ini'
13
23
  autoload :Levenshtein, 'cli/kit/levenshtein'
14
24
  autoload :Logger, 'cli/kit/logger'
25
+ autoload :Opts, 'cli/kit/opts'
26
+ autoload :ParseArgs, 'cli/kit/parse_args'
15
27
  autoload :Resolver, 'cli/kit/resolver'
16
28
  autoload :Support, 'cli/kit/support'
17
29
  autoload :System, 'cli/kit/system'
@@ -28,7 +40,8 @@ module CLI
28
40
  # a bare `rescue => e`.
29
41
  #
30
42
  # * Abort prints its message in red and exits 1;
31
- # * Bug additionally submits the exception to Bugsnag;
43
+ # * Bug additionally submits the exception to the exception_reporter passed to
44
+ # `CLI::Kit::ErrorHandler.new`
32
45
  # * AbortSilent and BugSilent do the same as above, but do not print
33
46
  # messages before exiting.
34
47
  #
@@ -51,10 +64,94 @@ module CLI
51
64
  # 1. rescue Abort or Bug
52
65
  # 2. Print a contextualized error message
53
66
  # 3. Re-raise AbortSilent or BugSilent respectively.
67
+ #
68
+ # These aren't the only exceptions that can carry this 'bug' and 'silent'
69
+ # metadata, however:
70
+ #
71
+ # If you raise an exception with `CLI::Kit.raise(..., bug: x, silent: y)`,
72
+ # those last two (optional) keyword arguments will attach the metadata to
73
+ # whatever exception you raise. This is interpreted later in the
74
+ # ErrorHandler to decide how to print output and whether to submit the
75
+ # exception to bugsnag.
54
76
  GenericAbort = Class.new(Exception) # rubocop:disable Lint/InheritException
55
- Abort = Class.new(GenericAbort)
56
- Bug = Class.new(GenericAbort)
57
- BugSilent = Class.new(GenericAbort)
58
- AbortSilent = Class.new(GenericAbort)
77
+
78
+ class Abort < GenericAbort # bug:false; silent: false
79
+ extend(T::Sig)
80
+
81
+ sig { returns(T::Boolean) }
82
+ def bug?
83
+ false
84
+ end
85
+ end
86
+
87
+ class Bug < GenericAbort # bug:true; silent:false
88
+ end
89
+
90
+ class BugSilent < GenericAbort # bug:true; silent:true
91
+ extend(T::Sig)
92
+
93
+ sig { returns(T::Boolean) }
94
+ def silent?
95
+ true
96
+ end
97
+ end
98
+
99
+ class AbortSilent < GenericAbort # bug:false; silent:true
100
+ extend(T::Sig)
101
+
102
+ sig { returns(T::Boolean) }
103
+ def bug?
104
+ false
105
+ end
106
+
107
+ sig { returns(T::Boolean) }
108
+ def silent?
109
+ true
110
+ end
111
+ end
112
+
113
+ class << self
114
+ extend T::Sig
115
+
116
+ # Mirrors the API of Kernel#raise, but with the addition of a few new
117
+ # optional keyword arguments. `bug` and `silent` attach metadata to the
118
+ # exception being raised, which is interpreted later in the ErrorHandler to
119
+ # decide what to print and whether to submit to bugsnag.
120
+ #
121
+ # `depth` is used to trim leading elements of the backtrace. If you wrap
122
+ # this method in your own wrapper, you'll want to pass `depth: 2`, for
123
+ # example.
124
+ sig do
125
+ params(
126
+ exception: T.any(Class, String, Exception),
127
+ string: T.untyped,
128
+ array: T.nilable(T::Array[String]),
129
+ cause: T.nilable(Exception),
130
+ bug: T.nilable(T::Boolean),
131
+ silent: T.nilable(T::Boolean),
132
+ depth: Integer,
133
+ ).returns(T.noreturn)
134
+ end
135
+ def raise(
136
+ # default arguments
137
+ exception = T.unsafe(nil), string = T.unsafe(nil), array = T.unsafe(nil), cause: $ERROR_INFO,
138
+ # new arguments
139
+ bug: nil, silent: nil, depth: 1
140
+ )
141
+ if array
142
+ T.unsafe(Kernel).raise(exception, string, array, cause: cause)
143
+ elsif string
144
+ T.unsafe(Kernel).raise(exception, string, Kernel.caller(depth), cause: cause)
145
+ elsif exception.is_a?(String)
146
+ T.unsafe(Kernel).raise(RuntimeError, exception, Kernel.caller(depth), cause: cause)
147
+ else
148
+ T.unsafe(Kernel).raise(exception, exception.message, Kernel.caller(depth), cause: cause)
149
+ end
150
+ rescue Exception => e # rubocop:disable Lint/RescueException
151
+ e.bug!(bug) unless bug.nil?
152
+ e.silent!(silent) unless silent.nil?
153
+ Kernel.raise(e, cause: cause)
154
+ end
155
+ end
59
156
  end
60
157
  end
metadata CHANGED
@@ -1,31 +1,32 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cli-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 5.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
8
8
  - Aaron Olson
9
9
  - Lisa Ugray
10
- autorequire:
10
+ - Don Kelly
11
+ autorequire:
11
12
  bindir: exe
12
13
  cert_chain: []
13
- date: 2021-11-10 00:00:00.000000000 Z
14
+ date: 2025-03-12 00:00:00.000000000 Z
14
15
  dependencies:
15
16
  - !ruby/object:Gem::Dependency
16
17
  name: cli-ui
17
18
  requirement: !ruby/object:Gem::Requirement
18
19
  requirements:
19
- - - ">="
20
+ - - "~>"
20
21
  - !ruby/object:Gem::Version
21
- version: 1.1.4
22
+ version: '2.0'
22
23
  type: :runtime
23
24
  prerelease: false
24
25
  version_requirements: !ruby/object:Gem::Requirement
25
26
  requirements:
26
- - - ">="
27
+ - - "~>"
27
28
  - !ruby/object:Gem::Version
28
- version: 1.1.4
29
+ version: '2.0'
29
30
  - !ruby/object:Gem::Dependency
30
31
  name: bundler
31
32
  requirement: !ruby/object:Gem::Requirement
@@ -73,6 +74,7 @@ email:
73
74
  - burke.libbey@shopify.com
74
75
  - aaron.olson@shopify.com
75
76
  - lisa.ugray@shopify.com
77
+ - don.kelly@shopify.com
76
78
  executables:
77
79
  - cli-kit
78
80
  extensions: []
@@ -80,10 +82,12 @@ extra_rdoc_files: []
80
82
  files:
81
83
  - ".github/CODEOWNERS"
82
84
  - ".github/dependabot.yml"
83
- - ".github/probots.yml"
85
+ - ".github/workflows/cla.yml"
84
86
  - ".github/workflows/ruby.yml"
85
87
  - ".gitignore"
88
+ - ".rubocop.sorbet.yml"
86
89
  - ".rubocop.yml"
90
+ - ".ruby-version"
87
91
  - Gemfile
88
92
  - Gemfile.lock
89
93
  - LICENSE.txt
@@ -91,6 +95,8 @@ files:
91
95
  - Rakefile
92
96
  - TODO.md
93
97
  - bin/console
98
+ - bin/onchange
99
+ - bin/tapioca
94
100
  - bin/test_gen
95
101
  - bin/testunit
96
102
  - cli-kit.gemspec
@@ -106,6 +112,7 @@ files:
106
112
  - gen/lib/gen/commands/new.rb
107
113
  - gen/lib/gen/entry_point.rb
108
114
  - gen/lib/gen/generator.rb
115
+ - gen/lib/gen/help.rb
109
116
  - gen/template/.gitignore
110
117
  - gen/template/Gemfile
111
118
  - gen/template/README.md
@@ -123,17 +130,26 @@ files:
123
130
  - gen/template/test/example_test.rb
124
131
  - gen/template/test/test_helper.rb
125
132
  - lib/cli/kit.rb
126
- - lib/cli/kit/autocall.rb
133
+ - lib/cli/kit/args.rb
134
+ - lib/cli/kit/args/definition.rb
135
+ - lib/cli/kit/args/evaluation.rb
136
+ - lib/cli/kit/args/parser.rb
137
+ - lib/cli/kit/args/parser/node.rb
138
+ - lib/cli/kit/args/tokenizer.rb
127
139
  - lib/cli/kit/base_command.rb
140
+ - lib/cli/kit/command_help.rb
128
141
  - lib/cli/kit/command_registry.rb
129
142
  - lib/cli/kit/config.rb
143
+ - lib/cli/kit/core_ext.rb
130
144
  - lib/cli/kit/error_handler.rb
131
145
  - lib/cli/kit/executor.rb
132
146
  - lib/cli/kit/ini.rb
133
147
  - lib/cli/kit/levenshtein.rb
134
148
  - lib/cli/kit/logger.rb
149
+ - lib/cli/kit/opts.rb
150
+ - lib/cli/kit/parse_args.rb
135
151
  - lib/cli/kit/resolver.rb
136
- - lib/cli/kit/ruby_backports/enumerable.rb
152
+ - lib/cli/kit/sorbet_runtime_stub.rb
137
153
  - lib/cli/kit/support.rb
138
154
  - lib/cli/kit/support/test_helper.rb
139
155
  - lib/cli/kit/system.rb
@@ -143,7 +159,7 @@ homepage: https://github.com/shopify/cli-kit
143
159
  licenses:
144
160
  - MIT
145
161
  metadata: {}
146
- post_install_message:
162
+ post_install_message:
147
163
  rdoc_options: []
148
164
  require_paths:
149
165
  - lib
@@ -151,15 +167,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
151
167
  requirements:
152
168
  - - ">="
153
169
  - !ruby/object:Gem::Version
154
- version: '0'
170
+ version: '3.0'
155
171
  required_rubygems_version: !ruby/object:Gem::Requirement
156
172
  requirements:
157
173
  - - ">="
158
174
  - !ruby/object:Gem::Version
159
175
  version: '0'
160
176
  requirements: []
161
- rubygems_version: 3.2.20
162
- signing_key:
177
+ rubygems_version: 3.0.3.1
178
+ signing_key:
163
179
  specification_version: 4
164
180
  summary: Terminal UI framework extensions
165
181
  test_files: []
data/.github/probots.yml DELETED
@@ -1,2 +0,0 @@
1
- enabled:
2
- - cla
@@ -1,21 +0,0 @@
1
- require 'cli/kit'
2
-
3
- module CLI
4
- module Kit
5
- module Autocall
6
- def autocall(const, &block)
7
- @autocalls ||= {}
8
- @autocalls[const] = block
9
- end
10
-
11
- def const_missing(const)
12
- block = begin
13
- @autocalls.fetch(const)
14
- rescue KeyError
15
- return super
16
- end
17
- const_set(const, block.call)
18
- end
19
- end
20
- end
21
- end
@@ -1,6 +0,0 @@
1
- module Enumerable
2
- def min_by(n = nil, &block)
3
- return sort_by(&block).first unless n
4
- sort_by(&block).first(n)
5
- end if instance_method(:min_by).arity == 0
6
- end