cli-kit 4.0.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/cla.yml +22 -0
  3. data/.github/workflows/ruby.yml +34 -2
  4. data/.gitignore +2 -0
  5. data/.rubocop.sorbet.yml +47 -0
  6. data/.rubocop.yml +16 -1
  7. data/Gemfile +10 -1
  8. data/Gemfile.lock +94 -18
  9. data/README.md +46 -3
  10. data/Rakefile +1 -0
  11. data/bin/onchange +30 -0
  12. data/bin/tapioca +29 -0
  13. data/bin/testunit +1 -0
  14. data/cli-kit.gemspec +2 -2
  15. data/dev.yml +35 -3
  16. data/examples/minimal/example.rb +3 -1
  17. data/examples/single-file/example.rb +25 -35
  18. data/gen/lib/gen/commands/help.rb +8 -10
  19. data/gen/lib/gen/commands/new.rb +23 -9
  20. data/gen/lib/gen/commands.rb +21 -9
  21. data/gen/lib/gen/entry_point.rb +12 -3
  22. data/gen/lib/gen/generator.rb +28 -7
  23. data/gen/lib/gen/help.rb +63 -0
  24. data/gen/lib/gen.rb +18 -23
  25. data/gen/template/bin/update-deps +2 -2
  26. data/gen/template/lib/__app__/commands.rb +1 -4
  27. data/gen/template/lib/__app__.rb +8 -17
  28. data/gen/template/test/example_test.rb +1 -1
  29. data/lib/cli/kit/args/definition.rb +344 -0
  30. data/lib/cli/kit/args/evaluation.rb +245 -0
  31. data/lib/cli/kit/args/parser/node.rb +132 -0
  32. data/lib/cli/kit/args/parser.rb +129 -0
  33. data/lib/cli/kit/args/tokenizer.rb +133 -0
  34. data/lib/cli/kit/args.rb +16 -0
  35. data/lib/cli/kit/base_command.rb +17 -32
  36. data/lib/cli/kit/command_help.rb +271 -0
  37. data/lib/cli/kit/command_registry.rb +69 -17
  38. data/lib/cli/kit/config.rb +25 -22
  39. data/lib/cli/kit/core_ext.rb +30 -0
  40. data/lib/cli/kit/error_handler.rb +131 -70
  41. data/lib/cli/kit/executor.rb +19 -3
  42. data/lib/cli/kit/ini.rb +31 -38
  43. data/lib/cli/kit/levenshtein.rb +12 -4
  44. data/lib/cli/kit/logger.rb +16 -2
  45. data/lib/cli/kit/opts.rb +301 -0
  46. data/lib/cli/kit/resolver.rb +8 -0
  47. data/lib/cli/kit/sorbet_runtime_stub.rb +156 -0
  48. data/lib/cli/kit/support/test_helper.rb +23 -14
  49. data/lib/cli/kit/support.rb +2 -0
  50. data/lib/cli/kit/system.rb +188 -54
  51. data/lib/cli/kit/util.rb +48 -103
  52. data/lib/cli/kit/version.rb +3 -1
  53. data/lib/cli/kit.rb +103 -7
  54. metadata +22 -10
  55. data/.github/probots.yml +0 -2
  56. data/lib/cli/kit/autocall.rb +0 -21
  57. 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,
22
+ space: T::Boolean).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.0'
4
6
  end
5
7
  end
data/lib/cli/kit.rb CHANGED
@@ -1,17 +1,28 @@
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'
15
26
  autoload :Resolver, 'cli/kit/resolver'
16
27
  autoload :Support, 'cli/kit/support'
17
28
  autoload :System, 'cli/kit/system'
@@ -28,7 +39,8 @@ module CLI
28
39
  # a bare `rescue => e`.
29
40
  #
30
41
  # * Abort prints its message in red and exits 1;
