thor 0.13.7 → 0.13.8
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +1 -0
- data/lib/thor.rb +9 -5
- data/lib/thor/base.rb +2 -1
- data/lib/thor/group.rb +1 -1
- data/lib/thor/invocation.rb +1 -1
- data/lib/thor/parser/argument.rb +15 -15
- data/lib/thor/parser/option.rb +38 -46
- data/lib/thor/parser/options.rb +1 -1
- data/lib/thor/task.rb +52 -40
- data/lib/thor/version.rb +1 -1
- data/spec/fixtures/script.thor +6 -0
- data/spec/parser/option_spec.rb +2 -2
- data/spec/spec_helper.rb +3 -4
- data/spec/task_spec.rb +5 -5
- data/spec/thor_spec.rb +32 -4
- metadata +4 -4
data/CHANGELOG.rdoc
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
== 0.13, released 2010-02-03
|
2
2
|
|
3
|
+
* Added :lazy_default which is only triggered if a switch is given
|
3
4
|
* Added Thor::Shell::HTML
|
4
5
|
* Decoupled Thor::Group and Thor, so it's easier to vendor
|
5
6
|
* Added check_unknown_options! in case you want error messages to be raised in valid switches
|
data/lib/thor.rb
CHANGED
@@ -23,6 +23,7 @@ class Thor
|
|
23
23
|
# ==== Parameters
|
24
24
|
# usage<String>
|
25
25
|
# description<String>
|
26
|
+
# options<String>
|
26
27
|
#
|
27
28
|
def desc(usage, description, options={})
|
28
29
|
if options[:for]
|
@@ -30,7 +31,7 @@ class Thor
|
|
30
31
|
task.usage = usage if usage
|
31
32
|
task.description = description if description
|
32
33
|
else
|
33
|
-
@usage, @desc = usage, description
|
34
|
+
@usage, @desc, @hide = usage, description, options[:hide] || false
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
@@ -135,6 +136,7 @@ class Thor
|
|
135
136
|
#
|
136
137
|
def start(original_args=ARGV, config={})
|
137
138
|
@@original_args = original_args
|
139
|
+
|
138
140
|
super do |given_args|
|
139
141
|
meth = given_args.first.to_s
|
140
142
|
|
@@ -154,7 +156,7 @@ class Thor
|
|
154
156
|
args, opts = given_args, {}
|
155
157
|
end
|
156
158
|
|
157
|
-
task ||= Thor::
|
159
|
+
task ||= Thor::DynamicTask.new(meth)
|
158
160
|
trailing = args[Range.new(arguments.size, -1)]
|
159
161
|
new(args, opts, config).invoke(task, trailing || [])
|
160
162
|
end
|
@@ -204,11 +206,12 @@ class Thor
|
|
204
206
|
# Returns tasks ready to be printed.
|
205
207
|
def printable_tasks(all = true, subcommand = false)
|
206
208
|
(all ? all_tasks : tasks).map do |_, task|
|
209
|
+
next if task.hidden?
|
207
210
|
item = []
|
208
211
|
item << banner(task, false, subcommand)
|
209
212
|
item << (task.description ? "# #{task.description.gsub(/\s+/m,' ')}" : "")
|
210
213
|
item
|
211
|
-
end
|
214
|
+
end.compact
|
212
215
|
end
|
213
216
|
|
214
217
|
def subcommands
|
@@ -239,8 +242,9 @@ class Thor
|
|
239
242
|
|
240
243
|
def create_task(meth) #:nodoc:
|
241
244
|
if @usage && @desc
|
242
|
-
|
243
|
-
|
245
|
+
base_class = @hide ? Thor::HiddenTask : Thor::Task
|
246
|
+
tasks[meth.to_s] = base_class.new(meth, @desc, @long_desc, @usage, method_options)
|
247
|
+
@usage, @desc, @long_desc, @method_options, @hide = nil
|
244
248
|
true
|
245
249
|
elsif self.all_tasks[meth.to_s] || meth.to_sym == :method_missing
|
246
250
|
true
|
data/lib/thor/base.rb
CHANGED
@@ -336,6 +336,7 @@ class Thor
|
|
336
336
|
def no_tasks
|
337
337
|
@no_tasks = true
|
338
338
|
yield
|
339
|
+
ensure
|
339
340
|
@no_tasks = false
|
340
341
|
end
|
341
342
|
|
@@ -449,7 +450,7 @@ class Thor
|
|
449
450
|
def build_option(name, options, scope) #:nodoc:
|
450
451
|
scope[name] = Thor::Option.new(name, options[:desc], options[:required],
|
451
452
|
options[:type], options[:default], options[:banner],
|
452
|
-
options[:group], options[:aliases])
|
453
|
+
options[:lazy_default], options[:group], options[:aliases])
|
453
454
|
end
|
454
455
|
|
455
456
|
# Receives a hash of options, parse them and add to the scope. This is a
|
data/lib/thor/group.rb
CHANGED
data/lib/thor/invocation.rb
CHANGED
@@ -156,7 +156,7 @@ class Thor
|
|
156
156
|
raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
|
157
157
|
|
158
158
|
task ||= klass.default_task if klass.respond_to?(:default_task)
|
159
|
-
task = klass.all_tasks[task.to_s] || Thor::
|
159
|
+
task = klass.all_tasks[task.to_s] || Thor::DynamicTask.new(task) if task && !task.is_a?(Thor::Task)
|
160
160
|
task
|
161
161
|
end
|
162
162
|
|
data/lib/thor/parser/argument.rb
CHANGED
@@ -31,10 +31,10 @@ class Thor
|
|
31
31
|
|
32
32
|
def show_default?
|
33
33
|
case default
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
34
|
+
when Array, String, Hash
|
35
|
+
!default.empty?
|
36
|
+
else
|
37
|
+
default
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -45,21 +45,21 @@ class Thor
|
|
45
45
|
end
|
46
46
|
|
47
47
|
def valid_type?(type)
|
48
|
-
VALID_TYPES.include?(type.to_sym)
|
48
|
+
self.class::VALID_TYPES.include?(type.to_sym)
|
49
49
|
end
|
50
50
|
|
51
51
|
def default_banner
|
52
52
|
case type
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
63
|
end
|
64
64
|
end
|
65
65
|
|
data/lib/thor/parser/option.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
class Thor
|
2
2
|
class Option < Argument #:nodoc:
|
3
|
-
attr_reader :aliases, :group
|
3
|
+
attr_reader :aliases, :group, :lazy_default
|
4
4
|
|
5
5
|
VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
|
6
6
|
|
7
|
-
def initialize(name, description=nil, required=nil, type=nil, default=nil, banner=nil, group=nil, aliases=nil)
|
7
|
+
def initialize(name, description=nil, required=nil, type=nil, default=nil, banner=nil, lazy_default=nil, group=nil, aliases=nil)
|
8
8
|
super(name, description, required, type, default, banner)
|
9
|
-
@
|
10
|
-
@group
|
9
|
+
@lazy_default = lazy_default
|
10
|
+
@group = group.to_s.capitalize if group
|
11
|
+
@aliases = [*aliases].compact
|
11
12
|
end
|
12
13
|
|
13
14
|
# This parse quick options given as method_options. It makes several
|
@@ -48,23 +49,22 @@ class Thor
|
|
48
49
|
default = value
|
49
50
|
|
50
51
|
type = case value
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
value.class.name.downcase.to_sym
|
52
|
+
when Symbol
|
53
|
+
default = nil
|
54
|
+
if VALID_TYPES.include?(value)
|
55
|
+
value
|
56
|
+
elsif required = (value == :required)
|
57
|
+
:string
|
58
|
+
end
|
59
|
+
when TrueClass, FalseClass
|
60
|
+
:boolean
|
61
|
+
when Numeric
|
62
|
+
:numeric
|
63
|
+
when Hash, Array, String
|
64
|
+
value.class.name.downcase.to_sym
|
65
65
|
end
|
66
66
|
|
67
|
-
self.new(name.to_s, nil, required, type, default, nil, nil, aliases)
|
67
|
+
self.new(name.to_s, nil, required, type, default, nil, nil, nil, aliases)
|
68
68
|
end
|
69
69
|
|
70
70
|
def switch_name
|
@@ -91,38 +91,30 @@ class Thor
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
else
|
101
|
-
super
|
102
|
-
end
|
94
|
+
VALID_TYPES.each do |type|
|
95
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
96
|
+
def #{type}?
|
97
|
+
self.type == #{type.inspect}
|
98
|
+
end
|
99
|
+
RUBY
|
103
100
|
end
|
104
101
|
|
105
|
-
|
106
|
-
|
107
|
-
def validate!
|
108
|
-
raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
|
109
|
-
end
|
110
|
-
|
111
|
-
def valid_type?(type)
|
112
|
-
VALID_TYPES.include?(type.to_sym)
|
113
|
-
end
|
102
|
+
protected
|
114
103
|
|
115
|
-
|
116
|
-
|
117
|
-
|
104
|
+
def validate!
|
105
|
+
raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
|
106
|
+
end
|
118
107
|
|
119
|
-
|
120
|
-
|
121
|
-
|
108
|
+
def dasherized?
|
109
|
+
name.index('-') == 0
|
110
|
+
end
|
122
111
|
|
123
|
-
|
124
|
-
|
125
|
-
|
112
|
+
def undasherize(str)
|
113
|
+
str.sub(/^-{1,2}/, '')
|
114
|
+
end
|
126
115
|
|
116
|
+
def dasherize(str)
|
117
|
+
(str.length > 1 ? "--" : "-") + str.gsub('_', '-')
|
118
|
+
end
|
127
119
|
end
|
128
120
|
end
|
data/lib/thor/parser/options.rb
CHANGED
@@ -150,7 +150,7 @@ class Thor
|
|
150
150
|
return nil # User set value to nil
|
151
151
|
elsif option.string? && !option.required?
|
152
152
|
# Return the default if there is one, else the human name
|
153
|
-
return option.default || option.human_name
|
153
|
+
return option.lazy_default || option.default || option.human_name
|
154
154
|
else
|
155
155
|
raise MalformattedArgumentError, "No value provided for option '#{switch}'"
|
156
156
|
end
|
data/lib/thor/task.rb
CHANGED
@@ -2,21 +2,6 @@ class Thor
|
|
2
2
|
class Task < Struct.new(:name, :description, :long_description, :usage, :options)
|
3
3
|
FILE_REGEXP = /^#{Regexp.escape(File.dirname(__FILE__))}/
|
4
4
|
|
5
|
-
# A dynamic task that handles method missing scenarios.
|
6
|
-
class Dynamic < Task
|
7
|
-
def initialize(name, options=nil)
|
8
|
-
super(name.to_s, "A dynamically-generated task", name.to_s, name.to_s, options)
|
9
|
-
end
|
10
|
-
|
11
|
-
def run(instance, args=[])
|
12
|
-
if (instance.methods & [name.to_s, name.to_sym]).empty?
|
13
|
-
super
|
14
|
-
else
|
15
|
-
instance.class.handle_no_task_error(name)
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
5
|
def initialize(name, description, long_description, usage, options=nil)
|
21
6
|
super(name.to_s, description, long_description, usage, options || {})
|
22
7
|
end
|
@@ -26,6 +11,10 @@ class Thor
|
|
26
11
|
self.options = other.options.dup if other.options
|
27
12
|
end
|
28
13
|
|
14
|
+
def hidden?
|
15
|
+
false
|
16
|
+
end
|
17
|
+
|
29
18
|
# By default, a task invokes a method in the thor class. You can change this
|
30
19
|
# implementation to create custom tasks.
|
31
20
|
def run(instance, args=[])
|
@@ -42,9 +31,8 @@ class Thor
|
|
42
31
|
# Returns the formatted usage by injecting given required arguments
|
43
32
|
# and required options into the given usage.
|
44
33
|
def formatted_usage(klass, namespace = true, subcommand = false)
|
45
|
-
namespace = klass.namespace unless namespace == false
|
46
|
-
|
47
34
|
if namespace
|
35
|
+
namespace = klass.namespace
|
48
36
|
formatted = "#{namespace.gsub(/^(default)/,'')}:"
|
49
37
|
formatted.sub!(/.$/, ' ') if subcommand
|
50
38
|
end
|
@@ -67,36 +55,60 @@ class Thor
|
|
67
55
|
formatted.strip
|
68
56
|
end
|
69
57
|
|
70
|
-
|
58
|
+
protected
|
71
59
|
|
72
|
-
|
73
|
-
|
74
|
-
|
60
|
+
def not_debugging?(instance)
|
61
|
+
!(instance.class.respond_to?(:debugging) && instance.class.debugging)
|
62
|
+
end
|
75
63
|
|
76
|
-
|
77
|
-
|
78
|
-
|
64
|
+
def required_options
|
65
|
+
@required_options ||= options.map{ |_, o| o.usage if o.required? }.compact.sort.join(" ")
|
66
|
+
end
|
79
67
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
68
|
+
# Given a target, checks if this class name is not a private/protected method.
|
69
|
+
def public_method?(instance) #:nodoc:
|
70
|
+
collection = instance.private_methods + instance.protected_methods
|
71
|
+
(collection & [name.to_s, name.to_sym]).empty?
|
72
|
+
end
|
85
73
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
74
|
+
def sans_backtrace(backtrace, caller) #:nodoc:
|
75
|
+
saned = backtrace.reject { |frame| frame =~ FILE_REGEXP }
|
76
|
+
saned -= caller
|
77
|
+
end
|
90
78
|
|
91
|
-
|
92
|
-
|
93
|
-
|
79
|
+
def handle_argument_error?(instance, error, caller)
|
80
|
+
not_debugging?(instance) && error.message =~ /wrong number of arguments/ && begin
|
81
|
+
saned = sans_backtrace(error.backtrace, caller)
|
82
|
+
# Ruby 1.9 always include the called method in the backtrace
|
83
|
+
saned.empty? || (saned.size == 1 && RUBY_VERSION >= "1.9")
|
94
84
|
end
|
85
|
+
end
|
95
86
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
87
|
+
def handle_no_method_error?(instance, error, caller)
|
88
|
+
not_debugging?(instance) &&
|
89
|
+
error.message =~ /^undefined method `#{name}' for #{Regexp.escape(instance.to_s)}$/
|
90
|
+
end
|
91
|
+
end
|
100
92
|
|
93
|
+
# A task that is hidden in help messages but still invocable.
|
94
|
+
class HiddenTask < Task
|
95
|
+
def hidden?
|
96
|
+
true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# A dynamic task that handles method missing scenarios.
|
101
|
+
class DynamicTask < Task
|
102
|
+
def initialize(name, options=nil)
|
103
|
+
super(name.to_s, "A dynamically-generated task", name.to_s, name.to_s, options)
|
104
|
+
end
|
105
|
+
|
106
|
+
def run(instance, args=[])
|
107
|
+
if (instance.methods & [name.to_s, name.to_sym]).empty?
|
108
|
+
super
|
109
|
+
else
|
110
|
+
instance.class.handle_no_task_error(name)
|
111
|
+
end
|
112
|
+
end
|
101
113
|
end
|
102
114
|
end
|
data/lib/thor/version.rb
CHANGED
data/spec/fixtures/script.thor
CHANGED
@@ -26,6 +26,11 @@ class MyScript < Thor
|
|
26
26
|
[type]
|
27
27
|
end
|
28
28
|
|
29
|
+
desc "hidden TYPE", "this is hidden", :hide => true
|
30
|
+
def hidden(type)
|
31
|
+
[type]
|
32
|
+
end
|
33
|
+
|
29
34
|
desc "foo BAR", <<END
|
30
35
|
do some fooing
|
31
36
|
This is more info!
|
@@ -67,6 +72,7 @@ END
|
|
67
72
|
end
|
68
73
|
|
69
74
|
method_options :all => :boolean
|
75
|
+
method_option :lazy, :lazy_default => "yes"
|
70
76
|
desc "with_optional NAME", "invoke with optional name"
|
71
77
|
def with_optional(name=nil)
|
72
78
|
[ name, options ]
|
data/spec/parser/option_spec.rb
CHANGED
@@ -6,8 +6,8 @@ describe Thor::Option do
|
|
6
6
|
Thor::Option.parse(key, value)
|
7
7
|
end
|
8
8
|
|
9
|
-
def option(name,
|
10
|
-
@option ||= Thor::Option.new(name,
|
9
|
+
def option(name, *args)
|
10
|
+
@option ||= Thor::Option.new(name, *args)
|
11
11
|
end
|
12
12
|
|
13
13
|
describe "#parse" do
|
data/spec/spec_helper.rb
CHANGED
@@ -10,7 +10,10 @@ require 'rdoc'
|
|
10
10
|
require 'diff/lcs' # You need diff/lcs installed to run specs (but not to run Thor).
|
11
11
|
require 'fakeweb' # You need fakeweb installed to run specs (but not to run Thor).
|
12
12
|
|
13
|
+
# Set shell to basic
|
14
|
+
$0 = "thor"
|
13
15
|
$thor_runner = true
|
16
|
+
Thor::Base.shell = Thor::Shell::Basic
|
14
17
|
|
15
18
|
# Load fixtures
|
16
19
|
load File.join(File.dirname(__FILE__), "fixtures", "task.thor")
|
@@ -18,10 +21,6 @@ load File.join(File.dirname(__FILE__), "fixtures", "group.thor")
|
|
18
21
|
load File.join(File.dirname(__FILE__), "fixtures", "script.thor")
|
19
22
|
load File.join(File.dirname(__FILE__), "fixtures", "invoke.thor")
|
20
23
|
|
21
|
-
# Set shell to basic
|
22
|
-
$0 = "thor"
|
23
|
-
Thor::Base.shell = Thor::Shell::Basic
|
24
|
-
|
25
24
|
Kernel.module_eval do
|
26
25
|
alias_method :must, :should
|
27
26
|
alias_method :must_not, :should_not
|
data/spec/task_spec.rb
CHANGED
@@ -31,16 +31,16 @@ describe Thor::Task do
|
|
31
31
|
|
32
32
|
describe "#dynamic" do
|
33
33
|
it "creates a dynamic task with the given name" do
|
34
|
-
Thor::
|
35
|
-
Thor::
|
36
|
-
Thor::
|
37
|
-
Thor::
|
34
|
+
Thor::DynamicTask.new('task').name.must == 'task'
|
35
|
+
Thor::DynamicTask.new('task').description.must == 'A dynamically-generated task'
|
36
|
+
Thor::DynamicTask.new('task').usage.must == 'task'
|
37
|
+
Thor::DynamicTask.new('task').options.must == {}
|
38
38
|
end
|
39
39
|
|
40
40
|
it "does not invoke an existing method" do
|
41
41
|
mock = mock()
|
42
42
|
mock.class.should_receive(:handle_no_task_error).with("to_s")
|
43
|
-
Thor::
|
43
|
+
Thor::DynamicTask.new('to_s').run(mock)
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
data/spec/thor_spec.rb
CHANGED
@@ -8,6 +8,17 @@ describe Thor do
|
|
8
8
|
options.must == { "force" => true }
|
9
9
|
end
|
10
10
|
|
11
|
+
it "sets method_option with given parameters" do
|
12
|
+
arg, options = MyScript.start(["with_optional"])
|
13
|
+
options.must == {}
|
14
|
+
|
15
|
+
arg, options = MyScript.start(["with_optional", "--lazy"])
|
16
|
+
options.must == { "lazy" => "yes" }
|
17
|
+
|
18
|
+
arg, options = MyScript.start(["with_optional", "--lazy", "yesyes!"])
|
19
|
+
options.must == { "lazy" => "yesyes!" }
|
20
|
+
end
|
21
|
+
|
11
22
|
describe "when :for is supplied" do
|
12
23
|
it "updates an already defined task" do
|
13
24
|
args, options = MyChildScript.start(["animal", "horse", "--other=fish"])
|
@@ -66,12 +77,19 @@ describe Thor do
|
|
66
77
|
end
|
67
78
|
|
68
79
|
describe "#desc" do
|
69
|
-
|
70
|
-
|
80
|
+
it "provides description for a task" do
|
81
|
+
content = capture(:stdout) { MyScript.start(["help"]) }
|
82
|
+
content.must =~ /thor my_script:zoo\s+# zoo around/m
|
71
83
|
end
|
72
84
|
|
73
|
-
it "provides
|
74
|
-
|
85
|
+
it "provides no namespace if $thor_runner is false" do
|
86
|
+
begin
|
87
|
+
$thor_runner = false
|
88
|
+
content = capture(:stdout) { MyScript.start(["help"]) }
|
89
|
+
content.must =~ /thor zoo\s+# zoo around/m
|
90
|
+
ensure
|
91
|
+
$thor_runner = true
|
92
|
+
end
|
75
93
|
end
|
76
94
|
|
77
95
|
describe "when :for is supplied" do
|
@@ -79,6 +97,16 @@ describe Thor do
|
|
79
97
|
capture(:stdout) { MyChildScript.start(["help"]) }.must =~ /animal KIND \s+# fish around/m
|
80
98
|
end
|
81
99
|
end
|
100
|
+
|
101
|
+
describe "when :hide is supplied" do
|
102
|
+
it "does not show the task in help" do
|
103
|
+
capture(:stdout) { MyScript.start(["help"]) }.must_not =~ /this is hidden/m
|
104
|
+
end
|
105
|
+
|
106
|
+
it "but the task is still invokcable not show the task in help" do
|
107
|
+
MyScript.start(["hidden", "yesyes"]).must == ["yesyes"]
|
108
|
+
end
|
109
|
+
end
|
82
110
|
end
|
83
111
|
|
84
112
|
describe "#method_options" do
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 59
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 13
|
9
|
-
-
|
10
|
-
version: 0.13.
|
9
|
+
- 8
|
10
|
+
version: 0.13.8
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Yehuda Katz
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2010-
|
19
|
+
date: 2010-07-16 00:00:00 +02:00
|
20
20
|
default_executable:
|
21
21
|
dependencies: []
|
22
22
|
|