bundler 1.3.6 → 1.4.0.pre.1

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -3
  3. data/CHANGELOG.md +27 -14
  4. data/CONTRIBUTING.md +2 -2
  5. data/{CONTRIBUTE.md → DEVELOPMENT.md} +31 -12
  6. data/ISSUES.md +1 -1
  7. data/README.md +6 -4
  8. data/Rakefile +1 -15
  9. data/bin/bundle +5 -8
  10. data/bundler.gemspec +1 -1
  11. data/lib/bundler.rb +37 -21
  12. data/lib/bundler/cli.rb +33 -21
  13. data/lib/bundler/constants.rb +5 -0
  14. data/lib/bundler/current_ruby.rb +88 -0
  15. data/lib/bundler/definition.rb +35 -11
  16. data/lib/bundler/dependency.rb +7 -78
  17. data/lib/bundler/dsl.rb +1 -1
  18. data/lib/bundler/fetcher.rb +37 -24
  19. data/lib/bundler/gem_helper.rb +2 -2
  20. data/lib/bundler/gem_installer.rb +9 -0
  21. data/lib/bundler/installer.rb +76 -7
  22. data/lib/bundler/parallel_workers.rb +18 -0
  23. data/lib/bundler/parallel_workers/thread_worker.rb +27 -0
  24. data/lib/bundler/parallel_workers/unix_worker.rb +88 -0
  25. data/lib/bundler/parallel_workers/worker.rb +68 -0
  26. data/lib/bundler/resolver.rb +17 -11
  27. data/lib/bundler/rubygems_ext.rb +2 -2
  28. data/lib/bundler/rubygems_integration.rb +37 -25
  29. data/lib/bundler/runtime.rb +8 -1
  30. data/lib/bundler/safe_catch.rb +101 -0
  31. data/lib/bundler/shared_helpers.rb +27 -1
  32. data/lib/bundler/source/git.rb +2 -1
  33. data/lib/bundler/source/git/git_proxy.rb +3 -3
  34. data/lib/bundler/source/path.rb +3 -2
  35. data/lib/bundler/source/rubygems.rb +5 -17
  36. data/lib/bundler/spec_set.rb +16 -1
  37. data/lib/bundler/templates/newgem/newgem.gemspec.tt +1 -1
  38. data/lib/bundler/vendor/net/http/persistent.rb +136 -38
  39. data/lib/bundler/vendor/thor.rb +211 -188
  40. data/lib/bundler/vendor/thor/actions.rb +19 -19
  41. data/lib/bundler/vendor/thor/actions/create_link.rb +3 -0
  42. data/lib/bundler/vendor/thor/actions/directory.rb +30 -10
  43. data/lib/bundler/vendor/thor/actions/empty_directory.rb +3 -19
  44. data/lib/bundler/vendor/thor/actions/file_manipulation.rb +6 -3
  45. data/lib/bundler/vendor/thor/base.rb +101 -97
  46. data/lib/bundler/vendor/thor/{task.rb → command.rb} +17 -13
  47. data/lib/bundler/vendor/thor/core_ext/io_binary_read.rb +12 -0
  48. data/lib/bundler/vendor/thor/error.rb +8 -11
  49. data/lib/bundler/vendor/thor/group.rb +35 -38
  50. data/lib/bundler/vendor/thor/invocation.rb +28 -26
  51. data/lib/bundler/vendor/thor/parser/options.rb +21 -19
  52. data/lib/bundler/vendor/thor/rake_compat.rb +3 -2
  53. data/lib/bundler/vendor/thor/runner.rb +22 -21
  54. data/lib/bundler/vendor/thor/shell/basic.rb +44 -22
  55. data/lib/bundler/vendor/thor/shell/color.rb +13 -9
  56. data/lib/bundler/vendor/thor/shell/html.rb +13 -9
  57. data/lib/bundler/vendor/thor/util.rb +214 -210
  58. data/lib/bundler/vendor/thor/version.rb +1 -1
  59. data/lib/bundler/version.rb +1 -1
  60. data/man/bundle-install.ronn +5 -1
  61. data/man/gemfile.5.ronn +10 -2
  62. data/spec/bundler/dsl_spec.rb +3 -3
  63. data/spec/bundler/gem_helper_spec.rb +14 -17
  64. data/spec/bundler/safe_catch_spec.rb +37 -0
  65. data/spec/install/gems/dependency_api_spec.rb +1 -36
  66. data/spec/install/gems/packed_spec.rb +4 -2
  67. data/spec/install/gems/resolving_spec.rb +37 -0
  68. data/spec/install/gems/simple_case_spec.rb +18 -16
  69. data/spec/install/git_spec.rb +1 -1
  70. data/spec/other/binstubs_spec.rb +24 -13
  71. data/spec/other/exec_spec.rb +24 -2
  72. data/spec/other/help_spec.rb +6 -6
  73. data/spec/other/outdated_spec.rb +3 -3
  74. data/spec/quality_spec.rb +3 -2
  75. data/spec/realworld/dependency_api_spec.rb +1 -1
  76. data/spec/realworld/edgecases_spec.rb +3 -3
  77. data/spec/realworld/parallel_install_spec.rb +19 -0
  78. data/spec/resolver/basic_spec.rb +11 -0
  79. data/spec/runtime/require_spec.rb +9 -0
  80. data/spec/runtime/setup_spec.rb +2 -3
  81. data/spec/spec_helper.rb +0 -1
  82. data/spec/support/builders.rb +2 -4
  83. data/spec/support/helpers.rb +4 -8
  84. data/spec/support/indexes.rb +18 -0
  85. data/spec/support/streams.rb +13 -0
  86. metadata +19 -11
  87. data/lib/bundler/vendor/thor/core_ext/dir_escape.rb +0 -0
  88. data/lib/bundler/vendor/thor/core_ext/file_binary_read.rb +0 -9
  89. data/spec/support/artifice/endpoint_host_redirect.rb +0 -15
  90. data/spec/support/permissions.rb +0 -11
