No more vendored thor.

This commit is contained in:
José Valim
2010-02-03 17:57:54 +01:00
parent 836cbe5735
commit 622e3dda84
34 changed files with 7 additions and 4376 deletions

View File

@@ -1,4 +1,4 @@
require 'thor'
require 'thor/group'
require 'rails/generators/actions'
module Rails

View File

@@ -5,12 +5,10 @@ module Rails
# just by implementing the next migration number method.
#
module Migration
attr_reader :migration_number, :migration_file_name, :migration_class_name
def self.included(base) #:nodoc:
base.class_eval do
extend ClassMethods
readers = lambda { attr_reader :migration_number, :migration_file_name, :migration_class_name }
respond_to?(:no_tasks) ? no_tasks(&readers) : readers.call
end
base.extend ClassMethods
end
module ClassMethods

View File

@@ -1,80 +0,0 @@
== 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
== 0.11, released 2009-07-01
* Added a rake compatibility layer. It allows you to use spec and rdoc tasks on
Thor classes.
* BACKWARDS INCOMPATIBLE: aliases are not generated automatically anymore
since it wrong behavior to the invocation system.
* thor help now show information about any class/task. All those calls are
possible:
thor help describe
thor help describe:amazing
Or even with default namespaces:
thor help :spec
* Thor::Runner now invokes the default task if none is supplied:
thor describe # invokes the default task, usually help
* Thor::Runner now works with mappings:
thor describe -h
* Added some documentation and code refactoring.
== 0.9.8, released 2008-10-20
* Fixed some tiny issues that were introduced lately.
== 0.9.7, released 2008-10-13
* Setting global method options on the initialize method works as expected:
All other tasks will accept these global options in addition to their own.
* Added 'group' notion to Thor task sets (class Thor); by default all tasks
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
== 0.9.5, released 2008-08-27
* Improve Windows compatibility
* Update (incorrect) README and task.thor sample file
* Options hash is now frozen (once returned)
* Allow magic predicates on options object. For instance: `options.force?`
* Add support for :numeric type
* BACKWARDS INCOMPATIBLE: Refactor Thor::Options. You cannot access shorthand forms in options hash anymore (for instance, options[:f])
* Allow specifying optional args with default values: method_options(:user => "mislav")
* Don't write options for nil or false values. This allows, for example, turning color off when running specs.
* Exit with the status of the spec command to help CI stuff out some.
== 0.9.4, released 2008-08-13
* Try to add Windows compatibility.
* BACKWARDS INCOMPATIBLE: options hash is now accessed as a property in your class and is not passed as last argument anymore
* Allow options at the beginning of the argument list as well as the end.
* Make options available with symbol keys in addition to string keys.
* Allow true to be passed to Thor#method_options to denote a boolean option.
* If loading a thor file fails, don't give up, just print a warning and keep going.
* Make sure that we re-raise errors if they happened further down the pipe than we care about.
* Only delete the old file on updating when the installation of the new one is a success
* Make it Ruby 1.8.5 compatible.
* Don't raise an error if a boolean switch is defined multiple times.
* Thor::Options now doesn't parse through things that look like options but aren't.
* Add URI detection to install task, and make sure we don't append ".thor" to URIs
* Add rake2thor to the gem binfiles.
* Make sure local Thorfiles override system-wide ones.

View File

@@ -1,20 +0,0 @@
Copyright (c) 2008 Yehuda Katz
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 File

@@ -1,234 +0,0 @@
= thor
Map options to a class. Simply create a class with the appropriate annotations
and have options automatically map to functions and parameters.
Example:
class App < Thor # [1]
map "-L" => :list # [2]
desc "install APP_NAME", "install one of the available apps" # [3]
method_options :force => :boolean, :alias => :string # [4]
def install(name)
user_alias = options[:alias]
if options.force?
# do something
end
# other code
end
desc "list [SEARCH]", "list all of the available apps, limited by SEARCH"
def list(search="")
# list everything
end
end
Thor automatically maps commands as such:
thor app:install myname --force
That gets converted to:
App.new.install("myname")
# with {'force' => true} as options hash
1. Inherit from Thor to turn a class into an option mapper
2. Map additional non-valid identifiers to specific methods. In this case, convert -L to :list
3. Describe the method immediately below. The first parameter is the usage information, and the second parameter is the description
4. Provide any additional options that will be available the instance method options.
== Types for <tt>method_options</tt>
* :boolean - is parsed as <tt>--option</tt> or <tt>--option=true</tt>
* :string - is parsed as <tt>--option=VALUE</tt>
* :numeric - is parsed as <tt>--option=N</tt>
* :array - is parsed as <tt>--option=one two three</tt>
* :hash - is parsed as <tt>--option=name:string age:integer</tt>
Besides, method_option allows a default value to be given, examples:
method_options :force => false
#=> Creates a boolean option with default value false
method_options :alias => "bar"
#=> Creates a string option with default value "bar"
method_options :threshold => 3.0
#=> Creates a numeric option with default value 3.0
You can also supply <tt>:option => :required</tt> to mark an option as required. The
type is assumed to be string. If you want a required hash with default values
as option, you can use <tt>method_option</tt> which uses a more declarative style:
method_option :attributes, :type => :hash, :default => {}, :required => true
All arguments can be set to nil (except required arguments), by suppling a no or
skip variant. For example:
thor app name --no-attributes
In previous versions, aliases for options were created automatically, but now
they should be explicit. You can supply aliases in both short and declarative
styles:
method_options %w( force -f ) => :boolean
Or:
method_option :force, :type => :boolean, :aliases => "-f"
You can supply as many aliases as you want.
NOTE: Type :optional available in Thor 0.9.0 was deprecated. Use :string or :boolean instead.
== Namespaces
By default, your Thor tasks are invoked using Ruby namespace. In the example
above, tasks are invoked as:
thor app:install name --force
However, you could namespace your class as:
module Sinatra
class App < Thor
# tasks
end
end
And then you should invoke your tasks as:
thor sinatra:app:install name --force
If desired, you can change the namespace:
module Sinatra
class App < Thor
namespace :myapp
# tasks
end
end
And then your tasks hould be invoked as:
thor myapp:install name --force
== Invocations
Thor comes with a invocation-dependency system as well which allows a task to be
invoked only once. For example:
class Counter < Thor
desc "one", "Prints 1, 2, 3"
def one
puts 1
invoke :two
invoke :three
end
desc "two", "Prints 2, 3"
def two
puts 2
invoke :three
end
desc "three", "Prints 3"
def three
puts 3
end
end
When invoking the task one:
thor counter:one
The output is "1 2 3", which means that the three task was invoked only once.
You can even invoke tasks from another class, so be sure to check the
documentation.
== Thor::Group
Thor has a special class called Thor::Group. The main difference to Thor class
is that it invokes all tasks at once. The example above could be rewritten in
Thor::Group as this:
class Counter < Thor::Group
desc "Prints 1, 2, 3"
def one
puts 1
end
def two
puts 2
end
def three
puts 3
end
end
When invoked:
thor counter
It prints "1 2 3" as well. Notice you should describe (using the method <tt>desc</tt>)
only the class and not each task anymore. Thor::Group is a great tool to create
generators, since you can define several steps which are invoked in the order they
are defined (Thor::Group is the tool use in generators in Rails 3.0).
Besides, Thor::Group can parse arguments and options as Thor tasks:
class Counter < Thor::Group
# number will be available as attr_accessor
argument :number, :type => :numeric, :desc => "The number to start counting"
desc "Prints the 'number' given upto 'number+2'"
def one
puts number + 0
end
def two
puts number + 1
end
def three
puts number + 2
end
end
The counter above expects one parameter and has the folling outputs:
thor counter 5
# Prints "5 6 7"
thor counter 11
# Prints "11 12 13"
You can also give options to Thor::Group, but instead of using <tt>method_option</tt>
and <tt>method_options</tt>, you should use <tt>class_option</tt> and <tt>class_options</tt>.
Both argument and class_options methods are available to Thor class as well.
== Actions
Thor comes with several actions which helps with script and generator tasks. You
might be familiar with them since some came from Rails Templates. They are:
<tt>say</tt>, <tt>ask</tt>, <tt>yes?</tt>, <tt>no?</tt>, <tt>add_file</tt>,
<tt>remove_file</tt>, <tt>copy_file</tt>, <tt>template</tt>, <tt>directory</tt>,
<tt>inside</tt>, <tt>run</tt>, <tt>inject_into_file</tt> and a couple more.
To use them, you just need to include Thor::Actions in your Thor classes:
class App < Thor
include Thor::Actions
# tasks
end
Some actions like copy file requires that a class method called source_root is
defined in your class. This is the directory where your templates should be
placed. Be sure to check the documentation.
== License
See MIT LICENSE.

