bundler 1.2.0.pre.1 → 1.2.0.rc

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bundler might be problematic. Click here for more details.

Files changed (50) hide show
  1. data/CHANGELOG.md +27 -1
  2. data/ISSUES.md +3 -3
  3. data/lib/bundler.rb +7 -5
  4. data/lib/bundler/cli.rb +17 -20
  5. data/lib/bundler/definition.rb +5 -0
  6. data/lib/bundler/dsl.rb +1 -1
  7. data/lib/bundler/fetcher.rb +5 -6
  8. data/lib/bundler/gem_path_manipulation.rb +8 -0
  9. data/lib/bundler/ruby_version.rb +4 -2
  10. data/lib/bundler/runtime.rb +46 -19
  11. data/lib/bundler/source.rb +25 -10
  12. data/lib/bundler/templates/newgem/{LICENSE.tt → LICENSE.txt.tt} +0 -0
  13. data/lib/bundler/templates/newgem/Rakefile.tt +0 -1
  14. data/lib/bundler/templates/newgem/newgem.gemspec.tt +6 -4
  15. data/lib/bundler/vendor/thor.rb +49 -28
  16. data/lib/bundler/vendor/thor/actions.rb +7 -3
  17. data/lib/bundler/vendor/thor/actions/create_link.rb +1 -1
  18. data/lib/bundler/vendor/thor/actions/directory.rb +9 -4
  19. data/lib/bundler/vendor/thor/actions/empty_directory.rb +24 -5
  20. data/lib/bundler/vendor/thor/actions/file_manipulation.rb +39 -1
  21. data/lib/bundler/vendor/thor/base.rb +65 -24
  22. data/lib/bundler/vendor/thor/core_ext/dir_escape.rb +0 -0
  23. data/lib/bundler/vendor/thor/error.rb +6 -1
  24. data/lib/bundler/vendor/thor/group.rb +21 -9
  25. data/lib/bundler/vendor/thor/invocation.rb +4 -2
  26. data/lib/bundler/vendor/thor/parser/arguments.rb +4 -0
  27. data/lib/bundler/vendor/thor/parser/option.rb +3 -2
  28. data/lib/bundler/vendor/thor/parser/options.rb +13 -7
  29. data/lib/bundler/vendor/thor/rake_compat.rb +13 -8
  30. data/lib/bundler/vendor/thor/runner.rb +15 -3
  31. data/lib/bundler/vendor/thor/shell.rb +4 -4
  32. data/lib/bundler/vendor/thor/shell/basic.rb +169 -82
  33. data/lib/bundler/vendor/thor/shell/color.rb +40 -4
  34. data/lib/bundler/vendor/thor/shell/html.rb +28 -26
  35. data/lib/bundler/vendor/thor/task.rb +24 -5
  36. data/lib/bundler/vendor/thor/util.rb +43 -6
  37. data/lib/bundler/vendor/thor/version.rb +1 -1
  38. data/lib/bundler/version.rb +1 -1
  39. data/man/bundle-config.ronn +2 -2
  40. data/spec/bundler/definition_spec.rb +25 -0
  41. data/spec/cache/git_spec.rb +47 -0
  42. data/spec/cache/path_spec.rb +18 -0
  43. data/spec/install/git_spec.rb +21 -6
  44. data/spec/lock/lockfile_spec.rb +1 -1
  45. data/spec/other/check_spec.rb +14 -1
  46. data/spec/other/newgem_spec.rb +1 -0
  47. data/spec/other/platform_spec.rb +164 -0
  48. data/spec/runtime/setup_spec.rb +87 -0
  49. data/spec/runtime/with_clean_env_spec.rb +14 -0
  50. metadata +78 -133
@@ -1,6 +1,6 @@
1
1
  class Thor
2
2
  # Thor::Error is raised when it's caused by wrong usage of thor classes. Those
3
- # errors have their backtrace supressed and are nicely shown to the user.
3
+ # errors have their backtrace suppressed and are nicely shown to the user.
4
4
  #
5
5
  # Errors that are caused by the developer, like declaring a method which
6
6
  # overwrites a thor keyword, it SHOULD NOT raise a Thor::Error. This way, we
@@ -27,4 +27,9 @@ class Thor
27
27
 
