mirror of
https://github.com/github/rails.git
synced 2026-04-26 03:00:59 -04:00
No more vendored thor.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
require 'thor'
|
||||
require 'thor/group'
|
||||
require 'rails/generators/actions'
|
||||
|
||||
module Rails
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
20
railties/lib/rails/vendor/thor-0.12.3/LICENSE
vendored
20
railties/lib/rails/vendor/thor-0.12.3/LICENSE
vendored
@@ -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.
|
||||
234
railties/lib/rails/vendor/thor-0.12.3/README.rdoc
vendored
234
railties/lib/rails/vendor/thor-0.12.3/README.rdoc
vendored
@@ -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.
|
||||
63
railties/lib/rails/vendor/thor-0.12.3/Thorfile
vendored
63
railties/lib/rails/vendor/thor-0.12.3/Thorfile
vendored
@@ -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
|
||||
240
railties/lib/rails/vendor/thor-0.12.3/lib/thor.rb
vendored
240
railties/lib/rails/vendor/thor-0.12.3/lib/thor.rb
vendored
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
require 'thor/parser/argument'
|
||||
require 'thor/parser/arguments'
|
||||
require 'thor/parser/option'
|
||||
require 'thor/parser/options'
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -1,3 +0,0 @@
|
||||
class Thor
|
||||
VERSION = "0.12.3".freeze
|
||||
end
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user