View File

@@ -1,63 +0,0 @@
# enconding: utf-8
require File.join(File.dirname(__FILE__), "lib", "thor", "version")
require 'thor/rake_compat'
require 'spec/rake/spectask'
require 'rdoc/task'
GEM_NAME = 'thor'
EXTRA_RDOC_FILES = ["README.rdoc", "LICENSE", "CHANGELOG.rdoc", "VERSION", "Thorfile"]
class Default < Thor
include Thor::RakeCompat
Spec::Rake::SpecTask.new(:spec) do |t|
t.libs << 'lib'
t.spec_opts = ['--options', "spec/spec.opts"]
t.spec_files = FileList['spec/**/*_spec.rb']
end
Spec::Rake::SpecTask.new(:rcov) do |t|
t.libs << 'lib'
t.spec_opts = ['--options', "spec/spec.opts"]
t.spec_files = FileList['spec/**/*_spec.rb']
t.rcov = true
t.rcov_dir = "rcov"
end
RDoc::Task.new do |rdoc|
rdoc.main = "README.rdoc"
rdoc.rdoc_dir = "rdoc"
rdoc.title = GEM_NAME
rdoc.rdoc_files.include(*EXTRA_RDOC_FILES)
rdoc.rdoc_files.include('lib/**/*.rb')
rdoc.options << '--line-numbers' << '--inline-source'
end
begin
require 'jeweler'
Jeweler::Tasks.new do |s|
s.name = GEM_NAME
s.version = Thor::VERSION
s.rubyforge_project = "textmate"
s.platform = Gem::Platform::RUBY
s.summary = "A scripting framework that replaces rake, sake and rubigen"
s.email = "ruby-thor@googlegroups.com"
s.homepage = "http://yehudakatz.com"
s.description = "A scripting framework that replaces rake, sake and rubigen"
s.authors = ['Yehuda Katz', 'José Valim']
s.has_rdoc = true
s.extra_rdoc_files = EXTRA_RDOC_FILES
s.require_path = 'lib'
s.bindir = "bin"
s.executables = %w( thor rake2thor )
s.files = s.extra_rdoc_files + Dir.glob("{bin,lib}/**/*")
s.files.exclude 'spec/sandbox/**/*'
s.test_files.exclude 'spec/sandbox/**/*'
end
Jeweler::GemcutterTasks.new
rescue LoadError
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
end
end

View File

@@ -1,240 +0,0 @@
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 File

@@ -1,274 +0,0 @@
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 File

@@ -1,103 +0,0 @@
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 File

@@ -1,91 +0,0 @@
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 File

@@ -1,134 +0,0 @@
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 File

@@ -1,223 +0,0 @@
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 File

@@ -1,101 +0,0 @@
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 File

@@ -1,515 +0,0 @@
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 File

@@ -1,9 +0,0 @@
class File #:nodoc:
unless File.respond_to?(:binread)
def self.binread(file)
File.open(file, 'rb') { |f| f.read }
end
end
end

View File

@@ -1,75 +0,0 @@
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 File

@@ -1,100 +0,0 @@
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 File

@@ -1,27 +0,0 @@
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 File

@@ -1,270 +0,0 @@
# 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?
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
"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:
Thor::Group
end
def create_task(meth) #:nodoc:
tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil)
true
end
end
include Thor::Base
protected
# 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
result = if block_given?
case block.arity
when 3
block.call(self, klass, task)
when 2
block.call(self, klass)
when 1
instance_exec(klass, &block)
end
else
invoke klass, task, *args
end
shell.padding -= 1
result
end
end

View File

