engineyard 2.1.1 → 2.1.2

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.
@@ -5,27 +5,32 @@ class Thor
5
5
  EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i
6
6
  SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
7
7
  SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
8
+ OPTS_END = '--'.freeze
8
9
 
9
10
  # Receives a hash and makes it switches.
10
11
  def self.to_switches(options)
11
12
  options.map do |key, value|
12
13
  case value
13
- when true
14
- "--#{key}"
15
- when Array
16
- "--#{key} #{value.map{ |v| v.inspect }.join(' ')}"
17
- when Hash
18
- "--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}"
19
- when nil, false
20
- ""
21
- else
22
- "--#{key} #{value.inspect}"
14
+ when true
15
+ "--#{key}"
16
+ when Array
17
+ "--#{key} #{value.map{ |v| v.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}"
23
24
  end
24
25
  end.join(" ")
25
26
  end
26
27
 
27
28
  # Takes a hash of Thor::Option and a hash with defaults.
28
- def initialize(hash_options={}, defaults={})
29
+ #
30
+ # If +stop_on_unknown+ is true, #parse will stop as soon as it encounters
31
+ # an unknown option or a regular argument.
32
+ def initialize(hash_options={}, defaults={}, stop_on_unknown=false)
33
+ @stop_on_unknown = stop_on_unknown
29
34
  options = hash_options.values
30
35
  super(options)
31
36
 
@@ -41,7 +46,8 @@ class Thor
41
46
  @switches[option.switch_name] = option
42
47
 
43
48
  option.aliases.each do |short|
44
- @shorts[short.to_s] ||= option.switch_name
49
+ name = short.to_s.sub(/^(?!\-)/, '-')
50
+ @shorts[name] ||= option.switch_name
45
51
  end
46
52
  end
47
53
  end
@@ -50,15 +56,30 @@ class Thor
50
56
  @extra
51
57
  end
52
58
 
59
+ def peek
60
+ return super unless @parsing_options
61
+
62
+ result = super
63
+ if result == OPTS_END
64
+ shift
65
+ @parsing_options = false
66
+ super
67
+ else
68
+ result
69
+ end
70
+ end
71
+
53
72
  def parse(args)
54
73
  @pile = args.dup
74
+ @parsing_options = true
55
75
 
56
76
  while peek
57
- match, is_switch = current_is_switch?
58
- shifted = shift
77
+ if parsing_options?
78
+ match, is_switch = current_is_switch?
79
+ shifted = shift
59
80
 
60
- if is_switch
61
- case shifted
81
+ if is_switch
82
+ case shifted
62
83
  when SHORT_SQ_RE
63
84
  unshift($1.split('').map { |f| "-#{f}" })
64
85
  next
@@ -67,16 +88,24 @@ class Thor
67
88
  switch = $1
68
89
  when LONG_RE, SHORT_RE
69
90
  switch = $1
91
+ end
92
+
93
+ switch = normalize_switch(switch)
94
+ option = switch_option(switch)
95
+ @assigns[option.human_name] = parse_peek(switch, option)
96
+ elsif @stop_on_unknown
97
+ @parsing_options = false
98
+ @extra << shifted
99
+ @extra << shift while peek
100
+ break
101
+ elsif match
102
+ @extra << shifted
103
+ @extra << shift while peek && peek !~ /^-/
104
+ else
105
+ @extra << shifted
70
106
  end
71
-
72
- switch = normalize_switch(switch)
73
- option = switch_option(switch)
74
- @assigns[option.human_name] = parse_peek(switch, option)
75
- elsif match
76
- @extra << shifted
77
- @extra << shift while peek && peek !~ /^-/
78
107
  else
79
- @extra << shifted
108
+ @extra << shift
80
109
  end
81
110
  end
82
111
 
@@ -95,8 +124,10 @@ class Thor
95
124
 
96
125
  protected
97
126
 
98
- # Returns true if the current value in peek is a registered switch.
127
+ # Check if the current value in peek is a registered switch.
99
128
  #
129
+ # Two booleans are returned. The first is true if the current value
130
+ # starts with a hyphen; the second is true if it is a registered switch.
100
131
  def current_is_switch?
101
132
  case peek
102
133
  when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
@@ -117,6 +148,10 @@ class Thor
117
148
  end
118
149
  end
119
150
 
151
+ def current_is_value?
152
+ peek && (!parsing_options? || super)
153
+ end
154
+
120
155
  def switch?(arg)
121
156
  switch_option(normalize_switch(arg))
122
157
  end
@@ -135,6 +170,11 @@ class Thor
135
170
  (@shorts[arg] || arg).tr('_', '-')
136
171
  end
137
172
 
173
+ def parsing_options?
174
+ peek
175
+ @parsing_options
176
+ end
177
+
138
178
  # Parse boolean values which can be given as --foo=true, --foo or --no-foo.
139
179
  #
140
180
  def parse_boolean(switch)
@@ -156,7 +196,7 @@ class Thor
156
196
  # Parse the value at the peek analyzing if it requires an input or not.
157
197
  #
158
198
  def parse_peek(switch, option)
159
- if current_is_switch_formatted? || last?
199
+ if parsing_options? && (current_is_switch_formatted? || last?)
160
200
  if option.boolean?
161
201
  # No problem for boolean types
162
202
  elsif no_or_skip?(switch)
@@ -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 responsable 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
@@ -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 = []
@@ -58,10 +58,10 @@ class Thor
58
58
  # ==== Example
59
59
  # say("I know you knew that.")
60
60
  #
61
- def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)$/))
61
+ def say(message="", color=nil, force_new_line=(message.to_s !~ /( |\t)\Z/))
62
62
  message = message.to_s
63
63
 
64
- message = set_color(message, *color) if color
64
+ message = set_color(message, *color) if color && can_display_colors?
65
65
 
66
66
  spaces = " " * padding
67
67
 
@@ -226,20 +226,20 @@ class Thor
226
226
  answer = ask %[Overwrite #{destination}? (enter "h" for help) #{options}]
227
227
 
228
228
  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
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
243
243
  end
244
244
  end
245
245
  end
@@ -275,6 +275,10 @@ class Thor
275
275
 
276
276
  protected
277
277
 
278
+ def can_display_colors?
279
+ false
280
+ end
281
+
278
282
  def lookup_color(color)
279
283
  return color unless color.is_a?(Symbol)
280
284
  self.class.const_get(color.to_s.upcase)
@@ -370,7 +374,7 @@ HELP
370
374
 
371
375
  def ask_simply(statement, color=nil)
372
376
  say("#{statement} ", color)
373
- stdin.gets.strip
377
+ stdin.gets.tap{|text| text.strip! if text}
374
378
  end
375
379
 
376
380
  def ask_filtered(statement, answer_set, *args)
@@ -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