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.
Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/.document +5 -0
  3. data/{CHANGELOG.rdoc → CHANGELOG.md} +91 -31
  4. data/CONTRIBUTING.md +15 -0
  5. data/{LICENSE → LICENSE.md} +2 -2
  6. data/README.md +36 -296
  7. data/bin/thor +1 -1
  8. data/lib/thor/actions/create_file.rb +33 -35
  9. data/lib/thor/actions/create_link.rb +7 -5
  10. data/lib/thor/actions/directory.rb +49 -24
  11. data/lib/thor/actions/empty_directory.rb +68 -67
  12. data/lib/thor/actions/file_manipulation.rb +85 -28
  13. data/lib/thor/actions/inject_into_file.rb +26 -32
  14. data/lib/thor/actions.rb +70 -66
  15. data/lib/thor/base.rb +314 -237
  16. data/lib/thor/command.rb +133 -0
  17. data/lib/thor/core_ext/hash_with_indifferent_access.rb +34 -24
  18. data/lib/thor/core_ext/io_binary_read.rb +12 -0
  19. data/lib/thor/core_ext/ordered_hash.rb +94 -65
  20. data/lib/thor/error.rb +10 -8
  21. data/lib/thor/group.rb +74 -66
  22. data/lib/thor/invocation.rb +65 -56
  23. data/lib/thor/line_editor/basic.rb +35 -0
  24. data/lib/thor/line_editor/readline.rb +88 -0
  25. data/lib/thor/line_editor.rb +17 -0
  26. data/lib/thor/parser/argument.rb +32 -29
  27. data/lib/thor/parser/arguments.rb +107 -93
  28. data/lib/thor/parser/option.rb +39 -13
  29. data/lib/thor/parser/options.rb +144 -97
  30. data/lib/thor/parser.rb +4 -4
  31. data/lib/thor/rake_compat.rb +21 -16
  32. data/lib/thor/runner.rb +166 -153
  33. data/lib/thor/shell/basic.rb +268 -122
  34. data/lib/thor/shell/color.rb +84 -43
  35. data/lib/thor/shell/html.rb +71 -66
  36. data/lib/thor/shell.rb +30 -37
  37. data/lib/thor/util.rb +227 -188
  38. data/lib/thor/version.rb +1 -1
  39. data/lib/thor.rb +289 -131
  40. data/thor.gemspec +21 -0
  41. metadata +50 -189
  42. data/Thorfile +0 -24
  43. data/bin/rake2thor +0 -86
  44. data/lib/thor/core_ext/file_binary_read.rb +0 -9
  45. data/lib/thor/task.rb +0 -114
  46. data/spec/actions/create_file_spec.rb +0 -170
  47. data/spec/actions/directory_spec.rb +0 -136
  48. data/spec/actions/empty_directory_spec.rb +0 -98
  49. data/spec/actions/file_manipulation_spec.rb +0 -310
  50. data/spec/actions/inject_into_file_spec.rb +0 -135
  51. data/spec/actions_spec.rb +0 -322
  52. data/spec/base_spec.rb +0 -269
  53. data/spec/core_ext/hash_with_indifferent_access_spec.rb +0 -43
  54. data/spec/core_ext/ordered_hash_spec.rb +0 -115
  55. data/spec/fixtures/application.rb +0 -2
  56. data/spec/fixtures/bundle/execute.rb +0 -6
  57. data/spec/fixtures/bundle/main.thor +0 -1
  58. data/spec/fixtures/doc/%file_name%.rb.tt +0 -1
  59. data/spec/fixtures/doc/README +0 -3
  60. data/spec/fixtures/doc/block_helper.rb +0 -3
  61. data/spec/fixtures/doc/components/.empty_directory +0 -0
  62. data/spec/fixtures/doc/config.rb +0 -1
  63. data/spec/fixtures/group.thor +0 -114
  64. data/spec/fixtures/invoke.thor +0 -112
  65. data/spec/fixtures/path with spaces +0 -0
  66. data/spec/fixtures/script.thor +0 -184
  67. data/spec/fixtures/task.thor +0 -10
  68. data/spec/group_spec.rb +0 -178
  69. data/spec/invocation_spec.rb +0 -100
  70. data/spec/parser/argument_spec.rb +0 -47
  71. data/spec/parser/arguments_spec.rb +0 -64
  72. data/spec/parser/option_spec.rb +0 -202
  73. data/spec/parser/options_spec.rb +0 -319
  74. data/spec/rake_compat_spec.rb +0 -68
  75. data/spec/register_spec.rb +0 -92
  76. data/spec/runner_spec.rb +0 -210
  77. data/spec/shell/basic_spec.rb +0 -223
  78. data/spec/shell/color_spec.rb +0 -41
  79. data/spec/shell/html_spec.rb +0 -27
  80. data/spec/shell_spec.rb +0 -47
  81. data/spec/spec_helper.rb +0 -54
  82. data/spec/task_spec.rb +0 -69
  83. data/spec/thor_spec.rb +0 -334
  84. data/spec/util_spec.rb +0 -163
