bundle_update_interactive 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +30 -0
- data/exe/bundler-ui +2 -1
- data/exe/bundler-update-interactive +2 -1
- data/lib/bundle_update_interactive/bundler_commands.rb +7 -4
- data/lib/bundle_update_interactive/changelog_locator.rb +5 -5
- data/lib/bundle_update_interactive/cli/options.rb +52 -0
- data/lib/bundle_update_interactive/cli.rb +32 -27
- data/lib/bundle_update_interactive/error.rb +6 -0
- data/lib/bundle_update_interactive/gemfile.rb +4 -0
- data/lib/bundle_update_interactive/lockfile.rb +47 -31
- data/lib/bundle_update_interactive/lockfile_entry.rb +4 -4
- data/lib/bundle_update_interactive/report.rb +6 -5
- data/lib/bundle_update_interactive/version.rb +1 -1
- data/lib/bundle_update_interactive.rb +1 -0
- metadata +5 -18
- data/lib/bundle_update_interactive/cli/thor_ext.rb +0 -76
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5eb0324c92b1a60c3611ce47993022fef9c3d69aeba735a752252cc2cffe8158
|
4
|
+
data.tar.gz: f68c74d7fc286abf743796b3323bed579736bf548ccc2a405b8a2433bf3eddf1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 874b63b22b4ff2ad9101f9a5a51b2947cf27009ff37096ae0ff3f00e3f5de5ba2ea32a4e2a2adae0d46bf4a523868deb5f8f7ec45599f988719a983e1bb9e16f
|
7
|
+
data.tar.gz: 7f134fec1b5304ed93921506a4e763a1f7ef7954f812b679cb63e271c99fc76bd291f68f1e9fd51a532a3d63a55e74ad01657767250924cd8a2a0d0d7baa69ce
|
data/README.md
CHANGED
@@ -87,6 +87,36 @@ https://github.com/rails/rails/compare/5a8d894...77dfa65
|
|
87
87
|
|
88
88
|
This feature currently works for GitHub, GitLab, and Bitbucket repos.
|
89
89
|
|
90
|
+
### Limit impact by Gemfile groups
|
91
|
+
|
92
|
+
The effects of `bundle update-interactive` can be limited to one or more Gemfile groups using the `--exclusively` option:
|
93
|
+
|
94
|
+
```sh
|
95
|
+
bundle update-interactive --exclusively=group1,group2
|
96
|
+
```
|
97
|
+
|
98
|
+
This is especially useful when you want to safely update a subset of your lock file without introducing any risk to your application in production. The best way to do this is with `--exclusively=development,test`, which can be abbreviated to simply `-D`:
|
99
|
+
|
100
|
+
```sh
|
101
|
+
# Update non-production dependencies.
|
102
|
+
# This is equivalent to `bundle update-interactive --exclusively=development,test`
|
103
|
+
bundle update-interactive -D
|
104
|
+
```
|
105
|
+
|
106
|
+
The `--exclusively` and `-D` options will cause `update-interactive` to only consider gems that are used _exclusively_ by the specified Gemfile groups. Indirect dependencies that are shared with other Gemfile groups will not be updated.
|
107
|
+
|
108
|
+
For example, given this Gemfile:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
gem "rails"
|
112
|
+
|
113
|
+
group :test do
|
114
|
+
gem "capybara"
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
If `--exclusively=test` is used, `capybara` and its indirect dependency `xpath` are both exclusively used in test and can therefore be updated. However, capybara's `nokogiri` indirect dependency, which is also used in production via `rails` → `actionpack` → `nokogiri`, would not be allowed to update.
|
119
|
+
|
90
120
|
### Conservative updates
|
91
121
|
|
92
122
|
`bundle update-interactive` updates the gems you select by running `bundle update --conservative [GEMS...]`. This means that only those specific gems will be updated. Indirect dependencies shared with other gems will not be affected.
|
data/exe/bundler-ui
CHANGED
@@ -9,10 +9,13 @@ module BundleUpdateInteractive
|
|
9
9
|
system "bundle update --conservative #{gems.flatten.map(&:shellescape).join(' ')}"
|
10
10
|
end
|
11
11
|
|
12
|
-
def read_updated_lockfile
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
def read_updated_lockfile(*gems)
|
13
|
+
command = ["bundle lock --print"]
|
14
|
+
command << "--conservative" if gems.any?
|
15
|
+
command << "--update"
|
16
|
+
command.push(*gems.flatten.map(&:shellescape))
|
17
|
+
|
18
|
+
`#{command.join(" ")}`.tap { raise "bundle lock command failed" unless Process.last_status.success? }
|
16
19
|
end
|
17
20
|
end
|
18
21
|
end
|
@@ -3,13 +3,13 @@
|
|
3
3
|
require "faraday"
|
4
4
|
require "json"
|
5
5
|
|
6
|
-
GITHUB_PATTERN = %r{^(?:https?://)?github\.com/([^/]+/[^/]+)(?:\.git)?/?}.freeze
|
7
|
-
URI_KEYS = %w[source_code_uri homepage_uri bug_tracker_uri wiki_uri].freeze
|
8
|
-
FILE_PATTERN = /changelog|changes|history|news|release/i.freeze
|
9
|
-
EXT_PATTERN = /md|txt|rdoc/i.freeze
|
10
|
-
|
11
6
|
module BundleUpdateInteractive
|
12
7
|
class ChangelogLocator
|
8
|
+
GITHUB_PATTERN = %r{^(?:https?://)?github\.com/([^/]+/[^/]+)(?:\.git)?/?}.freeze
|
9
|
+
URI_KEYS = %w[source_code_uri homepage_uri bug_tracker_uri wiki_uri].freeze
|
10
|
+
FILE_PATTERN = /changelog|changes|history|news|release/i.freeze
|
11
|
+
EXT_PATTERN = /md|txt|rdoc/i.freeze
|
12
|
+
|
13
13
|
class GitHubRepo
|
14
14
|
def self.from_uris(*uris)
|
15
15
|
uris.flatten.each do |uri|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
|
5
|
+
module BundleUpdateInteractive
|
6
|
+
class CLI::Options
|
7
|
+
class << self
|
8
|
+
def parse(argv=ARGV)
|
9
|
+
options = new
|
10
|
+
remaining = build_parser(options).parse!(argv.dup)
|
11
|
+
raise Error, "update-interactive does not accept arguments. See --help for available options." if remaining.any?
|
12
|
+
|
13
|
+
options.freeze
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def build_parser(options) # rubocop:disable Metrics/MethodLength
|
19
|
+
OptionParser.new do |parser|
|
20
|
+
parser.banner = "Usage: bundle update-interactive"
|
21
|
+
parser.on(
|
22
|
+
"--exclusively=GROUP",
|
23
|
+
"Update gems that exclusively belong to the specified Gemfile GROUP(s) (comma-separated)"
|
24
|
+
) do |value|
|
25
|
+
options.exclusively = value.split(",").map(&:strip).reject(&:empty?).map(&:to_sym)
|
26
|
+
end
|
27
|
+
parser.on(
|
28
|
+
"-D",
|
29
|
+
"Update development and test gems only; short for --exclusively=development,test"
|
30
|
+
) do
|
31
|
+
options.exclusively = %i[development test]
|
32
|
+
end
|
33
|
+
parser.on("-v", "--version", "Display bundle_update_interactive version") do
|
34
|
+
require "bundler"
|
35
|
+
puts "bundle_update_interactive/#{VERSION} bundler/#{Bundler::VERSION} #{RUBY_DESCRIPTION}"
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
parser.on("-h", "--help", "Show this help") do
|
39
|
+
puts parser
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_accessor :exclusively
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
@exclusively = []
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -1,40 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "bundler"
|
4
4
|
|
5
5
|
module BundleUpdateInteractive
|
6
|
-
class CLI
|
6
|
+
class CLI
|
7
7
|
autoload :MultiSelect, "bundle_update_interactive/cli/multi_select"
|
8
|
+
autoload :Options, "bundle_update_interactive/cli/options"
|
8
9
|
autoload :Row, "bundle_update_interactive/cli/row"
|
9
10
|
autoload :Table, "bundle_update_interactive/cli/table"
|
10
|
-
autoload :ThorExt, "bundle_update_interactive/cli/thor_ext"
|
11
11
|
|
12
|
-
|
12
|
+
def run(argv: ARGV) # rubocop:disable Metrics/AbcSize
|
13
|
+
options = Options.parse(argv)
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
report = generate_report(options)
|
16
|
+
puts("No gems to update.").then { return } if report.updateable_gems.empty?
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
desc "ui", "Update Gemfile.lock interactively", hide: true
|
23
|
-
def ui # rubocop:disable Metrics/AbcSize
|
24
|
-
report = generate_report
|
25
|
-
say("No gems to update.") && return if report.updateable_gems.empty?
|
26
|
-
|
27
|
-
say
|
28
|
-
say legend
|
29
|
-
say
|
18
|
+
puts
|
19
|
+
puts legend
|
20
|
+
puts
|
30
21
|
selected_gems = MultiSelect.prompt_for_gems_to_update(report.updateable_gems)
|
31
|
-
|
22
|
+
puts("No gems to update.").then { return } if selected_gems.empty?
|
32
23
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
24
|
+
puts "\nUpdating the following gems."
|
25
|
+
puts
|
26
|
+
puts Table.new(selected_gems).render
|
27
|
+
puts
|
37
28
|
report.bundle_update!(*selected_gems.keys)
|
29
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
30
|
+
handle_exception(e)
|
38
31
|
end
|
39
32
|
|
40
33
|
private
|
@@ -51,9 +44,9 @@ module BundleUpdateInteractive
|
|
51
44
|
LEGEND
|
52
45
|
end
|
53
46
|
|
54
|
-
def generate_report
|
47
|
+
def generate_report(options)
|
55
48
|
whisper "Resolving latest gem versions..."
|
56
|
-
report = Report.generate
|
49
|
+
report = Report.generate(groups: options.exclusively)
|
57
50
|
updateable_gems = report.updateable_gems
|
58
51
|
return report if updateable_gems.empty?
|
59
52
|
|
@@ -65,7 +58,7 @@ module BundleUpdateInteractive
|
|
65
58
|
end
|
66
59
|
|
67
60
|
def whisper(message)
|
68
|
-
$stderr.puts(message)
|
61
|
+
$stderr.puts(message)
|
69
62
|
end
|
70
63
|
|
71
64
|
def progress(message, items, &block)
|
@@ -76,5 +69,17 @@ module BundleUpdateInteractive
|
|
76
69
|
end
|
77
70
|
$stderr.print("\n")
|
78
71
|
end
|
72
|
+
|
73
|
+
def handle_exception(error)
|
74
|
+
case error
|
75
|
+
when Errno::EPIPE
|
76
|
+
# Ignore
|
77
|
+
when BundleUpdateInteractive::Error, OptionParser::ParseError, Interrupt, Bundler::Dsl::DSLError
|
78
|
+
$stderr.puts BundleUpdateInteractive.pastel.red(error.message)
|
79
|
+
exit false
|
80
|
+
else
|
81
|
+
raise
|
82
|
+
end
|
83
|
+
end
|
79
84
|
end
|
80
85
|
end
|
@@ -1,54 +1,70 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "bundler"
|
4
|
+
require "set"
|
4
5
|
|
5
6
|
module BundleUpdateInteractive
|
6
7
|
class Lockfile
|
7
|
-
|
8
|
-
def self.parse(lockfile_contents=File.read("Gemfile.lock")) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
8
|
+
def self.parse(lockfile_contents=File.read("Gemfile.lock"))
|
9
9
|
parser = Bundler::LockfileParser.new(lockfile_contents)
|
10
|
-
|
11
|
-
|
12
|
-
exact_children = {}
|
10
|
+
new(parser.specs)
|
11
|
+
end
|
13
12
|
|
14
|
-
|
15
|
-
|
13
|
+
def initialize(specs)
|
14
|
+
@specs_by_name = {}
|
15
|
+
required_exactly = Set.new
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
specs.each do |spec|
|
18
|
+
specs_by_name[spec.name] = spec
|
19
|
+
spec.dependencies.each { |dep| required_exactly << dep.name if dep.requirement.exact? }
|
20
|
+
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
end
|
22
|
+
@entries_by_name = specs_by_name.transform_values do |spec|
|
23
|
+
build_entry(spec, required_exactly.include?(spec.name))
|
23
24
|
end
|
25
|
+
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
until traversal.empty?
|
29
|
-
name = traversal.pop
|
30
|
-
next if exact_dependencies.include?(name)
|
27
|
+
def entries
|
28
|
+
entries_by_name.values
|
29
|
+
end
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
def [](gem_name)
|
32
|
+
entries_by_name[gem_name]
|
33
|
+
end
|
35
34
|
|
36
|
-
|
37
|
-
|
35
|
+
def gems_exclusively_installed_by(gemfile:, groups:)
|
36
|
+
return [] if groups.empty?
|
38
37
|
|
39
|
-
|
40
|
-
|
38
|
+
other_group_gems = gemfile.dependencies.filter_map { |gem| gem.name unless (gem.groups & groups).any? }
|
39
|
+
other_group_gems &= entries_by_name.keys
|
40
|
+
gems_installed_by_other_groups = other_group_gems + traverse_transient_dependencies(*other_group_gems)
|
41
41
|
|
42
|
-
|
43
|
-
@entries = entries.freeze
|
42
|
+
entries_by_name.keys - gems_installed_by_other_groups
|
44
43
|
end
|
45
44
|
|
46
|
-
|
47
|
-
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_reader :entries_by_name, :specs_by_name
|
48
|
+
|
49
|
+
def build_entry(spec, exact)
|
50
|
+
exact_dependencies = traverse_transient_dependencies(spec.name) { |dep| dep.requirement.exact? }
|
51
|
+
LockfileEntry.new(spec, exact_dependencies, exact)
|
48
52
|
end
|
49
53
|
|
50
|
-
def
|
51
|
-
|
54
|
+
def traverse_transient_dependencies(*gem_names) # rubocop:disable Metrics/AbcSize
|
55
|
+
traversal = Set.new
|
56
|
+
stack = gem_names.flatten
|
57
|
+
until stack.empty?
|
58
|
+
specs_by_name[stack.pop].dependencies.each do |dep|
|
59
|
+
next if traversal.include?(dep.name)
|
60
|
+
next unless specs_by_name.key?(dep.name)
|
61
|
+
next if block_given? && !yield(dep)
|
62
|
+
|
63
|
+
traversal << dep.name
|
64
|
+
stack << dep.name
|
65
|
+
end
|
66
|
+
end
|
67
|
+
traversal.to_a
|
52
68
|
end
|
53
69
|
end
|
54
70
|
end
|
@@ -4,10 +4,10 @@ module BundleUpdateInteractive
|
|
4
4
|
class LockfileEntry
|
5
5
|
attr_reader :spec, :exact_dependencies
|
6
6
|
|
7
|
-
def initialize(spec, exact_dependencies,
|
7
|
+
def initialize(spec, exact_dependencies, exact_requirement)
|
8
8
|
@spec = spec
|
9
9
|
@exact_dependencies = exact_dependencies
|
10
|
-
@
|
10
|
+
@exact_requirement = exact_requirement
|
11
11
|
end
|
12
12
|
|
13
13
|
def name
|
@@ -28,8 +28,8 @@ module BundleUpdateInteractive
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
32
|
-
@
|
31
|
+
def exact_requirement?
|
32
|
+
@exact_requirement
|
33
33
|
end
|
34
34
|
|
35
35
|
def git_version
|
@@ -8,11 +8,12 @@ require "set"
|
|
8
8
|
module BundleUpdateInteractive
|
9
9
|
class Report
|
10
10
|
class << self
|
11
|
-
def generate
|
11
|
+
def generate(groups: [])
|
12
12
|
gemfile = Gemfile.parse
|
13
13
|
current_lockfile = Lockfile.parse
|
14
|
-
|
14
|
+
gems = groups.any? ? current_lockfile.gems_exclusively_installed_by(gemfile: gemfile, groups: groups) : nil
|
15
15
|
|
16
|
+
updated_lockfile = gems&.none? ? nil : Lockfile.parse(BundlerCommands.read_updated_lockfile(*Array(gems)))
|
16
17
|
new(gemfile: gemfile, current_lockfile: current_lockfile, updated_lockfile: updated_lockfile)
|
17
18
|
end
|
18
19
|
end
|
@@ -21,9 +22,9 @@ module BundleUpdateInteractive
|
|
21
22
|
|
22
23
|
def initialize(gemfile:, current_lockfile:, updated_lockfile:)
|
23
24
|
@current_lockfile = current_lockfile
|
24
|
-
@outdated_gems
|
25
|
+
@outdated_gems = current_lockfile.entries.each_with_object({}) do |current_lockfile_entry, hash|
|
25
26
|
name = current_lockfile_entry.name
|
26
|
-
updated_lockfile_entry = updated_lockfile[name]
|
27
|
+
updated_lockfile_entry = updated_lockfile && updated_lockfile[name]
|
27
28
|
next unless current_lockfile_entry.older_than?(updated_lockfile_entry)
|
28
29
|
|
29
30
|
hash[name] = build_outdated_gem(current_lockfile_entry, updated_lockfile_entry, gemfile[name]&.groups)
|
@@ -36,7 +37,7 @@ module BundleUpdateInteractive
|
|
36
37
|
|
37
38
|
def updateable_gems
|
38
39
|
@updateable_gems ||= outdated_gems.reject do |name, _|
|
39
|
-
current_lockfile[name].
|
40
|
+
current_lockfile[name].exact_requirement?
|
40
41
|
end.freeze
|
41
42
|
end
|
42
43
|
|
@@ -6,6 +6,7 @@ module BundleUpdateInteractive
|
|
6
6
|
autoload :BundlerCommands, "bundle_update_interactive/bundler_commands"
|
7
7
|
autoload :ChangelogLocator, "bundle_update_interactive/changelog_locator"
|
8
8
|
autoload :CLI, "bundle_update_interactive/cli"
|
9
|
+
autoload :Error, "bundle_update_interactive/error"
|
9
10
|
autoload :Gemfile, "bundle_update_interactive/gemfile"
|
10
11
|
autoload :Lockfile, "bundle_update_interactive/lockfile"
|
11
12
|
autoload :LockfileEntry, "bundle_update_interactive/lockfile_entry"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bundle_update_interactive
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Brictson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-07-
|
11
|
+
date: 2024-07-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: 0.8.0
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: thor
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '1.2'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '1.2'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: tty-prompt
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -126,9 +112,10 @@ files:
|
|
126
112
|
- lib/bundle_update_interactive/changelog_locator.rb
|
127
113
|
- lib/bundle_update_interactive/cli.rb
|
128
114
|
- lib/bundle_update_interactive/cli/multi_select.rb
|
115
|
+
- lib/bundle_update_interactive/cli/options.rb
|
129
116
|
- lib/bundle_update_interactive/cli/row.rb
|
130
117
|
- lib/bundle_update_interactive/cli/table.rb
|
131
|
-
- lib/bundle_update_interactive/
|
118
|
+
- lib/bundle_update_interactive/error.rb
|
132
119
|
- lib/bundle_update_interactive/gemfile.rb
|
133
120
|
- lib/bundle_update_interactive/lockfile.rb
|
134
121
|
- lib/bundle_update_interactive/lockfile_entry.rb
|
@@ -160,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
160
147
|
- !ruby/object:Gem::Version
|
161
148
|
version: '0'
|
162
149
|
requirements: []
|
163
|
-
rubygems_version: 3.5.
|
150
|
+
rubygems_version: 3.5.16
|
164
151
|
signing_key:
|
165
152
|
specification_version: 4
|
166
153
|
summary: Adds an update-interactive command to Bundler
|
@@ -1,76 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "bundler"
|
4
|
-
|
5
|
-
class BundleUpdateInteractive::CLI
|
6
|
-
module ThorExt
|
7
|
-
# Configures Thor to behave more like a typical CLI, with better help and error handling.
|
8
|
-
#
|
9
|
-
# - Passing -h or --help to a command will show help for that command.
|
10
|
-
# - Unrecognized options will be treated as errors (instead of being silently ignored).
|
11
|
-
# - Error messages will be printed in red to stderr, without stack trace.
|
12
|
-
# - Full stack traces can be enabled by setting the VERBOSE environment variable.
|
13
|
-
# - Errors will cause Thor to exit with a non-zero status.
|
14
|
-
#
|
15
|
-
# To take advantage of this behavior, your CLI should subclass Thor and extend this module.
|
16
|
-
#
|
17
|
-
# class CLI < Thor
|
18
|
-
# extend ThorExt::Start
|
19
|
-
# end
|
20
|
-
#
|
21
|
-
# Start your CLI with:
|
22
|
-
#
|
23
|
-
# CLI.start
|
24
|
-
#
|
25
|
-
# In tests, prevent Kernel.exit from being called when an error occurs, like this:
|
26
|
-
#
|
27
|
-
# CLI.start(args, exit_on_failure: false)
|
28
|
-
#
|
29
|
-
module Start
|
30
|
-
def self.extended(base)
|
31
|
-
super
|
32
|
-
base.check_unknown_options!
|
33
|
-
end
|
34
|
-
|
35
|
-
def start(given_args=ARGV, config={})
|
36
|
-
config[:shell] ||= Thor::Base.shell.new
|
37
|
-
handle_help_switches(given_args) do |args|
|
38
|
-
dispatch(nil, args, nil, config)
|
39
|
-
end
|
40
|
-
rescue Exception => e # rubocop:disable Lint/RescueException
|
41
|
-
handle_exception_on_start(e, config)
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def handle_help_switches(given_args)
|
47
|
-
yield(given_args.dup)
|
48
|
-
rescue Thor::UnknownArgumentError => e
|
49
|
-
retry_with_args = []
|
50
|
-
|
51
|
-
if given_args.first == "help"
|
52
|
-
retry_with_args = ["help"] if given_args.length > 1
|
53
|
-
elsif (e.unknown & %w[-h --help]).any?
|
54
|
-
retry_with_args = ["help", (given_args - e.unknown).first]
|
55
|
-
end
|
56
|
-
raise unless retry_with_args.any?
|
57
|
-
|
58
|
-
yield(retry_with_args)
|
59
|
-
end
|
60
|
-
|
61
|
-
def handle_exception_on_start(error, config)
|
62
|
-
case error
|
63
|
-
when Errno::EPIPE
|
64
|
-
# Ignore
|
65
|
-
when Thor::Error, Interrupt, Bundler::Dsl::DSLError
|
66
|
-
raise unless config.fetch(:exit_on_failure, true)
|
67
|
-
|
68
|
-
config[:shell]&.say_error(error.message, :red)
|
69
|
-
exit(false)
|
70
|
-
else
|
71
|
-
raise
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|