wycats-thor 0.9.8 → 0.10.26
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +26 -3
- data/README.markdown +213 -42
- data/bin/rake2thor +4 -0
- data/bin/thor +1 -1
- data/lib/thor/actions/copy_file.rb +32 -0
- data/lib/thor/actions/create_file.rb +49 -0
- data/lib/thor/actions/directory.rb +76 -0
- data/lib/thor/actions/empty_directory.rb +30 -0
- data/lib/thor/actions/get.rb +58 -0
- data/lib/thor/actions/inject_into_file.rb +93 -0
- data/lib/thor/actions/template.rb +38 -0
- data/lib/thor/actions/templater.rb +195 -0
- data/lib/thor/actions.rb +328 -0
- data/lib/thor/base.rb +520 -0
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +75 -0
- data/lib/thor/core_ext/ordered_hash.rb +102 -0
- data/lib/thor/error.rb +25 -1
- data/lib/thor/group.rb +72 -0
- data/lib/thor/invocation.rb +161 -0
- data/lib/thor/parser/argument.rb +67 -0
- data/lib/thor/parser/arguments.rb +145 -0
- data/lib/thor/parser/option.rb +125 -0
- data/lib/thor/parser/options.rb +135 -0
- data/lib/thor/parser.rb +4 -0
- data/lib/thor/runner.rb +239 -230
- data/lib/thor/shell/basic.rb +221 -0
- data/lib/thor/shell/color.rb +106 -0
- data/lib/thor/shell.rb +72 -0
- data/lib/thor/task.rb +89 -60
- data/lib/thor/tasks/install.rb +35 -0
- data/lib/thor/tasks/package.rb +26 -13
- data/lib/thor/tasks/spec.rb +70 -0
- data/lib/thor/tasks.rb +3 -76
- data/lib/thor/util.rb +213 -41
- data/lib/thor.rb +206 -118
- metadata +38 -13
- data/lib/thor/options.rb +0 -242
- data/lib/thor/ordered_hash.rb +0 -64
- data/lib/thor/task_hash.rb +0 -22
@@ -0,0 +1,75 @@
|
|
1
|
+
class Thor
|
2
|
+
module CoreExt
|
3
|
+
|
4
|
+
# A hash with indifferent access and magic predicates.
|
5
|
+
#
|
6
|
+
# hash = Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
|
7
|
+
#
|
8
|
+
# hash[:foo] #=> 'bar'
|
9
|
+
# hash['foo'] #=> 'bar'
|
10
|
+
# hash.foo? #=> true
|
11
|
+
#
|
12
|
+
class HashWithIndifferentAccess < ::Hash
|
13
|
+
|
14
|
+
def initialize(hash={})
|
15
|
+
super()
|
16
|
+
hash.each do |key, value|
|
17
|
+
self[convert_key(key)] = value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](key)
|
22
|
+
super(convert_key(key))
|
23
|
+
end
|
24
|
+
|
25
|
+
def []=(key, value)
|
26
|
+
super(convert_key(key), value)
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete(key)
|
30
|
+
super(convert_key(key))
|
31
|
+
end
|
32
|
+
|
33
|
+
def values_at(*indices)
|
34
|
+
indices.collect { |key| self[convert_key(key)] }
|
35
|
+
end
|
36
|
+
|
37
|
+
def merge(other)
|
38
|
+
dup.merge!(other)
|
39
|
+
end
|
40
|
+
|
41
|
+
def merge!(other)
|
42
|
+
other.each do |key, value|
|
43
|
+
self[convert_key(key)] = value
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def convert_key(key)
|
51
|
+
key.is_a?(Symbol) ? key.to_s : key
|
52
|
+
end
|
53
|
+
|
54
|
+
# Magic predicates. For instance:
|
55
|
+
#
|
56
|
+
# options.force? # => !!options['force']
|
57
|
+
# options.shebang # => "/usr/lib/local/ruby"
|
58
|
+
# options.test_framework?(:rspec) # => options[:test_framework] == :rspec
|
59
|
+
#
|
60
|
+
def method_missing(method, *args, &block)
|
61
|
+
method = method.to_s
|
62
|
+
if method =~ /^(\w+)\?$/
|
63
|
+
if args.empty?
|
64
|
+
!!self[$1]
|
65
|
+
else
|
66
|
+
self[$1] == args.first
|
67
|
+
end
|
68
|
+
else
|
69
|
+
self[method]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
class Thor #:nodoc:
|
4
|
+
module CoreExt #:nodoc:
|
5
|
+
|
6
|
+
if RUBY_VERSION >= '1.9'
|
7
|
+
class OrderedHash < ::Hash
|
8
|
+
end
|
9
|
+
else
|
10
|
+
# This class is based on the Ruby 1.9 ordered hashes.
|
11
|
+
#
|
12
|
+
# It keeps the semantics and most of the efficiency of normal hashes
|
13
|
+
# while also keeping track of the order in which elements were set.
|
14
|
+
#
|
15
|
+
class OrderedHash #:nodoc:
|
16
|
+
include Enumerable
|
17
|
+
|
18
|
+
Node = Struct.new(:key, :value, :next, :prev)
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@hash = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def [](key)
|
25
|
+
@hash[key] && @hash[key].value
|
26
|
+
end
|
27
|
+
|
28
|
+
def []=(key, value)
|
29
|
+
if node = @hash[key]
|
30
|
+
node.value = value
|
31
|
+
else
|
32
|
+
node = Node.new(key, value)
|
33
|
+
|
34
|
+
if @first.nil?
|
35
|
+
@first = @last = node
|
36
|
+
else
|
37
|
+
node.prev = @last
|
38
|
+
@last.next = node
|
39
|
+
@last = node
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
@hash[key] = node
|
44
|
+
value
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete(key)
|
48
|
+
if node = @hash[key]
|
49
|
+
prev_node = node.prev
|
50
|
+
next_node = node.next
|
51
|
+
|
52
|
+
next_node.prev = prev_node if next_node
|
53
|
+
prev_node.next = next_node if prev_node
|
54
|
+
|
55
|
+
@first = next_node if @first == node
|
56
|
+
@last = prev_node if @last == node
|
57
|
+
|
58
|
+
value = node.value
|
59
|
+
end
|
60
|
+
|
61
|
+
@hash.delete(key)
|
62
|
+
value
|
63
|
+
end
|
64
|
+
|
65
|
+
def keys
|
66
|
+
self.map { |k, v| k }
|
67
|
+
end
|
68
|
+
|
69
|
+
def values
|
70
|
+
self.map { |k, v| v }
|
71
|
+
end
|
72
|
+
|
73
|
+
def each
|
74
|
+
return unless @first
|
75
|
+
yield [@first.key, @first.value]
|
76
|
+
node = @first
|
77
|
+
yield [node.key, node.value] while node = node.next
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
def merge(other)
|
82
|
+
hash = self.class.new
|
83
|
+
|
84
|
+
self.each do |key, value|
|
85
|
+
hash[key] = value
|
86
|
+
end
|
87
|
+
|
88
|
+
other.each do |key, value|
|
89
|
+
hash[key] = value
|
90
|
+
end
|
91
|
+
|
92
|
+
hash
|
93
|
+
end
|
94
|
+
|
95
|
+
def empty?
|
96
|
+
@hash.empty?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
data/lib/thor/error.rb
CHANGED
@@ -1,3 +1,27 @@
|
|
1
1
|
class Thor
|
2
|
-
|
2
|
+
# Thor::Error is raised when it's caused by the user invoking the task and
|
3
|
+
# only errors that inherit from it are rescued.
|
4
|
+
#
|
5
|
+
# So, for example, if the developer declares a required argument after an
|
6
|
+
# option, it should raise an ::ArgumentError and not ::Thor::ArgumentError,
|
7
|
+
# because it was caused by the developer and not the "final user".
|
8
|
+
#
|
9
|
+
class Error < StandardError #:nodoc:
|
10
|
+
end
|
11
|
+
|
12
|
+
# Raised when a task was not found.
|
13
|
+
#
|
14
|
+
class UndefinedTaskError < Error #:nodoc:
|
15
|
+
end
|
16
|
+
|
17
|
+
# Raised when a task was found, but not invoked properly.
|
18
|
+
#
|
19
|
+
class InvocationError < Error #:nodoc:
|
20
|
+
end
|
21
|
+
|
22
|
+
class RequiredArgumentMissingError < InvocationError #:nodoc:
|
23
|
+
end
|
24
|
+
|
25
|
+
class MalformattedArgumentError < InvocationError #:nodoc:
|
26
|
+
end
|
3
27
|
end
|
data/lib/thor/group.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
class Thor::Group
|
2
|
+
|
3
|
+
class << self
|
4
|
+
|
5
|
+
# The descrition for this Thor::Group. If none is provided, but a source root
|
6
|
+
# exists, tries to find the USAGE one folder above it, otherwise searches
|
7
|
+
# in the superclass.
|
8
|
+
#
|
9
|
+
# ==== Parameters
|
10
|
+
# description<String>:: The description for this Thor::Group.
|
11
|
+
#
|
12
|
+
def desc(description=nil)
|
13
|
+
case description
|
14
|
+
when nil
|
15
|
+
@desc ||= from_superclass(:desc, nil)
|
16
|
+
else
|
17
|
+
@desc = description
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Start works differently in Thor::Group, it simply invokes all tasks
|
22
|
+
# inside the class.
|
23
|
+
#
|
24
|
+
def start(given_args=ARGV, config={})
|
25
|
+
super do
|
26
|
+
if Thor::HELP_MAPPINGS.include?(given_args.first)
|
27
|
+
help(config[:shell])
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
args, opts = Thor::Options.split(given_args)
|
32
|
+
new(args, opts, config).invoke
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Prints help information.
|
37
|
+
#
|
38
|
+
# ==== Options
|
39
|
+
# short:: When true, shows only usage.
|
40
|
+
#
|
41
|
+
def help(shell, options={})
|
42
|
+
if options[:short]
|
43
|
+
shell.say banner
|
44
|
+
else
|
45
|
+
shell.say "Usage:"
|
46
|
+
shell.say " #{banner}"
|
47
|
+
shell.say
|
48
|
+
class_options_help(shell)
|
49
|
+
shell.say self.desc if self.desc
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
|
55
|
+
# The banner for this class. You can customize it if you are invoking the
|
56
|
+
# thor class by another means which is not the Thor::Runner.
|
57
|
+
#
|
58
|
+
def banner #:nodoc:
|
59
|
+
"#{self.namespace} #{self.arguments.map {|a| a.usage }.join(' ')}"
|
60
|
+
end
|
61
|
+
|
62
|
+
def baseclass #:nodoc:
|
63
|
+
Thor::Group
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_task(meth) #:nodoc:
|
67
|
+
tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
include Thor::Base
|
72
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
class Thor
|
2
|
+
module Invocation
|
3
|
+
|
4
|
+
# Make initializer aware of invocations and the initializer proc.
|
5
|
+
#
|
6
|
+
def initialize(args=[], options={}, config={}, &block) #:nodoc:
|
7
|
+
@_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] }
|
8
|
+
@_initializer = [ args, options, config ]
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
# Receives a name and invokes it. The name can be a string (either "task" or
|
13
|
+
# "namespace:task"), a Thor::Task, a Class or a Thor instance. If the task
|
14
|
+
# cannot be guessed by name, it can also be supplied as second argument.
|
15
|
+
#
|
16
|
+
# You can also supply the arguments, options and configuration values for
|
17
|
+
# the task to be invoked, if none is given, the same values used to
|
18
|
+
# initialize the invoker are used to initialize the invoked.
|
19
|
+
#
|
20
|
+
# ==== Examples
|
21
|
+
#
|
22
|
+
# class A < Thor
|
23
|
+
# def foo
|
24
|
+
# invoke :bar
|
25
|
+
# invoke "b:hello", ["José"]
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# def bar
|
29
|
+
# invoke "b:hello", ["José"]
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# class B < Thor
|
34
|
+
# def hello(name)
|
35
|
+
# puts "hello #{name}"
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# You can notice that the method "foo" above invokes two tasks: "bar",
|
40
|
+
# which belongs to the same class and "hello" which belongs to the class B.
|
41
|
+
#
|
42
|
+
# By using an invocation system you ensure that a task is invoked only once.
|
43
|
+
# In the example above, invoking "foo" will invoke "b:hello" just once, even
|
44
|
+
# if it's invoked later by "bar" method.
|
45
|
+
#
|
46
|
+
# When class A invokes class B, all arguments used on A initialization are
|
47
|
+
# supplied to B. This allows lazy parse of options. Let's suppose you have
|
48
|
+
# some rspec tasks:
|
49
|
+
#
|
50
|
+
# class Rspec < Thor::Group
|
51
|
+
# class_option :mock_framework, :type => :string, :default => :rr
|
52
|
+
#
|
53
|
+
# def invoke_mock_framework
|
54
|
+
# invoke "rspec:#{options[:mock_framework]}"
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# As you noticed, it invokes the given mock framework, which might have its
|
59
|
+
# own options:
|
60
|
+
#
|
61
|
+
# class Rspec::RR < Thor::Group
|
62
|
+
# class_option :style, :type => :string, :default => :mock
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# Since it's not rspec concern to parse mock framework options, when RR
|
66
|
+
# is invoked all options are parsed again, so RR can extract only the options
|
67
|
+
# that it's going to use.
|
68
|
+
#
|
69
|
+
# If you want Rspec::RR to be initialized with its own set of options, you
|
70
|
+
# have to do that explicitely:
|
71
|
+
#
|
72
|
+
# invoke "rspec:rr", [], :style => :foo
|
73
|
+
#
|
74
|
+
# Besides giving an instance, you can also give a class to invoke:
|
75
|
+
#
|
76
|
+
# invoke Rspec::RR, [], :style => :foo
|
77
|
+
#
|
78
|
+
def invoke(name=nil, task=nil, args=nil, opts=nil, config=nil)
|
79
|
+
task, args, opts, config = nil, task, args, opts if task.nil? || task.is_a?(Array)
|
80
|
+
args, opts, config = nil, args, opts if args.is_a?(Hash)
|
81
|
+
|
82
|
+
object, task = _setup_for_invoke(name, task)
|
83
|
+
if object.is_a?(Class)
|
84
|
+
klass = object
|
85
|
+
|
86
|
+
stored_args, stored_opts, stored_config = @_initializer
|
87
|
+
args ||= stored_args.dup
|
88
|
+
opts ||= stored_opts.dup
|
89
|
+
|
90
|
+
config ||= {}
|
91
|
+
config = stored_config.merge(_shared_configuration).merge!(config)
|
92
|
+
instance = klass.new(args, opts, config)
|
93
|
+
else
|
94
|
+
klass, instance = object.class, object
|
95
|
+
end
|
96
|
+
|
97
|
+
method_args = []
|
98
|
+
current = @_invocations[klass]
|
99
|
+
|
100
|
+
iterator = proc do |_, task|
|
101
|
+
unless current.include?(task.name)
|
102
|
+
current << task.name
|
103
|
+
task.run(instance, method_args)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
if task
|
108
|
+
args ||= []
|
109
|
+
method_args = args[Range.new(klass.arguments.size, -1)] || []
|
110
|
+
iterator.call(nil, task)
|
111
|
+
else
|
112
|
+
klass.all_tasks.map(&iterator)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
protected
|
117
|
+
|
118
|
+
# Configuration values that are shared between invocations.
|
119
|
+
#
|
120
|
+
def _shared_configuration
|
121
|
+
{ :invocations => @_invocations }
|
122
|
+
end
|
123
|
+
|
124
|
+
# This is the method responsable for retrieving and setting up an
|
125
|
+
# instance to be used in invoke.
|
126
|
+
#
|
127
|
+
def _setup_for_invoke(name, sent_task=nil) #:nodoc:
|
128
|
+
case name
|
129
|
+
when Thor::Task
|
130
|
+
task = name
|
131
|
+
when Symbol, String
|
132
|
+
name = name.to_s
|
133
|
+
|
134
|
+
# If is not one of this class tasks, do a lookup.
|
135
|
+
unless task = self.class.all_tasks[name]
|
136
|
+
object, task = Thor::Util.namespace_to_thor_class(name, false)
|
137
|
+
task ||= sent_task
|
138
|
+
end
|
139
|
+
else
|
140
|
+
object, task = name, sent_task
|
141
|
+
end
|
142
|
+
|
143
|
+
# If the object was not set, use self and use the name as task.
|
144
|
+
object, task = self, name unless object
|
145
|
+
return object, _validate_klass_and_task(object, task)
|
146
|
+
end
|
147
|
+
|
148
|
+
# Check if the object given is a Thor class object and get a task object
|
149
|
+
# for it.
|
150
|
+
#
|
151
|
+
def _validate_klass_and_task(object, task)
|
152
|
+
klass = object.is_a?(Class) ? object : object.class
|
153
|
+
raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
|
154
|
+
|
155
|
+
task ||= klass.default_task if klass <= Thor
|
156
|
+
task = klass.all_tasks[task.to_s] || Task.dynamic(task) if task && !task.is_a?(Thor::Task)
|
157
|
+
task
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class Thor
|
2
|
+
class Argument
|
3
|
+
VALID_TYPES = [ :numeric, :hash, :array, :string ]
|
4
|
+
|
5
|
+
attr_reader :name, :description, :required, :type, :default, :banner
|
6
|
+
alias :human_name :name
|
7
|
+
|
8
|
+
def initialize(name, description=nil, required=true, type=:string, default=nil, banner=nil)
|
9
|
+
class_name = self.class.name.split("::").last
|
10
|
+
|
11
|
+
raise ArgumentError, "#{class_name} name can't be nil." if name.nil?
|
12
|
+
raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type)
|
13
|
+
|
14
|
+
@name = name.to_s
|
15
|
+
@description = description
|
16
|
+
@required = required || false
|
17
|
+
@type = (type || :string).to_sym
|
18
|
+
@default = default
|
19
|
+
@banner = banner || default_banner
|
20
|
+
|
21
|
+
validate! # Trigger specific validations
|
22
|
+
end
|
23
|
+
|
24
|
+
def usage
|
25
|
+
required? ? banner : "[#{banner}]"
|
26
|
+
end
|
27
|
+
|
28
|
+
def required?
|
29
|
+
required
|
30
|
+
end
|
31
|
+
|
32
|
+
def show_default?
|
33
|
+
case default
|
34
|
+
when Array, String, Hash
|
35
|
+
!default.empty?
|
36
|
+
else
|
37
|
+
default
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
|
43
|
+
def validate!
|
44
|
+
raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
def valid_type?(type)
|
48
|
+
VALID_TYPES.include?(type.to_sym)
|
49
|
+
end
|
50
|
+
|
51
|
+
def default_banner
|
52
|
+
case type
|
53
|
+
when :boolean
|
54
|
+
nil
|
55
|
+
when :string, :default
|
56
|
+
human_name.upcase
|
57
|
+
when :numeric
|
58
|
+
"N"
|
59
|
+
when :hash
|
60
|
+
"key:value"
|
61
|
+
when :array
|
62
|
+
"one two three"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|