@@ -1,178 +0,0 @@
class Thor
module Invocation
def self.included(base) #:nodoc:
base.extend ClassMethods
end
module ClassMethods
# Prepare for class methods invocations. This method must return a klass to
# have the invoked class options showed in help messages in generators.
#
def prepare_for_invocation(key, name) #:nodoc:
case name
when Symbol, String
Thor::Util.namespace_to_thor_class_and_task(name.to_s, false)
else
name
end
end
end
# Make initializer aware of invocations and the initializer proc.
#
def initialize(args=[], options={}, config={}, &block) #:nodoc:
@_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] }
@_initializer = [ args, options, config ]
super
end
# Receives a name and invokes it. The name can be a string (either "task" or
# "namespace:task"), a Thor::Task, a Class or a Thor instance. If the task
# cannot be guessed by name, it can also be supplied as second argument.
#
# You can also supply the arguments, options and configuration values for
# the task to be invoked, if none is given, the same values used to
# initialize the invoker are used to initialize the invoked.
#
# ==== Examples
#
# class A < Thor
# def foo
# invoke :bar
# invoke "b:hello", ["José"]
# end
#
# def bar
# invoke "b:hello", ["José"]
# end
# end
#
# class B < Thor
# def hello(name)
# puts "hello #{name}"
# end
# end
#
# You can notice that the method "foo" above invokes two tasks: "bar",
# which belongs to the same class and "hello" which belongs to the class B.
#
# By using an invocation system you ensure that a task is invoked only once.
# In the example above, invoking "foo" will invoke "b:hello" just once, even
# if it's invoked later by "bar" method.
#
# When class A invokes class B, all arguments used on A initialization are
# supplied to B. This allows lazy parse of options. Let's suppose you have
# some rspec tasks:
#
# class Rspec < Thor::Group
# class_option :mock_framework, :type => :string, :default => :rr
#
# def invoke_mock_framework
# invoke "rspec:#{options[:mock_framework]}"
# end
# end
#
# As you noticed, it invokes the given mock framework, which might have its
# own options:
#
# class Rspec::RR < Thor::Group
# class_option :style, :type => :string, :default => :mock
# end
#
# Since it's not rspec concern to parse mock framework options, when RR
# is invoked all options are parsed again, so RR can extract only the options
# that it's going to use.
#
# If you want Rspec::RR to be initialized with its own set of options, you
# have to do that explicitely:
#
# invoke "rspec:rr", [], :style => :foo
#
# Besides giving an instance, you can also give a class to invoke:
#
# invoke Rspec::RR, [], :style => :foo
#
def invoke(name=nil, task=nil, args=nil, opts=nil, config=nil)
task, args, opts, config = nil, task, args, opts if task.nil? || task.is_a?(Array)
args, opts, config = nil, args, opts if args.is_a?(Hash)
object, task = _prepare_for_invocation(name, task)
klass, instance = _initialize_klass_with_initializer(object, args, opts, config)
method_args = []
current = @_invocations[klass]
iterator = proc do |_, task|
unless current.include?(task.name)
current << task.name
task.run(instance, method_args)
end
end
if task
args ||= []
method_args = args[Range.new(klass.arguments.size, -1)] || []
iterator.call(nil, task)
else
klass.all_tasks.map(&iterator)
end
end
protected
# Configuration values that are shared between invocations.
#
def _shared_configuration #:nodoc:
{ :invocations => @_invocations }
end
# Prepare for invocation in the instance level. In this case, we have to
# take into account that a just a task name from the current class was
# given or even a Thor::Task object.
#
def _prepare_for_invocation(name, sent_task=nil) #:nodoc:
if name.is_a?(Thor::Task)
task = name
elsif task = self.class.all_tasks[name.to_s]
object = self
else
object, task = self.class.prepare_for_invocation(nil, name)
task ||= sent_task
end
# If the object was not set, use self and use the name as task.
object, task = self, name unless object
return object, _validate_task(object, task)
end
# Check if the object given is a Thor class object and get a task object
# for it.
#
def _validate_task(object, task) #:nodoc:
klass = object.is_a?(Class) ? object : object.class
raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
task ||= klass.default_task if klass <= Thor
task = klass.all_tasks[task.to_s] || Thor::Task::Dynamic.new(task) if task && !task.is_a?(Thor::Task)
task
end
# Initialize klass using values stored in the @_initializer.
#
def _initialize_klass_with_initializer(object, args, opts, config) #:nodoc:
if object.is_a?(Class)
klass = object
stored_args, stored_opts, stored_config = @_initializer
args ||= stored_args.dup
opts ||= stored_opts.dup
config ||= {}
config = stored_config.merge(_shared_configuration).merge!(config)
[ klass, klass.new(args, opts, config) ]
else
[ object.class, object ]
end
end
end
end

View File

@@ -1,4 +0,0 @@
require 'thor/parser/argument'
require 'thor/parser/arguments'
require 'thor/parser/option'
require 'thor/parser/options'

View File

@@ -1,67 +0,0 @@
class Thor
class Argument #:nodoc:
VALID_TYPES = [ :numeric, :hash, :array, :string ]
attr_reader :name, :description, :required, :type, :default, :banner
alias :human_name :name
def initialize(name, description=nil, required=true, type=:string, default=nil, banner=nil)
class_name = self.class.name.split("::").last
raise ArgumentError, "#{class_name} name can't be nil." if name.nil?
raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type)
@name = name.to_s
@description = description
@required = required || false
@type = (type || :string).to_sym
@default = default
@banner = banner || default_banner
validate! # Trigger specific validations
end
def usage
required? ? banner : "[#{banner}]"
end
def required?
required
end
def show_default?
case default
when Array, String, Hash
!default.empty?
else
default
end
end
protected
def validate!
raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil?
end
def valid_type?(type)
VALID_TYPES.include?(type.to_sym)
end
def default_banner
case type
when :boolean
nil
when :string, :default
human_name.upcase
when :numeric
"N"
when :hash
"key:value"
when :array
"one two three"
end
end
end
end

View File