@@ -6,12 +6,13 @@ class Thor
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
@@ -1,6 +1,6 @@
1
1
  require 'thor'
2
2
  require 'thor/group'
3
- require 'thor/core_ext/file_binary_read'
3
+ require 'thor/core_ext/io_binary_read'
4
4
 
5
5
  require 'fileutils'
6
6
  require 'open-uri'
@@ -16,33 +16,33 @@ class Thor::Runner < Thor #:nodoc:
16
16
  def help(meth = nil)
17
17
  if meth && !self.respond_to?(meth)
18
18
  initialize_thorfiles(meth)
19
- klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
20
- self.class.handle_no_task_error(task, false) if klass.nil?
21
- klass.start(["-h", task].compact, :shell => self.shell)
19
+ klass, command = Thor::Util.find_class_and_command_by_namespace(meth)
20
+ self.class.handle_no_command_error(command, false) if klass.nil?
21
+ klass.start(["-h", command].compact, :shell => self.shell)
22
22
  else
23
23
  super
24
24
  end
25
25
  end
26
26
 
27
- # If a task is not found on Thor::Runner, method missing is invoked and
28
- # Thor::Runner is then responsible for finding the task in all classes.
27
+ # If a command is not found on Thor::Runner, method missing is invoked and
28
+ # Thor::Runner is then responsible for finding the command in all classes.
29
29
  #
30
30
  def method_missing(meth, *args)
31
31
  meth = meth.to_s
32
32
  initialize_thorfiles(meth)
33
- klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
34
- self.class.handle_no_task_error(task, false) if klass.nil?
35
- args.unshift(task) if task
33
+ klass, command = Thor::Util.find_class_and_command_by_namespace(meth)
34
+ self.class.handle_no_command_error(command, false) if klass.nil?
35
+ args.unshift(command) if command
36
36
  klass.start(args, :shell => self.shell)
37
37
  end
38
38
 
39
- desc "install NAME", "Install an optionally named Thor file into your system tasks"
39
+ desc "install NAME", "Install an optionally named Thor file into your system commands"
40
40
  method_options :as => :string, :relative => :boolean, :force => :boolean
41
41
  def install(name)
42
42
  initialize_thorfiles
43
43
 
44
44
  # If a directory name is provided as the argument, look for a 'main.thor'
45
- # task in said directory.
45
+ # command in said directory.
46
46
  begin
47
47
  if File.directory?(File.expand_path(name))
48
48
  base, package = File.join(name, "main.thor"), :directory
@@ -143,14 +143,14 @@ class Thor::Runner < Thor #:nodoc:
143
143
  end
144
144
  end
145
145
 
146
- desc "installed", "List the installed Thor modules and tasks"
146
+ desc "installed", "List the installed Thor modules and commands"
147
147
  method_options :internal => :boolean
148
148
  def installed
149
149
  initialize_thorfiles(nil, true)
150
150
  display_klasses(true, options["internal"])
151
151
  end
152
152
 
153
- desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
153
+ desc "list [SEARCH]", "List the available thor commands (--substring means .*SEARCH)"
154
154
  method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean
