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