@@ -1,145 +0,0 @@
class Thor
class Arguments #:nodoc:
NUMERIC = /(\d*\.\d+|\d+)/
# Receives an array of args and returns two arrays, one with arguments
# and one with switches.
#
def self.split(args)
arguments = []
args.each do |item|
break if item =~ /^-/
arguments << item
end
return arguments, args[Range.new(arguments.size, -1)]
end
def self.parse(base, args)
new(base).parse(args)
end
# Takes an array of Thor::Argument objects.
#
def initialize(arguments=[])
@assigns, @non_assigned_required = {}, []
@switches = arguments
arguments.each do |argument|
if argument.default
@assigns[argument.human_name] = argument.default
elsif argument.required?
@non_assigned_required << argument
end
end
end
def parse(args)
@pile = args.dup
@switches.each do |argument|
break unless peek
@non_assigned_required.delete(argument)
@assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name)
end
check_requirement!
@assigns
end
private
def peek
@pile.first
end
def shift
@pile.shift
end
def unshift(arg)
unless arg.kind_of?(Array)
@pile.unshift(arg)
else
@pile = arg + @pile
end
end
def current_is_value?
peek && peek.to_s !~ /^-/
end
# Runs through the argument array getting strings that contains ":" and
# mark it as a hash:
#
# [ "name:string", "age:integer" ]
#
# Becomes:
#
# { "name" => "string", "age" => "integer" }
#
def parse_hash(name)
return shift if peek.is_a?(Hash)
hash = {}
while current_is_value? && peek.include?(?:)
key, value = shift.split(':')
hash[key] = value
end
hash
end
# Runs through the argument array getting all strings until no string is
# found or a switch is found.
#
# ["a", "b", "c"]
#
# And returns it as an array:
#
# ["a", "b", "c"]
#
def parse_array(name)
return shift if peek.is_a?(Array)
array = []
while current_is_value?
array << shift
end
array
end
# Check if the peel is numeric ofrmat and return a Float or Integer.
# Otherwise raises an error.
#
def parse_numeric(name)
return shift if peek.is_a?(Numeric)
unless peek =~ NUMERIC && $& == peek
raise MalformattedArgumentError, "expected numeric value for '#{name}'; got #{peek.inspect}"
end
$&.index('.') ? shift.to_f : shift.to_i
end
# Parse string, i.e., just return the current value in the pile.
#
def parse_string(name)
shift
end
# Raises an error if @non_assigned_required array is not empty.
#
def check_requirement!
unless @non_assigned_required.empty?
names = @non_assigned_required.map do |o|
o.respond_to?(:switch_name) ? o.switch_name : o.human_name
end.join("', '")
class_name = self.class.name.split('::').last.downcase
raise RequiredArgumentMissingError, "no value provided for required #{class_name} '#{names}'"
end
end
end
end

View File

@@ -1,132 +0,0 @@
class Thor
class Option < Argument #:nodoc:
attr_reader :aliases, :group
VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
def initialize(name, description=nil, required=nil, type=nil, default=nil, banner=nil, group=nil, aliases=nil)
super(name, description, required, type, default, banner)
@aliases = [*aliases].compact
@group = group.to_s.capitalize if group
end
# This parse quick options given as method_options. It makes several
# assumptions, but you can be more specific using the option method.
#
# parse :foo => "bar"
# #=> Option foo with default value bar
#
# parse [:foo, :baz] => "bar"
# #=> Option foo with default value bar and alias :baz
#
# parse :foo => :required
# #=> Required option foo without default value
#
# parse :foo => 2
# #=> Option foo with default value 2 and type numeric
#
# parse :foo => :numeric
# #=> Option foo without default value and type numeric
#
# parse :foo => true
# #=> Option foo with default value true and type boolean
#
# The valid types are :boolean, :numeric, :hash, :array and :string. If none
# is given a default type is assumed. This default type accepts arguments as
# 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
else
name, aliases = key, []
end
name = name.to_s
default = value
type = case value
when Symbol
default = nil
if VALID_TYPES.include?(value)
value
elsif required = (value == :required)
:string
elsif value == :optional
# TODO Remove this warning in the future.
warn "Optional type is deprecated. Choose :boolean or :string instead. Assumed to be :boolean."
:boolean
end
when TrueClass, FalseClass
:boolean
when Numeric
:numeric
when Hash, Array, String
value.class.name.downcase.to_sym
end
self.new(name.to_s, nil, required, type, default, nil, nil, aliases)
end
def switch_name
@switch_name ||= dasherized? ? name : dasherize(name)
end
def human_name
@human_name ||= dasherized? ? undasherize(name) : name
end
def usage(padding=0)
sample = if banner && !banner.to_s.empty?
"#{switch_name}=#{banner}"
else
switch_name
end
sample = "[#{sample}]" unless required?
if aliases.empty?
(" " * padding) << sample
else
"#{aliases.join(', ')}, #{sample}"
end
end
# Allow some type predicates as: boolean?, string? and etc.
#
def method_missing(method, *args, &block)
given = method.to_s.sub(/\?$/, '').to_sym
if valid_type?(given)
self.type == given
else
super
end
end
protected
def validate!
raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
end
def valid_type?(type)
VALID_TYPES.include?(type.to_sym)
end
def dasherized?
name.index('-') == 0
end
def undasherize(str)
str.sub(/^-{1,2}/, '')
end
def dasherize(str)
(str.length > 1 ? "--" : "-") + str.gsub('_', '-')
end
end
end

View File

@@ -1,142 +0,0 @@
class Thor
# This is a modified version of Daniel Berger's Getopt::Long class, licensed
# under Ruby's license.
#
class Options < Arguments #:nodoc:
LONG_RE = /^(--\w+[-\w+]*)$/
SHORT_RE = /^(-[a-z])$/i
EQ_RE = /^(--\w+[-\w+]*|-[a-z])=(.*)$/i
SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
# Receives a hash and makes it switches.
#
def self.to_switches(options)
options.map do |key, value|
case value
when true
"--#{key}"
when Array
"--#{key} #{value.map{ |v| v.inspect }.join(' ')}"
when Hash
"--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}"
when nil, false
""
else
"--#{key} #{value.inspect}"
end
end.join(" ")
end
# Takes a hash of Thor::Option objects.
#
def initialize(options={})
options = options.values
super(options)
@shorts, @switches = {}, {}
options.each do |option|
@switches[option.switch_name] = option
option.aliases.each do |short|
@shorts[short.to_s] ||= option.switch_name
end
end
end
def parse(args)
@pile = args.dup
while peek
if current_is_switch?
case shift
when SHORT_SQ_RE
unshift($1.split('').map { |f| "-#{f}" })
next
when EQ_RE, SHORT_NUM
unshift($2)
switch = $1
when LONG_RE, SHORT_RE
switch = $1
end
switch = normalize_switch(switch)
next unless option = switch_option(switch)
@assigns[option.human_name] = parse_peek(switch, option)
else
shift
end
end
check_requirement!
@assigns
end
protected
# Returns true if the current value in peek is a registered switch.
#
def current_is_switch?
case peek
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
switch?($1)
when SHORT_SQ_RE
$1.split('').any? { |f| switch?("-#{f}") }
end
end
def switch?(arg)
switch_option(arg) || @shorts.key?(arg)
end
def switch_option(arg)
if match = no_or_skip?(arg)
@switches[arg] || @switches["--#{match}"]
else
@switches[arg]
end
end
def no_or_skip?(arg)
arg =~ /^--(no|skip)-([-\w]+)$/
$2
end
# Check if the given argument is actually a shortcut.
#
def normalize_switch(arg)
@shorts.key?(arg) ? @shorts[arg] : arg
end
# Parse boolean values which can be given as --foo=true, --foo or --no-foo.
#
def parse_boolean(switch)
if current_is_value?
["true", "TRUE", "t", "T", true].include?(shift)
else
@switches.key?(switch) || !no_or_skip?(switch)
end
end
# Parse the value at the peek analyzing if it requires an input or not.
#
def parse_peek(switch, option)
unless current_is_value?
if option.boolean?
# No problem for boolean types
elsif no_or_skip?(switch)
return nil # User set value to nil
elsif option.string? && !option.required?
return option.human_name # Return the option name
else
raise MalformattedArgumentError, "no value provided for option '#{switch}'"
end
end
@non_assigned_required.delete(option)
send(:"parse_#{option.type}", switch)
end
end
end