155
155
  def list(search="")
156
156
  initialize_thorfiles
@@ -168,8 +168,8 @@ class Thor::Runner < Thor #:nodoc:
168
168
 
169
169
  private
170
170
 
171
- def self.banner(task, all = false, subcommand = false)
172
- "thor " + task.formatted_usage(self, all, subcommand)
171
+ def self.banner(command, all = false, subcommand = false)
172
+ "thor " + command.formatted_usage(self, all, subcommand)
173
173
  end
174
174
 
175
175
  def thor_root
@@ -206,7 +206,7 @@ class Thor::Runner < Thor #:nodoc:
206
206
  # in the thor_root instead of loading them all.
207
207
  #
208
208
  # By default, it also traverses the current path until find Thor files, as
209
- # described in thorfiles. This look up can be skipped by suppliying
209
+ # described in thorfiles. This look up can be skipped by supplying
210
210
  # skip_lookup true.
211
211
  #
212
212
  def initialize_thorfiles(relevant_to=nil, skip_lookup=false)
@@ -276,25 +276,25 @@ class Thor::Runner < Thor #:nodoc:
276
276
  def display_klasses(with_modules=false, show_internal=false, klasses=Thor::Base.subclasses)
277
277
  klasses -= [Thor, Thor::Runner, Thor::Group] unless show_internal
278
278
 
279
- raise Error, "No Thor tasks available" if klasses.empty?
279
+ raise Error, "No Thor commands available" if klasses.empty?
280
280
  show_modules if with_modules && !thor_yaml.empty?
281
281
 
282
282
  list = Hash.new { |h,k| h[k] = [] }
283
283
  groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
284
284
 
285
285
  # Get classes which inherit from Thor
286
- (klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_tasks(false) }
286
+ (klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_commands(false) }
287
287
 
288
288
  # Get classes which inherit from Thor::Base
289
- groups.map! { |k| k.printable_tasks(false).first }
289
+ groups.map! { |k| k.printable_commands(false).first }
290
290
  list["root"] = groups
291
291
 
292
292
  # Order namespaces with default coming first
293
293
  list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') }
294
- list.each { |n, tasks| display_tasks(n, tasks) unless tasks.empty? }
294
+ list.each { |n, commands| display_commands(n, commands) unless commands.empty? }
295
295
  end
296
296
 
297
- def display_tasks(namespace, list) #:nodoc:
297
+ def display_commands(namespace, list) #:nodoc:
298
298
  list.sort!{ |a,b| a[0] <=> b[0] }
299
299
 
300
300
  say shell.set_color(namespace, :blue, true)
@@ -303,6 +303,7 @@ class Thor::Runner < Thor #:nodoc:
303
303
  print_table(list, :truncate => true)
304
304
  say
305
305
  end
306
+ alias display_tasks display_commands
306
307
 
307
308
  def show_modules #:nodoc:
308
309
  info = []
@@ -47,8 +47,13 @@ class Thor
47
47
  #
48
48
  def ask(statement, *args)
49
49
  options = args.last.is_a?(Hash) ? args.pop : {}
50
+ color = args.first
50
51
 
51
- options[:limited_to] ? ask_filtered(statement, options[:limited_to], *args) : ask_simply(statement, *args)
52
+ if options[:limited_to]
53
+ ask_filtered(statement, color, options)
54
+ else
55
+ ask_simply(statement, color, options)
56
+ end
52
57
  end
53
58
 
54
59
  # Say (print) something to the user. If the sentence ends with a whitespace
@@ -61,7 +66,7 @@ class Thor
61
66
  def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)\Z/))
62
67
  message = message.to_s
63
68
 
64
- message = set_color(message, *color) if color
69
+ message = set_color(message, *color) if color && can_display_colors?
65
70
 
66
71
  spaces = " " * padding
67
72
 
@@ -226,20 +231,20 @@ class Thor
226
231
  answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}]
227
232
 
228
233
  case answer
229
- when is?(:yes), is?(:force), ""
230
- return true
231
- when is?(:no), is?(:skip)
232
- return false
233
- when is?(:always)
234
- return @always_force = true
235
- when is?(:quit)
236
- say 'Aborting...'
237
- raise SystemExit
238
- when is?(:diff)
239
- show_diff(destination, yield) if block_given?
240
- say 'Retrying...'
241
- else
242
- say file_collision_help
234
+ when is?(:yes), is?(:force), ""
235
+ return true
236
+ when is?(:no), is?(:skip)
237
+ return false
238
+ when is?(:always)
239
+ return @always_force = true
240
+ when is?(:quit)
241
+ say 'Aborting...'
242
+ raise SystemExit
243
+ when is?(:diff)
244
+ show_diff(destination, yield) if block_given?
245
+ say 'Retrying...'
246
+ else
247
+ say file_collision_help
243
248
  end
