thor 0.16.0 → 1.2.1
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.
- checksums.yaml +7 -0
- data/CONTRIBUTING.md +15 -0
- data/README.md +23 -6
- data/bin/thor +1 -1
- data/lib/thor/actions/create_file.rb +34 -35
- data/lib/thor/actions/create_link.rb +9 -5
- data/lib/thor/actions/directory.rb +33 -23
- data/lib/thor/actions/empty_directory.rb +75 -85
- data/lib/thor/actions/file_manipulation.rb +103 -36
- data/lib/thor/actions/inject_into_file.rb +46 -36
- data/lib/thor/actions.rb +90 -68
- data/lib/thor/base.rb +302 -244
- data/lib/thor/command.rb +142 -0
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +52 -24
- data/lib/thor/error.rb +90 -10
- data/lib/thor/group.rb +70 -74
- data/lib/thor/invocation.rb +63 -55
- data/lib/thor/line_editor/basic.rb +37 -0
- data/lib/thor/line_editor/readline.rb +88 -0
- data/lib/thor/line_editor.rb +17 -0
- data/lib/thor/nested_context.rb +29 -0
- data/lib/thor/parser/argument.rb +24 -28
- data/lib/thor/parser/arguments.rb +110 -102
- data/lib/thor/parser/option.rb +53 -15
- data/lib/thor/parser/options.rb +174 -97
- data/lib/thor/parser.rb +4 -4
- data/lib/thor/rake_compat.rb +12 -11
- data/lib/thor/runner.rb +159 -155
- data/lib/thor/shell/basic.rb +216 -93
- data/lib/thor/shell/color.rb +53 -40
- data/lib/thor/shell/html.rb +61 -58
- data/lib/thor/shell.rb +29 -36
- data/lib/thor/util.rb +231 -213
- data/lib/thor/version.rb +1 -1
- data/lib/thor.rb +303 -166
- data/thor.gemspec +27 -24
- metadata +36 -226
- data/.gitignore +0 -44
- data/.rspec +0 -2
- data/.travis.yml +0 -7
- data/CHANGELOG.rdoc +0 -134
- data/Gemfile +0 -15
- data/Thorfile +0 -30
- data/bin/rake2thor +0 -86
- data/lib/thor/core_ext/dir_escape.rb +0 -0
- data/lib/thor/core_ext/file_binary_read.rb +0 -9
- data/lib/thor/core_ext/ordered_hash.rb +0 -100
- data/lib/thor/task.rb +0 -132
- data/spec/actions/create_file_spec.rb +0 -170
- data/spec/actions/create_link_spec.rb +0 -81
- data/spec/actions/directory_spec.rb +0 -149
- data/spec/actions/empty_directory_spec.rb +0 -130
- data/spec/actions/file_manipulation_spec.rb +0 -370
- data/spec/actions/inject_into_file_spec.rb +0 -135
- data/spec/actions_spec.rb +0 -331
- data/spec/base_spec.rb +0 -279
- data/spec/core_ext/hash_with_indifferent_access_spec.rb +0 -43
- data/spec/core_ext/ordered_hash_spec.rb +0 -115
- data/spec/exit_condition_spec.rb +0 -19
- data/spec/fixtures/application.rb +0 -2
- data/spec/fixtures/app{1}/README +0 -3
- data/spec/fixtures/bundle/execute.rb +0 -6
- data/spec/fixtures/bundle/main.thor +0 -1
- data/spec/fixtures/doc/%file_name%.rb.tt +0 -1
- data/spec/fixtures/doc/COMMENTER +0 -10
- data/spec/fixtures/doc/README +0 -3
- data/spec/fixtures/doc/block_helper.rb +0 -3
- data/spec/fixtures/doc/components/.empty_directory +0 -0
- data/spec/fixtures/doc/config.rb +0 -1
- data/spec/fixtures/doc/config.yaml.tt +0 -1
- data/spec/fixtures/enum.thor +0 -10
- data/spec/fixtures/group.thor +0 -114
- data/spec/fixtures/invoke.thor +0 -112
- data/spec/fixtures/path with spaces +0 -0
- data/spec/fixtures/script.thor +0 -190
- data/spec/fixtures/task.thor +0 -10
- data/spec/group_spec.rb +0 -216
- data/spec/invocation_spec.rb +0 -100
- data/spec/parser/argument_spec.rb +0 -53
- data/spec/parser/arguments_spec.rb +0 -66
- data/spec/parser/option_spec.rb +0 -202
- data/spec/parser/options_spec.rb +0 -330
- data/spec/rake_compat_spec.rb +0 -72
- data/spec/register_spec.rb +0 -135
- data/spec/runner_spec.rb +0 -241
- data/spec/shell/basic_spec.rb +0 -300
- data/spec/shell/color_spec.rb +0 -81
- data/spec/shell/html_spec.rb +0 -32
- data/spec/shell_spec.rb +0 -47
- data/spec/spec_helper.rb +0 -59
- data/spec/task_spec.rb +0 -80
- data/spec/thor_spec.rb +0 -418
- data/spec/util_spec.rb +0 -196
data/lib/thor/invocation.rb
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
class Thor
|
2
2
|
module Invocation
|
3
3
|
def self.included(base) #:nodoc:
|
4
|
+
super(base)
|
4
5
|
base.extend ClassMethods
|
5
6
|
end
|
6
7
|
|
7
8
|
module ClassMethods
|
8
9
|
# This method is responsible for receiving a name and find the proper
|
9
|
-
# class and
|
10
|
+
# class and command for it. The key is an optional parameter which is
|
10
11
|
# available only in class methods invocations (i.e. in Thor::Group).
|
11
12
|
def prepare_for_invocation(key, name) #:nodoc:
|
12
13
|
case name
|
13
14
|
when Symbol, String
|
14
|
-
Thor::Util.
|
15
|
+
Thor::Util.find_class_and_command_by_namespace(name.to_s, !key)
|
15
16
|
else
|
16
17
|
name
|
17
18
|
end
|
@@ -19,32 +20,37 @@ class Thor
|
|
19
20
|
end
|
20
21
|
|
21
22
|
# Make initializer aware of invocations and the initialization args.
|
22
|
-
def initialize(args=[], options={}, config={}, &block) #:nodoc:
|
23
|
-
@_invocations = config[:invocations] || Hash.new { |h,k| h[k] = [] }
|
24
|
-
@_initializer = [
|
23
|
+
def initialize(args = [], options = {}, config = {}, &block) #:nodoc:
|
24
|
+
@_invocations = config[:invocations] || Hash.new { |h, k| h[k] = [] }
|
25
|
+
@_initializer = [args, options, config]
|
25
26
|
super
|
26
27
|
end
|
27
28
|
|
28
|
-
#
|
29
|
-
|
30
|
-
|
29
|
+
# Make the current command chain accessible with in a Thor-(sub)command
|
30
|
+
def current_command_chain
|
31
|
+
@_invocations.values.flatten.map(&:to_sym)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Receives a name and invokes it. The name can be a string (either "command" or
|
35
|
+
# "namespace:command"), a Thor::Command, a Class or a Thor instance. If the
|
36
|
+
# command cannot be guessed by name, it can also be supplied as second argument.
|
31
37
|
#
|
32
38
|
# You can also supply the arguments, options and configuration values for
|
33
|
-
# the
|
39
|
+
# the command to be invoked, if none is given, the same values used to
|
34
40
|
# initialize the invoker are used to initialize the invoked.
|
35
41
|
#
|
36
|
-
# When no name is given, it will invoke the default
|
42
|
+
# When no name is given, it will invoke the default command of the current class.
|
37
43
|
#
|
38
44
|
# ==== Examples
|
39
45
|
#
|
40
46
|
# class A < Thor
|
41
47
|
# def foo
|
42
48
|
# invoke :bar
|
43
|
-
# invoke "b:hello", ["
|
49
|
+
# invoke "b:hello", ["Erik"]
|
44
50
|
# end
|
45
51
|
#
|
46
52
|
# def bar
|
47
|
-
# invoke "b:hello", ["
|
53
|
+
# invoke "b:hello", ["Erik"]
|
48
54
|
# end
|
49
55
|
# end
|
50
56
|
#
|
@@ -54,16 +60,16 @@ class Thor
|
|
54
60
|
# end
|
55
61
|
# end
|
56
62
|
#
|
57
|
-
# You can notice that the method "foo" above invokes two
|
63
|
+
# You can notice that the method "foo" above invokes two commands: "bar",
|
58
64
|
# which belongs to the same class and "hello" which belongs to the class B.
|
59
65
|
#
|
60
|
-
# By using an invocation system you ensure that a
|
66
|
+
# By using an invocation system you ensure that a command is invoked only once.
|
61
67
|
# In the example above, invoking "foo" will invoke "b:hello" just once, even
|
62
68
|
# if it's invoked later by "bar" method.
|
63
69
|
#
|
64
70
|
# When class A invokes class B, all arguments used on A initialization are
|
65
71
|
# supplied to B. This allows lazy parse of options. Let's suppose you have
|
66
|
-
# some rspec
|
72
|
+
# some rspec commands:
|
67
73
|
#
|
68
74
|
# class Rspec < Thor::Group
|
69
75
|
# class_option :mock_framework, :type => :string, :default => :rr
|
@@ -93,37 +99,39 @@ class Thor
|
|
93
99
|
#
|
94
100
|
# invoke Rspec::RR, [], :style => :foo
|
95
101
|
#
|
96
|
-
def invoke(name=nil, *args)
|
102
|
+
def invoke(name = nil, *args)
|
97
103
|
if name.nil?
|
98
104
|
warn "[Thor] Calling invoke() without argument is deprecated. Please use invoke_all instead.\n#{caller.join("\n")}"
|
99
105
|
return invoke_all
|
100
106
|
end
|
101
107
|
|
102
|
-
args.unshift(nil) if
|
103
|
-
|
108
|
+
args.unshift(nil) if args.first.is_a?(Array) || args.first.nil?
|
109
|
+
command, args, opts, config = args
|
104
110
|
|
105
|
-
klass,
|
111
|
+
klass, command = _retrieve_class_and_command(name, command)
|
112
|
+
raise "Missing Thor class for invoke #{name}" unless klass
|
106
113
|
raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
|
107
114
|
|
108
115
|
args, opts, config = _parse_initialization_options(args, opts, config)
|
109
|
-
klass.send(:dispatch,
|
116
|
+
klass.send(:dispatch, command, args, opts, config) do |instance|
|
110
117
|
instance.parent_options = options
|
111
118
|
end
|
112
119
|
end
|
113
120
|
|
114
|
-
# Invoke the given
|
115
|
-
def
|
121
|
+
# Invoke the given command if the given args.
|
122
|
+
def invoke_command(command, *args) #:nodoc:
|
116
123
|
current = @_invocations[self.class]
|
117
124
|
|
118
|
-
unless current.include?(
|
119
|
-
current <<
|
120
|
-
|
125
|
+
unless current.include?(command.name)
|
126
|
+
current << command.name
|
127
|
+
command.run(self, *args)
|
121
128
|
end
|
122
129
|
end
|
130
|
+
alias_method :invoke_task, :invoke_command
|
123
131
|
|
124
|
-
# Invoke all
|
132
|
+
# Invoke all commands for the current instance.
|
125
133
|
def invoke_all #:nodoc:
|
126
|
-
self.class.
|
134
|
+
self.class.all_commands.map { |_, command| invoke_command(command) }
|
127
135
|
end
|
128
136
|
|
129
137
|
# Invokes using shell padding.
|
@@ -131,40 +139,40 @@ class Thor
|
|
131
139
|
with_padding { invoke(*args) }
|
132
140
|
end
|
133
141
|
|
134
|
-
|
142
|
+
protected
|
135
143
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
144
|
+
# Configuration values that are shared between invocations.
|
145
|
+
def _shared_configuration #:nodoc:
|
146
|
+
{:invocations => @_invocations}
|
147
|
+
end
|
140
148
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
[klass, task || sent_task]
|
154
|
-
end
|
149
|
+
# This method simply retrieves the class and command to be invoked.
|
150
|
+
# If the name is nil or the given name is a command in the current class,
|
151
|
+
# use the given name and return self as class. Otherwise, call
|
152
|
+
# prepare_for_invocation in the current class.
|
153
|
+
def _retrieve_class_and_command(name, sent_command = nil) #:nodoc:
|
154
|
+
if name.nil?
|
155
|
+
[self.class, nil]
|
156
|
+
elsif self.class.all_commands[name.to_s]
|
157
|
+
[self.class, name.to_s]
|
158
|
+
else
|
159
|
+
klass, command = self.class.prepare_for_invocation(nil, name)
|
160
|
+
[klass, command || sent_command]
|
155
161
|
end
|
162
|
+
end
|
163
|
+
alias_method :_retrieve_class_and_task, :_retrieve_class_and_command
|
156
164
|
|
157
|
-
|
158
|
-
|
159
|
-
|
165
|
+
# Initialize klass using values stored in the @_initializer.
|
166
|
+
def _parse_initialization_options(args, opts, config) #:nodoc:
|
167
|
+
stored_args, stored_opts, stored_config = @_initializer
|
160
168
|
|
161
|
-
|
162
|
-
|
169
|
+
args ||= stored_args.dup
|
170
|
+
opts ||= stored_opts.dup
|
163
171
|
|
164
|
-
|
165
|
-
|
172
|
+
config ||= {}
|
173
|
+
config = stored_config.merge(_shared_configuration).merge!(config)
|
166
174
|
|
167
|
-
|
168
|
-
|
175
|
+
[args, opts, config]
|
176
|
+
end
|
169
177
|
end
|
170
178
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Thor
|
2
|
+
module LineEditor
|
3
|
+
class Basic
|
4
|
+
attr_reader :prompt, :options
|
5
|
+
|
6
|
+
def self.available?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(prompt, options)
|
11
|
+
@prompt = prompt
|
12
|
+
@options = options
|
13
|
+
end
|
14
|
+
|
15
|
+
def readline
|
16
|
+
$stdout.print(prompt)
|
17
|
+
get_input
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def get_input
|
23
|
+
if echo?
|
24
|
+
$stdin.gets
|
25
|
+
else
|
26
|
+
# Lazy-load io/console since it is gem-ified as of 2.3
|
27
|
+
require "io/console"
|
28
|
+
$stdin.noecho(&:gets)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def echo?
|
33
|
+
options.fetch(:echo, true)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
class Thor
|
2
|
+
module LineEditor
|
3
|
+
class Readline < Basic
|
4
|
+
def self.available?
|
5
|
+
begin
|
6
|
+
require "readline"
|
7
|
+
rescue LoadError
|
8
|
+
end
|
9
|
+
|
10
|
+
Object.const_defined?(:Readline)
|
11
|
+
end
|
12
|
+
|
13
|
+
def readline
|
14
|
+
if echo?
|
15
|
+
::Readline.completion_append_character = nil
|
16
|
+
# rb-readline does not allow Readline.completion_proc= to receive nil.
|
17
|
+
if complete = completion_proc
|
18
|
+
::Readline.completion_proc = complete
|
19
|
+
end
|
20
|
+
::Readline.readline(prompt, add_to_history?)
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def add_to_history?
|
29
|
+
options.fetch(:add_to_history, true)
|
30
|
+
end
|
31
|
+
|
32
|
+
def completion_proc
|
33
|
+
if use_path_completion?
|
34
|
+
proc { |text| PathCompletion.new(text).matches }
|
35
|
+
elsif completion_options.any?
|
36
|
+
proc do |text|
|
37
|
+
completion_options.select { |option| option.start_with?(text) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def completion_options
|
43
|
+
options.fetch(:limited_to, [])
|
44
|
+
end
|
45
|
+
|
46
|
+
def use_path_completion?
|
47
|
+
options.fetch(:path, false)
|
48
|
+
end
|
49
|
+
|
50
|
+
class PathCompletion
|
51
|
+
attr_reader :text
|
52
|
+
private :text
|
53
|
+
|
54
|
+
def initialize(text)
|
55
|
+
@text = text
|
56
|
+
end
|
57
|
+
|
58
|
+
def matches
|
59
|
+
relative_matches
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def relative_matches
|
65
|
+
absolute_matches.map { |path| path.sub(base_path, "") }
|
66
|
+
end
|
67
|
+
|
68
|
+
def absolute_matches
|
69
|
+
Dir[glob_pattern].map do |path|
|
70
|
+
if File.directory?(path)
|
71
|
+
"#{path}/"
|
72
|
+
else
|
73
|
+
path
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def glob_pattern
|
79
|
+
"#{base_path}#{text}*"
|
80
|
+
end
|
81
|
+
|
82
|
+
def base_path
|
83
|
+
"#{Dir.pwd}/"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative "line_editor/basic"
|
2
|
+
require_relative "line_editor/readline"
|
3
|
+
|
4
|
+
class Thor
|
5
|
+
module LineEditor
|
6
|
+
def self.readline(prompt, options = {})
|
7
|
+
best_available.new(prompt, options).readline
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.best_available
|
11
|
+
[
|
12
|
+
Thor::LineEditor::Readline,
|
13
|
+
Thor::LineEditor::Basic
|
14
|
+
].detect(&:available?)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Thor
|
2
|
+
class NestedContext
|
3
|
+
def initialize
|
4
|
+
@depth = 0
|
5
|
+
end
|
6
|
+
|
7
|
+
def enter
|
8
|
+
push
|
9
|
+
|
10
|
+
yield
|
11
|
+
ensure
|
12
|
+
pop
|
13
|
+
end
|
14
|
+
|
15
|
+
def entered?
|
16
|
+
@depth > 0
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def push
|
22
|
+
@depth += 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def pop
|
26
|
+
@depth -= 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/thor/parser/argument.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
class Thor
|
2
2
|
class Argument #:nodoc:
|
3
|
-
VALID_TYPES = [
|
3
|
+
VALID_TYPES = [:numeric, :hash, :array, :string]
|
4
4
|
|
5
5
|
attr_reader :name, :description, :enum, :required, :type, :default, :banner
|
6
|
-
|
6
|
+
alias_method :human_name, :name
|
7
7
|
|
8
|
-
def initialize(name, options={})
|
8
|
+
def initialize(name, options = {})
|
9
9
|
class_name = self.class.name.split("::").last
|
10
10
|
|
11
11
|
type = options[:type]
|
@@ -41,34 +41,30 @@ class Thor
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
|
44
|
+
protected
|
45
45
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
raise ArgumentError, "An argument cannot have an enum other than an array."
|
51
|
-
end
|
52
|
-
end
|
46
|
+
def validate!
|
47
|
+
raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil?
|
48
|
+
raise ArgumentError, "An argument cannot have an enum other than an array." if @enum && !@enum.is_a?(Array)
|
49
|
+
end
|
53
50
|
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
def valid_type?(type)
|
52
|
+
self.class::VALID_TYPES.include?(type.to_sym)
|
53
|
+
end
|
57
54
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
55
|
+
def default_banner
|
56
|
+
case type
|
57
|
+
when :boolean
|
58
|
+
nil
|
59
|
+
when :string, :default
|
60
|
+
human_name.upcase
|
61
|
+
when :numeric
|
62
|
+
"N"
|
63
|
+
when :hash
|
64
|
+
"key:value"
|
65
|
+
when :array
|
66
|
+
"one two three"
|
71
67
|
end
|
72
|
-
|
68
|
+
end
|
73
69
|
end
|
74
70
|
end
|