View File

@@ -1,66 +0,0 @@
require 'rake'
class Thor
# Adds a compatibility layer to your Thor classes which allows you to use
# rake package tasks. For example, to use rspec rake tasks, one can do:
#
# require 'thor/rake_compat'
#
# class Default < Thor
# include Thor::RakeCompat
#
# Spec::Rake::SpecTask.new(:spec) do |t|
# t.spec_opts = ['--options', "spec/spec.opts"]
# t.spec_files = FileList['spec/**/*_spec.rb']
# end
# end
#
module RakeCompat
def self.rake_classes
@rake_classes ||= []
end
def self.included(base)
# Hack. Make rakefile point to invoker, so rdoc task is generated properly.
rakefile = File.basename(caller[0].match(/(.*):\d+/)[1])
Rake.application.instance_variable_set(:@rakefile, rakefile)
self.rake_classes << base
end
end
end
class Object #:nodoc:
alias :rake_task :task
alias :rake_namespace :namespace
def task(*args, &block)
task = rake_task(*args, &block)
if klass = Thor::RakeCompat.rake_classes.last
non_namespaced_name = task.name.split(':').last
description = non_namespaced_name
description << task.arg_names.map{ |n| n.to_s.upcase }.join(' ')
description.strip!
klass.desc description, task.comment || non_namespaced_name
klass.send :define_method, non_namespaced_name do |*args|
Rake::Task[task.name.to_sym].invoke(*args)
end
end
task
end
def namespace(name, &block)
if klass = Thor::RakeCompat.rake_classes.last
const_name = Thor::Util.camel_case(name.to_s).to_sym
klass.const_set(const_name, Class.new(Thor))
new_klass = klass.const_get(const_name)
Thor::RakeCompat.rake_classes << new_klass
end
rake_namespace(name, &block)
Thor::RakeCompat.rake_classes.pop
end
end

View File

@@ -1,303 +0,0 @@
require 'fileutils'
require 'open-uri'
require 'yaml'
require 'digest/md5'
require 'pathname'
class Thor::Runner < Thor #:nodoc:
map "-T" => :list, "-i" => :install, "-u" => :update
# Override Thor#help so it can give information about any class and any method.
#
def help(meth=nil)
if meth && !self.respond_to?(meth)
initialize_thorfiles(meth)
klass, task = Thor::Util.namespace_to_thor_class_and_task(meth)
# Send mapping -h because it works with Thor::Group too
klass.start(["-h", task].compact, :shell => self.shell)
else
super
end
end
# If a task is not found on Thor::Runner, method missing is invoked and
# Thor::Runner is then responsable for finding the task in all classes.
#
def method_missing(meth, *args)
meth = meth.to_s
initialize_thorfiles(meth)
klass, task = Thor::Util.namespace_to_thor_class_and_task(meth)
args.unshift(task) if task
klass.start(args, :shell => shell)
end
desc "install NAME", "Install an optionally named Thor file into your system tasks"
method_options :as => :string, :relative => :boolean
def install(name)
initialize_thorfiles
# 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))
base, package = File.join(name, "main.thor"), :directory
contents = open(base).read
else
base, package = name, :file
contents = open(name).read
end
rescue OpenURI::HTTPError
raise Error, "Error opening URI '#{name}'"
rescue Errno::ENOENT
raise Error, "Error opening file '#{name}'"
end
say "Your Thorfile contains:"
say contents
return false if no?("Do you wish to continue [y/N]?")
as = options["as"] || begin
first_line = contents.split("\n")[0]
(match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
end
unless as
basename = File.basename(name)
as = ask("Please specify a name for #{name} in the system repository [#{basename}]:")
as = basename if as.empty?
end
location = if options[:relative] || name =~ /^http:\/\//
name
else
File.expand_path(name)
end
thor_yaml[as] = {
:filename => Digest::MD5.hexdigest(name + as),
:location => location,
:namespaces => Thor::Util.namespaces_in_content(contents, base)
}
save_yaml(thor_yaml)
say "Storing thor file in your system repository"
destination = File.join(thor_root, thor_yaml[as][:filename])
if package == :file
File.open(destination, "w") { |f| f.puts contents }
else
FileUtils.cp_r(name, destination)
end
thor_yaml[as][:filename] # Indicate success
end
desc "uninstall NAME", "Uninstall a named Thor module"
def uninstall(name)
raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
say "Uninstalling #{name}."
FileUtils.rm_rf(File.join(thor_root, "#{thor_yaml[name][:filename]}"))
thor_yaml.delete(name)
save_yaml(thor_yaml)
puts "Done."
end
desc "update NAME", "Update a Thor file from its original location"
def update(name)
raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location]
say "Updating '#{name}' from #{thor_yaml[name][:location]}"
old_filename = thor_yaml[name][:filename]
self.options = self.options.merge("as" => name)
filename = install(thor_yaml[name][:location])
unless filename == old_filename
File.delete(File.join(thor_root, old_filename))
end
end
desc "installed", "List the installed Thor modules and tasks"
method_options :internal => :boolean
def installed
initialize_thorfiles(nil, true)
display_klasses(true, options["internal"])
end
desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
method_options :substring => :boolean, :group => :string, :all => :boolean
def list(search="")
initialize_thorfiles
search = ".*#{search}" if options["substring"]
search = /^#{search}.*/i
group = options[:group] || "standard"
klasses = Thor::Base.subclasses.select do |k|
(options[:all] || k.group == group) && k.namespace =~ search
end
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
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 || {}
end
end
# Save the yaml file. If none exists in thor root, creates one.
#
def save_yaml(yaml)
yaml_file = File.join(thor_root, "thor.yml")
unless File.exists?(yaml_file)
FileUtils.mkdir_p(thor_root)
yaml_file = File.join(thor_root, "thor.yml")
FileUtils.touch(yaml_file)
end
File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml }
end
def self.exit_on_failure?
true
end
# Load the thorfiles. If relevant_to is supplied, looks for specific files
# in the thor_root instead of loading them all.
#
# By default, it also traverses the current path until find Thor files, as
# described in thorfiles. This look up can be skipped by suppliying
# skip_lookup true.
#
def initialize_thorfiles(relevant_to=nil, skip_lookup=false)
thorfiles(relevant_to, skip_lookup).each do |f|
Thor::Util.load_thorfile(f) unless Thor::Base.subclass_files.keys.include?(File.expand_path(f))
end
end
# Finds Thorfiles by traversing from your current directory down to the root
# directory of your system. If at any time we find a Thor file, we stop.
#
# We also ensure that system-wide Thorfiles are loaded first, so local
# Thorfiles can override them.
#
# ==== Example
#
# If we start at /Users/wycats/dev/thor ...
#
# 1. /Users/wycats/dev/thor
# 2. /Users/wycats/dev
# 3. /Users/wycats <-- we find a Thorfile here, so we stop
#
# Suppose we start at c:\Documents and Settings\james\dev\thor ...
#
# 1. c:\Documents and Settings\james\dev\thor
# 2. c:\Documents and Settings\james\dev
# 3. c:\Documents and Settings\james
# 4. c:\Documents and Settings
# 5. c:\ <-- no Thorfiles found!
#
def thorfiles(relevant_to=nil, skip_lookup=false)
thorfiles = []
unless skip_lookup
Pathname.pwd.ascend do |path|
thorfiles = Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten
break unless thorfiles.empty?
end
end
files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Thor::Util.thor_root_glob)
files += thorfiles
files -= ["#{thor_root}/thor.yml"]
files.map! do |file|
File.directory?(file) ? File.join(file, "main.thor") : file
end
end
# Load thorfiles relevant to the given method. If you provide "foo:bar" it
# will load all thor files in the thor.yaml that has "foo" e "foo:bar"
# namespaces registered.
#
def thorfiles_relevant_to(meth)
lookup = [ meth, meth.split(":")[0...-1].join(":") ]
files = thor_yaml.select do |k, v|
v[:namespaces] && !(v[:namespaces] & lookup).empty?
end
files.map { |k, v| File.join(thor_root, "#{v[:filename]}") }
end
# 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, 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?
# Remove subclasses
klasses.dup.each do |klass|
klasses -= Thor::Util.thor_classes_in(klass)
end
list = Hash.new { |h,k| h[k] = [] }
groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
# Get classes which inherit from Thor
(klasses - groups).each { |k| list[k.namespace] += k.printable_tasks(false) }
# Get classes which inherit from Thor::Base
groups.map! { |k| k.printable_tasks(false).first }
list["root"] = groups
# 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
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
def show_modules #:nodoc:
info = []
labels = ["Modules", "Namespaces"]
info << labels
info << [ "-" * labels[0].size, "-" * labels[1].size ]
thor_yaml.each do |name, hash|
info << [ name, hash[:namespaces].join(", ") ]
end
print_table info
say ""
end
end

