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