bin/thor in thor-0.9.2 vs bin/thor in thor-0.9.5

- old
+ new

@@ -1,327 +1,7 @@ #!/usr/bin/env ruby +# -*- mode: ruby -*- -require "thor" -require "open-uri" -require "fileutils" -require "yaml" -require "digest/md5" -require "readline" +require File.dirname(__FILE__) + "/../lib/thor" +require 'thor/runner' -module ObjectSpace - - class << self - - # ==== Returns - # Array[Class]:: All the classes in the object space. - def classes - klasses = [] - ObjectSpace.each_object(Class) {|o| klasses << o} - klasses - end - end - -end - -class Thor::Util - - # @public - def self.constant_to_thor_path(str) - snake_case(str).squeeze(":") - end - - # @public - def self.constant_from_thor_path(str) - make_constant(to_constant(str)) - end - - def self.to_constant(str) - str.gsub(/:(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } - end - - def self.constants_in_contents(str) - klasses = self.constants.dup - eval(str) - ret = self.constants - klasses - ret.each {|k| self.send(:remove_const, k)} - ret - end - - private - # @private - def self.make_constant(str) - list = str.split("::") - obj = Object - list.each {|x| obj = obj.const_get(x) } - obj - end - - # @private - def self.snake_case(str) - return str.downcase if str =~ /^[A-Z]+$/ - str.gsub(/([A-Z]+)(?=[A-Z][a-z]?)|\B[A-Z]/, '_\&') =~ /_*(.*)/ - return $+.downcase - end - -end - -class Thor::Runner < Thor - - def self.globs_for(path) - ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"] - end - - def initialize_thorfiles(include_system = true) - thorfiles(include_system).each {|f| load f unless Thor.subclass_files.keys.include?(File.expand_path(f))} - end - - map "-T" => :list, "-i" => :install, "-u" => :update - - desc "install NAME", "install a Thor file into your system tasks, optionally named for future updates" - method_options :as => :optional - def install(name, opts) - initialize_thorfiles - begin - contents = open(name).read - rescue OpenURI::HTTPError - puts "The URI you provided: `#{name}' was invalid" - return - rescue Errno::ENOENT - puts "`#{name}' is not a valid file" - return - end - - puts "Your Thorfile contains: " - puts contents - print "Do you wish to continue [y/N]? " - response = Readline.readline - - return unless response =~ /^\s*y/i - - constants = Thor::Util.constants_in_contents(contents) - - name = name =~ /\.thor$/ ? name : "#{name}.thor" - - as = opts["as"] || begin - first_line = contents.split("\n")[0] - (match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil - end - - if !as - print "Please specify a name for #{name} in the system repository [#{name}]: " - as = Readline.readline - as = name if as.empty? - end - - FileUtils.mkdir_p thor_root - - yaml_file = File.join(thor_root, "thor.yml") - FileUtils.touch(yaml_file) - yaml = thor_yaml - - yaml[as] = {:filename => Digest::MD5.hexdigest(name + as), :location => name, :constants => constants} - - save_yaml(yaml) - - puts "Storing thor file in your system repository" - - File.open(File.join(thor_root, yaml[as][:filename] + ".thor"), "w") do |file| - file.puts contents - end - end - - desc "uninstall NAME", "uninstall a named Thor module" - def uninstall(name) - yaml = thor_yaml - unless yaml[name] - puts "There was no module by that name installed" - return - end - - puts "Uninstalling #{name}." - - file = File.join(thor_root, "#{yaml[name][:filename]}.thor") - File.delete(file) - yaml.delete(name) - save_yaml(yaml) - - puts "Done." - end - - desc "update NAME", "update a Thor file from its original location" - def update(name) - yaml = thor_yaml - if !yaml[name] || !yaml[name][:location] - puts "`#{name}' was not found in the system repository" - else - puts "Updating `#{name}' from #{yaml[name][:location]}" - install(yaml[name][:location], "as" => name) - end - end - - def installed - Dir["#{ENV["HOME"]}/.thor/**/*.thor"].each do |f| - load f unless Thor.subclass_files.keys.include?(File.expand_path(f)) - end - display_klasses(true) - 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 = {}) - initialize_thorfiles - search = ".*#{search}" if options["substring"] - search = /^#{search}.*/i - - display_klasses(false, Thor.subclasses.select {|k| - Thor::Util.constant_to_thor_path(k.name) =~ search}) - end - - def method_missing(meth, *args) - initialize_thorfiles(false) - meth = meth.to_s - unless meth =~ /:/ - puts "Thor tasks must contain a :" - return - end - - thor_klass = meth.split(":")[0...-1].join(":") - to_call = meth.split(":").last - - yaml = thor_yaml - - klass_str = Thor::Util.to_constant(thor_klass) - files = yaml.inject([]) { |a,(k,v)| a << v[:filename] if v[:constants] && v[:constants].include?(klass_str); a } - - unless files.empty? - files.each do |f| - load File.join(thor_root, "#{f}.thor") - end - klass = Thor::Util.constant_from_thor_path(thor_klass) - else - begin - klass = Thor::Util.constant_from_thor_path(thor_klass) - rescue - puts "There was no available namespace `#{thor_klass}'." - return - end - end - - unless klass.ancestors.include?(Thor) - puts "`#{thor_klass}' is not a Thor module" - return - end - - ARGV.replace [to_call, *(args + ARGV)].compact - begin - klass.start - rescue ArgumentError - puts "You need to call #{to_call} as `#{klass.usage_for_method(to_call)}'" - rescue NoMethodError - puts "`#{to_call}' is not available in #{thor_klass}" - end - end - - private - def thor_root - File.join(ENV["HOME"], ".thor") - end - - def thor_yaml - yaml_file = File.join(thor_root, "thor.yml") - yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file) - yaml || {} - end - - def save_yaml(yaml) - yaml_file = File.join(thor_root, "thor.yml") - File.open(yaml_file, "w") {|f| f.puts yaml.to_yaml } - end - - def display_klasses(with_modules = false, klasses = Thor.subclasses) - klasses = klasses - [Thor::Runner] - - if klasses.empty? - puts "No thorfiles available" - return - end - - if with_modules - yaml = thor_yaml - max_name = yaml.max {|(xk,xv),(yk,yv)| xk.size <=> yk.size }.first.size - - print "%-#{max_name + 4}s" % "Name" - puts "Modules" - print "%-#{max_name + 4}s" % "----" - puts "-------" - - yaml.each do |name, info| - print "%-#{max_name + 4}s" % name - puts info[:constants].map {|c| Thor::Util.constant_to_thor_path(c)}.join(", ") - end - - puts - end - - puts "Tasks" - puts "-----" - - # Calculate the largest base class name - max_base = klasses.max do |x,y| - Thor::Util.constant_to_thor_path(x.name).size <=> Thor::Util.constant_to_thor_path(y.name).size - end.name.size - - # Calculate the size of the largest option description - max_left_item = klasses.max do |x,y| - (x.help_list && x.help_list.max.usage + x.help_list.max.opt).to_i <=> - (y.help_list && y.help_list.max.usage + y.help_list.max.opt).to_i - end - - max_left = max_left_item.help_list.max.usage + max_left_item.help_list.max.opt - - klasses.map {|k| k.help_list}.compact.each do |item| - display_tasks(item, max_base, max_left) - end - end - - def display_tasks(item, max_base, max_left) - base = Thor::Util.constant_to_thor_path(item.klass.name) - item.usages.each do |name, usage| - format_string = "%-#{max_left + max_base + 5}s" - print format_string % - "#{base}:#{item.usages.assoc(name).last} #{display_opts(item.opts.assoc(name) && item.opts.assoc(name).last)}" - puts item.descriptions.assoc(name).last - end - end - - def display_opts(opts) - return "" unless opts - opts.map do |opt, val| - if val == true || val == "BOOLEAN" - "[#{opt}]" - elsif val == "REQUIRED" - opt + "=" + opt.gsub(/\-/, "").upcase - elsif val == "OPTIONAL" - "[" + opt + "=" + opt.gsub(/\-/, "").upcase + "]" - end - end.join(" ") - end - - def thorfiles(include_system = true) - path = Dir.pwd - system_thorfiles = Dir["#{ENV["HOME"]}/.thor/**/*.thor"] - thorfiles = [] - - # Look for Thorfile or *.thor in the current directory or a parent directory, until the root - while thorfiles.empty? - thorfiles = Dir[*Thor::Runner.globs_for(path)] - path = File.dirname(path) - break if path == "/" - end - thorfiles + (include_system ? system_thorfiles : []) - end - -end - -unless defined?(Spec) - Thor::Runner.start -end \ No newline at end of file +Thor::Runner.start