244
249
  end
245
250
  end
@@ -275,6 +280,10 @@ class Thor
275
280
 
276
281
  protected
277
282
 
283
+ def can_display_colors?
284
+ false
285
+ end
286
+
278
287
  def lookup_color(color)
279
288
  return color unless color.is_a?(Symbol)
280
289
  self.class.const_get(color.to_s.upcase)
@@ -368,17 +377,30 @@ HELP
368
377
  end
369
378
  end
370
379
 
371
- def ask_simply(statement, color=nil)
372
- say("#{statement} ", color)
373
- stdin.gets.tap{|text| text.strip! if text}
380
+ def ask_simply(statement, color, options)
381
+ default = options[:default]
382
+ message = [statement, ("(#{default})" if default), nil].uniq.join(" ")
383
+ say(message, color)
384
+ result = stdin.gets
385
+
386
+ return unless result
387
+
388
+ result.strip!
389
+
390
+ if default && result == ""
391
+ default
392
+ else
393
+ result
394
+ end
374
395
  end
375
396
 
376
- def ask_filtered(statement, answer_set, *args)
397
+ def ask_filtered(statement, color, options)
398
+ answer_set = options[:limited_to]
377
399
  correct_answer = nil
378
400
  until correct_answer
379
- answer = ask_simply("#{statement} #{answer_set.inspect}", *args)
401
+ answers = answer_set.join(", ")
402
+ answer = ask_simply("#{statement} [#{answers}]", color, options)
380
403
  correct_answer = answer_set.include?(answer) ? answer : nil
381
- answers = answer_set.map(&:inspect).join(", ")
382
404
  say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
383
405
  end
384
406
  correct_answer
@@ -94,6 +94,10 @@ class Thor
94
94
 
95
95
  protected
96
96
 
97
+ def can_display_colors?
98
+ stdout.tty?
99
+ end
100
+
97
101
  # Overwrite show_diff to show diff with colors if Diff::LCS is
98
102
  # available.
99
103
  #
@@ -112,15 +116,15 @@ class Thor
112
116
 
113
117
  def output_diff_line(diff) #:nodoc:
114
118
  case diff.action
115
- when '-'
116
- say "- #{diff.old_element.chomp}", :red, true
117
- when '+'
118
- say "+ #{diff.new_element.chomp}", :green, true
119
- when '!'
120
- say "- #{diff.old_element.chomp}", :red, true
121
- say "+ #{diff.new_element.chomp}", :green, true
122
- else
123
- say " #{diff.old_element.chomp}", nil, true
119
+ when '-'
120
+ say "- #{diff.old_element.chomp}", :red, true
121
+ when '+'
122
+ say "+ #{diff.new_element.chomp}", :green, true
123
+ when '!'
124
+ say "- #{diff.old_element.chomp}", :red, true
125
+ say "+ #{diff.new_element.chomp}", :green, true
126
+ else
127
+ say " #{diff.old_element.chomp}", nil, true
124
128
  end
125
129
  end
126
130
 
@@ -73,6 +73,10 @@ class Thor
73
73
 
74
74
  protected
75
75
 
76
+ def can_display_colors?
77
+ true
78
+ end
79
+
76
80
  # Overwrite show_diff to show diff with colors if Diff::LCS is
77
81
  # available.
78
82
  #
@@ -91,15 +95,15 @@ class Thor
91
95
 
92
96
  def output_diff_line(diff) #:nodoc:
93
97
  case diff.action
94
- when '-'
95
- say "- #{diff.old_element.chomp}", :red, true
96
- when '+'
97
- say "+ #{diff.new_element.chomp}", :green, true
98
- when '!'
99
- say "- #{diff.old_element.chomp}", :red, true
100
- say "+ #{diff.new_element.chomp}", :green, true
101
- else
102
- say " #{diff.old_element.chomp}", nil, true
98
+ when '-'
99
+ say "- #{diff.old_element.chomp}", :red, true
100
+ when '+'
101
+ say "+ #{diff.new_element.chomp}", :green, true
102
+ when '!'
103
+ say "- #{diff.old_element.chomp}", :red, true
104
+ say "+ #{diff.new_element.chomp}", :green, true
105
+ else
106
+ say " #{diff.old_element.chomp}", nil, true
103
107
  end
