From 1f7978378e725b0b603609d8a2e98c7219c9a737 Mon Sep 17 00:00:00 2001 From: Yoshiki Takagi Date: Mon, 10 Jan 2022 22:59:55 +0900 Subject: [PATCH 01/29] Fix CI setting for Ruby 3.0 --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 783a7909..b0e5dd92 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,7 +5,8 @@ jobs: strategy: fail-fast: false matrix: - ruby: [2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 3.0, 3.1, head] + # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0' + ruby: [2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, '3.0', 3.1, head] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From eea09554b9eb2c1f8563f9f0c4ed2e1e32e9a976 Mon Sep 17 00:00:00 2001 From: Tim Diggins Date: Fri, 4 Mar 2022 11:25:44 +0000 Subject: [PATCH 02/29] fix options spec. --- spec/parser/options_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/parser/options_spec.rb b/spec/parser/options_spec.rb index b1e50fbf..5caf9f67 100644 --- a/spec/parser/options_spec.rb +++ b/spec/parser/options_spec.rb @@ -116,7 +116,9 @@ def remaining expected = "Unknown switches \"--baz\"" expected << "\nDid you mean? \"--bar\"" if Thor::Correctable - expect { check_unknown! }.to raise_error(Thor::UnknownArgumentError, expected) + expect { check_unknown! }.to raise_error(Thor::UnknownArgumentError) do |error| + expect(error.to_s).to eq(expected) + end end it "skips leading non-switches" do From 0c01a189c9b612eb0c02e8c8ce7c7a7cb77ad9c2 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 3 May 2022 15:34:13 -0700 Subject: [PATCH 03/29] Switch hash from MD5 to SHA256 On FIPS-compliant systems (http://en.wikipedia.org/wiki/FIPS_140), MD5 cannot be used. Switch to SHA256 instead. However, this change does not keep backward compatibility with systems with already-installed Thor recipes. Closes #287 --- lib/thor/runner.rb | 4 ++-- spec/runner_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/thor/runner.rb b/lib/thor/runner.rb index 7b1b8d9b..8d076759 100644 --- a/lib/thor/runner.rb +++ b/lib/thor/runner.rb @@ -2,7 +2,7 @@ require_relative "group" require "yaml" -require "digest/md5" +require "digest/sha2" require "pathname" class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength @@ -91,7 +91,7 @@ def install(name) # rubocop:disable MethodLength end thor_yaml[as] = { - :filename => Digest::MD5.hexdigest(name + as), + :filename => Digest::SHA256.hexdigest(name + as), :location => location, :namespaces => Thor::Util.namespaces_in_content(contents, base) } diff --git a/spec/runner_spec.rb b/spec/runner_spec.rb index 6a8cc67f..52748fcc 100644 --- a/spec/runner_spec.rb +++ b/spec/runner_spec.rb @@ -219,7 +219,7 @@ def when_no_thorfiles_exist allow(FileUtils).to receive(:touch) allow(Thor::LineEditor).to receive(:readline).and_return("Y") - path = File.join(Thor::Util.thor_root, Digest::MD5.hexdigest(@location + "random")) + path = File.join(Thor::Util.thor_root, Digest::SHA256.hexdigest(@location + "random")) expect(File).to receive(:open).with(path, "w") end From 0def4cfba5bf470f76877eb3b8a8895f0018e574 Mon Sep 17 00:00:00 2001 From: Tim Diggins Date: Fri, 4 Mar 2022 12:16:58 +0000 Subject: [PATCH 04/29] fix expectations for ruby 3 treatment of hash arg --- spec/line_editor_spec.rb | 4 ++-- spec/shell/basic_spec.rb | 34 +++++++++++++++++----------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/spec/line_editor_spec.rb b/spec/line_editor_spec.rb index 575fd336..f034ec8d 100644 --- a/spec/line_editor_spec.rb +++ b/spec/line_editor_spec.rb @@ -13,7 +13,7 @@ describe ".readline" do it "uses the Readline line editor" do editor = double("Readline") - expect(Thor::LineEditor::Readline).to receive(:new).with("Enter your name ", :default => "Brian").and_return(editor) + expect(Thor::LineEditor::Readline).to receive(:new).with("Enter your name ", {:default => "Brian"}).and_return(editor) expect(editor).to receive(:readline).and_return("George") expect(Thor::LineEditor.readline("Enter your name ", :default => "Brian")).to eq("George") end @@ -35,7 +35,7 @@ describe ".readline" do it "uses the Basic line editor" do editor = double("Basic") - expect(Thor::LineEditor::Basic).to receive(:new).with("Enter your name ", :default => "Brian").and_return(editor) + expect(Thor::LineEditor::Basic).to receive(:new).with("Enter your name ", {:default => "Brian"}).and_return(editor) expect(editor).to receive(:readline).and_return("George") expect(Thor::LineEditor.readline("Enter your name ", :default => "Brian")).to eq("George") end diff --git a/spec/shell/basic_spec.rb b/spec/shell/basic_spec.rb index b51c5e8a..b795a80a 100644 --- a/spec/shell/basic_spec.rb +++ b/spec/shell/basic_spec.rb @@ -70,80 +70,80 @@ def shell it "prints a message to the user with the available options, expects case-sensitive matching, and determines the correctness of the answer" do flavors = %w(strawberry chocolate vanilla) - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', :limited_to => flavors).and_return("chocolate") + expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', {:limited_to => flavors}).and_return("chocolate") expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors)).to eq("chocolate") end it "prints a message to the user with the available options, expects case-sensitive matching, and reasks the question after an incorrect response" do flavors = %w(strawberry chocolate vanilla) expect($stdout).to receive(:print).with("Your response must be one of: [strawberry, chocolate, vanilla]. Please try again.\n") - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', :limited_to => flavors).and_return("moose tracks", "chocolate") + expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', {:limited_to => flavors}).and_return("moose tracks", "chocolate") expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors)).to eq("chocolate") end it "prints a message to the user with the available options, expects case-sensitive matching, and reasks the question after a case-insensitive match" do flavors = %w(strawberry chocolate vanilla) expect($stdout).to receive(:print).with("Your response must be one of: [strawberry, chocolate, vanilla]. Please try again.\n") - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', :limited_to => flavors).and_return("cHoCoLaTe", "chocolate") + expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', {:limited_to => flavors}).and_return("cHoCoLaTe", "chocolate") expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors)).to eq("chocolate") end it "prints a message to the user with the available options, expects case-insensitive matching, and determines the correctness of the answer" do flavors = %w(strawberry chocolate vanilla) - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', :limited_to => flavors, :case_insensitive => true).and_return("CHOCOLATE") + expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', {:limited_to => flavors, :case_insensitive => true}).and_return("CHOCOLATE") expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors, :case_insensitive => true)).to eq("chocolate") end it "prints a message to the user with the available options, expects case-insensitive matching, and reasks the question after an incorrect response" do flavors = %w(strawberry chocolate vanilla) expect($stdout).to receive(:print).with("Your response must be one of: [strawberry, chocolate, vanilla]. Please try again.\n") - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', :limited_to => flavors, :case_insensitive => true).and_return("moose tracks", "chocolate") + expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', {:limited_to => flavors, :case_insensitive => true}).and_return("moose tracks", "chocolate") expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors, :case_insensitive => true)).to eq("chocolate") end it "prints a message to the user containing a default and sets the default if only enter is pressed" do - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? (vanilla) ', :default => "vanilla").and_return("") + expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? (vanilla) ', {:default => "vanilla"}).and_return("") expect(shell.ask('What\'s your favorite Neopolitan flavor?', :default => "vanilla")).to eq("vanilla") end it "prints a message to the user with the available options and reasks the question after an incorrect response and then returns the default" do flavors = %w(strawberry chocolate vanilla) expect($stdout).to receive(:print).with("Your response must be one of: [strawberry, chocolate, vanilla]. Please try again.\n") - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] (vanilla) ', :default => "vanilla", :limited_to => flavors).and_return("moose tracks", "") + expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] (vanilla) ', {:default => "vanilla", :limited_to => flavors}).and_return("moose tracks", "") expect(shell.ask("What's your favorite Neopolitan flavor?", :default => "vanilla", :limited_to => flavors)).to eq("vanilla") end end describe "#yes?" do it "asks the user and returns true if the user replies yes" do - expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", :add_to_history => false).and_return("y") + expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", {:add_to_history => false}).and_return("y") expect(shell.yes?("Should I overwrite it?")).to be true end it "asks the user and returns false if the user replies no" do - expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", :add_to_history => false).and_return("n") + expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", {:add_to_history => false}).and_return("n") expect(shell.yes?("Should I overwrite it?")).not_to be true end it "asks the user and returns false if the user replies with an answer other than yes or no" do - expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", :add_to_history => false).and_return("foobar") + expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", {:add_to_history => false}).and_return("foobar") expect(shell.yes?("Should I overwrite it?")).to be false end end describe "#no?" do it "asks the user and returns true if the user replies no" do - expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", :add_to_history => false).and_return("n") + expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", {:add_to_history => false}).and_return("n") expect(shell.no?("Should I overwrite it?")).to be true end it "asks the user and returns false if the user replies yes" do - expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", :add_to_history => false).and_return("Yes") + expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", {:add_to_history => false}).and_return("Yes") expect(shell.no?("Should I overwrite it?")).to be false end it "asks the user and returns false if the user replies with an answer other than yes or no" do - expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", :add_to_history => false).and_return("foobar") + expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", {:add_to_history => false}).and_return("foobar") expect(shell.no?("Should I overwrite it?")).to be false end end @@ -431,13 +431,13 @@ def #456 Lanç... expect(content).to eq(<<-TABLE) Name Number Color Erik 1234567890123 green -TABLE + TABLE end end describe "#file_collision" do it "shows a menu with options" do - expect(Thor::LineEditor).to receive(:readline).with('Overwrite foo? (enter "h" for help) [Ynaqh] ', :add_to_history => false).and_return("n") + expect(Thor::LineEditor).to receive(:readline).with('Overwrite foo? (enter "h" for help) [Ynaqh] ', {:add_to_history => false}).and_return("n") shell.file_collision("foo") end @@ -478,7 +478,7 @@ def #456 Lanç... end it "always returns true if the user chooses always" do - expect(Thor::LineEditor).to receive(:readline).with('Overwrite foo? (enter "h" for help) [Ynaqh] ', :add_to_history => false).and_return("a") + expect(Thor::LineEditor).to receive(:readline).with('Overwrite foo? (enter "h" for help) [Ynaqh] ', {:add_to_history => false}).and_return("a") expect(shell.file_collision("foo")).to be true @@ -488,7 +488,7 @@ def #456 Lanç... describe "when a block is given" do it "displays diff and merge options to the user" do - expect(Thor::LineEditor).to receive(:readline).with('Overwrite foo? (enter "h" for help) [Ynaqdhm] ', :add_to_history => false).and_return("s") + expect(Thor::LineEditor).to receive(:readline).with('Overwrite foo? (enter "h" for help) [Ynaqdhm] ', {:add_to_history => false}).and_return("s") shell.file_collision("foo") {} end From 4ee68cbdcc2bb823b3f66ac1a546b9d560766c4a Mon Sep 17 00:00:00 2001 From: Tim Diggins Date: Fri, 4 Mar 2022 12:28:18 +0000 Subject: [PATCH 05/29] try coveralls_reborn to fix ssl errors. Note that we could also use the coveralls action as recommended in https://github.com/tagliala/coveralls-ruby-reborn but it seems like a github token is needed, which makes it more complex for contributors This does mean dropping coveralls for EOLed rubies but do we really need to post to coveralls on each test run? Wouldn't one test run be enough? --- Gemfile | 2 +- spec/helper.rb | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index bc5f7c53..ce880f0d 100644 --- a/Gemfile +++ b/Gemfile @@ -9,7 +9,7 @@ end group :test do gem "childlabor" - gem "coveralls", ">= 0.8.19" + gem 'coveralls_reborn', '~> 0.23.1', require: false if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0") gem "rspec", ">= 3.2" gem "rspec-mocks", ">= 3" gem "rubocop", "~> 0.50.0" diff --git a/spec/helper.rb b/spec/helper.rb index c19dd6f2..d3df9a9b 100644 --- a/spec/helper.rb +++ b/spec/helper.rb @@ -1,13 +1,15 @@ $TESTING = true -require "simplecov" -require "coveralls" +if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0") + require "simplecov" + require "coveralls" -SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter] + SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter] -SimpleCov.start do - add_filter "/spec" - minimum_coverage(90) + SimpleCov.start do + add_filter "/spec" + minimum_coverage(90) + end end $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib")) From 46d1422902e1c66b31fae79be7dca79ff8b2e81b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 15 Jun 2022 19:35:27 +0200 Subject: [PATCH 06/29] Reimplement did_you_mean suggestions to keep behaviour accross rubies Ruby 3.2 will introduce `Exception#detailed_message` and `did_you_mean` has been already updated in Ruby 3.2 to use that. The new behaviour means not changing the original `Exception#message`. That means it is hard to get the previous error output, because `Exception#detailed_message` includes not only `did_you_mean` decorations, but also additional information like the exception class. To fix this, I bring the old did_you_mean behavior into Thor, so that the above changes do not affect us. --- lib/thor/error.rb | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/thor/error.rb b/lib/thor/error.rb index 893b135e..cc3dfe41 100644 --- a/lib/thor/error.rb +++ b/lib/thor/error.rb @@ -11,7 +11,15 @@ def initialize(dictionary) end end - DidYouMean::Correctable + Module.new do + def to_s + super + DidYouMean.formatter.message_for(corrections) + end + + def corrections + @corrections ||= self.class.const_get(:SpellChecker).new(self).corrections + end + end end # Thor::Error is raised when it's caused by wrong usage of thor classes. Those @@ -100,16 +108,4 @@ class RequiredArgumentMissingError < InvocationError class MalformattedArgumentError < InvocationError end - - if Correctable - if DidYouMean.respond_to?(:correct_error) - DidYouMean.correct_error(Thor::UndefinedCommandError, UndefinedCommandError::SpellChecker) - DidYouMean.correct_error(Thor::UnknownArgumentError, UnknownArgumentError::SpellChecker) - else - DidYouMean::SPELL_CHECKERS.merge!( - 'Thor::UndefinedCommandError' => UndefinedCommandError::SpellChecker, - 'Thor::UnknownArgumentError' => UnknownArgumentError::SpellChecker - ) - end - end end From 130dc74b7a219bfc20e5cdc3961ba8badd88fbec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 16 Jun 2022 12:27:53 +0200 Subject: [PATCH 07/29] Fix running readline specs in isolation --- spec/line_editor_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/line_editor_spec.rb b/spec/line_editor_spec.rb index f034ec8d..4dec34fd 100644 --- a/spec/line_editor_spec.rb +++ b/spec/line_editor_spec.rb @@ -1,4 +1,5 @@ require "helper" +require "readline" describe Thor::LineEditor, "on a system with Readline support" do before do From de85c51d0959d6021cb3b4fa3424c234007f8587 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 16 Jun 2022 12:28:23 +0200 Subject: [PATCH 08/29] Cleanup unnecessary setup from readline specs --- spec/line_editor_spec.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/spec/line_editor_spec.rb b/spec/line_editor_spec.rb index 4dec34fd..9c6250e7 100644 --- a/spec/line_editor_spec.rb +++ b/spec/line_editor_spec.rb @@ -3,7 +3,7 @@ describe Thor::LineEditor, "on a system with Readline support" do before do - @original_readline = ::Readline if defined? ::Readline + @original_readline = ::Readline silence_warnings { ::Readline = double("Readline") } end @@ -23,10 +23,8 @@ describe Thor::LineEditor, "on a system without Readline support" do before do - if defined? ::Readline - @original_readline = ::Readline - Object.send(:remove_const, :Readline) - end + @original_readline = ::Readline + Object.send(:remove_const, :Readline) end after do From 2edc9d5f97d495755a596ef3c49a11d0d64a6688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 16 Jun 2022 12:31:14 +0200 Subject: [PATCH 09/29] No need to silence warnings in readline specs --- spec/line_editor_spec.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/line_editor_spec.rb b/spec/line_editor_spec.rb index 9c6250e7..5e91d092 100644 --- a/spec/line_editor_spec.rb +++ b/spec/line_editor_spec.rb @@ -4,11 +4,13 @@ describe Thor::LineEditor, "on a system with Readline support" do before do @original_readline = ::Readline - silence_warnings { ::Readline = double("Readline") } + Object.send(:remove_const, :Readline) + ::Readline = double("Readline") end after do - silence_warnings { ::Readline = @original_readline } + Object.send(:remove_const, :Readline) + ::Readline = @original_readline end describe ".readline" do @@ -28,7 +30,7 @@ end after do - silence_warnings { ::Readline = @original_readline } + ::Readline = @original_readline end describe ".readline" do From 39d16cb2a0bc9e0ccbdd537f93457e5c59d176bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 16 Jun 2022 12:22:00 +0200 Subject: [PATCH 10/29] Upgrade RuboCop, fix config and offenses, and run it in CI --- .github/workflows/lint.yml | 14 ++++++++++ .gitignore | 1 + .rubocop.yml | 36 +++++++++++++++----------- .rubocop_todo.yml | 8 ------ Gemfile | 3 +-- lib/thor.rb | 2 +- lib/thor/base.rb | 2 +- lib/thor/group.rb | 2 +- lib/thor/parser/arguments.rb | 2 +- lib/thor/parser/option.rb | 2 +- lib/thor/parser/options.rb | 8 +++--- lib/thor/rake_compat.rb | 4 +-- lib/thor/runner.rb | 4 +-- lib/thor/shell/basic.rb | 2 +- lib/thor/util.rb | 6 ++--- lint_gems.rb | 3 +++ spec/actions/file_manipulation_spec.rb | 4 +-- spec/shell/basic_spec.rb | 34 ++++++++++++------------ 18 files changed, 76 insertions(+), 61 deletions(-) create mode 100644 .github/workflows/lint.yml create mode 100644 lint_gems.rb diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..a026879d --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,14 @@ +name: Run linters +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-latest + env: + BUNDLE_GEMFILE: lint_gems.rb + steps: + - uses: actions/checkout@v2 + - uses: ruby/setup-ruby@v1 + with: + ruby-version: 3.1 + bundler-cache: true # runs 'bundle install' and caches installed gems automatically + - run: bundle exec rubocop diff --git a/.gitignore b/.gitignore index 2271be59..1e037f79 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ .yardoc Desktop.ini Gemfile.lock +lint_gems.rb.lock Icon? InstalledFiles Session.vim diff --git a/.rubocop.yml b/.rubocop.yml index 9bd4573b..545c5587 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -6,32 +6,38 @@ AllCops: Exclude: - spec/sandbox/**/* - spec/fixtures/**/* + - vendor/bundle/**/** # Enforce Ruby 1.8-compatible hash syntax -HashSyntax: +Style/HashSyntax: EnforcedStyle: hash_rockets # No spaces inside hash literals -SpaceInsideHashLiteralBraces: +Layout/SpaceInsideHashLiteralBraces: EnforcedStyle: no_space # Enforce outdenting of access modifiers (i.e. public, private, protected) -AccessModifierIndentation: +Layout/AccessModifierIndentation: EnforcedStyle: outdent -EmptyLinesAroundAccessModifier: +Layout/EmptyLinesAroundAccessModifier: Enabled: true # Align ends correctly -EndAlignment: +Layout/EndAlignment: EnforcedStyleAlignWith: variable + Exclude: + - 'lib/thor/actions.rb' + - 'lib/thor/error.rb' + - 'lib/thor/shell/basic.rb' + - 'lib/thor/parser/option.rb' # Indentation of when/else -CaseIndentation: +Layout/CaseIndentation: EnforcedStyle: end IndentOneStep: false -StringLiterals: +Style/StringLiterals: EnforcedStyle: double_quotes Lint/AssignmentInCondition: @@ -39,16 +45,11 @@ Lint/AssignmentInCondition: - 'lib/thor/line_editor/readline.rb' - 'lib/thor/parser/arguments.rb' -Lint/EndAlignment: - Exclude: - - 'lib/thor/actions.rb' - - 'lib/thor/parser/option.rb' - Security/Eval: Exclude: - 'spec/helper.rb' -Lint/HandleExceptions: +Lint/SuppressedException: Exclude: - 'lib/thor/line_editor/readline.rb' @@ -99,10 +100,15 @@ Style/GlobalVars: - 'spec/register_spec.rb' - 'spec/thor_spec.rb' -Layout/IndentArray: +Layout/FirstArrayElementIndentation: EnforcedStyle: consistent -Style/MethodMissing: +Lint/MissingSuper: + Exclude: + - 'lib/thor/error.rb' + - 'spec/rake_compat_spec.rb' + +Style/MissingRespondToMissing: Exclude: - 'lib/thor/core_ext/hash_with_indifferent_access.rb' - 'lib/thor/runner.rb' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 07b30897..c538d929 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -25,14 +25,6 @@ Layout/SpaceInsideHashLiteralBraces: - 'spec/actions_spec.rb' - 'spec/command_spec.rb' -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleAlignWith, SupportedStylesAlignWith, AutoCorrect. -# SupportedStylesAlignWith: keyword, variable, start_of_line -Lint/EndAlignment: - Exclude: - - 'lib/thor/error.rb' - # Offense count: 65 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. diff --git a/Gemfile b/Gemfile index ce880f0d..86413b01 100644 --- a/Gemfile +++ b/Gemfile @@ -9,10 +9,9 @@ end group :test do gem "childlabor" - gem 'coveralls_reborn', '~> 0.23.1', require: false if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0") + gem 'coveralls_reborn', '~> 0.23.1', :require => false if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.6.0") gem "rspec", ">= 3.2" gem "rspec-mocks", ">= 3" - gem "rubocop", "~> 0.50.0" gem "simplecov", ">= 0.13" gem "webmock" end diff --git a/lib/thor.rb b/lib/thor.rb index c84de308..e9001962 100644 --- a/lib/thor.rb +++ b/lib/thor.rb @@ -356,7 +356,7 @@ def disable_required_check #:nodoc: end # The method responsible for dispatching given the args. - def dispatch(meth, given_args, given_opts, config) #:nodoc: # rubocop:disable MethodLength + def dispatch(meth, given_args, given_opts, config) #:nodoc: meth ||= retrieve_command_name(given_args) command = all_commands[normalize_command_name(meth)] diff --git a/lib/thor/base.rb b/lib/thor/base.rb index 657880cc..39551e02 100644 --- a/lib/thor/base.rb +++ b/lib/thor/base.rb @@ -610,7 +610,7 @@ def build_options(options, scope) #:nodoc: def find_and_refresh_command(name) #:nodoc: if commands[name.to_s] commands[name.to_s] - elsif command = all_commands[name.to_s] # rubocop:disable AssignmentInCondition + elsif command = all_commands[name.to_s] # rubocop:disable Lint/AssignmentInCondition commands[name.to_s] = command.clone else raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found." diff --git a/lib/thor/group.rb b/lib/thor/group.rb index d36e57c8..263ccfe6 100644 --- a/lib/thor/group.rb +++ b/lib/thor/group.rb @@ -169,7 +169,7 @@ def class_options_help(shell, groups = {}) #:nodoc: # options are added to group_options hash. Options that already exists # in base_options are not added twice. # - def get_options_from_invocations(group_options, base_options) #:nodoc: # rubocop:disable MethodLength + def get_options_from_invocations(group_options, base_options) #:nodoc: invocations.each do |name, from_option| value = if from_option option = class_options[name] diff --git a/lib/thor/parser/arguments.rb b/lib/thor/parser/arguments.rb index 05c1a659..30c350d0 100644 --- a/lib/thor/parser/arguments.rb +++ b/lib/thor/parser/arguments.rb @@ -1,5 +1,5 @@ class Thor - class Arguments #:nodoc: # rubocop:disable ClassLength + class Arguments #:nodoc: NUMERIC = /[-+]?(\d*\.\d+|\d+)/ # Receives an array of args and returns two arrays, one with arguments diff --git a/lib/thor/parser/option.rb b/lib/thor/parser/option.rb index 6a6761b6..a7647bbe 100644 --- a/lib/thor/parser/option.rb +++ b/lib/thor/parser/option.rb @@ -58,7 +58,7 @@ def self.parse(key, value) default = nil if VALID_TYPES.include?(value) value - elsif required = (value == :required) # rubocop:disable AssignmentInCondition + elsif required = (value == :required) # rubocop:disable Lint/AssignmentInCondition :string end when TrueClass, FalseClass diff --git a/lib/thor/parser/options.rb b/lib/thor/parser/options.rb index 8c1bbe7f..2d6145bb 100644 --- a/lib/thor/parser/options.rb +++ b/lib/thor/parser/options.rb @@ -1,5 +1,5 @@ class Thor - class Options < Arguments #:nodoc: # rubocop:disable ClassLength + class Options < Arguments #:nodoc: LONG_RE = /^(--\w+(?:-\w+)*)$/ SHORT_RE = /^(-[a-z])$/i EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i @@ -85,7 +85,7 @@ def unshift(arg, is_value: false) super(arg) end - def parse(args) # rubocop:disable MethodLength + def parse(args) # rubocop:disable Metrics/MethodLength @pile = args.dup @is_treated_as_value = false @parsing_options = true @@ -101,7 +101,7 @@ def parse(args) # rubocop:disable MethodLength unshift($1.split("").map { |f| "-#{f}" }) next when EQ_RE - unshift($2, is_value: true) + unshift($2, :is_value => true) switch = $1 when SHORT_NUM unshift($2) @@ -194,7 +194,7 @@ def switch?(arg) end def switch_option(arg) - if match = no_or_skip?(arg) # rubocop:disable AssignmentInCondition + if match = no_or_skip?(arg) # rubocop:disable Lint/AssignmentInCondition @switches[arg] || @switches["--#{match}"] else @switches[arg] diff --git a/lib/thor/rake_compat.rb b/lib/thor/rake_compat.rb index 36e1edf3..f458b2d8 100644 --- a/lib/thor/rake_compat.rb +++ b/lib/thor/rake_compat.rb @@ -41,7 +41,7 @@ def self.included(base) def task(*) task = super - if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition + if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable Lint/AssignmentInCondition non_namespaced_name = task.name.split(":").last description = non_namespaced_name @@ -59,7 +59,7 @@ def task(*) end def namespace(name) - if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition + if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable Lint/AssignmentInCondition const_name = Thor::Util.camel_case(name.to_s).to_sym klass.const_set(const_name, Class.new(Thor)) new_klass = klass.const_get(const_name) diff --git a/lib/thor/runner.rb b/lib/thor/runner.rb index 7b1b8d9b..70c0d618 100644 --- a/lib/thor/runner.rb +++ b/lib/thor/runner.rb @@ -5,7 +5,7 @@ require "digest/md5" require "pathname" -class Thor::Runner < Thor #:nodoc: # rubocop:disable ClassLength +class Thor::Runner < Thor #:nodoc: autoload :OpenURI, "open-uri" map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version @@ -45,7 +45,7 @@ def method_missing(meth, *args) desc "install NAME", "Install an optionally named Thor file into your system commands" method_options :as => :string, :relative => :boolean, :force => :boolean - def install(name) # rubocop:disable MethodLength + def install(name) # rubocop:disable Metrics/MethodLength initialize_thorfiles # If a directory name is provided as the argument, look for a 'main.thor' diff --git a/lib/thor/shell/basic.rb b/lib/thor/shell/basic.rb index a490de44..ce673a2e 100644 --- a/lib/thor/shell/basic.rb +++ b/lib/thor/shell/basic.rb @@ -182,7 +182,7 @@ def print_in_columns(array) # indent:: Indent the first column by indent value. # colwidth:: Force the first column to colwidth spaces wide. # - def print_table(array, options = {}) # rubocop:disable MethodLength + def print_table(array, options = {}) # rubocop:disable Metrics/MethodLength return if array.empty? formats = [] diff --git a/lib/thor/util.rb b/lib/thor/util.rb index 628c6048..5f1c4842 100644 --- a/lib/thor/util.rb +++ b/lib/thor/util.rb @@ -90,7 +90,7 @@ def thor_classes_in(klass) def snake_case(str) return str.downcase if str =~ /^[A-Z_]+$/ str.gsub(/\B[A-Z]/, '_\&').squeeze("_") =~ /_*(.*)/ - $+.downcase + Regexp.last_match(-1).downcase end # Receives a string and convert it to camel case. camel_case returns CamelCase. @@ -189,7 +189,7 @@ def user_home # Returns the root where thor files are located, depending on the OS. # def thor_root - File.join(user_home, ".thor").tr('\\', "/") + File.join(user_home, ".thor").tr("\\", "/") end # Returns the files in the thor root. On Windows thor_root will be something @@ -236,7 +236,7 @@ def ruby_command # symlink points to 'ruby_install_name' ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby end - rescue NotImplementedError # rubocop:disable HandleExceptions + rescue NotImplementedError # rubocop:disable Lint/HandleExceptions # just ignore on windows end end diff --git a/lint_gems.rb b/lint_gems.rb new file mode 100644 index 00000000..8bc06f81 --- /dev/null +++ b/lint_gems.rb @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem "rubocop", "~> 1.30" diff --git a/spec/actions/file_manipulation_spec.rb b/spec/actions/file_manipulation_spec.rb index 3e1fefc5..f2ecf93d 100644 --- a/spec/actions/file_manipulation_spec.rb +++ b/spec/actions/file_manipulation_spec.rb @@ -319,7 +319,7 @@ def file end it "does not replace if pretending" do - runner({ :pretend => true }, :revoke) + runner({:pretend => true}, :revoke) action :gsub_file, "doc/README", "__start__", "START" expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n") end @@ -349,7 +349,7 @@ def file end it "does not replace if pretending" do - runner({ :pretend => true }, :revoke) + runner({:pretend => true}, :revoke) action :gsub_file, "doc/README", "__start__", "START", :force => true expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n") end diff --git a/spec/shell/basic_spec.rb b/spec/shell/basic_spec.rb index b795a80a..74b89697 100644 --- a/spec/shell/basic_spec.rb +++ b/spec/shell/basic_spec.rb @@ -63,53 +63,53 @@ def shell end it "prints a message to the user and does not echo stdin if the echo option is set to false" do - expect($stdout).to receive(:print).with('What\'s your password? ') + expect($stdout).to receive(:print).with("What's your password? ") expect($stdin).to receive(:noecho).and_return("mysecretpass") expect(shell.ask("What's your password?", :echo => false)).to eq("mysecretpass") end it "prints a message to the user with the available options, expects case-sensitive matching, and determines the correctness of the answer" do flavors = %w(strawberry chocolate vanilla) - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', {:limited_to => flavors}).and_return("chocolate") - expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors)).to eq("chocolate") + expect(Thor::LineEditor).to receive(:readline).with("What's your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ", {:limited_to => flavors}).and_return("chocolate") + expect(shell.ask("What's your favorite Neopolitan flavor?", :limited_to => flavors)).to eq("chocolate") end it "prints a message to the user with the available options, expects case-sensitive matching, and reasks the question after an incorrect response" do flavors = %w(strawberry chocolate vanilla) expect($stdout).to receive(:print).with("Your response must be one of: [strawberry, chocolate, vanilla]. Please try again.\n") - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', {:limited_to => flavors}).and_return("moose tracks", "chocolate") - expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors)).to eq("chocolate") + expect(Thor::LineEditor).to receive(:readline).with("What's your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ", {:limited_to => flavors}).and_return("moose tracks", "chocolate") + expect(shell.ask("What's your favorite Neopolitan flavor?", :limited_to => flavors)).to eq("chocolate") end it "prints a message to the user with the available options, expects case-sensitive matching, and reasks the question after a case-insensitive match" do flavors = %w(strawberry chocolate vanilla) expect($stdout).to receive(:print).with("Your response must be one of: [strawberry, chocolate, vanilla]. Please try again.\n") - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', {:limited_to => flavors}).and_return("cHoCoLaTe", "chocolate") - expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors)).to eq("chocolate") + expect(Thor::LineEditor).to receive(:readline).with("What's your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ", {:limited_to => flavors}).and_return("cHoCoLaTe", "chocolate") + expect(shell.ask("What's your favorite Neopolitan flavor?", :limited_to => flavors)).to eq("chocolate") end it "prints a message to the user with the available options, expects case-insensitive matching, and determines the correctness of the answer" do flavors = %w(strawberry chocolate vanilla) - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', {:limited_to => flavors, :case_insensitive => true}).and_return("CHOCOLATE") - expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors, :case_insensitive => true)).to eq("chocolate") + expect(Thor::LineEditor).to receive(:readline).with("What's your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ", {:limited_to => flavors, :case_insensitive => true}).and_return("CHOCOLATE") + expect(shell.ask("What's your favorite Neopolitan flavor?", :limited_to => flavors, :case_insensitive => true)).to eq("chocolate") end it "prints a message to the user with the available options, expects case-insensitive matching, and reasks the question after an incorrect response" do flavors = %w(strawberry chocolate vanilla) expect($stdout).to receive(:print).with("Your response must be one of: [strawberry, chocolate, vanilla]. Please try again.\n") - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ', {:limited_to => flavors, :case_insensitive => true}).and_return("moose tracks", "chocolate") - expect(shell.ask('What\'s your favorite Neopolitan flavor?', :limited_to => flavors, :case_insensitive => true)).to eq("chocolate") + expect(Thor::LineEditor).to receive(:readline).with("What's your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] ", {:limited_to => flavors, :case_insensitive => true}).and_return("moose tracks", "chocolate") + expect(shell.ask("What's your favorite Neopolitan flavor?", :limited_to => flavors, :case_insensitive => true)).to eq("chocolate") end it "prints a message to the user containing a default and sets the default if only enter is pressed" do - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? (vanilla) ', {:default => "vanilla"}).and_return("") - expect(shell.ask('What\'s your favorite Neopolitan flavor?', :default => "vanilla")).to eq("vanilla") + expect(Thor::LineEditor).to receive(:readline).with("What's your favorite Neopolitan flavor? (vanilla) ", {:default => "vanilla"}).and_return("") + expect(shell.ask("What's your favorite Neopolitan flavor?", :default => "vanilla")).to eq("vanilla") end it "prints a message to the user with the available options and reasks the question after an incorrect response and then returns the default" do flavors = %w(strawberry chocolate vanilla) expect($stdout).to receive(:print).with("Your response must be one of: [strawberry, chocolate, vanilla]. Please try again.\n") - expect(Thor::LineEditor).to receive(:readline).with('What\'s your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] (vanilla) ', {:default => "vanilla", :limited_to => flavors}).and_return("moose tracks", "") + expect(Thor::LineEditor).to receive(:readline).with("What's your favorite Neopolitan flavor? [strawberry, chocolate, vanilla] (vanilla) ", {:default => "vanilla", :limited_to => flavors}).and_return("moose tracks", "") expect(shell.ask("What's your favorite Neopolitan flavor?", :default => "vanilla", :limited_to => flavors)).to eq("vanilla") end end @@ -400,7 +400,7 @@ def #456 Lanç... it "prints a table with small numbers and right-aligns them" do table = [ - ["Name", "Number", "Color"], # rubocop: disable WordArray + ["Name", "Number", "Color"], # rubocop: disable Style/WordArray ["Erik", 1, "green"] ] content = capture(:stdout) { shell.print_table(table) } @@ -412,7 +412,7 @@ def #456 Lanç... it "doesn't output extra spaces for right-aligned columns in the last column" do table = [ - ["Name", "Number"], # rubocop: disable WordArray + ["Name", "Number"], # rubocop: disable Style/WordArray ["Erik", 1] ] content = capture(:stdout) { shell.print_table(table) } @@ -424,7 +424,7 @@ def #456 Lanç... it "prints a table with big numbers" do table = [ - ["Name", "Number", "Color"], # rubocop: disable WordArray + ["Name", "Number", "Color"], # rubocop: disable Style/WordArray ["Erik", 1_234_567_890_123, "green"] ] content = capture(:stdout) { shell.print_table(table) } From 6c27e0a982cfe0133dd4b208131e29911e1449bf Mon Sep 17 00:00:00 2001 From: Tim Diggins Date: Fri, 4 Mar 2022 18:18:30 +0000 Subject: [PATCH 11/29] create breaking spec --- spec/encoding_spec.rb | 22 ++++++++++++++++++++++ spec/fixtures/encoding_implicit.thor | 16 ++++++++++++++++ spec/fixtures/encoding_other.thor | 17 +++++++++++++++++ spec/fixtures/encoding_with_utf8.thor | 17 +++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 spec/encoding_spec.rb create mode 100644 spec/fixtures/encoding_implicit.thor create mode 100644 spec/fixtures/encoding_other.thor create mode 100644 spec/fixtures/encoding_with_utf8.thor diff --git a/spec/encoding_spec.rb b/spec/encoding_spec.rb new file mode 100644 index 00000000..71e3c86b --- /dev/null +++ b/spec/encoding_spec.rb @@ -0,0 +1,22 @@ +require "helper" +require "thor/base" + + +describe "file's encoding" do + def load_thorfile(filename) + Thor::Util.load_thorfile(File.expand_path("./fixtures/#{filename}", __dir__)) + end + + it "respects explicit UTF-8" do + load_thorfile("encoding_with_utf8.thor") + expect(capture(:stdout) { Thor::Sandbox::EncodingWithUtf8.new.invoke(:encoding) }).to match(/ok/) + end + it "respects explicit non-UTF-8" do + load_thorfile("encoding_other.thor") + expect(capture(:stdout) { Thor::Sandbox::EncodingOther.new.invoke(:encoding) }).to match(/ok/) + end + it "has implicit UTF-8" do + load_thorfile("encoding_implicit.thor") + expect(capture(:stdout) { Thor::Sandbox::EncodingImplicit.new.invoke(:encoding) }).to match(/ok/) + end +end diff --git a/spec/fixtures/encoding_implicit.thor b/spec/fixtures/encoding_implicit.thor new file mode 100644 index 00000000..f9fae8de --- /dev/null +++ b/spec/fixtures/encoding_implicit.thor @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class EncodingImplicit < Thor + SOME_STRING = "Some λέξεις 一些词 🎉" + + desc "encoding", "tests that encoding is correct" + + def encoding + puts "#{SOME_STRING.inspect}: #{SOME_STRING.encoding}:" + if SOME_STRING.encoding.name == "UTF-8" + puts "ok" + else + puts "expected #{SOME_STRING.encoding.name} to equal UTF-8" + end + end +end diff --git a/spec/fixtures/encoding_other.thor b/spec/fixtures/encoding_other.thor new file mode 100644 index 00000000..1f1202f4 --- /dev/null +++ b/spec/fixtures/encoding_other.thor @@ -0,0 +1,17 @@ +# encoding: ISO-8859-7 +# frozen_string_literal: true + +class EncodingOther < Thor + SOME_STRING = "Some " + + desc "encoding", "tests that encoding is correct" + + def encoding + puts "#{SOME_STRING.inspect}: #{SOME_STRING.encoding}:" + if SOME_STRING.encoding.name == "ISO-8859-7" + puts "ok" + else + puts "expected #{SOME_STRING.encoding.name} to equal ISO-8859-7" + end + end +end diff --git a/spec/fixtures/encoding_with_utf8.thor b/spec/fixtures/encoding_with_utf8.thor new file mode 100644 index 00000000..af6842d6 --- /dev/null +++ b/spec/fixtures/encoding_with_utf8.thor @@ -0,0 +1,17 @@ +# encoding: UTF-8 +# frozen_string_literal: true + +class EncodingWithUtf8 < Thor + SOME_STRING = "Some λέξεις 一些词 🎉" + + desc "encoding", "tests that encoding is correct" + + def encoding + puts "#{SOME_STRING.inspect}: #{SOME_STRING.encoding}:" + if SOME_STRING.encoding.name == "UTF-8" + puts "ok" + else + puts "expected #{SOME_STRING.encoding.name} to equal UTF-8" + end + end +end From c4f445ee6519ed222c94bb6ed83709b4cd952cc7 Mon Sep 17 00:00:00 2001 From: Tim Diggins Date: Fri, 4 Mar 2022 18:19:14 +0000 Subject: [PATCH 12/29] a potential fix --- lib/thor/util.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thor/util.rb b/lib/thor/util.rb index 628c6048..171d222d 100644 --- a/lib/thor/util.rb +++ b/lib/thor/util.rb @@ -150,7 +150,7 @@ def find_class_and_command_by_namespace(namespace, fallback = true) # inside the sandbox to avoid namespacing conflicts. # def load_thorfile(path, content = nil, debug = false) - content ||= File.binread(path) + content ||= File.read(path) begin Thor::Sandbox.class_eval(content, path) From 439a5930804a05b1079ea33b7e862dbe2dff291b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 3 Jun 2022 13:41:09 +0200 Subject: [PATCH 13/29] Support `thor install ` to install remote thor files The previous code suggested that this was supported, but it was not really working as expected. --- lib/thor/runner.rb | 45 ++++++++++++++++++------------- spec/runner_spec.rb | 65 ++++++++++++++++++++++++++++++--------------- 2 files changed, 70 insertions(+), 40 deletions(-) diff --git a/lib/thor/runner.rb b/lib/thor/runner.rb index 971e59cc..441ab7df 100644 --- a/lib/thor/runner.rb +++ b/lib/thor/runner.rb @@ -6,8 +6,6 @@ require "pathname" class Thor::Runner < Thor #:nodoc: - autoload :OpenURI, "open-uri" - map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version def self.banner(command, all = false, subcommand = false) @@ -48,22 +46,33 @@ def method_missing(meth, *args) def install(name) # rubocop:disable Metrics/MethodLength initialize_thorfiles - # If a directory name is provided as the argument, look for a 'main.thor' - # command in said directory. - begin - if File.directory?(File.expand_path(name)) - base = File.join(name, "main.thor") - package = :directory - contents = open(base, &:read) - else - base = name - package = :file - contents = open(name, &:read) + is_uri = name =~ %r{^https?\://} + + if is_uri + base = name + package = :file + require "open-uri" + begin + contents = URI.send(:open, name, &:read) # Using `send` for Ruby 2.4- support + rescue OpenURI::HTTPError + raise Error, "Error opening URI '#{name}'" + end + else + # If a directory name is provided as the argument, look for a 'main.thor' + # command in said directory. + begin + if File.directory?(File.expand_path(name)) + base = File.join(name, "main.thor") + package = :directory + contents = open(base, &:read) + else + base = name + package = :file + contents = open(name, &:read) + end + rescue Errno::ENOENT + raise Error, "Error opening file '#{name}'" end - rescue OpenURI::HTTPError - raise Error, "Error opening URI '#{name}'" - rescue Errno::ENOENT - raise Error, "Error opening file '#{name}'" end say "Your Thorfile contains:" @@ -84,7 +93,7 @@ def install(name) # rubocop:disable Metrics/MethodLength as = basename if as.empty? end - location = if options[:relative] || name =~ %r{^https?://} + location = if options[:relative] || is_uri name else File.expand_path(name) diff --git a/spec/runner_spec.rb b/spec/runner_spec.rb index 52748fcc..0edbe2f1 100644 --- a/spec/runner_spec.rb +++ b/spec/runner_spec.rb @@ -118,11 +118,11 @@ def when_no_thorfiles_exist end describe "commands" do + let(:location) { "#{File.dirname(__FILE__)}/fixtures/command.thor" } before do - @location = "#{File.dirname(__FILE__)}/fixtures/command.thor" @original_yaml = { "random" => { - :location => @location, + :location => location, :filename => "4a33b894ffce85d7b412fc1b36f88fe0", :namespaces => %w(amazing) } @@ -214,31 +214,52 @@ def when_no_thorfiles_exist end describe "install/update" do - before do - allow(FileUtils).to receive(:mkdir_p) - allow(FileUtils).to receive(:touch) - allow(Thor::LineEditor).to receive(:readline).and_return("Y") - - path = File.join(Thor::Util.thor_root, Digest::SHA256.hexdigest(@location + "random")) - expect(File).to receive(:open).with(path, "w") - end + context "with local thor files" do + before do + allow(FileUtils).to receive(:mkdir_p) + allow(FileUtils).to receive(:touch) + allow(Thor::LineEditor).to receive(:readline).and_return("Y") + + path = File.join(Thor::Util.thor_root, Digest::SHA256.hexdigest(location + "random")) + expect(File).to receive(:open).with(path, "w") + end - it "updates existing thor files" do - path = File.join(Thor::Util.thor_root, @original_yaml["random"][:filename]) - if File.directory? path - expect(FileUtils).to receive(:rm_rf).with(path) - else - expect(File).to receive(:delete).with(path) + it "updates existing thor files" do + path = File.join(Thor::Util.thor_root, @original_yaml["random"][:filename]) + if File.directory? path + expect(FileUtils).to receive(:rm_rf).with(path) + else + expect(File).to receive(:delete).with(path) + end + silence_warnings do + silence(:stdout) { Thor::Runner.start(%w(update random)) } + end end - silence_warnings do - silence(:stdout) { Thor::Runner.start(%w(update random)) } + + it "installs thor files" do + ARGV.replace %W(install #{location}) + silence_warnings do + silence(:stdout) { Thor::Runner.start } + end end end - it "installs thor files" do - ARGV.replace %W(install #{@location}) - silence_warnings do - silence(:stdout) { Thor::Runner.start } + context "with remote thor files" do + let(:location) { "https://example.com/Thorfile" } + + it "installs thor files" do + allow(Thor::LineEditor).to receive(:readline).and_return("Y", "random") + stub_request(:get, location).to_return(:body => "class Foo < Thor; end") + path = File.join(Thor::Util.thor_root, Digest::SHA256.hexdigest(location + "random")) + expect(File).to receive(:open).with(path, "w") + expect { silence(:stdout) { Thor::Runner.start(%W(install #{location})) } }.not_to raise_error + end + + it "shows proper errors" do + expect(Thor::Runner).to receive :exit + expect(URI).to receive(:open).with(location).and_raise(OpenURI::HTTPError.new("foo", StringIO.new)) + content = capture(:stderr) { Thor::Runner.start(%W(install #{location})) } + expect(content).to include("Error opening URI '#{location}'") end end end From 9ebb3756215f85a3c6944e137cb0257af13cebb3 Mon Sep 17 00:00:00 2001 From: Joe Bauser Date: Sun, 7 Aug 2022 11:35:15 -0400 Subject: [PATCH 14/29] Respect the updated NO_COLOR specification Since implementation the NO_COLOR specification has changed. Previously if NO_COLOR was set in the environment it should be respected regardless of value. In the new updated specification NO_COLOR should only be respected if set to any non-empty string. See this comment thread on the original commit for more information. https://github.com/rails/thor/commit/21e65684432c0fcc975c1d6fda497da24149f763#r80414525 Fixes #795 --- lib/thor/shell/color.rb | 2 +- spec/shell/color_spec.rb | 72 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/lib/thor/shell/color.rb b/lib/thor/shell/color.rb index 33e9d9d9..aee95292 100644 --- a/lib/thor/shell/color.rb +++ b/lib/thor/shell/color.rb @@ -105,7 +105,7 @@ def are_colors_supported? end def are_colors_disabled? - !ENV['NO_COLOR'].nil? + !ENV['NO_COLOR'].nil? && !ENV['NO_COLOR'].empty? end # Overwrite show_diff to show diff with colors if Diff::LCS is diff --git a/spec/shell/color_spec.rb b/spec/shell/color_spec.rb index a0f9686a..2c63bee7 100644 --- a/spec/shell/color_spec.rb +++ b/spec/shell/color_spec.rb @@ -21,8 +21,8 @@ def shell shell.ask "Is this green?", :green, :limited_to => %w(Yes No Maybe) end - it "does not set the color if specified and NO_COLOR is set" do - allow(ENV).to receive(:[]).with("NO_COLOR").and_return("") + it "does not set the color if specified and NO_COLOR is set to a non-empty value" do + allow(ENV).to receive(:[]).with("NO_COLOR").and_return("non-empty value") expect(Thor::LineEditor).to receive(:readline).with("Is this green? ", anything).and_return("yes") shell.ask "Is this green?", :green @@ -30,6 +30,24 @@ def shell shell.ask "Is this green?", :green, :limited_to => %w(Yes No Maybe) end + it "sets the color when NO_COLOR is ignored because the environment variable is nil" do + allow(ENV).to receive(:[]).with("NO_COLOR").and_return(nil) + expect(Thor::LineEditor).to receive(:readline).with("\e[32mIs this green? \e[0m", anything).and_return("yes") + shell.ask "Is this green?", :green + + expect(Thor::LineEditor).to receive(:readline).with("\e[32mIs this green? [Yes, No, Maybe] \e[0m", anything).and_return("Yes") + shell.ask "Is this green?", :green, :limited_to => %w(Yes No Maybe) + end + + it "sets the color when NO_COLOR is ignored because the environment variable is an empty-string" do + allow(ENV).to receive(:[]).with("NO_COLOR").and_return("") + expect(Thor::LineEditor).to receive(:readline).with("\e[32mIs this green? \e[0m", anything).and_return("yes") + shell.ask "Is this green?", :green + + expect(Thor::LineEditor).to receive(:readline).with("\e[32mIs this green? [Yes, No, Maybe] \e[0m", anything).and_return("Yes") + shell.ask "Is this green?", :green, :limited_to => %w(Yes No Maybe) + end + it "handles an Array of colors" do expect(Thor::LineEditor).to receive(:readline).with("\e[32m\e[47m\e[1mIs this green on white? \e[0m", anything).and_return("yes") shell.ask "Is this green on white?", [:green, :on_white, :bold] @@ -59,13 +77,31 @@ def shell expect(out.chomp).to eq("Wow! Now we have colors!") end - it "does not set the color if NO_COLOR is set" do + it "does not set the color if NO_COLOR is set to any value that is not an empty string" do + allow(ENV).to receive(:[]).with("NO_COLOR").and_return("non-empty string value") + out = capture(:stdout) do + shell.say "NO_COLOR is enforced! We should not have colors!", :green + end + + expect(out.chomp).to eq("NO_COLOR is enforced! We should not have colors!") + end + + it "colors are still used and NO_COLOR is ignored if the environment variable is nil" do + allow(ENV).to receive(:[]).with("NO_COLOR").and_return(nil) + out = capture(:stdout) do + shell.say "NO_COLOR is ignored! We have colors!", :green + end + + expect(out.chomp).to eq("\e[32mNO_COLOR is ignored! We have colors!\e[0m") + end + + it "colors are still used and NO_COLOR is ignored if the environment variable is an empty-string" do allow(ENV).to receive(:[]).with("NO_COLOR").and_return("") out = capture(:stdout) do - shell.say "Wow! Now we have colors!", :green + shell.say "NO_COLOR is ignored! We have colors!", :green end - expect(out.chomp).to eq("Wow! Now we have colors!") + expect(out.chomp).to eq("\e[32mNO_COLOR is ignored! We have colors!\e[0m") end it "does not use a new line even with colors" do @@ -145,12 +181,34 @@ def shell expect(colorless).to eq("hi!") end - it "does nothing when the NO_COLOR environment variable is set" do - allow(ENV).to receive(:[]).with("NO_COLOR").and_return("") + it "does nothing when the NO_COLOR environment variable is set to a non-empty string" do + allow(ENV).to receive(:[]).with("NO_COLOR").and_return("non-empty value") allow($stdout).to receive(:tty?).and_return(true) colorless = shell.set_color "hi!", :white expect(colorless).to eq("hi!") end + + it "sets color when the NO_COLOR environment variable is ignored for being nil" do + allow(ENV).to receive(:[]).with("NO_COLOR").and_return(nil) + allow($stdout).to receive(:tty?).and_return(true) + + red = shell.set_color "hi!", :red + expect(red).to eq("\e[31mhi!\e[0m") + + on_red = shell.set_color "hi!", :white, :on_red + expect(on_red).to eq("\e[37m\e[41mhi!\e[0m") + end + + it "sets color when the NO_COLOR environment variable is ignored for being an empty string" do + allow(ENV).to receive(:[]).with("NO_COLOR").and_return("") + allow($stdout).to receive(:tty?).and_return(true) + + red = shell.set_color "hi!", :red + expect(red).to eq("\e[31mhi!\e[0m") + + on_red = shell.set_color "hi!", :white, :on_red + expect(on_red).to eq("\e[37m\e[41mhi!\e[0m") + end end describe "#file_collision" do From 86e21aa73d2f483fe225a0c6452c30a28fa779e9 Mon Sep 17 00:00:00 2001 From: Joe Bauser Date: Sun, 7 Aug 2022 13:03:54 -0400 Subject: [PATCH 15/29] Fix webmock errors in specs for ruby 2.1 and 2.2 New versions of webmock released within the last month have adopted the ruby 2.4 method invocation syntax of &. but the tested ruby versions do not support it. This commit resolves the problem by conditionally bundling either the older version of webmock or newer versions based on the current ruby version. --- Gemfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 86413b01..53a6d1e5 100644 --- a/Gemfile +++ b/Gemfile @@ -13,7 +13,8 @@ group :test do gem "rspec", ">= 3.2" gem "rspec-mocks", ">= 3" gem "simplecov", ">= 0.13" - gem "webmock" + gem "webmock", '~> 3.14.0' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.4.0") + gem "webmock", '>= 3.14' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.4.0") end gemspec From fa25522c9c6d3b7a9246bd378888df19cfe8b71b Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 3 Nov 2022 14:52:51 -0400 Subject: [PATCH 16/29] Drop support for HP-UX Support for HP-UX was dropped in Ruby in ruby/ruby#5457. --- lib/thor/shell/basic.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thor/shell/basic.rb b/lib/thor/shell/basic.rb index ce673a2e..f7581166 100644 --- a/lib/thor/shell/basic.rb +++ b/lib/thor/shell/basic.rb @@ -425,7 +425,7 @@ def dynamic_width_tput end def unix? - RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i + RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix)/i end def truncate(string, width) From da3bdc6d8191ed65fdcc1ed17e09699ca172d084 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 3 Nov 2022 14:55:15 -0400 Subject: [PATCH 17/29] Drop support for IRIX The IRIX OS is no longer maintained with the last release being 16 years ago. --- lib/thor/shell/basic.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thor/shell/basic.rb b/lib/thor/shell/basic.rb index f7581166..64e0afe7 100644 --- a/lib/thor/shell/basic.rb +++ b/lib/thor/shell/basic.rb @@ -425,7 +425,7 @@ def dynamic_width_tput end def unix? - RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix)/i + RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris)/i end def truncate(string, width) From d1d420e3e67a200442d403276ecbb35bf8e21a65 Mon Sep 17 00:00:00 2001 From: Steve Brown Date: Sat, 12 Nov 2022 17:05:26 +0900 Subject: [PATCH 18/29] Indicated error might occur when content already exists --- lib/thor/actions/inject_into_file.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thor/actions/inject_into_file.rb b/lib/thor/actions/inject_into_file.rb index fd0046b5..2635e755 100644 --- a/lib/thor/actions/inject_into_file.rb +++ b/lib/thor/actions/inject_into_file.rb @@ -21,7 +21,7 @@ module Actions # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n") # end # - WARNINGS = { unchanged_no_flag: 'File unchanged! The supplied flag value not found!' } + WARNINGS = { unchanged_no_flag: 'File unchanged! Either the supplied flag value not found or the content has already been inserted!' } def insert_into_file(destination, *args, &block) data = block_given? ? block : args.shift From 8fab5f10b246e2c7e52b7343037a44a9cf951818 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sun, 1 Jan 2023 13:04:18 -0800 Subject: [PATCH 19/29] Fix some typos Discovered using codespell: https://github.com/codespell-project/codespell --- lib/thor/actions.rb | 2 +- lib/thor/actions/file_manipulation.rb | 2 +- lib/thor/base.rb | 2 +- spec/actions_spec.rb | 2 +- spec/shell/html_spec.rb | 2 +- spec/thor_spec.rb | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/thor/actions.rb b/lib/thor/actions.rb index 5779419c..7e81e5c0 100644 --- a/lib/thor/actions.rb +++ b/lib/thor/actions.rb @@ -175,7 +175,7 @@ def inside(dir = "", config = {}, &block) shell.padding += 1 if verbose @destination_stack.push File.expand_path(dir, destination_root) - # If the directory doesnt exist and we're not pretending + # If the directory doesn't exist and we're not pretending if !File.exist?(destination_root) && !pretend require "fileutils" FileUtils.mkdir_p(destination_root) diff --git a/lib/thor/actions/file_manipulation.rb b/lib/thor/actions/file_manipulation.rb index 19aa4c0d..c20e5d19 100644 --- a/lib/thor/actions/file_manipulation.rb +++ b/lib/thor/actions/file_manipulation.rb @@ -252,7 +252,7 @@ def inject_into_module(path, module_name, *args, &block) # flag:: the regexp or string to be replaced # replacement:: the replacement, can be also given as a block # config:: give :verbose => false to not log the status, and - # :force => true, to force the replacement regardles of runner behavior. + # :force => true, to force the replacement regardless of runner behavior. # # ==== Example # diff --git a/lib/thor/base.rb b/lib/thor/base.rb index 39551e02..a44a4d9a 100644 --- a/lib/thor/base.rb +++ b/lib/thor/base.rb @@ -618,7 +618,7 @@ def find_and_refresh_command(name) #:nodoc: end alias_method :find_and_refresh_task, :find_and_refresh_command - # Everytime someone inherits from a Thor class, register the klass + # Every time someone inherits from a Thor class, register the klass # and file into baseclass. def inherited(klass) super(klass) diff --git a/spec/actions_spec.rb b/spec/actions_spec.rb index 73b0cad1..14df41fd 100644 --- a/spec/actions_spec.rb +++ b/spec/actions_spec.rb @@ -86,7 +86,7 @@ def file expect(runner.relative_to_original_destination_root("/test/file")).to eq("/test/file") end - it "doesn't remove the root path from the absolute path if it is not at the begining" do + it "doesn't remove the root path from the absolute path if it is not at the beginning" do runner.destination_root = "/app" expect(runner.relative_to_original_destination_root("/something/app/project")).to eq("/something/app/project") end diff --git a/spec/shell/html_spec.rb b/spec/shell/html_spec.rb index d94864e4..811c490a 100644 --- a/spec/shell/html_spec.rb +++ b/spec/shell/html_spec.rb @@ -30,7 +30,7 @@ def shell end describe "#set_color" do - it "escapes HTML content when unsing the default colors" do + it "escapes HTML content when using the default colors" do expect(shell.set_color("", :blue)).to eq "<htmlcontent>" end diff --git a/spec/thor_spec.rb b/spec/thor_spec.rb index 77f423c5..fbb087e5 100644 --- a/spec/thor_spec.rb +++ b/spec/thor_spec.rb @@ -395,7 +395,7 @@ def self.exit_on_failure? expect(capture(:stdout) { MyScript.start(%w(help)) }).not_to match(/this is hidden/m) end - it "but the command is still invokable, does not show the command in help" do + it "but the command is still invocable, does not show the command in help" do expect(MyScript.start(%w(hidden yesyes))).to eq(%w(yesyes)) end end From 8cc8677d145d12cdf03a6915240c4924801fafbb Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 11 Jan 2023 14:49:37 +0100 Subject: [PATCH 20/29] Avoid anonymous eval It makes it hard to locate code when profiling etc. --- lib/thor/base.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thor/base.rb b/lib/thor/base.rb index 39551e02..bd053f8d 100644 --- a/lib/thor/base.rb +++ b/lib/thor/base.rb @@ -506,7 +506,7 @@ def start(given_args = ARGV, config = {}) # def public_command(*names) names.each do |name| - class_eval "def #{name}(*); super end" + class_eval "def #{name}(*); super end", __FILE__, __LINE__ end end alias_method :public_task, :public_command From 22765e3cc5bd9917fe2f212db8df692a96231fbd Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Fri, 20 Jan 2023 03:43:38 +0900 Subject: [PATCH 21/29] CI against Ruby 3.2 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b0e5dd92..b90a8d7c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,7 @@ jobs: fail-fast: false matrix: # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0' - ruby: [2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, '3.0', 3.1, head] + ruby: [2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, '3.0', 3.1, 3.2, head] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From 70fb81b6c88ab6d8763f15ab60dc9225d4c160ba Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Sat, 21 Jan 2023 01:35:17 +0900 Subject: [PATCH 22/29] Unquoted `2.0` means "the latest 2.x" i.e. 2.7.7 as of today see: https://github.com/actions/runner/issues/849 --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b90a8d7c..8d54e00c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,7 +6,7 @@ jobs: fail-fast: false matrix: # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0' - ruby: [2.0, 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, '3.0', 3.1, 3.2, head] + ruby: ['2.0', 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, '3.0', 3.1, 3.2, head] runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 From 12f635d74f55e071ade24bb99efa7ec66803378b Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Sat, 21 Jan 2023 01:53:52 +0900 Subject: [PATCH 23/29] rexml 3.2.5 includes an incompatibility with Ruby 2.0 --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index 53a6d1e5..fa8c4399 100644 --- a/Gemfile +++ b/Gemfile @@ -15,6 +15,7 @@ group :test do gem "simplecov", ">= 0.13" gem "webmock", '~> 3.14.0' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.4.0") gem "webmock", '>= 3.14' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.4.0") + gem "rexml", '3.2.4' if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.1.0") end gemspec From f3e6538ba0267f5ff52580805719800ef66a85e6 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 13 Dec 2022 18:44:32 +0900 Subject: [PATCH 24/29] Prefer to use URI.open and File.open instead of Kernel.open --- lib/thor/actions.rb | 2 +- lib/thor/runner.rb | 5 +++-- spec/actions_spec.rb | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/thor/actions.rb b/lib/thor/actions.rb index 7e81e5c0..67d07720 100644 --- a/lib/thor/actions.rb +++ b/lib/thor/actions.rb @@ -225,7 +225,7 @@ def apply(path, config = {}) require "open-uri" URI.open(path, "Accept" => "application/x-thor-template", &:read) else - open(path, &:read) + File.open(path, &:read) end instance_eval(contents, path) diff --git a/lib/thor/runner.rb b/lib/thor/runner.rb index 441ab7df..24accfde 100644 --- a/lib/thor/runner.rb +++ b/lib/thor/runner.rb @@ -64,11 +64,12 @@ def install(name) # rubocop:disable Metrics/MethodLength if File.directory?(File.expand_path(name)) base = File.join(name, "main.thor") package = :directory - contents = open(base, &:read) + contents = File.open(base, &:read) else base = name package = :file - contents = open(name, &:read) + require "open-uri" + contents = URI.send(:open, name, &:read) # for ruby 2.1-2.4 end rescue Errno::ENOENT raise Error, "Error opening file '#{name}'" diff --git a/spec/actions_spec.rb b/spec/actions_spec.rb index 14df41fd..ee050deb 100644 --- a/spec/actions_spec.rb +++ b/spec/actions_spec.rb @@ -234,7 +234,7 @@ def file allow(@template).to receive(:read).and_return(@template) @file = "/" - allow(runner).to receive(:open).and_return(@template) + allow(File).to receive(:open).and_return(@template) end it "accepts a URL as the path" do @@ -255,7 +255,7 @@ def file it "accepts a local file path with spaces" do @file = File.expand_path("fixtures/path with spaces", File.dirname(__FILE__)) - expect(runner).to receive(:open).with(@file).and_return(@template) + expect(File).to receive(:open).with(@file).and_return(@template) action(:apply, @file) end From e7713ad30300dbc587dcd93738891f68c69237f1 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 13 Dec 2022 18:50:26 +0900 Subject: [PATCH 25/29] Workaround for Ruby 2.1-2.4 --- lib/thor/actions.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/thor/actions.rb b/lib/thor/actions.rb index 67d07720..e4af5df9 100644 --- a/lib/thor/actions.rb +++ b/lib/thor/actions.rb @@ -223,7 +223,8 @@ def apply(path, config = {}) contents = if is_uri require "open-uri" - URI.open(path, "Accept" => "application/x-thor-template", &:read) + # for ruby 2.1-2.4 + URI.send(:open, path, "Accept" => "application/x-thor-template", &:read) else File.open(path, &:read) end From 16db5639f18021fd70c050107bbca5df5fbc3f65 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 15 Dec 2022 13:57:24 +0900 Subject: [PATCH 26/29] Also use File.open --- lib/thor/actions/file_manipulation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thor/actions/file_manipulation.rb b/lib/thor/actions/file_manipulation.rb index c20e5d19..d7e6cb9f 100644 --- a/lib/thor/actions/file_manipulation.rb +++ b/lib/thor/actions/file_manipulation.rb @@ -85,7 +85,7 @@ def get(source, *args, &block) URI.send(:open, source) { |input| input.binmode.read } else source = File.expand_path(find_in_source_paths(source.to_s)) - open(source) { |input| input.binmode.read } + File.open(source) { |input| input.binmode.read } end destination ||= if block_given? From 17619ce8da4536a2899260f914bbe60dd3452c1f Mon Sep 17 00:00:00 2001 From: Petrik Date: Thu, 9 Feb 2023 16:51:49 +0100 Subject: [PATCH 27/29] Properly pad aliases for option usage When printing the options of a command, options without aliases are padded so they aligned with options with aliases The size of the padding is calculated by multiplying the max number of aliases for an option by the number 4 (a dash, a letter, a comma and a space?). Options can have aliases of arbitrary length, not just just a dash with a single letter. For example in Rails the `main` option has the [alias](https://github.com/rails/rails/blob/main/railties/lib/rails/generators/app_base.rb#L100)` --master`. Also, the current implementation adds padding only to options without aliases. This results in strange output when callings `bin/rails new -h`: ```console -T, [--skip-test], [--no-skip-test] # Skip test files [--skip-system-test], [--no-skip-system-test] # Skip system test files [--skip-bootsnap], [--no-skip-bootsnap] # Skip bootsnap gem ... [--edge], [--no-edge] # Set up the application with a Gemfile pointing to the main branch on the Rails repository --master, [--main], [--no-main] # Set up the application with Gemfile pointing to Rails repository main branch ``` When printing the usage for options we should look at the actual formatted options. __Before (examples)__ ```console Usage: thor my_counter N [N] Options: -t, [--third=THREE] # The third argument # Default: 3 [--fourth=N] # The fourth argument z, [--simple=N] y, r, [--symbolic=N] ``` __After (examples)__ ```console Usage: thor my_counter N [N] Options: -t, [--third=THREE] # The third argument # Default: 3 [--fourth=N] # The fourth argument z, [--simple=N] y, r, [--symbolic=N] ``` --- lib/thor/base.rb | 3 +-- lib/thor/parser/option.rb | 8 ++++++-- spec/base_spec.rb | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/thor/base.rb b/lib/thor/base.rb index 6d7bae3d..c0fb960c 100644 --- a/lib/thor/base.rb +++ b/lib/thor/base.rb @@ -558,8 +558,7 @@ def print_options(shell, options, group_name = nil) return if options.empty? list = [] - padding = options.map { |o| o.aliases.size }.max.to_i * 4 - + padding = options.map { |o| o.aliases_for_usage.size }.max.to_i options.each do |option| next if option.hide item = [option.usage(padding)] diff --git a/lib/thor/parser/option.rb b/lib/thor/parser/option.rb index a7647bbe..3d389063 100644 --- a/lib/thor/parser/option.rb +++ b/lib/thor/parser/option.rb @@ -93,10 +93,14 @@ def usage(padding = 0) sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-") end + aliases_for_usage.ljust(padding) + sample + end + + def aliases_for_usage if aliases.empty? - (" " * padding) << sample + "" else - "#{aliases.join(', ')}, #{sample}" + "#{aliases.join(', ')}, " end end diff --git a/spec/base_spec.rb b/spec/base_spec.rb index e5a9561c..29ff7bd9 100644 --- a/spec/base_spec.rb +++ b/spec/base_spec.rb @@ -139,8 +139,9 @@ def hello end it "use padding in options that do not have aliases" do - expect(@content).to match(/^ -t, \[--third/) - expect(@content).to match(/^ \[--fourth/) + expect(@content).to match(/^ -t, \[--third/) + expect(@content).to match(/^ \[--fourth/) + expect(@content).to match(/^ y, r, \[--symbolic/) end it "allows extra options to be given" do From 81f9965b1688513ecb9d87814f31fc455dd24093 Mon Sep 17 00:00:00 2001 From: Hartley McGuire Date: Fri, 5 May 2023 14:33:05 -0400 Subject: [PATCH 28/29] Allow setting file permissions with create_file Previously, to restrict the permissions of a file created using create_file, chmod could be called right after. However, that leaves a short amount of time between when the file is first created and when the permissions are updated where another user could read the file. This commit enables passing file permissions to create_file so that permissions can be set when the file is created. --- lib/thor/actions/create_file.rb | 2 +- spec/actions/create_file_spec.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/thor/actions/create_file.rb b/lib/thor/actions/create_file.rb index fa7b22cb..4886dec6 100644 --- a/lib/thor/actions/create_file.rb +++ b/lib/thor/actions/create_file.rb @@ -60,7 +60,7 @@ def invoke! invoke_with_conflict_check do require "fileutils" FileUtils.mkdir_p(File.dirname(destination)) - File.open(destination, "wb") { |f| f.write render } + File.open(destination, "wb", config[:perm]) { |f| f.write render } end given_destination end diff --git a/spec/actions/create_file_spec.rb b/spec/actions/create_file_spec.rb index 1e0c934e..1eae4f1c 100644 --- a/spec/actions/create_file_spec.rb +++ b/spec/actions/create_file_spec.rb @@ -33,6 +33,14 @@ def silence! expect(File.exist?(File.join(destination_root, "doc/config.rb"))).to be true end + it "allows setting file permissions" do + create_file("config/private.key", :perm => 0o600) + invoke! + + stat = File.stat(File.join(destination_root, "config/private.key")) + expect(stat.mode.to_s(8)).to eq "100600" + end + it "does not create a file if pretending" do create_file("doc/config.rb", {}, :pretend => true) invoke! From a4110566c0df158fda54ff3e411f94819beef226 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 11 May 2023 19:57:16 +0000 Subject: [PATCH 29/29] Prepare for 1.2.2 release --- lib/thor/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/thor/version.rb b/lib/thor/version.rb index c46acaf7..d10fe12a 100644 --- a/lib/thor/version.rb +++ b/lib/thor/version.rb @@ -1,3 +1,3 @@ class Thor - VERSION = "1.2.1" + VERSION = "1.2.2" end