thor 0.9.2 → 0.9.5
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +35 -0
- data/README.markdown +47 -32
- data/Rakefile +4 -39
- data/bin/rake2thor +83 -0
- data/bin/thor +4 -324
- data/lib/thor.rb +104 -97
- data/lib/thor/error.rb +3 -0
- data/lib/thor/options.rb +238 -0
- data/lib/thor/ordered_hash.rb +64 -0
- data/lib/thor/runner.rb +260 -0
- data/lib/thor/task.rb +68 -0
- data/lib/thor/task_hash.rb +22 -0
- data/lib/thor/tasks.rb +39 -38
- data/lib/thor/tasks/package.rb +18 -0
- data/lib/thor/util.rb +43 -0
- metadata +17 -10
- data/lib/getopt.rb +0 -238
- data/lib/vendor/ruby2ruby.rb +0 -1090
- data/lib/vendor/sexp.rb +0 -278
- data/lib/vendor/sexp_processor.rb +0 -336
- data/lib/vendor/unified_ruby.rb +0 -196
data/lib/thor.rb
CHANGED
@@ -1,12 +1,31 @@
|
|
1
1
|
$:.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
-
require "
|
2
|
+
require "thor/options"
|
3
|
+
require "thor/util"
|
4
|
+
require "thor/task"
|
5
|
+
require "thor/task_hash"
|
3
6
|
|
4
7
|
class Thor
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
+
attr_accessor :options
|
9
|
+
|
10
|
+
def self.map(map)
|
11
|
+
@map ||= superclass.instance_variable_get("@map") || {}
|
12
|
+
map.each do |key, value|
|
13
|
+
if key.respond_to?(:each)
|
14
|
+
key.each {|subkey| @map[subkey] = value}
|
15
|
+
else
|
16
|
+
@map[key] = value
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.desc(usage, description)
|
22
|
+
@usage, @desc = usage, description
|
8
23
|
end
|
9
24
|
|
25
|
+
def self.method_options(opts)
|
26
|
+
@method_options = opts
|
27
|
+
end
|
28
|
+
|
10
29
|
def self.subclass_files
|
11
30
|
@subclass_files ||= Hash.new {|h,k| h[k] = []}
|
12
31
|
end
|
@@ -15,117 +34,105 @@ class Thor
|
|
15
34
|
@subclasses ||= []
|
16
35
|
end
|
17
36
|
|
18
|
-
def self.
|
19
|
-
|
20
|
-
return if !public_instance_methods.include?(meth) || !@usage
|
21
|
-
@descriptions ||= []
|
22
|
-
@usages ||= []
|
23
|
-
@opts ||= []
|
24
|
-
|
25
|
-
@descriptions.delete(@descriptions.assoc(meth))
|
26
|
-
@descriptions << [meth, @desc]
|
27
|
-
|
28
|
-
@usages.delete(@usages.assoc(meth))
|
29
|
-
@usages << [meth, @usage]
|
30
|
-
|
31
|
-
@opts.delete(@opts.assoc(meth))
|
32
|
-
@opts << [meth, @method_options] if @method_options
|
33
|
-
|
34
|
-
@usage, @desc, @method_options = nil
|
37
|
+
def self.tasks
|
38
|
+
@tasks ||= TaskHash.new(self)
|
35
39
|
end
|
36
40
|
|
37
|
-
def self.
|
38
|
-
@
|
39
|
-
@map.merge! map
|
41
|
+
def self.opts
|
42
|
+
(@opts || {}).merge(self == Thor ? {} : superclass.opts)
|
40
43
|
end
|
41
44
|
|
42
|
-
def self.
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
@method_options = opts.inject({}) do |accum, (k,v)|
|
48
|
-
accum.merge("--" + k.to_s => v.to_s.upcase)
|
49
|
-
end
|
45
|
+
def self.[](task)
|
46
|
+
namespaces = task.split(":")
|
47
|
+
klass = Thor::Util.constant_from_thor_path(namespaces[0...-1].join(":"))
|
48
|
+
raise Error, "`#{klass}' is not a Thor class" unless klass <= Thor
|
49
|
+
klass.tasks[namespaces.last]
|
50
50
|
end
|
51
51
|
|
52
|
-
def self.
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
max_opts =
|
57
|
-
|
58
|
-
Struct.new(:klass, :usages, :opts, :descriptions, :max).new(
|
59
|
-
self, @usages, @opts, @descriptions, Struct.new(:usage, :opt, :desc).new(max_usage, max_opts, max_desc)
|
60
|
-
)
|
52
|
+
def self.maxima
|
53
|
+
@maxima ||= begin
|
54
|
+
max_usage = tasks.map {|_, t| t.usage}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
|
55
|
+
max_desc = tasks.map {|_, t| t.description}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
|
56
|
+
max_opts = tasks.map {|_, t| t.opts ? t.opts.formatted_usage : ""}.max {|x,y| x.to_s.size <=> y.to_s.size}.size
|
57
|
+
Struct.new(:description, :usage, :opt).new(max_desc, max_usage, max_opts)
|
61
58
|
end
|
62
59
|
end
|
63
60
|
|
64
|
-
def self.
|
65
|
-
|
66
|
-
opts
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
61
|
+
def self.start(args = ARGV)
|
62
|
+
options = Thor::Options.new(self.opts)
|
63
|
+
opts = options.parse(args, false)
|
64
|
+
args = options.trailing_non_opts
|
65
|
+
|
66
|
+
meth = args.first
|
67
|
+
meth = @map[meth].to_s if @map && @map[meth]
|
68
|
+
meth ||= "help"
|
69
|
+
|
70
|
+
tasks[meth].parse new(opts, *args), args[1..-1]
|
71
|
+
rescue Thor::Error => e
|
72
|
+
$stderr.puts e.message
|
75
73
|
end
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
break if ARGV.first =~ /^\-/
|
82
|
-
params << ARGV.shift
|
83
|
-
end
|
84
|
-
if defined?(@map) && @map[meth]
|
85
|
-
meth = @map[meth].to_s
|
74
|
+
|
75
|
+
class << self
|
76
|
+
protected
|
77
|
+
def inherited(klass)
|
78
|
+
register_klass_file klass
|
86
79
|
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
80
|
+
|
81
|
+
def method_added(meth)
|
82
|
+
meth = meth.to_s
|
83
|
+
|
84
|
+
if meth == "initialize"
|
85
|
+
@opts = @method_options
|
86
|
+
@method_options = nil
|
87
|
+
return
|
88
|
+
end
|
89
|
+
|
90
|
+
return if !public_instance_methods.include?(meth) || !@usage
|
91
|
+
register_klass_file self
|
92
|
+
|
93
|
+
tasks[meth] = Task.new(meth, @desc, @usage, @method_options)
|
94
|
+
|
95
|
+
@usage, @desc, @method_options = nil
|
94
96
|
end
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
rescue ArgumentError
|
106
|
-
puts "`#{op}' was called incorrectly. Call as `#{usage(op)}'"
|
97
|
+
|
98
|
+
def register_klass_file(klass, file = caller[1].split(":")[0])
|
99
|
+
unless self == Thor
|
100
|
+
superclass.register_klass_file(klass, file)
|
101
|
+
return
|
102
|
+
end
|
103
|
+
|
104
|
+
file_subclasses = subclass_files[File.expand_path(file)]
|
105
|
+
file_subclasses << klass unless file_subclasses.include?(klass)
|
106
|
+
subclasses << klass unless subclasses.include?(klass)
|
107
107
|
end
|
108
108
|
end
|
109
109
|
|
110
|
-
|
111
|
-
|
112
|
-
def usage(meth)
|
113
|
-
list = self.class.help_list
|
114
|
-
list.usages.assoc(meth)[1] + (list.opts.assoc(meth) ? " " + self.class.format_opts(list.opts.assoc(meth)[1]) : "")
|
110
|
+
def initialize(opts = {}, *args)
|
115
111
|
end
|
116
112
|
|
117
|
-
map "--help" => :help
|
113
|
+
map ["-h", "-?", "--help", "-D"] => :help
|
118
114
|
|
119
|
-
desc "help", "
|
120
|
-
def help
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
115
|
+
desc "help [TASK]", "describe available tasks or one specific task"
|
116
|
+
def help(task = nil)
|
117
|
+
if task
|
118
|
+
if task.include? ?:
|
119
|
+
task = self.class[task]
|
120
|
+
namespace = true
|
121
|
+
else
|
122
|
+
task = self.class.tasks[task]
|
123
|
+
end
|
124
|
+
|
125
|
+
puts task.formatted_usage(namespace)
|
126
|
+
puts task.description
|
127
|
+
else
|
128
|
+
puts "Options"
|
129
|
+
puts "-------"
|
130
|
+
self.class.tasks.each do |_, task|
|
131
|
+
format = "%-" + (self.class.maxima.usage + self.class.maxima.opt + 4).to_s + "s"
|
132
|
+
print format % ("#{task.formatted_usage}")
|
133
|
+
puts task.description.split("\n").first
|
134
|
+
end
|
128
135
|
end
|
129
136
|
end
|
130
137
|
|
131
|
-
end
|
138
|
+
end
|
data/lib/thor/error.rb
ADDED
data/lib/thor/options.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
# This is a modified version of Daniel Berger's Getopt::Long class,
|
2
|
+
# licensed under Ruby's license.
|
3
|
+
|
4
|
+
class Thor
|
5
|
+
class Options
|
6
|
+
class Error < StandardError; end
|
7
|
+
|
8
|
+
# simple Hash with indifferent access
|
9
|
+
class Hash < ::Hash
|
10
|
+
def initialize(hash)
|
11
|
+
super()
|
12
|
+
update hash
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
super convert_key(key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def values_at(*indices)
|
20
|
+
indices.collect { |key| self[convert_key(key)] }
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def convert_key(key)
|
25
|
+
key.kind_of?(Symbol) ? key.to_s : key
|
26
|
+
end
|
27
|
+
|
28
|
+
# Magic predicates. For instance:
|
29
|
+
# options.force? # => !!options['force']
|
30
|
+
def method_missing(method, *args, &block)
|
31
|
+
method.to_s =~ /^(\w+)\?$/ ? !!self[$1] : super
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
NUMERIC = /(\d*\.\d+|\d+)/
|
36
|
+
LONG_RE = /^(--\w+[-\w+]*)$/
|
37
|
+
SHORT_RE = /^(-[a-z])$/i
|
38
|
+
EQ_RE = /^(--\w+[-\w+]*|-[a-z])=(.*)$/i
|
39
|
+
SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
|
40
|
+
SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
|
41
|
+
|
42
|
+
attr_reader :leading_non_opts, :trailing_non_opts
|
43
|
+
|
44
|
+
def non_opts
|
45
|
+
leading_non_opts + trailing_non_opts
|
46
|
+
end
|
47
|
+
|
48
|
+
# Takes an array of switches. Each array consists of up to three
|
49
|
+
# elements that indicate the name and type of switch. Returns a hash
|
50
|
+
# containing each switch name, minus the '-', as a key. The value
|
51
|
+
# for each key depends on the type of switch and/or the value provided
|
52
|
+
# by the user.
|
53
|
+
#
|
54
|
+
# The long switch _must_ be provided. The short switch defaults to the
|
55
|
+
# first letter of the short switch. The default type is :boolean.
|
56
|
+
#
|
57
|
+
# Example:
|
58
|
+
#
|
59
|
+
# opts = Thor::Options.new(
|
60
|
+
# "--debug" => true,
|
61
|
+
# ["--verbose", "-v"] => true,
|
62
|
+
# ["--level", "-l"] => :numeric
|
63
|
+
# ).parse(args)
|
64
|
+
#
|
65
|
+
def initialize(switches)
|
66
|
+
@defaults = {}
|
67
|
+
@shorts = {}
|
68
|
+
|
69
|
+
@switches = switches.inject({}) do |mem, (name, type)|
|
70
|
+
if name.is_a?(Array)
|
71
|
+
name, *shorts = name
|
72
|
+
else
|
73
|
+
name = name.to_s
|
74
|
+
shorts = []
|
75
|
+
end
|
76
|
+
# we need both nice and dasherized form of switch name
|
77
|
+
if name.index('-') == 0
|
78
|
+
nice_name = undasherize name
|
79
|
+
else
|
80
|
+
nice_name = name
|
81
|
+
name = dasherize name
|
82
|
+
end
|
83
|
+
# if there are no shortcuts specified, generate one using the first character
|
84
|
+
shorts << "-" + nice_name[0,1] if shorts.empty? and nice_name.length > 1
|
85
|
+
shorts.each { |short| @shorts[short] = name }
|
86
|
+
|
87
|
+
# normalize type
|
88
|
+
case type
|
89
|
+
when TrueClass then type = :boolean
|
90
|
+
when String
|
91
|
+
@defaults[nice_name] = type
|
92
|
+
type = :optional
|
93
|
+
when Numeric
|
94
|
+
@defaults[nice_name] = type
|
95
|
+
type = :numeric
|
96
|
+
end
|
97
|
+
|
98
|
+
mem[name] = type
|
99
|
+
mem
|
100
|
+
end
|
101
|
+
|
102
|
+
# remove shortcuts that happen to coincide with any of the main switches
|
103
|
+
@shorts.keys.each do |short|
|
104
|
+
@shorts.delete(short) if @switches.key?(short)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def parse(args, skip_leading_non_opts = true)
|
109
|
+
@args = args
|
110
|
+
# start with Thor::Options::Hash pre-filled with defaults
|
111
|
+
hash = Hash.new @defaults
|
112
|
+
|
113
|
+
@leading_non_opts = []
|
114
|
+
if skip_leading_non_opts
|
115
|
+
@leading_non_opts << shift until current_is_option? || @args.empty?
|
116
|
+
end
|
117
|
+
|
118
|
+
while current_is_option?
|
119
|
+
case shift
|
120
|
+
when SHORT_SQ_RE
|
121
|
+
unshift $1.split('').map { |f| "-#{f}" }
|
122
|
+
next
|
123
|
+
when EQ_RE, SHORT_NUM
|
124
|
+
unshift $2
|
125
|
+
switch = $1
|
126
|
+
when LONG_RE, SHORT_RE
|
127
|
+
switch = $1
|
128
|
+
end
|
129
|
+
|
130
|
+
switch = normalize_switch(switch)
|
131
|
+
nice_name = undasherize(switch)
|
132
|
+
type = switch_type(switch)
|
133
|
+
|
134
|
+
case type
|
135
|
+
when :required
|
136
|
+
assert_value!(switch)
|
137
|
+
raise Error, "cannot pass switch '#{peek}' as an argument" if valid?(peek)
|
138
|
+
hash[nice_name] = shift
|
139
|
+
when :optional
|
140
|
+
hash[nice_name] = peek.nil? || valid?(peek) || shift
|
141
|
+
when :boolean
|
142
|
+
hash[nice_name] = true
|
143
|
+
when :numeric
|
144
|
+
assert_value!(switch)
|
145
|
+
unless peek =~ NUMERIC and $& == peek
|
146
|
+
raise Error, "expected numeric value for '#{switch}'; got #{peek.inspect}"
|
147
|
+
end
|
148
|
+
hash[nice_name] = $&.index('.') ? shift.to_f : shift.to_i
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
@trailing_non_opts = @args
|
153
|
+
|
154
|
+
check_required! hash
|
155
|
+
hash.freeze
|
156
|
+
hash
|
157
|
+
end
|
158
|
+
|
159
|
+
def formatted_usage
|
160
|
+
return "" if @switches.empty?
|
161
|
+
@switches.map do |opt, type|
|
162
|
+
case type
|
163
|
+
when :boolean
|
164
|
+
"[#{opt}]"
|
165
|
+
when :required
|
166
|
+
opt + "=" + opt.gsub(/\-/, "").upcase
|
167
|
+
else
|
168
|
+
sample = @defaults[undasherize(opt)]
|
169
|
+
sample ||= case type
|
170
|
+
when :optional then opt.gsub(/\-/, "").upcase
|
171
|
+
when :numeric then "N"
|
172
|
+
end
|
173
|
+
"[" + opt + "=" + sample.to_s + "]"
|
174
|
+
end
|
175
|
+
end.join(" ")
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def assert_value!(switch)
|
181
|
+
raise Error, "no value provided for argument '#{switch}'" if peek.nil?
|
182
|
+
end
|
183
|
+
|
184
|
+
def undasherize(str)
|
185
|
+
str.sub(/^-{1,2}/, '')
|
186
|
+
end
|
187
|
+
|
188
|
+
def dasherize(str)
|
189
|
+
(str.length > 1 ? "--" : "-") + str
|
190
|
+
end
|
191
|
+
|
192
|
+
def peek
|
193
|
+
@args.first
|
194
|
+
end
|
195
|
+
|
196
|
+
def shift
|
197
|
+
@args.shift
|
198
|
+
end
|
199
|
+
|
200
|
+
def unshift(arg)
|
201
|
+
unless arg.kind_of?(Array)
|
202
|
+
@args.unshift(arg)
|
203
|
+
else
|
204
|
+
@args = arg + @args
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def valid?(arg)
|
209
|
+
@switches.key?(arg) or @shorts.key?(arg)
|
210
|
+
end
|
211
|
+
|
212
|
+
def current_is_option?
|
213
|
+
case peek
|
214
|
+
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
|
215
|
+
valid?($1)
|
216
|
+
when SHORT_SQ_RE
|
217
|
+
$1.split('').any? { |f| valid?("-#{f}") }
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def normalize_switch(switch)
|
222
|
+
@shorts.key?(switch) ? @shorts[switch] : switch
|
223
|
+
end
|
224
|
+
|
225
|
+
def switch_type(switch)
|
226
|
+
@switches[switch]
|
227
|
+
end
|
228
|
+
|
229
|
+
def check_required!(hash)
|
230
|
+
for name, type in @switches
|
231
|
+
if type == :required and !hash[undasherize(name)]
|
232
|
+
raise Error, "no value provided for required argument '#{name}'"
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
end
|