104
108
  end
105
109
 
@@ -16,251 +16,255 @@ class Thor
16
16
  #
17
17
  module Util
18
18
 
19
- # Receives a namespace and search for it in the Thor::Base subclasses.
20
- #
21
- # ==== Parameters
22
- # namespace<String>:: The namespace to search for.
23
- #
24
- def self.find_by_namespace(namespace)
25
- namespace = "default#{namespace}" if namespace.empty? || namespace =~ /^:/
26
- Thor::Base.subclasses.find { |klass| klass.namespace == namespace }
27
- end
19
+ class << self
28
20
 
29
- # Receives a constant and converts it to a Thor namespace. Since Thor tasks
30
- # can be added to a sandbox, this method is also responsable for removing
31
- # the sandbox namespace.
32
- #
33
- # This method should not be used in general because it's used to deal with
34
- # older versions of Thor. On current versions, if you need to get the
35
- # namespace from a class, just call namespace on it.
36
- #
37
- # ==== Parameters
38
- # constant<Object>:: The constant to be converted to the thor path.
39
- #
40
- # ==== Returns
41
- # String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz"
42
- #
43
- def self.namespace_from_thor_class(constant)
44
- constant = constant.to_s.gsub(/^Thor::Sandbox::/, "")
45
- constant = snake_case(constant).squeeze(":")
46
- constant
47
- end
21
+ # Receives a namespace and search for it in the Thor::Base subclasses.
22
+ #
23
+ # ==== Parameters
24
+ # namespace<String>:: The namespace to search for.
25
+ #
26
+ def find_by_namespace(namespace)
27
+ namespace = "default#{namespace}" if namespace.empty? || namespace =~ /^:/
28
+ Thor::Base.subclasses.find { |klass| klass.namespace == namespace }
29
+ end
48
30
 
49
- # Given the contents, evaluate it inside the sandbox and returns the
50
- # namespaces defined in the sandbox.
51
- #
52
- # ==== Parameters
53
- # contents<String>
54
- #
55
- # ==== Returns
56
- # Array[Object]
57
- #
58
- def self.namespaces_in_content(contents, file=__FILE__)
59
- old_constants = Thor::Base.subclasses.dup
60
- Thor::Base.subclasses.clear
31
+ # Receives a constant and converts it to a Thor namespace. Since Thor
32
+ # commands can be added to a sandbox, this method is also responsable for
33
+ # removing the sandbox namespace.
34
+ #
35
+ # This method should not be used in general because it's used to deal with
36
+ # older versions of Thor. On current versions, if you need to get the
37
+ # namespace from a class, just call namespace on it.
38
+ #
39
+ # ==== Parameters
40
+ # constant<Object>:: The constant to be converted to the thor path.
41
+ #
42
+ # ==== Returns
43
+ # String:: If we receive Foo::Bar::Baz it returns "foo:bar:baz"
44
+ #
45
+ def namespace_from_thor_class(constant)
46
+ constant = constant.to_s.gsub(/^Thor::Sandbox::/, "")
47
+ constant = snake_case(constant).squeeze(":")
48
+ constant
49
+ end
61
50
 
62
- load_thorfile(file, contents)
51
+ # Given the contents, evaluate it inside the sandbox and returns the
52
+ # namespaces defined in the sandbox.
53
+ #
54
+ # ==== Parameters
55
+ # contents<String>
56
+ #
57
+ # ==== Returns
58
+ # Array[Object]
59
+ #
60
+ def namespaces_in_content(contents, file=__FILE__)
61
+ old_constants = Thor::Base.subclasses.dup
62
+ Thor::Base.subclasses.clear
63
63
 
64
- new_constants = Thor::Base.subclasses.dup
65
- Thor::Base.subclasses.replace(old_constants)
64
+ load_thorfile(file, contents)
66
65
 
67
- new_constants.map!{ |c| c.namespace }
68
- new_constants.compact!
69
- new_constants
70
- end
66
+ new_constants = Thor::Base.subclasses.dup
67
+ Thor::Base.subclasses.replace(old_constants)
71
68
 
72
- # Returns the thor classes declared inside the given class.
73
- #
74
- def self.thor_classes_in(klass)
75
- stringfied_constants = klass.constants.map { |c| c.to_s }
76
- Thor::Base.subclasses.select do |subclass|
77
- next unless subclass.name
78
- stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", ''))
69
+ new_constants.map!{ |c| c.namespace }
70
+ new_constants.compact!
71
+ new_constants
79
72
  end
