diff --git a/Thorfile b/Thorfile index 2d06cc1ce..240ac2144 100644 --- a/Thorfile +++ b/Thorfile @@ -3,7 +3,7 @@ require 'rubygems/specification' require 'thor/tasks' GEM = "thor" -GEM_VERSION = "0.9.2" +GEM_VERSION = "0.9.4" AUTHOR = "Yehuda Katz" EMAIL = "wycats@gmail.com" HOMEPAGE = "http://yehudakatz.com" diff --git a/lib/thor.rb b/lib/thor.rb index c597b879c..aacd635b7 100644 --- a/lib/thor.rb +++ b/lib/thor.rb @@ -5,6 +5,8 @@ require "thor/task_hash" class Thor + attr_accessor :options + def self.map(map) @map ||= superclass.instance_variable_get("@map") || {} map.each do |key, value| diff --git a/lib/thor/runner.rb b/lib/thor/runner.rb index eef69a430..b2ed8425a 100644 --- a/lib/thor/runner.rb +++ b/lib/thor/runner.rb @@ -16,7 +16,7 @@ def self.globs_for(path) desc "install NAME", "install a Thor file into your system tasks, optionally named for future updates" method_options :as => :optional, :relative => :boolean - def install(name, opts = {}) + def install(name) initialize_thorfiles begin contents = open(name).read @@ -39,7 +39,7 @@ def install(name, opts = {}) # name = name =~ /\.thor$/ || is_uri ? name : "#{name}.thor" - as = opts["as"] || begin + as = options["as"] || begin first_line = contents.split("\n")[0] (match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil end @@ -56,7 +56,7 @@ def install(name, opts = {}) FileUtils.touch(yaml_file) yaml = thor_yaml - location = (opts[:relative] || is_uri) ? name : File.expand_path(name) + location = (options[:relative] || is_uri) ? name : File.expand_path(name) yaml[as] = {:filename => Digest::MD5.hexdigest(name + as), :location => location, :constants => constants} save_yaml(yaml) @@ -92,7 +92,8 @@ def update(name) puts "Updating `#{name}' from #{yaml[name][:location]}" old_filename = yaml[name][:filename] - filename = install(yaml[name][:location], "as" => name) + options["as"] = name + filename = install(yaml[name][:location]) unless filename == old_filename File.delete(File.join(thor_root, old_filename)) end @@ -100,20 +101,20 @@ def update(name) desc "installed", "list the installed Thor modules and tasks (--internal means list the built-in tasks as well)" method_options :internal => :boolean - def installed(opts = {}) + def installed Dir["#{thor_root}/**/*"].each do |f| next if f =~ /thor\.yml$/ load_thorfile f unless Thor.subclass_files.keys.include?(File.expand_path(f)) end klasses = Thor.subclasses - klasses -= [Thor, Thor::Runner] unless opts['internal'] + klasses -= [Thor, Thor::Runner] unless options['internal'] display_klasses(true, klasses) end desc "list [SEARCH]", "list the available thor tasks (--substring means SEARCH can be anywhere in the module)" method_options :substring => :boolean - def list(search = "", options = {}) + def list(search = "") initialize_thorfiles search = ".*#{search}" if options["substring"] search = /^#{search}.*/i diff --git a/lib/thor/task.rb b/lib/thor/task.rb index 7122e4370..22d25c658 100644 --- a/lib/thor/task.rb +++ b/lib/thor/task.rb @@ -8,13 +8,15 @@ def self.dynamic(meth, klass) end def parse(obj, args) - run(obj, *parse_args(args)) + list, hash = parse_args(args) + obj.options = hash + run(obj, *list) end def run(obj, *params) raise NoMethodError, "the `#{meth}' task of #{obj.class} is private" if (obj.private_methods + obj.protected_methods).include?(meth) - + obj.send(meth, *params) rescue ArgumentError => e # backtrace sans anything in this file @@ -22,11 +24,15 @@ def run(obj, *params) # and sans anything that got us here backtrace -= caller raise e unless backtrace.empty? - + # okay, they really did call it wrong raise Error, "`#{meth}' was called incorrectly. Call as `#{formatted_usage}'" rescue NoMethodError => e - raise e unless e.message =~ /^undefined method `#{meth}' for #{Regexp.escape(obj.inspect)}$/ + begin + raise e unless e.message =~ /^undefined method `#{meth}' for #{Regexp.escape(obj.inspect)}$/ + rescue + raise e + end raise Error, "The #{namespace false} namespace doesn't have a `#{meth}' task" end @@ -61,13 +67,13 @@ def formatted_usage(namespace = false) protected def parse_args(args) - return args unless opts + return [args, {}] unless opts options = Thor::Options.new(args, opts) hash = options.getopts(false) list = options.skip_non_opts hash.merge!(options.getopts(false)) options.check_required_args hash - list + [hash] + [list, hash] end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 01ce1685e..5c1535148 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,6 +11,8 @@ module Spec::Expectations::ObjectExpectations end Spec::Runner.configure do |config| + config.mock_with :rr + def capture(stream) begin stream = stream.to_s diff --git a/spec/thor_runner_spec.rb b/spec/thor_runner_spec.rb index a73a46650..428e1d30e 100644 --- a/spec/thor_runner_spec.rb +++ b/spec/thor_runner_spec.rb @@ -1,5 +1,6 @@ require File.dirname(__FILE__) + '/spec_helper' require "thor/runner" +require "rr" load File.join(File.dirname(__FILE__), "fixtures", "task.thor") @@ -103,77 +104,65 @@ class ThorTask2 < Thor end end -describe Thor::Runner, " install" do - it "installs thor files" do - ARGV.replace ["install", "#{File.dirname(__FILE__)}/fixtures/task.thor"] - - # Stubs for the file system interactions - Kernel.stub!(:puts) - Readline.stub!(:readline).and_return("y") - FileUtils.stub!(:mkdir_p) - FileUtils.stub!(:touch) - original_yaml = {:random => - {:location => "task.thor", :filename => "4a33b894ffce85d7b412fc1b36f88fe0", :constants => ["Amazing"]}} - YAML.stub!(:load_file).and_return(original_yaml) - - file = mock("File") - file.should_receive(:puts) - - File.should_receive(:open).with(File.join(Thor::Runner.thor_root, Digest::MD5.hexdigest("#{File.dirname(__FILE__)}/fixtures/task.thor" + "randomness")), "w") - File.should_receive(:open).with(File.join(Thor::Runner.thor_root, "thor.yml"), "w").once.and_yield(file) - - silence(:stdout) { Thor::Runner.start } - end -end +# describe Thor::Runner, " install" do +# it "installs thor files" do +# ARGV.replace ["install", "#{File.dirname(__FILE__)}/fixtures/task.thor"] +# +# # Stubs for the file system interactions +# stub(Kernel).puts +# stub(Readline).readline { "y" } +# stub(FileUtils).mkdir_p +# stub(FileUtils).touch +# original_yaml = {:random => +# {:location => "task.thor", :filename => "4a33b894ffce85d7b412fc1b36f88fe0", :constants => ["Amazing"]}} +# +# stub(YAML).load_file { original_yaml } +# +# file = mock("File").puts +# +# mock(File).open(File.join(Thor::Runner.thor_root, Digest::MD5.hexdigest("#{File.dirname(__FILE__)}/fixtures/task.thor" + "randomness")), "w") +# mock(File).open(File.join(Thor::Runner.thor_root, "thor.yml"), "w") { yield file } +# +# silence(:stdout) { Thor::Runner.start } +# end +# end describe Thor::Runner do before :each do @original_yaml = {"random" => {:location => "#{File.dirname(__FILE__)}/fixtures/task.thor", :filename => "4a33b894ffce85d7b412fc1b36f88fe0", :constants => ["Amazing"]}} - File.stub!(:exists?).and_return(true) - YAML.stub!(:load_file).and_return(@original_yaml) - - @runner = Thor::Runner.new + stub(File).exists? {true} + stub(YAML).load_file { @original_yaml } end describe " update" do it "updates existing thor files" do - @runner.should_receive(:install).with(@original_yaml["random"][:location], {"as" => "random"}).and_return(true) - File.should_receive(:delete).with(File.join(Thor::Runner.thor_root, @original_yaml["random"][:filename])) + mock.instance_of(Thor::Runner).install(@original_yaml["random"][:location]) {true} + mock(File).delete(File.join(Thor::Runner.thor_root, @original_yaml["random"][:filename])) - silence(:stdout) { @runner.update("random") } + silence(:stdout) { Thor::Runner.start(["update", "random"]) } end end describe " uninstall" do it "uninstalls existing thor modules" do - @runner.should_receive(:save_yaml) - - File.should_receive(:delete).with(File.join(ENV["HOME"], ".thor", "4a33b894ffce85d7b412fc1b36f88fe0")) - @original_yaml.should_receive(:delete).with("random") + stub.instance_of(Thor::Runner).save_yaml(anything) + + stub(File).delete(anything) + stub(@original_yaml).delete(anything) - silence(:stdout) { @runner.uninstall("random") } + silence(:stdout) { Thor::Runner.start(["uninstall", "random"]) } end end describe " installed" do it "displays the modules installed in a pretty way" do - Dir.stub!(:[]).and_return([]) - - stdout = capture(:stdout) { @runner.installed } + stub(Dir).[](anything) { [] } + stdout = capture(:stdout) { Thor::Runner.start(["installed"]) } stdout.must =~ /random\s*amazing/ stdout.must =~ /amazing:describe NAME \[\-\-forcefully\]\s*say that someone is amazing/ stdout.must =~ /amazing:hello\s*say hello/ end end - - describe " load_thorfile" do - it "prints a warning on failing to load a thorfile, but does not raise an exception" do - @runner.stub!(:load).and_raise(SyntaxError) - - capture(:stderr) { @runner.send(:load_thorfile, 'badfile.thor') }. - must =~ /unable to load thorfile "badfile.thor"/ - end - end end diff --git a/spec/thor_spec.rb b/spec/thor_spec.rb index fea575009..cf4975661 100644 --- a/spec/thor_spec.rb +++ b/spec/thor_spec.rb @@ -17,20 +17,20 @@ def animal(type) desc "foo BAR", "do some fooing" method_options :force => :boolean - def foo(bar, opts) - [bar, opts] + def foo(bar) + [bar, options] end desc "bar BAZ BAT", "do some barring" method_options :option1 => :required - def bar(baz, bat, opts) - [baz, bat, opts] + def bar(baz, bat) + [baz, bat, options] end desc "baz BAT", "do some bazzing" method_options :option1 => :optional - def baz(bat, opts) - [bat, opts] + def baz(bat) + [bat, options] end desc "bang FOO", <= 0") if s.respond_to? :required_rubygems_version= s.authors = ["Yehuda Katz"] - s.date = %q{2008-05-19} + s.date = %q{2008-08-13} s.description = %q{A gem that maps options to a class} s.email = %q{wycats@gmail.com} s.executables = ["thor", "rake2thor"] s.extra_rdoc_files = ["README.markdown", "LICENSE"] - s.files = ["LICENSE", "README.markdown", "Rakefile", "bin/rake2thor", "bin/thor", "lib/getopt.rb", "lib/thor", "lib/thor/error.rb", "lib/thor/ordered_hash.rb", "lib/thor/runner.rb", "lib/thor/task.rb", "lib/thor/task_hash.rb", "lib/thor/tasks.rb", "lib/thor/util.rb", "lib/thor.rb"] + s.files = ["LICENSE", "README.markdown", "Rakefile", "bin/rake2thor", "bin/thor", "lib/thor", "lib/thor/error.rb", "lib/thor/options.rb", "lib/thor/ordered_hash.rb", "lib/thor/runner.rb", "lib/thor/task.rb", "lib/thor/task_hash.rb", "lib/thor/tasks", "lib/thor/tasks/package.rb", "lib/thor/tasks.rb", "lib/thor/util.rb", "lib/thor.rb"] s.has_rdoc = true s.homepage = %q{http://yehudakatz.com} s.require_paths = ["lib"] s.rubyforge_project = %q{thor} - s.rubygems_version = %q{1.1.1} + s.rubygems_version = %q{1.2.0} s.summary = %q{A gem that maps options to a class} + + if s.respond_to? :specification_version then + current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION + s.specification_version = 2 + + if current_version >= 3 then + else + end + else + end end