@@ -1,34 +1,36 @@
1
1
  class Thor
2
- # This is a modified version of Daniel Berger's Getopt::Long class, licensed
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
- when true
17
- "--#{key}"
18
- when Array
19
- "--#{key} #{value.map{ |v| v.inspect }.join(' ')}"
20
- when Hash
21
- "--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}"
22
- when nil, false
23
- ""
24
- else
25
- "--#{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
+ ""
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
- 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)
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, @switches, @unknown = {}, {}, []
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
- @shorts[short.to_s] ||= option.switch_name
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 parse(args)
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
- match, is_switch = current_is_switch?
79
+ if parsing_options?
80
+ match, is_switch = current_is_switch?
81
+ shifted = shift
57
82
 
58
- if is_switch
59
- case shift
83
+ if is_switch
84
+ case shifted
60
85
  when SHORT_SQ_RE
61
- unshift($1.split('').map { |f| "-#{f}" })
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
- raise UnknownArgumentError, "Unknown switches '#{@unknown.join(', ')}'" unless @unknown.empty?
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
- protected
92
-
93
- # Returns true if the current value in peek is a registered switch.
94
- #
95
- def current_is_switch?
96
- case peek
97
- when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
98
- [true, switch?($1)]
99
- when SHORT_SQ_RE
100
- [true, $1.split('').any? { |f| switch?("-#{f}") }]
101
- else
102
- [false, false]
103
- end
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
- def current_is_switch_formatted?
107
- case peek
108
- when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
109
- true
110
- else
111
- false
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
- def switch?(arg)
116
- switch_option(normalize_switch(arg))
117
- end
153
+ def current_is_value?
154
+ peek && (!parsing_options? || super)
155
+ end
118
156
 
119
- def switch_option(arg)
120
- if match = no_or_skip?(arg)
121
- @switches[arg] || @switches["--#{match}"]
122
- else
123
- @switches[arg]
124
- end
125
- end
157
+ def switch?(arg)
158
+ switch_option(normalize_switch(arg))
159
+ end
126
160
 
127
- # Check if the given argument is actually a shortcut.
128
- #
129
- def normalize_switch(arg)
130
- (@shorts[arg] || arg).tr('_', '-')
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
- # Parse boolean values which can be given as --foo=true, --foo or --no-foo.
134
- #
135
- def parse_boolean(switch)
136
- if current_is_value?
137
- if ["true", "TRUE", "t", "T", true].include?(peek)
138
- shift
139
- true
140
- elsif ["false", "FALSE", "f", "F", false].include?(peek)
141
- shift
142
- false
143
- else
144
- true
145
- end
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
- @switches.key?(switch) || !no_or_skip?(switch)
191
+ true
148
192
  end
193
+ else
194
+ @switches.key?(switch) || !no_or_skip?(switch)
149
195
  end
196
+ end
150
197
 
151
- # Parse the value at the peek analyzing if it requires an input or not.
152
- #
153
- def parse_peek(switch, option)
154
- if current_is_switch_formatted? || last?
155
- if option.boolean?
156
- # No problem for boolean types
157
- elsif no_or_skip?(switch)
158
- return nil # User set value to nil
159
- elsif option.string? && !option.required?
160
- # Return the default if there is one, else the human name
161
- return option.lazy_default || option.default || option.human_name
162
- elsif option.lazy_default
163
- return option.lazy_default
164
- else
165
- raise MalformattedArgumentError, "No value provided for option '#{switch}'"
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 'thor/parser/argument'
2
- require 'thor/parser/arguments'
3
- require 'thor/parser/option'
4
- require 'thor/parser/options'
1
+ require "thor/parser/argument"
2
+ require "thor/parser/arguments"
3
+ require "thor/parser/option"
4
+ require "thor/parser/options"
@@ -1,21 +1,25 @@
1
- require 'rake'
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
- # Spec::Rake::SpecTask.new(:spec) do |t|
13
- # t.spec_opts = ['--options', "spec/spec.opts"]
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
- self.rake_classes << base
31
+ rake_classes << base
28
32
  end
29
33
  end
30
34
  end
31
35
 
32
- class Object #:nodoc:
33
- alias :rake_task :task
34
- alias :rake_namespace :namespace
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(*args, &block)
37
- task = rake_task(*args, &block)
40
+ def task(*)
41
+ task = super
38
42
 
39
- if klass = Thor::RakeCompat.rake_classes.last
40
- non_namespaced_name = task.name.split(':').last
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, task.comment || non_namespaced_name
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, &block)
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
- rake_namespace(name, &block)
68
+ super
64
69
  Thor::RakeCompat.rake_classes.pop
65
70
  end
66
71
  end