28
28
  class MalformattedArgumentError < InvocationError
29
29
  end
30
+
31
+ # Raised when a user tries to call a private method encoded in templated filename.
32
+ #
33
+ class PrivateMethodEncodedError < Error
34
+ end
30
35
  end
@@ -104,7 +104,7 @@ class Thor::Group
104
104
  #
105
105
  # ==== Custom invocations
106
106
  #
107
- # You can also supply a block to customize how the option is giong to be
107
+ # You can also supply a block to customize how the option is going to be
108
108
  # invoked. The block receives two parameters, an instance of the current
109
109
  # class and the klass to be invoked.
110
110
  #
@@ -180,16 +180,16 @@ class Thor::Group
180
180
  end
181
181
  next unless value
182
182
 
183
- klass, task = prepare_for_invocation(name, value)
183
+ klass, _ = prepare_for_invocation(name, value)
184
184
  next unless klass && klass.respond_to?(:class_options)
185
185
 
186
186
  value = value.to_s
187
187
  human_name = value.respond_to?(:classify) ? value.classify : value
188
188
 
189
189
  group_options[human_name] ||= []
190
- group_options[human_name] += klass.class_options.values.select do |option|
191
- base_options[option.name.to_sym].nil? && option.group.nil? &&
192
- !group_options.values.flatten.any? { |i| i.name == option.name }
190
+ group_options[human_name] += klass.class_options.values.select do |class_option|
191
+ base_options[class_option.name.to_sym].nil? && class_option.group.nil? &&
192
+ !group_options.values.flatten.any? { |i| i.name == class_option.name }
193
193
  end
194
194
 
195
195
  yield klass if block_given?
@@ -204,8 +204,16 @@ class Thor::Group
204
204
  [item]
205
205
  end
206
206
 
207
- def handle_argument_error(task, error) #:nodoc:
208
- raise error, "#{task.name.inspect} was called incorrectly. Are you sure it has arity equals to 0?"
207
+ def handle_argument_error(task, error, arity=nil) #:nodoc:
208
+ if arity > 0
209
+ msg = "#{basename} #{task.name} takes #{arity} argument"
210
+ msg << "s" if arity > 1
211
+ msg << ", but it should not."
212
+ else
213
+ msg = "You should not pass arguments to #{basename} #{task.name}."
214
+ end
215
+
216
+ raise error, msg
209
217
  end
210
218
 
211
219
  protected
@@ -220,10 +228,14 @@ class Thor::Group
220
228
  args, opts = Thor::Options.split(given_args)
221
229
  opts = given_opts || opts
222
230
 
231
+ instance = new(args, opts, config)
232
+ yield instance if block_given?
233
+ args = instance.args
234
+
223
235
  if task
224
- new(args, opts, config).invoke_task(all_tasks[task])
236
+ instance.invoke_task(all_tasks[task])
225
237
  else
226
- new(args, opts, config).invoke_all
238
+ instance.invoke_all
227
239
  end
228
240
  end
229
241
 
@@ -85,7 +85,7 @@ class Thor
85
85
  # that it's going to use.
86
86
  #
87
87
  # If you want Rspec::RR to be initialized with its own set of options, you
88
- # have to do that explicitely:
88
+ # have to do that explicitly:
89
89
  #
90
90
  # invoke "rspec:rr", [], :style => :foo
91
91
  #
@@ -106,7 +106,9 @@ class Thor
106
106
  raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
107
107
 
108
108
  args, opts, config = _parse_initialization_options(args, opts, config)
109
- klass.send(:dispatch, task, args, opts, config)
109
+ klass.send(:dispatch, task, args, opts, config) do |instance|
110
+ instance.parent_options = options
111
+ end
110
112
  end
111
113
 
112
114
  # Invoke the given task if the given args.
@@ -49,6 +49,10 @@ class Thor
49
49
  @assigns
50
50
  end
51
51
 
52
+ def remaining
53
+ @pile
54
+ end
55
+
52
56
  private
53
57
 
54
58
  def no_or_skip?(arg)
@@ -1,14 +1,15 @@
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, description=nil, required=nil, type=nil, default=nil, banner=nil, lazy_default=nil, group=nil, aliases=nil)
7
+ def initialize(name, description=nil, required=nil, type=nil, default=nil, banner=nil, lazy_default=nil, group=nil, aliases=nil, hide=nil)
8
8
  super(name, description, required, type, default, banner)
