thor 0.14.6 → 0.19.4
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/.document +5 -0
- data/{CHANGELOG.rdoc → CHANGELOG.md} +91 -31
- data/CONTRIBUTING.md +15 -0
- data/{LICENSE → LICENSE.md} +2 -2
- data/README.md +36 -296
- data/bin/thor +1 -1
- data/lib/thor/actions/create_file.rb +33 -35
- data/lib/thor/actions/create_link.rb +7 -5
- data/lib/thor/actions/directory.rb +49 -24
- data/lib/thor/actions/empty_directory.rb +68 -67
- data/lib/thor/actions/file_manipulation.rb +85 -28
- data/lib/thor/actions/inject_into_file.rb +26 -32
- data/lib/thor/actions.rb +70 -66
- data/lib/thor/base.rb +314 -237
- data/lib/thor/command.rb +133 -0
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +34 -24
- data/lib/thor/core_ext/io_binary_read.rb +12 -0
- data/lib/thor/core_ext/ordered_hash.rb +94 -65
- data/lib/thor/error.rb +10 -8
- data/lib/thor/group.rb +74 -66
- data/lib/thor/invocation.rb +65 -56
- data/lib/thor/line_editor/basic.rb +35 -0
- data/lib/thor/line_editor/readline.rb +88 -0
- data/lib/thor/line_editor.rb +17 -0
- data/lib/thor/parser/argument.rb +32 -29
- data/lib/thor/parser/arguments.rb +107 -93
- data/lib/thor/parser/option.rb +39 -13
- data/lib/thor/parser/options.rb +144 -97
- data/lib/thor/parser.rb +4 -4
- data/lib/thor/rake_compat.rb +21 -16
- data/lib/thor/runner.rb +166 -153
- data/lib/thor/shell/basic.rb +268 -122
- data/lib/thor/shell/color.rb +84 -43
- data/lib/thor/shell/html.rb +71 -66
- data/lib/thor/shell.rb +30 -37
- data/lib/thor/util.rb +227 -188
- data/lib/thor/version.rb +1 -1
- data/lib/thor.rb +289 -131
- data/thor.gemspec +21 -0
- metadata +50 -189
- data/Thorfile +0 -24
- data/bin/rake2thor +0 -86
- data/lib/thor/core_ext/file_binary_read.rb +0 -9
- data/lib/thor/task.rb +0 -114
- data/spec/actions/create_file_spec.rb +0 -170
- data/spec/actions/directory_spec.rb +0 -136
- data/spec/actions/empty_directory_spec.rb +0 -98
- data/spec/actions/file_manipulation_spec.rb +0 -310
- data/spec/actions/inject_into_file_spec.rb +0 -135
- data/spec/actions_spec.rb +0 -322
- data/spec/base_spec.rb +0 -269
- data/spec/core_ext/hash_with_indifferent_access_spec.rb +0 -43
- data/spec/core_ext/ordered_hash_spec.rb +0 -115
- data/spec/fixtures/application.rb +0 -2
- 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/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/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 -184
- data/spec/fixtures/task.thor +0 -10
- data/spec/group_spec.rb +0 -178
- data/spec/invocation_spec.rb +0 -100
- data/spec/parser/argument_spec.rb +0 -47
- data/spec/parser/arguments_spec.rb +0 -64
- data/spec/parser/option_spec.rb +0 -202
- data/spec/parser/options_spec.rb +0 -319
- data/spec/rake_compat_spec.rb +0 -68
- data/spec/register_spec.rb +0 -92
- data/spec/runner_spec.rb +0 -210
- data/spec/shell/basic_spec.rb +0 -223
- data/spec/shell/color_spec.rb +0 -41
- data/spec/shell/html_spec.rb +0 -27
- data/spec/shell_spec.rb +0 -47
- data/spec/spec_helper.rb +0 -54
- data/spec/task_spec.rb +0 -69
- data/spec/thor_spec.rb +0 -334
- data/spec/util_spec.rb +0 -163
data/lib/thor/parser/options.rb
CHANGED
|
@@ -1,34 +1,36 @@
|
|
|
1
1
|
class Thor
|
|
2
|
-
|
|
3
|
-
# under Ruby's license.
|
|
4
|
-
#
|
|
5
|
-
class Options < Arguments #:nodoc:
|
|
2
|
+
class Options < Arguments #:nodoc: # rubocop:disable ClassLength
|
|
6
3
|
LONG_RE = /^(--\w+(?:-\w+)*)$/
|
|
7
4
|
SHORT_RE = /^(-[a-z])$/i
|
|
8
5
|
EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i
|
|
9
6
|
SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
|
|
10
7
|
SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
|
|
8
|
+
OPTS_END = "--".freeze
|
|
11
9
|
|
|
12
10
|
# Receives a hash and makes it switches.
|
|
13
11
|
def self.to_switches(options)
|
|
14
12
|
options.map do |key, value|
|
|
15
13
|
case value
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
when true
|
|
15
|
+
"--#{key}"
|
|
16
|
+
when Array
|
|
17
|
+
"--#{key} #{value.map(&:inspect).join(' ')}"
|
|
18
|
+
when Hash
|
|
19
|
+
"--#{key} #{value.map { |k, v| "#{k}:#{v}" }.join(' ')}"
|
|
20
|
+
when nil, false
|
|
21
|
+
""
|
|
22
|
+
else
|
|
23
|
+
"--#{key} #{value.inspect}"
|
|
26
24
|
end
|
|
27
25
|
end.join(" ")
|
|
28
26
|
end
|
|
29
27
|
|
|
30
28
|
# Takes a hash of Thor::Option and a hash with defaults.
|
|
31
|
-
|
|
29
|
+
#
|
|
30
|
+
# If +stop_on_unknown+ is true, #parse will stop as soon as it encounters
|
|
31
|
+
# an unknown option or a regular argument.
|
|
32
|
+
def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false)
|
|
33
|
+
@stop_on_unknown = stop_on_unknown
|
|
32
34
|
options = hash_options.values
|
|
33
35
|
super(options)
|
|
34
36
|
|
|
@@ -38,42 +40,74 @@ class Thor
|
|
|
38
40
|
@non_assigned_required.delete(hash_options[key])
|
|
39
41
|
end
|
|
40
42
|
|
|
41
|
-
@shorts
|
|
43
|
+
@shorts = {}
|
|
44
|
+
@switches = {}
|
|
45
|
+
@extra = []
|
|
42
46
|
|
|
43
47
|
options.each do |option|
|
|
44
48
|
@switches[option.switch_name] = option
|
|
45
49
|
|
|
46
50
|
option.aliases.each do |short|
|
|
47
|
-
|
|
51
|
+
name = short.to_s.sub(/^(?!\-)/, "-")
|
|
52
|
+
@shorts[name] ||= option.switch_name
|
|
48
53
|
end
|
|
49
54
|
end
|
|
50
55
|
end
|
|
51
56
|
|
|
52
|
-
def
|
|
57
|
+
def remaining
|
|
58
|
+
@extra
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def peek
|
|
62
|
+
return super unless @parsing_options
|
|
63
|
+
|
|
64
|
+
result = super
|
|
65
|
+
if result == OPTS_END
|
|
66
|
+
shift
|
|
67
|
+
@parsing_options = false
|
|
68
|
+
super
|
|
69
|
+
else
|
|
70
|
+
result
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def parse(args) # rubocop:disable MethodLength
|
|
53
75
|
@pile = args.dup
|
|
76
|
+
@parsing_options = true
|
|
54
77
|
|
|
55
78
|
while peek
|
|
56
|
-
|
|
79
|
+
if parsing_options?
|
|
80
|
+
match, is_switch = current_is_switch?
|
|
81
|
+
shifted = shift
|
|
57
82
|
|
|
58
|
-
|
|
59
|
-
|
|
83
|
+
if is_switch
|
|
84
|
+
case shifted
|
|
60
85
|
when SHORT_SQ_RE
|
|
61
|
-
unshift($1.split(
|
|
86
|
+
unshift($1.split("").map { |f| "-#{f}" })
|
|
62
87
|
next
|
|
63
88
|
when EQ_RE, SHORT_NUM
|
|
64
89
|
unshift($2)
|
|
65
90
|
switch = $1
|
|
66
91
|
when LONG_RE, SHORT_RE
|
|
67
92
|
switch = $1
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
switch = normalize_switch(switch)
|
|
96
|
+
option = switch_option(switch)
|
|
97
|
+
@assigns[option.human_name] = parse_peek(switch, option)
|
|
98
|
+
elsif @stop_on_unknown
|
|
99
|
+
@parsing_options = false
|
|
100
|
+
@extra << shifted
|
|
101
|
+
@extra << shift while peek
|
|
102
|
+
break
|
|
103
|
+
elsif match
|
|
104
|
+
@extra << shifted
|
|
105
|
+
@extra << shift while peek && peek !~ /^-/
|
|
106
|
+
else
|
|
107
|
+
@extra << shifted
|
|
68
108
|
end
|
|
69
|
-
|
|
70
|
-
switch = normalize_switch(switch)
|
|
71
|
-
option = switch_option(switch)
|
|
72
|
-
@assigns[option.human_name] = parse_peek(switch, option)
|
|
73
|
-
elsif match
|
|
74
|
-
@unknown << shift
|
|
75
109
|
else
|
|
76
|
-
shift
|
|
110
|
+
@extra << shift
|
|
77
111
|
end
|
|
78
112
|
end
|
|
79
113
|
|
|
@@ -85,89 +119,102 @@ class Thor
|
|
|
85
119
|
end
|
|
86
120
|
|
|
87
121
|
def check_unknown!
|
|
88
|
-
|
|
122
|
+
# an unknown option starts with - or -- and has no more --'s afterward.
|
|
123
|
+
unknown = @extra.select { |str| str =~ /^--?(?:(?!--).)*$/ }
|
|
124
|
+
raise UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'" unless unknown.empty?
|
|
89
125
|
end
|
|
90
126
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
127
|
+
protected
|
|
128
|
+
|
|
129
|
+
# Check if the current value in peek is a registered switch.
|
|
130
|
+
#
|
|
131
|
+
# Two booleans are returned. The first is true if the current value
|
|
132
|
+
# starts with a hyphen; the second is true if it is a registered switch.
|
|
133
|
+
def current_is_switch?
|
|
134
|
+
case peek
|
|
135
|
+
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
|
|
136
|
+
[true, switch?($1)]
|
|
137
|
+
when SHORT_SQ_RE
|
|
138
|
+
[true, $1.split("").any? { |f| switch?("-#{f}") }]
|
|
139
|
+
else
|
|
140
|
+
[false, false]
|
|
104
141
|
end
|
|
142
|
+
end
|
|
105
143
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
end
|
|
144
|
+
def current_is_switch_formatted?
|
|
145
|
+
case peek
|
|
146
|
+
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
|
|
147
|
+
true
|
|
148
|
+
else
|
|
149
|
+
false
|
|
113
150
|
end
|
|
151
|
+
end
|
|
114
152
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
153
|
+
def current_is_value?
|
|
154
|
+
peek && (!parsing_options? || super)
|
|
155
|
+
end
|
|
118
156
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
else
|
|
123
|
-
@switches[arg]
|
|
124
|
-
end
|
|
125
|
-
end
|
|
157
|
+
def switch?(arg)
|
|
158
|
+
switch_option(normalize_switch(arg))
|
|
159
|
+
end
|
|
126
160
|
|
|
127
|
-
|
|
128
|
-
#
|
|
129
|
-
|
|
130
|
-
|
|
161
|
+
def switch_option(arg)
|
|
162
|
+
if match = no_or_skip?(arg) # rubocop:disable AssignmentInCondition
|
|
163
|
+
@switches[arg] || @switches["--#{match}"]
|
|
164
|
+
else
|
|
165
|
+
@switches[arg]
|
|
131
166
|
end
|
|
167
|
+
end
|
|
132
168
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
169
|
+
# Check if the given argument is actually a shortcut.
|
|
170
|
+
#
|
|
171
|
+
def normalize_switch(arg)
|
|
172
|
+
(@shorts[arg] || arg).tr("_", "-")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def parsing_options?
|
|
176
|
+
peek
|
|
177
|
+
@parsing_options
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Parse boolean values which can be given as --foo=true, --foo or --no-foo.
|
|
181
|
+
#
|
|
182
|
+
def parse_boolean(switch)
|
|
183
|
+
if current_is_value?
|
|
184
|
+
if ["true", "TRUE", "t", "T", true].include?(peek)
|
|
185
|
+
shift
|
|
186
|
+
true
|
|
187
|
+
elsif ["false", "FALSE", "f", "F", false].include?(peek)
|
|
188
|
+
shift
|
|
189
|
+
false
|
|
146
190
|
else
|
|
147
|
-
|
|
191
|
+
true
|
|
148
192
|
end
|
|
193
|
+
else
|
|
194
|
+
@switches.key?(switch) || !no_or_skip?(switch)
|
|
149
195
|
end
|
|
196
|
+
end
|
|
150
197
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
end
|
|
198
|
+
# Parse the value at the peek analyzing if it requires an input or not.
|
|
199
|
+
#
|
|
200
|
+
def parse_peek(switch, option)
|
|
201
|
+
if parsing_options? && (current_is_switch_formatted? || last?)
|
|
202
|
+
if option.boolean?
|
|
203
|
+
# No problem for boolean types
|
|
204
|
+
elsif no_or_skip?(switch)
|
|
205
|
+
return nil # User set value to nil
|
|
206
|
+
elsif option.string? && !option.required?
|
|
207
|
+
# Return the default if there is one, else the human name
|
|
208
|
+
return option.lazy_default || option.default || option.human_name
|
|
209
|
+
elsif option.lazy_default
|
|
210
|
+
return option.lazy_default
|
|
211
|
+
else
|
|
212
|
+
raise MalformattedArgumentError, "No value provided for option '#{switch}'"
|
|
167
213
|
end
|
|
168
|
-
|
|
169
|
-
@non_assigned_required.delete(option)
|
|
170
|
-
send(:"parse_#{option.type}", switch)
|
|
171
214
|
end
|
|
215
|
+
|
|
216
|
+
@non_assigned_required.delete(option)
|
|
217
|
+
send(:"parse_#{option.type}", switch)
|
|
218
|
+
end
|
|
172
219
|
end
|
|
173
220
|
end
|
data/lib/thor/parser.rb
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
4
|
-
require
|
|
1
|
+
require "thor/parser/argument"
|
|
2
|
+
require "thor/parser/arguments"
|
|
3
|
+
require "thor/parser/option"
|
|
4
|
+
require "thor/parser/options"
|
data/lib/thor/rake_compat.rb
CHANGED
|
@@ -1,21 +1,25 @@
|
|
|
1
|
-
require
|
|
1
|
+
require "rake"
|
|
2
|
+
require "rake/dsl_definition"
|
|
2
3
|
|
|
3
4
|
class Thor
|
|
4
5
|
# Adds a compatibility layer to your Thor classes which allows you to use
|
|
5
6
|
# rake package tasks. For example, to use rspec rake tasks, one can do:
|
|
6
7
|
#
|
|
7
8
|
# require 'thor/rake_compat'
|
|
9
|
+
# require 'rspec/core/rake_task'
|
|
8
10
|
#
|
|
9
11
|
# class Default < Thor
|
|
10
12
|
# include Thor::RakeCompat
|
|
11
13
|
#
|
|
12
|
-
#
|
|
13
|
-
# t.spec_opts = ['--options',
|
|
14
|
+
# RSpec::Core::RakeTask.new(:spec) do |t|
|
|
15
|
+
# t.spec_opts = ['--options', './.rspec']
|
|
14
16
|
# t.spec_files = FileList['spec/**/*_spec.rb']
|
|
15
17
|
# end
|
|
16
18
|
# end
|
|
17
19
|
#
|
|
18
20
|
module RakeCompat
|
|
21
|
+
include Rake::DSL if defined?(Rake::DSL)
|
|
22
|
+
|
|
19
23
|
def self.rake_classes
|
|
20
24
|
@rake_classes ||= []
|
|
21
25
|
end
|
|
@@ -24,26 +28,27 @@ class Thor
|
|
|
24
28
|
# Hack. Make rakefile point to invoker, so rdoc task is generated properly.
|
|
25
29
|
rakefile = File.basename(caller[0].match(/(.*):\d+/)[1])
|
|
26
30
|
Rake.application.instance_variable_set(:@rakefile, rakefile)
|
|
27
|
-
|
|
31
|
+
rake_classes << base
|
|
28
32
|
end
|
|
29
33
|
end
|
|
30
34
|
end
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
alias
|
|
36
|
+
# override task on (main), for compatibility with Rake 0.9
|
|
37
|
+
instance_eval do
|
|
38
|
+
alias rake_namespace namespace
|
|
35
39
|
|
|
36
|
-
def task(*
|
|
37
|
-
task =
|
|
40
|
+
def task(*)
|
|
41
|
+
task = super
|
|
38
42
|
|
|
39
|
-
if klass = Thor::RakeCompat.rake_classes.last
|
|
40
|
-
non_namespaced_name = task.name.split(
|
|
43
|
+
if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition
|
|
44
|
+
non_namespaced_name = task.name.split(":").last
|
|
41
45
|
|
|
42
46
|
description = non_namespaced_name
|
|
43
|
-
description << task.arg_names.map{ |n| n.to_s.upcase }.join(
|
|
47
|
+
description << task.arg_names.map { |n| n.to_s.upcase }.join(" ")
|
|
44
48
|
description.strip!
|
|
45
49
|
|
|
46
|
-
klass.desc description,
|
|
50
|
+
klass.desc description, Rake.application.last_description || non_namespaced_name
|
|
51
|
+
Rake.application.last_description = nil
|
|
47
52
|
klass.send :define_method, non_namespaced_name do |*args|
|
|
48
53
|
Rake::Task[task.name.to_sym].invoke(*args)
|
|
49
54
|
end
|
|
@@ -52,15 +57,15 @@ class Object #:nodoc:
|
|
|
52
57
|
task
|
|
53
58
|
end
|
|
54
59
|
|
|
55
|
-
def namespace(name
|
|
56
|
-
if klass = Thor::RakeCompat.rake_classes.last
|
|
60
|
+
def namespace(name)
|
|
61
|
+
if klass = Thor::RakeCompat.rake_classes.last # rubocop:disable AssignmentInCondition
|
|
57
62
|
const_name = Thor::Util.camel_case(name.to_s).to_sym
|
|
58
63
|
klass.const_set(const_name, Class.new(Thor))
|
|
59
64
|
new_klass = klass.const_get(const_name)
|
|
60
65
|
Thor::RakeCompat.rake_classes << new_klass
|
|
61
66
|
end
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
super
|
|
64
69
|
Thor::RakeCompat.rake_classes.pop
|
|
65
70
|
end
|
|
66
71
|
end
|