31
- # * Bug additionally submits the exception to Bugsnag;
42
+ # * Bug additionally submits the exception to the exception_reporter passed to
43
+ # `CLI::Kit::ErrorHandler.new`
32
44
  # * AbortSilent and BugSilent do the same as above, but do not print
33
45
  # messages before exiting.
34
46
  #
@@ -51,10 +63,94 @@ module CLI
51
63
  # 1. rescue Abort or Bug
52
64
  # 2. Print a contextualized error message
53
65
  # 3. Re-raise AbortSilent or BugSilent respectively.
66
+ #
67
+ # These aren't the only exceptions that can carry this 'bug' and 'silent'
68
+ # metadata, however:
69
+ #
70
+ # If you raise an exception with `CLI::Kit.raise(..., bug: x, silent: y)`,
71
+ # those last two (optional) keyword arguments will attach the metadata to
72
+ # whatever exception you raise. This is interpreted later in the
73
+ # ErrorHandler to decide how to print output and whether to submit the
74
+ # exception to bugsnag.
54
75
  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)
76
+
77
+ class Abort < GenericAbort # bug:false; silent: false
78
+ extend(T::Sig)
79
+
80
+ sig { returns(T::Boolean) }
81
+ def bug?
82
+ false
83
+ end
84
+ end
85
+
86
+ class Bug < GenericAbort # bug:true; silent:false
87
+ end
88
+
89
+ class BugSilent < GenericAbort # bug:true; silent:true
90
+ extend(T::Sig)
91
+
92
+ sig { returns(T::Boolean) }
93
+ def silent?
94
+ true
95
+ end
96
+ end
97
+
98
+ class AbortSilent < GenericAbort # bug:false; silent:true
99
+ extend(T::Sig)
100
+
101
+ sig { returns(T::Boolean) }
102
+ def bug?
103
+ false
104
+ end
105
+
106
+ sig { returns(T::Boolean) }
107
+ def silent?
108
+ true
109
+ end
110
+ end
111
+
112
+ class << self
113
+ extend T::Sig
114
+
115
+ # Mirrors the API of Kernel#raise, but with the addition of a few new
116
+ # optional keyword arguments. `bug` and `silent` attach metadata to the
117
+ # exception being raised, which is interpreted later in the ErrorHandler to
118
+ # decide what to print and whether to submit to bugsnag.
119
+ #
120
+ # `depth` is used to trim leading elements of the backtrace. If you wrap
121
+ # this method in your own wrapper, you'll want to pass `depth: 2`, for
122
+ # example.
123
+ sig do
124
+ params(
125
+ exception: T.any(Class, String, Exception),
126
+ string: T.untyped,
127
+ array: T.nilable(T::Array[String]),
128
+ cause: T.nilable(Exception),
129
+ bug: T.nilable(T::Boolean),
130
+ silent: T.nilable(T::Boolean),
131
+ depth: Integer,
132
+ ).returns(T.noreturn)
133
+ end
134
+ def raise(
135
+ # default arguments
136
+ exception = T.unsafe(nil), string = T.unsafe(nil), array = T.unsafe(nil), cause: $ERROR_INFO,
137
+ # new arguments
138
+ bug: nil, silent: nil, depth: 1
139
+ )
140
+ if array
141
+ T.unsafe(Kernel).raise(exception, string, array, cause: cause)
142
+ elsif string
143
+ T.unsafe(Kernel).raise(exception, string, Kernel.caller(depth), cause: cause)
144
+ elsif exception.is_a?(String)
145
+ T.unsafe(Kernel).raise(RuntimeError, exception, Kernel.caller(depth), cause: cause)
146
+ else
147
+ T.unsafe(Kernel).raise(exception, exception.message, Kernel.caller(depth), cause: cause)
148
+ end
149
+ rescue Exception => e # rubocop:disable Lint/RescueException
150
+ e.bug!(bug) unless bug.nil?
151
+ e.silent!(silent) unless silent.nil?
152
+ Kernel.raise(e, cause: cause)
153
+ end
154
+ end
59
155
  end
