Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Moving in thor

  • Loading branch information...
commit 67859e42a04e8f77a1d62779b1f2f2e34e4b62e8 1 parent 2f42417
Carlhuda authored
Showing with 4,901 additions and 0 deletions.
  1. +3 −0  .gitignore
  2. +20 −0 LICENSE
  3. +46 −0 Rakefile
  4. +20 −0 lib/bubble.rb
  5. +33 −0 lib/bubble/definition.rb
  6. +13 −0 lib/bubble/dependency.rb
  7. +20 −0 lib/bubble/dsl.rb
  8. +15 −0 lib/bubble/environment.rb
  9. +246 −0 lib/bubble/resolver.rb
  10. +240 −0 lib/bubble/vendor/thor.rb
  11. +274 −0 lib/bubble/vendor/thor/actions.rb
  12. +103 −0 lib/bubble/vendor/thor/actions/create_file.rb
  13. +91 −0 lib/bubble/vendor/thor/actions/directory.rb
  14. +134 −0 lib/bubble/vendor/thor/actions/empty_directory.rb
  15. +223 −0 lib/bubble/vendor/thor/actions/file_manipulation.rb
  16. +101 −0 lib/bubble/vendor/thor/actions/inject_into_file.rb
  17. +515 −0 lib/bubble/vendor/thor/base.rb
  18. +9 −0 lib/bubble/vendor/thor/core_ext/file_binary_read.rb
  19. +75 −0 lib/bubble/vendor/thor/core_ext/hash_with_indifferent_access.rb
  20. +100 −0 lib/bubble/vendor/thor/core_ext/ordered_hash.rb
  21. +27 −0 lib/bubble/vendor/thor/error.rb
  22. +267 −0 lib/bubble/vendor/thor/group.rb
  23. +178 −0 lib/bubble/vendor/thor/invocation.rb
  24. +4 −0 lib/bubble/vendor/thor/parser.rb
  25. +67 −0 lib/bubble/vendor/thor/parser/argument.rb
  26. +145 −0 lib/bubble/vendor/thor/parser/arguments.rb
  27. +132 −0 lib/bubble/vendor/thor/parser/option.rb
  28. +142 −0 lib/bubble/vendor/thor/parser/options.rb
  29. +66 −0 lib/bubble/vendor/thor/rake_compat.rb
  30. +303 −0 lib/bubble/vendor/thor/runner.rb
  31. +78 −0 lib/bubble/vendor/thor/shell.rb
  32. +239 −0 lib/bubble/vendor/thor/shell/basic.rb
  33. +108 −0 lib/bubble/vendor/thor/shell/color.rb
  34. +111 −0 lib/bubble/vendor/thor/task.rb
  35. +233 −0 lib/bubble/vendor/thor/util.rb
  36. +3 −0  lib/bubble/vendor/thor/version.rb
  37. +13 −0 spec/install/gems_spec.rb
  38. +40 −0 spec/runtime/load_spec.rb
  39. +12 −0 spec/runtime/setup_spec.rb
  40. +36 −0 spec/spec_helper.rb
  41. +257 −0 spec/support/builders.rb
  42. +83 −0 spec/support/helpers.rb
  43. +18 −0 spec/support/matchers.rb
  44. +33 −0 spec/support/path.rb
  45. +25 −0 spec/support/rubygems.rb
