thor 0.9.2 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|