thor 0.12.0 → 0.12.2
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/Thorfile +2 -1
- data/lib/thor.rb +29 -27
- data/lib/thor/actions.rb +5 -5
- data/lib/thor/actions/directory.rb +2 -4
- data/lib/thor/actions/file_manipulation.rb +8 -4
- data/lib/thor/base.rb +30 -32
- data/lib/thor/group.rb +7 -11
- data/lib/thor/runner.rb +7 -3
- data/lib/thor/shell.rb +1 -1
- data/lib/thor/shell/basic.rb +49 -29
- data/lib/thor/task.rb +29 -39
- data/lib/thor/version.rb +1 -1
- data/spec/actions/directory_spec.rb +4 -3
- data/spec/actions/file_manipulation_spec.rb +14 -1
- data/spec/base_spec.rb +1 -1
- data/spec/fixtures/bundle/main.thor +1 -0
- data/spec/fixtures/doc/%file_name%.rb.tt +1 -0
- data/spec/fixtures/doc/README +3 -0
- data/spec/fixtures/group.thor +83 -0
- data/spec/fixtures/invoke.thor +112 -0
- data/spec/fixtures/script.thor +130 -0
- data/spec/fixtures/task.thor +10 -0
- data/spec/runner_spec.rb +5 -5
- data/spec/shell/basic_spec.rb +16 -22
- data/spec/spec.opts +1 -0
- data/spec/task_spec.rb +3 -23
- data/spec/thor_spec.rb +19 -17
- metadata +51 -16
data/lib/thor/task.rb
CHANGED
@@ -25,10 +25,6 @@ class Thor
|
|
25
25
|
self.options = other.options.dup if other.options
|
26
26
|
end
|
27
27
|
|
28
|
-
def short_description
|
29
|
-
description.split("\n").first if description
|
30
|
-
end
|
31
|
-
|
32
28
|
# By default, a task invokes a method in the thor class. You can change this
|
33
29
|
# implementation to create custom tasks.
|
34
30
|
#
|
@@ -36,49 +32,51 @@ class Thor
|
|
36
32
|
raise UndefinedTaskError, "the '#{name}' task of #{instance.class} is private" unless public_method?(instance)
|
37
33
|
instance.send(name, *args)
|
38
34
|
rescue ArgumentError => e
|
35
|
+
raise e if instance.class.respond_to?(:debugging) && instance.class.debugging
|
39
36
|
parse_argument_error(instance, e, caller)
|
40
37
|
rescue NoMethodError => e
|
38
|
+
raise e if instance.class.respond_to?(:debugging) && instance.class.debugging
|
41
39
|
parse_no_method_error(instance, e)
|
42
40
|
end
|
43
41
|
|
44
|
-
# Returns the formatted usage
|
45
|
-
#
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
"#{
|
42
|
+
# Returns the formatted usage by injecting given required arguments
|
43
|
+
# and required options into the given usage.
|
44
|
+
def formatted_usage(klass, namespace=nil)
|
45
|
+
namespace = klass.namespace if namespace.nil?
|
46
|
+
|
47
|
+
# Add namespace
|
48
|
+
formatted = if namespace
|
49
|
+
"#{namespace.gsub(/^(default|thor:runner:)/,'')}:"
|
52
50
|
else
|
53
51
|
""
|
54
52
|
end
|
55
53
|
|
56
|
-
|
57
|
-
formatted <<
|
58
|
-
formatted.strip!
|
59
|
-
formatted
|
60
|
-
end
|
61
|
-
|
62
|
-
# Injects the class arguments into the task usage.
|
63
|
-
#
|
64
|
-
def formatted_arguments(klass)
|
65
|
-
if klass && !klass.arguments.empty?
|
54
|
+
# Add usage with required arguments
|
55
|
+
formatted << if klass && !klass.arguments.empty?
|
66
56
|
usage.to_s.gsub(/^#{name}/) do |match|
|
67
|
-
match << " " << klass
|
57
|
+
match << " " << required_arguments(klass)
|
68
58
|
end
|
69
59
|
else
|
70
60
|
usage.to_s
|
71
61
|
end
|
72
|
-
end
|
73
62
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
63
|
+
# Add required options
|
64
|
+
formatted << " #{required_options}"
|
65
|
+
|
66
|
+
# Strip and go!
|
67
|
+
formatted.strip
|
78
68
|
end
|
79
69
|
|
80
70
|
protected
|
81
71
|
|
72
|
+
def required_arguments(klass)
|
73
|
+
klass.arguments.map{ |a| a.usage if a.required? }.compact.join(' ')
|
74
|
+
end
|
75
|
+
|
76
|
+
def required_options
|
77
|
+
@required_options ||= options.map{ |_, o| o.usage if o.required? }.compact.sort.join(" ")
|
78
|
+
end
|
79
|
+
|
82
80
|
# Given a target, checks if this class name is not a private/protected method.
|
83
81
|
#
|
84
82
|
def public_method?(instance) #:nodoc:
|
@@ -86,23 +84,15 @@ class Thor
|
|
86
84
|
(collection & [name.to_s, name.to_sym]).empty?
|
87
85
|
end
|
88
86
|
|
89
|
-
# Clean everything that comes from the Thor gempath and remove the caller.
|
90
|
-
#
|
91
|
-
def sans_backtrace(backtrace, caller) #:nodoc:
|
92
|
-
dirname = /^#{Regexp.escape(File.dirname(__FILE__))}/
|
93
|
-
saned = backtrace.reject { |frame| frame =~ dirname }
|
94
|
-
saned -= caller
|
95
|
-
end
|
96
|
-
|
97
87
|
def parse_argument_error(instance, e, caller) #:nodoc:
|
98
|
-
|
88
|
+
method_name = /`#{Regexp.escape(name.split(':').last)}'/
|
99
89
|
|
100
|
-
if
|
90
|
+
if e.message =~ /wrong number of arguments/ && e.backtrace.first.to_s =~ method_name
|
101
91
|
if instance.is_a?(Thor::Group)
|
102
92
|
raise e, "'#{name}' was called incorrectly. Are you sure it has arity equals to 0?"
|
103
93
|
else
|
104
94
|
raise InvocationError, "'#{name}' was called incorrectly. Call as " <<
|
105
|
-
"'#{formatted_usage(instance.class
|
95
|
+
"'#{formatted_usage(instance.class)}'"
|
106
96
|
end
|
107
97
|
else
|
108
98
|
raise e
|
data/lib/thor/version.rb
CHANGED
@@ -110,10 +110,11 @@ describe Thor::Actions::Directory do
|
|
110
110
|
end
|
111
111
|
|
112
112
|
it "yields a block" do
|
113
|
-
|
114
|
-
|
115
|
-
|
113
|
+
checked = false
|
114
|
+
invoke!("doc") do |content|
|
115
|
+
checked ||= !!(content =~ /FOO/)
|
116
116
|
end
|
117
|
+
checked.must be_true
|
117
118
|
end
|
118
119
|
end
|
119
120
|
|
@@ -65,13 +65,19 @@ describe Thor::Actions do
|
|
65
65
|
runner.inside("doc") do
|
66
66
|
action :copy_file, "README"
|
67
67
|
end
|
68
|
-
|
69
68
|
exists_and_identical?("doc/README", "doc/README")
|
70
69
|
end
|
71
70
|
|
72
71
|
it "logs status" do
|
73
72
|
action(:copy_file, "task.thor").must == " create task.thor\n"
|
74
73
|
end
|
74
|
+
|
75
|
+
it "accepts a block to change output" do
|
76
|
+
action :copy_file, "task.thor" do |content|
|
77
|
+
"OMG" + content
|
78
|
+
end
|
79
|
+
File.read(File.join(destination_root, "task.thor")).must =~ /^OMG/
|
80
|
+
end
|
75
81
|
end
|
76
82
|
|
77
83
|
describe "#get" do
|
@@ -126,6 +132,13 @@ describe Thor::Actions do
|
|
126
132
|
it "logs status" do
|
127
133
|
capture(:stdout){ runner.template("doc/config.rb") }.must == " create doc/config.rb\n"
|
128
134
|
end
|
135
|
+
|
136
|
+
it "accepts a block to change output" do
|
137
|
+
action :template, "doc/config.rb" do |content|
|
138
|
+
"OMG" + content
|
139
|
+
end
|
140
|
+
File.read(File.join(destination_root, "doc/config.rb")).must =~ /^OMG/
|
141
|
+
end
|
129
142
|
end
|
130
143
|
|
131
144
|
describe "when changing existent files" do
|
data/spec/base_spec.rb
CHANGED
@@ -134,7 +134,7 @@ describe Thor::Base do
|
|
134
134
|
it "allows extra options to be given" do
|
135
135
|
hash = { "Foo" => B.class_options.values }
|
136
136
|
|
137
|
-
content = capture(:stdout) { MyCounter.send(:class_options_help, Thor::Base.shell.new,
|
137
|
+
content = capture(:stdout) { MyCounter.send(:class_options_help, Thor::Base.shell.new, hash) }
|
138
138
|
content.must =~ /Foo options\:/
|
139
139
|
content.must =~ /--last-name=LAST_NAME/
|
140
140
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'execute')
|
@@ -0,0 +1 @@
|
|
1
|
+
FOO = <%= "FOO" %>
|
@@ -0,0 +1,83 @@
|
|
1
|
+
class MyCounter < Thor::Group
|
2
|
+
include Thor::Actions
|
3
|
+
add_runtime_options!
|
4
|
+
|
5
|
+
def self.get_from_super
|
6
|
+
from_superclass(:get_from_super, 13)
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.source_root
|
10
|
+
File.expand_path(File.dirname(__FILE__))
|
11
|
+
end
|
12
|
+
source_paths << File.expand_path("broken", File.dirname(__FILE__))
|
13
|
+
|
14
|
+
argument :first, :type => :numeric
|
15
|
+
argument :second, :type => :numeric, :default => 2
|
16
|
+
|
17
|
+
class_option :third, :type => :numeric, :desc => "The third argument", :default => 3,
|
18
|
+
:banner => "THREE", :aliases => "-t"
|
19
|
+
class_option :fourth, :type => :numeric, :desc => "The fourth argument"
|
20
|
+
|
21
|
+
desc <<-FOO
|
22
|
+
Description:
|
23
|
+
This generator run three tasks: one, two and three.
|
24
|
+
FOO
|
25
|
+
|
26
|
+
def one
|
27
|
+
first
|
28
|
+
end
|
29
|
+
|
30
|
+
def two
|
31
|
+
second
|
32
|
+
end
|
33
|
+
|
34
|
+
def three
|
35
|
+
options[:third]
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.inherited(base)
|
39
|
+
super
|
40
|
+
base.source_paths.unshift(File.expand_path(File.join(File.dirname(__FILE__), "doc")))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class ClearCounter < MyCounter
|
45
|
+
remove_argument :first, :second, :undefine => true
|
46
|
+
remove_class_option :third
|
47
|
+
|
48
|
+
def self.source_root
|
49
|
+
File.expand_path(File.join(File.dirname(__FILE__), "bundle"))
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class BrokenCounter < MyCounter
|
54
|
+
namespace "app:broken:counter"
|
55
|
+
class_option :fail, :type => :boolean, :default => false
|
56
|
+
|
57
|
+
class << self
|
58
|
+
undef_method :source_root
|
59
|
+
end
|
60
|
+
|
61
|
+
def one
|
62
|
+
options[:first]
|
63
|
+
end
|
64
|
+
|
65
|
+
def four
|
66
|
+
respond_to?(:fail)
|
67
|
+
end
|
68
|
+
|
69
|
+
def five
|
70
|
+
options[:fail] ? this_method_does_not_exist : 5
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class WhinyGenerator < Thor::Group
|
75
|
+
include Thor::Actions
|
76
|
+
|
77
|
+
def self.source_root
|
78
|
+
File.expand_path(File.dirname(__FILE__))
|
79
|
+
end
|
80
|
+
|
81
|
+
def wrong_arity(required)
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
class A < Thor
|
2
|
+
include Thor::Actions
|
3
|
+
|
4
|
+
desc "one", "invoke one"
|
5
|
+
def one
|
6
|
+
p 1
|
7
|
+
invoke :two
|
8
|
+
invoke :three
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "two", "invoke two"
|
12
|
+
def two
|
13
|
+
p 2
|
14
|
+
invoke :three
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "three", "invoke three"
|
18
|
+
def three
|
19
|
+
p 3
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "four", "invoke four"
|
23
|
+
def four
|
24
|
+
p 4
|
25
|
+
invoke "defined:five"
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "five N", "check if number is equal 5"
|
29
|
+
def five(number)
|
30
|
+
number == 5
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "invoker", "invoke a b task"
|
34
|
+
def invoker(*args)
|
35
|
+
invoke :b, :one, ["Jose"]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class B < Thor
|
40
|
+
class_option :last_name, :type => :string
|
41
|
+
|
42
|
+
desc "one FIRST_NAME", "invoke one"
|
43
|
+
def one(first_name)
|
44
|
+
"#{options.last_name}, #{first_name}"
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "two", "invoke two"
|
48
|
+
def two
|
49
|
+
options
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "three", "invoke three"
|
53
|
+
def three
|
54
|
+
self
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class C < Thor::Group
|
59
|
+
include Thor::Actions
|
60
|
+
|
61
|
+
def one
|
62
|
+
p 1
|
63
|
+
end
|
64
|
+
|
65
|
+
def two
|
66
|
+
p 2
|
67
|
+
end
|
68
|
+
|
69
|
+
def three
|
70
|
+
p 3
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Defined < Thor::Group
|
75
|
+
class_option :unused, :type => :boolean, :desc => "This option has no use"
|
76
|
+
|
77
|
+
def one
|
78
|
+
p 1
|
79
|
+
invoke "a:two"
|
80
|
+
invoke "a:three"
|
81
|
+
invoke "a:four"
|
82
|
+
invoke "defined:five"
|
83
|
+
end
|
84
|
+
|
85
|
+
def five
|
86
|
+
p 5
|
87
|
+
end
|
88
|
+
|
89
|
+
def print_status
|
90
|
+
say_status :finished, :counting
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class E < Thor::Group
|
95
|
+
invoke Defined
|
96
|
+
end
|
97
|
+
|
98
|
+
class F < Thor::Group
|
99
|
+
invoke "b:one" do |instance, klass, task|
|
100
|
+
instance.invoke klass, task, [ "Jose" ], :last_name => "Valim"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class G < Thor::Group
|
105
|
+
class_option :invoked, :type => :string, :default => "defined"
|
106
|
+
invoke_from_option :invoked
|
107
|
+
end
|
108
|
+
|
109
|
+
class H < Thor::Group
|
110
|
+
class_option :defined, :type => :boolean, :default => true
|
111
|
+
invoke_from_option :defined
|
112
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
class MyScript < Thor
|
2
|
+
group :script
|
3
|
+
default_task :example_default_task
|
4
|
+
|
5
|
+
map "-T" => :animal, ["-f", "--foo"] => :foo
|
6
|
+
|
7
|
+
desc "zoo", "zoo around"
|
8
|
+
def zoo
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "animal TYPE", "horse around"
|
13
|
+
|
14
|
+
no_tasks do
|
15
|
+
def this_is_not_a_task
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def animal(type)
|
20
|
+
[type]
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "foo BAR", <<END
|
24
|
+
do some fooing
|
25
|
+
This is more info!
|
26
|
+
Everyone likes more info!
|
27
|
+
END
|
28
|
+
method_option :force, :type => :boolean, :desc => "Force to do some fooing"
|
29
|
+
def foo(bar)
|
30
|
+
[bar, options]
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "example_default_task", "example!"
|
34
|
+
def example_default_task
|
35
|
+
options.empty? ? "default task" : options
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "call_myself_with_wrong_arity", "get the right error"
|
39
|
+
def call_myself_with_wrong_arity
|
40
|
+
call_myself_with_wrong_arity(4)
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "call_unexistent_method", "Call unexistent method inside a task"
|
44
|
+
def call_unexistent_method
|
45
|
+
boom!
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "long_description", "a" * 80
|
49
|
+
def long_description
|
50
|
+
end
|
51
|
+
|
52
|
+
method_options :all => :boolean
|
53
|
+
desc "with_optional NAME", "invoke with optional name"
|
54
|
+
def with_optional(name=nil)
|
55
|
+
[ name, options ]
|
56
|
+
end
|
57
|
+
|
58
|
+
class AnotherScript < Thor
|
59
|
+
desc "baz", "do some bazing"
|
60
|
+
def baz
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def method_missing(meth, *args)
|
67
|
+
if meth == :boom!
|
68
|
+
super
|
69
|
+
else
|
70
|
+
[meth, args]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
desc "what", "what"
|
75
|
+
def what
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
class MyChildScript < MyScript
|
80
|
+
remove_task :bar
|
81
|
+
|
82
|
+
method_options :force => :boolean, :param => :numeric
|
83
|
+
def initialize(*args)
|
84
|
+
super
|
85
|
+
end
|
86
|
+
|
87
|
+
desc "zoo", "zoo around"
|
88
|
+
method_options :param => :required
|
89
|
+
def zoo
|
90
|
+
options
|
91
|
+
end
|
92
|
+
|
93
|
+
desc "animal TYPE", "horse around"
|
94
|
+
def animal(type)
|
95
|
+
[type, options]
|
96
|
+
end
|
97
|
+
method_option :other, :type => :string, :default => "method default", :for => :animal
|
98
|
+
desc "animal KIND", "fish around", :for => :animal
|
99
|
+
|
100
|
+
desc "boom", "explodes everything"
|
101
|
+
def boom
|
102
|
+
end
|
103
|
+
|
104
|
+
remove_task :boom, :undefine => true
|
105
|
+
end
|
106
|
+
|
107
|
+
module Scripts
|
108
|
+
class MyScript < MyChildScript
|
109
|
+
argument :accessor, :type => :string
|
110
|
+
class_options :force => :boolean
|
111
|
+
method_option :new_option, :type => :string, :for => :example_default_task
|
112
|
+
|
113
|
+
def zoo
|
114
|
+
self.accessor
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class MyDefaults < Thor
|
119
|
+
namespace :default
|
120
|
+
desc "test", "prints 'test'"
|
121
|
+
def test
|
122
|
+
puts "test"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
class ChildDefault < Thor
|
127
|
+
namespace "default:child"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|