9
9
  @lazy_default = lazy_default
10
10
  @group = group.to_s.capitalize if group
11
11
  @aliases = [*aliases].compact
12
+ @hide = hide
12
13
  end
13
14
 
14
15
  # This parse quick options given as method_options. It makes several
@@ -35,7 +35,7 @@ class Thor
35
35
  @non_assigned_required.delete(hash_options[key])
36
36
  end
37
37
 
38
- @shorts, @switches, @unknown = {}, {}, []
38
+ @shorts, @switches, @extra = {}, {}, []
39
39
 
40
40
  options.each do |option|
41
41
  @switches[option.switch_name] = option
@@ -46,14 +46,19 @@ class Thor
46
46
  end
47
47
  end
48
48
 
49
+ def remaining
50
+ @extra
51
+ end
52
+
49
53
  def parse(args)
50
54
  @pile = args.dup
51
55
 
52
56
  while peek
53
57
  match, is_switch = current_is_switch?
58
+ shifted = shift
54
59
 
55
60
  if is_switch
56
- case shift
61
+ case shifted
57
62
  when SHORT_SQ_RE
58
63
  unshift($1.split('').map { |f| "-#{f}" })
59
64
  next
@@ -68,9 +73,10 @@ class Thor
68
73
  option = switch_option(switch)
69
74
  @assigns[option.human_name] = parse_peek(switch, option)
70
75
  elsif match
71
- @unknown << shift
76
+ @extra << shifted
77
+ @extra << shift while peek && peek !~ /^-/
72
78
  else
73
- shift
79
+ @extra << shifted
74
80
  end
75
81
  end
76
82
 
@@ -82,9 +88,9 @@ class Thor
82
88
  end
83
89
 
84
90
  def check_unknown!
85
- unless ARGV.include?("exec") || ARGV.include?("config")
86
- raise UnknownArgumentError, "Unknown switches '#{@unknown.join(', ')}'" unless @unknown.empty?
87
- end
91
+ # 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?
88
94
  end
89
95
 
90
96
  protected
@@ -1,4 +1,5 @@
1
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
@@ -16,6 +17,8 @@ class Thor
16
17
  # end
17
18
  #
18
19
  module RakeCompat
20
+ include Rake::DSL if defined?(Rake::DSL)
21
+
19
22
  def self.rake_classes
20
23
  @rake_classes ||= []
21
24
  end
@@ -29,12 +32,12 @@ class Thor
29
32
  end
30
33
  end
31
34
 
32
- class Object #:nodoc:
33
- alias :rake_task :task
34
- alias :rake_namespace :namespace
35
+ # override task on (main), for compatibility with Rake 0.9
36
+ self.instance_eval do
37
+ alias rake_namespace namespace
35
38
 
36
- def task(*args, &block)
37
- task = rake_task(*args, &block)
39
+ def task(*)
40
+ task = super
38
41
 
39
42
  if klass = Thor::RakeCompat.rake_classes.last
40
43
  non_namespaced_name = task.name.split(':').last
@@ -43,7 +46,8 @@ class Object #:nodoc:
43
46
  description << task.arg_names.map{ |n| n.to_s.upcase }.join(' ')
44
47
  description.strip!
45
48
 
46
- klass.desc description, task.comment || non_namespaced_name
49
+ klass.desc description, Rake.application.last_description || non_namespaced_name
50
+ Rake.application.last_description = nil
47
51
  klass.send :define_method, non_namespaced_name do |*args|
48
52
  Rake::Task[task.name.to_sym].invoke(*args)
49
53
  end
@@ -52,7 +56,7 @@ class Object #:nodoc:
52
56
  task
53
57
  end
54
58
 
55
- def namespace(name, &block)
59
+ def namespace(name)
56
60
  if klass = Thor::RakeCompat.rake_classes.last
57
61
  const_name = Thor::Util.camel_case(name.to_s).to_sym
58
62
  klass.const_set(const_name, Class.new(Thor))
@@ -60,7 +64,8 @@ class Object #:nodoc:
60
64
  Thor::RakeCompat.rake_classes << new_klass