View File

@@ -1,78 +0,0 @@
require 'rbconfig'
require 'thor/shell/color'
class Thor
module Base
# Returns the shell used in all Thor classes. If you are in a Unix platform
# it will use a colored log, otherwise it will use a basic one without color.
#
def self.shell
@shell ||= if Config::CONFIG['host_os'] =~ /mswin|mingw/
Thor::Shell::Basic
else
Thor::Shell::Color
end
end
# Sets the shell used in all Thor classes.
#
def self.shell=(klass)
@shell = klass
end
end
module Shell
SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_table]
# Add shell to initialize config values.
#
# ==== Configuration
# shell<Object>:: An instance of the shell to be used.
#
# ==== Examples
#
# class MyScript < Thor
# argument :first, :type => :numeric
# end
#
# MyScript.new [1.0], { :foo => :bar }, :shell => Thor::Shell::Basic.new
#
def initialize(args=[], options={}, config={})
super
self.shell = config[:shell]
self.shell.base ||= self if self.shell.respond_to?(:base)
end
# Holds the shell for the given Thor instance. If no shell is given,
# it gets a default shell from Thor::Base.shell.
#
def shell
@shell ||= Thor::Base.shell.new
end
# Sets the shell for this thor class.
#
def shell=(shell)
@shell = shell
end
# Common methods that are delegated to the shell.
#
SHELL_DELEGATED_METHODS.each do |method|
module_eval <<-METHOD, __FILE__, __LINE__
def #{method}(*args)
shell.#{method}(*args)
end
METHOD
end
protected
# Allow shell to be shared between invocations.
#
def _shared_configuration #:nodoc:
super.merge!(:shell => self.shell)
end
end
end

View File

