engineyard 2.1.1 → 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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