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/argument.rb
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
class Thor
|
|
2
2
|
class Argument #:nodoc:
|
|
3
|
-
VALID_TYPES = [
|
|
3
|
+
VALID_TYPES = [:numeric, :hash, :array, :string]
|
|
4
4
|
|
|
5
|
-
attr_reader :name, :description, :required, :type, :default, :banner
|
|
6
|
-
|
|
5
|
+
attr_reader :name, :description, :enum, :required, :type, :default, :banner
|
|
6
|
+
alias_method :human_name, :name
|
|
7
7
|
|
|
8
|
-
def initialize(name,
|
|
8
|
+
def initialize(name, options = {})
|
|
9
9
|
class_name = self.class.name.split("::").last
|
|
10
10
|
|
|
11
|
+
type = options[:type]
|
|
12
|
+
|
|
11
13
|
raise ArgumentError, "#{class_name} name can't be nil." if name.nil?
|
|
12
14
|
raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type)
|
|
13
15
|
|
|
14
16
|
@name = name.to_s
|
|
15
|
-
@description =
|
|
16
|
-
@required = required
|
|
17
|
+
@description = options[:desc]
|
|
18
|
+
@required = options.key?(:required) ? options[:required] : true
|
|
17
19
|
@type = (type || :string).to_sym
|
|
18
|
-
@default = default
|
|
19
|
-
@banner = banner || default_banner
|
|
20
|
+
@default = options[:default]
|
|
21
|
+
@banner = options[:banner] || default_banner
|
|
22
|
+
@enum = options[:enum]
|
|
20
23
|
|
|
21
24
|
validate! # Trigger specific validations
|
|
22
25
|
end
|
|
@@ -38,30 +41,30 @@ class Thor
|
|
|
38
41
|
end
|
|
39
42
|
end
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
protected
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
def valid_type?(type)
|
|
52
|
+
self.class::VALID_TYPES.include?(type.to_sym)
|
|
53
|
+
end
|
|
50
54
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
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"
|
|
64
67
|
end
|
|
65
|
-
|
|
68
|
+
end
|
|
66
69
|
end
|
|
67
70
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
class Thor
|
|
2
|
-
class Arguments #:nodoc:
|
|
3
|
-
NUMERIC = /(\d*\.\d+|\d+)/
|
|
2
|
+
class Arguments #:nodoc: # rubocop:disable ClassLength
|
|
3
|
+
NUMERIC = /[-+]?(\d*\.\d+|\d+)/
|
|
4
4
|
|
|
5
5
|
# Receives an array of args and returns two arrays, one with arguments
|
|
6
6
|
# and one with switches.
|
|
@@ -13,7 +13,7 @@ class Thor
|
|
|
13
13
|
arguments << item
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
[arguments, args[Range.new(arguments.size, -1)]]
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def self.parse(*args)
|
|
@@ -23,12 +23,13 @@ class Thor
|
|
|
23
23
|
|
|
24
24
|
# Takes an array of Thor::Argument objects.
|
|
25
25
|
#
|
|
26
|
-
def initialize(arguments=[])
|
|
27
|
-
@assigns
|
|
26
|
+
def initialize(arguments = [])
|
|
27
|
+
@assigns = {}
|
|
28
|
+
@non_assigned_required = []
|
|
28
29
|
@switches = arguments
|
|
29
30
|
|
|
30
31
|
arguments.each do |argument|
|
|
31
|
-
if argument.default
|
|
32
|
+
if !argument.default.nil?
|
|
32
33
|
@assigns[argument.human_name] = argument.default
|
|
33
34
|
elsif argument.required?
|
|
34
35
|
@non_assigned_required << argument
|
|
@@ -49,113 +50,126 @@ class Thor
|
|
|
49
50
|
@assigns
|
|
50
51
|
end
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
def remaining
|
|
54
|
+
@pile
|
|
55
|
+
end
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
arg =~ /^--(no|skip)-([-\w]+)$/
|
|
56
|
-
$2
|
|
57
|
-
end
|
|
57
|
+
private
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
def no_or_skip?(arg)
|
|
60
|
+
arg =~ /^--(no|skip)-([-\w]+)$/
|
|
61
|
+
$2
|
|
62
|
+
end
|
|
62
63
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
def last?
|
|
65
|
+
@pile.empty?
|
|
66
|
+
end
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
def peek
|
|
69
|
+
@pile.first
|
|
70
|
+
end
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
else
|
|
75
|
-
@pile = arg + @pile
|
|
76
|
-
end
|
|
77
|
-
end
|
|
72
|
+
def shift
|
|
73
|
+
@pile.shift
|
|
74
|
+
end
|
|
78
75
|
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
def unshift(arg)
|
|
77
|
+
if arg.is_a?(Array)
|
|
78
|
+
@pile = arg + @pile
|
|
79
|
+
else
|
|
80
|
+
@pile.unshift(arg)
|
|
81
81
|
end
|
|
82
|
+
end
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
# [ "name:string", "age:integer" ]
|
|
87
|
-
#
|
|
88
|
-
# Becomes:
|
|
89
|
-
#
|
|
90
|
-
# { "name" => "string", "age" => "integer" }
|
|
91
|
-
#
|
|
92
|
-
def parse_hash(name)
|
|
93
|
-
return shift if peek.is_a?(Hash)
|
|
94
|
-
hash = {}
|
|
95
|
-
|
|
96
|
-
while current_is_value? && peek.include?(?:)
|
|
97
|
-
key, value = shift.split(':')
|
|
98
|
-
hash[key] = value
|
|
99
|
-
end
|
|
100
|
-
hash
|
|
101
|
-
end
|
|
84
|
+
def current_is_value?
|
|
85
|
+
peek && peek.to_s !~ /^-/
|
|
86
|
+
end
|
|
102
87
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
88
|
+
# Runs through the argument array getting strings that contains ":" and
|
|
89
|
+
# mark it as a hash:
|
|
90
|
+
#
|
|
91
|
+
# [ "name:string", "age:integer" ]
|
|
92
|
+
#
|
|
93
|
+
# Becomes:
|
|
94
|
+
#
|
|
95
|
+
# { "name" => "string", "age" => "integer" }
|
|
96
|
+
#
|
|
97
|
+
def parse_hash(name)
|
|
98
|
+
return shift if peek.is_a?(Hash)
|
|
99
|
+
hash = {}
|
|
100
|
+
|
|
101
|
+
while current_is_value? && peek.include?(":")
|
|
102
|
+
key, value = shift.split(":", 2)
|
|
103
|
+
raise MalformattedArgumentError, "You can't specify '#{key}' more than once in option '#{name}'; got #{key}:#{hash[key]} and #{key}:#{value}" if hash.include? key
|
|
104
|
+
hash[key] = value
|
|
120
105
|
end
|
|
106
|
+
hash
|
|
107
|
+
end
|
|
121
108
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
109
|
+
# Runs through the argument array getting all strings until no string is
|
|
110
|
+
# found or a switch is found.
|
|
111
|
+
#
|
|
112
|
+
# ["a", "b", "c"]
|
|
113
|
+
#
|
|
114
|
+
# And returns it as an array:
|
|
115
|
+
#
|
|
116
|
+
# ["a", "b", "c"]
|
|
117
|
+
#
|
|
118
|
+
def parse_array(name)
|
|
119
|
+
return shift if peek.is_a?(Array)
|
|
120
|
+
array = []
|
|
121
|
+
array << shift while current_is_value?
|
|
122
|
+
array
|
|
123
|
+
end
|
|
127
124
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
125
|
+
# Check if the peek is numeric format and return a Float or Integer.
|
|
126
|
+
# Check if the peek is included in enum if enum is provided.
|
|
127
|
+
# Otherwise raises an error.
|
|
128
|
+
#
|
|
129
|
+
def parse_numeric(name)
|
|
130
|
+
return shift if peek.is_a?(Numeric)
|
|
131
131
|
|
|
132
|
-
|
|
132
|
+
unless peek =~ NUMERIC && $& == peek
|
|
133
|
+
raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}"
|
|
133
134
|
end
|
|
134
135
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def parse_string(name)
|
|
140
|
-
if no_or_skip?(name)
|
|
141
|
-
nil
|
|
142
|
-
else
|
|
143
|
-
shift
|
|
136
|
+
value = $&.index(".") ? shift.to_f : shift.to_i
|
|
137
|
+
if @switches.is_a?(Hash) && switch = @switches[name]
|
|
138
|
+
if switch.enum && !switch.enum.include?(value)
|
|
139
|
+
raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
|
|
144
140
|
end
|
|
145
141
|
end
|
|
142
|
+
value
|
|
143
|
+
end
|
|
146
144
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
145
|
+
# Parse string:
|
|
146
|
+
# for --string-arg, just return the current value in the pile
|
|
147
|
+
# for --no-string-arg, nil
|
|
148
|
+
# Check if the peek is included in enum if enum is provided. Otherwise raises an error.
|
|
149
|
+
#
|
|
150
|
+
def parse_string(name)
|
|
151
|
+
if no_or_skip?(name)
|
|
152
|
+
nil
|
|
153
|
+
else
|
|
154
|
+
value = shift
|
|
155
|
+
if @switches.is_a?(Hash) && switch = @switches[name]
|
|
156
|
+
if switch.enum && !switch.enum.include?(value)
|
|
157
|
+
raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
|
|
158
|
+
end
|
|
157
159
|
end
|
|
160
|
+
value
|
|
158
161
|
end
|
|
162
|
+
end
|
|
159
163
|
|
|
164
|
+
# Raises an error if @non_assigned_required array is not empty.
|
|
165
|
+
#
|
|
166
|
+
def check_requirement!
|
|
167
|
+
return if @non_assigned_required.empty?
|
|
168
|
+
names = @non_assigned_required.map do |o|
|
|
169
|
+
o.respond_to?(:switch_name) ? o.switch_name : o.human_name
|
|
170
|
+
end.join("', '")
|
|
171
|
+
class_name = self.class.name.split("::").last.downcase
|
|
172
|
+
raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'"
|
|
173
|
+
end
|
|
160
174
|
end
|
|
161
175
|
end
|
data/lib/thor/parser/option.rb
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
class Thor
|
|
2
2
|
class Option < Argument #:nodoc:
|
|
3
|
-
attr_reader :aliases, :group, :lazy_default
|
|
3
|
+
attr_reader :aliases, :group, :lazy_default, :hide
|
|
4
4
|
|
|
5
5
|
VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
|
|
6
6
|
|
|
7
|
-
def initialize(name,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@
|
|
11
|
-
@
|
|
7
|
+
def initialize(name, options = {})
|
|
8
|
+
options[:required] = false unless options.key?(:required)
|
|
9
|
+
super
|
|
10
|
+
@lazy_default = options[:lazy_default]
|
|
11
|
+
@group = options[:group].to_s.capitalize if options[:group]
|
|
12
|
+
@aliases = Array(options[:aliases])
|
|
13
|
+
@hide = options[:hide]
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
# This parse quick options given as method_options. It makes several
|
|
@@ -42,7 +44,8 @@ class Thor
|
|
|
42
44
|
if key.is_a?(Array)
|
|
43
45
|
name, *aliases = key
|
|
44
46
|
else
|
|
45
|
-
name
|
|
47
|
+
name = key
|
|
48
|
+
aliases = []
|
|
46
49
|
end
|
|
47
50
|
|
|
48
51
|
name = name.to_s
|
|
@@ -53,7 +56,7 @@ class Thor
|
|
|
53
56
|
default = nil
|
|
54
57
|
if VALID_TYPES.include?(value)
|
|
55
58
|
value
|
|
56
|
-
elsif required = (value == :required)
|
|
59
|
+
elsif required = (value == :required) # rubocop:disable AssignmentInCondition
|
|
57
60
|
:string
|
|
58
61
|
end
|
|
59
62
|
when TrueClass, FalseClass
|
|
@@ -64,7 +67,7 @@ class Thor
|
|
|
64
67
|
value.class.name.downcase.to_sym
|
|
65
68
|
end
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
new(name.to_s, :required => required, :type => type, :default => default, :aliases => aliases)
|
|
68
71
|
end
|
|
69
72
|
|
|
70
73
|
def switch_name
|
|
@@ -75,7 +78,7 @@ class Thor
|
|
|
75
78
|
@human_name ||= dasherized? ? undasherize(name) : name
|
|
76
79
|
end
|
|
77
80
|
|
|
78
|
-
def usage(padding=0)
|
|
81
|
+
def usage(padding = 0)
|
|
79
82
|
sample = if banner && !banner.to_s.empty?
|
|
80
83
|
"#{switch_name}=#{banner}"
|
|
81
84
|
else
|
|
@@ -84,6 +87,10 @@ class Thor
|
|
|
84
87
|
|
|
85
88
|
sample = "[#{sample}]" unless required?
|
|
86
89
|
|
|
90
|
+
if boolean?
|
|
91
|
+
sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-")
|
|
92
|
+
end
|
|
93
|
+
|
|
87
94
|
if aliases.empty?
|
|
88
95
|
(" " * padding) << sample
|
|
89
96
|
else
|
|
@@ -103,18 +110,37 @@ class Thor
|
|
|
103
110
|
|
|
104
111
|
def validate!
|
|
105
112
|
raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
|
|
113
|
+
validate_default_type!
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def validate_default_type!
|
|
117
|
+
default_type = case @default
|
|
118
|
+
when nil
|
|
119
|
+
return
|
|
120
|
+
when TrueClass, FalseClass
|
|
121
|
+
required? ? :string : :boolean
|
|
122
|
+
when Numeric
|
|
123
|
+
:numeric
|
|
124
|
+
when Symbol
|
|
125
|
+
:string
|
|
126
|
+
when Hash, Array, String
|
|
127
|
+
@default.class.name.downcase.to_sym
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# TODO: This should raise an ArgumentError in a future version of Thor
|
|
131
|
+
warn "Expected #{@type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" unless default_type == @type
|
|
106
132
|
end
|
|
107
133
|
|
|
108
134
|
def dasherized?
|
|
109
|
-
name.index(
|
|
135
|
+
name.index("-") == 0
|
|
110
136
|
end
|
|
111
137
|
|
|
112
138
|
def undasherize(str)
|
|
113
|
-
str.sub(/^-{1,2}/,
|
|
139
|
+
str.sub(/^-{1,2}/, "")
|
|
114
140
|
end
|
|
115
141
|
|
|
116
142
|
def dasherize(str)
|
|
117
|
-
(str.length > 1 ? "--" : "-") + str.
|
|
143
|
+
(str.length > 1 ? "--" : "-") + str.tr("_", "-")
|
|
118
144
|
end
|
|
119
145
|
end
|
|
120
146
|
end
|