61
65
  end
62
66
 
63
- rake_namespace(name, &block)
67
+ super
64
68
  Thor::RakeCompat.rake_classes.pop
65
69
  end
66
70
  end
71
+
@@ -17,6 +17,7 @@ class Thor::Runner < Thor #:nodoc:
17
17
  if meth && !self.respond_to?(meth)
18
18
  initialize_thorfiles(meth)
19
19
  klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
20
+ self.class.handle_no_task_error(task, false) if klass.nil?
20
21
  klass.start(["-h", task].compact, :shell => self.shell)
21
22
  else
22
23
  super
@@ -30,6 +31,7 @@ class Thor::Runner < Thor #:nodoc:
30
31
  meth = meth.to_s
31
32
  initialize_thorfiles(meth)
32
33
  klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
34
+ self.class.handle_no_task_error(task, false) if klass.nil?
33
35
  args.unshift(task) if task
34
36
  klass.start(args, :shell => self.shell)
35
37
  end
@@ -124,7 +126,17 @@ class Thor::Runner < Thor #:nodoc:
124
126
 
125
127
  old_filename = thor_yaml[name][:filename]
126
128
  self.options = self.options.merge("as" => name)
127
- filename = install(thor_yaml[name][:location])
129
+
130
+ if File.directory? File.expand_path(name)
131
+ FileUtils.rm_rf(File.join(thor_root, old_filename))
132
+
133
+ thor_yaml.delete(old_filename)
134
+ save_yaml(thor_yaml)
135
+
136
+ filename = install(name)
137
+ else
138
+ filename = install(thor_yaml[name][:location])
139
+ end
128
140
 
129
141
  unless filename == old_filename
130
142
  File.delete(File.join(thor_root, old_filename))
@@ -190,7 +202,7 @@ class Thor::Runner < Thor #:nodoc:
190
202
  true
191
203
  end
192
204
 
193
- # Load the thorfiles. If relevant_to is supplied, looks for specific files
205
+ # Load the Thorfiles. If relevant_to is supplied, looks for specific files
194
206
  # in the thor_root instead of loading them all.
195
207
  #
196
208
  # By default, it also traverses the current path until find Thor files, as
@@ -244,7 +256,7 @@ class Thor::Runner < Thor #:nodoc:
244
256
  end
245
257
  end
246
258
 
247
- # Load thorfiles relevant to the given method. If you provide "foo:bar" it
259
+ # Load Thorfiles relevant to the given method. If you provide "foo:bar" it
248
260
  # will load all thor files in the thor.yaml that has "foo" e "foo:bar"
249
261
  # namespaces registered.
250
262
  #
@@ -8,7 +8,7 @@ class Thor
8
8
  def self.shell
9
9
  @shell ||= if ENV['THOR_SHELL'] && ENV['THOR_SHELL'].size > 0
10
10
  Thor::Shell.const_get(ENV['THOR_SHELL'])
11
- elsif RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
11
+ elsif ((RbConfig::CONFIG['host_os'] =~ /mswin|mingw/) && !(ENV['ANSICON']))
12
12
  Thor::Shell::Basic
13
13
  else
14
14
  Thor::Shell::Color
@@ -23,7 +23,7 @@ class Thor
23
23
  end
24
24
 
25
25
  module Shell
26
- SHELL_DELEGATED_METHODS = [:ask, :yes?, :no?, :say, :say_status, :print_table]
26
+ SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width]
27
27
 
28
28
  autoload :Basic, 'thor/shell/basic'
29
29
  autoload :Color, 'thor/shell/color'
@@ -62,8 +62,8 @@ class Thor
62
62
  # Common methods that are delegated to the shell.
63
63
  SHELL_DELEGATED_METHODS.each do |method|
64
64
  module_eval <<-METHOD, __FILE__, __LINE__
65
- def #{method}(*args)
66
- shell.#{method}(*args)
65
+ def #{method}(*args,&block)
66
+ shell.#{method}(*args,&block)
67
67
  end
68
68
  METHOD
69
69
  end
@@ -3,12 +3,13 @@ require 'tempfile'
3
3
  class Thor
4
4
  module Shell
5
5
  class Basic
