pax_global_header 0000666 0000000 0000000 00000000064 14002057672 0014515 g ustar 00root root 0000000 0000000 52 comment=b60e9eba629f2b0be4da9f2ab6208798f3945692
spec/ 0000775 0000000 0000000 00000000000 14002057672 0012013 5 ustar 00root root 0000000 0000000 spec/actions/ 0000775 0000000 0000000 00000000000 14002057672 0013453 5 ustar 00root root 0000000 0000000 spec/actions/create_file_spec.rb 0000664 0000000 0000000 00000014207 14002057672 0017260 0 ustar 00root root 0000000 0000000 require "helper"
require "thor/actions"
describe Thor::Actions::CreateFile do
before do
@silence = false
::FileUtils.rm_rf(destination_root)
end
def create_file(destination = nil, config = {}, options = {})
@base = MyCounter.new([1, 2], options, :destination_root => destination_root)
allow(@base).to receive(:file_name).and_return("rdoc")
@action = Thor::Actions::CreateFile.new(@base, destination, "CONFIGURATION", {:verbose => !@silence}.merge(config))
end
def invoke!
capture(:stdout) { @action.invoke! }
end
def revoke!
capture(:stdout) { @action.revoke! }
end
def silence!
@silence = true
end
describe "#invoke!" do
it "creates a file" do
create_file("doc/config.rb")
invoke!
expect(File.exist?(File.join(destination_root, "doc/config.rb"))).to be true
end
it "does not create a file if pretending" do
create_file("doc/config.rb", {}, :pretend => true)
invoke!
expect(File.exist?(File.join(destination_root, "doc/config.rb"))).to be false
end
it "shows created status to the user" do
create_file("doc/config.rb")
expect(invoke!).to eq(" create doc/config.rb\n")
end
it "does not show any information if log status is false" do
silence!
create_file("doc/config.rb")
expect(invoke!).to be_empty
end
it "returns the given destination" do
capture(:stdout) do
expect(create_file("doc/config.rb").invoke!).to eq("doc/config.rb")
end
end
it "converts encoded instructions" do
create_file("doc/%file_name%.rb.tt")
invoke!
expect(File.exist?(File.join(destination_root, "doc/rdoc.rb.tt"))).to be true
end
describe "when file exists" do
before do
create_file("doc/config.rb")
invoke!
end
describe "and is identical" do
it "shows identical status" do
create_file("doc/config.rb")
invoke!
expect(invoke!).to eq(" identical doc/config.rb\n")
end
end
describe "and is not identical" do
before do
File.open(File.join(destination_root, "doc/config.rb"), "w") { |f| f.write("FOO = 3") }
end
it "shows forced status to the user if force is given" do
expect(create_file("doc/config.rb", {}, :force => true)).not_to be_identical
expect(invoke!).to eq(" force doc/config.rb\n")
end
it "shows skipped status to the user if skip is given" do
expect(create_file("doc/config.rb", {}, :skip => true)).not_to be_identical
expect(invoke!).to eq(" skip doc/config.rb\n")
end
it "shows forced status to the user if force is configured" do
expect(create_file("doc/config.rb", :force => true)).not_to be_identical
expect(invoke!).to eq(" force doc/config.rb\n")
end
it "shows skipped status to the user if skip is configured" do
expect(create_file("doc/config.rb", :skip => true)).not_to be_identical
expect(invoke!).to eq(" skip doc/config.rb\n")
end
it "shows conflict status to the user" do
file = File.join(destination_root, "doc/config.rb")
expect(create_file("doc/config.rb")).not_to be_identical
expect(Thor::LineEditor).to receive(:readline).with("Overwrite #{file}? (enter \"h\" for help) [Ynaqdhm] ", anything).and_return("s")
content = invoke!
expect(content).to match(%r{conflict doc/config\.rb})
expect(content).to match(%r{skip doc/config\.rb})
end
it "creates the file if the file collision menu returns true" do
create_file("doc/config.rb")
expect(Thor::LineEditor).to receive(:readline).and_return("y")
expect(invoke!).to match(%r{force doc/config\.rb})
end
it "skips the file if the file collision menu returns false" do
create_file("doc/config.rb")
expect(Thor::LineEditor).to receive(:readline).and_return("n")
expect(invoke!).to match(%r{skip doc/config\.rb})
end
it "executes the block given to show file content" do
create_file("doc/config.rb")
expect(Thor::LineEditor).to receive(:readline).and_return("d", "n")
expect(@base.shell).to receive(:system).with(/diff -u/)
invoke!
end
it "executes the block given to run merge tool" do
create_file("doc/config.rb")
allow(@base.shell).to receive(:merge_tool).and_return("meld")
expect(Thor::LineEditor).to receive(:readline).and_return("m")
expect(@base.shell).to receive(:system).with(/meld/)
invoke!
end
end
end
context "when file exists and it causes a file clash" do
before do
create_file("doc/config")
invoke!
end
it "generates a file clash" do
create_file("doc/config/config.rb")
expect(invoke!).to eq(" file_clash doc/config/config.rb\n")
end
end
context "when directory exists and it causes a file clash" do
before do
create_file("doc/config/hello")
invoke!
end
it "generates a file clash" do
create_file("doc/config")
expect(invoke!) .to eq(" file_clash doc/config\n")
end
end
end
describe "#revoke!" do
it "removes the destination file" do
create_file("doc/config.rb")
invoke!
revoke!
expect(File.exist?(@action.destination)).to be false
end
it "does not raise an error if the file does not exist" do
create_file("doc/config.rb")
revoke!
expect(File.exist?(@action.destination)).to be false
end
end
describe "#exists?" do
it "returns true if the destination file exists" do
create_file("doc/config.rb")
expect(@action.exists?).to be false
invoke!
expect(@action.exists?).to be true
end
end
describe "#identical?" do
it "returns true if the destination file exists and is identical" do
create_file("doc/config.rb")
expect(@action.identical?).to be false
invoke!
expect(@action.identical?).to be true
end
end
end
spec/actions/create_link_spec.rb 0000664 0000000 0000000 00000006313 14002057672 0017275 0 ustar 00root root 0000000 0000000 require "helper"
require "thor/actions"
require "tempfile"
describe Thor::Actions::CreateLink, :unless => windows? do
before do
@hardlink_to = File.join(Dir.tmpdir, "linkdest.rb")
::FileUtils.rm_rf(destination_root)
::FileUtils.rm_rf(@hardlink_to)
end
let(:config) { {} }
let(:options) { {} }
let(:base) do
base = MyCounter.new([1, 2], options, :destination_root => destination_root)
allow(base).to receive(:file_name).and_return("rdoc")
base
end
let(:tempfile) { Tempfile.new("config.rb") }
let(:source) { tempfile.path }
let(:destination) { "doc/config.rb" }
let(:action) do
Thor::Actions::CreateLink.new(base, destination, source, config)
end
def invoke!
capture(:stdout) { action.invoke! }
end
def revoke!
capture(:stdout) { action.revoke! }
end
describe "#invoke!" do
context "specifying :symbolic => true" do
let(:config) { {:symbolic => true} }
it "creates a symbolic link" do
invoke!
destination_path = File.join(destination_root, "doc/config.rb")
expect(File.exist?(destination_path)).to be true
expect(File.symlink?(destination_path)).to be true
end
end
context "specifying :symbolic => false" do
let(:config) { {:symbolic => false} }
let(:destination) { @hardlink_to }
it "creates a hard link" do
invoke!
destination_path = @hardlink_to
expect(File.exist?(destination_path)).to be true
expect(File.symlink?(destination_path)).to be false
end
end
it "creates a symbolic link by default" do
invoke!
destination_path = File.join(destination_root, "doc/config.rb")
expect(File.exist?(destination_path)).to be true
expect(File.symlink?(destination_path)).to be true
end
context "specifying :pretend => true" do
let(:options) { {:pretend => true} }
it "does not create a link" do
invoke!
expect(File.exist?(File.join(destination_root, "doc/config.rb"))).to be false
end
end
it "shows created status to the user" do
expect(invoke!).to eq(" create doc/config.rb\n")
end
context "specifying :verbose => false" do
let(:config) { {:verbose => false} }
it "does not show any information" do
expect(invoke!).to be_empty
end
end
end
describe "#identical?" do
it "returns true if the destination link exists and is identical" do
expect(action.identical?).to be false
invoke!
expect(action.identical?).to be true
end
context "with source path relative to destination" do
let(:source) do
destination_path = File.dirname(File.join(destination_root, destination))
Pathname.new(super()).relative_path_from(Pathname.new(destination_path)).to_s
end
it "returns true if the destination link exists and is identical" do
expect(action.identical?).to be false
invoke!
expect(action.identical?).to be true
end
end
end
describe "#revoke!" do
it "removes the symbolic link of non-existent destination" do
invoke!
File.delete(tempfile.path)
revoke!
expect(File.symlink?(action.destination)).to be false
end
end
end
spec/actions/directory_spec.rb 0000664 0000000 0000000 00000014012 14002057672 0017014 0 ustar 00root root 0000000 0000000 require "tmpdir"
require "helper"
require "thor/actions"
describe Thor::Actions::Directory do
before do
::FileUtils.rm_rf(destination_root)
allow(invoker).to receive(:file_name).and_return("rdoc")
end
def invoker
@invoker ||= WhinyGenerator.new([1, 2], {}, :destination_root => destination_root)
end
def revoker
@revoker ||= WhinyGenerator.new([1, 2], {}, :destination_root => destination_root, :behavior => :revoke)
end
def invoke!(*args, &block)
capture(:stdout) { invoker.directory(*args, &block) }
end
def revoke!(*args, &block)
capture(:stdout) { revoker.directory(*args, &block) }
end
def exists_and_identical?(source_path, destination_path)
%w(config.rb README).each do |file|
source = File.join(source_root, source_path, file)
destination = File.join(destination_root, destination_path, file)
expect(File.exist?(destination)).to be true
expect(FileUtils.identical?(source, destination)).to be true
end
end
describe "#invoke!" do
it "raises an error if the source does not exist" do
expect do
invoke! "unknown"
end.to raise_error(Thor::Error, /Could not find "unknown" in any of your source paths/)
end
it "does not create a directory in pretend mode" do
invoke! "doc", "ghost", :pretend => true
expect(File.exist?("ghost")).to be false
end
it "copies the whole directory recursively to the default destination" do
invoke! "doc"
exists_and_identical?("doc", "doc")
end
it "copies the whole directory recursively to the specified destination" do
invoke! "doc", "docs"
exists_and_identical?("doc", "docs")
end
it "copies only the first level files if recursive" do
invoke! ".", "commands", :recursive => false
file = File.join(destination_root, "commands", "group.thor")
expect(File.exist?(file)).to be true
file = File.join(destination_root, "commands", "doc")
expect(File.exist?(file)).to be false
file = File.join(destination_root, "commands", "doc", "README")
expect(File.exist?(file)).to be false
end
it "ignores files within excluding/ directories when exclude_pattern is provided" do
invoke! "doc", "docs", :exclude_pattern => %r{excluding/}
file = File.join(destination_root, "docs", "excluding", "rdoc.rb")
expect(File.exist?(file)).to be false
end
it "copies and evaluates files within excluding/ directory when no exclude_pattern is present" do
invoke! "doc", "docs"
file = File.join(destination_root, "docs", "excluding", "rdoc.rb")
expect(File.exist?(file)).to be true
expect(File.read(file)).to eq("BAR = BAR\n")
end
it "copies files from the source relative to the current path" do
invoker.inside "doc" do
invoke! "."
end
exists_and_identical?("doc", "doc")
end
it "copies and evaluates templates" do
invoke! "doc", "docs"
file = File.join(destination_root, "docs", "rdoc.rb")
expect(File.exist?(file)).to be true
expect(File.read(file)).to eq("FOO = FOO\n")
end
it "copies directories and preserves file mode" do
invoke! "preserve", "preserved", :mode => :preserve
original = File.join(source_root, "preserve", "script.sh")
copy = File.join(destination_root, "preserved", "script.sh")
expect(File.stat(original).mode).to eq(File.stat(copy).mode)
end
it "copies directories" do
invoke! "doc", "docs"
file = File.join(destination_root, "docs", "components")
expect(File.exist?(file)).to be true
expect(File.directory?(file)).to be true
end
it "does not copy .empty_directory files" do
invoke! "doc", "docs"
file = File.join(destination_root, "docs", "components", ".empty_directory")
expect(File.exist?(file)).to be false
end
it "copies directories even if they are empty" do
invoke! "doc/components", "docs/components"
file = File.join(destination_root, "docs", "components")
expect(File.exist?(file)).to be true
end
it "does not copy empty directories twice" do
content = invoke!("doc/components", "docs/components")
expect(content).not_to match(/exist/)
end
it "logs status" do
content = invoke!("doc")
expect(content).to match(%r{create doc/README})
expect(content).to match(%r{create doc/config\.rb})
expect(content).to match(%r{create doc/rdoc\.rb})
expect(content).to match(%r{create doc/components})
end
it "yields a block" do
checked = false
invoke!("doc") do |content|
checked ||= !!(content =~ /FOO/)
end
expect(checked).to be true
end
it "works with glob characters in the path" do
content = invoke!("app{1}")
expect(content).to match(%r{create app\{1\}/README})
end
context "windows temp directories", :if => windows? do
let(:spec_dir) { File.join(@temp_dir, "spec") }
before(:each) do
@temp_dir = Dir.mktmpdir("thor")
Dir.mkdir(spec_dir)
File.new(File.join(spec_dir, "spec_helper.rb"), "w").close
end
after(:each) { FileUtils.rm_rf(@temp_dir) }
it "works with windows temp dir" do
invoke! spec_dir, "specs"
file = File.join(destination_root, "specs")
expect(File.exist?(file)).to be true
expect(File.directory?(file)).to be true
end
end
end
describe "#revoke!" do
it "removes the destination file" do
invoke! "doc"
revoke! "doc"
expect(File.exist?(File.join(destination_root, "doc", "README"))).to be false
expect(File.exist?(File.join(destination_root, "doc", "config.rb"))).to be false
expect(File.exist?(File.join(destination_root, "doc", "components"))).to be false
end
it "works with glob characters in the path" do
invoke! "app{1}"
expect(File.exist?(File.join(destination_root, "app{1}", "README"))).to be true
revoke! "app{1}"
expect(File.exist?(File.join(destination_root, "app{1}", "README"))).to be false
end
end
end
spec/actions/empty_directory_spec.rb 0000664 0000000 0000000 00000007131 14002057672 0020236 0 ustar 00root root 0000000 0000000 require "helper"
require "thor/actions"
describe Thor::Actions::EmptyDirectory do
before do
::FileUtils.rm_rf(destination_root)
end
def empty_directory(destination, options = {})
@action = Thor::Actions::EmptyDirectory.new(base, destination)
end
def invoke!
capture(:stdout) { @action.invoke! }
end
def revoke!
capture(:stdout) { @action.revoke! }
end
def base
@base ||= MyCounter.new([1, 2], {}, :destination_root => destination_root)
end
describe "#destination" do
it "returns the full destination with the destination_root" do
expect(empty_directory("doc").destination).to eq(File.join(destination_root, "doc"))
end
it "takes relative root into account" do
base.inside("doc") do
expect(empty_directory("contents").destination).to eq(File.join(destination_root, "doc", "contents"))
end
end
end
describe "#relative_destination" do
it "returns the relative destination to the original destination root" do
base.inside("doc") do
expect(empty_directory("contents").relative_destination).to eq("doc/contents")
end
end
end
describe "#given_destination" do
it "returns the destination supplied by the user" do
base.inside("doc") do
expect(empty_directory("contents").given_destination).to eq("contents")
end
end
end
describe "#invoke!" do
it "copies the file to the specified destination" do
empty_directory("doc")
invoke!
expect(File.exist?(File.join(destination_root, "doc"))).to be true
end
it "shows created status to the user" do
empty_directory("doc")
expect(invoke!).to eq(" create doc\n")
end
it "does not create a directory if pretending" do
base.inside("foo", :pretend => true) do
empty_directory("ghost")
end
expect(File.exist?(File.join(base.destination_root, "ghost"))).to be false
end
describe "when directory exists" do
it "shows exist status" do
empty_directory("doc")
invoke!
expect(invoke!).to eq(" exist doc\n")
end
end
end
describe "#revoke!" do
it "removes the destination file" do
empty_directory("doc")
invoke!
revoke!
expect(File.exist?(@action.destination)).to be false
end
end
describe "#exists?" do
it "returns true if the destination file exists" do
empty_directory("doc")
expect(@action.exists?).to be false
invoke!
expect(@action.exists?).to be true
end
end
context "protected methods" do
describe "#convert_encoded_instructions" do
before do
empty_directory("test_dir")
allow(@action.base).to receive(:file_name).and_return("expected")
end
it "accepts and executes a 'legal' %\w+% encoded instruction" do
expect(@action.send(:convert_encoded_instructions, "%file_name%.txt")).to eq("expected.txt")
end
it "accepts and executes a private %\w+% encoded instruction" do
@action.base.extend Module.new {
def private_file_name
"expected"
end
private :private_file_name
}
expect(@action.send(:convert_encoded_instructions, "%private_file_name%.txt")).to eq("expected.txt")
end
it "ignores an 'illegal' %\w+% encoded instruction" do
expect(@action.send(:convert_encoded_instructions, "%some_name%.txt")).to eq("%some_name%.txt")
end
it "ignores incorrectly encoded instruction" do
expect(@action.send(:convert_encoded_instructions, "%some.name%.txt")).to eq("%some.name%.txt")
end
end
end
end
spec/actions/file_manipulation_spec.rb 0000664 0000000 0000000 00000044664 14002057672 0020527 0 ustar 00root root 0000000 0000000 require "helper"
class Application; end
module ApplicationHelper; end
describe Thor::Actions do
def runner(options = {}, behavior = :invoke)
@runner ||= MyCounter.new([1], options, :destination_root => destination_root, :behavior => behavior)
end
def action(*args, &block)
capture(:stdout) { runner.send(*args, &block) }
end
def exists_and_identical?(source, destination)
destination = File.join(destination_root, destination)
expect(File.exist?(destination)).to be true
source = File.join(source_root, source)
expect(FileUtils).to be_identical(source, destination)
end
def file
File.join(destination_root, "foo")
end
before do
::FileUtils.rm_rf(destination_root)
end
describe "#chmod" do
it "executes the command given" do
expect(FileUtils).to receive(:chmod_R).with(0755, file)
action :chmod, "foo", 0755
end
it "does not execute the command if pretending" do
expect(FileUtils).not_to receive(:chmod_R)
runner(:pretend => true)
action :chmod, "foo", 0755
end
it "logs status" do
expect(FileUtils).to receive(:chmod_R).with(0755, file)
expect(action(:chmod, "foo", 0755)).to eq(" chmod foo\n")
end
it "does not log status if required" do
expect(FileUtils).to receive(:chmod_R).with(0755, file)
expect(action(:chmod, "foo", 0755, :verbose => false)).to be_empty
end
end
describe "#copy_file" do
it "copies file from source to default destination" do
action :copy_file, "command.thor"
exists_and_identical?("command.thor", "command.thor")
end
it "copies file from source to the specified destination" do
action :copy_file, "command.thor", "foo.thor"
exists_and_identical?("command.thor", "foo.thor")
end
it "copies file from the source relative to the current path" do
runner.inside("doc") do
action :copy_file, "README"
end
exists_and_identical?("doc/README", "doc/README")
end
it "copies file from source to default destination and preserves file mode" do
action :copy_file, "preserve/script.sh", :mode => :preserve
original = File.join(source_root, "preserve/script.sh")
copy = File.join(destination_root, "preserve/script.sh")
expect(File.stat(original).mode).to eq(File.stat(copy).mode)
end
it "copies file from source to default destination and preserves file mode for templated filenames" do
expect(runner).to receive(:filename).and_return("app")
action :copy_file, "preserve/%filename%.sh", :mode => :preserve
original = File.join(source_root, "preserve/%filename%.sh")
copy = File.join(destination_root, "preserve/app.sh")
expect(File.stat(original).mode).to eq(File.stat(copy).mode)
end
it "logs status" do
expect(action(:copy_file, "command.thor")).to eq(" create command.thor\n")
end
it "accepts a block to change output" do
action :copy_file, "command.thor" do |content|
"OMG" + content
end
expect(File.read(File.join(destination_root, "command.thor"))).to match(/^OMG/)
end
end
describe "#link_file", :unless => windows? do
it "links file from source to default destination" do
action :link_file, "command.thor"
exists_and_identical?("command.thor", "command.thor")
end
it "links file from source to the specified destination" do
action :link_file, "command.thor", "foo.thor"
exists_and_identical?("command.thor", "foo.thor")
end
it "links file from the source relative to the current path" do
runner.inside("doc") do
action :link_file, "README"
end
exists_and_identical?("doc/README", "doc/README")
end
it "logs status" do
expect(action(:link_file, "command.thor")).to eq(" create command.thor\n")
end
end
describe "#get" do
it "copies file from source to the specified destination" do
action :get, "doc/README", "docs/README"
exists_and_identical?("doc/README", "docs/README")
end
it "uses just the source basename as destination if none is specified" do
action :get, "doc/README"
exists_and_identical?("doc/README", "README")
end
it "allows the destination to be set as a block result" do
action(:get, "doc/README") { "docs/README" }
exists_and_identical?("doc/README", "docs/README")
end
it "yields file content to a block" do
action :get, "doc/README" do |content|
expect(content).to eq("__start__\nREADME\n__end__\n")
end
end
it "logs status" do
expect(action(:get, "doc/README", "docs/README")).to eq(" create docs/README\n")
end
it "accepts http remote sources" do
body = "__start__\nHTTPFILE\n__end__\n"
stub_request(:get, "http://example.com/file.txt").to_return(:body => body.dup)
action :get, "http://example.com/file.txt" do |content|
expect(a_request(:get, "http://example.com/file.txt")).to have_been_made
expect(content).to eq(body)
end
end
it "accepts https remote sources" do
body = "__start__\nHTTPSFILE\n__end__\n"
stub_request(:get, "https://example.com/file.txt").to_return(:body => body.dup)
action :get, "https://example.com/file.txt" do |content|
expect(a_request(:get, "https://example.com/file.txt")).to have_been_made
expect(content).to eq(body)
end
end
end
describe "#template" do
it "allows using block helpers in the template" do
action :template, "doc/block_helper.rb"
file = File.join(destination_root, "doc/block_helper.rb")
expect(File.read(file)).to eq("Hello world!")
end
it "evaluates the template given as source" do
runner.instance_variable_set("@klass", "Config")
action :template, "doc/config.rb"
file = File.join(destination_root, "doc/config.rb")
expect(File.read(file)).to eq("class Config; end\n")
end
it "copies the template to the specified destination" do
runner.instance_variable_set("@klass", "Config")
action :template, "doc/config.rb", "doc/configuration.rb"
file = File.join(destination_root, "doc/configuration.rb")
expect(File.exist?(file)).to be true
end
it "converts encoded instructions" do
expect(runner).to receive(:file_name).and_return("rdoc")
action :template, "doc/%file_name%.rb.tt"
file = File.join(destination_root, "doc/rdoc.rb")
expect(File.exist?(file)).to be true
end
it "accepts filename without .tt for template method" do
expect(runner).to receive(:file_name).and_return("rdoc")
action :template, "doc/%file_name%.rb"
file = File.join(destination_root, "doc/rdoc.rb")
expect(File.exist?(file)).to be true
end
it "logs status" do
runner.instance_variable_set("@klass", "Config")
expect(capture(:stdout) { runner.template("doc/config.rb") }).to eq(" create doc/config.rb\n")
end
it "accepts a block to change output" do
runner.instance_variable_set("@klass", "Config")
action :template, "doc/config.rb" do |content|
"OMG" + content
end
expect(File.read(File.join(destination_root, "doc/config.rb"))).to match(/^OMG/)
end
it "accepts a context to use as the binding" do
begin
@klass = "FooBar"
action :template, "doc/config.rb", :context => eval("binding")
expect(File.read(File.join(destination_root, "doc/config.rb"))).to eq("class FooBar; end\n")
ensure
remove_instance_variable(:@klass)
end
end
it "guesses the destination name when given only a source" do
action :template, "doc/config.yaml.tt"
file = File.join(destination_root, "doc/config.yaml")
expect(File.exist?(file)).to be true
end
it "has proper ERB stacktraces" do
error = nil
begin
action :template, "template/bad_config.yaml.tt"
rescue => e
error = e
end
expect(error).to be_a NameError
expect(error.backtrace.to_s).to include("bad_config.yaml.tt:2")
end
end
describe "when changing existent files" do
before do
::FileUtils.cp_r(source_root, destination_root)
end
def file
File.join(destination_root, "doc", "README")
end
describe "#remove_file" do
it "removes the file given" do
action :remove_file, "doc/README"
expect(File.exist?(file)).to be false
end
it "removes directories too" do
action :remove_dir, "doc"
expect(File.exist?(File.join(destination_root, "doc"))).to be false
end
it "does not remove if pretending" do
runner(:pretend => true)
action :remove_file, "doc/README"
expect(File.exist?(file)).to be true
end
it "logs status" do
expect(action(:remove_file, "doc/README")).to eq(" remove doc/README\n")
end
it "does not log status if required" do
expect(action(:remove_file, "doc/README", :verbose => false)).to be_empty
end
end
describe "#gsub_file" do
context "with invoke behavior" do
it "replaces the content in the file" do
action :gsub_file, "doc/README", "__start__", "START"
expect(File.binread(file)).to eq("START\nREADME\n__end__\n")
end
it "does not replace if pretending" do
runner(:pretend => true)
action :gsub_file, "doc/README", "__start__", "START"
expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n")
end
it "accepts a block" do
action(:gsub_file, "doc/README", "__start__") { |match| match.gsub("__", "").upcase }
expect(File.binread(file)).to eq("START\nREADME\n__end__\n")
end
it "logs status" do
expect(action(:gsub_file, "doc/README", "__start__", "START")).to eq(" gsub doc/README\n")
end
it "does not log status if required" do
expect(action(:gsub_file, file, "__", :verbose => false) { |match| match * 2 }).to be_empty
end
end
context "with revoke behavior" do
context "and no force option" do
it "does not replace the content in the file" do
runner({}, :revoke)
action :gsub_file, "doc/README", "__start__", "START"
expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n")
end
it "does not replace if pretending" do
runner({ :pretend => true }, :revoke)
action :gsub_file, "doc/README", "__start__", "START"
expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n")
end
it "does not replace the content in the file when given a block" do
runner({}, :revoke)
action(:gsub_file, "doc/README", "__start__") { |match| match.gsub("__", "").upcase }
expect(File.binread(file)).to eq("__start__\nREADME\n__end__\n")
end
it "does not log status" do
runner({}, :revoke)
expect(action(:gsub_file, "doc/README", "__start__", "START")).to be_empty
end
it "does not log status if required" do
runner({}, :revoke)
expect(action(:gsub_file, file, "__", :verbose => false) { |match| match * 2 }).to be_empty
end
end
context "and force option" do
it "replaces the content in the file" do
runner({}, :revoke)
action :gsub_file, "doc/README", "__start__", "START", :force => true
expect(File.binread(file)).to eq("START\nREADME\n__end__\n")
end
it "does not replace if pretending" do
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
it "replaces the content in the file when given a block" do
runner({}, :revoke)
action(:gsub_file, "doc/README", "__start__", :force => true) { |match| match.gsub("__", "").upcase }
expect(File.binread(file)).to eq("START\nREADME\n__end__\n")
end
it "logs status" do
runner({}, :revoke)
expect(action(:gsub_file, "doc/README", "__start__", "START", :force => true)).to eq(" gsub doc/README\n")
end
it "does not log status if required" do
runner({}, :revoke)
expect(action(:gsub_file, file, "__", :verbose => false, :force => true) { |match| match * 2 }).to be_empty
end
end
end
end
describe "#append_to_file" do
it "appends content to the file" do
action :append_to_file, "doc/README", "END\n"
expect(File.binread(file)).to eq("__start__\nREADME\n__end__\nEND\n")
end
it "accepts a block" do
action(:append_to_file, "doc/README") { "END\n" }
expect(File.binread(file)).to eq("__start__\nREADME\n__end__\nEND\n")
end
it "logs status" do
expect(action(:append_to_file, "doc/README", "END")).to eq(" append doc/README\n")
end
end
describe "#prepend_to_file" do
it "prepends content to the file" do
action :prepend_to_file, "doc/README", "START\n"
expect(File.binread(file)).to eq("START\n__start__\nREADME\n__end__\n")
end
it "accepts a block" do
action(:prepend_to_file, "doc/README") { "START\n" }
expect(File.binread(file)).to eq("START\n__start__\nREADME\n__end__\n")
end
it "logs status" do
expect(action(:prepend_to_file, "doc/README", "START")).to eq(" prepend doc/README\n")
end
end
describe "#inject_into_class" do
def file
File.join(destination_root, "application.rb")
end
it "appends content to a class" do
action :inject_into_class, "application.rb", Application, " filter_parameters :password\n"
expect(File.binread(file)).to eq("class Application < Base\n filter_parameters :password\nend\n")
end
it "accepts a block" do
action(:inject_into_class, "application.rb", Application) { " filter_parameters :password\n" }
expect(File.binread(file)).to eq("class Application < Base\n filter_parameters :password\nend\n")
end
it "logs status" do
expect(action(:inject_into_class, "application.rb", Application, " filter_parameters :password\n")).to eq(" insert application.rb\n")
end
it "does not append if class name does not match" do
action :inject_into_class, "application.rb", "App", " filter_parameters :password\n"
expect(File.binread(file)).to eq("class Application < Base\nend\n")
end
end
describe "#inject_into_module" do
def file
File.join(destination_root, "application_helper.rb")
end
it "appends content to a module" do
action :inject_into_module, "application_helper.rb", ApplicationHelper, " def help; 'help'; end\n"
expect(File.binread(file)).to eq("module ApplicationHelper\n def help; 'help'; end\nend\n")
end
it "accepts a block" do
action(:inject_into_module, "application_helper.rb", ApplicationHelper) { " def help; 'help'; end\n" }
expect(File.binread(file)).to eq("module ApplicationHelper\n def help; 'help'; end\nend\n")
end
it "logs status" do
expect(action(:inject_into_module, "application_helper.rb", ApplicationHelper, " def help; 'help'; end\n")).to eq(" insert application_helper.rb\n")
end
it "does not append if module name does not match" do
action :inject_into_module, "application_helper.rb", "App", " def help; 'help'; end\n"
expect(File.binread(file)).to eq("module ApplicationHelper\nend\n")
end
end
end
describe "when adjusting comments" do
before do
::FileUtils.cp_r(source_root, destination_root)
end
def file
File.join(destination_root, "doc", "COMMENTER")
end
unmodified_comments_file = /__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\norange\n purple\n ind#igo\n # ind#igo\n__end__/
describe "#uncomment_lines" do
it "uncomments all matching lines in the file" do
action :uncomment_lines, "doc/COMMENTER", "green"
expect(File.binread(file)).to match(/__start__\n greenblue\n#\n# yellowblue\n#yellowred\n greenred\norange\n purple\n ind#igo\n # ind#igo\n__end__/)
action :uncomment_lines, "doc/COMMENTER", "red"
expect(File.binread(file)).to match(/__start__\n greenblue\n#\n# yellowblue\nyellowred\n greenred\norange\n purple\n ind#igo\n # ind#igo\n__end__/)
end
it "correctly uncomments lines with hashes in them" do
action :uncomment_lines, "doc/COMMENTER", "ind#igo"
expect(File.binread(file)).to match(/__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\norange\n purple\n ind#igo\n ind#igo\n__end__/)
end
it "does not modify already uncommented lines in the file" do
action :uncomment_lines, "doc/COMMENTER", "orange"
action :uncomment_lines, "doc/COMMENTER", "purple"
expect(File.binread(file)).to match(unmodified_comments_file)
end
it "does not uncomment the wrong line when uncommenting lines preceded by blank commented line" do
action :uncomment_lines, "doc/COMMENTER", "yellow"
expect(File.binread(file)).to match(/__start__\n # greenblue\n#\nyellowblue\nyellowred\n #greenred\norange\n purple\n ind#igo\n # ind#igo\n__end__/)
end
end
describe "#comment_lines" do
it "comments lines which are not commented" do
action :comment_lines, "doc/COMMENTER", "orange"
expect(File.binread(file)).to match(/__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\n# orange\n purple\n ind#igo\n # ind#igo\n__end__/)
action :comment_lines, "doc/COMMENTER", "purple"
expect(File.binread(file)).to match(/__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\n# orange\n # purple\n ind#igo\n # ind#igo\n__end__/)
end
it "correctly comments lines with hashes in them" do
action :comment_lines, "doc/COMMENTER", "ind#igo"
expect(File.binread(file)).to match(/__start__\n # greenblue\n#\n# yellowblue\n#yellowred\n #greenred\norange\n purple\n # ind#igo\n # ind#igo\n__end__/)
end
it "does not modify already commented lines" do
action :comment_lines, "doc/COMMENTER", "green"
expect(File.binread(file)).to match(unmodified_comments_file)
end
end
end
end
spec/actions/inject_into_file_spec.rb 0000664 0000000 0000000 00000014337 14002057672 0020326 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require "helper"
require "thor/actions"
describe Thor::Actions::InjectIntoFile do
before do
::FileUtils.rm_rf(destination_root)
::FileUtils.cp_r(source_root, destination_root)
end
def invoker(options = {})
@invoker ||= MyCounter.new([1, 2], options, :destination_root => destination_root)
end
def revoker
@revoker ||= MyCounter.new([1, 2], {}, :destination_root => destination_root, :behavior => :revoke)
end
def invoke!(*args, &block)
capture(:stdout) { invoker.insert_into_file(*args, &block) }
end
def revoke!(*args, &block)
capture(:stdout) { revoker.insert_into_file(*args, &block) }
end
def file
File.join(destination_root, "doc/README")
end
describe "#invoke!" do
it "changes the file adding content after the flag" do
invoke! "doc/README", "\nmore content", :after => "__start__"
expect(File.read(file)).to eq("__start__\nmore content\nREADME\n__end__\n")
end
it "changes the file adding content before the flag" do
invoke! "doc/README", "more content\n", :before => "__end__"
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
end
it "appends content to the file if before and after arguments not provided" do
invoke!("doc/README", "more content\n")
expect(File.read(file)).to eq("__start__\nREADME\n__end__\nmore content\n")
end
it "does not change the file and logs the warning if flag not found in the file" do
expect(invoke!("doc/README", "more content\n", after: "whatever")).to(
eq("#{Thor::Actions::WARNINGS[:unchanged_no_flag]} doc/README\n")
)
end
it "accepts data as a block" do
invoke! "doc/README", :before => "__end__" do
"more content\n"
end
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
end
it "logs status" do
expect(invoke!("doc/README", "\nmore content", :after => "__start__")).to eq(" insert doc/README\n")
end
it "does not change the file if pretending" do
invoker :pretend => true
invoke! "doc/README", "\nmore content", :after => "__start__"
expect(File.read(file)).to eq("__start__\nREADME\n__end__\n")
end
it "does not change the file if already includes content" do
invoke! "doc/README", :before => "__end__" do
"more content\n"
end
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
invoke! "doc/README", :before => "__end__" do
"more content\n"
end
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
end
it "does not attempt to change the file if it doesn't exist - instead raises Thor::Error" do
expect do
invoke! "idontexist", :before => "something" do
"any content"
end
end.to raise_error(Thor::Error, /does not appear to exist/)
expect(File.exist?("idontexist")).to be_falsey
end
it "does not attempt to change the file if it doesn't exist and pretending" do
expect do
invoker :pretend => true
invoke! "idontexist", :before => "something" do
"any content"
end
end.not_to raise_error
expect(File.exist?("idontexist")).to be_falsey
end
it "does change the file if already includes content and :force is true" do
invoke! "doc/README", :before => "__end__" do
"more content\n"
end
expect(File.read(file)).to eq("__start__\nREADME\nmore content\n__end__\n")
invoke! "doc/README", :before => "__end__", :force => true do
"more content\n"
end
expect(File.read(file)).to eq("__start__\nREADME\nmore content\nmore content\n__end__\n")
end
it "can insert chinese" do
encoding_original = Encoding.default_external
begin
Encoding.default_external = Encoding.find("UTF-8")
invoke! "doc/README.zh", "\n中文", :after => "__start__"
expect(File.read(File.join(destination_root, "doc/README.zh"))).to eq("__start__\n中文\n说明\n__end__\n")
ensure
Encoding.default_external = encoding_original
end
end
end
describe "#revoke!" do
it "subtracts the destination file after injection" do
invoke! "doc/README", "\nmore content", :after => "__start__"
revoke! "doc/README", "\nmore content", :after => "__start__"
expect(File.read(file)).to eq("__start__\nREADME\n__end__\n")
end
it "subtracts the destination file before injection" do
invoke! "doc/README", "more content\n", :before => "__start__"
revoke! "doc/README", "more content\n", :before => "__start__"
expect(File.read(file)).to eq("__start__\nREADME\n__end__\n")
end
it "subtracts even with double after injection" do
invoke! "doc/README", "\nmore content", :after => "__start__"
invoke! "doc/README", "\nanother stuff", :after => "__start__"
revoke! "doc/README", "\nmore content", :after => "__start__"
expect(File.read(file)).to eq("__start__\nanother stuff\nREADME\n__end__\n")
end
it "subtracts even with double before injection" do
invoke! "doc/README", "more content\n", :before => "__start__"
invoke! "doc/README", "another stuff\n", :before => "__start__"
revoke! "doc/README", "more content\n", :before => "__start__"
expect(File.read(file)).to eq("another stuff\n__start__\nREADME\n__end__\n")
end
it "subtracts when prepending" do
invoke! "doc/README", "more content\n", :after => /\A/
invoke! "doc/README", "another stuff\n", :after => /\A/
revoke! "doc/README", "more content\n", :after => /\A/
expect(File.read(file)).to eq("another stuff\n__start__\nREADME\n__end__\n")
end
it "subtracts when appending" do
invoke! "doc/README", "more content\n", :before => /\z/
invoke! "doc/README", "another stuff\n", :before => /\z/
revoke! "doc/README", "more content\n", :before => /\z/
expect(File.read(file)).to eq("__start__\nREADME\n__end__\nanother stuff\n")
end
it "shows progress information to the user" do
invoke!("doc/README", "\nmore content", :after => "__start__")
expect(revoke!("doc/README", "\nmore content", :after => "__start__")).to eq(" subtract doc/README\n")
end
end
end
spec/actions_spec.rb 0000664 0000000 0000000 00000032676 14002057672 0015030 0 ustar 00root root 0000000 0000000 require "helper"
describe Thor::Actions do
def runner(options = {})
@runner ||= MyCounter.new([1], options, :destination_root => destination_root)
end
def action(*args, &block)
capture(:stdout) { runner.send(*args, &block) }
end
def file
File.join(destination_root, "foo")
end
describe "on include" do
it "adds runtime options to the base class" do
expect(MyCounter.class_options.keys).to include(:pretend)
expect(MyCounter.class_options.keys).to include(:force)
expect(MyCounter.class_options.keys).to include(:quiet)
expect(MyCounter.class_options.keys).to include(:skip)
end
end
describe "#initialize" do
it "has default behavior invoke" do
expect(runner.behavior).to eq(:invoke)
end
it "can have behavior revoke" do
expect(MyCounter.new([1], {}, :behavior => :revoke).behavior).to eq(:revoke)
end
it "when behavior is set to force, overwrite options" do
runner = MyCounter.new([1], {:force => false, :skip => true}, :behavior => :force)
expect(runner.behavior).to eq(:invoke)
expect(runner.options.force).to be true
expect(runner.options.skip).not_to be true
end
it "when behavior is set to skip, overwrite options" do
runner = MyCounter.new([1], %w(--force), :behavior => :skip)
expect(runner.behavior).to eq(:invoke)
expect(runner.options.force).not_to be true
expect(runner.options.skip).to be true
end
end
describe "accessors" do
describe "#destination_root=" do
it "gets the current directory and expands the path to set the root" do
base = MyCounter.new([1])
base.destination_root = "here"
expect(base.destination_root).to eq(File.expand_path(File.join(File.dirname(__FILE__), "..", "here")))
end
it "does not use the current directory if one is given" do
root = File.expand_path("/")
base = MyCounter.new([1])
base.destination_root = root
expect(base.destination_root).to eq(root)
end
it "uses the current directory if none is given" do
base = MyCounter.new([1])
expect(base.destination_root).to eq(File.expand_path(File.join(File.dirname(__FILE__), "..")))
end
end
describe "#relative_to_original_destination_root" do
it "returns the path relative to the absolute root" do
expect(runner.relative_to_original_destination_root(file)).to eq("foo")
end
it "does not remove dot if required" do
expect(runner.relative_to_original_destination_root(file, false)).to eq("./foo")
end
it "always use the absolute root" do
runner.inside("foo") do
expect(runner.relative_to_original_destination_root(file)).to eq("foo")
end
end
it "creates proper relative paths for absolute file location" do
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
runner.destination_root = "/app"
expect(runner.relative_to_original_destination_root("/something/app/project")).to eq("/something/app/project")
end
it "doesn't removes the root path from the absolute path only if it is only the partial name of the directory" do
runner.destination_root = "/app"
expect(runner.relative_to_original_destination_root("/application/project")).to eq("/application/project")
end
it "removes the root path from the absolute path only once" do
runner.destination_root = "/app"
expect(runner.relative_to_original_destination_root("/app/app/project")).to eq("app/project")
end
it "does not fail with files containing regexp characters" do
runner = MyCounter.new([1], {}, :destination_root => File.join(destination_root, "fo[o-b]ar"))
expect(runner.relative_to_original_destination_root("bar")).to eq("bar")
end
describe "#source_paths_for_search" do
it "add source_root to source_paths_for_search" do
expect(MyCounter.source_paths_for_search).to include(File.expand_path("fixtures", File.dirname(__FILE__)))
end
it "keeps only current source root in source paths" do
expect(ClearCounter.source_paths_for_search).to include(File.expand_path("fixtures/bundle", File.dirname(__FILE__)))
expect(ClearCounter.source_paths_for_search).not_to include(File.expand_path("fixtures", File.dirname(__FILE__)))
end
it "customized source paths should be before source roots" do
expect(ClearCounter.source_paths_for_search[0]).to eq(File.expand_path("fixtures/doc", File.dirname(__FILE__)))
expect(ClearCounter.source_paths_for_search[1]).to eq(File.expand_path("fixtures/bundle", File.dirname(__FILE__)))
end
it "keeps inherited source paths at the end" do
expect(ClearCounter.source_paths_for_search.last).to eq(File.expand_path("fixtures/broken", File.dirname(__FILE__)))
end
end
end
describe "#find_in_source_paths" do
it "raises an error if source path is empty" do
expect do
A.new.find_in_source_paths("foo")
end.to raise_error(Thor::Error, /Currently you have no source paths/)
end
it "finds a template inside the source path" do
expect(runner.find_in_source_paths("doc")).to eq(File.expand_path("doc", source_root))
expect { runner.find_in_source_paths("README") }.to raise_error(Thor::Error, /Could not find "README" in any of your source paths./)
new_path = File.join(source_root, "doc")
runner.instance_variable_set(:@source_paths, nil)
runner.source_paths.unshift(new_path)
expect(runner.find_in_source_paths("README")).to eq(File.expand_path("README", new_path))
end
end
end
describe "#inside" do
it "executes the block inside the given folder" do
runner.inside("foo") do
expect(Dir.pwd).to eq(file)
end
end
it "changes the base root" do
runner.inside("foo") do
expect(runner.destination_root).to eq(file)
end
end
it "creates the directory if it does not exist" do
runner.inside("foo") do
expect(File.exist?(file)).to be true
end
end
describe "when pretending" do
it "no directories should be created" do
runner.inside("bar", :pretend => true) {}
expect(File.exist?("bar")).to be false
end
end
describe "when verbose" do
it "logs status" do
expect(capture(:stdout) do
runner.inside("foo", :verbose => true) {}
end).to match(/inside foo/)
end
it "uses padding in next status" do
expect(capture(:stdout) do
runner.inside("foo", :verbose => true) do
runner.say_status :cool, :padding
end
end).to match(/cool padding/)
end
it "removes padding after block" do
expect(capture(:stdout) do
runner.inside("foo", :verbose => true) {}
runner.say_status :no, :padding
end).to match(/no padding/)
end
end
end
describe "#in_root" do
it "executes the block in the root folder" do
runner.inside("foo") do
runner.in_root { expect(Dir.pwd).to eq(destination_root) }
end
end
it "changes the base root" do
runner.inside("foo") do
runner.in_root { expect(runner.destination_root).to eq(destination_root) }
end
end
it "returns to the previous state" do
runner.inside("foo") do
runner.in_root {}
expect(runner.destination_root).to eq(file)
end
end
end
describe "#apply" do
before do
@template = <<-TEMPLATE.dup
@foo = "FOO"
say_status :cool, :padding
TEMPLATE
allow(@template).to receive(:read).and_return(@template)
@file = "/"
allow(runner).to receive(:open).and_return(@template)
end
it "accepts a URL as the path" do
@file = "http://gist.github.com/103208.txt"
stub_request(:get, @file)
expect(runner).to receive(:apply).with(@file).and_return(@template)
action(:apply, @file)
end
it "accepts a secure URL as the path" do
@file = "https://gist.github.com/103208.txt"
stub_request(:get, @file)
expect(runner).to receive(:apply).with(@file).and_return(@template)
action(:apply, @file)
end
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)
action(:apply, @file)
end
it "opens a file and executes its content in the instance binding" do
action :apply, @file
expect(runner.instance_variable_get("@foo")).to eq("FOO")
end
it "applies padding to the content inside the file" do
expect(action(:apply, @file)).to match(/cool padding/)
end
it "logs its status" do
expect(action(:apply, @file)).to match(/ apply #{@file}\n/)
end
it "does not log status" do
content = action(:apply, @file, :verbose => false)
expect(content).to match(/cool padding/)
expect(content).not_to match(/apply http/)
end
end
describe "#run" do
describe "when not pretending" do
before do
expect(runner).to receive(:system).with("ls")
end
it "executes the command given" do
action :run, "ls"
end
it "logs status" do
expect(action(:run, "ls")).to eq(" run ls from \".\"\n")
end
it "does not log status if required" do
expect(action(:run, "ls", :verbose => false)).to be_empty
end
it "accepts a color as status" do
expect(runner.shell).to receive(:say_status).with(:run, 'ls from "."', :yellow)
action :run, "ls", :verbose => :yellow
end
end
describe "when pretending" do
it "doesn't execute the command" do
runner = MyCounter.new([1], %w(--pretend))
expect(runner).not_to receive(:system)
runner.run("ls", :verbose => false)
end
end
describe "when not capturing" do
it "aborts when abort_on_failure is given and command fails" do
expect { action :run, "false", :abort_on_failure => true }.to raise_error(SystemExit)
end
it "succeeds when abort_on_failure is given and command succeeds" do
expect { action :run, "true", :abort_on_failure => true }.not_to raise_error
end
it "supports env option" do
expect { action :run, "echo $BAR", :env => { "BAR" => "foo" } }.to output("foo\n").to_stdout_from_any_process
end
end
describe "when capturing" do
it "aborts when abort_on_failure is given, capture is given and command fails" do
expect { action :run, "false", :abort_on_failure => true, :capture => true }.to raise_error(SystemExit)
end
it "succeeds when abort_on_failure is given and command succeeds" do
expect { action :run, "true", :abort_on_failure => true, :capture => true }.not_to raise_error
end
it "supports env option" do
silence(:stdout) do
expect(runner.run "echo $BAR", :env => { "BAR" => "foo" }, :capture => true).to eq("foo\n")
end
end
end
context "exit_on_failure? is true" do
before do
allow(MyCounter).to receive(:exit_on_failure?).and_return(true)
end
it "aborts when command fails even if abort_on_failure is not given" do
expect { action :run, "false" }.to raise_error(SystemExit)
end
it "does not abort when abort_on_failure is false even if the command fails" do
expect { action :run, "false", :abort_on_failure => false }.not_to raise_error
end
end
end
describe "#run_ruby_script" do
before do
allow(Thor::Util).to receive(:ruby_command).and_return("/opt/jruby")
expect(runner).to receive(:system).with("/opt/jruby script.rb")
end
it "executes the ruby script" do
action :run_ruby_script, "script.rb"
end
it "logs status" do
expect(action(:run_ruby_script, "script.rb")).to eq(" run jruby script.rb from \".\"\n")
end
it "does not log status if required" do
expect(action(:run_ruby_script, "script.rb", :verbose => false)).to be_empty
end
end
describe "#thor" do
it "executes the thor command" do
expect(runner).to receive(:system).with("thor list")
action :thor, :list, :verbose => true
end
it "converts extra arguments to command arguments" do
expect(runner).to receive(:system).with("thor list foo bar")
action :thor, :list, "foo", "bar"
end
it "converts options hash to switches" do
expect(runner).to receive(:system).with("thor list foo bar --foo")
action :thor, :list, "foo", "bar", :foo => true
expect(runner).to receive(:system).with("thor list --foo 1 2 3")
action :thor, :list, :foo => [1, 2, 3]
end
it "logs status" do
expect(runner).to receive(:system).with("thor list")
expect(action(:thor, :list)).to eq(" run thor list from \".\"\n")
end
it "does not log status if required" do
expect(runner).to receive(:system).with("thor list --foo 1 2 3")
expect(action(:thor, :list, :foo => [1, 2, 3], :verbose => false)).to be_empty
end
it "captures the output when :capture is given" do
expect(runner).to receive(:run).with("list", hash_including(:capture => true))
action :thor, :list, :capture => true
end
end
end
spec/base_spec.rb 0000664 0000000 0000000 00000022731 14002057672 0014271 0 ustar 00root root 0000000 0000000 require "helper"
require "thor/base"
class Amazing
desc "hello", "say hello"
def hello
puts "Hello"
end
end
describe Thor::Base do
describe "#initialize" do
it "sets arguments array" do
base = MyCounter.new [1, 2]
expect(base.first).to eq(1)
expect(base.second).to eq(2)
end
it "sets arguments default values" do
base = MyCounter.new [1]
expect(base.second).to eq(2)
end
it "sets options default values" do
base = MyCounter.new [1, 2]
expect(base.options[:third]).to eq(3)
end
it "allows options to be given as symbols or strings" do
base = MyCounter.new [1, 2], :third => 4
expect(base.options[:third]).to eq(4)
base = MyCounter.new [1, 2], "third" => 4
expect(base.options[:third]).to eq(4)
end
it "creates options with indifferent access" do
base = MyCounter.new [1, 2], :third => 3
expect(base.options["third"]).to eq(3)
end
it "creates options with magic predicates" do
base = MyCounter.new [1, 2], :third => 3
expect(base.options.third).to eq(3)
end
end
describe "#no_commands" do
it "avoids methods being added as commands" do
expect(MyScript.commands.keys).to include("animal")
expect(MyScript.commands.keys).not_to include("this_is_not_a_command")
expect(MyScript.commands.keys).not_to include("neither_is_this")
end
end
describe "#argument" do
it "sets a value as required and creates an accessor for it" do
expect(MyCounter.start(%w(1 2 --third 3))[0]).to eq(1)
expect(Scripts::MyScript.start(%w(zoo my_special_param --param=normal_param))).to eq("my_special_param")
end
it "does not set a value in the options hash" do
expect(BrokenCounter.start(%w(1 2 --third 3))[0]).to be nil
end
end
describe "#arguments" do
it "returns the arguments for the class" do
expect(MyCounter.arguments.size).to be(2)
end
end
describe ":aliases" do
it "supports string aliases without a dash prefix" do
expect(MyCounter.start(%w(1 2 -z 3))[4]).to eq(3)
end
it "supports symbol aliases" do
expect(MyCounter.start(%w(1 2 -y 3))[5]).to eq(3)
expect(MyCounter.start(%w(1 2 -r 3))[5]).to eq(3)
end
end
describe "#class_option" do
it "sets options class wise" do
expect(MyCounter.start(%w(1 2 --third 3))[2]).to eq(3)
end
it "does not create an accessor for it" do
expect(BrokenCounter.start(%w(1 2 --third 3))[3]).to be false
end
end
describe "#class_options" do
it "sets default options overwriting superclass definitions" do
options = Scripts::MyScript.class_options
expect(options[:force]).not_to be_required
end
end
describe "#remove_argument" do
it "removes previously defined arguments from class" do
expect(ClearCounter.arguments).to be_empty
end
it "undefine accessors if required" do
expect(ClearCounter.new).not_to respond_to(:first)
expect(ClearCounter.new).not_to respond_to(:second)
end
end
describe "#remove_class_option" do
it "removes previous defined class option" do
expect(ClearCounter.class_options[:third]).to be nil
end
end
describe "#class_options_help" do
before do
@content = capture(:stdout) { MyCounter.help(Thor::Base.shell.new) }
end
it "shows option's description" do
expect(@content).to match(/# The third argument/)
end
it "shows usage with banner content" do
expect(@content).to match(/\[\-\-third=THREE\]/)
end
it "shows default values below descriptions" do
expect(@content).to match(/# Default: 3/)
end
it "shows options in different groups" do
expect(@content).to match(/Options\:/)
expect(@content).to match(/Runtime options\:/)
expect(@content).to match(/\-p, \[\-\-pretend\]/)
end
it "use padding in options that do not have aliases" do
expect(@content).to match(/^ -t, \[--third/)
expect(@content).to match(/^ \[--fourth/)
end
it "allows extra options to be given" do
hash = {"Foo" => B.class_options.values}
content = capture(:stdout) { MyCounter.send(:class_options_help, Thor::Base.shell.new, hash) }
expect(content).to match(/Foo options\:/)
expect(content).to match(/--last-name=LAST_NAME/)
end
it "displays choices for enums" do
content = capture(:stdout) { Enum.help(Thor::Base.shell.new) }
expect(content).to match(/Possible values\: apple, banana/)
end
end
describe "#namespace" do
it "returns the default class namespace" do
expect(Scripts::MyScript.namespace).to eq("scripts:my_script")
end
it "sets a namespace to the class" do
expect(Scripts::MyDefaults.namespace).to eq("default")
end
end
describe "#group" do
it "sets a group" do
expect(MyScript.group).to eq("script")
end
it "inherits the group from parent" do
expect(MyChildScript.group).to eq("script")
end
it "defaults to standard if no group is given" do
expect(Amazing.group).to eq("standard")
end
end
describe "#subclasses" do
it "tracks its subclasses in an Array" do
expect(Thor::Base.subclasses).to include(MyScript)
expect(Thor::Base.subclasses).to include(MyChildScript)
expect(Thor::Base.subclasses).to include(Scripts::MyScript)
end
end
describe "#subclass_files" do
it "returns tracked subclasses, grouped by the files they come from" do
thorfile = File.join(File.dirname(__FILE__), "fixtures", "script.thor")
expect(Thor::Base.subclass_files[File.expand_path(thorfile)]).to eq([
MyScript, MyScript::AnotherScript, MyChildScript, Barn,
PackageNameScript, Scripts::MyScript, Scripts::MyDefaults,
Scripts::ChildDefault, Scripts::Arities
])
end
it "tracks a single subclass across multiple files" do
thorfile = File.join(File.dirname(__FILE__), "fixtures", "command.thor")
expect(Thor::Base.subclass_files[File.expand_path(thorfile)]).to include(Amazing)
expect(Thor::Base.subclass_files[File.expand_path(__FILE__)]).to include(Amazing)
end
end
describe "#commands" do
it "returns a list with all commands defined in this class" do
expect(MyChildScript.new).to respond_to("animal")
expect(MyChildScript.commands.keys).to include("animal")
end
it "raises an error if a command with reserved word is defined" do
expect do
klass = Class.new(Thor::Group)
klass.class_eval "def shell; end"
end.to raise_error(RuntimeError, /"shell" is a Thor reserved word and cannot be defined as command/)
end
end
describe "#all_commands" do
it "returns a list with all commands defined in this class plus superclasses" do
expect(MyChildScript.new).to respond_to("foo")
expect(MyChildScript.all_commands.keys).to include("foo")
end
end
describe "#remove_command" do
it "removes the command from its commands hash" do
expect(MyChildScript.all_commands.keys).not_to include("name_with_dashes")
expect(MyChildScript.commands.keys).not_to include("boom")
end
it "undefines the method if desired" do
expect(MyChildScript.new).not_to respond_to("boom")
end
end
describe "#from_superclass" do
it "does not send a method to the superclass if the superclass does not respond to it" do
expect(MyCounter.get_from_super).to eq(13)
end
end
describe "#start" do
it "raises an error instead of rescuing if THOR_DEBUG=1 is given" do
begin
ENV["THOR_DEBUG"] = "1"
expect do
MyScript.start %w(what --debug)
end.to raise_error(Thor::UndefinedCommandError, 'Could not find command "what" in "my_script" namespace.')
ensure
ENV["THOR_DEBUG"] = nil
end
end
it "raises an error instead of rescuing if :debug option is given" do
expect do
MyScript.start %w(what), :debug => true
end.to raise_error(Thor::UndefinedCommandError, 'Could not find command "what" in "my_script" namespace.')
end
it "suggests commands that are similar if there is a typo" do
expected = "Could not find command \"paintz\" in \"barn\" namespace.\n"
expected << "Did you mean? \"paint\"\n" if Thor::Correctable
expect(capture(:stderr) { Barn.start(%w(paintz)) }).to eq(expected)
end
it "does not steal args" do
args = %w(foo bar --force true)
MyScript.start(args)
expect(args).to eq(%w(foo bar --force true))
end
it "checks unknown options" do
expect(capture(:stderr) do
MyScript.start(%w(foo bar --force true --unknown baz))
end.strip).to eq("Unknown switches \"--unknown\"")
end
it "checks unknown options except specified" do
expect(capture(:stderr) do
expect(MyScript.start(%w(with_optional NAME --omg --invalid))).to eq(["NAME", {}, %w(--omg --invalid)])
end.strip).to be_empty
end
end
describe "attr_*" do
it "does not add attr_reader as a command" do
expect(capture(:stderr) { MyScript.start(%w(another_attribute)) }).to match(/Could not find/)
end
it "does not add attr_writer as a command" do
expect(capture(:stderr) { MyScript.start(%w(another_attribute= foo)) }).to match(/Could not find/)
end
it "does not add attr_accessor as a command" do
expect(capture(:stderr) { MyScript.start(["some_attribute"]) }).to match(/Could not find/)
expect(capture(:stderr) { MyScript.start(["some_attribute=", "foo"]) }).to match(/Could not find/)
end
end
end
spec/command_spec.rb 0000664 0000000 0000000 00000005726 14002057672 0015002 0 ustar 00root root 0000000 0000000 require "helper"
describe Thor::Command do
def command(options = {}, usage = "can_has")
options.each do |key, value|
options[key] = Thor::Option.parse(key, value)
end
@command ||= Thor::Command.new(:can_has, "I can has cheezburger", "I can has cheezburger\nLots and lots of it", usage, options)
end
describe "#formatted_usage" do
it "includes namespace within usage" do
object = Struct.new(:namespace, :arguments).new("foo", [])
expect(command(:bar => :required).formatted_usage(object)).to eq("foo:can_has --bar=BAR")
end
it "includes subcommand name within subcommand usage" do
object = Struct.new(:namespace, :arguments).new("main:foo", [])
expect(command(:bar => :required).formatted_usage(object, false, true)).to eq("foo can_has --bar=BAR")
end
it "removes default from namespace" do
object = Struct.new(:namespace, :arguments).new("default:foo", [])
expect(command(:bar => :required).formatted_usage(object)).to eq(":foo:can_has --bar=BAR")
end
it "injects arguments into usage" do
options = {:required => true, :type => :string}
object = Struct.new(:namespace, :arguments).new("foo", [Thor::Argument.new(:bar, options)])
expect(command(:foo => :required).formatted_usage(object)).to eq("foo:can_has BAR --foo=FOO")
end
it "allows multiple usages" do
object = Struct.new(:namespace, :arguments).new("foo", [])
expect(command({ :bar => :required }, ["can_has FOO", "can_has BAR"]).formatted_usage(object, false)).to eq("can_has FOO --bar=BAR\ncan_has BAR --bar=BAR")
end
end
describe "#dynamic" do
it "creates a dynamic command with the given name" do
expect(Thor::DynamicCommand.new("command").name).to eq("command")
expect(Thor::DynamicCommand.new("command").description).to eq("A dynamically-generated command")
expect(Thor::DynamicCommand.new("command").usage).to eq("command")
expect(Thor::DynamicCommand.new("command").options).to eq({})
end
it "does not invoke an existing method" do
dub = double
expect(dub.class).to receive(:handle_no_command_error).with("to_s")
Thor::DynamicCommand.new("to_s").run(dub)
end
end
describe "#dup" do
it "dup options hash" do
command = Thor::Command.new("can_has", nil, nil, nil, :foo => true, :bar => :required)
command.dup.options.delete(:foo)
expect(command.options[:foo]).to be
end
end
describe "#run" do
it "runs a command by calling a method in the given instance" do
dub = double
expect(dub).to receive(:can_has) { |*args| args }
expect(command.run(dub, [1, 2, 3])).to eq([1, 2, 3])
end
it "raises an error if the method to be invoked is private" do
klass = Class.new do
def self.handle_no_command_error(name)
name
end
def can_has
"fail"
end
private :can_has
end
expect(command.run(klass.new)).to eq("can_has")
end
end
end
spec/core_ext/ 0000775 0000000 0000000 00000000000 14002057672 0013623 5 ustar 00root root 0000000 0000000 spec/core_ext/hash_with_indifferent_access_spec.rb 0000664 0000000 0000000 00000005267 14002057672 0023050 0 ustar 00root root 0000000 0000000 require "helper"
require "thor/core_ext/hash_with_indifferent_access"
describe Thor::CoreExt::HashWithIndifferentAccess do
before do
@hash = Thor::CoreExt::HashWithIndifferentAccess.new :foo => "bar", "baz" => "bee", :force => true
end
it "has values accessible by either strings or symbols" do
expect(@hash["foo"]).to eq("bar")
expect(@hash[:foo]).to eq("bar")
expect(@hash.values_at(:foo, :baz)).to eq(%w(bar bee))
expect(@hash.delete(:foo)).to eq("bar")
end
it "supports fetch" do
expect(@hash.fetch("foo")).to eq("bar")
expect(@hash.fetch("foo", nil)).to eq("bar")
expect(@hash.fetch(:foo)).to eq("bar")
expect(@hash.fetch(:foo, nil)).to eq("bar")
expect(@hash.fetch("baz")).to eq("bee")
expect(@hash.fetch("baz", nil)).to eq("bee")
expect(@hash.fetch(:baz)).to eq("bee")
expect(@hash.fetch(:baz, nil)).to eq("bee")
expect { @hash.fetch(:missing) }.to raise_error(IndexError)
expect(@hash.fetch(:missing, :found)).to eq(:found)
end
it "has key checkable by either strings or symbols" do
expect(@hash.key?("foo")).to be true
expect(@hash.key?(:foo)).to be true
expect(@hash.key?("nothing")).to be false
expect(@hash.key?(:nothing)).to be false
end
it "handles magic boolean predicates" do
expect(@hash.force?).to be true
expect(@hash.foo?).to be true
expect(@hash.nothing?).to be false
end
it "handles magic comparisons" do
expect(@hash.foo?("bar")).to be true
expect(@hash.foo?("bee")).to be false
end
it "maps methods to keys" do
expect(@hash.foo).to eq(@hash["foo"])
end
it "merges keys independent if they are symbols or strings" do
@hash["force"] = false
@hash[:baz] = "boom"
expect(@hash[:force]).to eq(false)
expect(@hash["baz"]).to eq("boom")
end
it "creates a new hash by merging keys independent if they are symbols or strings" do
other = @hash.merge("force" => false, :baz => "boom")
expect(other[:force]).to eq(false)
expect(other["baz"]).to eq("boom")
end
it "converts to a traditional hash" do
expect(@hash.to_hash.class).to eq(Hash)
expect(@hash).to eq("foo" => "bar", "baz" => "bee", "force" => true)
end
it "handles reverse_merge" do
other = {:foo => "qux", "boo" => "bae"}
new_hash = @hash.reverse_merge(other)
expect(@hash.object_id).not_to eq(new_hash.object_id)
expect(new_hash[:foo]).to eq("bar")
expect(new_hash[:boo]).to eq("bae")
end
it "handles reverse_merge!" do
other = {:foo => "qux", "boo" => "bae"}
new_hash = @hash.reverse_merge!(other)
expect(@hash.object_id).to eq(new_hash.object_id)
expect(new_hash[:foo]).to eq("bar")
expect(new_hash[:boo]).to eq("bae")
end
end
spec/exit_condition_spec.rb 0000664 0000000 0000000 00000000662 14002057672 0016375 0 ustar 00root root 0000000 0000000 require "helper"
require "thor/base"
describe "Exit conditions" do
it "exits 0, not bubble up EPIPE, if EPIPE is raised" do
epiped = false
command = Class.new(Thor) do
desc "my_action", "testing EPIPE"
define_method :my_action do
epiped = true
raise Errno::EPIPE
end
end
expect { command.start(["my_action"]) }.to raise_error(SystemExit)
expect(epiped).to eq(true)
end
end
spec/fixtures/ 0000775 0000000 0000000 00000000000 14002057672 0013664 5 ustar 00root root 0000000 0000000 spec/fixtures/application.rb 0000664 0000000 0000000 00000000035 14002057672 0016512 0 ustar 00root root 0000000 0000000 class Application < Base
end
spec/fixtures/application_helper.rb 0000664 0000000 0000000 00000000035 14002057672 0020051 0 ustar 00root root 0000000 0000000 module ApplicationHelper
end
spec/fixtures/app{1}/ 0000775 0000000 0000000 00000000000 14002057672 0015115 5 ustar 00root root 0000000 0000000 spec/fixtures/app{1}/README 0000664 0000000 0000000 00000000031 14002057672 0015767 0 ustar 00root root 0000000 0000000 __start__
README
__end__
spec/fixtures/command.thor 0000664 0000000 0000000 00000000446 14002057672 0016204 0 ustar 00root root 0000000 0000000 # module: random
class Amazing < Thor
def self.exit_on_failure?
false
end
desc "describe NAME", "say that someone is amazing"
method_options :forcefully => :boolean
def describe(name, opts)
ret = "#{name} is amazing"
puts opts["forcefully"] ? ret.upcase : ret
end
end
spec/fixtures/doc/ 0000775 0000000 0000000 00000000000 14002057672 0014431 5 ustar 00root root 0000000 0000000 spec/fixtures/doc/%file_name%.rb.tt 0000664 0000000 0000000 00000000023 14002057672 0017430 0 ustar 00root root 0000000 0000000 FOO = <%= "FOO" %>
spec/fixtures/doc/COMMENTER 0000664 0000000 0000000 00000000154 14002057672 0015705 0 ustar 00root root 0000000 0000000 __start__
# greenblue
#
# yellowblue
#yellowred
#greenred
orange
purple
ind#igo
# ind#igo
__end__
spec/fixtures/doc/README 0000664 0000000 0000000 00000000031 14002057672 0015303 0 ustar 00root root 0000000 0000000 __start__
README
__end__
spec/fixtures/doc/README.zh 0000664 0000000 0000000 00000000031 14002057672 0015723 0 ustar 00root root 0000000 0000000 __start__
说明
__end__
spec/fixtures/doc/block_helper.rb 0000664 0000000 0000000 00000000041 14002057672 0017402 0 ustar 00root root 0000000 0000000 <% world do -%>
Hello
<% end -%>
spec/fixtures/doc/components/ 0000775 0000000 0000000 00000000000 14002057672 0016616 5 ustar 00root root 0000000 0000000 spec/fixtures/doc/components/.empty_directory 0000664 0000000 0000000 00000000000 14002057672 0022027 0 ustar 00root root 0000000 0000000 spec/fixtures/doc/config.rb 0000664 0000000 0000000 00000000031 14002057672 0016215 0 ustar 00root root 0000000 0000000 class <%= @klass %>; end
spec/fixtures/doc/config.yaml.tt 0000664 0000000 0000000 00000000021 14002057672 0017201 0 ustar 00root root 0000000 0000000 --- Hi from yaml
spec/fixtures/doc/excluding/ 0000775 0000000 0000000 00000000000 14002057672 0016413 5 ustar 00root root 0000000 0000000 spec/fixtures/doc/excluding/%file_name%.rb.tt 0000664 0000000 0000000 00000000023 14002057672 0021412 0 ustar 00root root 0000000 0000000 BAR = <%= "BAR" %>
spec/fixtures/enum.thor 0000664 0000000 0000000 00000000310 14002057672 0015520 0 ustar 00root root 0000000 0000000 class Enum < Thor::Group
include Thor::Actions
desc "snack"
class_option "fruit", :aliases => "-f", :type => :string, :enum => %w(apple banana)
def snack
puts options['fruit']
end
end
spec/fixtures/exit_status.thor 0000664 0000000 0000000 00000000414 14002057672 0017135 0 ustar 00root root 0000000 0000000 require "thor"
class ExitStatus < Thor
def self.exit_on_failure?
true
end
desc "error", "exit with a planned error"
def error
raise Thor::Error.new("planned error")
end
desc "ok", "exit with no error"
def ok
end
end
ExitStatus.start(ARGV)
spec/fixtures/group.thor 0000664 0000000 0000000 00000004536 14002057672 0015726 0 ustar 00root root 0000000 0000000 class MyCounter < Thor::Group
include Thor::Actions
add_runtime_options!
def self.exit_on_failure?
false
end
def self.get_from_super
from_superclass(:get_from_super, 13)
end
source_root File.expand_path(File.dirname(__FILE__))
source_paths << File.expand_path("broken", File.dirname(__FILE__))
argument :first, :type => :numeric
argument :second, :type => :numeric, :default => 2
class_option :third, :type => :numeric, :desc => "The third argument", :default => 3,
:banner => "THREE", :aliases => "-t"
class_option :fourth, :type => :numeric, :desc => "The fourth argument"
class_option :simple, :type => :numeric, :aliases => 'z'
class_option :symbolic, :type => :numeric, :aliases => [:y, :r]
desc <<-FOO
Description:
This generator runs three commands: one, two and three.
FOO
def one
first
end
def two
second
end
def three
options[:third]
end
def four
options[:fourth]
end
def five
options[:simple]
end
def six
options[:symbolic]
end
def self.inherited(base)
super
base.source_paths.unshift(File.expand_path(File.join(File.dirname(__FILE__), "doc")))
end
no_commands do
def world(&block)
result = capture(&block)
concat(result.strip + " world!")
end
end
end
class ClearCounter < MyCounter
remove_argument :first, :second, :undefine => true
remove_class_option :third
def self.source_root
File.expand_path(File.join(File.dirname(__FILE__), "bundle"))
end
end
class BrokenCounter < MyCounter
namespace "app:broken:counter"
class_option :fail, :type => :boolean, :default => false
class << self
undef_method :source_root
end
def one
options[:first]
end
def four
respond_to?(:fail)
end
def five
options[:fail] ? this_method_does_not_exist : 5
end
end
class WhinyGenerator < Thor::Group
include Thor::Actions
def self.source_root
File.expand_path(File.dirname(__FILE__))
end
def wrong_arity(required)
end
end
class CommandConflict < Thor::Group
desc "A group with the same name as a default command"
def group
puts "group"
end
end
class ParentGroup < Thor::Group
private
def foo
"foo"
end
def baz(name = 'baz')
name
end
end
class ChildGroup < ParentGroup
def bar
"bar"
end
public_command :foo, :baz
end
spec/fixtures/help.thor 0000664 0000000 0000000 00000000276 14002057672 0015517 0 ustar 00root root 0000000 0000000 Bundler.require :development, :default
class Help < Thor
desc :bugs, "ALL THE BUGZ!"
option "--not_help", :type => :boolean
def bugs
puts "Invoked!"
end
end
Help.start(ARGV)
spec/fixtures/invoke.thor 0000664 0000000 0000000 00000003654 14002057672 0016065 0 ustar 00root root 0000000 0000000 class A < Thor
include Thor::Actions
desc "one", "invoke one"
def one
p 1
invoke :two
invoke :three
end
desc "two", "invoke two"
def two
p 2
invoke :three
end
desc "three", "invoke three"
def three
p 3
end
desc "four", "invoke four"
def four
p 4
invoke "defined:five"
end
desc "five N", "check if number is equal 5"
def five(number)
number == 5
end
desc "invoker", "invoke a b command"
def invoker(*args)
invoke :b, :one, ["Jose"]
end
end
class B < Thor
class_option :last_name, :type => :string
desc "one FIRST_NAME", "invoke one"
def one(first_name)
"#{options.last_name}, #{first_name}"
end
desc "two", "invoke two"
def two
options
end
desc "three", "invoke three"
def three
self
end
desc "four", "invoke four"
option :defaulted_value, :type => :string, :default => 'default'
def four
options.defaulted_value
end
end
class C < Thor::Group
include Thor::Actions
def one
p 1
end
def two
p 2
end
def three
p 3
end
end
class Defined < Thor::Group
class_option :unused, :type => :boolean, :desc => "This option has no use"
def one
p 1
invoke "a:two"
invoke "a:three"
invoke "a:four"
invoke "defined:five"
end
def five
p 5
end
def print_status
say_status :finished, :counting
end
end
class E < Thor::Group
invoke Defined
end
class F < Thor::Group
invoke "b:one" do |instance, klass, command|
instance.invoke klass, command, [ "Jose" ], :last_name => "Valim"
end
end
class G < Thor::Group
class_option :invoked, :type => :string, :default => "defined"
invoke_from_option :invoked
end
class H < Thor::Group
class_option :defined, :type => :boolean, :default => true
invoke_from_option :defined
end
class I < Thor
desc "two", "Two"
def two
current_command_chain
end
end
class J < Thor
desc "i", "I"
subcommand :one, I
end
spec/fixtures/path with spaces 0000664 0000000 0000000 00000000000 14002057672 0016724 0 ustar 00root root 0000000 0000000 spec/fixtures/preserve/ 0000775 0000000 0000000 00000000000 14002057672 0015517 5 ustar 00root root 0000000 0000000 spec/fixtures/preserve/%filename%.sh 0000775 0000000 0000000 00000000022 14002057672 0017742 0 ustar 00root root 0000000 0000000 #!/bin/sh
exit 0
spec/fixtures/preserve/script.sh 0000775 0000000 0000000 00000000022 14002057672 0017354 0 ustar 00root root 0000000 0000000 #!/bin/sh
exit 0
spec/fixtures/script.thor 0000664 0000000 0000000 00000012027 14002057672 0016070 0 ustar 00root root 0000000 0000000 class MyScript < Thor
check_unknown_options! :except => :with_optional
def self.exit_on_failure?
false
end
attr_accessor :some_attribute
attr_writer :another_attribute
attr_reader :another_attribute
private
attr_reader :private_attribute
public
group :script
default_command :example_default_command
map "-T" => :animal, ["-f", "--foo"] => :foo
map "animal_prison" => "zoo"
desc "zoo", "zoo around"
def zoo
true
end
desc "animal TYPE", "horse around"
no_commands do
no_commands do
def this_is_not_a_command
end
end
def neither_is_this
end
end
def animal(type)
[type]
end
map "hid" => "hidden"
desc "hidden TYPE", "this is hidden", :hide => true
def hidden(type)
[type]
end
map "fu" => "zoo"
desc "foo BAR", <<END
do some fooing
This is more info!
Everyone likes more info!
END
method_option :force, :type => :boolean, :desc => "Force to do some fooing"
def foo(bar)
[bar, options]
end
method_option :all, :desc => "Do bazing for all the things"
desc ["baz THING", "baz --all"], "super cool"
def baz(thing = nil)
raise if thing.nil? && !options.include?(:all)
end
desc "example_default_command", "example!"
method_options :with => :string
def example_default_command
options.empty? ? "default command" : options
end
desc "call_myself_with_wrong_arity", "get the right error"
def call_myself_with_wrong_arity
call_myself_with_wrong_arity(4)
end
desc "call_unexistent_method", "Call unexistent method inside a command"
def call_unexistent_method
boom!
end
desc "long_description", "a" * 80
long_desc <<-D
This is a really really really long description.
Here you go. So very long.
It even has two paragraphs.
D
def long_description
end
desc "name-with-dashes", "Ensure normalization of command names"
def name_with_dashes
end
method_options :all => :boolean
method_option :lazy, :lazy_default => "yes"
method_option :lazy_numeric, :type => :numeric, :lazy_default => 42
method_option :lazy_array, :type => :array, :lazy_default => %w[eat at joes]
method_option :lazy_hash, :type => :hash, :lazy_default => {'swedish' => 'meatballs'}
desc "with_optional NAME", "invoke with optional name"
def with_optional(name=nil, *args)
[name, options, args]
end
class AnotherScript < Thor
desc "baz", "do some bazing"
def baz
end
end
desc "send", "send as a command name"
def send
true
end
private
def method_missing(meth, *args)
if meth == :boom!
super
else
[meth, args]
end
end
desc "what", "what"
def what
end
end
class MyChildScript < MyScript
remove_command :name_with_dashes
method_options :force => :boolean, :param => :numeric
def initialize(*args)
super
end
desc "zoo", "zoo around"
method_options :param => :required
def zoo
options
end
desc "animal TYPE", "horse around"
def animal(type)
[type, options]
end
method_option :other, :type => :string, :default => "method default", :for => :animal
desc "animal KIND", "fish around", :for => :animal
desc "boom", "explodes everything"
def boom
end
remove_command :boom, :undefine => true
end
class Barn < Thor
def self.exit_on_failure?
false
end
desc "open [ITEM]", "open the barn door"
def open(item = nil)
if item == "shotgun"
puts "That's going to leave a mark."
else
puts "Open sesame!"
end
end
desc "paint [COLOR]", "paint the barn"
method_option :coats, :type => :numeric, :default => 2, :desc => 'how many coats of paint'
def paint(color='red')
puts "#{options[:coats]} coats of #{color} paint"
end
end
class PackageNameScript < Thor
package_name "Baboon"
end
module Scripts
class MyScript < MyChildScript
argument :accessor, :type => :string
class_options :force => :boolean
method_option :new_option, :type => :string, :for => :example_default_command
def zoo
self.accessor
end
end
class MyDefaults < Thor
check_unknown_options!
def self.exit_on_failure?
false
end
namespace :default
desc "cow", "prints 'moo'"
def cow
puts "moo"
end
desc "command_conflict", "only gets called when prepended with a colon"
def command_conflict
puts "command"
end
desc "barn", "commands to manage the barn"
subcommand "barn", Barn
end
class ChildDefault < Thor
namespace "default:child"
end
class Arities < Thor
def self.exit_on_failure?
false
end
desc "zero_args", "takes zero args"
def zero_args
end
desc "one_arg ARG", "takes one arg"
def one_arg(arg)
end
desc "two_args ARG1 ARG2", "takes two args"
def two_args(arg1, arg2)
end
desc "optional_arg [ARG]", "takes an optional arg"
def optional_arg(arg='default')
end
desc ["multiple_usages ARG --foo", "multiple_usages ARG --bar"], "takes mutually exclusive combinations of args and flags"
def multiple_usages(arg)
end
end
end
spec/fixtures/subcommand.thor 0000664 0000000 0000000 00000000412 14002057672 0016707 0 ustar 00root root 0000000 0000000 module TestSubcommands
class Subcommand < Thor
desc "print_opt", "My method"
def print_opt
print options["opt"]
end
end
class Parent < Thor
class_option "opt"
desc "sub", "My subcommand"
subcommand "sub", Subcommand
end
end
spec/fixtures/template/ 0000775 0000000 0000000 00000000000 14002057672 0015477 5 ustar 00root root 0000000 0000000 spec/fixtures/template/bad_config.yaml.tt 0000664 0000000 0000000 00000000054 14002057672 0021063 0 ustar 00root root 0000000 0000000 --- Hi from yaml
<%= unresolved_variable %>
spec/fixtures/verbose.thor 0000664 0000000 0000000 00000000205 14002057672 0016224 0 ustar 00root root 0000000 0000000 #!/usr/bin/ruby
$VERBOSE = true
require 'thor'
class Test < Thor
def self.exit_on_failure?
true
end
end
Test.start(ARGV)
spec/group_spec.rb 0000664 0000000 0000000 00000015745 14002057672 0014522 0 ustar 00root root 0000000 0000000 require "helper"
describe Thor::Group do
describe "command" do
it "allows to use private methods from parent class as commands" do
expect(ChildGroup.start).to eq(%w(bar foo baz))
expect(ChildGroup.new.baz("bar")).to eq("bar")
end
end
describe "#start" do
it "invokes all the commands under the Thor group" do
expect(MyCounter.start(%w(1 2 --third 3))).to eq([1, 2, 3, nil, nil, nil])
end
it "uses argument's default value" do
expect(MyCounter.start(%w(1 --third 3))).to eq([1, 2, 3, nil, nil, nil])
end
it "invokes all the commands in the Thor group and its parents" do
expect(BrokenCounter.start(%w(1 2 --third 3))).to eq([nil, 2, 3, false, 5, nil])
end
it "raises an error if a required argument is added after a non-required" do
expect do
MyCounter.argument(:foo, :type => :string)
end.to raise_error(ArgumentError, 'You cannot have "foo" as required argument after the non-required argument "second".')
end
it "raises when an exception happens within the command call" do
expect { BrokenCounter.start(%w(1 2 --fail)) }.to raise_error(NameError, /undefined local variable or method `this_method_does_not_exist'/)
end
it "raises an error when a Thor group command expects arguments" do
expect { WhinyGenerator.start }.to raise_error(ArgumentError, /thor wrong_arity takes 1 argument, but it should not/)
end
it "invokes help message if any of the shortcuts are given" do
expect(MyCounter).to receive(:help)
MyCounter.start(%w(-h))
end
end
describe "#desc" do
it "sets the description for a given class" do
expect(MyCounter.desc).to eq("Description:\n This generator runs three commands: one, two and three.\n")
end
it "can be inherited" do
expect(BrokenCounter.desc).to eq("Description:\n This generator runs three commands: one, two and three.\n")
end
it "can be nil" do
expect(WhinyGenerator.desc).to be nil
end
end
describe "#help" do
before do
@content = capture(:stdout) { MyCounter.help(Thor::Base.shell.new) }
end
it "provides usage information" do
expect(@content).to match(/my_counter N \[N\]/)
end
it "shows description" do
expect(@content).to match(/Description:/)
expect(@content).to match(/This generator runs three commands: one, two and three./)
end
it "shows options information" do
expect(@content).to match(/Options/)
expect(@content).to match(/\[\-\-third=THREE\]/)
end
end
describe "#invoke" do
before do
@content = capture(:stdout) { E.start }
end
it "allows to invoke a class from the class binding" do
expect(@content).to match(/1\n2\n3\n4\n5\n/)
end
it "shows invocation information to the user" do
expect(@content).to match(/invoke Defined/)
end
it "uses padding on status generated by the invoked class" do
expect(@content).to match(/finished counting/)
end
it "allows invocation to be configured with blocks" do
capture(:stdout) do
expect(F.start).to eq(["Valim, Jose"])
end
end
it "shows invoked options on help" do
content = capture(:stdout) { E.help(Thor::Base.shell.new) }
expect(content).to match(/Defined options:/)
expect(content).to match(/\[--unused\]/)
expect(content).to match(/# This option has no use/)
end
end
describe "#invoke_from_option" do
describe "with default type" do
before do
@content = capture(:stdout) { G.start }
end
it "allows to invoke a class from the class binding by a default option" do
expect(@content).to match(/1\n2\n3\n4\n5\n/)
end
it "does not invoke if the option is nil" do
expect(capture(:stdout) { G.start(%w(--skip-invoked)) }).not_to match(/invoke/)
end
it "prints a message if invocation cannot be found" do
content = capture(:stdout) { G.start(%w(--invoked unknown)) }
expect(content).to match(/error unknown \[not found\]/)
end
it "allows to invoke a class from the class binding by the given option" do
error = nil
content = capture(:stdout) do
error = capture(:stderr) do
G.start(%w(--invoked e))
end
end
expect(content).to match(/invoke e/)
expect(error).to match(/ERROR: "thor two" was called with arguments/)
end
it "shows invocation information to the user" do
expect(@content).to match(/invoke defined/)
end
it "uses padding on status generated by the invoked class" do
expect(@content).to match(/finished counting/)
end
it "shows invoked options on help" do
content = capture(:stdout) { G.help(Thor::Base.shell.new) }
expect(content).to match(/defined options:/)
expect(content).to match(/\[--unused\]/)
expect(content).to match(/# This option has no use/)
end
end
describe "with boolean type" do
before do
@content = capture(:stdout) { H.start }
end
it "allows to invoke a class from the class binding by a default option" do
expect(@content).to match(/1\n2\n3\n4\n5\n/)
end
it "does not invoke if the option is false" do
expect(capture(:stdout) { H.start(%w(--no-defined)) }).not_to match(/invoke/)
end
it "shows invocation information to the user" do
expect(@content).to match(/invoke defined/)
end
it "uses padding on status generated by the invoked class" do
expect(@content).to match(/finished counting/)
end
it "shows invoked options on help" do
content = capture(:stdout) { H.help(Thor::Base.shell.new) }
expect(content).to match(/defined options:/)
expect(content).to match(/\[--unused\]/)
expect(content).to match(/# This option has no use/)
end
end
end
describe "edge-cases" do
it "can handle boolean options followed by arguments" do
klass = Class.new(Thor::Group) do
desc "say hi to name"
argument :name, :type => :string
class_option :loud, :type => :boolean
def hi
self.name = name.upcase if options[:loud]
"Hi #{name}"
end
end
expect(klass.start(%w(jose))).to eq(["Hi jose"])
expect(klass.start(%w(jose --loud))).to eq(["Hi JOSE"])
expect(klass.start(%w(--loud jose))).to eq(["Hi JOSE"])
end
it "provides extra args as `args`" do
klass = Class.new(Thor::Group) do
desc "say hi to name"
argument :name, :type => :string
class_option :loud, :type => :boolean
def hi
self.name = name.upcase if options[:loud]
out = "Hi #{name}"
out << ": " << args.join(", ") unless args.empty?
out
end
end
expect(klass.start(%w(jose))).to eq(["Hi jose"])
expect(klass.start(%w(jose --loud))).to eq(["Hi JOSE"])
expect(klass.start(%w(--loud jose))).to eq(["Hi JOSE"])
end
end
end
spec/helper.rb 0000664 0000000 0000000 00000003767 14002057672 0013634 0 ustar 00root root 0000000 0000000 $TESTING = true
require "simplecov"
require "coveralls"
SimpleCov.formatters = [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter]
SimpleCov.start do
add_filter "/spec"
minimum_coverage(90)
end
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
require "thor"
require "thor/group"
require "stringio"
require "rdoc"
require "rspec"
require "diff/lcs" # You need diff/lcs installed to run specs (but not to run Thor).
require "webmock/rspec"
WebMock.disable_net_connect!(:allow => "coveralls.io")
# Set shell to basic
ENV["THOR_COLUMNS"] = "10000"
$0 = "thor"
$thor_runner = true
ARGV.clear
Thor::Base.shell = Thor::Shell::Basic
# Load fixtures
load File.join(File.dirname(__FILE__), "fixtures", "enum.thor")
load File.join(File.dirname(__FILE__), "fixtures", "group.thor")
load File.join(File.dirname(__FILE__), "fixtures", "invoke.thor")
load File.join(File.dirname(__FILE__), "fixtures", "script.thor")
load File.join(File.dirname(__FILE__), "fixtures", "subcommand.thor")
load File.join(File.dirname(__FILE__), "fixtures", "command.thor")
RSpec.configure do |config|
config.before do
ARGV.replace []
end
config.expect_with :rspec do |c|
c.syntax = :expect
end
def capture(stream)
begin
stream = stream.to_s
eval "$#{stream} = StringIO.new"
yield
result = eval("$#{stream}").string
ensure
eval("$#{stream} = #{stream.upcase}")
end
result
end
def source_root
File.join(File.dirname(__FILE__), "fixtures")
end
def destination_root
File.join(File.dirname(__FILE__), "sandbox")
end
# This code was adapted from Ruby on Rails, available under MIT-LICENSE
# Copyright (c) 2004-2013 David Heinemeier Hansson
def silence_warnings
old_verbose = $VERBOSE
$VERBOSE = nil
yield
ensure
$VERBOSE = old_verbose
end
# true if running on windows, used for conditional spec skips
#
# @return [TrueClass/FalseClass]
def windows?
Gem.win_platform?
end
alias silence capture
end
spec/invocation_spec.rb 0000664 0000000 0000000 00000007341 14002057672 0015530 0 ustar 00root root 0000000 0000000 require "helper"
require "thor/base"
describe Thor::Invocation do
describe "#invoke" do
it "invokes a command inside another command" do
expect(capture(:stdout) { A.new.invoke(:two) }).to eq("2\n3\n")
end
it "invokes a command just once" do
expect(capture(:stdout) { A.new.invoke(:one) }).to eq("1\n2\n3\n")
end
it "invokes a command just once even if they belongs to different classes" do
expect(capture(:stdout) { Defined.new.invoke(:one) }).to eq("1\n2\n3\n4\n5\n")
end
it "invokes a command with arguments" do
expect(A.new.invoke(:five, [5])).to be true
expect(A.new.invoke(:five, [7])).to be false
end
it "invokes the default command if none is given to a Thor class" do
content = capture(:stdout) { A.new.invoke("b") }
expect(content).to match(/Commands/)
expect(content).to match(/LAST_NAME/)
end
it "accepts a class as argument without a command to invoke" do
content = capture(:stdout) { A.new.invoke(B) }
expect(content).to match(/Commands/)
expect(content).to match(/LAST_NAME/)
end
it "accepts a class as argument with a command to invoke" do
base = A.new([], :last_name => "Valim")
expect(base.invoke(B, :one, %w(Jose))).to eq("Valim, Jose")
end
it "allows customized options to be given" do
base = A.new([], :last_name => "Wrong")
expect(base.invoke(B, :one, %w(Jose), :last_name => "Valim")).to eq("Valim, Jose")
end
it "reparses options in the new class" do
expect(A.start(%w(invoker --last-name Valim))).to eq("Valim, Jose")
end
it "shares initialize options with invoked class" do
expect(A.new([], :foo => :bar).invoke("b:two")).to eq("foo" => :bar)
end
it "uses default options from invoked class if no matching arguments are given" do
expect(A.new([]).invoke("b:four")).to eq("default")
end
it "overrides default options if options are passed to the invoker" do
expect(A.new([], :defaulted_value => "not default").invoke("b:four")).to eq("not default")
end
it "returns the command chain" do
expect(I.new.invoke("two")).to eq([:two])
expect(J.start(%w(one two))).to eq([:one, :two])
end
it "dump configuration values to be used in the invoked class" do
base = A.new
expect(base.invoke("b:three").shell).to eq(base.shell)
end
it "allow extra configuration values to be given" do
base = A.new
shell = Thor::Base.shell.new
expect(base.invoke("b:three", [], {}, :shell => shell).shell).to eq(shell)
end
it "invokes a Thor::Group and all of its commands" do
expect(capture(:stdout) { A.new.invoke(:c) }).to eq("1\n2\n3\n")
end
it "does not invoke a Thor::Group twice" do
base = A.new
silence(:stdout) { base.invoke(:c) }
expect(capture(:stdout) { base.invoke(:c) }).to be_empty
end
it "does not invoke any of Thor::Group commands twice" do
base = A.new
silence(:stdout) { base.invoke(:c) }
expect(capture(:stdout) { base.invoke("c:one") }).to be_empty
end
it "raises Thor::UndefinedCommandError if the command can't be found" do
expect do
A.new.invoke("foo:bar")
end.to raise_error(Thor::UndefinedCommandError)
end
it "raises Thor::UndefinedCommandError if the command can't be found even if all commands were already executed" do
base = C.new
silence(:stdout) { base.invoke_all }
expect do
base.invoke("foo:bar")
end.to raise_error(Thor::UndefinedCommandError)
end
it "raises an error if a non Thor class is given" do
expect do
A.new.invoke(Object)
end.to raise_error(RuntimeError, "Expected Thor class, got Object")
end
end
end
spec/line_editor/ 0000775 0000000 0000000 00000000000 14002057672 0014310 5 ustar 00root root 0000000 0000000 spec/line_editor/basic_spec.rb 0000664 0000000 0000000 00000001712 14002057672 0016731 0 ustar 00root root 0000000 0000000 require "helper"
describe Thor::LineEditor::Basic do
describe ".available?" do
it "returns true" do
expect(Thor::LineEditor::Basic).to be_available
end
end
describe "#readline" do
it "uses $stdin and $stdout to get input from the user" do
expect($stdout).to receive(:print).with("Enter your name ")
expect($stdin).to receive(:gets).and_return("George")
expect($stdin).not_to receive(:noecho)
editor = Thor::LineEditor::Basic.new("Enter your name ", {})
expect(editor.readline).to eq("George")
end
it "disables echo when asked to" do
expect($stdout).to receive(:print).with("Password: ")
noecho_stdin = double("noecho_stdin")
expect(noecho_stdin).to receive(:gets).and_return("secret")
expect($stdin).to receive(:noecho).and_yield(noecho_stdin)
editor = Thor::LineEditor::Basic.new("Password: ", :echo => false)
expect(editor.readline).to eq("secret")
end
end
end
spec/line_editor/readline_spec.rb 0000664 0000000 0000000 00000005276 14002057672 0017444 0 ustar 00root root 0000000 0000000 require "helper"
describe Thor::LineEditor::Readline do
before do
# Eagerly check Readline availability before mocking
Thor::LineEditor::Readline.available?
unless defined? ::Readline
::Readline = double("Readline")
allow(::Readline).to receive(:completion_append_character=).with(nil)
end
end
describe ".available?" do
it "returns true when ::Readline exists" do
allow(Object).to receive(:const_defined?).with(:Readline).and_return(true)
expect(described_class).to be_available
end
it "returns false when ::Readline does not exist" do
allow(Object).to receive(:const_defined?).with(:Readline).and_return(false)
expect(described_class).not_to be_available
end
end
describe "#readline" do
it "invokes the readline library" do
expect(::Readline).to receive(:readline).with("> ", true).and_return("foo")
expect(::Readline).to_not receive(:completion_proc=)
editor = Thor::LineEditor::Readline.new("> ", {})
expect(editor.readline).to eq("foo")
end
it "supports the add_to_history option" do
expect(::Readline).to receive(:readline).with("> ", false).and_return("foo")
expect(::Readline).to_not receive(:completion_proc=)
editor = Thor::LineEditor::Readline.new("> ", :add_to_history => false)
expect(editor.readline).to eq("foo")
end
it "provides tab completion when given a limited_to option" do
expect(::Readline).to receive(:readline)
expect(::Readline).to receive(:completion_proc=) do |proc|
expect(proc.call("")).to eq %w(Apples Chicken Chocolate)
expect(proc.call("Ch")).to eq %w(Chicken Chocolate)
expect(proc.call("Chi")).to eq ["Chicken"]
end
editor = Thor::LineEditor::Readline.new("Best food: ", :limited_to => %w(Apples Chicken Chocolate))
editor.readline
end
it "provides path tab completion when given the path option" do
expect(::Readline).to receive(:readline)
expect(::Readline).to receive(:completion_proc=) do |proc|
expect(proc.call("../line_ed").sort).to eq ["../line_editor/", "../line_editor_spec.rb"].sort
end
editor = Thor::LineEditor::Readline.new("Path to file: ", :path => true)
Dir.chdir(File.dirname(__FILE__)) { editor.readline }
end
it "uses STDIN when asked not to echo input" do
expect($stdout).to receive(:print).with("Password: ")
noecho_stdin = double("noecho_stdin")
expect(noecho_stdin).to receive(:gets).and_return("secret")
expect($stdin).to receive(:noecho).and_yield(noecho_stdin)
editor = Thor::LineEditor::Readline.new("Password: ", :echo => false)
expect(editor.readline).to eq("secret")
end
end
end
spec/line_editor_spec.rb 0000664 0000000 0000000 00000002536 14002057672 0015655 0 ustar 00root root 0000000 0000000 require "helper"
describe Thor::LineEditor, "on a system with Readline support" do
before do
@original_readline = ::Readline if defined? ::Readline
silence_warnings { ::Readline = double("Readline") }
end
after do
silence_warnings { ::Readline = @original_readline }
end
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(editor).to receive(:readline).and_return("George")
expect(Thor::LineEditor.readline("Enter your name ", :default => "Brian")).to eq("George")
end
end
end
describe Thor::LineEditor, "on a system without Readline support" do
before do
if defined? ::Readline
@original_readline = ::Readline
Object.send(:remove_const, :Readline)
end
end
after do
silence_warnings { ::Readline = @original_readline }
end
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(editor).to receive(:readline).and_return("George")
expect(Thor::LineEditor.readline("Enter your name ", :default => "Brian")).to eq("George")
end
end
end
spec/nested_context_spec.rb 0000664 0000000 0000000 00000000647 14002057672 0016407 0 ustar 00root root 0000000 0000000 require "helper"
describe Thor::NestedContext do
subject(:context) { described_class.new }
describe "#enter" do
it "is never empty within the entered block" do
context.enter do
context.enter {}
expect(context).to be_entered
end
end
it "is empty when outside of all blocks" do
context.enter { context.enter {} }
expect(context).not_to be_entered
end
end
end
spec/no_warnings_spec.rb 0000664 0000000 0000000 00000000744 14002057672 0015703 0 ustar 00root root 0000000 0000000 require "open3"
context "when $VERBOSE is enabled" do
it "prints no warnings" do
root = File.expand_path("..", __dir__)
_, err, = Open3.capture3("ruby -I #{root}/lib #{root}/spec/fixtures/verbose.thor")
expect(err).to be_empty
end
it "prints no warnings even when erroring" do
root = File.expand_path("..", __dir__)
_, err, = Open3.capture3("ruby -I #{root}/lib #{root}/spec/fixtures/verbose.thor noop")
expect(err).to_not match(/warning:/)
end
end
spec/parser/ 0000775 0000000 0000000 00000000000 14002057672 0013307 5 ustar 00root root 0000000 0000000 spec/parser/argument_spec.rb 0000664 0000000 0000000 00000003067 14002057672 0016476 0 ustar 00root root 0000000 0000000 require "helper"
require "thor/parser"
describe Thor::Argument do
def argument(name, options = {})
@argument ||= Thor::Argument.new(name, options)
end
describe "errors" do
it "raises an error if name is not supplied" do
expect do
argument(nil)
end.to raise_error(ArgumentError, "Argument name can't be nil.")
end
it "raises an error if type is unknown" do
expect do
argument(:command, :type => :unknown)
end.to raise_error(ArgumentError, "Type :unknown is not valid for arguments.")
end
it "raises an error if argument is required and has default values" do
expect do
argument(:command, :type => :string, :default => "bar", :required => true)
end.to raise_error(ArgumentError, "An argument cannot be required and have default value.")
end
it "raises an error if enum isn't an array" do
expect do
argument(:command, :type => :string, :enum => "bar")
end.to raise_error(ArgumentError, "An argument cannot have an enum other than an array.")
end
end
describe "#usage" do
it "returns usage for string types" do
expect(argument(:foo, :type => :string).usage).to eq("FOO")
end
it "returns usage for numeric types" do
expect(argument(:foo, :type => :numeric).usage).to eq("N")
end
it "returns usage for array types" do
expect(argument(:foo, :type => :array).usage).to eq("one two three")
end
it "returns usage for hash types" do
expect(argument(:foo, :type => :hash).usage).to eq("key:value")
end
end
end
spec/parser/arguments_spec.rb 0000664 0000000 0000000 00000005300 14002057672 0016651 0 ustar 00root root 0000000 0000000 require "helper"
require "thor/parser"
describe Thor::Arguments do
def create(opts = {})
arguments = opts.map do |type, default|
options = {:required => default.nil?, :type => type, :default => default}
Thor::Argument.new(type.to_s, options)
end
arguments.sort! { |a, b| b.name <=> a.name }
@opt = Thor::Arguments.new(arguments)
end
def parse(*args)
@opt.parse(args)
end
describe "#parse" do
it "parses arguments in the given order" do
create :string => nil, :numeric => nil
expect(parse("name", "13")["string"]).to eq("name")
expect(parse("name", "13")["numeric"]).to eq(13)
expect(parse("name", "+13")["numeric"]).to eq(13)
expect(parse("name", "+13.3")["numeric"]).to eq(13.3)
expect(parse("name", "-13")["numeric"]).to eq(-13)
expect(parse("name", "-13.3")["numeric"]).to eq(-13.3)
end
it "accepts hashes" do
create :string => nil, :hash => nil
expect(parse("product", "title:string", "age:integer")["string"]).to eq("product")
expect(parse("product", "title:string", "age:integer")["hash"]).to eq("title" => "string", "age" => "integer")
expect(parse("product", "url:http://www.amazon.com/gp/product/123")["hash"]).to eq("url" => "http://www.amazon.com/gp/product/123")
end
it "accepts arrays" do
create :string => nil, :array => nil
expect(parse("product", "title", "age")["string"]).to eq("product")
expect(parse("product", "title", "age")["array"]).to eq(%w(title age))
end
it "accepts - as an array argument" do
create :array => nil
expect(parse("-")["array"]).to eq(%w(-))
expect(parse("-", "title", "-")["array"]).to eq(%w(- title -))
end
describe "with no inputs" do
it "and no arguments returns an empty hash" do
create
expect(parse).to eq({})
end
it "and required arguments raises an error" do
create :string => nil, :numeric => nil
expect { parse }.to raise_error(Thor::RequiredArgumentMissingError, "No value provided for required arguments 'string', 'numeric'")
end
it "and default arguments returns default values" do
create :string => "name", :numeric => 13
expect(parse).to eq("string" => "name", "numeric" => 13)
end
end
it "returns the input if it's already parsed" do
create :string => nil, :hash => nil, :array => nil, :numeric => nil
expect(parse("", 0, {}, [])).to eq("string" => "", "numeric" => 0, "hash" => {}, "array" => [])
end
it "returns the default value if none is provided" do
create :string => "foo", :numeric => 3.0
expect(parse("bar")).to eq("string" => "bar", "numeric" => 3.0)
end
end
end
spec/parser/option_spec.rb 0000664 0000000 0000000 00000020671 14002057672 0016164 0 ustar 00root root 0000000 0000000 require "helper"
require "thor/parser"
describe Thor::Option do
def parse(key, value)
Thor::Option.parse(key, value)
end
def option(name, options = {})
@option ||= Thor::Option.new(name, options)
end
describe "#parse" do
describe "with value as a symbol" do
describe "and symbol is a valid type" do
it "has type equals to the symbol" do
expect(parse(:foo, :string).type).to eq(:string)
expect(parse(:foo, :numeric).type).to eq(:numeric)
end
it "has no default value" do
expect(parse(:foo, :string).default).to be nil
expect(parse(:foo, :numeric).default).to be nil
end
end
describe "equals to :required" do
it "has type equals to :string" do
expect(parse(:foo, :required).type).to eq(:string)
end
it "has no default value" do
expect(parse(:foo, :required).default).to be nil
end
end
describe "and symbol is not a reserved key" do
it "has type equal to :string" do
expect(parse(:foo, :bar).type).to eq(:string)
end
it "has no default value" do
expect(parse(:foo, :bar).default).to be nil
end
end
end
describe "with value as hash" do
it "has default type :hash" do
expect(parse(:foo, :a => :b).type).to eq(:hash)
end
it "has default value equal to the hash" do
expect(parse(:foo, :a => :b).default).to eq(:a => :b)
end
end
describe "with value as array" do
it "has default type :array" do
expect(parse(:foo, [:a, :b]).type).to eq(:array)
end
it "has default value equal to the array" do
expect(parse(:foo, [:a, :b]).default).to eq([:a, :b])
end
end
describe "with value as string" do
it "has default type :string" do
expect(parse(:foo, "bar").type).to eq(:string)
end
it "has default value equal to the string" do
expect(parse(:foo, "bar").default).to eq("bar")
end
end
describe "with value as numeric" do
it "has default type :numeric" do
expect(parse(:foo, 2.0).type).to eq(:numeric)
end
it "has default value equal to the numeric" do
expect(parse(:foo, 2.0).default).to eq(2.0)
end
end
describe "with value as boolean" do
it "has default type :boolean" do
expect(parse(:foo, true).type).to eq(:boolean)
expect(parse(:foo, false).type).to eq(:boolean)
end
it "has default value equal to the boolean" do
expect(parse(:foo, true).default).to eq(true)
expect(parse(:foo, false).default).to eq(false)
end
end
describe "with key as a symbol" do
it "sets the name equal to the key" do
expect(parse(:foo, true).name).to eq("foo")
end
end
describe "with key as an array" do
it "sets the first items in the array to the name" do
expect(parse([:foo, :bar, :baz], true).name).to eq("foo")
end
it "sets all other items as aliases" do
expect(parse([:foo, :bar, :baz], true).aliases).to eq([:bar, :baz])
end
end
end
it "returns the switch name" do
expect(option("foo").switch_name).to eq("--foo")
expect(option("--foo").switch_name).to eq("--foo")
end
it "returns the human name" do
expect(option("foo").human_name).to eq("foo")
expect(option("--foo").human_name).to eq("foo")
end
it "converts underscores to dashes" do
expect(option("foo_bar").switch_name).to eq("--foo-bar")
end
it "can be required and have default values" do
option = option("foo", :required => true, :type => :string, :default => "bar")
expect(option.default).to eq("bar")
expect(option).to be_required
end
it "raises an error if default is inconsistent with type and check_default_type is true" do
expect do
option("foo_bar", :type => :numeric, :default => "baz", :check_default_type => true)
end.to raise_error(ArgumentError, 'Expected numeric default value for \'--foo-bar\'; got "baz" (string)')
end
it "raises an error if repeatable and default is inconsistent with type and check_default_type is true" do
expect do
option("foo_bar", :type => :numeric, :repeatable => true, :default => "baz", :check_default_type => true)
end.to raise_error(ArgumentError, 'Expected array default value for \'--foo-bar\'; got "baz" (string)')
end
it "raises an error type hash is repeatable and default is inconsistent with type and check_default_type is true" do
expect do
option("foo_bar", :type => :hash, :repeatable => true, :default => "baz", :check_default_type => true)
end.to raise_error(ArgumentError, 'Expected hash default value for \'--foo-bar\'; got "baz" (string)')
end
it "does not raises an error if type hash is repeatable and default is consistent with type and check_default_type is true" do
expect do
option("foo_bar", :type => :hash, :repeatable => true, :default => {}, :check_default_type => true)
end.not_to raise_error
end
it "does not raises an error if repeatable and default is consistent with type and check_default_type is true" do
expect do
option("foo_bar", :type => :numeric, :repeatable => true, :default => [1], :check_default_type => true)
end.not_to raise_error
end
it "does not raises an error if default is an symbol and type string and check_default_type is true" do
expect do
option("foo", :type => :string, :default => :bar, :check_default_type => true)
end.not_to raise_error
end
it "does not raises an error if default is inconsistent with type and check_default_type is false" do
expect do
option("foo_bar", :type => :numeric, :default => "baz", :check_default_type => false)
end.not_to raise_error
end
it "boolean options cannot be required" do
expect do
option("foo", :required => true, :type => :boolean)
end.to raise_error(ArgumentError, "An option cannot be boolean and required.")
end
it "does not raises an error if default is a boolean and it is required" do
expect do
option("foo", :required => true, :default => true)
end.not_to raise_error
end
it "allows type predicates" do
expect(parse(:foo, :string)).to be_string
expect(parse(:foo, :boolean)).to be_boolean
expect(parse(:foo, :numeric)).to be_numeric
end
it "raises an error on method missing" do
expect do
parse(:foo, :string).unknown?
end.to raise_error(NoMethodError)
end
describe "#usage" do
it "returns usage for string types" do
expect(parse(:foo, :string).usage).to eq("[--foo=FOO]")
end
it "returns usage for numeric types" do
expect(parse(:foo, :numeric).usage).to eq("[--foo=N]")
end
it "returns usage for array types" do
expect(parse(:foo, :array).usage).to eq("[--foo=one two three]")
end
it "returns usage for hash types" do
expect(parse(:foo, :hash).usage).to eq("[--foo=key:value]")
end
it "returns usage for boolean types" do
expect(parse(:foo, :boolean).usage).to eq("[--foo], [--no-foo]")
end
it "does not use padding when no aliases are given" do
expect(parse(:foo, :boolean).usage).to eq("[--foo], [--no-foo]")
end
it "documents a negative option when boolean" do
expect(parse(:foo, :boolean).usage).to include("[--no-foo]")
end
it "does not document a negative option for a negative boolean" do
expect(parse(:'no-foo', :boolean).usage).not_to include("[--no-no-foo]")
end
it "documents a negative option for a positive boolean starting with 'no'" do
expect(parse(:'nougat', :boolean).usage).to include("[--no-nougat]")
end
it "uses banner when supplied" do
expect(option(:foo, :required => false, :type => :string, :banner => "BAR").usage).to eq("[--foo=BAR]")
end
it "checks when banner is an empty string" do
expect(option(:foo, :required => false, :type => :string, :banner => "").usage).to eq("[--foo]")
end
describe "with required values" do
it "does not show the usage between brackets" do
expect(parse(:foo, :required).usage).to eq("--foo=FOO")
end
end
describe "with aliases" do
it "does not show the usage between brackets" do
expect(parse([:foo, "-f", "-b"], :required).usage).to eq("-f, -b, --foo=FOO")
end
it "does not negate the aliases" do
expect(parse([:foo, "-f", "-b"], :boolean).usage).to eq("-f, -b, [--foo], [--no-foo]")
end
end
end
end
spec/parser/options_spec.rb 0000664 0000000 0000000 00000041355 14002057672 0016351 0 ustar 00root root 0000000 0000000 require "helper"
require "thor/parser"
describe Thor::Options do
def create(opts, defaults = {}, stop_on_unknown = false)
opts.each do |key, value|
opts[key] = Thor::Option.parse(key, value) unless value.is_a?(Thor::Option)
end
@opt = Thor::Options.new(opts, defaults, stop_on_unknown)
end
def parse(*args)
@opt.parse(args.flatten)
end
def check_unknown!
@opt.check_unknown!
end
def remaining
@opt.remaining
end
describe "#to_switches" do
it "turns true values into a flag" do
expect(Thor::Options.to_switches(:color => true)).to eq("--color")
end
it "ignores nil" do
expect(Thor::Options.to_switches(:color => nil)).to eq("")
end
it "ignores false" do
expect(Thor::Options.to_switches(:color => false)).to eq("")
end
it "avoids extra spaces" do
expect(Thor::Options.to_switches(:color => false, :foo => nil)).to eq("")
end
it "writes --name value for anything else" do
expect(Thor::Options.to_switches(:format => "specdoc")).to eq('--format "specdoc"')
end
it "joins several values" do
switches = Thor::Options.to_switches(:color => true, :foo => "bar").split(" ").sort
expect(switches).to eq(%w("bar" --color --foo))
end
it "accepts arrays" do
expect(Thor::Options.to_switches(:count => [1, 2, 3])).to eq("--count 1 2 3")
end
it "accepts hashes" do
expect(Thor::Options.to_switches(:count => {:a => :b})).to eq("--count a:b")
end
it "accepts underscored options" do
expect(Thor::Options.to_switches(:under_score_option => "foo bar")).to eq('--under_score_option "foo bar"')
end
end
describe "#parse" do
it "allows multiple aliases for a given switch" do
create %w(--foo --bar --baz) => :string
expect(parse("--foo", "12")["foo"]).to eq("12")
expect(parse("--bar", "12")["foo"]).to eq("12")
expect(parse("--baz", "12")["foo"]).to eq("12")
end
it "allows custom short names" do
create "-f" => :string
expect(parse("-f", "12")).to eq("f" => "12")
end
it "allows custom short-name aliases" do
create %w(--bar -f) => :string
expect(parse("-f", "12")).to eq("bar" => "12")
end
it "accepts conjoined short switches" do
create %w(--foo -f) => true, %w(--bar -b) => true, %w(--app -a) => true
opts = parse("-fba")
expect(opts["foo"]).to be true
expect(opts["bar"]).to be true
expect(opts["app"]).to be true
end
it "accepts conjoined short switches with input" do
create %w(--foo -f) => true, %w(--bar -b) => true, %w(--app -a) => :required
opts = parse "-fba", "12"
expect(opts["foo"]).to be true
expect(opts["bar"]).to be true
expect(opts["app"]).to eq("12")
end
it "returns the default value if none is provided" do
create :foo => "baz", :bar => :required
expect(parse("--bar", "boom")["foo"]).to eq("baz")
end
it "returns the default value from defaults hash to required arguments" do
create Hash[:bar => :required], Hash[:bar => "baz"]
expect(parse["bar"]).to eq("baz")
end
it "gives higher priority to defaults given in the hash" do
create Hash[:bar => true], Hash[:bar => false]
expect(parse["bar"]).to eq(false)
end
it "raises an error for unknown switches" do
create :foo => "baz", :bar => :required
parse("--bar", "baz", "--baz", "unknown")
expected = "Unknown switches \"--baz\""
expected << "\nDid you mean? \"--bar\"" if Thor::Correctable
expect { check_unknown! }.to raise_error(Thor::UnknownArgumentError, expected)
end
it "skips leading non-switches" do
create(:foo => "baz")
expect(parse("asdf", "--foo", "bar")).to eq("foo" => "bar")
end
it "correctly recognizes things that look kind of like options, but aren't, as not options" do
create(:foo => "baz")
expect(parse("--asdf---asdf", "baz", "--foo", "--asdf---dsf--asdf")).to eq("foo" => "--asdf---dsf--asdf")
check_unknown!
end
it "accepts underscores in commandline args hash for boolean" do
create :foo_bar => :boolean
expect(parse("--foo_bar")["foo_bar"]).to eq(true)
expect(parse("--no_foo_bar")["foo_bar"]).to eq(false)
end
it "accepts underscores in commandline args hash for strings" do
create :foo_bar => :string, :baz_foo => :string
expect(parse("--foo_bar", "baz")["foo_bar"]).to eq("baz")
expect(parse("--baz_foo", "foo bar")["baz_foo"]).to eq("foo bar")
end
it "interprets everything after -- as args instead of options" do
create(:foo => :string, :bar => :required)
expect(parse(%w(--bar abc moo -- --foo def -a))).to eq("bar" => "abc")
expect(remaining).to eq(%w(moo --foo def -a))
end
it "ignores -- when looking for single option values" do
create(:foo => :string, :bar => :required)
expect(parse(%w(--bar -- --foo def -a))).to eq("bar" => "--foo")
expect(remaining).to eq(%w(def -a))
end
it "ignores -- when looking for array option values" do
create(:foo => :array)
expect(parse(%w(--foo a b -- c d -e))).to eq("foo" => %w(a b c d -e))
expect(remaining).to eq([])
end
it "ignores -- when looking for hash option values" do
create(:foo => :hash)
expect(parse(%w(--foo a:b -- c:d -e))).to eq("foo" => {"a" => "b", "c" => "d"})
expect(remaining).to eq(%w(-e))
end
it "ignores trailing --" do
create(:foo => :string)
expect(parse(%w(--foo --))).to eq("foo" => nil)
expect(remaining).to eq([])
end
describe "with no input" do
it "and no switches returns an empty hash" do
create({})
expect(parse).to eq({})
end
it "and several switches returns an empty hash" do
create "--foo" => :boolean, "--bar" => :string
expect(parse).to eq({})
end
it "and a required switch raises an error" do
create "--foo" => :required
expect { parse }.to raise_error(Thor::RequiredArgumentMissingError, "No value provided for required options '--foo'")
end
end
describe "with one required and one optional switch" do
before do
create "--foo" => :required, "--bar" => :boolean
end
it "raises an error if the required switch has no argument" do
expect { parse("--foo") }.to raise_error(Thor::MalformattedArgumentError)
end
it "raises an error if the required switch isn't given" do
expect { parse("--bar") }.to raise_error(Thor::RequiredArgumentMissingError)
end
it "raises an error if the required switch is set to nil" do
expect { parse("--no-foo") }.to raise_error(Thor::RequiredArgumentMissingError)
end
it "does not raises an error if the required option has a default value" do
options = {:required => true, :type => :string, :default => "baz"}
create :foo => Thor::Option.new("foo", options), :bar => :boolean
expect { parse("--bar") }.not_to raise_error
end
end
context "when stop_on_unknown is true" do
before do
create({:foo => :string, :verbose => :boolean}, {}, true)
end
it "stops parsing on first non-option" do
expect(parse(%w(foo --verbose))).to eq({})
expect(remaining).to eq(%w(foo --verbose))
end
it "stops parsing on unknown option" do
expect(parse(%w(--bar --verbose))).to eq({})
expect(remaining).to eq(%w(--bar --verbose))
end
it "retains -- after it has stopped parsing" do
expect(parse(%w(--bar -- whatever))).to eq({})
expect(remaining).to eq(%w(--bar -- whatever))
end
it "still accepts options that are given before non-options" do
expect(parse(%w(--verbose foo))).to eq("verbose" => true)
expect(remaining).to eq(%w(foo))
end
it "still accepts options that require a value" do
expect(parse(%w(--foo bar baz))).to eq("foo" => "bar")
expect(remaining).to eq(%w(baz))
end
it "still interprets everything after -- as args instead of options" do
expect(parse(%w(-- --verbose))).to eq({})
expect(remaining).to eq(%w(--verbose))
end
end
describe "with :string type" do
before do
create %w(--foo -f) => :required
end
it "accepts a switch <value> assignment" do
expect(parse("--foo", "12")["foo"]).to eq("12")
end
it "accepts a switch=<value> assignment" do
expect(parse("-f=12")["foo"]).to eq("12")
expect(parse("--foo=12")["foo"]).to eq("12")
expect(parse("--foo=bar=baz")["foo"]).to eq("bar=baz")
end
it "must accept underscores switch=value assignment" do
create :foo_bar => :required
expect(parse("--foo_bar=http://example.com/under_score/")["foo_bar"]).to eq("http://example.com/under_score/")
end
it "accepts a --no-switch format" do
create "--foo" => "bar"
expect(parse("--no-foo")["foo"]).to be nil
end
it "does not consume an argument for --no-switch format" do
create "--cheese" => :string
expect(parse("burger", "--no-cheese", "fries")["cheese"]).to be nil
end
it "accepts a --switch format on non required types" do
create "--foo" => :string
expect(parse("--foo")["foo"]).to eq("foo")
end
it "accepts a --switch format on non required types with default values" do
create "--baz" => :string, "--foo" => "bar"
expect(parse("--baz", "bang", "--foo")["foo"]).to eq("bar")
end
it "overwrites earlier values with later values" do
expect(parse("--foo=bar", "--foo", "12")["foo"]).to eq("12")
expect(parse("--foo", "12", "--foo", "13")["foo"]).to eq("13")
end
it "raises error when value isn't in enum" do
enum = %w(apple banana)
create :fruit => Thor::Option.new("fruit", :type => :string, :enum => enum)
expect { parse("--fruit", "orange") }.to raise_error(Thor::MalformattedArgumentError,
"Expected '--fruit' to be one of #{enum.join(', ')}; got orange")
end
it "does not erroneously mutate defaults" do
create :foo => Thor::Option.new("foo", :type => :string, :repeatable => true, :required => false, :default => [])
expect(parse("--foo=bar", "--foo", "12")["foo"]).to eq(["bar", "12"])
expect(@opt.instance_variable_get(:@switches)["--foo"].default).to eq([])
end
end
describe "with :boolean type" do
before do
create "--foo" => false
end
it "accepts --opt assignment" do
expect(parse("--foo")["foo"]).to eq(true)
expect(parse("--foo", "--bar")["foo"]).to eq(true)
end
it "uses the default value if no switch is given" do
expect(parse("")["foo"]).to eq(false)
end
it "accepts --opt=value assignment" do
expect(parse("--foo=true")["foo"]).to eq(true)
expect(parse("--foo=false")["foo"]).to eq(false)
end
it "accepts --[no-]opt variant, setting false for value" do
expect(parse("--no-foo")["foo"]).to eq(false)
end
it "accepts --[skip-]opt variant, setting false for value" do
expect(parse("--skip-foo")["foo"]).to eq(false)
end
it "accepts --[skip-]opt variant, setting false for value, even if there's a trailing non-switch" do
expect(parse("--skip-foo", "asdf")["foo"]).to eq(false)
end
it "will prefer 'no-opt' variant over inverting 'opt' if explicitly set" do
create "--no-foo" => true
expect(parse("--no-foo")["no-foo"]).to eq(true)
end
it "will prefer 'skip-opt' variant over inverting 'opt' if explicitly set" do
create "--skip-foo" => true
expect(parse("--skip-foo")["skip-foo"]).to eq(true)
end
it "will prefer 'skip-opt' variant over inverting 'opt' if explicitly set, even if there's a trailing non-switch" do
create "--skip-foo" => true
expect(parse("--skip-foo", "asdf")["skip-foo"]).to eq(true)
end
it "will prefer 'skip-opt' variant over inverting 'opt' if explicitly set, and given a value" do
create "--skip-foo" => true
expect(parse("--skip-foo=f")["skip-foo"]).to eq(false)
expect(parse("--skip-foo=false")["skip-foo"]).to eq(false)
expect(parse("--skip-foo=t")["skip-foo"]).to eq(true)
expect(parse("--skip-foo=true")["skip-foo"]).to eq(true)
end
it "accepts inputs in the human name format" do
create :foo_bar => :boolean
expect(parse("--foo-bar")["foo_bar"]).to eq(true)
expect(parse("--no-foo-bar")["foo_bar"]).to eq(false)
expect(parse("--skip-foo-bar")["foo_bar"]).to eq(false)
end
it "doesn't eat the next part of the param" do
expect(parse("--foo", "bar")).to eq("foo" => true)
expect(@opt.remaining).to eq(%w(bar))
end
it "doesn't eat the next part of the param with 'no-opt' variant" do
expect(parse("--no-foo", "bar")).to eq("foo" => false)
expect(@opt.remaining).to eq(%w(bar))
end
it "doesn't eat the next part of the param with 'skip-opt' variant" do
expect(parse("--skip-foo", "bar")).to eq("foo" => false)
expect(@opt.remaining).to eq(%w(bar))
end
it "allows multiple values if repeatable is specified" do
create :verbose => Thor::Option.new("verbose", :type => :boolean, :aliases => '-v', :repeatable => true)
expect(parse("-v", "-v", "-v")["verbose"].count).to eq(3)
end
end
describe "with :hash type" do
before do
create "--attributes" => :hash
end
it "accepts a switch=<value> assignment" do
expect(parse("--attributes=name:string", "age:integer")["attributes"]).to eq("name" => "string", "age" => "integer")
end
it "accepts a switch <value> assignment" do
expect(parse("--attributes", "name:string", "age:integer")["attributes"]).to eq("name" => "string", "age" => "integer")
end
it "must not mix values with other switches" do
expect(parse("--attributes", "name:string", "age:integer", "--baz", "cool")["attributes"]).to eq("name" => "string", "age" => "integer")
end
it "must not allow the same hash key to be specified multiple times" do
expect { parse("--attributes", "name:string", "name:integer") }.to raise_error(Thor::MalformattedArgumentError, "You can't specify 'name' more than once in option '--attributes'; got name:string and name:integer")
end
it "allows multiple values if repeatable is specified" do
create :attributes => Thor::Option.new("attributes", :type => :hash, :repeatable => true)
expect(parse("--attributes", "name:one", "foo:1", "--attributes", "name:two", "bar:2")["attributes"]).to eq({"name"=>"two", "foo"=>"1", "bar" => "2"})
end
end
describe "with :array type" do
before do
create "--attributes" => :array
end
it "accepts a switch=<value> assignment" do
expect(parse("--attributes=a", "b", "c")["attributes"]).to eq(%w(a b c))
end
it "accepts a switch <value> assignment" do
expect(parse("--attributes", "a", "b", "c")["attributes"]).to eq(%w(a b c))
end
it "must not mix values with other switches" do
expect(parse("--attributes", "a", "b", "c", "--baz", "cool")["attributes"]).to eq(%w(a b c))
end
it "allows multiple values if repeatable is specified" do
create :attributes => Thor::Option.new("attributes", :type => :array, :repeatable => true)
expect(parse("--attributes", "1", "2", "--attributes", "3", "4")["attributes"]).to eq([["1", "2"], ["3", "4"]])
end
end
describe "with :numeric type" do
before do
create "n" => :numeric, "m" => 5
end
it "accepts a -nXY assignment" do
expect(parse("-n12")["n"]).to eq(12)
end
it "converts values to numeric types" do
expect(parse("-n", "3", "-m", ".5")).to eq("n" => 3, "m" => 0.5)
end
it "raises error when value isn't numeric" do
expect { parse("-n", "foo") }.to raise_error(Thor::MalformattedArgumentError,
"Expected numeric value for '-n'; got \"foo\"")
end
it "raises error when value isn't in enum" do
enum = [1, 2]
create :limit => Thor::Option.new("limit", :type => :numeric, :enum => enum)
expect { parse("--limit", "3") }.to raise_error(Thor::MalformattedArgumentError,
"Expected '--limit' to be one of #{enum.join(', ')}; got 3")
end
it "allows multiple values if repeatable is specified" do
create :run => Thor::Option.new("run", :type => :numeric, :repeatable => true)
expect(parse("--run", "1", "--run", "2")["run"]).to eq([1, 2])
end
end
end
end
spec/quality_spec.rb 0000664 0000000 0000000 00000003757 14002057672 0015056 0 ustar 00root root 0000000 0000000 describe "The library itself" do
def check_for_spec_defs_with_single_quotes(filename)
failing_lines = []
File.readlines(filename).each_with_index do |line, number|
failing_lines << number + 1 if line =~ /^ *(describe|it|context) {1}'{1}/
end
"#{filename} uses inconsistent single quotes on lines #{failing_lines.join(', ')}" unless failing_lines.empty?
end
def check_for_tab_characters(filename)
failing_lines = []
File.readlines(filename).each_with_index do |line, number|
failing_lines << number + 1 if line =~ /\t/
end
"#{filename} has tab characters on lines #{failing_lines.join(', ')}" unless failing_lines.empty?
end
def check_for_extra_spaces(filename)
failing_lines = []
File.readlines(filename).each_with_index do |line, number|
next if line =~ /^\s+#.*\s+\n$/
failing_lines << number + 1 if line =~ /\s+\n$/
end
"#{filename} has spaces on the EOL on lines #{failing_lines.join(', ')}" unless failing_lines.empty?
end
RSpec::Matchers.define :be_well_formed do
failure_message do |actual|
actual.join("\n")
end
match(&:empty?)
end
it "has no malformed whitespace" do
exempt = /\.gitmodules|\.marshal|fixtures|vendor|spec|ssl_certs|LICENSE/
error_messages = []
Dir.chdir(File.expand_path("../..", __FILE__)) do
`git ls-files`.split("\n").each do |filename|
next if filename =~ exempt
error_messages << check_for_tab_characters(filename)
error_messages << check_for_extra_spaces(filename)
end
end
expect(error_messages.compact).to be_well_formed
end
it "uses double-quotes consistently in specs" do
included = /spec/
error_messages = []
Dir.chdir(File.expand_path("../", __FILE__)) do
`git ls-files`.split("\n").each do |filename|
next unless filename =~ included
error_messages << check_for_spec_defs_with_single_quotes(filename)
end
end
expect(error_messages.compact).to be_well_formed
end
end
spec/rake_compat_spec.rb 0000664 0000000 0000000 00000003122 14002057672 0015635 0 ustar 00root root 0000000 0000000 require "helper"
require "thor/rake_compat"
require "rake/tasklib"
$main = self
class RakeTask < Rake::TaskLib
def initialize
define
end
def define
$main.instance_eval do
desc "Say it's cool"
task :cool do
puts "COOL"
end
namespace :hiper_mega do
task :super do
puts "HIPER MEGA SUPER"
end
end
end
end
end
class ThorTask < Thor
include Thor::RakeCompat
RakeTask.new
end
describe Thor::RakeCompat do
it "sets the rakefile application" do
expect(%w(rake_compat_spec.rb Thorfile)).to include(Rake.application.rakefile)
end
it "adds rake tasks to thor classes too" do
task = ThorTask.tasks["cool"]
expect(task).to be
end
it "uses rake tasks descriptions on thor" do
expect(ThorTask.tasks["cool"].description).to eq("Say it's cool")
end
it "gets usage from rake tasks name" do
expect(ThorTask.tasks["cool"].usage).to eq("cool")
end
it "uses non namespaced name as description if non is available" do
expect(ThorTask::HiperMega.tasks["super"].description).to eq("super")
end
it "converts namespaces to classes" do
expect(ThorTask.const_get(:HiperMega)).to eq(ThorTask::HiperMega)
end
it "does not add tasks from higher namespaces in lowers namespaces" do
expect(ThorTask.tasks["super"]).not_to be
end
it "invoking the thor task invokes the rake task" do
expect(capture(:stdout) do
ThorTask.start %w(cool)
end).to eq("COOL\n")
expect(capture(:stdout) do
ThorTask::HiperMega.start %w(super)
end).to eq("HIPER MEGA SUPER\n")
end
end
spec/register_spec.rb 0000664 0000000 0000000 00000013306 14002057672 0015201 0 ustar 00root root 0000000 0000000 require "helper"
class BoringVendorProvidedCLI < Thor
desc "boring", "do boring stuff"
def boring
puts "bored. <yawn>"
end
end
class ExcitingPluginCLI < Thor
desc "hooray", "say hooray!"
def hooray
puts "hooray!"
end
desc "fireworks", "exciting fireworks!"
def fireworks
puts "kaboom!"
end
end
class SuperSecretPlugin < Thor
default_command :squirrel
desc "squirrel", "All of secret squirrel's secrets"
def squirrel
puts "I love nuts"
end
end
class GroupPlugin < Thor::Group
desc "part one"
def part_one
puts "part one"
end
desc "part two"
def part_two
puts "part two"
end
end
class ClassOptionGroupPlugin < Thor::Group
class_option :who,
:type => :string,
:aliases => "-w",
:default => "zebra"
end
class PluginInheritingFromClassOptionsGroup < ClassOptionGroupPlugin
desc "animal"
def animal
p options[:who]
end
end
class PluginWithDefault < Thor
desc "say MSG", "print MSG"
def say(msg)
puts msg
end
default_command :say
end
class PluginWithDefaultMultipleArguments < Thor
desc "say MSG [MSG]", "print multiple messages"
def say(*args)
puts args
end
default_command :say
end
class PluginWithDefaultcommandAndDeclaredArgument < Thor
desc "say MSG [MSG]", "print multiple messages"
argument :msg
def say
puts msg
end
default_command :say
end
class SubcommandWithDefault < Thor
default_command :default
desc "default", "default subcommand"
def default
puts "default"
end
desc "with_args", "subcommand with arguments"
def with_args(*args)
puts "received arguments: " + args.join(",")
end
end
BoringVendorProvidedCLI.register(
ExcitingPluginCLI,
"exciting",
"do exciting things",
"Various non-boring actions"
)
BoringVendorProvidedCLI.register(
SuperSecretPlugin,
"secret",
"secret stuff",
"Nothing to see here. Move along.",
:hide => true
)
BoringVendorProvidedCLI.register(
GroupPlugin,
"groupwork",
"Do a bunch of things in a row",
"purple monkey dishwasher"
)
BoringVendorProvidedCLI.register(
PluginInheritingFromClassOptionsGroup,
"zoo",
"zoo [-w animal]",
"Shows a provided animal or just zebra"
)
BoringVendorProvidedCLI.register(
PluginWithDefault,
"say",
"say message",
"subcommands ftw"
)
BoringVendorProvidedCLI.register(
PluginWithDefaultMultipleArguments,
"say_multiple",
"say message",
"subcommands ftw"
)
BoringVendorProvidedCLI.register(
PluginWithDefaultcommandAndDeclaredArgument,
"say_argument",
"say message",
"subcommands ftw"
)
BoringVendorProvidedCLI.register(SubcommandWithDefault,
"subcommand", "subcommand", "Run subcommands")
describe ".register-ing a Thor subclass" do
it "registers the plugin as a subcommand" do
fireworks_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(exciting fireworks)) }
expect(fireworks_output).to eq("kaboom!\n")
end
it "includes the plugin's usage in the help" do
help_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(help)) }
expect(help_output).to include("do exciting things")
end
context "with a default command," do
it "invokes the default command correctly" do
output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(say hello)) }
expect(output).to include("hello")
end
it "invokes the default command correctly with multiple args" do
output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(say_multiple hello adam)) }
expect(output).to include("hello")
expect(output).to include("adam")
end
it "invokes the default command correctly with a declared argument" do
output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(say_argument hello)) }
expect(output).to include("hello")
end
it "displays the subcommand's help message" do
output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(subcommand help)) }
expect(output).to include("default subcommand")
expect(output).to include("subcommand with argument")
end
it "invokes commands with their actual args" do
output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(subcommand with_args actual_argument)) }
expect(output.strip).to eql("received arguments: actual_argument")
end
end
context "when $thor_runner is false" do
it "includes the plugin's subcommand name in subcommand's help" do
begin
$thor_runner = false
help_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(exciting)) }
expect(help_output).to include("thor exciting fireworks")
ensure
$thor_runner = true
end
end
end
context "when hidden" do
it "omits the hidden plugin's usage from the help" do
help_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(help)) }
expect(help_output).not_to include("secret stuff")
end
it "registers the plugin as a subcommand" do
secret_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(secret squirrel)) }
expect(secret_output).to eq("I love nuts\n")
end
end
end
describe ".register-ing a Thor::Group subclass" do
it "registers the group as a single command" do
group_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(groupwork)) }
expect(group_output).to eq("part one\npart two\n")
end
end
describe ".register-ing a Thor::Group subclass with class options" do
it "works w/o command options" do
group_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(zoo)) }
expect(group_output).to match(/zebra/)
end
it "works w/command options" do
group_output = capture(:stdout) { BoringVendorProvidedCLI.start(%w(zoo -w lion)) }
expect(group_output).to match(/lion/)
end
end
spec/runner_spec.rb 0000664 0000000 0000000 00000021343 14002057672 0014666 0 ustar 00root root 0000000 0000000 require "helper"
require "thor/runner"
describe Thor::Runner do
def when_no_thorfiles_exist
old_dir = Dir.pwd
Dir.chdir ".."
delete = Thor::Base.subclasses.select { |e| e.namespace == "default" }
delete.each { |e| Thor::Base.subclasses.delete e }
yield
Thor::Base.subclasses.concat delete
Dir.chdir old_dir
end
describe "#help" do
it "shows information about Thor::Runner itself" do
expect(capture(:stdout) { Thor::Runner.start(%w(help)) }).to match(/List the available thor commands/)
end
it "shows information about a specific Thor::Runner command" do
content = capture(:stdout) { Thor::Runner.start(%w(help list)) }
expect(content).to match(/List the available thor commands/)
expect(content).not_to match(/help \[COMMAND\]/)
end
it "shows information about a specific Thor class" do
content = capture(:stdout) { Thor::Runner.start(%w(help my_script)) }
expect(content).to match(/zoo\s+# zoo around/m)
end
it "shows information about a specific command from a specific Thor class" do
content = capture(:stdout) { Thor::Runner.start(%w(help my_script:zoo)) }
expect(content).to match(/zoo around/)
expect(content).not_to match(/help \[COMMAND\]/)
end
it "shows information about a specific Thor group class" do
content = capture(:stdout) { Thor::Runner.start(%w(help my_counter)) }
expect(content).to match(/my_counter N/)
end
it "raises error if a class/command cannot be found" do
content = capture(:stderr) { Thor::Runner.start(%w(help unknown)) }
expect(content.strip).to eq('Could not find command "unknown" in "default" namespace.')
end
it "raises error if a class/command cannot be found for a setup without thorfiles" do
when_no_thorfiles_exist do
expect(Thor::Runner).to receive :exit
content = capture(:stderr) { Thor::Runner.start(%w(help unknown)) }
expect(content.strip).to eq('Could not find command "unknown".')
end
end
end
describe "#start" do
it "invokes a command from Thor::Runner" do
ARGV.replace %w(list)
expect(capture(:stdout) { Thor::Runner.start }).to match(/my_counter N/)
end
it "invokes a command from a specific Thor class" do
ARGV.replace %w(my_script:zoo)
expect(Thor::Runner.start).to be true
end
it "invokes the default command from a specific Thor class if none is specified" do
ARGV.replace %w(my_script)
expect(Thor::Runner.start).to eq("default command")
end
it "forwards arguments to the invoked command" do
ARGV.replace %w(my_script:animal horse)
expect(Thor::Runner.start).to eq(%w(horse))
end
it "invokes commands through shortcuts" do
ARGV.replace %w(my_script -T horse)
expect(Thor::Runner.start).to eq(%w(horse))
end
it "invokes a Thor::Group" do
ARGV.replace %w(my_counter 1 2 --third 3)
expect(Thor::Runner.start).to eq([1, 2, 3, nil, nil, nil])
end
it "raises an error if class/command can't be found" do
ARGV.replace %w(unknown)
content = capture(:stderr) { Thor::Runner.start }
expect(content.strip).to eq('Could not find command "unknown" in "default" namespace.')
end
it "raises an error if class/command can't be found in a setup without thorfiles" do
when_no_thorfiles_exist do
ARGV.replace %w(unknown)
expect(Thor::Runner).to receive :exit
content = capture(:stderr) { Thor::Runner.start }
expect(content.strip).to eq('Could not find command "unknown".')
end
end
it "does not swallow NoMethodErrors that occur inside the called method" do
ARGV.replace %w(my_script:call_unexistent_method)
expect { Thor::Runner.start }.to raise_error(NoMethodError)
end
it "does not swallow Thor::Group InvocationError" do
ARGV.replace %w(whiny_generator)
expect { Thor::Runner.start }.to raise_error(ArgumentError, /thor wrong_arity takes 1 argument, but it should not/)
end
it "does not swallow Thor InvocationError" do
ARGV.replace %w(my_script:animal)
content = capture(:stderr) { Thor::Runner.start }
expect(content.strip).to eq('ERROR: "thor animal" was called with no arguments
Usage: "thor my_script:animal TYPE"')
end
end
describe "commands" do
before do
@location = "#{File.dirname(__FILE__)}/fixtures/command.thor"
@original_yaml = {
"random" => {
:location => @location,
:filename => "4a33b894ffce85d7b412fc1b36f88fe0",
:namespaces => %w(amazing)
}
}
root_file = File.join(Thor::Util.thor_root, "thor.yml")
# Stub load and save to avoid thor.yaml from being overwritten
allow(YAML).to receive(:load_file).and_return(@original_yaml)
allow(File).to receive(:exist?).with(root_file).and_return(true)
allow(File).to receive(:open).with(root_file, "w")
end
describe "list" do
it "gives a list of the available commands" do
ARGV.replace %w(list)
content = capture(:stdout) { Thor::Runner.start }
expect(content).to match(/amazing:describe NAME\s+# say that someone is amazing/m)
end
it "gives a list of the available Thor::Group classes" do
ARGV.replace %w(list)
expect(capture(:stdout) { Thor::Runner.start }).to match(/my_counter N/)
end
it "can filter a list of the available commands by --group" do
ARGV.replace %w(list --group standard)
expect(capture(:stdout) { Thor::Runner.start }).to match(/amazing:describe NAME/)
ARGV.replace []
expect(capture(:stdout) { Thor::Runner.start }).not_to match(/my_script:animal TYPE/)
ARGV.replace %w(list --group script)
expect(capture(:stdout) { Thor::Runner.start }).to match(/my_script:animal TYPE/)
end
it "can skip all filters to show all commands using --all" do
ARGV.replace %w(list --all)
content = capture(:stdout) { Thor::Runner.start }
expect(content).to match(/amazing:describe NAME/)
expect(content).to match(/my_script:animal TYPE/)
end
it "doesn't list superclass commands in the subclass" do
ARGV.replace %w(list)
expect(capture(:stdout) { Thor::Runner.start }).not_to match(/amazing:help/)
end
it "presents commands in the default namespace with an empty namespace" do
ARGV.replace %w(list)
expect(capture(:stdout) { Thor::Runner.start }).to match(/^thor :cow\s+# prints 'moo'/m)
end
it "runs commands with an empty namespace from the default namespace" do
ARGV.replace %w(:command_conflict)
expect(capture(:stdout) { Thor::Runner.start }).to eq("command\n")
end
it "runs groups even when there is a command with the same name" do
ARGV.replace %w(command_conflict)
expect(capture(:stdout) { Thor::Runner.start }).to eq("group\n")
end
it "runs commands with no colon in the default namespace" do
ARGV.replace %w(cow)
expect(capture(:stdout) { Thor::Runner.start }).to eq("moo\n")
end
end
describe "uninstall" do
before do
path = File.join(Thor::Util.thor_root, @original_yaml["random"][:filename])
expect(FileUtils).to receive(:rm_rf).with(path)
end
it "uninstalls existing thor modules" do
silence(:stdout) { Thor::Runner.start(%w(uninstall random)) }
end
end
describe "installed" do
before do
expect(Dir).to receive(:[]).and_return([])
end
it "displays the modules installed in a pretty way" do
stdout = capture(:stdout) { Thor::Runner.start(%w(installed)) }
expect(stdout).to match(/random\s*amazing/)
expect(stdout).to match(/amazing:describe NAME\s+# say that someone is amazing/m)
end
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::MD5.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)
end
silence_warnings do
silence(:stdout) { Thor::Runner.start(%w(update random)) }
end
end
it "installs thor files" do
ARGV.replace %W(install #{@location})
silence_warnings do
silence(:stdout) { Thor::Runner.start }
end
end
end
end
end
spec/script_exit_status_spec.rb 0000664 0000000 0000000 00000001566 14002057672 0017322 0 ustar 00root root 0000000 0000000 describe "when the Thor class's exit_with_failure? method returns true" do
def thor_command(command)
gem_dir= File.expand_path("#{File.dirname(__FILE__)}/..")
lib_path= "#{gem_dir}/lib"
script_path= "#{gem_dir}/spec/fixtures/exit_status.thor"
ruby_lib= ENV['RUBYLIB'].nil? ? lib_path : "#{lib_path}:#{ENV['RUBYLIB']}"
full_command= "ruby #{script_path} #{command}"
r,w= IO.pipe
pid= spawn({'RUBYLIB' => ruby_lib},
full_command,
{:out => w, :err => [:child, :out]})
w.close
_, exit_status= Process.wait2(pid)
r.read
r.close
exit_status.exitstatus
end
it "a command that raises a Thor::Error exits with a status of 1" do
expect(thor_command("error")).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)
end
end
spec/shell/ 0000775 0000000 0000000 00000000000 14002057672 0013122 5 ustar 00root root 0000000 0000000 spec/shell/basic_spec.rb 0000664 0000000 0000000 00000044441 14002057672 0015551 0 ustar 00root root 0000000 0000000 # coding: utf-8
require "helper"
describe Thor::Shell::Basic do
def shell
@shell ||= Thor::Shell::Basic.new
end
describe "#padding" do
it "cannot be set to below zero" do
shell.padding = 10
expect(shell.padding).to eq(10)
shell.padding = -1
expect(shell.padding).to eq(0)
end
end
describe "#indent" do
it "sets the padding temporarily" do
shell.indent { expect(shell.padding).to eq(1) }
expect(shell.padding).to eq(0)
end
it "derives padding from original value" do
shell.padding = 6
shell.indent { expect(shell.padding).to eq(7) }
end
it "accepts custom indentation amounts" do
shell.indent(6) do
expect(shell.padding).to eq(6)
end
end
it "increases the padding when nested" do
shell.indent do
expect(shell.padding).to eq(1)
shell.indent do
expect(shell.padding).to eq(2)
end
end
expect(shell.padding).to eq(0)
end
end
describe "#ask" do
it "prints a message to the user and gets the response" do
expect(Thor::LineEditor).to receive(:readline).with("Should I overwrite it? ", {}).and_return("Sure")
expect(shell.ask("Should I overwrite it?")).to eq("Sure")
end
it "prints a message to the user prefixed with the current padding" do
expect(Thor::LineEditor).to receive(:readline).with(" Enter your name: ", {}).and_return("George")
shell.padding = 2
shell.ask("Enter your name:")
end
it "prints a message and returns nil if EOF is given as input" do
expect(Thor::LineEditor).to receive(:readline).with(" ", {}).and_return(nil)
expect(shell.ask("")).to eq(nil)
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($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")
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")
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")
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")
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")
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")
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(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(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(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(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(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(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(shell.no?("Should I overwrite it?")).to be false
end
end
describe "#say" do
it "prints a message to the user" do
expect($stdout).to receive(:print).with("Running...\n")
shell.say("Running...")
end
it "prints a message to the user without new line if it ends with a whitespace" do
expect($stdout).to receive(:print).with("Running... ")
shell.say("Running... ")
end
it "does not use a new line with whitespace+newline embedded" do
expect($stdout).to receive(:print).with("It's \nRunning...\n")
shell.say("It's \nRunning...")
end
it "prints a message to the user without new line" do
expect($stdout).to receive(:print).with("Running...")
shell.say("Running...", nil, false)
end
it "coerces everything to a string before printing" do
expect($stdout).to receive(:print).with("this_is_not_a_string\n")
shell.say(:this_is_not_a_string, nil, true)
end
it "does not print a message if muted" do
expect($stdout).not_to receive(:print)
shell.mute do
shell.say("Running...")
end
end
it "does not print a message if base is set to quiet" do
shell.base = MyCounter.new [1, 2]
expect(shell.base).to receive(:options).and_return(:quiet => true)
expect($stdout).not_to receive(:print)
shell.say("Running...")
end
end
describe "#print_wrapped" do
let(:message) do
"Creates a back-up of the given folder by compressing it in a .tar.gz\n"\
"file and then uploading it to the configured Amazon S3 Bucket.\n\n"\
"It does not verify the integrity of the generated back-up."
end
before do
allow(ENV).to receive(:[]).with("THOR_COLUMNS").and_return(80)
end
context "without indentation" do
subject(:wrap_text) { described_class.new.print_wrapped(message) }
let(:expected_output) do
"Creates a back-up of the given folder by compressing it in a .tar.gz file and\n"\
"then uploading it to the configured Amazon S3 Bucket.\n\n"\
"It does not verify the integrity of the generated back-up.\n"
end
it "properly wraps the text around the 80th column" do
expect { wrap_text }.to output(expected_output).to_stdout
end
end
context "with indentation" do
subject(:wrap_text) { described_class.new.print_wrapped(message, :indent => 4) }
let(:expected_output) do
" Creates a back-up of the given folder by compressing it in a .tar.gz file\n"\
" and then uploading it to the configured Amazon S3 Bucket.\n\n"\
" It does not verify the integrity of the generated back-up.\n"
end
it "properly wraps the text around the 80th column" do
expect { wrap_text }.to output(expected_output).to_stdout
end
end
end
describe "#say_status" do
it "prints a message to the user with status" do
expect($stdout).to receive(:print).with(" create ~/.thor/command.thor\n")
shell.say_status(:create, "~/.thor/command.thor")
end
it "always uses new line" do
expect($stdout).to receive(:print).with(" create \n")
shell.say_status(:create, "")
end
it "does not print a message if base is muted" do
expect(shell).to receive(:mute?).and_return(true)
expect($stdout).not_to receive(:print)
shell.mute do
shell.say_status(:created, "~/.thor/command.thor")
end
end
it "does not print a message if base is set to quiet" do
base = MyCounter.new [1, 2]
expect(base).to receive(:options).and_return(:quiet => true)
expect($stdout).not_to receive(:print)
shell.base = base
shell.say_status(:created, "~/.thor/command.thor")
end
it "does not print a message if log status is set to false" do
expect($stdout).not_to receive(:print)
shell.say_status(:created, "~/.thor/command.thor", false)
end
it "uses padding to set message's left margin" do
shell.padding = 2
expect($stdout).to receive(:print).with(" create ~/.thor/command.thor\n")
shell.say_status(:create, "~/.thor/command.thor")
end
end
describe "#print_in_columns" do
before do
@array = [1_234_567_890]
@array += ("a".."e").to_a
end
it "prints in columns" do
content = capture(:stdout) { shell.print_in_columns(@array) }
expect(content.rstrip).to eq("1234567890 a b c d e")
end
end
describe "#print_table" do
before do
@table = []
@table << ["abc", "#123", "first three"]
@table << ["", "#0", "empty"]
@table << ["xyz", "#786", "last three"]
end
it "prints a table" do
content = capture(:stdout) { shell.print_table(@table) }
expect(content).to eq(<<-TABLE)
abc #123 first three
#0 empty
xyz #786 last three
TABLE
end
it "prints a table with indentation" do
content = capture(:stdout) { shell.print_table(@table, :indent => 2) }
expect(content).to eq(<<-TABLE)
abc #123 first three
#0 empty
xyz #786 last three
TABLE
end
it "uses maximum terminal width" do
@table << ["def", "#456", "Lançam foo bar"]
@table << ["ghi", "#789", "بالله عليكم"]
expect(shell).to receive(:terminal_width).and_return(20)
content = capture(:stdout) { shell.print_table(@table, :indent => 2, :truncate => true) }
expect(content).to eq(<<-TABLE)
abc #123 firs...
#0 empty
xyz #786 last...
def #456 Lanç...
ghi #789 بالل...
TABLE
end
it "honors the colwidth option" do
content = capture(:stdout) { shell.print_table(@table, :colwidth => 10) }
expect(content).to eq(<<-TABLE)
abc #123 first three
#0 empty
xyz #786 last three
TABLE
end
it "prints tables with implicit columns" do
2.times { @table.first.pop }
content = capture(:stdout) { shell.print_table(@table) }
expect(content).to eq(<<-TABLE)
abc
#0 empty
xyz #786 last three
TABLE
end
it "prints a table with small numbers and right-aligns them" do
table = [
["Name", "Number", "Color"], # rubocop: disable WordArray
["Erik", 1, "green"]
]
content = capture(:stdout) { shell.print_table(table) }
expect(content).to eq(<<-TABLE)
Name Number Color
Erik 1 green
TABLE
end
it "doesn't output extra spaces for right-aligned columns in the last column" do
table = [
["Name", "Number"], # rubocop: disable WordArray
["Erik", 1]
]
content = capture(:stdout) { shell.print_table(table) }
expect(content).to eq(<<-TABLE)
Name Number
Erik 1
TABLE
end
it "prints a table with big numbers" do
table = [
["Name", "Number", "Color"], # rubocop: disable WordArray
["Erik", 1_234_567_890_123, "green"]
]
content = capture(:stdout) { shell.print_table(table) }
expect(content).to eq(<<-TABLE)
Name Number Color
Erik 1234567890123 green
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")
shell.file_collision("foo")
end
it "outputs a new line and returns true if stdin is closed" do
expect($stdout).to receive(:print).with("\n")
expect(Thor::LineEditor).to receive(:readline).and_return(nil)
expect(shell.file_collision("foo")).to be true
end
it "returns true if the user chooses default option" do
expect(Thor::LineEditor).to receive(:readline).and_return("")
expect(shell.file_collision("foo")).to be true
end
it "returns false if the user chooses no" do
expect(Thor::LineEditor).to receive(:readline).and_return("n")
expect(shell.file_collision("foo")).to be false
end
it "returns true if the user chooses yes" do
expect(Thor::LineEditor).to receive(:readline).and_return("y")
expect(shell.file_collision("foo")).to be true
end
it "shows help usage if the user chooses help" do
expect(Thor::LineEditor).to receive(:readline).and_return("h", "n")
help = capture(:stdout) { shell.file_collision("foo") }
expect(help).to match(/h \- help, show this help/)
end
it "quits if the user chooses quit" do
expect($stdout).to receive(:print).with("Aborting...\n")
expect(Thor::LineEditor).to receive(:readline).and_return("q")
expect do
shell.file_collision("foo")
end.to raise_error(SystemExit)
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(shell.file_collision("foo")).to be true
expect($stdout).not_to receive(:print)
expect(shell.file_collision("foo")).to be true
end
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")
shell.file_collision("foo") {}
end
it "invokes the diff command" do
expect(Thor::LineEditor).to receive(:readline).and_return("d")
expect(Thor::LineEditor).to receive(:readline).and_return("n")
expect(shell).to receive(:system).with(/diff -u/)
capture(:stdout) { shell.file_collision("foo") {} }
end
it "invokes the merge tool" do
allow(shell).to receive(:merge_tool).and_return("meld")
expect(Thor::LineEditor).to receive(:readline).and_return("m")
expect(shell).to receive(:system).with(/meld/)
capture(:stdout) { shell.file_collision("foo") {} }
end
it "invokes the merge tool that specified at ENV['THOR_MERGE']" do
allow(ENV).to receive(:[]).with("THOR_MERGE").and_return("meld")
expect(Thor::LineEditor).to receive(:readline).and_return("m")
expect(shell).to receive(:system).with(/meld/)
capture(:stdout) { shell.file_collision("foo") {} }
end
it "show warning if user chooses merge but merge tool is not specified" do
allow(shell).to receive(:merge_tool).and_return("")
expect(Thor::LineEditor).to receive(:readline).and_return("m")
expect(Thor::LineEditor).to receive(:readline).and_return("n")
help = capture(:stdout) { shell.file_collision("foo") {} }
expect(help).to match(/Please specify merge tool to `THOR_MERGE` env/)
end
end
end
end
spec/shell/color_spec.rb 0000664 0000000 0000000 00000013271 14002057672 0015603 0 ustar 00root root 0000000 0000000 require "helper"
describe Thor::Shell::Color do
def shell
@shell ||= Thor::Shell::Color.new
end
before do
allow($stdout).to receive(:tty?).and_return(true)
allow(ENV).to receive(:[]).and_return(nil)
allow(ENV).to receive(:[]).with("TERM").and_return("ansi")
allow_any_instance_of(StringIO).to receive(:tty?).and_return(true)
end
describe "#ask" do
it "sets the color if specified and tty?" do
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 "does not set the color if specified and NO_COLOR is set" do
allow(ENV).to receive(:[]).with("NO_COLOR").and_return("")
expect(Thor::LineEditor).to receive(:readline).with("Is this green? ", anything).and_return("yes")
shell.ask "Is this green?", :green
expect(Thor::LineEditor).to receive(:readline).with("Is this green? [Yes, No, Maybe] ", 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]
end
it "supports the legacy color syntax" do
expect(Thor::LineEditor).to receive(:readline).with("\e[1m\e[34mIs this legacy blue? \e[0m", anything).and_return("yes")
shell.ask "Is this legacy blue?", [:blue, true]
end
end
describe "#say" do
it "set the color if specified and tty?" do
out = capture(:stdout) do
shell.say "Wow! Now we have colors!", :green
end
expect(out.chomp).to eq("\e[32mWow! Now we have colors!\e[0m")
end
it "does not set the color if output is not a tty" do
out = capture(:stdout) do
expect($stdout).to receive(:tty?).and_return(false)
shell.say "Wow! Now we have colors!", :green
end
expect(out.chomp).to eq("Wow! Now we have colors!")
end
it "does not set the color if NO_COLOR is set" do
allow(ENV).to receive(:[]).with("NO_COLOR").and_return("")
out = capture(:stdout) do
shell.say "Wow! Now we have colors!", :green
end
expect(out.chomp).to eq("Wow! Now we have colors!")
end
it "does not use a new line even with colors" do
out = capture(:stdout) do
shell.say "Wow! Now we have colors! ", :green
end
expect(out.chomp).to eq("\e[32mWow! Now we have colors! \e[0m")
end
it "handles an Array of colors" do
out = capture(:stdout) do
shell.say "Wow! Now we have colors *and* background colors", [:green, :on_red, :bold]
end
expect(out.chomp).to eq("\e[32m\e[41m\e[1mWow! Now we have colors *and* background colors\e[0m")
end
it "supports the legacy color syntax" do
out = capture(:stdout) do
shell.say "Wow! This still works?", [:blue, true]
end
expect(out.chomp).to eq("\e[1m\e[34mWow! This still works?\e[0m")
end
end
describe "#say_status" do
it "uses color to say status" do
out = capture(:stdout) do
shell.say_status :conflict, "README", :red
end
expect(out.chomp).to eq("\e[1m\e[31m conflict\e[0m README")
end
end
describe "#set_color" do
it "colors a string with a foreground color" do
red = shell.set_color "hi!", :red
expect(red).to eq("\e[31mhi!\e[0m")
end
it "colors a string with a background color" do
on_red = shell.set_color "hi!", :white, :on_red
expect(on_red).to eq("\e[37m\e[41mhi!\e[0m")
end
it "colors a string with a bold color" do
bold = shell.set_color "hi!", :white, true
expect(bold).to eq("\e[1m\e[37mhi!\e[0m")
bold = shell.set_color "hi!", :white, :bold
expect(bold).to eq("\e[37m\e[1mhi!\e[0m")
bold = shell.set_color "hi!", :white, :on_red, :bold
expect(bold).to eq("\e[37m\e[41m\e[1mhi!\e[0m")
end
it "does nothing when there are no colors" do
colorless = shell.set_color "hi!", nil
expect(colorless).to eq("hi!")
colorless = shell.set_color "hi!"
expect(colorless).to eq("hi!")
end
it "does nothing when stdout is not a tty" do
allow($stdout).to receive(:tty?).and_return(false)
colorless = shell.set_color "hi!", :white
expect(colorless).to eq("hi!")
end
it "does nothing when the TERM environment variable is set to 'dumb'" do
allow(ENV).to receive(:[]).with("TERM").and_return("dumb")
colorless = shell.set_color "hi!", :white
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("")
allow($stdout).to receive(:tty?).and_return(true)
colorless = shell.set_color "hi!", :white
expect(colorless).to eq("hi!")
end
end
describe "#file_collision" do
describe "when a block is given" do
it "invokes the diff command" do
allow($stdout).to receive(:print)
allow($stdout).to receive(:tty?).and_return(true)
expect(Thor::LineEditor).to receive(:readline).and_return("d", "n")
output = capture(:stdout) { shell.file_collision("spec/fixtures/doc/README") { "README\nEND\n" } }
expect(output).to match(/\e\[31m\- __start__\e\[0m/)
expect(output).to match(/^ README/)
expect(output).to match(/\e\[32m\+ END\e\[0m/)
end
end
end
end
spec/shell/html_spec.rb 0000664 0000000 0000000 00000002761 14002057672 0015433 0 ustar 00root root 0000000 0000000 require "helper"
describe Thor::Shell::HTML do
def shell
@shell ||= Thor::Shell::HTML.new
end
describe "#say" do
it "sets the color if specified" do
out = capture(:stdout) { shell.say "Wow! Now we have colors!", :green }
expect(out.chomp).to eq('<span style="color: green;">Wow! Now we have colors!</span>')
end
it "sets bold if specified" do
out = capture(:stdout) { shell.say "Wow! Now we have colors *and* bold!", [:green, :bold] }
expect(out.chomp).to eq('<span style="color: green; font-weight: bold;">Wow! Now we have colors *and* bold!</span>')
end
it "does not use a new line even with colors" do
out = capture(:stdout) { shell.say "Wow! Now we have colors! ", :green }
expect(out.chomp).to eq('<span style="color: green;">Wow! Now we have colors! </span>')
end
end
describe "#say_status" do
it "uses color to say status" do
expect($stdout).to receive(:print).with("<span style=\"color: red; font-weight: bold;\"> conflict</span> README\n")
shell.say_status :conflict, "README", :red
end
end
describe "#set_color" do
it "escapes HTML content when unsing the default colors" do
expect(shell.set_color("<htmlcontent>", :blue)).to eq "<span style=\"color: blue;\"><htmlcontent></span>"
end
it "escapes HTML content when not using the default colors" do
expect(shell.set_color("<htmlcontent>", [:nocolor])).to eq "<span style=\";\"><htmlcontent></span>"
end
end
end
spec/shell_spec.rb 0000664 0000000 0000000 00000002341 14002057672 0014461 0 ustar 00root root 0000000 0000000 require "helper"
describe Thor::Shell do
def shell
@shell ||= Thor::Base.shell.new
end
describe "#initialize" do
it "sets shell value" do
base = MyCounter.new [1, 2], {}, :shell => shell
expect(base.shell).to eq(shell)
end
it "sets the base value on the shell if an accessor is available" do
base = MyCounter.new [1, 2], {}, :shell => shell
expect(shell.base).to eq(base)
end
end
describe "#shell" do
it "returns the shell in use" do
expect(MyCounter.new([1, 2]).shell).to be_kind_of(Thor::Base.shell)
end
it "uses $THOR_SHELL" do
class Thor::Shell::TestShell < Thor::Shell::Basic; end
expect(Thor::Base.shell).to eq(shell.class)
ENV["THOR_SHELL"] = "TestShell"
Thor::Base.shell = nil
expect(Thor::Base.shell).to eq(Thor::Shell::TestShell)
ENV["THOR_SHELL"] = ""
Thor::Base.shell = shell.class
expect(Thor::Base.shell).to eq(shell.class)
end
end
describe "with_padding" do
it "uses padding for inside block outputs" do
base = MyCounter.new([1, 2])
base.with_padding do
expect(capture(:stdout) { base.say_status :padding, "cool" }.strip).to eq("padding cool")
end
end
end
end
spec/subcommand_spec.rb 0000664 0000000 0000000 00000005175 14002057672 0015512 0 ustar 00root root 0000000 0000000 require "helper"
describe Thor do
describe "#subcommand" do
it "maps a given subcommand to another Thor subclass" do
barn_help = capture(:stdout) { Scripts::MyDefaults.start(%w(barn)) }
expect(barn_help).to include("barn help [COMMAND] # Describe subcommands or one specific subcommand")
end
it "passes commands to subcommand classes" do
expect(capture(:stdout) { Scripts::MyDefaults.start(%w(barn open)) }.strip).to eq("Open sesame!")
end
it "passes arguments to subcommand classes" do
expect(capture(:stdout) { Scripts::MyDefaults.start(%w(barn open shotgun)) }.strip).to eq("That's going to leave a mark.")
end
it "ignores unknown options (the subcommand class will handle them)" do
expect(capture(:stdout) { Scripts::MyDefaults.start(%w(barn paint blue --coats 4)) }.strip).to eq("4 coats of blue paint")
end
it "passes parsed options to subcommands" do
output = capture(:stdout) { TestSubcommands::Parent.start(%w(sub print_opt --opt output)) }
expect(output).to eq("output")
end
it "accepts the help switch and calls the help command on the subcommand" do
output = capture(:stdout) { TestSubcommands::Parent.start(%w(sub print_opt --help)) }
sub_help = capture(:stdout) { TestSubcommands::Parent.start(%w(sub help print_opt)) }
expect(output).to eq(sub_help)
end
it "accepts the help short switch and calls the help command on the subcommand" do
output = capture(:stdout) { TestSubcommands::Parent.start(%w(sub print_opt -h)) }
sub_help = capture(:stdout) { TestSubcommands::Parent.start(%w(sub help print_opt)) }
expect(output).to eq(sub_help)
end
it "the help command on the subcommand and after it should result in the same output" do
output = capture(:stdout) { TestSubcommands::Parent.start(%w(sub help)) }
sub_help = capture(:stdout) { TestSubcommands::Parent.start(%w(help sub)) }
expect(output).to eq(sub_help)
end
end
context "subcommand with an arg" do
module SubcommandTest1
class Child1 < Thor
desc "foo NAME", "Fooo"
def foo(name)
puts "#{name} was given"
end
end
class Parent < Thor
desc "child1", "child1 description"
subcommand "child1", Child1
def self.exit_on_failure?
false
end
end
end
it "shows subcommand name and method name" do
sub_help = capture(:stderr) { SubcommandTest1::Parent.start(%w(child1 foo)) }
expect(sub_help).to eq ['ERROR: "thor child1 foo" was called with no arguments', 'Usage: "thor child1 foo NAME"', ""].join("\n")
end
end
end
spec/thor_spec.rb 0000664 0000000 0000000 00000065160 14002057672 0014336 0 ustar 00root root 0000000 0000000 require "helper"
describe Thor do
describe "#method_option" do
it "sets options to the next method to be invoked" do
args = %w(foo bar --force)
_, options = MyScript.start(args)
expect(options).to eq("force" => true)
end
describe ":lazy_default" do
it "is absent when option is not specified" do
_, options = MyScript.start(%w(with_optional))
expect(options).to eq({})
end
it "sets a default that can be overridden for strings" do
_, options = MyScript.start(%w(with_optional --lazy))
expect(options).to eq("lazy" => "yes")
_, options = MyScript.start(%w(with_optional --lazy yesyes!))
expect(options).to eq("lazy" => "yesyes!")
end
it "sets a default that can be overridden for numerics" do
_, options = MyScript.start(%w(with_optional --lazy-numeric))
expect(options).to eq("lazy_numeric" => 42)
_, options = MyScript.start(%w(with_optional --lazy-numeric 20000))
expect(options).to eq("lazy_numeric" => 20_000)
end
it "sets a default that can be overridden for arrays" do
_, options = MyScript.start(%w(with_optional --lazy-array))
expect(options).to eq("lazy_array" => %w(eat at joes))
_, options = MyScript.start(%w(with_optional --lazy-array hello there))
expect(options).to eq("lazy_array" => %w(hello there))
end
it "sets a default that can be overridden for hashes" do
_, options = MyScript.start(%w(with_optional --lazy-hash))
expect(options).to eq("lazy_hash" => {"swedish" => "meatballs"})
_, options = MyScript.start(%w(with_optional --lazy-hash polish:sausage))
expect(options).to eq("lazy_hash" => {"polish" => "sausage"})
end
end
describe "when :for is supplied" do
it "updates an already defined command" do
_, options = MyChildScript.start(%w(animal horse --other=fish))
expect(options[:other]).to eq("fish")
end
describe "and the target is on the parent class" do
it "updates an already defined command" do
args = %w(example_default_command my_param --new-option=verified)
options = Scripts::MyScript.start(args)
expect(options[:new_option]).to eq("verified")
end
it "adds a command to the command list if the updated command is on the parent class" do
expect(Scripts::MyScript.commands["example_default_command"]).to be
end
it "clones the parent command" do
expect(Scripts::MyScript.commands["example_default_command"]).not_to eq(MyChildScript.commands["example_default_command"])
end
end
end
end
describe "#default_command" do
it "sets a default command" do
expect(MyScript.default_command).to eq("example_default_command")
end
it "invokes the default command if no command is specified" do
expect(MyScript.start([])).to eq("default command")
end
it "invokes the default command if no command is specified even if switches are given" do
expect(MyScript.start(%w(--with option))).to eq("with" => "option")
end
it "inherits the default command from parent" do
expect(MyChildScript.default_command).to eq("example_default_command")
end
end
describe "#stop_on_unknown_option!" do
my_script = Class.new(Thor) do
class_option "verbose", :type => :boolean
class_option "mode", :type => :string
stop_on_unknown_option! :exec
desc "exec", "Run a command"
def exec(*args)
[options, args]
end
desc "boring", "An ordinary command"
def boring(*args)
[options, args]
end
end
it "passes remaining args to command when it encounters a non-option" do
expect(my_script.start(%w(exec command --verbose))).to eq [{}, %w(command --verbose)]
end
it "passes remaining args to command when it encounters an unknown option" do
expect(my_script.start(%w(exec --foo command --bar))).to eq [{}, %w(--foo command --bar)]
end
it "still accepts options that are given before non-options" do
expect(my_script.start(%w(exec --verbose command --foo))).to eq [{"verbose" => true}, %w(command --foo)]
end
it "still accepts options that require a value" do
expect(my_script.start(%w(exec --mode rashly command))).to eq [{"mode" => "rashly"}, %w(command)]
end
it "still passes everything after -- to command" do
expect(my_script.start(%w(exec -- --verbose))).to eq [{}, %w(--verbose)]
end
it "still passes everything after -- to command, complex" do
expect(my_script.start(%w[exec command --mode z again -- --verbose more])).to eq [{}, %w[command --mode z again -- --verbose more]]
end
it "does not affect ordinary commands" do
expect(my_script.start(%w(boring command --verbose))).to eq [{"verbose" => true}, %w(command)]
end
context "when provided with multiple command names" do
klass = Class.new(Thor) do
stop_on_unknown_option! :foo, :bar
end
it "affects all specified commands" do
expect(klass.stop_on_unknown_option?(double(:name => "foo"))).to be true
expect(klass.stop_on_unknown_option?(double(:name => "bar"))).to be true
expect(klass.stop_on_unknown_option?(double(:name => "baz"))).to be false
end
end
context "when invoked several times" do
klass = Class.new(Thor) do
stop_on_unknown_option! :foo
stop_on_unknown_option! :bar
end
it "affects all specified commands" do
expect(klass.stop_on_unknown_option?(double(:name => "foo"))).to be true
expect(klass.stop_on_unknown_option?(double(:name => "bar"))).to be true
expect(klass.stop_on_unknown_option?(double(:name => "baz"))).to be false
end
end
it "doesn't break new" do
expect(my_script.new).to be_a(Thor)
end
context "along with check_unknown_options!" do
my_script2 = Class.new(Thor) do
class_option "verbose", :type => :boolean
class_option "mode", :type => :string
check_unknown_options!
stop_on_unknown_option! :exec
desc "exec", "Run a command"
def exec(*args)
[options, args]
end
def self.exit_on_failure?
false
end
end
it "passes remaining args to command when it encounters a non-option" do
expect(my_script2.start(%w[exec command --verbose])).to eq [{}, %w[command --verbose]]
end
it "does not accept if first non-option looks like an option, but only refuses that invalid option" do
expect(capture(:stderr) do
my_script2.start(%w[exec --foo command --bar])
end.strip).to eq("Unknown switches \"--foo\"")
end
it "still accepts options that are given before non-options" do
expect(my_script2.start(%w[exec --verbose command])).to eq [{"verbose" => true}, %w[command]]
end
it "still accepts when non-options are given after real options and argument" do
expect(my_script2.start(%w[exec --verbose command --foo])).to eq [{"verbose" => true}, %w[command --foo]]
end
it "does not accept when non-option looks like an option and is after real options" do
expect(capture(:stderr) do
my_script2.start(%w[exec --verbose --foo])
end.strip).to eq("Unknown switches \"--foo\"")
end
it "still accepts options that require a value" do
expect(my_script2.start(%w[exec --mode rashly command])).to eq [{"mode" => "rashly"}, %w[command]]
end
it "still passes everything after -- to command" do
expect(my_script2.start(%w[exec -- --verbose])).to eq [{}, %w[--verbose]]
end
it "still passes everything after -- to command, complex" do
expect(my_script2.start(%w[exec command --mode z again -- --verbose more])).to eq [{}, %w[command --mode z again -- --verbose more]]
end
end
end
describe "#check_unknown_options!" do
my_script = Class.new(Thor) do
class_option "verbose", :type => :boolean
class_option "mode", :type => :string
check_unknown_options!
desc "checked", "a command with checked"
def checked(*args)
[options, args]
end
def self.exit_on_failure?
false
end
end
it "still accept options and arguments" do
expect(my_script.start(%w[checked command --verbose])).to eq [{"verbose" => true}, %w[command]]
end
it "still accepts options that are given before arguments" do
expect(my_script.start(%w[checked --verbose command])).to eq [{"verbose" => true}, %w[command]]
end
it "does not accept if non-option that looks like an option is before the arguments" do
expect(capture(:stderr) do
my_script.start(%w[checked --foo command --bar])
end.strip).to eq("Unknown switches \"--foo\", \"--bar\"")
end
it "does not accept if non-option that looks like an option is after an argument" do
expect(capture(:stderr) do
my_script.start(%w[checked command --foo --bar])
end.strip).to eq("Unknown switches \"--foo\", \"--bar\"")
end
it "does not accept when non-option that looks like an option is after real options" do
expect(capture(:stderr) do
my_script.start(%w[checked --verbose --foo])
end.strip).to eq("Unknown switches \"--foo\"")
end
it "does not accept when non-option that looks like an option is before real options" do
expect(capture(:stderr) do
my_script.start(%w[checked --foo --verbose])
end.strip).to eq("Unknown switches \"--foo\"")
end
it "still accepts options that require a value" do
expect(my_script.start(%w[checked --mode rashly command])).to eq [{"mode" => "rashly"}, %w[command]]
end
it "still passes everything after -- to command" do
expect(my_script.start(%w[checked -- --verbose])).to eq [{}, %w[--verbose]]
end
it "still passes everything after -- to command, complex" do
expect(my_script.start(%w[checked command --mode z again -- --verbose more])).to eq [{"mode" => "z"}, %w[command again --verbose more]]
end
end
describe "#disable_required_check!" do
my_script = Class.new(Thor) do
class_option "foo", :required => true
disable_required_check! :boring
desc "exec", "Run a command"
def exec(*args)
[options, args]
end
desc "boring", "An ordinary command"
def boring(*args)
[options, args]
end
def self.exit_on_failure?
false
end
end
it "does not check the required option in the given command" do
expect(my_script.start(%w(boring command))).to eq [{}, %w(command)]
end
it "does check the required option of the remaining command" do
content = capture(:stderr) { my_script.start(%w(exec command)) }
expect(content).to eq "No value provided for required options '--foo'\n"
end
it "does affects help by default" do
expect(my_script.disable_required_check?(double(:name => "help"))).to be true
end
context "when provided with multiple command names" do
klass = Class.new(Thor) do
disable_required_check! :foo, :bar
end
it "affects all specified commands" do
expect(klass.disable_required_check?(double(:name => "help"))).to be true
expect(klass.disable_required_check?(double(:name => "foo"))).to be true
expect(klass.disable_required_check?(double(:name => "bar"))).to be true
expect(klass.disable_required_check?(double(:name => "baz"))).to be false
end
end
context "when invoked several times" do
klass = Class.new(Thor) do
disable_required_check! :foo
disable_required_check! :bar
end
it "affects all specified commands" do
expect(klass.disable_required_check?(double(:name => "help"))).to be true
expect(klass.disable_required_check?(double(:name => "foo"))).to be true
expect(klass.disable_required_check?(double(:name => "bar"))).to be true
expect(klass.disable_required_check?(double(:name => "baz"))).to be false
end
end
end
describe "#map" do
it "calls the alias of a method if one is provided" do
expect(MyScript.start(%w(-T fish))).to eq(%w(fish))
end
it "calls the alias of a method if several are provided via #map" do
expect(MyScript.start(%w(-f fish))).to eq(["fish", {}])
expect(MyScript.start(%w(--foo fish))).to eq(["fish", {}])
end
it "inherits all mappings from parent" do
expect(MyChildScript.default_command).to eq("example_default_command")
end
end
describe "#package_name" do
it "provides a proper description for a command when the package_name is assigned" do
content = capture(:stdout) { PackageNameScript.start(%w(help)) }
expect(content).to match(/Baboon commands:/m)
end
# TODO: remove this, might be redundant, just wanted to prove full coverage
it "provides a proper description for a command when the package_name is NOT assigned" do
content = capture(:stdout) { MyScript.start(%w(help)) }
expect(content).to match(/Commands:/m)
end
end
describe "#desc" do
it "provides description for a command" do
content = capture(:stdout) { MyScript.start(%w(help)) }
expect(content).to match(/thor my_script:zoo\s+# zoo around/m)
end
it "provides no namespace if $thor_runner is false" do
begin
$thor_runner = false
content = capture(:stdout) { MyScript.start(%w(help)) }
expect(content).to match(/thor zoo\s+# zoo around/m)
ensure
$thor_runner = true
end
end
describe "when :for is supplied" do
it "overwrites a previous defined command" do
expect(capture(:stdout) { MyChildScript.start(%w(help)) }).to match(/animal KIND \s+# fish around/m)
end
end
describe "when :hide is supplied" do
it "does not show the command in help" do
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
expect(MyScript.start(%w(hidden yesyes))).to eq(%w(yesyes))
end
end
end
describe "#method_options" do
it "sets default options if called before an initializer" do
options = MyChildScript.class_options
expect(options[:force].type).to eq(:boolean)
expect(options[:param].type).to eq(:numeric)
end
it "overwrites default options if called on the method scope" do
args = %w(zoo --force --param feathers)
options = MyChildScript.start(args)
expect(options).to eq("force" => true, "param" => "feathers")
end
it "allows default options to be merged with method options" do
args = %w(animal bird --force --param 1.0 --other tweets)
arg, options = MyChildScript.start(args)
expect(arg).to eq("bird")
expect(options).to eq("force" => true, "param" => 1.0, "other" => "tweets")
end
end
describe "#start" do
it "calls a no-param method when no params are passed" do
expect(MyScript.start(%w(zoo))).to eq(true)
end
it "calls a single-param method when a single param is passed" do
expect(MyScript.start(%w(animal fish))).to eq(%w(fish))
end
it "does not set options in attributes" do
expect(MyScript.start(%w(with_optional --all))).to eq([nil, {"all" => true}, []])
end
it "raises an error if the wrong number of params are provided" do
arity_asserter = lambda do |args, msg|
stderr = capture(:stderr) { Scripts::Arities.start(args) }
expect(stderr.strip).to eq(msg)
end
arity_asserter.call %w(zero_args one), 'ERROR: "thor zero_args" was called with arguments ["one"]
Usage: "thor scripts:arities:zero_args"'
arity_asserter.call %w(one_arg), 'ERROR: "thor one_arg" was called with no arguments
Usage: "thor scripts:arities:one_arg ARG"'
arity_asserter.call %w(one_arg one two), 'ERROR: "thor one_arg" was called with arguments ["one", "two"]
Usage: "thor scripts:arities:one_arg ARG"'
arity_asserter.call %w(one_arg one two), 'ERROR: "thor one_arg" was called with arguments ["one", "two"]
Usage: "thor scripts:arities:one_arg ARG"'
arity_asserter.call %w(two_args one), 'ERROR: "thor two_args" was called with arguments ["one"]
Usage: "thor scripts:arities:two_args ARG1 ARG2"'
arity_asserter.call %w(optional_arg one two), 'ERROR: "thor optional_arg" was called with arguments ["one", "two"]
Usage: "thor scripts:arities:optional_arg [ARG]"'
arity_asserter.call %w(multiple_usages), 'ERROR: "thor multiple_usages" was called with no arguments
Usage: "thor scripts:arities:multiple_usages ARG --foo"
"thor scripts:arities:multiple_usages ARG --bar"'
end
it "raises an error if the invoked command does not exist" do
expect(capture(:stderr) { Amazing.start(%w(animal)) }.strip).to eq('Could not find command "animal" in "amazing" namespace.')
end
it "calls method_missing if an unknown method is passed in" do
expect(MyScript.start(%w(unk hello))).to eq([:unk, %w(hello)])
end
it "does not call a private method no matter what" do
expect(capture(:stderr) { MyScript.start(%w(what)) }.strip).to eq('Could not find command "what" in "my_script" namespace.')
end
it "uses command default options" do
options = MyChildScript.start(%w(animal fish)).last
expect(options).to eq("other" => "method default")
end
it "raises when an exception happens within the command call" do
expect { MyScript.start(%w(call_myself_with_wrong_arity)) }.to raise_error(ArgumentError)
end
context "when the user enters an unambiguous substring of a command" do
it "invokes a command" do
expect(MyScript.start(%w(z))).to eq(MyScript.start(%w(zoo)))
end
it "invokes a command, even when there's an alias it resolves to the same command" do
expect(MyScript.start(%w(hi arg))).to eq(MyScript.start(%w(hidden arg)))
end
it "invokes an alias" do
expect(MyScript.start(%w(animal_pri))).to eq(MyScript.start(%w(zoo)))
end
end
context "when the user enters an ambiguous substring of a command" do
it "raises an exception and displays a message that explains the ambiguity" do
shell = Thor::Base.shell.new
expect(shell).to receive(:error).with("Ambiguous command call matches [call_myself_with_wrong_arity, call_unexistent_method]")
MyScript.start(%w(call), :shell => shell)
end
it "raises an exception when there is an alias" do
shell = Thor::Base.shell.new
expect(shell).to receive(:error).with("Ambiguous command f matches [foo, fu]")
MyScript.start(%w(f), :shell => shell)
end
end
end
describe "#help" do
def shell
@shell ||= Thor::Base.shell.new
end
describe "on general" do
before do
@content = capture(:stdout) { MyScript.help(shell) }
end
it "provides useful help info for the help method itself" do
expect(@content).to match(/help \[COMMAND\]\s+# Describe available commands/)
end
it "provides useful help info for a method with params" do
expect(@content).to match(/animal TYPE\s+# horse around/)
end
it "uses the maximum terminal size to show commands" do
expect(@shell).to receive(:terminal_width).and_return(80)
content = capture(:stdout) { MyScript.help(shell) }
expect(content).to match(/aaa\.\.\.$/)
end
it "provides description for commands from classes in the same namespace" do
expect(@content).to match(/baz\s+# do some bazing/)
end
it "shows superclass commands" do
content = capture(:stdout) { MyChildScript.help(shell) }
expect(content).to match(/foo BAR \s+# do some fooing/)
end
it "shows class options information" do
content = capture(:stdout) { MyChildScript.help(shell) }
expect(content).to match(/Options\:/)
expect(content).to match(/\[\-\-param=N\]/)
end
it "injects class arguments into default usage" do
content = capture(:stdout) { Scripts::MyScript.help(shell) }
expect(content).to match(/zoo ACCESSOR \-\-param\=PARAM/)
end
end
describe "for a specific command" do
it "provides full help info when talking about a specific command" do
expect(capture(:stdout) { MyScript.command_help(shell, "foo") }).to eq(<<-END)
Usage:
thor my_script:foo BAR
Options:
[--force] # Force to do some fooing
do some fooing
This is more info!
Everyone likes more info!
END
end
it "provides full help info when talking about a specific command with multiple usages" do
expect(capture(:stdout) { MyScript.command_help(shell, "baz") }).to eq(<<-END)
Usage:
thor my_script:baz THING
thor my_script:baz --all
Options:
[--all=ALL] # Do bazing for all the things
super cool
END
end
it "raises an error if the command can't be found" do
expect do
MyScript.command_help(shell, "unknown")
end.to raise_error(Thor::UndefinedCommandError, 'Could not find command "unknown" in "my_script" namespace.')
end
it "normalizes names before claiming they don't exist" do
expect(capture(:stdout) { MyScript.command_help(shell, "name-with-dashes") }).to match(/thor my_script:name-with-dashes/)
end
it "uses the long description if it exists" do
expect(capture(:stdout) { MyScript.command_help(shell, "long_description") }).to eq(<<-HELP)
Usage:
thor my_script:long_description
Description:
This is a really really really long description. Here you go. So very long.
It even has two paragraphs.
HELP
end
it "doesn't assign the long description to the next command without one" do
expect(capture(:stdout) do
MyScript.command_help(shell, "name_with_dashes")
end).not_to match(/so very long/i)
end
end
describe "instance method" do
it "calls the class method" do
expect(capture(:stdout) { MyScript.start(%w(help)) }).to match(/Commands:/)
end
it "calls the class method" do
expect(capture(:stdout) { MyScript.start(%w(help foo)) }).to match(/Usage:/)
end
end
context "with required class_options" do
let(:klass) do
Class.new(Thor) do
class_option :foo, :required => true
desc "bar", "do something"
def bar; end
end
end
it "shows the command help" do
content = capture(:stdout) { klass.start(%w(help)) }
expect(content).to match(/Commands:/)
end
end
end
describe "subcommands" do
it "triggers a subcommand help when passed --help" do
parent = Class.new(Thor)
child = Class.new(Thor)
parent.desc "child", "child subcommand"
parent.subcommand "child", child
parent.desc "dummy", "dummy"
expect(child).to receive(:help).with(anything, anything)
parent.start ["child", "--help"]
end
end
describe "when creating commands" do
it "prints a warning if a public method is created without description or usage" do
expect(capture(:stdout) do
klass = Class.new(Thor)
klass.class_eval "def hello_from_thor; end"
end).to match(/\[WARNING\] Attempted to create command "hello_from_thor" without usage or description/)
end
it "does not print if overwriting a previous command" do
expect(capture(:stdout) do
klass = Class.new(Thor)
klass.class_eval "def help; end"
end).to be_empty
end
end
describe "edge-cases" do
it "can handle boolean options followed by arguments" do
klass = Class.new(Thor) do
method_option :loud, :type => :boolean
desc "hi NAME", "say hi to name"
def hi(name)
name = name.upcase if options[:loud]
"Hi #{name}"
end
end
expect(klass.start(%w(hi jose))).to eq("Hi jose")
expect(klass.start(%w(hi jose --loud))).to eq("Hi JOSE")
expect(klass.start(%w(hi --loud jose))).to eq("Hi JOSE")
end
it "passes through unknown options" do
klass = Class.new(Thor) do
desc "unknown", "passing unknown options"
def unknown(*args)
args
end
end
expect(klass.start(%w(unknown foo --bar baz bat --bam))).to eq(%w(foo --bar baz bat --bam))
expect(klass.start(%w(unknown --bar baz))).to eq(%w(--bar baz))
end
it "does not pass through unknown options with strict args" do
klass = Class.new(Thor) do
strict_args_position!
desc "unknown", "passing unknown options"
def unknown(*args)
args
end
end
expect(klass.start(%w(unknown --bar baz))).to eq([])
expect(klass.start(%w(unknown foo --bar baz))).to eq(%w(foo))
end
it "strict args works in the inheritance chain" do
parent = Class.new(Thor) do
strict_args_position!
end
klass = Class.new(parent) do
desc "unknown", "passing unknown options"
def unknown(*args)
args
end
end
expect(klass.start(%w(unknown --bar baz))).to eq([])
expect(klass.start(%w(unknown foo --bar baz))).to eq(%w(foo))
end
it "issues a deprecation warning on incompatible types by default" do
expect do
Class.new(Thor) do
option "bar", :type => :numeric, :default => "foo"
end
end.to output(/^Deprecation warning/).to_stderr
end
it "allows incompatible types if allow_incompatible_default_type! is called" do
expect do
Class.new(Thor) do
allow_incompatible_default_type!
option "bar", :type => :numeric, :default => "foo"
end
end.not_to output.to_stderr
end
it "allows incompatible types if `check_default_type: false` is given" do
expect do
Class.new(Thor) do
option "bar", :type => :numeric, :default => "foo", :check_default_type => false
end
end.not_to output.to_stderr
end
it "checks the default type when check_default_type! is called" do
expect do
Class.new(Thor) do
check_default_type!
option "bar", :type => :numeric, :default => "foo"
end
end.to raise_error(ArgumentError, "Expected numeric default value for '--bar'; got \"foo\" (string)")
end
it "send as a command name" do
expect(MyScript.start(%w(send))).to eq(true)
end
end
context "without an exit_on_failure? method" do
my_script = Class.new(Thor) do
desc "no arg", "do nothing"
def no_arg
end
end
it "outputs a deprecation warning on error" do
expect do
my_script.start(%w[no_arg one])
end.to output(/^Deprecation.*exit_on_failure/).to_stderr
end
end
end
spec/util_spec.rb 0000664 0000000 0000000 00000016005 14002057672 0014331 0 ustar 00root root 0000000 0000000 require "helper"
module Thor::Util
def self.clear_user_home!
@@user_home = nil
end
end
describe Thor::Util do
describe "#find_by_namespace" do
it "returns 'default' if no namespace is given" do
expect(Thor::Util.find_by_namespace("")).to eq(Scripts::MyDefaults)
end
it "adds 'default' if namespace starts with :" do
expect(Thor::Util.find_by_namespace(":child")).to eq(Scripts::ChildDefault)
end
it "returns nil if the namespace can't be found" do
expect(Thor::Util.find_by_namespace("thor:core_ext:hash_with_indifferent_access")).to be nil
end
it "returns a class if it matches the namespace" do
expect(Thor::Util.find_by_namespace("app:broken:counter")).to eq(BrokenCounter)
end
it "matches classes default namespace" do
expect(Thor::Util.find_by_namespace("scripts:my_script")).to eq(Scripts::MyScript)
end
end
describe "#namespace_from_thor_class" do
it "replaces constant nesting with command namespacing" do
expect(Thor::Util.namespace_from_thor_class("Foo::Bar::Baz")).to eq("foo:bar:baz")
end
it "snake-cases component strings" do
expect(Thor::Util.namespace_from_thor_class("FooBar::BarBaz::BazBoom")).to eq("foo_bar:bar_baz:baz_boom")
end
it "accepts class and module objects" do
expect(Thor::Util.namespace_from_thor_class(Thor::CoreExt::HashWithIndifferentAccess)).to eq("thor:core_ext:hash_with_indifferent_access")
expect(Thor::Util.namespace_from_thor_class(Thor::Util)).to eq("thor:util")
end
it "removes Thor::Sandbox namespace" do
expect(Thor::Util.namespace_from_thor_class("Thor::Sandbox::Package")).to eq("package")
end
end
describe "#namespaces_in_content" do
it "returns an array of names of constants defined in the string" do
list = Thor::Util.namespaces_in_content("class Foo; class Bar < Thor; end; end; class Baz; class Bat; end; end")
expect(list).to include("foo:bar")
expect(list).not_to include("bar:bat")
end
it "doesn't put the newly-defined constants in the enclosing namespace" do
Thor::Util.namespaces_in_content("class Blat; end")
expect(defined?(Blat)).not_to be
expect(defined?(Thor::Sandbox::Blat)).to be
end
end
describe "#snake_case" do
it "preserves no-cap strings" do
expect(Thor::Util.snake_case("foo")).to eq("foo")
expect(Thor::Util.snake_case("foo_bar")).to eq("foo_bar")
end
it "downcases all-caps strings" do
expect(Thor::Util.snake_case("FOO")).to eq("foo")
expect(Thor::Util.snake_case("FOO_BAR")).to eq("foo_bar")
end
it "downcases initial-cap strings" do
expect(Thor::Util.snake_case("Foo")).to eq("foo")
end
it "replaces camel-casing with underscores" do
expect(Thor::Util.snake_case("FooBarBaz")).to eq("foo_bar_baz")
expect(Thor::Util.snake_case("Foo_BarBaz")).to eq("foo_bar_baz")
end
it "places underscores between multiple capitals" do
expect(Thor::Util.snake_case("ABClass")).to eq("a_b_class")
end
end
describe "#find_class_and_command_by_namespace" do
it "returns a Thor::Group class if full namespace matches" do
expect(Thor::Util.find_class_and_command_by_namespace("my_counter")).to eq([MyCounter, nil])
end
it "returns a Thor class if full namespace matches" do
expect(Thor::Util.find_class_and_command_by_namespace("thor")).to eq([Thor, nil])
end
it "returns a Thor class and the command name" do
expect(Thor::Util.find_class_and_command_by_namespace("thor:help")).to eq([Thor, "help"])
end
it "falls back in the namespace:command look up even if a full namespace does not match" do
Thor.const_set(:Help, Module.new)
expect(Thor::Util.find_class_and_command_by_namespace("thor:help")).to eq([Thor, "help"])
Thor.send :remove_const, :Help
end
it "falls back on the default namespace class if nothing else matches" do
expect(Thor::Util.find_class_and_command_by_namespace("test")).to eq([Scripts::MyDefaults, "test"])
end
end
describe "#thor_classes_in" do
it "returns thor classes inside the given class" do
expect(Thor::Util.thor_classes_in(MyScript)).to eq([MyScript::AnotherScript])
expect(Thor::Util.thor_classes_in(MyScript::AnotherScript)).to be_empty
end
end
describe "#user_home" do
before do
allow(ENV).to receive(:[])
Thor::Util.clear_user_home!
end
it "returns the user path if no variable is set on the environment" do
expect(Thor::Util.user_home).to eq(File.expand_path("~"))
end
it "returns the *nix system path if file cannot be expanded and separator does not exist" do
expect(File).to receive(:expand_path).with("~").and_raise(RuntimeError)
previous_value = File::ALT_SEPARATOR
capture(:stderr) { File.const_set(:ALT_SEPARATOR, false) }
expect(Thor::Util.user_home).to eq("/")
capture(:stderr) { File.const_set(:ALT_SEPARATOR, previous_value) }
end
it "returns the windows system path if file cannot be expanded and a separator exists" do
expect(File).to receive(:expand_path).with("~").and_raise(RuntimeError)
previous_value = File::ALT_SEPARATOR
capture(:stderr) { File.const_set(:ALT_SEPARATOR, true) }
expect(Thor::Util.user_home).to eq("C:/")
capture(:stderr) { File.const_set(:ALT_SEPARATOR, previous_value) }
end
it "returns HOME/.thor if set" do
allow(ENV).to receive(:[]).with("HOME").and_return("/home/user/")
expect(Thor::Util.user_home).to eq("/home/user/")
end
it "returns path with HOMEDRIVE and HOMEPATH if set" do
allow(ENV).to receive(:[]).with("HOMEDRIVE").and_return("D:/")
allow(ENV).to receive(:[]).with("HOMEPATH").and_return("Documents and Settings/James")
expect(Thor::Util.user_home).to eq("D:/Documents and Settings/James")
end
it "returns APPDATA/.thor if set" do
allow(ENV).to receive(:[]).with("APPDATA").and_return("/home/user/")
expect(Thor::Util.user_home).to eq("/home/user/")
end
end
describe "#thor_root_glob" do
before do
allow(ENV).to receive(:[])
Thor::Util.clear_user_home!
end
it "escapes globs in path" do
allow(ENV).to receive(:[]).with("HOME").and_return("/home/user{1}/")
expect(Dir).to receive(:[]).with('/home/user\\{1\\}/.thor/*').and_return([])
expect(Thor::Util.thor_root_glob).to eq([])
end
end
describe "#globs_for" do
it "escapes globs in path" do
expect(Thor::Util.globs_for("/home/apps{1}")).to eq([
'/home/apps\\{1\\}/Thorfile',
'/home/apps\\{1\\}/*.thor',
'/home/apps\\{1\\}/tasks/*.thor',
'/home/apps\\{1\\}/lib/tasks/*.thor'
])
end
end
describe "#escape_globs" do
it "escapes ? * { } [ ] glob characters" do
expect(Thor::Util.escape_globs("apps?")).to eq('apps\\?')
expect(Thor::Util.escape_globs("apps*")).to eq('apps\\*')
expect(Thor::Util.escape_globs("apps {1}")).to eq('apps \\{1\\}')
expect(Thor::Util.escape_globs("apps [1]")).to eq('apps \\[1\\]')
end
end
end