@@ -1,239 +0,0 @@
require 'tempfile'
class Thor
module Shell
class Basic
attr_accessor :base, :padding
# Initialize base and padding to nil.
#
def initialize #:nodoc:
@base, @padding = nil, 0
end
# Sets the output padding, not allowing less than zero values.
#
def padding=(value)
@padding = [0, value].max
end
# Ask something to the user and receives a response.
#
# ==== Example
# ask("What is your name?")
#
def ask(statement, color=nil)
say("#{statement} ", color)
$stdin.gets.strip
end
# Say (print) something to the user. If the sentence ends with a whitespace
# or tab character, a new line is not appended (print + flush). Otherwise
# are passed straight to puts (behavior got from Highline).
#
# ==== Example
# say("I know you knew that.")
#
def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)$/))
message = message.to_s
message = set_color(message, color) if color
if force_new_line
$stdout.puts(message)
else
$stdout.print(message)
$stdout.flush
end
end
# Say a status with the given color and appends the message. Since this
# method is used frequently by actions, it allows nil or false to be given
# in log_status, avoiding the message from being shown. If a Symbol is
# given in log_status, it's used as the color.
#
def say_status(status, message, log_status=true)
return if quiet? || log_status == false
spaces = " " * (padding + 1)
color = log_status.is_a?(Symbol) ? log_status : :green
status = status.to_s.rjust(12)
status = set_color status, color, true if color
say "#{status}#{spaces}#{message}", nil, true
end
# Make a question the to user and returns true if the user replies "y" or
# "yes".
#
def yes?(statement, color=nil)
ask(statement, color) =~ is?(:yes)
end
# Make a question the to user and returns true if the user replies "n" or
# "no".
#
def no?(statement, color=nil)
!yes?(statement, color)
end
# Prints a table.
#
# ==== Parameters
# Array[Array[String, String, ...]]
#
# ==== Options
# ident<Integer>:: Ident the first column by ident value.
#
def print_table(table, options={})
return if table.empty?
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, " " * ident)
formats << "%s"
table.each do |row|
sentence = ""
row.each_with_index do |column, i|
sentence << formats[i] % column.to_s
end
sentence = truncate(sentence, options[:truncate]) if options[:truncate]
$stdout.puts sentence
end
end
# Deals with file collision and returns true if the file should be
# overwriten and false otherwise. If a block is given, it uses the block
# response as the content for the diff.
#
# ==== Parameters
# destination<String>:: the destination file to solve conflicts
# block<Proc>:: an optional block that returns the value to be used in diff
#
def file_collision(destination)
return true if @always_force
options = block_given? ? "[Ynaqdh]" : "[Ynaqh]"
while true
answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}]
case answer
when is?(:yes), is?(:force), ""
return true
when is?(:no), is?(:skip)
return false
when is?(:always)
return @always_force = true
when is?(:quit)
say 'Aborting...'
raise SystemExit
when is?(:diff)
show_diff(destination, yield) if block_given?
say 'Retrying...'
else
say file_collision_help
end
end
end
# Called if something goes wrong during the execution. This is used by Thor
# internally and should not be used inside your scripts. If someone went
# wrong, you can always raise an exception. If you raise a Thor::Error, it
# will be rescued and wrapped in the method below.
#
def error(statement)
$stderr.puts statement
end
# Apply color to the given string with optional bold. Disabled in the
# Thor::Shell::Basic class.
#
def set_color(string, color, bold=false) #:nodoc:
string
end
protected
def is?(value) #:nodoc:
value = value.to_s
if value.size == 1
/\A#{value}\z/i
else
/\A(#{value}|#{value[0,1]})\z/i
end
end
def file_collision_help #:nodoc:
<<HELP
Y - yes, overwrite
n - no, do not overwrite
a - all, overwrite this and all others
q - quit, abort
d - diff, show the differences between the old and the new
h - help, show this help
HELP
end
def show_diff(destination, content) #:nodoc:
diff_cmd = ENV['THOR_DIFF'] || ENV['RAILS_DIFF'] || 'diff -u'
Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
temp.write content
temp.rewind
system %(#{diff_cmd} "#{destination}" "#{temp.path}")
end
end
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

View File

@@ -1,108 +0,0 @@
require 'thor/shell/basic'
class Thor
module Shell
# Inherit from Thor::Shell::Basic and add set_color behavior. Check
# Thor::Shell::Basic to see all available methods.
#
class Color < Basic
# Embed in a String to clear all previous ANSI sequences.
CLEAR = "\e[0m"
# The start of an ANSI bold sequence.
BOLD = "\e[1m"
# Set the terminal's foreground ANSI color to black.
BLACK = "\e[30m"
# Set the terminal's foreground ANSI color to red.
RED = "\e[31m"
# Set the terminal's foreground ANSI color to green.
GREEN = "\e[32m"
# Set the terminal's foreground ANSI color to yellow.
YELLOW = "\e[33m"
# Set the terminal's foreground ANSI color to blue.
BLUE = "\e[34m"
# Set the terminal's foreground ANSI color to magenta.
MAGENTA = "\e[35m"
# Set the terminal's foreground ANSI color to cyan.
CYAN = "\e[36m"
# Set the terminal's foreground ANSI color to white.
WHITE = "\e[37m"
# Set the terminal's background ANSI color to black.
ON_BLACK = "\e[40m"
# Set the terminal's background ANSI color to red.
ON_RED = "\e[41m"
# Set the terminal's background ANSI color to green.
ON_GREEN = "\e[42m"
# Set the terminal's background ANSI color to yellow.
ON_YELLOW = "\e[43m"
# Set the terminal's background ANSI color to blue.
ON_BLUE = "\e[44m"
# Set the terminal's background ANSI color to magenta.
ON_MAGENTA = "\e[45m"
# Set the terminal's background ANSI color to cyan.
ON_CYAN = "\e[46m"
# Set the terminal's background ANSI color to white.
ON_WHITE = "\e[47m"
# Set color by using a string or one of the defined constants. If a third
# option is set to true, it also adds bold to the string. This is based
# on Highline implementation and it automatically appends CLEAR to the end
# of the returned String.
#
def set_color(string, color, bold=false)
color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
bold = bold ? BOLD : ""
"#{bold}#{color}#{string}#{CLEAR}"
end
protected
# Overwrite show_diff to show diff with colors if Diff::LCS is
# available.
#
def show_diff(destination, content) #:nodoc:
if diff_lcs_loaded? && ENV['THOR_DIFF'].nil? && ENV['RAILS_DIFF'].nil?
actual = File.binread(destination).to_s.split("\n")
content = content.to_s.split("\n")
Diff::LCS.sdiff(actual, content).each do |diff|
output_diff_line(diff)
end
else
super
end
end
def output_diff_line(diff) #:nodoc:
case diff.action
when '-'
say "- #{diff.old_element.chomp}", :red, true
when '+'
say "+ #{diff.new_element.chomp}", :green, true
when '!'
say "- #{diff.old_element.chomp}", :red, true
say "+ #{diff.new_element.chomp}", :green, true
else
say " #{diff.old_element.chomp}", nil, true
end
end
# Check if Diff::LCS is loaded. If it is, use it to create pretty output
# for diff.
#
def diff_lcs_loaded? #:nodoc:
return true if defined?(Diff::LCS)
return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
@diff_lcs_loaded = begin
require 'diff/lcs'
true
rescue LoadError
false
end
end
end
end
end

View File

@@ -1,111 +0,0 @@
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, options=nil)
super(name.to_s, "A dynamically-generated task", name.to_s, options)
end
def run(instance, args=[])
unless (instance.methods & [name.to_s, name.to_sym]).empty?
raise Error, "could not find Thor class or task '#{name}'"
end
super
end
end
def initialize(name, description, usage, options=nil)
super(name.to_s, description, usage, options || {})
end
def initialize_copy(other) #:nodoc:
super(other)
self.options = other.options.dup if other.options
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 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
# 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 }.compact.join(' ')
end
else
usage.to_s
end
# 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
# 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:
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)}'"
end
else
raise e
end
end
def parse_no_method_error(instance, e) #:nodoc:
if e.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
raise UndefinedTaskError, "The #{instance.class.namespace} namespace " <<
"doesn't have a '#{name}' task"
else
raise e
end
end
end
end

View File