6
- attr_accessor :base, :padding
6
+ attr_accessor :base
7
+ attr_reader :padding
7
8
 
8
- # Initialize base and padding to nil.
9
+ # Initialize base, mute and padding to nil.
9
10
  #
10
11
  def initialize #:nodoc:
11
- @base, @padding = nil, 0
12
+ @base, @mute, @padding = nil, false, 0
12
13
  end
13
14
 
14
15
  # Mute everything that's inside given block
@@ -32,14 +33,22 @@ class Thor
32
33
  @padding = [0, value].max
33
34
  end
34
35
 
35
- # Ask something to the user and receives a response.
36
+ # Asks something to the user and receives a response.
37
+ #
38
+ # If asked to limit the correct responses, you can pass in an
39
+ # array of acceptable answers. If one of those is not supplied,
40
+ # they will be shown a message stating that one of those answers
41
+ # must be given and re-asked the question.
36
42
  #
37
43
  # ==== Example
38
44
  # ask("What is your name?")
39
45
  #
40
- def ask(statement, color=nil)
41
- say("#{statement} ", color)
42
- stdin.gets.strip
46
+ # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
47
+ #
48
+ def ask(statement, *args)
49
+ options = args.last.is_a?(Hash) ? args.pop : {}
50
+
51
+ options[:limited_to] ? ask_filtered(statement, options[:limited_to], *args) : ask_simply(statement, *args)
43
52
  end
44
53
 
45
54
  # Say (print) something to the user. If the sentence ends with a whitespace
@@ -51,7 +60,8 @@ class Thor
51
60
  #
52
61
  def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)$/))
53
62
  message = message.to_s
54
- message = set_color(message, color) if color
63
+
64
+ message = set_color(message, *color) if color
55
65
 
56
66
  spaces = " " * padding
57
67
 
@@ -94,37 +104,77 @@ class Thor
94
104
  !yes?(statement, color)
95
105
  end
96
106
 
107
+ # Prints values in columns
108
+ #
109
+ # ==== Parameters
110
+ # Array[String, String, ...]
111
+ #
112
+ def print_in_columns(array)
113
+ return if array.empty?
114
+ colwidth = (array.map{|el| el.to_s.size}.max || 0) + 2
115
+ array.each_with_index do |value, index|
116
+ # Don't output trailing spaces when printing the last column
117
+ if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
118
+ stdout.puts value
119
+ else
120
+ stdout.printf("%-#{colwidth}s", value)
121
+ end
122
+ end
123
+ end
124
+
97
125
  # Prints a table.
98
126
  #
99
127
  # ==== Parameters
100
128
  # Array[Array[String, String, ...]]
101
129
  #
102
130
  # ==== Options
103
- # ident<Integer>:: Indent the first column by ident value.
131
+ # indent<Integer>:: Indent the first column by indent value.
104
132
  # colwidth<Integer>:: Force the first column to colwidth spaces wide.
105
133
  #
106
- def print_table(table, options={})
107
- return if table.empty?
134
+ def print_table(array, options={})
135
+ return if array.empty?
108
136
 
109
- formats, ident, colwidth = [], options[:ident].to_i, options[:colwidth]
137
+ formats, indent, colwidth = [], options[:indent].to_i, options[:colwidth]
110
138
  options[:truncate] = terminal_width if options[:truncate] == true
111
139
 
112
140
  formats << "%-#{colwidth + 2}s" if colwidth
113
141
  start = colwidth ? 1 : 0
114
142
 
115
- start.upto(table.first.length - 2) do |i|
116
- maxima ||= table.max{|a,b| a[i].size <=> b[i].size }[i].size
117
- formats << "%-#{maxima + 2}s"
143
+ colcount = array.max{|a,b| a.size <=> b.size }.size
144
+
145
+ maximas = []
146
+
147
+ start.upto(colcount - 1) do |index|
148
+ maxima = array.map {|row| row[index] ? row[index].to_s.size : 0 }.max
149
+ maximas << maxima
150
+ if index == colcount - 1
151
+ # Don't output 2 trailing spaces when printing the last column
152
+ formats << "%-s"
153
+ else
154
+ formats << "%-#{maxima + 2}s"
155
+ end
118
156
  end
119
157
 
