lib/thor.rb
$:.unshift File.expand_path(File.dirname(__FILE__)) require "thor/options" require "thor/util" require "thor/task" require "thor/task_hash" class Thor attr_accessor :options def self.map(map) @map ||= superclass.instance_variable_get("@map") || {} map.each do |key, value| if key.respond_to?(:each) key.each {|subkey| @map[subkey] = value} else @map[key] = value end end end def self.desc(usage, description) @usage, @desc = usage, description end def self.group(name) @group_name = name.to_s end def self.group_name @group_name || 'standard' end def self.method_options(opts) @method_options = opts end def self.subclass_files @subclass_files ||= Hash.new {|h,k| h[k] = []} end def self.subclasses @subclasses ||= [] end def self.tasks @tasks ||= TaskHash.new(self) end def self.opts (@opts || {}).merge(self == Thor ? {} : superclass.opts) end def self.[](task) namespaces = task.split(":") klass = Thor::Util.constant_from_thor_path(namespaces[0...-1].join(":")) raise Error, "`#{klass}' is not a Thor class" unless klass <= Thor klass.tasks[namespaces.last] end def self.maxima @maxima ||= begin max_usage = tasks.map {|_, t| t.usage}.max {|x,y| x.to_s.size <=> y.to_s.size}.size max_desc = tasks.map {|_, t| t.description}.max {|x,y| x.to_s.size <=> y.to_s.size}.size max_opts = tasks.map {|_, t| t.opts ? t.opts.formatted_usage : ""}.max {|x,y| x.to_s.size <=> y.to_s.size}.size Struct.new(:description, :usage, :opt).new(max_desc, max_usage, max_opts) end end def self.start(args = ARGV) options = Thor::Options.new(self.opts) opts = options.parse(args, false) args = options.trailing_non_opts meth = args.first meth = @map[meth].to_s if @map && @map[meth] meth ||= "help" tasks[meth].parse new(opts, *args), args[1..-1] rescue Thor::Error => e $stderr.puts e.message end class << self protected def inherited(klass) register_klass_file klass end def method_added(meth) meth = meth.to_s if meth == "initialize" @opts = @method_options @method_options = nil return end return if !public_instance_methods.include?(meth) || !@usage register_klass_file self tasks[meth] = Task.new(meth, @desc, @usage, @method_options) @usage, @desc, @method_options = nil end def register_klass_file(klass, file = caller[1].split(":")[0]) unless self == Thor superclass.register_klass_file(klass, file) return end file_subclasses = subclass_files[File.expand_path(file)] file_subclasses << klass unless file_subclasses.include?(klass) subclasses << klass unless subclasses.include?(klass) end end def initialize(opts = {}, *args) end map ["-h", "-?", "--help", "-D"] => :help desc "help [TASK]", "describe available tasks or one specific task" def help(task = nil) if task if task.include? ?: task = self.class[task] namespace = true else task = self.class.tasks[task] end puts task.formatted_usage(namespace) puts task.description else puts "Options" puts "-------" self.class.tasks.each do |_, task| format = "%-" + (self.class.maxima.usage + self.class.maxima.opt + 4).to_s + "s" print format % ("#{task.formatted_usage}") puts task.description.split("\n").first end end end end