bundler 1.3.6 → 1.4.0.pre.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.
Potentially problematic release.
This version of bundler might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +1 -3
- data/CHANGELOG.md +27 -14
- data/CONTRIBUTING.md +2 -2
- data/{CONTRIBUTE.md → DEVELOPMENT.md} +31 -12
- data/ISSUES.md +1 -1
- data/README.md +6 -4
- data/Rakefile +1 -15
- data/bin/bundle +5 -8
- data/bundler.gemspec +1 -1
- data/lib/bundler.rb +37 -21
- data/lib/bundler/cli.rb +33 -21
- data/lib/bundler/constants.rb +5 -0
- data/lib/bundler/current_ruby.rb +88 -0
- data/lib/bundler/definition.rb +35 -11
- data/lib/bundler/dependency.rb +7 -78
- data/lib/bundler/dsl.rb +1 -1
- data/lib/bundler/fetcher.rb +37 -24
- data/lib/bundler/gem_helper.rb +2 -2
- data/lib/bundler/gem_installer.rb +9 -0
- data/lib/bundler/installer.rb +76 -7
- data/lib/bundler/parallel_workers.rb +18 -0
- data/lib/bundler/parallel_workers/thread_worker.rb +27 -0
- data/lib/bundler/parallel_workers/unix_worker.rb +88 -0
- data/lib/bundler/parallel_workers/worker.rb +68 -0
- data/lib/bundler/resolver.rb +17 -11
- data/lib/bundler/rubygems_ext.rb +2 -2
- data/lib/bundler/rubygems_integration.rb +37 -25
- data/lib/bundler/runtime.rb +8 -1
- data/lib/bundler/safe_catch.rb +101 -0
- data/lib/bundler/shared_helpers.rb +27 -1
- data/lib/bundler/source/git.rb +2 -1
- data/lib/bundler/source/git/git_proxy.rb +3 -3
- data/lib/bundler/source/path.rb +3 -2
- data/lib/bundler/source/rubygems.rb +5 -17
- data/lib/bundler/spec_set.rb +16 -1
- data/lib/bundler/templates/newgem/newgem.gemspec.tt +1 -1
- data/lib/bundler/vendor/net/http/persistent.rb +136 -38
- data/lib/bundler/vendor/thor.rb +211 -188
- data/lib/bundler/vendor/thor/actions.rb +19 -19
- data/lib/bundler/vendor/thor/actions/create_link.rb +3 -0
- data/lib/bundler/vendor/thor/actions/directory.rb +30 -10
- data/lib/bundler/vendor/thor/actions/empty_directory.rb +3 -19
- data/lib/bundler/vendor/thor/actions/file_manipulation.rb +6 -3
- data/lib/bundler/vendor/thor/base.rb +101 -97
- data/lib/bundler/vendor/thor/{task.rb → command.rb} +17 -13
- data/lib/bundler/vendor/thor/core_ext/io_binary_read.rb +12 -0
- data/lib/bundler/vendor/thor/error.rb +8 -11
- data/lib/bundler/vendor/thor/group.rb +35 -38
- data/lib/bundler/vendor/thor/invocation.rb +28 -26
- data/lib/bundler/vendor/thor/parser/options.rb +21 -19
- data/lib/bundler/vendor/thor/rake_compat.rb +3 -2
- data/lib/bundler/vendor/thor/runner.rb +22 -21
- data/lib/bundler/vendor/thor/shell/basic.rb +44 -22
- data/lib/bundler/vendor/thor/shell/color.rb +13 -9
- data/lib/bundler/vendor/thor/shell/html.rb +13 -9
- data/lib/bundler/vendor/thor/util.rb +214 -210
- data/lib/bundler/vendor/thor/version.rb +1 -1
- data/lib/bundler/version.rb +1 -1
- data/man/bundle-install.ronn +5 -1
- data/man/gemfile.5.ronn +10 -2
- data/spec/bundler/dsl_spec.rb +3 -3
- data/spec/bundler/gem_helper_spec.rb +14 -17
- data/spec/bundler/safe_catch_spec.rb +37 -0
- data/spec/install/gems/dependency_api_spec.rb +1 -36
- data/spec/install/gems/packed_spec.rb +4 -2
- data/spec/install/gems/resolving_spec.rb +37 -0
- data/spec/install/gems/simple_case_spec.rb +18 -16
- data/spec/install/git_spec.rb +1 -1
- data/spec/other/binstubs_spec.rb +24 -13
- data/spec/other/exec_spec.rb +24 -2
- data/spec/other/help_spec.rb +6 -6
- data/spec/other/outdated_spec.rb +3 -3
- data/spec/quality_spec.rb +3 -2
- data/spec/realworld/dependency_api_spec.rb +1 -1
- data/spec/realworld/edgecases_spec.rb +3 -3
- data/spec/realworld/parallel_install_spec.rb +19 -0
- data/spec/resolver/basic_spec.rb +11 -0
- data/spec/runtime/require_spec.rb +9 -0
- data/spec/runtime/setup_spec.rb +2 -3
- data/spec/spec_helper.rb +0 -1
- data/spec/support/builders.rb +2 -4
- data/spec/support/helpers.rb +4 -8
- data/spec/support/indexes.rb +18 -0
- data/spec/support/streams.rb +13 -0
- metadata +19 -11
- data/lib/bundler/vendor/thor/core_ext/dir_escape.rb +0 -0
- data/lib/bundler/vendor/thor/core_ext/file_binary_read.rb +0 -9
- data/spec/support/artifice/endpoint_host_redirect.rb +0 -15
- 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
|
-
#
|
14
|
-
# t.spec_opts = ['--options', "
|
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/
|
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,
|
20
|
-
self.class.
|
21
|
-
klass.start(["-h",
|
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
|
28
|
-
# Thor::Runner is then responsible for finding the
|
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,
|
34
|
-
self.class.
|
35
|
-
args.unshift(
|
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
|
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
|
-
#
|
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
|
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
|
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(
|
172
|
-
"thor " +
|
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
|
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
|
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.
|
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.
|
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,
|
294
|
+
list.each { |n, commands| display_commands(n, commands) unless commands.empty? }
|
295
295
|
end
|
296
296
|
|
297
|
-
def
|
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
|
-
|
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
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
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
|
372
|
-
|
373
|
-
|
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,
|
397
|
+
def ask_filtered(statement, color, options)
|
398
|
+
answer_set = options[:limited_to]
|
377
399
|
correct_answer = nil
|
378
400
|
until correct_answer
|
379
|
-
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
-
|
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
|
-
|
65
|
-
Thor::Base.subclasses.replace(old_constants)
|
64
|
+
load_thorfile(file, contents)
|
66
65
|
|
67
|
-
|
68
|
-
|
69
|
-
new_constants
|
70
|
-
end
|
66
|
+
new_constants = Thor::Base.subclasses.dup
|
67
|
+
Thor::Base.subclasses.replace(old_constants)
|
71
68
|
|
72
|
-
|
73
|
-
|
74
|
-
|
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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
141
|
-
|
142
|
-
|
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
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
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
|
-
|
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
|
-
|
177
|
-
rescue
|
178
|
-
|
179
|
-
|
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
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
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
|
-
|
204
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
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
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
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
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
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
|
-
|
231
|
-
|
232
|
-
|
233
|
+
# ruby is a symlink
|
234
|
+
if File.symlink? alternate_ruby
|
235
|
+
linked_ruby = File.readlink alternate_ruby
|
233
236
|
|
234
|
-
|
235
|
-
|
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
|
-
|
243
|
-
|
244
|
-
|
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
|