80
- end
81
-
82
- # Receives a string and convert it to snake case. SnakeCase returns snake_case.
83
- #
84
- # ==== Parameters
85
- # String
86
- #
87
- # ==== Returns
88
- # String
89
- #
90
- def self.snake_case(str)
91
- return str.downcase if str =~ /^[A-Z_]+$/
92
- str.gsub(/\B[A-Z]/, '_\&').squeeze('_') =~ /_*(.*)/
93
- return $+.downcase
94
- end
95
-
96
- # Receives a string and convert it to camel case. camel_case returns CamelCase.
97
- #
98
- # ==== Parameters
99
- # String
100
- #
101
- # ==== Returns
102
- # String
103
- #
104
- def self.camel_case(str)
105
- return str if str !~ /_/ && str =~ /[A-Z]+.*/
106
- str.split('_').map { |i| i.capitalize }.join
107
- end
108
73
 
109
- # Receives a namespace and tries to retrieve a Thor or Thor::Group class
110
- # from it. It first searches for a class using the all the given namespace,
111
- # if it's not found, removes the highest entry and searches for the class
112
- # again. If found, returns the highest entry as the class name.
113
- #
114
- # ==== Examples
115
- #
116
- # class Foo::Bar < Thor
117
- # def baz
118
- # end
119
- # end
120
- #
121
- # class Baz::Foo < Thor::Group
122
- # end
123
- #
124
- # Thor::Util.namespace_to_thor_class("foo:bar") #=> Foo::Bar, nil # will invoke default task
125
- # Thor::Util.namespace_to_thor_class("baz:foo") #=> Baz::Foo, nil
126
- # Thor::Util.namespace_to_thor_class("foo:bar:baz") #=> Foo::Bar, "baz"
127
- #
128
- # ==== Parameters
129
- # namespace<String>
130
- #
131
- def self.find_class_and_task_by_namespace(namespace, fallback = true)
132
- if namespace.include?(?:) # look for a namespaced task
133
- pieces = namespace.split(":")
134
- task = pieces.pop
135
- klass = Thor::Util.find_by_namespace(pieces.join(":"))
136
- end
137
- unless klass # look for a Thor::Group with the right name
138
- klass, task = Thor::Util.find_by_namespace(namespace), nil
74
+ # Returns the thor classes declared inside the given class.
75
+ #
76
+ def thor_classes_in(klass)
77
+ stringfied_constants = klass.constants.map { |c| c.to_s }
78
+ Thor::Base.subclasses.select do |subclass|
79
+ next unless subclass.name
80
+ stringfied_constants.include?(subclass.name.gsub("#{klass.name}::", ''))
81
+ end
139
82
  end
140
- if !klass && fallback # try a task in the default namespace
141
- task = namespace
142
- klass = Thor::Util.find_by_namespace('')
83
+
84
+ # Receives a string and convert it to snake case. SnakeCase returns snake_case.
85
+ #
86
+ # ==== Parameters
87
+ # String
88
+ #
89
+ # ==== Returns
90
+ # String
91
+ #
92
+ def snake_case(str)
93
+ return str.downcase if str =~ /^[A-Z_]+$/
94
+ str.gsub(/\B[A-Z]/, '_\&').squeeze('_') =~ /_*(.*)/
95
+ return $+.downcase
143
96
  end
144
- return klass, task
145
- end
146
97
 
147
- # Receives a path and load the thor file in the path. The file is evaluated
148
- # inside the sandbox to avoid namespacing conflicts.
149
- #
150
- def self.load_thorfile(path, content=nil, debug=false)
151
- content ||= File.binread(path)
98
+ # Receives a string and convert it to camel case. camel_case returns CamelCase.
99
+ #
100
+ # ==== Parameters
101
+ # String
102
+ #
103
+ # ==== Returns
104
+ # String
105
+ #
106
+ def camel_case(str)
107
+ return str if str !~ /_/ && str =~ /[A-Z]+.*/
108
+ str.split('_').map { |i| i.capitalize }.join
109
+ end
152
110
 
