Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Thor2, a class with more intuitive defaults #621

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/thor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -507,3 +507,5 @@ def help(command = nil, subcommand = false)
end
end
end

require "thor/thor2"
6 changes: 4 additions & 2 deletions lib/thor/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ def attr_accessor(*) #:nodoc:
end

# If you want to raise an error for unknown options, call check_unknown_options!
# This is disabled by default to allow dynamic invocations.
# This is disabled by default in the Thor class to allow dynamic invocations.
# This is enabled by default in the Thor2 class
def check_unknown_options!
@check_unknown_options = true
end
Expand All @@ -153,7 +154,8 @@ def check_unknown_options?(config) #:nodoc:

# If you want to raise an error when the default value of an option does not match
# the type call check_default_type!
# This is disabled by default for compatibility.
# This is disabled by default in the Thor class for compatibility.
# This is enabled by default in the Thor2 class
def check_default_type!
@check_default_type = true
end
Expand Down
32 changes: 32 additions & 0 deletions lib/thor/thor2.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
class Thor
class Thor2 < Thor
# This is a class to use instead of Thor when declaring your CLI
# This alternative works the same way as Thor, but has more common defaults:
# * If there is a failure in the argument parsing and other Thor-side
# things, the exit code will be non-zero
# * Things that look like options but are not valid options will
# will show an error of being unknown option instead of being
# used as arguments.
# * Make sure the default value of options is of the correct type
# For backward compatibility reasons, these cannot be made default in
# the regular `Thor` class
#
# This class is available in the top-level as Thor2, so you can do
# class MyCli < Thor2
# ...
# end

# Fail on unknown options instead of treating them as argument
check_unknown_options!

# Make sure the default value of options is of the correct type
check_default_type!

# All failures should result in non-zero error code
def self.exit_on_failure?
true
end
end
end

::Thor2 = Thor::Thor2
15 changes: 15 additions & 0 deletions spec/fixtures/thor2.thor
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require "thor"

class MySimpleThor2 < Thor2
class_option "verbose", :type => :boolean
class_option "mode", :type => :string

desc "checked", "a command with checked"
def checked(*args)
puts [options, args].inspect
[options, args]
end
end

MySimpleThor2.start(ARGV)

20 changes: 20 additions & 0 deletions spec/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,24 @@ def silence_warnings
end

alias silence capture

# Runs the fixture in a different process.
# Useful to deal with exit_on_failure?, which interrupts the tests when it calls `exit`
# This doesn't run on ruby 1.8.7
def run_thor_fixture_standalone(fixture, command)
gem_dir = File.expand_path("#{File.dirname(__FILE__)}/..")
lib_path = "#{gem_dir}/lib"
script_path = "#{gem_dir}/spec/fixtures/#{fixture}.thor"
ruby_lib = ENV['RUBYLIB'].nil? ? lib_path : "#{lib_path}:#{ENV['RUBYLIB']}"

if command.is_a?(String)
full_command = "ruby #{script_path} #{command}"
elsif command.is_a?(Array)
full_command = ['ruby', script_path] + command
end

require 'open3'
stdout, stderr, status = Open3.capture3({'RUBYLIB' => ruby_lib}, *full_command)
[stdout, stderr, status]
end
end
6 changes: 4 additions & 2 deletions spec/script_exit_status_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ def thor_command(command)
end

it "a command that raises a Thor::Error exits with a status of 1" do
expect(thor_command("error")).to eq(1)
_stdout, _stderr, status = run_thor_fixture_standalone('exit_status', ['error'])
expect(status.exitstatus).to eq(1)
end

it "a command that does not raise a Thor::Error exits with a status of 0" do
expect(thor_command("ok")).to eq(0)
_stdout, _stderr, status = run_thor_fixture_standalone('exit_status', ['ok'])
expect(status.exitstatus).to eq(0)
end
end if RUBY_VERSION > "1.8.7"
26 changes: 26 additions & 0 deletions spec/thor2_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require "helper"

describe Thor2 do
describe "#check_unknown_options!" do
it "still accept options and arguments" do
stdout, _, status = run_thor_fixture_standalone('thor2', %w(checked command --verbose))

expect(stdout.strip).to eq [{"verbose" => true}, %w[command]].inspect
expect(status.exitstatus).to eq(0)
end

it "does not accept if non-option that looks like an option is after an argument and exits with code 1" do
_stdout, stderr, status = run_thor_fixture_standalone('thor2', %w(checked command --foo --bar))
expect(stderr.strip).to eq("Unknown switches '--foo, --bar'")
expect(status.exitstatus).to eq(1)
end
end if RUBY_VERSION > "1.8.7"

it "checks the default type" do
expect do
Class.new(Thor2) do
option "bar", :type => :numeric, :default => "foo"
end
end.to raise_error(ArgumentError, "Expected numeric default value for '--bar'; got \"foo\" (string)")
end
end