@@ -1,233 +0,0 @@
require 'rbconfig'
class Thor
module Sandbox #:nodoc:
end
# This module holds several utilities:
#
# 1) Methods to convert thor namespaces to constants and vice-versa.
#
# Thor::Utils.namespace_from_thor_class(Foo::Bar::Baz) #=> "foo:bar:baz"
#
# 2) Loading thor files and sandboxing:
#
# Thor::Utils.load_thorfile("~/.thor/foo")
#
module Util
# Receives a namespace and search for it in the Thor::Base subclasses.
#
# ==== Parameters
# namespace<String>:: The namespace to search for.
#
def self.find_by_namespace(namespace)
namespace = "default#{namespace}" if namespace.empty? || namespace =~ /^:/
Thor::Base.subclasses.find do |klass|
klass.namespace == namespace
end
end
# Receives a constant and converts it to a Thor namespace. Since Thor tasks
# can be added to a sandbox, this method is also responsable for removing
# the sandbox namespace.
#
# This method should not be used in general because it's used to deal with
# older versions of Thor. On current versions, if you need to get the
# namespace from a class, just call namespace on it.
#
# ==== Parameters
# constant<Object>:: The constant to be converted to the thor path.
#
# ==== Returns
# String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz"
#
def self.namespace_from_thor_class(constant, remove_default=true)
constant = constant.to_s.gsub(/^Thor::Sandbox::/, "")
constant = snake_case(constant).squeeze(":")
constant.gsub!(/^default/, '') if remove_default
constant
end
# Given the contents, evaluate it inside the sandbox and returns the
# namespaces defined in the sandbox.
#
# ==== Parameters
# contents<String>
#
# ==== Returns
# Array[Object]
#
def self.namespaces_in_content(contents, file=__FILE__)
old_constants = Thor::Base.subclasses.dup
Thor::Base.subclasses.clear
load_thorfile(file, contents)
new_constants = Thor::Base.subclasses.dup
Thor::Base.subclasses.replace(old_constants)
new_constants.map!{ |c| c.namespace }
new_constants.compact!
new_constants
end
# 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|
next unless subclass.name
stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", ''))
end
end
# Receives a string and convert it to snake case. SnakeCase returns snake_case.
#
# ==== Parameters
# String
#
# ==== Returns
# String
#
def self.snake_case(str)
return str.downcase if str =~ /^[A-Z_]+$/
str.gsub(/\B[A-Z]/, '_\&').squeeze('_') =~ /_*(.*)/
return $+.downcase
end
# Receives a string and convert it to camel case. camel_case returns CamelCase.
#
# ==== Parameters
# String
#
# ==== Returns
# String
#
def self.camel_case(str)
return str if str !~ /_/ && str =~ /[A-Z]+.*/
str.split('_').map { |i| i.capitalize }.join
end
# Receives a namespace and tries to retrieve a Thor or Thor::Group class
# from it. It first searches for a class using the all the given namespace,
# if it's not found, removes the highest entry and searches for the class
# again. If found, returns the highest entry as the class name.
#
# ==== Examples
#
# class Foo::Bar < Thor
# def baz
# end
# end
#
# class Baz::Foo < Thor::Group
# end
#
# Thor::Util.namespace_to_thor_class("foo:bar") #=> Foo::Bar, nil # will invoke default task
# Thor::Util.namespace_to_thor_class("baz:foo") #=> Baz::Foo, nil
# Thor::Util.namespace_to_thor_class("foo:bar:baz") #=> Foo::Bar, "baz"
#
# ==== Parameters
# namespace<String>
#
# ==== Errors
# Thor::Error:: raised if the namespace cannot be found.
#
# Thor::Error:: raised if the namespace evals to a class which does not
# inherit from Thor or Thor::Group.
#
def self.namespace_to_thor_class_and_task(namespace, raise_if_nil=true)
if namespace.include?(?:)
pieces = namespace.split(":")
task = pieces.pop
klass = Thor::Util.find_by_namespace(pieces.join(":"))
end
unless klass
klass, task = Thor::Util.find_by_namespace(namespace), nil
end
raise Error, "could not find Thor class or task '#{namespace}'" if raise_if_nil && klass.nil?
return klass, task
end
# Receives a path and load the thor file in the path. The file is evaluated
# inside the sandbox to avoid namespacing conflicts.
#
def self.load_thorfile(path, content=nil)
content ||= File.binread(path)
begin
Thor::Sandbox.class_eval(content, path)
rescue Exception => e
$stderr.puts "WARNING: unable to load thorfile #{path.inspect}: #{e.message}"
end
end
def self.user_home
@@user_home ||= if ENV["HOME"]
ENV["HOME"]
elsif ENV["USERPROFILE"]
ENV["USERPROFILE"]
elsif ENV["HOMEDRIVE"] && ENV["HOMEPATH"]
File.join(ENV["HOMEDRIVE"], ENV["HOMEPATH"])
elsif ENV["APPDATA"]
ENV["APPDATA"]
else
begin
File.expand_path("~")
rescue
if File::ALT_SEPARATOR
"C:/"
else
"/"
end
end
end
end
# Returns the root where thor files are located, dependending on the OS.
#
def self.thor_root
File.join(user_home, ".thor").gsub(/\\/, '/')
end
# Returns the files in the thor root. On Windows thor_root will be something
# like this:
#
# C:\Documents and Settings\james\.thor
#
# If we don't #gsub the \ character, Dir.glob will fail.
#
def self.thor_root_glob
files = Dir["#{thor_root}/*"]
files.map! do |file|
File.directory?(file) ? File.join(file, "main.thor") : file
end
end
# Where to look for Thor files.
#
def self.globs_for(path)
["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"]
end
# Return the path to the ruby interpreter taking into account multiple
# installations and windows extensions.
#
def self.ruby_command
@ruby_command ||= begin
ruby = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
ruby << Config::CONFIG['EXEEXT']
# escape string in case path to ruby executable contain spaces.
ruby.sub!(/.*\s.*/m, '"\&"')
ruby
end
end
end
end

View File

@@ -1,3 +0,0 @@
class Thor
VERSION = "0.12.3".freeze
end

View File

@@ -9,8 +9,9 @@ Gem::Specification.new do |s|
EOF
s.add_dependency('rake', '>= 0.8.3')
s.add_dependency('activesupport', '= 3.0.pre')
s.add_dependency('actionpack', '= 3.0.pre')
s.add_dependency('thor', '~> 0.13')
s.add_dependency('activesupport', '= 3.0.pre')
s.add_dependency('actionpack', '= 3.0.pre')
s.rdoc_options << '--exclude' << '.'
s.has_rdoc = false