60
156
  end
metadata CHANGED
@@ -1,7 +1,7 @@
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.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burke Libbey
@@ -10,22 +10,22 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2021-11-10 00:00:00.000000000 Z
13
+ date: 2022-11-18 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: cli-ui
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  requirements:
19
- - - ">="
19
+ - - "~>"
20
20
  - !ruby/object:Gem::Version
21
- version: 1.1.4
21
+ version: '2.0'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
- - - ">="
26
+ - - "~>"
27
27
  - !ruby/object:Gem::Version
28
- version: 1.1.4
28
+ version: '2.0'
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: bundler
31
31
  requirement: !ruby/object:Gem::Requirement
@@ -80,9 +80,10 @@ extra_rdoc_files: []
80
80
  files:
81
81
  - ".github/CODEOWNERS"
82
82
  - ".github/dependabot.yml"
83
- - ".github/probots.yml"
83
+ - ".github/workflows/cla.yml"
84
84
  - ".github/workflows/ruby.yml"
85
85
  - ".gitignore"
86
+ - ".rubocop.sorbet.yml"
86
87
  - ".rubocop.yml"
87
88
  - Gemfile
88
89
  - Gemfile.lock
@@ -91,6 +92,8 @@ files:
91
92
  - Rakefile
92
93
  - TODO.md
93
94
  - bin/console
95
+ - bin/onchange
96
+ - bin/tapioca
94
97
  - bin/test_gen
95
98
  - bin/testunit
96
99
  - cli-kit.gemspec
@@ -106,6 +109,7 @@ files:
106
109
  - gen/lib/gen/commands/new.rb
107
110
  - gen/lib/gen/entry_point.rb
108
111
  - gen/lib/gen/generator.rb
112
+ - gen/lib/gen/help.rb
109
113
  - gen/template/.gitignore
110
114
  - gen/template/Gemfile
111
115
  - gen/template/README.md
@@ -123,17 +127,25 @@ files:
123
127
  - gen/template/test/example_test.rb
124
128
  - gen/template/test/test_helper.rb
125
129
  - lib/cli/kit.rb
126
- - lib/cli/kit/autocall.rb
130
+ - lib/cli/kit/args.rb
131
+ - lib/cli/kit/args/definition.rb
132
+ - lib/cli/kit/args/evaluation.rb
133
+ - lib/cli/kit/args/parser.rb
134
+ - lib/cli/kit/args/parser/node.rb
135
+ - lib/cli/kit/args/tokenizer.rb
127
136
  - lib/cli/kit/base_command.rb
137
+ - lib/cli/kit/command_help.rb
128
138
  - lib/cli/kit/command_registry.rb
129
139
  - lib/cli/kit/config.rb
140
+ - lib/cli/kit/core_ext.rb
130
141
  - lib/cli/kit/error_handler.rb
131
142
  - lib/cli/kit/executor.rb
132
143
  - lib/cli/kit/ini.rb
133
144
  - lib/cli/kit/levenshtein.rb
134
145
  - lib/cli/kit/logger.rb
146
+ - lib/cli/kit/opts.rb
135
147
  - lib/cli/kit/resolver.rb
136
- - lib/cli/kit/ruby_backports/enumerable.rb
148
+ - lib/cli/kit/sorbet_runtime_stub.rb
137
149
  - lib/cli/kit/support.rb
138
150
  - lib/cli/kit/support/test_helper.rb
139
151
  - lib/cli/kit/system.rb
@@ -158,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
158
170
  - !ruby/object:Gem::Version
159
171
  version: '0'
160
172
  requirements: []
161
- rubygems_version: 3.2.20
173
+ rubygems_version: 3.2.33
162
174
  signing_key:
163
175
  specification_version: 4
164
176
  summary: Terminal UI framework extensions
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