Blob Blame History Raw
pax_global_header00006660000000000000000000000064140020576720014515gustar00rootroot0000000000000052 comment=b60e9eba629f2b0be4da9f2ab6208798f3945692
spec/000077500000000000000000000000001400205767200120135ustar00rootroot00000000000000spec/actions/000077500000000000000000000000001400205767200134535ustar00rootroot00000000000000spec/actions/create_file_spec.rb000066400000000000000000000142071400205767200172600ustar00rootroot00000000000000require "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.rb000066400000000000000000000063131400205767200172750ustar00rootroot00000000000000require "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.rb000066400000000000000000000140121400205767200170140ustar00rootroot00000000000000require "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.rb000066400000000000000000000071311400205767200202360ustar00rootroot00000000000000require "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.rb000066400000000000000000000446641400205767200205270ustar00rootroot00000000000000require "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.rb000066400000000000000000000143371400205767200203260ustar00rootroot00000000000000# 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.rb000066400000000000000000000326761400205767200150300ustar00rootroot00000000000000require "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.rb000066400000000000000000000227311400205767200142710ustar00rootroot00000000000000require "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.rb000066400000000000000000000057261400205767200150020ustar00rootroot00000000000000require "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/000077500000000000000000000000001400205767200136235ustar00rootroot00000000000000spec/core_ext/hash_with_indifferent_access_spec.rb000066400000000000000000000052671400205767200230500ustar00rootroot00000000000000require "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.rb000066400000000000000000000006621400205767200163750ustar00rootroot00000000000000require "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/000077500000000000000000000000001400205767200136645ustar00rootroot00000000000000spec/fixtures/application.rb000066400000000000000000000000351400205767200165120ustar00rootroot00000000000000class Application < Base
end
spec/fixtures/application_helper.rb000066400000000000000000000000351400205767200200510ustar00rootroot00000000000000module ApplicationHelper
end
spec/fixtures/app{1}/000077500000000000000000000000001400205767200151155ustar00rootroot00000000000000spec/fixtures/app{1}/README000066400000000000000000000000311400205767200157670ustar00rootroot00000000000000__start__
README
__end__
spec/fixtures/command.thor000066400000000000000000000004461400205767200162040ustar00rootroot00000000000000# 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/000077500000000000000000000000001400205767200144315ustar00rootroot00000000000000spec/fixtures/doc/%file_name%.rb.tt000066400000000000000000000000231400205767200174300ustar00rootroot00000000000000FOO = <%= "FOO" %>
spec/fixtures/doc/COMMENTER000066400000000000000000000001541400205767200157050ustar00rootroot00000000000000__start__
 # greenblue
#
# yellowblue
#yellowred
 #greenred
orange
    purple
  ind#igo
  # ind#igo
__end__
spec/fixtures/doc/README000066400000000000000000000000311400205767200153030ustar00rootroot00000000000000__start__
README
__end__
spec/fixtures/doc/README.zh000066400000000000000000000000311400205767200157230ustar00rootroot00000000000000__start__
说明
__end__
spec/fixtures/doc/block_helper.rb000066400000000000000000000000411400205767200174020ustar00rootroot00000000000000<% world do -%>
Hello
<% end -%>
spec/fixtures/doc/components/000077500000000000000000000000001400205767200166165ustar00rootroot00000000000000spec/fixtures/doc/components/.empty_directory000066400000000000000000000000001400205767200220270ustar00rootroot00000000000000spec/fixtures/doc/config.rb000066400000000000000000000000311400205767200162150ustar00rootroot00000000000000class <%= @klass %>; end
spec/fixtures/doc/config.yaml.tt000066400000000000000000000000211400205767200172010ustar00rootroot00000000000000--- Hi from yaml
spec/fixtures/doc/excluding/000077500000000000000000000000001400205767200164135ustar00rootroot00000000000000spec/fixtures/doc/excluding/%file_name%.rb.tt000066400000000000000000000000231400205767200214120ustar00rootroot00000000000000BAR = <%= "BAR" %>
spec/fixtures/enum.thor000066400000000000000000000003101400205767200155200ustar00rootroot00000000000000class 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.thor000066400000000000000000000004141400205767200171350ustar00rootroot00000000000000require "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.thor000066400000000000000000000045361400205767200157260ustar00rootroot00000000000000class 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.thor000066400000000000000000000002761400205767200155170ustar00rootroot00000000000000Bundler.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.thor000066400000000000000000000036541400205767200160650ustar00rootroot00000000000000class 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 spaces000066400000000000000000000000001400205767200167240ustar00rootroot00000000000000spec/fixtures/preserve/000077500000000000000000000000001400205767200155175ustar00rootroot00000000000000spec/fixtures/preserve/%filename%.sh000077500000000000000000000000221400205767200177420ustar00rootroot00000000000000#!/bin/sh