153
- begin
154
- Thor::Sandbox.class_eval(content, path)
155
- rescue Exception => e
156
- $stderr.puts("WARNING: unable to load thorfile #{path.inspect}: #{e.message}")
157
- if debug
158
- $stderr.puts(*e.backtrace)
159
- else
160
- $stderr.puts(e.backtrace.first)
111
+ # Receives a namespace and tries to retrieve a Thor or Thor::Group class
112
+ # from it. It first searches for a class using the all the given namespace,
113
+ # if it's not found, removes the highest entry and searches for the class
114
+ # again. If found, returns the highest entry as the class name.
115
+ #
116
+ # ==== Examples
117
+ #
118
+ # class Foo::Bar < Thor
119
+ # def baz
120
+ # end
121
+ # end
122
+ #
123
+ # class Baz::Foo < Thor::Group
124
+ # end
125
+ #
126
+ # Thor::Util.namespace_to_thor_class("foo:bar") #=> Foo::Bar, nil # will invoke default command
127
+ # Thor::Util.namespace_to_thor_class("baz:foo") #=> Baz::Foo, nil
128
+ # Thor::Util.namespace_to_thor_class("foo:bar:baz") #=> Foo::Bar, "baz"
129
+ #
130
+ # ==== Parameters
131
+ # namespace<String>
132
+ #
133
+ def find_class_and_command_by_namespace(namespace, fallback = true)
134
+ if namespace.include?(?:) # look for a namespaced command
135
+ pieces = namespace.split(":")
136
+ command = pieces.pop
137
+ klass = Thor::Util.find_by_namespace(pieces.join(":"))
138
+ end
139
+ unless klass # look for a Thor::Group with the right name
140
+ klass, command = Thor::Util.find_by_namespace(namespace), nil
141
+ end
142
+ if !klass && fallback # try a command in the default namespace
143
+ command = namespace
144
+ klass = Thor::Util.find_by_namespace('')
161
145
  end
146
+ return klass, command
162
147
  end
163
- end
148
+ alias find_class_and_task_by_namespace find_class_and_command_by_namespace
149
+
150
+ # Receives a path and load the thor file in the path. The file is evaluated
151
+ # inside the sandbox to avoid namespacing conflicts.
152
+ #
153
+ def load_thorfile(path, content=nil, debug=false)
154
+ content ||= File.binread(path)
164
155
 
165
- def self.user_home
166
- @@user_home ||= if ENV["HOME"]
167
- ENV["HOME"]
168
- elsif ENV["USERPROFILE"]
169
- ENV["USERPROFILE"]
170
- elsif ENV["HOMEDRIVE"] && ENV["HOMEPATH"]
171
- File.join(ENV["HOMEDRIVE"], ENV["HOMEPATH"])
172
- elsif ENV["APPDATA"]
173
- ENV["APPDATA"]
174
- else
175
156
  begin
176
- File.expand_path("~")
177
- rescue
178
- if File::ALT_SEPARATOR
179
- "C:/"
157
+ Thor::Sandbox.class_eval(content, path)
158
+ rescue Exception => e
159
+ $stderr.puts("WARNING: unable to load thorfile #{path.inspect}: #{e.message}")
160
+ if debug
161
+ $stderr.puts(*e.backtrace)
180
162
  else
181
- "/"
163
+ $stderr.puts(e.backtrace.first)
182
164
  end
183
165
  end
184
166
  end
185
- end
186
167
 
187
- # Returns the root where thor files are located, depending on the OS.
188
- #
189
- def self.thor_root
190
- File.join(user_home, ".thor").gsub(/\\/, '/')
191
- end
168
+ def user_home
169
+ @@user_home ||= if ENV["HOME"]
170
+ ENV["HOME"]
171
+ elsif ENV["USERPROFILE"]
172
+ ENV["USERPROFILE"]
173
+ elsif ENV["HOMEDRIVE"] && ENV["HOMEPATH"]
174
+ File.join(ENV["HOMEDRIVE"], ENV["HOMEPATH"])
175
+ elsif ENV["APPDATA"]
176
+ ENV["APPDATA"]
177
+ else
178
+ begin
179
+ File.expand_path("~")
180
+ rescue
181
+ if File::ALT_SEPARATOR
182
+ "C:/"
183
+ else
184
+ "/"
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ # Returns the root where thor files are located, depending on the OS.
191
+ #
192
+ def thor_root
193
+ File.join(user_home, ".thor").gsub(/\\/, '/')
194
+ end
192
195
 
193
- # Returns the files in the thor root. On Windows thor_root will be something
194
- # like this:
195
- #
196
- # C:\Documents and Settings\james\.thor
197
- #
198
- # If we don't #gsub the \ character, Dir.glob will fail.
199
- #
200
- def self.thor_root_glob
201
- files = Dir["#{escape_globs(thor_root)}/*"]
196
+ # Returns the files in the thor root. On Windows thor_root will be something
197
+ # like this:
198
+ #
199
+ # C:\Documents and Settings\james\.thor
200
+ #
201
+ # If we don't #gsub the \ character, Dir.glob will fail.
202
+ #
203
+ def thor_root_glob
204
+ files = Dir["#{escape_globs(thor_root)}/*"]
202
205
 