120
- formats[0] = formats[0].insert(0, " " * ident)
158
+ formats[0] = formats[0].insert(0, " " * indent)
121
159
  formats << "%s"
122
160
 
123
- table.each do |row|
161
+ array.each do |row|
124
162
  sentence = ""
125
163
 
126
- row.each_with_index do |column, i|
127
- sentence << formats[i] % column.to_s
164
+ row.each_with_index do |column, index|
165
+ maxima = maximas[index]
166
+
167
+ if column.is_a?(Numeric)
168
+ if index == row.size - 1
169
+ # Don't output 2 trailing spaces when printing the last column
170
+ f = "%#{maxima}s"
171
+ else
172
+ f = "%#{maxima}s "
173
+ end
174
+ else
175
+ f = formats[index]
176
+ end
177
+ sentence << f % column.to_s
128
178
  end
129
179
 
130
180
  sentence = truncate(sentence, options[:truncate]) if options[:truncate]
@@ -139,11 +189,11 @@ class Thor
139
189
  # String
140
190
  #
141
191
  # ==== Options
142
- # ident<Integer>:: Indent each line of the printed paragraph by ident value.
192
+ # indent<Integer>:: Indent each line of the printed paragraph by indent value.
143
193
  #
144
194
  def print_wrapped(message, options={})
145
- ident = options[:ident] || 0
146
- width = terminal_width - ident
195
+ indent = options[:indent] || 0
196
+ width = terminal_width - indent
147
197
  paras = message.split("\n\n")
148
198
 
149
199
  paras.map! do |unwrapped|
@@ -154,14 +204,14 @@ class Thor
154
204
 
155
205
  paras.each do |para|
156
206
  para.split("\n").each do |line|
157
- stdout.puts line.insert(0, " " * ident)
207
+ stdout.puts line.insert(0, " " * indent)
158
208
  end
159
209
  stdout.puts unless para == paras.last
160
210
  end
161
211
  end
162
212
 
163
213
  # Deals with file collision and returns true if the file should be
164
- # overwriten and false otherwise. If a block is given, it uses the block
214
+ # overwritten and false otherwise. If a block is given, it uses the block
165
215
  # response as the content for the diff.
166
216
  #
167
217
  # ==== Parameters
@@ -194,6 +244,19 @@ class Thor
194
244
  end
195
245
  end
196
246
 
247
+ # This code was copied from Rake, available under MIT-LICENSE
248
+ # Copyright (c) 2003, 2004 Jim Weirich
249
+ def terminal_width
250
+ if ENV['THOR_COLUMNS']
251
+ result = ENV['THOR_COLUMNS'].to_i
252
+ else
253
+ result = unix? ? dynamic_width : 80
254
+ end
255
+ (result < 10) ? 80 : result
256
+ rescue
257
+ 80
258
+ end
259
+
197
260
  # Called if something goes wrong during the execution. This is used by Thor
198
261
  # internally and should not be used inside your scripts. If something went
199
262
  # wrong, you can always raise an exception. If you raise a Thor::Error, it
@@ -206,35 +269,40 @@ class Thor
206
269
  # Apply color to the given string with optional bold. Disabled in the
207
270
  # Thor::Shell::Basic class.
208
271
  #
209
- def set_color(string, color, bold=false) #:nodoc:
272
+ def set_color(string, *args) #:nodoc:
210
273
  string
211
274
  end
212
275
 
213
- protected
276
+ protected
214
277
 
215
- def stdout
216
- $stdout
217
- end
278
+ def lookup_color(color)
279
+ return color unless color.is_a?(Symbol)
280
+ self.class.const_get(color.to_s.upcase)
281
+ end
218
282
 
219
- def stdin
220
- $stdin
221
- end
283
+ def stdout
284
+ $stdout
285
+ end
222
286
 
223
- def stderr
224
- $stderr
225
- end
287
+ def stdin
288
+ $stdin
289
+ end
290
+
291
+ def stderr
292
+ $stderr
293
+ end
226
294
 
227
- def is?(value) #:nodoc:
228
- value = value.to_s
295
+ def is?(value) #:nodoc:
296
+ value = value.to_s
229
297
 