exit 0
spec/fixtures/preserve/script.sh000077500000000000000000000000221400205767200173540ustar00rootroot00000000000000#!/bin/sh

exit 0
spec/fixtures/script.thor000066400000000000000000000120271400205767200160700ustar00rootroot00000000000000class 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.thor000066400000000000000000000004121400205767200167070ustar00rootroot00000000000000module 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/000077500000000000000000000000001400205767200154775ustar00rootroot00000000000000spec/fixtures/template/bad_config.yaml.tt000066400000000000000000000000541400205767200210630ustar00rootroot00000000000000--- Hi from yaml
<%= unresolved_variable %>
spec/fixtures/verbose.thor000066400000000000000000000002051400205767200162240ustar00rootroot00000000000000#!/usr/bin/ruby

$VERBOSE = true

require 'thor'

class Test < Thor
  def self.exit_on_failure?
    true
  end
end

Test.start(ARGV)
spec/group_spec.rb000066400000000000000000000157451400205767200145220ustar00rootroot00000000000000require "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.rb000066400000000000000000000037671400205767200136340ustar00rootroot00000000000000$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.rb000066400000000000000000000073411400205767200155300ustar00rootroot00000000000000require "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/000077500000000000000000000000001400205767200143105ustar00rootroot00000000000000spec/line_editor/basic_spec.rb000066400000000000000000000017121400205767200167310ustar00rootroot00000000000000require "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.rb000066400000000000000000000052761400205767200174440ustar00rootroot00000000000000require "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.rb000066400000000000000000000025361400205767200156550ustar00rootroot00000000000000require "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.rb000066400000000000000000000006471400205767200164070ustar00rootroot00000000000000require "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.rb000066400000000000000000000007441400205767200157030ustar00rootroot00000000000000require "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/000077500000000000000000000000001400205767200133075ustar00rootroot00000000000000spec/parser/argument_spec.rb000066400000000000000000000030671400205767200164760ustar00rootroot00000000000000require "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.rb000066400000000000000000000053001400205767200166510ustar00rootroot00000000000000require "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.rb000066400000000000000000000206711400205767200161640ustar00rootroot00000000000000require "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.rb000066400000000000000000000413551400205767200163510ustar00rootroot00000000000000require "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.rb000066400000000000000000000037571400205767200150560ustar00rootroot00000000000000describe "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.rb000066400000000000000000000031221400205767200156350ustar00rootroot00000000000000require "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.rb000066400000000000000000000133061400205767200152010ustar00rootroot00000000000000require "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.rb000066400000000000000000000213431400205767200146660ustar00rootroot00000000000000require "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.rb000066400000000000000000000015661400205767200173220ustar00rootroot00000000000000describe "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/000077500000000000000000000000001400205767200131225ustar00rootroot00000000000000spec/shell/basic_spec.rb000066400000000000000000000444411400205767200155510ustar00rootroot00000000000000# 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.rb000066400000000000000000000132711400205767200156030ustar00rootroot00000000000000require "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.rb000066400000000000000000000027611400205767200154330ustar00rootroot00000000000000require "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;\">&lt;htmlcontent&gt;</span>"
    end

    it "escapes HTML content when not using the default colors" do
      expect(shell.set_color("<htmlcontent>", [:nocolor])).to eq "<span style=\";\">&lt;htmlcontent&gt;</span>"
    end
  end
end
spec/shell_spec.rb000066400000000000000000000023411400205767200144610ustar00rootroot00000000000000require "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.rb000066400000000000000000000051751400205767200155120ustar00rootroot00000000000000require "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.rb000066400000000000000000000651601400205767200143360ustar00rootroot00000000000000require "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.rb000066400000000000000000000160051400205767200143310ustar00rootroot00000000000000require "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