203
- files.map! do |file|
204
- File.directory?(file) ? File.join(file, "main.thor") : file
206
+ files.map! do |file|
207
+ File.directory?(file) ? File.join(file, "main.thor") : file
208
+ end
205
209
  end
206
- end
207
210
 
208
- # Where to look for Thor files.
209
- #
210
- def self.globs_for(path)
211
- path = escape_globs(path)
212
- ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"]
213
- end
211
+ # Where to look for Thor files.
212
+ #
213
+ def globs_for(path)
214
+ path = escape_globs(path)
215
+ ["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"]
216
+ end
214
217
 
215
- # Return the path to the ruby interpreter taking into account multiple
216
- # installations and windows extensions.
217
- #
218
- def self.ruby_command
219
- @ruby_command ||= begin
220
- ruby_name = RbConfig::CONFIG['ruby_install_name']
221
- ruby = File.join(RbConfig::CONFIG['bindir'], ruby_name)
222
- ruby << RbConfig::CONFIG['EXEEXT']
218
+ # Return the path to the ruby interpreter taking into account multiple
219
+ # installations and windows extensions.
220
+ #
221
+ def ruby_command
222
+ @ruby_command ||= begin
223
+ ruby_name = RbConfig::CONFIG['ruby_install_name']
224
+ ruby = File.join(RbConfig::CONFIG['bindir'], ruby_name)
225
+ ruby << RbConfig::CONFIG['EXEEXT']
223
226
 
224
- # avoid using different name than ruby (on platforms supporting links)
225
- if ruby_name != 'ruby' && File.respond_to?(:readlink)
226
- begin
227
- alternate_ruby = File.join(RbConfig::CONFIG['bindir'], 'ruby')
228
- alternate_ruby << RbConfig::CONFIG['EXEEXT']
227
+ # avoid using different name than ruby (on platforms supporting links)
228
+ if ruby_name != 'ruby' && File.respond_to?(:readlink)
229
+ begin
230
+ alternate_ruby = File.join(RbConfig::CONFIG['bindir'], 'ruby')
231
+ alternate_ruby << RbConfig::CONFIG['EXEEXT']
229
232
 
230
- # ruby is a symlink
231
- if File.symlink? alternate_ruby
232
- linked_ruby = File.readlink alternate_ruby
233
+ # ruby is a symlink
234
+ if File.symlink? alternate_ruby
235
+ linked_ruby = File.readlink alternate_ruby
233
236
 
234
- # symlink points to 'ruby_install_name'
235
- ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby
237
+ # symlink points to 'ruby_install_name'
238
+ ruby = alternate_ruby if linked_ruby == ruby_name || linked_ruby == ruby
239
+ end
240
+ rescue NotImplementedError
241
+ # just ignore on windows
236
242
  end
237
- rescue NotImplementedError
238
- # just ignore on windows
239
243
  end
244
+
245
+ # escape string in case path to ruby executable contain spaces.
246
+ ruby.sub!(/.*\s.*/m, '"\&"')
247
+ ruby
240
248
  end
249
+ end
241
250
 
242
- # escape string in case path to ruby executable contain spaces.
243
- ruby.sub!(/.*\s.*/m, '"\&"')
244
- ruby
251
+ # Returns a string that has had any glob characters escaped.
252
+ # The glob characters are `* ? { } [ ]`.
253
+ #
254
+ # ==== Examples
255
+ #
256
+ # Thor::Util.escape_globs('[apps]') # => '\[apps\]'
257
+ #
258
+ # ==== Parameters
259
+ # String
260
+ #
261
+ # ==== Returns
262
+ # String
263
+ #
264
+ def escape_globs(path)
265
+ path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&')
245
266
  end
246
- end
247
267
 
248
- # Returns a string that has had any glob characters escaped.
249
- # The glob characters are `* ? { } [ ]`.
250
- #
251
- # ==== Examples
252
- #
253
- # Thor::Util.escape_globs('[apps]') # => '\[apps\]'
254
- #
255
- # ==== Parameters
256
- # String
257
- #
258
- # ==== Returns
259
- # String
260
- #
261
- def self.escape_globs(path)
262
- path.to_s.gsub(/[*?{}\[\]]/, '\\\\\\&')
263
268
  end
264
-
265
269
  end
266
270
  end