提交 fd719d7e 编写于 作者: J José Valim

Update vendored Thor.

上级 f50bb48e
......@@ -10,7 +10,7 @@
require 'active_support/core_ext/string/inflections'
# TODO: Do not always push on vendored thor
$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/vendor/thor-0.12.1/lib")
$LOAD_PATH.unshift("#{File.dirname(__FILE__)}/vendor/thor-0.12.3/lib")
require 'rails/generators/base'
require 'rails/generators/named_base'
......
== TODO
* Improve spec coverage for Thor::Runner
== 0.12, released 2009-11-06
== 0.12, released 2010-01-02
* Removed rr in favor to rspec mock framework
* Improved output for thor -T
* [#7] Do not force white color on status
* [#8] Yield a block with the filename on directory
......@@ -17,7 +15,7 @@
* thor help now show information about any class/task. All those calls are
possible:
thor help describe
thor help describe:amazing
......@@ -47,7 +45,7 @@
are in the 'standard' group. Running 'thor -T' will only show the standard
tasks - adding --all will show all tasks. You can also filter on a specific
group using the --group option: thor -T --group advanced
== 0.9.6, released 2008-09-13
* Generic improvements
......
......@@ -78,14 +78,14 @@ def method_options(options=nil)
@method_options
end
# Adds an option to the set of class options. If :for is given as option,
# Adds an option to the set of method options. If :for is given as option,
# it allows you to change the options from a previous defined task.
#
# def previous_task
# # magic
# end
#
# method_options :foo => :bar, :for => :previous_task
# method_option :foo => :bar, :for => :previous_task
#
# def next_task
# # magic
......@@ -101,7 +101,6 @@ def method_options(options=nil)
# :default - Default value for this argument. It cannot be required and have default values.
# :aliases - Aliases for this option.
# :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
# :group - The group for this options. Use by class options to output options in different levels.
# :banner - String to show on usage notes.
#
def method_option(name, options={})
......@@ -140,49 +139,48 @@ def start(given_args=ARGV, config={})
end
end
# Prints help information. If a task name is given, it shows information
# only about the specific task.
# Prints help information for the given task.
#
# ==== Parameters
# meth<String>:: An optional task name to print usage information about.
# shell<Thor::Shell>
# task_name<String>
#
def task_help(shell, task_name)
task = all_tasks[task_name]
raise UndefinedTaskError, "task '#{task_name}' could not be found in namespace '#{self.namespace}'" unless task
shell.say "Usage:"
shell.say " #{banner(task)}"
shell.say
class_options_help(shell, nil => task.options.map { |_, o| o })
shell.say task.description
end
# Prints help information for this class.
#
# ==== Options
# namespace:: When true, shows the namespace in the output before the usage.
# skip_inherited:: When true, does not show tasks from superclass.
# ==== Parameters
# shell<Thor::Shell>
#
def help(shell, meth=nil, options={})
meth, options = nil, meth if meth.is_a?(Hash)
if meth
task = all_tasks[meth]
raise UndefinedTaskError, "task '#{meth}' could not be found in namespace '#{self.namespace}'" unless task
shell.say "Usage:"
shell.say " #{banner(task, options[:namespace], false)}"
shell.say
class_options_help(shell, "Class", :Method => task.options.map { |_, o| o })
shell.say task.description
else
list = (options[:short] ? tasks : all_tasks).map do |_, task|
item = [ banner(task, options[:namespace]) ]
item << "# #{task.short_description}" if task.short_description
item << " "
end
options[:ident] ||= 2
if options[:short]
shell.print_list(list, :ident => options[:ident])
else
shell.say "Tasks:"
shell.print_list(list, :ident => options[:ident])
end
def help(shell)
list = printable_tasks
Thor::Util.thor_classes_in(self).each do |klass|
list += klass.printable_tasks(false)
end
list.sort!{ |a,b| a[0] <=> b[0] }
Thor::Util.thor_classes_in(self).each do |subclass|
namespace = options[:namespace] == true || subclass.namespace.gsub(/^#{self.namespace}:/, '')
subclass.help(shell, options.merge(:short => true, :namespace => namespace))
end
shell.say "Tasks:"
shell.print_table(list, :ident => 2, :truncate => true)
shell.say
class_options_help(shell)
end
class_options_help(shell, "Class") unless options[:short]
# Returns tasks ready to be printed.
def printable_tasks(all=true)
(all ? all_tasks : tasks).map do |_, task|
item = []
item << banner(task)
item << (task.description ? "# #{task.description.gsub(/\s+/m,' ')}" : "")
item
end
end
......@@ -193,8 +191,8 @@ def help(shell, meth=nil, options={})
# the task that is going to be invoked and a boolean which indicates if
# the namespace should be displayed as arguments.
#
def banner(task, namespace=true, show_options=true)
task.formatted_usage(self, namespace, show_options)
def banner(task)
"thor " + task.formatted_usage(self)
end
def baseclass #:nodoc:
......@@ -237,6 +235,6 @@ def normalize_task_name(meth) #:nodoc:
desc "help [TASK]", "Describe available tasks or one specific task"
def help(task=nil)
self.class.help(shell, task, :namespace => task && task.include?(?:))
task ? self.class.task_help(shell, task) : self.class.help(shell)
end
end
require 'fileutils'
require 'thor/core_ext/file_binary_read'
Dir[File.join(File.dirname(__FILE__), "actions", "*.rb")].each do |action|
require action
......@@ -38,17 +39,17 @@ def source_paths_for_search
# Add runtime options that help actions execution.
#
def add_runtime_options!
class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime,
:desc => "Run but do not make any changes"
class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime,
:desc => "Overwrite files that already exist"
class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime,
:desc => "Skip files that already exist"
class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime,
:desc => "Run but do not make any changes"
class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime,
:desc => "Supress status output"
class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime,
:desc => "Skip files that already exist"
end
end
......@@ -114,7 +115,7 @@ def source_paths
@source_paths ||= self.class.source_paths_for_search
end
# Receives a file or directory and search for it in the source paths.
# Receives a file or directory and search for it in the source paths.
#
def find_in_source_paths(file)
relative_root = relative_to_original_destination_root(destination_root, false)
......@@ -222,7 +223,7 @@ def run_ruby_script(command, config={})
run "#{command}", config.merge(:with => Thor::Util.ruby_command)
end
# Run a thor command. A hash of options can be given and it's converted to
# Run a thor command. A hash of options can be given and it's converted to
# switches.
#
# ==== Parameters
......
......@@ -42,7 +42,7 @@ def initialize(base, destination, data, config={})
# Boolean:: true if it is identical, false otherwise.
#
def identical?
exists? && File.read(destination) == render
exists? && File.binread(destination) == render
end
# Holds the content to be added to the file.
......@@ -58,7 +58,7 @@ def render
def invoke!
invoke_with_conflict_check do
FileUtils.mkdir_p(File.dirname(destination))
File.open(destination, 'w'){ |f| f.write render }
File.open(destination, 'wb') { |f| f.write render }
end
given_destination
end
......
......@@ -23,7 +23,7 @@ def copy_file(source, destination=nil, config={}, &block)
source = File.expand_path(find_in_source_paths(source.to_s))
create_file destination, nil, config do
content = File.read(source)
content = File.binread(source)
content = block.call(content) if block
content
end
......@@ -48,7 +48,7 @@ def copy_file(source, destination=nil, config={}, &block)
#
def get(source, destination=nil, config={}, &block)
source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^http\:\/\//
render = open(source).read
render = File.binread(source)
destination ||= if block_given?
block.arity == 1 ? block.call(render) : block.call
......@@ -80,7 +80,7 @@ def template(source, destination=nil, config={}, &block)
context = instance_eval('binding')
create_file destination, nil, config do
content = ERB.new(::File.read(source), nil, '-').result(context)
content = ERB.new(::File.binread(source), nil, '-').result(context)
content = block.call(content) if block
content
end
......@@ -193,7 +193,7 @@ def gsub_file(path, flag, *args, &block)
say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
unless options[:pretend]
content = File.read(path)
content = File.binread(path)
content.gsub!(flag, *args, &block)
File.open(path, 'wb') { |file| file.write(content) }
end
......
......@@ -11,7 +11,7 @@ module Actions
# data<String>:: Data to add to the file. Can be given as a block.
# config<Hash>:: give :verbose => false to not log the status and the flag
# for injection (:after or :before).
#
#
# ==== Examples
#
# inject_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
......@@ -90,7 +90,7 @@ def say_status(behavior)
#
def replace!(regexp, string)
unless base.options[:pretend]
content = File.read(destination)
content = File.binread(destination)
content.gsub!(regexp, string)
File.open(destination, 'wb') { |file| file.write(content) }
end
......
......@@ -92,6 +92,8 @@ def register_klass_file(klass) #:nodoc:
end
module ClassMethods
attr_accessor :debugging
# Adds an argument to the class and creates an attr_accessor for it.
#
# Arguments are different from options in several aspects. The first one
......@@ -347,10 +349,11 @@ def namespace(name=nil)
# Default way to start generators from the command line.
#
def start(given_args=ARGV, config={})
self.debugging = given_args.include?("--debug")
config[:shell] ||= Thor::Base.shell.new
yield
rescue Thor::Error => e
if given_args.include?("--debug")
if debugging
raise e
else
config[:shell].error e.message
......@@ -361,48 +364,43 @@ def start(given_args=ARGV, config={})
protected
# Prints the class options per group. If an option does not belong to
# any group, it uses the ungrouped name value. This method provide to
# hooks to add extra options, one of them if the third argument called
# extra_group that should be a hash in the format :group => Array[Options].
#
# The second is by returning a lambda used to print values. The lambda
# requires two options: the group name and the array of options.
# any group, it's printed as Class option.
#
def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc:
groups = {}
def class_options_help(shell, groups={}) #:nodoc:
# Group options by group
class_options.each do |_, value|
groups[value.group] ||= []
groups[value.group] << value
end
printer = proc do |group_name, options|
list = []
padding = options.collect{ |o| o.aliases.size }.max.to_i * 4
# Deal with default group
global_options = groups.delete(nil) || []
print_options(shell, global_options)
# Print all others
groups.each do |group_name, options|
print_options(shell, options, group_name)
end
end
# Receives a set of options and print them.
def print_options(shell, options, group_name=nil)
return if options.empty?
options.each do |option|
item = [ option.usage(padding) ]
item.push(option.description ? "# #{option.description}" : "")
list = []
padding = options.collect{ |o| o.aliases.size }.max.to_i * 4
list << item
list << [ "", "# Default: #{option.default}" ] if option.show_default?
end
options.each do |option|
item = [ option.usage(padding) ]
item.push(option.description ? "# #{option.description}" : "")
unless list.empty?
shell.say(group_name ? "#{group_name} options:" : "Options:")
shell.print_table(list, :ident => 2)
shell.say ""
end
list << item
list << [ "", "# Default: #{option.default}" ] if option.show_default?
end
# Deal with default group
global_options = groups.delete(nil) || []
printer.call(ungrouped_name, global_options) if global_options
# Print all others
groups = extra_group.merge(groups) if extra_group
groups.each(&printer)
printer
shell.say(group_name ? "#{group_name} options:" : "Options:")
shell.print_table(list, :ident => 2)
shell.say ""
end
# Raises an error if the word given is a Thor reserved word.
......
class File #:nodoc:
unless File.respond_to?(:binread)
def self.binread(file)
File.open(file, 'rb') { |f| f.read }
end
end
end
......@@ -41,16 +41,12 @@ def start(given_args=ARGV, config={})
# ==== Options
# short:: When true, shows only usage.
#
def help(shell, options={})
if options[:short]
shell.say banner
else
shell.say "Usage:"
shell.say " #{banner}"
shell.say
class_options_help(shell)
shell.say self.desc if self.desc
end
def help(shell)
shell.say "Usage:"
shell.say " #{banner}\n"
shell.say
class_options_help(shell)
shell.say self.desc if self.desc
end
# Stores invocations for this class merging with superclass values.
......@@ -132,7 +128,7 @@ def invoke_from_option(*names, &block)
names.each do |name|
unless class_options.key?(name)
raise ArgumentError, "You have to define the option #{name.inspect} " <<
raise ArgumentError, "You have to define the option #{name.inspect} " <<
"before setting invoke_from_option."
end
......@@ -177,15 +173,11 @@ def remove_invocation(*names)
# Overwrite class options help to allow invoked generators options to be
# shown recursively when invoking a generator.
#
def class_options_help(shell, ungrouped_name=nil, extra_group=nil) #:nodoc:
group_options = {}
get_options_from_invocations(group_options, class_options) do |klass|
klass.send(:get_options_from_invocations, group_options, class_options)
def class_options_help(shell, groups={}) #:nodoc:
get_options_from_invocations(groups, class_options) do |klass|
klass.send(:get_options_from_invocations, groups, class_options)
end
group_options.merge!(extra_group) if extra_group
super(shell, ungrouped_name, group_options)
super(shell, groups)
end
# Get invocations array and merge options from invocations. Those
......@@ -218,13 +210,26 @@ def get_options_from_invocations(group_options, base_options) #:nodoc:
end
end
# Returns tasks ready to be printed.
def printable_tasks(*)
item = []
item << banner
item << (desc ? "# #{desc.gsub(/\s+/m,' ')}" : "")
[item]
end
protected
# The banner for this class. You can customize it if you are invoking the
# thor class by another ways which is not the Thor::Runner.
#
def banner
"#{self.namespace} #{self.arguments.map {|a| a.usage }.join(' ')}"
"thor #{self_task.formatted_usage(self, false)}"
end
# Represents the whole class as a task.
def self_task #:nodoc:
Thor::Task::Dynamic.new(self.namespace, class_options)
end
def baseclass #:nodoc:
......@@ -243,7 +248,6 @@ def create_task(meth) #:nodoc:
# Shortcut to invoke with padding and block handling. Use internally by
# invoke and invoke_from_option class methods.
#
def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc:
shell.padding += 1
......
......@@ -36,7 +36,7 @@ def initialize(name, description=nil, required=nil, type=nil, default=nil, banne
# string (--foo=value) or booleans (just --foo).
#
# By default all options are optional, unless :required is given.
#
#
def self.parse(key, value)
if key.is_a?(Array)
name, *aliases = key
......
......@@ -36,7 +36,7 @@ def method_missing(meth, *args)
def install(name)
initialize_thorfiles
# If a directory name is provided as the argument, look for a 'main.thor'
# If a directory name is provided as the argument, look for a 'main.thor'
# task in said directory.
begin
if File.directory?(File.expand_path(name))
......@@ -124,11 +124,7 @@ def update(name)
method_options :internal => :boolean
def installed
initialize_thorfiles(nil, true)
klasses = Thor::Base.subclasses
klasses -= [Thor, Thor::Runner] unless options["internal"]
display_klasses(true, klasses)
display_klasses(true, options["internal"])
end
desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
......@@ -144,11 +140,15 @@ def list(search="")
(options[:all] || k.group == group) && k.namespace =~ search
end
display_klasses(false, klasses)
display_klasses(false, false, klasses)
end
private
def self.banner(task)
"thor " + task.formatted_usage(self, false)
end
def thor_root
Thor::Util.thor_root
end
......@@ -156,7 +156,7 @@ def thor_root
def thor_yaml
@thor_yaml ||= begin
yaml_file = File.join(thor_root, "thor.yml")
yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
yaml || {}
end
end
......@@ -215,9 +215,6 @@ def initialize_thorfiles(relevant_to=nil, skip_lookup=false)
# 5. c:\ <-- no Thorfiles found!
#
def thorfiles(relevant_to=nil, skip_lookup=false)
# TODO Remove this dealing with deprecated thor when :namespaces: is available as constants
save_yaml(thor_yaml) if Thor::Util.convert_constants_to_namespaces(thor_yaml)
thorfiles = []
unless skip_lookup
......@@ -253,47 +250,54 @@ def thorfiles_relevant_to(meth)
# Display information about the given klasses. If with_module is given,
# it shows a table with information extracted from the yaml file.
#
def display_klasses(with_modules=false, klasses=Thor.subclasses)
klasses -= [Thor, Thor::Runner] unless with_modules
def display_klasses(with_modules=false, show_internal=false, klasses=Thor::Base.subclasses)
klasses -= [Thor, Thor::Runner, Thor::Group] unless show_internal
raise Error, "No Thor tasks available" if klasses.empty?
show_modules if with_modules && !thor_yaml.empty?
if with_modules && !thor_yaml.empty?
info = []
labels = ["Modules", "Namespaces"]
# Remove subclasses
klasses.dup.each do |klass|
klasses -= Thor::Util.thor_classes_in(klass)
end
info << labels
info << [ "-" * labels[0].size, "-" * labels[1].size ]
list = Hash.new { |h,k| h[k] = [] }
groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
thor_yaml.each do |name, hash|
info << [ name, hash[:namespaces].join(", ") ]
end
# Get classes which inherit from Thor
(klasses - groups).each { |k| list[k.namespace] += k.printable_tasks(false) }
print_table info
say ""
end
# Get classes which inherit from Thor::Base
groups.map! { |k| k.printable_tasks(false).first }
list["root"] = groups
unless klasses.empty?
klasses.dup.each do |klass|
klasses -= Thor::Util.thor_classes_in(klass)
end
# Order namespaces with default coming first
list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') }
list.each { |n, tasks| display_tasks(n, tasks) unless tasks.empty? }
end
klasses.each { |k| display_tasks(k) }
else
say "\033[1;34mNo Thor tasks available\033[0m"
end
def display_tasks(namespace, list) #:nodoc:
list.sort!{ |a,b| a[0] <=> b[0] }
say shell.set_color(namespace, :blue, true)
say "-" * namespace.size
print_table(list, :truncate => true)
say
end
# Display tasks from the given Thor class.
#
def display_tasks(klass)
unless klass.tasks.empty?
base = klass.namespace
def show_modules #:nodoc:
info = []
labels = ["Modules", "Namespaces"]
color = base == "default" ? :magenta : :blue
say shell.set_color(base, color, true)
say "-" * base.length
info << labels
info << [ "-" * labels[0].size, "-" * labels[1].size ]
klass.help(shell, :short => true, :ident => 0, :namespace => true)
thor_yaml.each do |name, hash|
info << [ name, hash[:namespaces].join(", ") ]
end
print_table info
say ""
end
end
......@@ -22,7 +22,7 @@ def self.shell=(klass)
end
module Shell
SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_list, :print_table]
SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_table]
# Add shell to initialize config values.
#
......
......@@ -75,30 +75,6 @@ def no?(statement, color=nil)
!yes?(statement, color)
end
# Prints a list of items.
#
# ==== Parameters
# list<Array[String, String, ...]>
#
# ==== Options
# mode:: Can be :rows or :inline. Defaults to :rows.
# ident:: Ident each item with the value given.
#
def print_list(list, options={})
return if list.empty?
ident = " " * (options[:ident] || 0)
content = case options[:mode]
when :inline
last = list.pop
"#{list.join(", ")}, and #{last}"
else # rows
ident + list.join("\n#{ident}")
end
$stdout.puts content
end
# Prints a table.
#
# ==== Parameters
......@@ -110,20 +86,26 @@ def print_list(list, options={})
def print_table(table, options={})
return if table.empty?
formats = []
formats, ident = [], options[:ident].to_i
options[:truncate] = terminal_width if options[:truncate] == true
0.upto(table.first.length - 2) do |i|
maxima = table.max{ |a,b| a[i].size <=> b[i].size }[i].size
formats << "%-#{maxima + 2}s"
end
formats[0] = formats[0].insert(0, " " * options[:ident]) if options[:ident]
formats[0] = formats[0].insert(0, " " * ident)
formats << "%s"
table.each do |row|
sentence = ""
row.each_with_index do |column, i|
$stdout.print formats[i] % column.to_s
sentence << formats[i] % column.to_s
end
$stdout.puts
sentence = truncate(sentence, options[:truncate]) if options[:truncate]
$stdout.puts sentence
end
end
......@@ -214,6 +196,44 @@ def quiet? #:nodoc:
base && base.options[:quiet]
end
# This code was copied from Rake, available under MIT-LICENSE
# Copyright (c) 2003, 2004 Jim Weirich
def terminal_width
if ENV['THOR_COLUMNS']
result = ENV['THOR_COLUMNS'].to_i
else
result = unix? ? dynamic_width : 80
end
(result < 10) ? 80 : result
rescue
80
end
# Calculate the dynamic width of the terminal
def dynamic_width
@dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
end
def dynamic_width_stty
%x{stty size 2>/dev/null}.split[1].to_i
end
def dynamic_width_tput
%x{tput cols 2>/dev/null}.to_i
end
def unix?
RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
end
def truncate(string, width)
if string.length <= width
string
else
( string[0, width-3] || "" ) + "..."
end
end
end
end
end
......@@ -63,7 +63,7 @@ def set_color(string, color, bold=false)
#
def show_diff(destination, content) #:nodoc:
if diff_lcs_loaded? && ENV['THOR_DIFF'].nil? && ENV['RAILS_DIFF'].nil?
actual = File.read(destination).to_s.split("\n")
actual = File.binread(destination).to_s.split("\n")
content = content.to_s.split("\n")
Diff::LCS.sdiff(actual, content).each do |diff|
......
class Thor
class Task < Struct.new(:name, :description, :usage, :options)
FILE_REGEXP = /^#{Regexp.escape(File.expand_path(__FILE__))}:[\w:]+ `run'$/
# A dynamic task that handles method missing scenarios.
#
class Dynamic < Task
def initialize(name)
super(name.to_s, "A dynamically-generated task", name.to_s)
def initialize(name, options=nil)
super(name.to_s, "A dynamically-generated task", name.to_s, options)
end
def run(instance, args=[])
......@@ -25,84 +25,73 @@ def initialize_copy(other) #:nodoc:
self.options = other.options.dup if other.options
end
def short_description
description.split("\n").first if description
end
# By default, a task invokes a method in the thor class. You can change this
# implementation to create custom tasks.
#
def run(instance, args=[])
raise UndefinedTaskError, "the '#{name}' task of #{instance.class} is private" unless public_method?(instance)
instance.send(name, *args)
rescue ArgumentError => e
raise e if instance.class.respond_to?(:debugging) && instance.class.debugging
parse_argument_error(instance, e, caller)
rescue NoMethodError => e
raise e if instance.class.respond_to?(:debugging) && instance.class.debugging
parse_no_method_error(instance, e)
end
# Returns the formatted usage. If a class is given, the class arguments are
# injected in the usage.
#
def formatted_usage(klass=nil, namespace=false, show_options=true)
formatted = if namespace.is_a?(String)
"#{namespace}:"
elsif klass && namespace
"#{klass.namespace.gsub(/^default/,'')}:"
# Returns the formatted usage by injecting given required arguments
# and required options into the given usage.
def formatted_usage(klass, namespace=nil)
namespace = klass.namespace if namespace.nil?
# Add namespace
formatted = if namespace
"#{namespace.gsub(/^(default|thor:runner:)/,'')}:"
else
""
end
formatted << formatted_arguments(klass)
formatted << " #{formatted_options}" if show_options
formatted.strip!
formatted
end
# Injects the class arguments into the task usage.
#
def formatted_arguments(klass)
if klass && !klass.arguments.empty?
# Add usage with required arguments
formatted << if klass && !klass.arguments.empty?
usage.to_s.gsub(/^#{name}/) do |match|
match << " " << klass.arguments.map{ |a| a.usage }.join(' ')
match << " " << klass.arguments.map{ |a| a.usage }.compact.join(' ')
end
else
usage.to_s
end
end
# Returns the options usage for this task.
#
def formatted_options
@formatted_options ||= options.map{ |_, o| o.usage }.sort.join(" ")
# Add required options
formatted << " #{required_options}"
# Strip and go!
formatted.strip
end
protected
def required_options
@required_options ||= options.map{ |_, o| o.usage if o.required? }.compact.sort.join(" ")
end
# Given a target, checks if this class name is not a private/protected method.
#
def public_method?(instance) #:nodoc:
collection = instance.private_methods + instance.protected_methods
(collection & [name.to_s, name.to_sym]).empty?
end
# Clean everything that comes from the Thor gempath and remove the caller.
#
def sans_backtrace(backtrace, caller) #:nodoc:
dirname = /^#{Regexp.escape(File.dirname(__FILE__))}/
saned = backtrace.reject { |frame| frame =~ dirname }
saned -= caller
# For Ruby <= 1.8.7, we have to match the method name that we are trying to call.
# In Ruby >= 1.9.1, we have to match the method run in this file.
def backtrace_match?(backtrace) #:nodoc:
method_name = /`#{Regexp.escape(name.split(':').last)}'/
backtrace =~ method_name || backtrace =~ FILE_REGEXP
end
def parse_argument_error(instance, e, caller) #:nodoc:
backtrace = sans_backtrace(e.backtrace, caller)
if backtrace.empty? && e.message =~ /wrong number of arguments/
if e.message =~ /wrong number of arguments/ && backtrace_match?(e.backtrace.first.to_s)
if instance.is_a?(Thor::Group)
raise e, "'#{name}' was called incorrectly. Are you sure it has arity equals to 0?"
else
raise InvocationError, "'#{name}' was called incorrectly. Call as " <<
"'#{formatted_usage(instance.class, true)}'"
"'#{formatted_usage(instance.class)}'"
end
else
raise e
......
......@@ -76,8 +76,10 @@ def self.namespaces_in_content(contents, file=__FILE__)
# Returns the thor classes declared inside the given class.
#
def self.thor_classes_in(klass)
stringfied_constants = klass.constants.map { |c| c.to_s }
Thor::Base.subclasses.select do |subclass|
klass.constants.include?(subclass.name.gsub("#{klass.name}::", ''))
next unless subclass.name
stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", ''))
end
end
......@@ -155,7 +157,7 @@ def self.namespace_to_thor_class_and_task(namespace, raise_if_nil=true)
# inside the sandbox to avoid namespacing conflicts.
#
def self.load_thorfile(path, content=nil)
content ||= File.read(path)
content ||= File.binread(path)
begin
Thor::Sandbox.class_eval(content, path)
......@@ -164,26 +166,6 @@ def self.load_thorfile(path, content=nil)
end
end
# Receives a yaml (hash) and updates all constants entries to namespace.
# This was added to deal with deprecated versions of Thor.
#
# TODO Deprecate this method in the future.
#
# ==== Returns
# TrueClass|FalseClass:: Returns true if any change to the yaml file was made.
#
def self.convert_constants_to_namespaces(yaml)
yaml_changed = false
yaml.each do |k, v|
next unless v[:constants] && v[:namespaces].nil?
yaml_changed = true
yaml[k][:namespaces] = v[:constants].map{|c| Thor::Util.namespace_from_thor_class(c)}
end
yaml_changed
end
def self.user_home
@@user_home ||= if ENV["HOME"]
ENV["HOME"]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册