View
3  .gitignore
@@ -0,0 +1,3 @@
+.DS_Store
+tmp
+pkg
View
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2009 Engine Yard
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
46 Rakefile
@@ -0,0 +1,46 @@
+$:.unshift File.expand_path("../lib", __FILE__)
+
+require 'rubygems'
+require 'rubygems/specification'
+require 'bubble'
+
+spec = Gem::Specification.new do |s|
+ s.name = "bubble"
+ s.version = Bubble::VERSION
+ s.authors = ["Carl Lerche", "Yehuda Katz"]
+ s.email = ["carlhuda@engineyard.com"]
+ s.homepage = "http://github.com/carlhuda/bubble"
+ s.summary = "Bubbles are fun"
+
+ s.platform = Gem::Platform::RUBY
+
+ s.required_rubygems_version = ">= 1.3.5"
+
+ s.files = Dir.glob("{bin,lib}/**/*") + %w(LICENSE README)
+ s.executables = ['bbl']
+ s.require_path = 'lib'
+end
+
+begin
+ require 'spec/rake/spectask'
+rescue LoadError
+ task :spec do
+ $stderr.puts '`gem install rspec` to run specs'
+ end
+else
+ desc "Run specs"
+ Spec::Rake::SpecTask.new do |t|
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ t.spec_opts = %w(-fs --color)
+ t.warning = true
+ end
+end
+
+desc "create a gemspec file"
+task :gemspec do
+ File.open("#{spec.name}.gemspec", 'w') do |file|
+ file.puts spec.to_ruby
+ end
+end
+
+task :default => :spec
View
20 lib/bubble.rb
@@ -0,0 +1,20 @@
+module Bubble
+ VERSION = "0.9.0.pre"
+
+ autoload :Definition, 'bubble/definition'
+ autoload :Dependency, 'bubble/dependency'
+ autoload :Dsl, 'bubble/dsl'
+ autoload :Environment, 'bubble/environment'
+
+ class GemfileNotFound < StandardError; end
+ class GemNotFound < StandardError; end
+ class VersionConflict < StandardError; end
+
+ def self.load(gemfile = nil)
+ Environment.new(definition(gemfile))
+ end
+
+ def self.definition(gemfile = nil)
+ Definition.from_gemfile(gemfile)
+ end
+end
View
33 lib/bubble/definition.rb
@@ -0,0 +1,33 @@
+module Bubble
+ class Definition
+ def self.from_gemfile(gemfile)
+ gemfile = Pathname.new(gemfile || default_gemfile).expand_path
+
+ unless gemfile.file?
+ raise GemfileNotFound, "`#{gemfile}` not found"
+ end
+
+ definition = new
+ Dsl.evaluate(gemfile, definition)
+ definition
+ end
+
+ def self.default_gemfile
+ current = Pathname.new(Dir.pwd)
+
+ until current.root?
+ filename = current.join("Gemfile")
+ return filename if filename.exist?
+ current = current.parent
+ end
+
+ raise GemfileNotFound, "the default Gemfile was not found"
+ end
+
+ attr_reader :dependencies
+
+ def initialize
+ @dependencies = []
+ end
+ end
+end
View
13 lib/bubble/dependency.rb
@@ -0,0 +1,13 @@
+require 'rubygems/dependency'
+
+module Bubble
+ class Dependency < Gem::Dependency
+ def initialize(name, version, options = {}, &blk)
+ options.each do |k, v|
+ options[k.to_s] = v
+ end
+
+ super(name, version)
+ end
+ end
+end
View
20 lib/bubble/dsl.rb
@@ -0,0 +1,20 @@
+module Bubble
+ class Dsl
+ def self.evaluate(gemfile, definition)
+ builder = new(definition)
+ builder.instance_eval(File.read(gemfile.to_s), gemfile.to_s, 1)
+ definition
+ end
+
+ def initialize(definition)
+ @definition = definition
+ end
+
+ def gem(name, *args)
+ options = Hash === args.last ? args.pop : {}
+ version = args.last || ">= 0"
+
+ @definition.dependencies << Dependency.new(name, version, options)
+ end
+ end
+end
View
15 lib/bubble/environment.rb
@@ -0,0 +1,15 @@
+module Bubble
+ class Environment
+ def initialize(definition)
+ @definition = definition
+ end
+
+ def dependencies
+ @definition.dependencies
+ end
+
+ def gems
+ []
+ end
+ end
+end
View
246 lib/bubble/resolver.rb
@@ -0,0 +1,246 @@
+# This is the latest iteration of the gem dependency resolving algorithm. As of now,
+# it can resolve (as a success of failure) any set of gem dependencies we throw at it
+# in a reasonable amount of time. The most iterations I've seen it take is about 150.
+# The actual implementation of the algorithm is not as good as it could be yet, but that
+# can come later.
+
+# Extending Gem classes to add necessary tracking information
+module Gem
+ class Dependency
+ def required_by
+ @required_by ||= []
+ end
+ end
+ class Specification
+ def required_by
+ @required_by ||= []
+ end
+ end
+end
+
+module Bubble
+ class Resolver
+
+ attr_reader :errors
+
+ # Figures out the best possible configuration of gems that satisfies
+ # the list of passed dependencies and any child dependencies without
+ # causing any gem activation errors.
+ #
+ # ==== Parameters
+ # *dependencies<Gem::Dependency>:: The list of dependencies to resolve
+ #
+ # ==== Returns
+ # <GemBundle>,nil:: If the list of dependencies can be resolved, a
+ # collection of gemspecs is returned. Otherwise, nil is returned.
+ def self.resolve(requirements, sources)
+ source_requirements = {}
+
+ requirements.each do |r|
+ next unless r.source
+ source_requirements[r.name] = r.source
+ end
+
+ resolver = new(sources, source_requirements)
+ result = catch(:success) do
+ resolver.resolve(requirements, {})
+ output = resolver.errors.inject("") do |o, (conflict, (origin, requirement))|
+ o << " Conflict on: #{conflict.inspect}:\n"
+ o << " * #{conflict} (#{origin.version}) activated by #{origin.required_by.first}\n"
+ o << " * #{requirement} required by #{requirement.required_by.first}\n"
+ o << " All possible versions of origin requirements conflict."
+ end
+ raise VersionConflict, "No compatible versions could be found for required dependencies:\n #{output}"
+ nil
+ end
+ if result
+ # Order gems in order of dependencies. Every gem's dependency is at
+ # a smaller index in the array.
+ ordered = []
+ result.values.each do |spec1|
+ index = nil
+ place = ordered.detect do |spec2|
+ spec1.dependencies.any? { |d| d.name == spec2.name }
+ end
+ place ?
+ ordered.insert(ordered.index(place), spec1) :
+ ordered << spec1
+ end
+ ordered.reverse
+ end
+ end
+
+ def initialize(sources, source_requirements)
+ @errors = {}
+ @stack = []
+ @specs = Hash.new { |h,k| h[k] = [] }
+ @by_gem = source_requirements
+ @cache = {}
+ @index = {}
+
+ sources.each do |source|
+ source.gems.each do |name, specs|
+ # Hack to work with a regular Gem::SourceIndex
+ specs = [specs] unless specs.is_a?(Array)
+ specs.compact.each do |spec|
+ next if @specs[spec.name].any? { |s| s.version == spec.version && s.platform == spec.platform }
+ @specs[spec.name] << spec
+ end
+ end
+ end
+ end
+
+ def debug
+ puts yield if defined?($debug) && $debug
+ end
+
+ def resolve(reqs, activated)
+ # If the requirements are empty, then we are in a success state. Aka, all
+ # gem dependencies have been resolved.
+ throw :success, activated if reqs.empty?
+
+ debug { STDIN.gets ; print "\e[2J\e[f" ; "==== Iterating ====\n\n" }
+
+ # Sort dependencies so that the ones that are easiest to resolve are first.
+ # Easiest to resolve is defined by:
+ # 1) Is this gem already activated?
+ # 2) Do the version requirements include prereleased gems?
+ # 3) Sort by number of gems available in the source.
+ reqs = reqs.sort_by do |a|
+ [ activated[a.name] ? 0 : 1,
+ a.version_requirements.prerelease? ? 0 : 1,
+ @errors[a.name] ? 0 : 1,
+ activated[a.name] ? 0 : search(a).size ]
+ end
+
+ debug { "Activated:\n" + activated.values.map { |a| " #{a.name} (#{a.version})" }.join("\n") }
+ debug { "Requirements:\n" + reqs.map { |r| " #{r.name} (#{r.version_requirements})"}.join("\n") }
+
+ activated = activated.dup
+ # Pull off the first requirement so that we can resolve it
+ current = reqs.shift
+
+ debug { "Attempting:\n #{current.name} (#{current.version_requirements})"}
+
+ # Check if the gem has already been activated, if it has, we will make sure
+ # that the currently activated gem satisfies the requirement.
+ if existing = activated[current.name]
+ if current.version_requirements.satisfied_by?(existing.version)
+ debug { " * [SUCCESS] Already activated" }
+ @errors.delete(existing.name)
+ # Since the current requirement is satisfied, we can continue resolving
+ # the remaining requirements.
+ resolve(reqs, activated)
+ else
+ debug { " * [FAIL] Already activated" }
+ @errors[existing.name] = [existing, current]
+ debug { current.required_by.map {|d| " * #{d.name} (#{d.version_requirements})" }.join("\n") }
+ # debug { " * All current conflicts:\n" + @errors.keys.map { |c| " - #{c}" }.join("\n") }
+ # Since the current requirement conflicts with an activated gem, we need
+ # to backtrack to the current requirement's parent and try another version
+ # of it (maybe the current requirement won't be present anymore). If the
+ # current requirement is a root level requirement, we need to jump back to
+ # where the conflicting gem was activated.
+ parent = current.required_by.last || existing.required_by.last
+ # We track the spot where the current gem was activated because we need
+ # to keep a list of every spot a failure happened.
+ debug { " -> Jumping to: #{parent.name}" }
+ throw parent.name, existing.required_by.last.name
+ end
+ else
+ # There are no activated gems for the current requirement, so we are going
+ # to find all gems that match the current requirement and try them in decending
+ # order. We also need to keep a set of all conflicts that happen while trying
+ # this gem. This is so that if no versions work, we can figure out the best
+ # place to backtrack to.
+ conflicts = Set.new
+
+ # Fetch all gem versions matching the requirement
+ #
+ # TODO: Warn / error when no matching versions are found.
+ matching_versions = search(current)
+
+ if matching_versions.empty?
+ if current.required_by.empty?
+ location = @by_gem[current.name] ? @by_gem[current.name] : "any of the sources"
+ raise GemNotFound, "Could not find gem '#{current}' in #{location}"
+ end
+ Bundler.logger.warn "Could not find gem '#{current}' (required by '#{current.required_by.last}') in any of the sources"
+ end
+
+ matching_versions.reverse_each do |spec|
+ conflict = resolve_requirement(spec, current, reqs.dup, activated.dup)
+ conflicts << conflict if conflict
+ end
+ # If the current requirement is a root level gem and we have conflicts, we
+ # can figure out the best spot to backtrack to.
+ if current.required_by.empty? && !conflicts.empty?
+ # Check the current "catch" stack for the first one that is included in the
+ # conflicts set. That is where the parent of the conflicting gem was required.
+ # By jumping back to this spot, we can try other version of the parent of
+ # the conflicting gem, hopefully finding a combination that activates correctly.
+ @stack.reverse_each do |savepoint|
+ if conflicts.include?(savepoint)
+ debug { " -> Jumping to: #{savepoint}" }
+ throw savepoint
+ end
+ end
+ end
+ end
+ end
+
+ def resolve_requirement(spec, requirement, reqs, activated)
+ # We are going to try activating the spec. We need to keep track of stack of
+ # requirements that got us to the point of activating this gem.
+ spec.required_by.replace requirement.required_by
+ spec.required_by << requirement
+
+ activated[spec.name] = spec
+ debug { " Activating: #{spec.name} (#{spec.version})" }
+ debug { spec.required_by.map { |d| " * #{d.name} (#{d.version_requirements})" }.join("\n") }
+
+ # Now, we have to loop through all child dependencies and add them to our
+ # array of requirements.
+ debug { " Dependencies"}
+ spec.dependencies.each do |dep|
+ next if dep.type == :development
+ debug { " * #{dep.name} (#{dep.version_requirements})" }
+ dep.required_by.replace(requirement.required_by)
+ dep.required_by << requirement
+ reqs << dep
+ end
+
+ # We create a savepoint and mark it by the name of the requirement that caused
+ # the gem to be activated. If the activated gem ever conflicts, we are able to
+ # jump back to this point and try another version of the gem.
+ length = @stack.length
+ @stack << requirement.name
+ retval = catch(requirement.name) do
+ resolve(reqs, activated)
+ end
+ # Since we're doing a lot of throw / catches. A push does not necessarily match
+ # up to a pop. So, we simply slice the stack back to what it was before the catch
+ # block.
+ @stack.slice!(length..-1)
+ retval
+ end
+
+ def search(dependency)
+ @cache[dependency.hash] ||= begin
+ pinned = @by_gem[dependency.name].gems if @by_gem[dependency.name]
+ specs = (pinned || @specs)[dependency.name]
+
+ wants_prerelease = dependency.version_requirements.prerelease?
+ only_prerelease = specs.all? {|spec| spec.version.prerelease? }
+
+ found = specs.select { |spec| dependency =~ spec }
+
+ unless wants_prerelease || (pinned && only_prerelease)
+ found.reject! { |spec| spec.version.prerelease? }
+ end
+
+ found.sort_by {|s| [s.version, s.platform.to_s == 'ruby' ? "\0" : s.platform.to_s] }
+ end
+ end
+ end
+end
View
240 lib/bubble/vendor/thor.rb
@@ -0,0 +1,240 @@
+require 'thor/base'
+require 'thor/group'
+require 'thor/actions'
+
+class Thor
+ class << self
+ # Sets the default task when thor is executed without an explicit task to be called.
+ #
+ # ==== Parameters
+ # meth<Symbol>:: name of the defaut task
+ #
+ def default_task(meth=nil)
+ case meth
+ when :none
+ @default_task = 'help'
+ when nil
+ @default_task ||= from_superclass(:default_task, 'help')
+ else
+ @default_task = meth.to_s
+ end
+ end
+
+ # Defines the usage and the description of the next task.
+ #
+ # ==== Parameters
+ # usage<String>
+ # description<String>
+ #
+ def desc(usage, description, options={})
+ if options[:for]
+ task = find_and_refresh_task(options[:for])
+ task.usage = usage if usage
+ task.description = description if description
+ else
+ @usage, @desc = usage, description
+ end
+ end
+
+ # Maps an input to a task. If you define:
+ #
+ # map "-T" => "list"
+ #
+ # Running:
+ #
+ # thor -T
+ #
+ # Will invoke the list task.
+ #
+ # ==== Parameters
+ # Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given task.
+ #
+ def map(mappings=nil)
+ @map ||= from_superclass(:map, {})
+
+ if mappings
+ mappings.each do |key, value|
+ if key.respond_to?(:each)
+ key.each {|subkey| @map[subkey] = value}
+ else
+ @map[key] = value
+ end
+ end
+ end
+
+ @map
+ end
+
+ # Declares the options for the next task to be declared.
+ #
+ # ==== Parameters
+ # Hash[Symbol => Object]:: The hash key is the name of the option and the value
+ # is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
+ # or :required (string). If you give a value, the type of the value is used.
+ #
+ def method_options(options=nil)
+ @method_options ||= {}
+ build_options(options, @method_options) if options
+ @method_options
+ end
+
+ # 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_option :foo => :bar, :for => :previous_task
+ #
+ # def next_task
+ # # magic
+ # end
+ #
+ # ==== Parameters
+ # name<Symbol>:: The name of the argument.
+ # options<Hash>:: Described below.
+ #
+ # ==== Options
+ # :desc - Description for the argument.
+ # :required - If the argument is required or not.
+ # :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.
+ # :banner - String to show on usage notes.
+ #
+ def method_option(name, options={})
+ scope = if options[:for]
+ find_and_refresh_task(options[:for]).options
+ else
+ method_options
+ end
+
+ build_option(name, options, scope)
+ end
+
+ # Parses the task and options from the given args, instantiate the class
+ # and invoke the task. This method is used when the arguments must be parsed
+ # from an array. If you are inside Ruby and want to use a Thor class, you
+ # can simply initialize it:
+ #
+ # script = MyScript.new(args, options, config)
+ # script.invoke(:task, first_arg, second_arg, third_arg)
+ #
+ def start(given_args=ARGV, config={})
+ super do
+ meth = normalize_task_name(given_args.shift)
+ task = all_tasks[meth]
+
+ if task
+ args, opts = Thor::Options.split(given_args)
+ config.merge!(:task_options => task.options)
+ else
+ args, opts = given_args, {}
+ end
+
+ task ||= Thor::Task::Dynamic.new(meth)
+ trailing = args[Range.new(arguments.size, -1)]
+ new(args, opts, config).invoke(task, trailing || [])
+ end
+ end
+
+ # Prints help information for the given task.
+ #
+ # ==== Parameters
+ # 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.
+ #
+ # ==== Parameters
+ # shell<Thor::Shell>
+ #
+ 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] }
+
+ shell.say "Tasks:"
+ shell.print_table(list, :ident => 2, :truncate => true)
+ shell.say
+ class_options_help(shell)
+ end
+
+ # 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
+
+ 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. It receives
+ # the task that is going to be invoked and a boolean which indicates if
+ # the namespace should be displayed as arguments.
+ #
+ def banner(task)
+ "thor " + task.formatted_usage(self)
+ end
+
+ def baseclass #:nodoc:
+ Thor
+ end
+
+ def create_task(meth) #:nodoc:
+ if @usage && @desc
+ tasks[meth.to_s] = Thor::Task.new(meth, @desc, @usage, method_options)
+ @usage, @desc, @method_options = nil
+ true
+ elsif self.all_tasks[meth.to_s] || meth.to_sym == :method_missing
+ true
+ else
+ puts "[WARNING] Attempted to create task #{meth.inspect} without usage or description. " <<
+ "Call desc if you want this method to be available as task or declare it inside a " <<
+ "no_tasks{} block. Invoked from #{caller[1].inspect}."
+ false
+ end
+ end
+
+ def initialize_added #:nodoc:
+ class_options.merge!(method_options)
+ @method_options = nil
+ end
+
+ # Receives a task name (can be nil), and try to get a map from it.
+ # If a map can't be found use the sent name or the default task.
+ #
+ def normalize_task_name(meth) #:nodoc:
+ mapping = map[meth.to_s]
+ meth = mapping || meth || default_task
+ meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
+ end
+ end
+
+ include Thor::Base
+
+ map HELP_MAPPINGS => :help
+
+ desc "help [TASK]", "Describe available tasks or one specific task"
+ def help(task=nil)
+ task ? self.class.task_help(shell, task) : self.class.help(shell)
+ end
+end
View
274 lib/bubble/vendor/thor/actions.rb
@@ -0,0 +1,274 @@
+require 'fileutils'
+require 'thor/core_ext/file_binary_read'
+
+Dir[File.join(File.dirname(__FILE__), "actions", "*.rb")].each do |action|
+ require action
+end
+
+class Thor
+ module Actions
+ attr_accessor :behavior
+
+ def self.included(base) #:nodoc:
+ base.extend ClassMethods
+ end
+
+ module ClassMethods
+ # Hold source paths for one Thor instance. source_paths_for_search is the
+ # method responsible to gather source_paths from this current class,
+ # inherited paths and the source root.
+ #
+ def source_paths
+ @source_paths ||= []
+ end
+
+ # Returns the source paths in the following order:
+ #
+ # 1) This class source paths
+ # 2) Source root
+ # 3) Parents source paths
+ #
+ def source_paths_for_search
+ paths = []
+ paths += self.source_paths
+ paths << self.source_root if self.respond_to?(:source_root)
+ paths += from_superclass(:source_paths, [])
+ paths
+ end
+
+ # Add runtime options that help actions execution.
+ #
+ def add_runtime_options!
+ class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime,
+ :desc => "Overwrite 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
+
+ # Extends initializer to add more configuration options.
+ #
+ # ==== Configuration
+ # behavior<Symbol>:: The actions default behavior. Can be :invoke or :revoke.
+ # It also accepts :force, :skip and :pretend to set the behavior
+ # and the respective option.
+ #
+ # destination_root<String>:: The root directory needed for some actions.
+ #
+ def initialize(args=[], options={}, config={})
+ self.behavior = case config[:behavior].to_s
+ when "force", "skip"
+ _cleanup_options_and_set(options, config[:behavior])
+ :invoke
+ when "revoke"
+ :revoke
+ else
+ :invoke
+ end
+
+ super
+ self.destination_root = config[:destination_root]
+ end
+
+ # Wraps an action object and call it accordingly to the thor class behavior.
+ #
+ def action(instance) #:nodoc:
+ if behavior == :revoke
+ instance.revoke!
+ else
+ instance.invoke!
+ end
+ end
+
+ # Returns the root for this thor class (also aliased as destination root).
+ #
+ def destination_root
+ @destination_stack.last
+ end
+
+ # Sets the root for this thor class. Relatives path are added to the
+ # directory where the script was invoked and expanded.
+ #
+ def destination_root=(root)
+ @destination_stack ||= []
+ @destination_stack[0] = File.expand_path(root || '')
+ end
+
+ # Returns the given path relative to the absolute root (ie, root where
+ # the script started).
+ #
+ def relative_to_original_destination_root(path, remove_dot=true)
+ path = path.gsub(@destination_stack[0], '.')
+ remove_dot ? (path[2..-1] || '') : path
+ end
+
+ # Holds source paths in instance so they can be manipulated.
+ #
+ 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.
+ #
+ def find_in_source_paths(file)
+ relative_root = relative_to_original_destination_root(destination_root, false)
+
+ source_paths.each do |source|
+ source_file = File.expand_path(file, File.join(source, relative_root))
+ return source_file if File.exists?(source_file)
+ end
+
+ if source_paths.empty?
+ raise Error, "You don't have any source path defined for class #{self.class.name}. To fix this, " <<
+ "you can define a source_root in your class."
+ else
+ raise Error, "Could not find #{file.inspect} in source paths."
+ end
+ end
+
+ # Do something in the root or on a provided subfolder. If a relative path
+ # is given it's referenced from the current root. The full path is yielded
+ # to the block you provide. The path is set back to the previous path when
+ # the method exits.
+ #
+ # ==== Parameters
+ # dir<String>:: the directory to move to.
+ # config<Hash>:: give :verbose => true to log and use padding.
+ #
+ def inside(dir='', config={}, &block)
+ verbose = config.fetch(:verbose, false)
+
+ say_status :inside, dir, verbose
+ shell.padding += 1 if verbose
+ @destination_stack.push File.expand_path(dir, destination_root)
+
+ FileUtils.mkdir_p(destination_root) unless File.exist?(destination_root)
+ FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
+
+ @destination_stack.pop
+ shell.padding -= 1 if verbose
+ end
+
+ # Goes to the root and execute the given block.
+ #
+ def in_root
+ inside(@destination_stack.first) { yield }
+ end
+
+ # Loads an external file and execute it in the instance binding.
+ #
+ # ==== Parameters
+ # path<String>:: The path to the file to execute. Can be a web address or
+ # a relative path from the source root.
+ #
+ # ==== Examples
+ #
+ # apply "http://gist.github.com/103208"
+ #
+ # apply "recipes/jquery.rb"
+ #
+ def apply(path, config={})
+ verbose = config.fetch(:verbose, true)
+ path = find_in_source_paths(path) unless path =~ /^http\:\/\//
+
+ say_status :apply, path, verbose
+ shell.padding += 1 if verbose
+ instance_eval(open(path).read)
+ shell.padding -= 1 if verbose
+ end
+
+ # Executes a command.
+ #
+ # ==== Parameters
+ # command<String>:: the command to be executed.
+ # config<Hash>:: give :verbose => false to not log the status. Specify :with
+ # to append an executable to command executation.
+ #
+ # ==== Example
+ #
+ # inside('vendor') do
+ # run('ln -s ~/edge rails')
+ # end
+ #
+ def run(command, config={})
+ return unless behavior == :invoke
+
+ destination = relative_to_original_destination_root(destination_root, false)
+ desc = "#{command} from #{destination.inspect}"
+
+ if config[:with]
+ desc = "#{File.basename(config[:with].to_s)} #{desc}"
+ command = "#{config[:with]} #{command}"
+ end
+
+ say_status :run, desc, config.fetch(:verbose, true)
+ system(command) unless options[:pretend]
+ end
+
+ # Executes a ruby script (taking into account WIN32 platform quirks).
+ #
+ # ==== Parameters
+ # command<String>:: the command to be executed.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ def run_ruby_script(command, config={})
+ return unless behavior == :invoke
+ 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
+ # switches.
+ #
+ # ==== Parameters
+ # task<String>:: the task to be invoked
+ # args<Array>:: arguments to the task
+ # config<Hash>:: give :verbose => false to not log the status. Other options
+ # are given as parameter to Thor.
+ #
+ # ==== Examples
+ #
+ # thor :install, "http://gist.github.com/103208"
+ # #=> thor install http://gist.github.com/103208
+ #
+ # thor :list, :all => true, :substring => 'rails'
+ # #=> thor list --all --substring=rails
+ #
+ def thor(task, *args)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ verbose = config.key?(:verbose) ? config.delete(:verbose) : true
+
+ args.unshift task
+ args.push Thor::Options.to_switches(config)
+ command = args.join(' ').strip
+
+ run command, :with => :thor, :verbose => verbose
+ end
+
+ protected
+
+ # Allow current root to be shared between invocations.
+ #
+ def _shared_configuration #:nodoc:
+ super.merge!(:destination_root => self.destination_root)
+ end
+
+ def _cleanup_options_and_set(options, key) #:nodoc:
+ case options
+ when Array
+ %w(--force -f --skip -s).each { |i| options.delete(i) }
+ options << "--#{key}"
+ when Hash
+ [:force, :skip, "force", "skip"].each { |i| options.delete(i) }
+ options.merge!(key => true)
+ end
+ end
+
+ end
+end
View
103 lib/bubble/vendor/thor/actions/create_file.rb
@@ -0,0 +1,103 @@
+require 'thor/actions/empty_directory'
+
+class Thor
+ module Actions
+
+ # Create a new file relative to the destination root with the given data,
+ # which is the return value of a block or a data string.
+ #
+ # ==== Parameters
+ # destination<String>:: the relative path to the destination root.
+ # data<String|NilClass>:: the data to append to the file.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # create_file "lib/fun_party.rb" do
+ # hostname = ask("What is the virtual hostname I should use?")
+ # "vhost.name = #{hostname}"
+ # end
+ #
+ # create_file "config/apach.conf", "your apache config"
+ #
+ def create_file(destination, data=nil, config={}, &block)
+ action CreateFile.new(self, destination, block || data.to_s, config)
+ end
+ alias :add_file :create_file
+
+ # AddFile is a subset of Template, which instead of rendering a file with
+ # ERB, it gets the content from the user.
+ #
+ class CreateFile < EmptyDirectory #:nodoc:
+ attr_reader :data
+
+ def initialize(base, destination, data, config={})
+ @data = data
+ super(base, destination, config)
+ end
+
+ # Checks if the content of the file at the destination is identical to the rendered result.
+ #
+ # ==== Returns
+ # Boolean:: true if it is identical, false otherwise.
+ #
+ def identical?
+ exists? && File.binread(destination) == render
+ end
+
+ # Holds the content to be added to the file.
+ #
+ def render
+ @render ||= if data.is_a?(Proc)
+ data.call
+ else
+ data
+ end
+ end
+
+ def invoke!
+ invoke_with_conflict_check do
+ FileUtils.mkdir_p(File.dirname(destination))
+ File.open(destination, 'wb') { |f| f.write render }
+ end
+ given_destination
+ end
+
+ protected
+
+ # Now on conflict we check if the file is identical or not.
+ #
+ def on_conflict_behavior(&block)
+ if identical?
+ say_status :identical, :blue
+ else
+ options = base.options.merge(config)
+ force_or_skip_or_conflict(options[:force], options[:skip], &block)
+ end
+ end
+
+ # If force is true, run the action, otherwise check if it's not being
+ # skipped. If both are false, show the file_collision menu, if the menu
+ # returns true, force it, otherwise skip.
+ #
+ def force_or_skip_or_conflict(force, skip, &block)
+ if force
+ say_status :force, :yellow
+ block.call unless pretend?
+ elsif skip
+ say_status :skip, :yellow
+ else
+ say_status :conflict, :red
+ force_or_skip_or_conflict(force_on_collision?, true, &block)
+ end
+ end
+
+ # Shows the file collision menu to the user and gets the result.
+ #
+ def force_on_collision?
+ base.shell.file_collision(destination){ render }
+ end
+
+ end
+ end
+end
View
91 lib/bubble/vendor/thor/actions/directory.rb
@@ -0,0 +1,91 @@
+require 'thor/actions/empty_directory'
+
+class Thor
+ module Actions
+
+ # Copies recursively the files from source directory to root directory.
+ # If any of the files finishes with .tt, it's considered to be a template
+ # and is placed in the destination without the extension .tt. If any
+ # empty directory is found, it's copied and all .empty_directory files are
+ # ignored. Remember that file paths can also be encoded, let's suppose a doc
+ # directory with the following files:
+ #
+ # doc/
+ # components/.empty_directory
+ # README
+ # rdoc.rb.tt
+ # %app_name%.rb
+ #
+ # When invoked as:
+ #
+ # directory "doc"
+ #
+ # It will create a doc directory in the destination with the following
+ # files (assuming that the app_name is "blog"):
+ #
+ # doc/
+ # components/
+ # README
+ # rdoc.rb
+ # blog.rb
+ #
+ # ==== Parameters
+ # source<String>:: the relative path to the source root.
+ # destination<String>:: the relative path to the destination root.
+ # config<Hash>:: give :verbose => false to not log the status.
+ # If :recursive => false, does not look for paths recursively.
+ #
+ # ==== Examples
+ #
+ # directory "doc"
+ # directory "doc", "docs", :recursive => false
+ #
+ def directory(source, destination=nil, config={}, &block)
+ action Directory.new(self, source, destination || source, config, &block)
+ end
+
+ class Directory < EmptyDirectory #:nodoc:
+ attr_reader :source
+
+ def initialize(base, source, destination=nil, config={}, &block)
+ @source = File.expand_path(base.find_in_source_paths(source.to_s))
+ @block = block
+ super(base, destination, { :recursive => true }.merge(config))
+ end
+
+ def invoke!
+ base.empty_directory given_destination, config
+ execute!
+ end
+
+ def revoke!
+ execute!
+ end
+
+ protected
+
+ def execute!
+ lookup = config[:recursive] ? File.join(source, '**') : source
+ lookup = File.join(lookup, '{*,.[a-z]*}')
+
+ Dir[lookup].each do |file_source|
+ next if File.directory?(file_source)
+ file_destination = File.join(given_destination, file_source.gsub(source, '.'))
+ file_destination.gsub!('/./', '/')
+
+ case file_source
+ when /\.empty_directory$/
+ dirname = File.dirname(file_destination).gsub(/\/\.$/, '')
+ next if dirname == given_destination
+ base.empty_directory(dirname, config)
+ when /\.tt$/
+ destination = base.template(file_source, file_destination[0..-4], config, &@block)
+ else
+ destination = base.copy_file(file_source, file_destination, config, &@block)
+ end
+ end
+ end
+
+ end
+ end
+end
View
134 lib/bubble/vendor/thor/actions/empty_directory.rb
@@ -0,0 +1,134 @@
+class Thor
+ module Actions
+
+ # Creates an empty directory.
+ #
+ # ==== Parameters
+ # destination<String>:: the relative path to the destination root.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # empty_directory "doc"
+ #
+ def empty_directory(destination, config={})
+ action EmptyDirectory.new(self, destination, config)
+ end
+
+ # Class which holds create directory logic. This is the base class for
+ # other actions like create_file and directory.
+ #
+ # This implementation is based in Templater actions, created by Jonas Nicklas
+ # and Michael S. Klishin under MIT LICENSE.
+ #
+ class EmptyDirectory #:nodoc:
+ attr_reader :base, :destination, :given_destination, :relative_destination, :config
+
+ # Initializes given the source and destination.
+ #
+ # ==== Parameters
+ # base<Thor::Base>:: A Thor::Base instance
+ # source<String>:: Relative path to the source of this file
+ # destination<String>:: Relative path to the destination of this file
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ def initialize(base, destination, config={})
+ @base, @config = base, { :verbose => true }.merge(config)
+ self.destination = destination
+ end
+
+ # Checks if the destination file already exists.
+ #
+ # ==== Returns
+ # Boolean:: true if the file exists, false otherwise.
+ #
+ def exists?
+ ::File.exists?(destination)
+ end
+
+ def invoke!
+ invoke_with_conflict_check do
+ ::FileUtils.mkdir_p(destination)
+ end
+ end
+
+ def revoke!
+ say_status :remove, :red
+ ::FileUtils.rm_rf(destination) if !pretend? && exists?
+ given_destination
+ end
+
+ protected
+
+ # Shortcut for pretend.
+ #
+ def pretend?
+ base.options[:pretend]
+ end
+
+ # Sets the absolute destination value from a relative destination value.
+ # It also stores the given and relative destination. Let's suppose our
+ # script is being executed on "dest", it sets the destination root to
+ # "dest". The destination, given_destination and relative_destination
+ # are related in the following way:
+ #
+ # inside "bar" do
+ # empty_directory "baz"
+ # end
+ #
+ # destination #=> dest/bar/baz
+ # relative_destination #=> bar/baz
+ # given_destination #=> baz
+ #
+ def destination=(destination)
+ if destination
+ @given_destination = convert_encoded_instructions(destination.to_s)
+ @destination = ::File.expand_path(@given_destination, base.destination_root)
+ @relative_destination = base.relative_to_original_destination_root(@destination)
+ end
+ end
+
+ # Filenames in the encoded form are converted. If you have a file:
+ #
+ # %class_name%.rb
+ #
+ # It gets the class name from the base and replace it:
+ #
+ # user.rb
+ #
+ def convert_encoded_instructions(filename)
+ filename.gsub(/%(.*?)%/) do |string|
+ instruction = $1.strip
+ base.respond_to?(instruction) ? base.send(instruction) : string
+ end
+ end
+
+ # Receives a hash of options and just execute the block if some
+ # conditions are met.
+ #
+ def invoke_with_conflict_check(&block)
+ if exists?
+ on_conflict_behavior(&block)
+ else
+ say_status :create, :green
+ block.call unless pretend?
+ end
+
+ destination
+ end
+
+ # What to do when the destination file already exists.
+ #
+ def on_conflict_behavior(&block)
+ say_status :exist, :blue
+ end
+
+ # Shortcut to say_status shell method.
+ #
+ def say_status(status, color)
+ base.shell.say_status status, relative_destination, color if config[:verbose]
+ end
+
+ end
+ end
+end
View
223 lib/bubble/vendor/thor/actions/file_manipulation.rb
@@ -0,0 +1,223 @@
+require 'erb'
+require 'open-uri'
+
+class Thor
+ module Actions
+
+ # Copies the file from the relative source to the relative destination. If
+ # the destination is not given it's assumed to be equal to the source.
+ #
+ # ==== Parameters
+ # source<String>:: the relative path to the source root.
+ # destination<String>:: the relative path to the destination root.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # copy_file "README", "doc/README"
+ #
+ # copy_file "doc/README"
+ #
+ def copy_file(source, destination=nil, config={}, &block)
+ destination ||= source
+ source = File.expand_path(find_in_source_paths(source.to_s))
+
+ create_file destination, nil, config do
+ content = File.binread(source)
+ content = block.call(content) if block
+ content
+ end
+ end
+
+ # Gets the content at the given address and places it at the given relative
+ # destination. If a block is given instead of destination, the content of
+ # the url is yielded and used as location.
+ #
+ # ==== Parameters
+ # source<String>:: the address of the given content.
+ # destination<String>:: the relative path to the destination root.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # get "http://gist.github.com/103208", "doc/README"
+ #
+ # get "http://gist.github.com/103208" do |content|
+ # content.split("\n").first
+ # end
+ #
+ def get(source, destination=nil, config={}, &block)
+ source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^http\:\/\//
+ render = File.binread(source)
+
+ destination ||= if block_given?
+ block.arity == 1 ? block.call(render) : block.call
+ else
+ File.basename(source)
+ end
+
+ create_file destination, render, config
+ end
+
+ # Gets an ERB template at the relative source, executes it and makes a copy
+ # at the relative destination. If the destination is not given it's assumed
+ # to be equal to the source removing .tt from the filename.
+ #
+ # ==== Parameters
+ # source<String>:: the relative path to the source root.
+ # destination<String>:: the relative path to the destination root.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # template "README", "doc/README"
+ #
+ # template "doc/README"
+ #
+ def template(source, destination=nil, config={}, &block)
+ destination ||= source
+ source = File.expand_path(find_in_source_paths(source.to_s))
+ context = instance_eval('binding')
+
+ create_file destination, nil, config do
+ content = ERB.new(::File.binread(source), nil, '-').result(context)
+ content = block.call(content) if block
+ content
+ end
+ end
+
+ # Changes the mode of the given file or directory.
+ #
+ # ==== Parameters
+ # mode<Integer>:: the file mode
+ # path<String>:: the name of the file to change mode
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Example
+ #
+ # chmod "script/*", 0755
+ #
+ def chmod(path, mode, config={})
+ return unless behavior == :invoke
+ path = File.expand_path(path, destination_root)
+ say_status :chmod, relative_to_original_destination_root(path), config.fetch(:verbose, true)
+ FileUtils.chmod_R(mode, path) unless options[:pretend]
+ end
+
+ # Prepend text to a file. Since it depends on inject_into_file, it's reversible.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # data<String>:: the data to prepend to the file, can be also given as a block.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Example
+ #
+ # prepend_file 'config/environments/test.rb', 'config.gem "rspec"'
+ #
+ # prepend_file 'config/environments/test.rb' do
+ # 'config.gem "rspec"'
+ # end
+ #
+ def prepend_file(path, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ config.merge!(:after => /\A/)
+ inject_into_file(path, *(args << config), &block)
+ end
+
+ # Append text to a file. Since it depends on inject_into_file, it's reversible.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # data<String>:: the data to append to the file, can be also given as a block.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Example
+ #
+ # append_file 'config/environments/test.rb', 'config.gem "rspec"'
+ #
+ # append_file 'config/environments/test.rb' do
+ # 'config.gem "rspec"'
+ # end
+ #
+ def append_file(path, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ config.merge!(:before => /\z/)
+ inject_into_file(path, *(args << config), &block)
+ end
+
+ # Injects text right after the class definition. Since it depends on
+ # inject_into_file, it's reversible.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # klass<String|Class>:: the class to be manipulated
+ # data<String>:: the data to append to the class, can be also given as a block.
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Examples
+ #
+ # inject_into_class "app/controllers/application_controller.rb", " filter_parameter :password\n"
+ #
+ # inject_into_class "app/controllers/application_controller.rb", ApplicationController do
+ # " filter_parameter :password\n"
+ # end
+ #
+ def inject_into_class(path, klass, *args, &block)
+ config = args.last.is_a?(Hash) ? args.pop : {}
+ config.merge!(:after => /class #{klass}\n|class #{klass} .*\n/)
+ inject_into_file(path, *(args << config), &block)
+ end
+
+ # Run a regular expression replacement on a file.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # flag<Regexp|String>:: the regexp or string to be replaced
+ # replacement<String>:: the replacement, can be also given as a block
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Example
+ #
+ # gsub_file 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1'
+ #
+ # gsub_file 'README', /rake/, :green do |match|
+ # match << " no more. Use thor!"
+ # end
+ #
+ def gsub_file(path, flag, *args, &block)
+ return unless behavior == :invoke
+ config = args.last.is_a?(Hash) ? args.pop : {}
+
+ path = File.expand_path(path, destination_root)
+ say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true)
+
+ unless options[:pretend]
+ content = File.binread(path)
+ content.gsub!(flag, *args, &block)
+ File.open(path, 'wb') { |file| file.write(content) }
+ end
+ end
+
+ # Removes a file at the given location.
+ #
+ # ==== Parameters
+ # path<String>:: path of the file to be changed
+ # config<Hash>:: give :verbose => false to not log the status.
+ #
+ # ==== Example
+ #
+ # remove_file 'README'
+ # remove_file 'app/controllers/application_controller.rb'
+ #
+ def remove_file(path, config={})
+ return unless behavior == :invoke
+ path = File.expand_path(path, destination_root)
+
+ say_status :remove, relative_to_original_destination_root(path), config.fetch(:verbose, true)
+ ::FileUtils.rm_rf(path) if !options[:pretend] && File.exists?(path)
+ end
+ alias :remove_dir :remove_file
+
+ end
+end
View
101 lib/bubble/vendor/thor/actions/inject_into_file.rb
@@ -0,0 +1,101 @@
+require 'thor/actions/empty_directory'
+
+class Thor
+ module Actions
+
+ # Injects the given content into a file. Different from gsub_file, this
+ # method is reversible.
+ #
+ # ==== Parameters
+ # destination<String>:: Relative path to the destination root
+ # 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"
+ #
+ # inject_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do
+ # gems = ask "Which gems would you like to add?"
+ # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
+ # end
+ #
+ def inject_into_file(destination, *args, &block)
+ if block_given?
+ data, config = block, args.shift
+ else
+ data, config = args.shift, args.shift
+ end
+ action InjectIntoFile.new(self, destination, data, config)
+ end
+
+ class InjectIntoFile < EmptyDirectory #:nodoc:
+ attr_reader :replacement, :flag, :behavior
+
+ def initialize(base, destination, data, config)
+ super(base, destination, { :verbose => true }.merge(config))
+
+ @behavior, @flag = if @config.key?(:after)
+ [:after, @config.delete(:after)]
+ else
+ [:before, @config.delete(:before)]
+ end
+
+ @replacement = data.is_a?(Proc) ? data.call : data
+ @flag = Regexp.escape(@flag) unless @flag.is_a?(Regexp)
+ end
+
+ def invoke!
+ say_status :invoke
+
+ content = if @behavior == :after
+ '\0' + replacement
+ else
+ replacement + '\0'
+ end
+
+ replace!(/#{flag}/, content)
+ end
+
+ def revoke!
+ say_status :revoke
+
+ regexp = if @behavior == :after
+ content = '\1\2'
+ /(#{flag})(.*)(#{Regexp.escape(replacement)})/m
+ else
+ content = '\2\3'
+ /(#{Regexp.escape(replacement)})(.*)(#{flag})/m
+ end
+
+ replace!(regexp, content)
+ end
+
+ protected
+
+ def say_status(behavior)
+ status = if flag == /\A/
+ behavior == :invoke ? :prepend : :unprepend
+ elsif flag == /\z/
+ behavior == :invoke ? :append : :unappend
+ else
+ behavior == :invoke ? :inject : :deinject
+ end
+
+ super(status, config[:verbose])
+ end
+
+ # Adds the content to the file.
+ #
+ def replace!(regexp, string)
+ unless base.options[:pretend]
+ content = File.binread(destination)
+ content.gsub!(regexp, string)
+ File.open(destination, 'wb') { |file| file.write(content) }
+ end
+ end
+
+ end
+ end
+end
View
515 lib/bubble/vendor/thor/base.rb
@@ -0,0 +1,515 @@
+require 'thor/core_ext/hash_with_indifferent_access'
+require 'thor/core_ext/ordered_hash'
+require 'thor/error'
+require 'thor/shell'
+require 'thor/invocation'
+require 'thor/parser'
+require 'thor/task'
+require 'thor/util'
+
+class Thor
+ # Shortcuts for help.
+ HELP_MAPPINGS = %w(-h -? --help -D)
+
+ # Thor methods that should not be overwritten by the user.
+ THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root
+ action add_file create_file in_root inside run run_ruby_script)
+
+ module Base
+ attr_accessor :options
+
+ # It receives arguments in an Array and two hashes, one for options and
+ # other for configuration.
+ #
+ # Notice that it does not check if all required arguments were supplied.
+ # It should be done by the parser.
+ #
+ # ==== Parameters
+ # args<Array[Object]>:: An array of objects. The objects are applied to their
+ # respective accessors declared with <tt>argument</tt>.
+ #
+ # options<Hash>:: An options hash that will be available as self.options.
+ # The hash given is converted to a hash with indifferent
+ # access, magic predicates (options.skip?) and then frozen.
+ #
+ # config<Hash>:: Configuration for this Thor class.
+ #
+ def initialize(args=[], options={}, config={})
+ Thor::Arguments.parse(self.class.arguments, args).each do |key, value|
+ send("#{key}=", value)
+ end
+
+ parse_options = self.class.class_options
+
+ if options.is_a?(Array)
+ task_options = config.delete(:task_options) # hook for start
+ parse_options = parse_options.merge(task_options) if task_options
+ array_options, hash_options = options, {}
+ else
+ array_options, hash_options = [], options
+ end
+
+ options = Thor::Options.parse(parse_options, array_options)
+ self.options = Thor::CoreExt::HashWithIndifferentAccess.new(options).merge!(hash_options)
+ self.options.freeze
+ end
+
+ class << self
+ def included(base) #:nodoc:
+ base.send :extend, ClassMethods
+ base.send :include, Invocation
+ base.send :include, Shell
+ end
+
+ # Returns the classes that inherits from Thor or Thor::Group.
+ #
+ # ==== Returns
+ # Array[Class]
+ #
+ def subclasses
+ @subclasses ||= []
+ end
+
+ # Returns the files where the subclasses are kept.
+ #
+ # ==== Returns
+ # Hash[path<String> => Class]
+ #
+ def subclass_files
+ @subclass_files ||= Hash.new{ |h,k| h[k] = [] }
+ end
+
+ # Whenever a class inherits from Thor or Thor::Group, we should track the
+ # class and the file on Thor::Base. This is the method responsable for it.
+ #
+ def register_klass_file(klass) #:nodoc:
+ file = caller[1].match(/(.*):\d+/)[1]
+ Thor::Base.subclasses << klass unless Thor::Base.subclasses.include?(klass)
+
+ file_subclasses = Thor::Base.subclass_files[File.expand_path(file)]
+ file_subclasses << klass unless file_subclasses.include?(klass)
+ end
+ 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
+ # is how they are parsed from the command line, arguments are retrieved
+ # from position:
+ #
+ # thor task NAME
+ #
+ # Instead of:
+ #
+ # thor task --name=NAME
+ #
+ # Besides, arguments are used inside your code as an accessor (self.argument),
+ # while options are all kept in a hash (self.options).
+ #
+ # Finally, arguments cannot have type :default or :boolean but can be
+ # optional (supplying :optional => :true or :required => false), although
+ # you cannot have a required argument after a non-required argument. If you
+ # try it, an error is raised.
+ #
+ # ==== Parameters
+ # name<Symbol>:: The name of the argument.
+ # options<Hash>:: Described below.
+ #
+ # ==== Options
+ # :desc - Description for the argument.
+ # :required - If the argument is required or not.
+ # :optional - If the argument is optional or not.
+ # :type - The type of the argument, can be :string, :hash, :array, :numeric.
+ # :default - Default value for this argument. It cannot be required and have default values.
+ # :banner - String to show on usage notes.
+ #
+ # ==== Errors
+ # ArgumentError:: Raised if you supply a required argument after a non required one.
+ #
+ def argument(name, options={})
+ is_thor_reserved_word?(name, :argument)
+ no_tasks { attr_accessor name }
+
+ required = if options.key?(:optional)
+ !options[:optional]
+ elsif options.key?(:required)
+ options[:required]
+ else
+ options[:default].nil?
+ end
+
+ remove_argument name
+
+ arguments.each do |argument|
+ next if argument.required?
+ raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " <<
+ "the non-required argument #{argument.human_name.inspect}."
+ end if required
+
+ arguments << Thor::Argument.new(name, options[:desc], required, options[:type],
+ options[:default], options[:banner])
+ end
+
+ # Returns this class arguments, looking up in the ancestors chain.
+ #
+ # ==== Returns
+ # Array[Thor::Argument]
+ #
+ def arguments
+ @arguments ||= from_superclass(:arguments, [])
+ end
+
+ # Adds a bunch of options to the set of class options.
+ #
+ # class_options :foo => false, :bar => :required, :baz => :string
+ #
+ # If you prefer more detailed declaration, check class_option.
+ #
+ # ==== Parameters
+ # Hash[Symbol => Object]
+ #
+ def class_options(options=nil)
+ @class_options ||= from_superclass(:class_options, {})
+ build_options(options, @class_options) if options
+ @class_options
+ end
+
+ # Adds an option to the set of class options
+ #
+ # ==== Parameters
+ # name<Symbol>:: The name of the argument.
+ # options<Hash>:: Described below.
+ #
+ # ==== Options
+ # :desc - Description for the argument.
+ # :required - If the argument is required or not.
+ # :default - Default value for this argument.
+ # :group - The group for this options. Use by class options to output options in different levels.
+ # :aliases - Aliases for this option.
+ # :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
+ # :banner - String to show on usage notes.
+ #
+ def class_option(name, options={})
+ build_option(name, options, class_options)
+ end
+
+ # Removes a previous defined argument. If :undefine is given, undefine
+ # accessors as well.
+ #
+ # ==== Paremeters
+ # names<Array>:: Arguments to be removed
+ #
+ # ==== Examples
+ #
+ # remove_argument :foo
+ # remove_argument :foo, :bar, :baz, :undefine => true
+ #
+ def remove_argument(*names)
+ options = names.last.is_a?(Hash) ? names.pop : {}
+
+ names.each do |name|
+ arguments.delete_if { |a| a.name == name.to_s }
+ undef_method name, "#{name}=" if options[:undefine]
+ end
+ end
+
+ # Removes a previous defined class option.
+ #
+ # ==== Paremeters
+ # names<Array>:: Class options to be removed
+ #
+ # ==== Examples
+ #
+ # remove_class_option :foo
+ # remove_class_option :foo, :bar, :baz
+ #
+ def remove_class_option(*names)
+ names.each do |name|
+ class_options.delete(name)
+ end
+ end
+
+ # Defines the group. This is used when thor list is invoked so you can specify
+ # that only tasks from a pre-defined group will be shown. Defaults to standard.
+ #
+ # ==== Parameters
+ # name<String|Symbol>
+ #
+ def group(name=nil)
+ case name
+ when nil
+ @group ||= from_superclass(:group, 'standard')
+ else
+ @group = name.to_s
+ end
+ end
+
+ # Returns the tasks for this Thor class.
+ #
+ # ==== Returns
+ # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task
+ # objects as values.
+ #
+ def tasks
+ @tasks ||= Thor::CoreExt::OrderedHash.new
+ end
+
+ # Returns the tasks for this Thor class and all subclasses.
+ #
+ # ==== Returns
+ # OrderedHash:: An ordered hash with tasks names as keys and Thor::Task
+ # objects as values.
+ #
+ def all_tasks
+ @all_tasks ||= from_superclass(:all_tasks, Thor::CoreExt::OrderedHash.new)
+ @all_tasks.merge(tasks)
+ end
+
+ # Removes a given task from this Thor class. This is usually done if you
+ # are inheriting from another class and don't want it to be available
+ # anymore.
+ #
+ # By default it only remove the mapping to the task. But you can supply
+ # :undefine => true to undefine the method from the class as well.
+ #
+ # ==== Parameters
+ # name<Symbol|String>:: The name of the task to be removed
+ # options<Hash>:: You can give :undefine => true if you want tasks the method
+ # to be undefined from the class as well.
+ #
+ def remove_task(*names)
+ options = names.last.is_a?(Hash) ? names.pop : {}
+
+ names.each do |name|
+ tasks.delete(name.to_s)
+ all_tasks.delete(name.to_s)
+ undef_method name if options[:undefine]
+ end
+ end
+
+ # All methods defined inside the given block are not added as tasks.
+ #
+ # So you can do:
+ #
+ # class MyScript < Thor
+ # no_tasks do
+ # def this_is_not_a_task
+ # end
+ # end
+ # end
+ #
+ # You can also add the method and remove it from the task list:
+ #
+ # class MyScript < Thor
+ # def this_is_not_a_task
+ # end
+ # remove_task :this_is_not_a_task
+ # end
+ #
+ def no_tasks
+ @no_tasks = true
+ yield
+ @no_tasks = false
+ end
+
+ # Sets the namespace for the Thor or Thor::Group class. By default the
+ # namespace is retrieved from the class name. If your Thor class is named
+ # Scripts::MyScript, the help method, for example, will be called as:
+ #
+ # thor scripts:my_script -h
+ #
+ # If you change the namespace:
+ #
+ # namespace :my_scripts
+ #
+ # You change how your tasks are invoked:
+ #
+ # thor my_scripts -h
+ #
+ # Finally, if you change your namespace to default:
+ #
+ # namespace :default
+ #
+ # Your tasks can be invoked with a shortcut. Instead of:
+ #
+ # thor :my_task
+ #
+ def namespace(name=nil)
+ case name
+ when nil
+ @namespace ||= Thor::Util.namespace_from_thor_class(self, false)
+ else
+ @namespace = name.to_s
+ end
+ end
+
+ # 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 debugging
+ raise e
+ else
+ config[:shell].error e.message
+ end
+ exit(1) if exit_on_failure?
+ end
+
+ protected
+
+ # Prints the class options per group. If an option does not belong to
+ # any group, it's printed as Class option.
+ #
+ def class_options_help(shell, groups={}) #:nodoc:
+ # Group options by group
+ class_options.each do |_, value|
+ groups[value.group] ||= []
+ groups[value.group] << value
+ end
+
+ # 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?
+
+ list = []
+ padding = options.collect{ |o| o.aliases.size }.max.to_i * 4
+
+ options.each do |option|
+ item = [ option.usage(padding) ]
+ item.push(option.description ? "# #{option.description}" : "")
+
+ list << item
+ list << [ "", "# Default: #{option.default}" ] if option.show_default?
+ end
+
+ 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.
+ #
+ def is_thor_reserved_word?(word, type) #:nodoc:
+ return false unless THOR_RESERVED_WORDS.include?(word.to_s)
+ raise "#{word.inspect} is a Thor reserved word and cannot be defined as #{type}"
+ end
+
+ # Build an option and adds it to the given scope.
+ #
+ # ==== Parameters
+ # name<Symbol>:: The name of the argument.
+ # options<Hash>:: Described in both class_option and method_option.
+ #
+ def build_option(name, options, scope) #:nodoc:
+ scope[name] = Thor::Option.new(name, options[:desc], options[:required],
+ options[:type], options[:default], options[:banner],
+ options[:group], options[:aliases])
+ end
+
+ # Receives a hash of options, parse them and add to the scope. This is a
+ # fast way to set a bunch of options:
+ #
+ # build_options :foo => true, :bar => :required, :baz => :string
+ #
+ # ==== Parameters
+ # Hash[Symbol => Object]
+ #
+ def build_options(options, scope) #:nodoc:
+ options.each do |key, value|
+ scope[key] = Thor::Option.parse(key, value)
+ end
+ end
+
+ # Finds a task with the given name. If the task belongs to the current
+ # class, just return it, otherwise dup it and add the fresh copy to the
+ # current task hash.
+ #
+ def find_and_refresh_task(name) #:nodoc:
+ task = if task = tasks[name.to_s]
+ task
+ elsif task = all_tasks[name.to_s]
+ tasks[name.to_s] = task.clone
+ else
+ raise ArgumentError, "You supplied :for => #{name.inspect}, but the task #{name.inspect} could not be found."
+ end
+ end
+
+ # Everytime someone inherits from a Thor class, register the klass
+ # and file into baseclass.
+ #
+ def inherited(klass)
+ Thor::Base.register_klass_file(klass)
+ end
+
+ # Fire this callback whenever a method is added. Added methods are
+ # tracked as tasks by invoking the create_task method.
+ #
+ def method_added(meth)
+ meth = meth.to_s
+
+ if meth == "initialize"
+ initialize_added
+ return
+ end
+
+ # Return if it's not a public instance method
+ return unless public_instance_methods.include?(meth) ||
+ public_instance_methods.include?(meth.to_sym)
+
+ return if @no_tasks || !create_task(meth)
+
+ is_thor_reserved_word?(meth, :task)
+ Thor::Base.register_klass_file(self)
+ end
+
+ # Retrieves a value from superclass. If it reaches the baseclass,
+ # returns default.
+ #
+ def from_superclass(method, default=nil)
+ if self == baseclass || !superclass.respond_to?(method, true)
+ default
+ else
+ value = superclass.send(method)
+ value.dup if value
+ end
+ end
+
+ # A flag that makes the process exit with status 1 if any error happens.
+ #
+ def exit_on_failure?
+ false
+ end
+
+ # SIGNATURE: Sets the baseclass. This is where the superclass lookup
+ # finishes.
+ def baseclass #:nodoc:
+ end
+
+ # SIGNATURE: Creates a new task if valid_task? is true. This method is
+ # called when a new method is added to the class.
+ def create_task(meth) #:nodoc:
+ end
+
+ # SIGNATURE: Defines behavior when the initialize method is added to the
+ # class.
+ def initialize_added #:nodoc:
+ end
+ end
+ end
+end
View
9 lib/bubble/vendor/thor/core_ext/file_binary_read.rb
@@ -0,0 +1,9 @@
+class File #:nodoc:
+
+ unless File.respond_to?(:binread)
+ def self.binread(file)
+ File.open(file, 'rb') { |f| f.read }
+ end
+ end
+
+end
View
75 lib/bubble/vendor/thor/core_ext/hash_with_indifferent_access.rb
@@ -0,0 +1,75 @@
+class Thor
+ module CoreExt #:nodoc:
+
+ # A hash with indifferent access and magic predicates.
+ #
+ # hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
+ #
+ # hash[:foo] #=> 'bar'
+ # hash['foo'] #=> 'bar'
+ # hash.foo? #=> true
+ #
+ class HashWithIndifferentAccess < ::Hash #:nodoc:
+
+ def initialize(hash={})
+ super()
+ hash.each do |key, value|
+ self[convert_key(key)] = value
+ end
+ end
+
+ def [](key)
+ super(convert_key(key))
+ end
+
+ def []=(key, value)
+ super(convert_key(key), value)
+ end
+
+ def delete(key)
+ super(convert_key(key))
+ end
+
+ def values_at(*indices)
+ indices.collect { |key| self[convert_key(key)] }
+ end
+
+ def merge(other)
+ dup.merge!(other)
+ end
+
+ def merge!(other)
+ other.each do |key, value|
+ self[convert_key(key)] = value
+ end
+ self
+ end
+
+ protected
+
+ def convert_key(key)
+ key.is_a?(Symbol) ? key.to_s : key
+ end
+
+ # Magic predicates. For instance:
+ #
+ # options.force? # => !!options['force']
+ # options.shebang # => "/usr/lib/local/ruby"
+ # options.test_framework?(:rspec) # => options[:test_framework] == :rspec
+ #
+ def method_missing(method, *args, &block)
+ method = method.to_s
+ if method =~ /^(\w+)\?$/
+ if args.empty?
+ !!self[$1]
+ else
+ self[$1] == args.first
+ end
+ else
+ self[method]
+ end
+ end
+
+ end
+ end
+end
View
100 lib/bubble/vendor/thor/core_ext/ordered_hash.rb
@@ -0,0 +1,100 @@
+class Thor
+ module CoreExt #:nodoc:
+
+ if RUBY_VERSION >= '1.9'
+ class OrderedHash < ::Hash
+ end
+ else
+ # This class is based on the Ruby 1.9 ordered hashes.
+ #
+ # It keeps the semantics and most of the efficiency of normal hashes
+ # while also keeping track of the order in which elements were set.
+ #
+ class OrderedHash #:nodoc:
+ include Enumerable
+
+ Node = Struct.new(:key, :value, :next, :prev)
+
+ def initialize
+ @hash = {}
+ end
+
+ def [](key)
+ @hash[key] && @hash[key].value
+ end
+
+ def []=(key, value)
+ if node = @hash[key]
+ node.value = value
+ else
+ node = Node.new(key, value)
+
+ if @first.nil?
+ @first = @last = node
+ else
+ node.prev = @last
+ @last.next = node
+ @last = node
+ end
+ end
+
+ @hash[key] = node
+ value
+ end
+
+ def delete(key)
+ if node = @hash[key]
+ prev_node = node.prev
+ next_node = node.next
+
+ next_node.prev = prev_node if next_node
+ prev_node.next = next_node if prev_node
+
+ @first = next_node if @first == node
+ @last = prev_node if @last == node
+
+ value = node.value
+ end
+
+ @hash.delete(key)
+ value
+ end
+
+ def keys
+ self.map { |k, v| k }
+ end
+
+ def values
+ self.map { |k, v| v }
+ end
+
+ def each
+ return unless @first
+ yield [@first.key, @first.value]
+ node = @first
+ yield [node.key, node.value] while node = node.next
+ self
+ end
+
+ def merge(other)
+ hash = self.class.new
+
+ self.each do |key, value|
+ hash[key] = value
+ end
+
+ other.each do |key, value|
+ hash[key] = value
+ end
+
+ hash
+ end
+
+ def empty?
+ @hash.empty?
+ end
+ end
+ end
+
+ end
+end
View
27 lib/bubble/vendor/thor/error.rb
@@ -0,0 +1,27 @@
+class Thor
+ # Thor::Error is raised when it's caused by wrong usage of thor classes. Those
+ # errors have their backtrace supressed and are nicely shown to the user.
+ #
+ # Errors that are caused by the developer, like declaring a method which
+ # overwrites a thor keyword, it SHOULD NOT raise a Thor::Error. This way, we
+ # ensure that developer errors are shown with full backtrace.
+ #
+ class Error < StandardError
+ end
+
+ # Raised when a task was not found.
+ #
+ class UndefinedTaskError < Error
+ end
+
+ # Raised when a task was found, but not invoked properly.
+ #
+ class InvocationError < Error
+ end
+
+ class RequiredArgumentMissingError < InvocationError
+ end
+
+ class MalformattedArgumentError < InvocationError
+ end
+end
View
267 lib/bubble/vendor/thor/group.rb
@@ -0,0 +1,267 @@
+# Thor has a special class called Thor::Group. The main difference to Thor class
+# is that it invokes all tasks at once. It also include some methods that allows
+# invocations to be done at the class method, which are not available to Thor
+# tasks.
+#
+class Thor::Group
+ class << self
+ # The descrition for this Thor::Group. If none is provided, but a source root
+ # exists, tries to find the USAGE one folder above it, otherwise searches
+ # in the superclass.
+ #
+ # ==== Parameters
+ # description<String>:: The description for this Thor::Group.
+ #
+ def desc(description=nil)
+ case description
+ when nil
+ @desc ||= from_superclass(:desc, nil)
+ else
+ @desc = description
+ end
+ end
+
+ # Start works differently in Thor::Group, it simply invokes all tasks
+ # inside the class.
+ #
+ def start(given_args=ARGV, config={})
+ super do
+ if Thor::HELP_MAPPINGS.include?(given_args.first)
+ help(config[:shell])
+ return
+ end
+
+ args, opts = Thor::Options.split(given_args)
+ new(args, opts, config).invoke
+ end
+ end
+
+ # Prints help information.
+ #
+ # ==== Options
+ # short:: When true, shows only usage.
+ #
+ 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.
+ #
+ def invocations #:nodoc:
+ @invocations ||= from_superclass(:invocations, {})
+ end
+
+ # Stores invocation blocks used on invoke_from_option.
+ #
+ def invocation_blocks #:nodoc:
+ @invocation_blocks ||= from_superclass(:invocation_blocks, {})
+ end
+
+ # Invoke the given namespace or class given. It adds an instance
+ # method that will invoke the klass and task. You can give a block to
+ # configure how it will be invoked.
+ #
+ # The namespace/class given will have its options showed on the help
+ # usage. Check invoke_from_option for more information.
+ #
+ def invoke(*names, &block)
+ options = names.last.is_a?(Hash) ? names.pop : {}
+ verbose = options.fetch(:verbose, true)
+
+ names.each do |name|
+ invocations[name] = false
+ invocation_blocks[name] = block if block_given?
+
+ class_eval <<-METHOD, __FILE__, __LINE__
+ def _invoke_#{name.to_s.gsub(/\W/, '_')}
+ klass, task = self.class.prepare_for_invocation(nil, #{name.inspect})
+
+ if klass
+ say_status :invoke, #{name.inspect}, #{verbose.inspect}
+ block = self.class.invocation_blocks[#{name.inspect}]
+ _invoke_for_class_method klass, task, &block
+ else
+ say_status :error, %(#{name.inspect} [not found]), :red
+ end
+ end
+ METHOD
+ end
+ end
+
+ # Invoke a thor class based on the value supplied by the user to the
+ # given option named "name". A class option must be created before this
+ # method is invoked for each name given.
+ #
+ # ==== Examples
+ #
+ # class GemGenerator < Thor::Group
+ # class_option :test_framework, :type => :string
+ # invoke_from_option :test_framework
+ # end
+ #
+ # ==== Boolean options
+ #
+ # In some cases, you want to invoke a thor class if some option is true or
+ # false. This is automatically handled by invoke_from_option. Then the
+ # option name is used to invoke the generator.
+ #
+ # ==== Preparing for invocation
+ #
+ # In some cases you want to customize how a specified hook is going to be
+ # invoked. You can do that by overwriting the class method
+ # prepare_for_invocation. The class method must necessarily return a klass
+ # and an optional task.
+ #
+ # ==== Custom invocations
+ #
+ # You can also supply a block to customize how the option is giong to be
+ # invoked. The block receives two parameters, an instance of the current
+ # class and the klass to be invoked.
+ #
+ def invoke_from_option(*names, &block)
+ options = names.last.is_a?(Hash) ? names.pop : {}
+ verbose = options.fetch(:verbose, :white)
+
+ names.each do |name|
+ unless class_options.key?(name)
+ raise ArgumentError, "You have to define the option #{name.inspect} " <<
+ "before setting invoke_from_option."
+ end
+
+ invocations[name] = true
+ invocation_blocks[name] = block if block_given?
+
+ class_eval <<-METHOD, __FILE__, __LINE__
+ def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')}
+ return unless options[#{name.inspect}]
+
+ value = options[#{name.inspect}]
+ value = #{name.inspect} if TrueClass === value
+ klass, task = self.class.prepare_for_invocation(#{name.inspect}, value)
+
+ if klass
+ say_status :invoke, value, #{verbose.inspect}
+ block = self.class.invocation_blocks[#{name.inspect}]
+ _invoke_for_class_method klass, task, &block
+ else
+ say_status :error, %(\#{value} [not found]), :red
+ end
+ end
+ METHOD
+ end
+ end
+
+ # Remove a previously added invocation.
+ #
+ # ==== Examples
+ #
+ # remove_invocation :test_framework
+ #
+ def remove_invocation(*names)
+ names.each do |name|
+ remove_task(name)
+ remove_class_option(name)
+ invocations.delete(name)
+ invocation_blocks.delete(name)
+ end
+ end
+
+ # Overwrite class options help to allow invoked generators options to be
+ # shown recursively when invoking a generator.
+ #
+ 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
+ super(shell, groups)
+ end
+
+ # Get invocations array and merge options from invocations. Those
+ # options are added to group_options hash. Options that already exists
+ # in base_options are not added twice.
+ #
+ def get_options_from_invocations(group_options, base_options) #:nodoc:
+ invocations.each do |name, from_option|
+ value = if from_option
+ option = class_options[name]
+ option.type == :boolean ? name : option.default
+ else
+ name
+ end
+ next unless value
+
+ klass, task = prepare_for_invocation(name, value)
+ next unless klass && klass.respond_to?(:class_options)
+
+ value = value.to_s
+ human_name = value.respond_to?(:classify) ? value.classify : value
+
+ group_options[human_name] ||= []
+ group_options[human_name] += klass.class_options.values.select do |option|
+ base_options[option.name.to_sym].nil? && option.group.nil? &&
+ !group_options.values.flatten.any? { |i| i.name == option.name }
+ end
+
+ yield klass if block_given?