230
- if value.size == 1
231
- /\A#{value}\z/i
232
- else
233
- /\A(#{value}|#{value[0,1]})\z/i
234
- end
298
+ if value.size == 1
299
+ /\A#{value}\z/i
300
+ else
301
+ /\A(#{value}|#{value[0,1]})\z/i
235
302
  end
303
+ end
236
304
 
237
- def file_collision_help #:nodoc:
305
+ def file_collision_help #:nodoc:
238
306
  <<HELP
239
307
  Y - yes, overwrite
240
308
  n - no, do not overwrite
@@ -243,59 +311,78 @@ q - quit, abort
243
311
  d - diff, show the differences between the old and the new
244
312
  h - help, show this help
245
313
  HELP
246
- end
314
+ end
247
315
 
248
- def show_diff(destination, content) #:nodoc:
249
- diff_cmd = ENV['THOR_DIFF'] || ENV['RAILS_DIFF'] || 'diff -u'
316
+ def show_diff(destination, content) #:nodoc:
317
+ diff_cmd = ENV['THOR_DIFF'] || ENV['RAILS_DIFF'] || 'diff -u'
250
318
 
251
- Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
252
- temp.write content
253
- temp.rewind
254
- system %(#{diff_cmd} "#{destination}" "#{temp.path}")
255
- end
319
+ Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
320
+ temp.write content
321
+ temp.rewind
322
+ system %(#{diff_cmd} "#{destination}" "#{temp.path}")
256
323
  end
324
+ end
257
325
 
258
- def quiet? #:nodoc:
259
- mute? || (base && base.options[:quiet])
260
- end
326
+ def quiet? #:nodoc:
327
+ mute? || (base && base.options[:quiet])
328
+ end
329
+
330
+ # Calculate the dynamic width of the terminal
331
+ def dynamic_width
332
+ @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
333
+ end
334
+
335
+ def dynamic_width_stty
336
+ %x{stty size 2>/dev/null}.split[1].to_i
337
+ end
261
338
 
262
- # This code was copied from Rake, available under MIT-LICENSE
263
- # Copyright (c) 2003, 2004 Jim Weirich
264
- def terminal_width
265
- if ENV['THOR_COLUMNS']
266
- result = ENV['THOR_COLUMNS'].to_i
339
+ def dynamic_width_tput
340
+ %x{tput cols 2>/dev/null}.to_i
341
+ end
342
+
343
+ def unix?
344
+ RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
345
+ end
346
+
347
+ def truncate(string, width)
348
+ as_unicode do
349
+ chars = string.chars.to_a
350
+ if chars.length <= width
351
+ chars.join
267
352
  else
268
- result = unix? ? dynamic_width : 80
353
+ ( chars[0, width-3].join ) + "..."
269
354
  end
270
- (result < 10) ? 80 : result
271
- rescue
272
- 80
273
- end
274
-
275
- # Calculate the dynamic width of the terminal
276
- def dynamic_width
277
- @dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
278
355
  end
356
+ end
279
357
 
280
- def dynamic_width_stty
281
- %x{stty size 2>/dev/null}.split[1].to_i
358
+ if "".respond_to?(:encode)
359
+ def as_unicode
360
+ yield
282
361
  end
283
-
284
- def dynamic_width_tput
285
- %x{tput cols 2>/dev/null}.to_i
362
+ else
363
+ def as_unicode
364
+ old, $KCODE = $KCODE, "U"
365
+ yield
366
+ ensure
367
+ $KCODE = old
286
368
  end
369
+ end
287
370
 
288
- def unix?
289
- RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
290
- end
371
+ def ask_simply(statement, color=nil)
372
+ say("#{statement} ", color)
373
+ stdin.gets.strip
374
+ end
291
375
 
292
- def truncate(string, width)
293
- if string.length <= width
294
- string
295
- else
296
- ( string[0, width-3] || "" ) + "..."
297
- end
376
+ def ask_filtered(statement, answer_set, *args)
377
+ correct_answer = nil
378
+ until correct_answer
379
+ answer = ask_simply("#{statement} #{answer_set.inspect}", *args)
380
+ correct_answer = answer_set.include?(answer) ? answer : nil
381
+ answers = answer_set.map(&:inspect).join(", ")
382
+ say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
298
383
  end
384
+ correct_answer
385
+ end
299
386
 
300
387
  end
301
388
  end