thor 0.13.4 → 0.14.3

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.
Files changed (45) hide show
  1. data/CHANGELOG.rdoc +5 -3
  2. data/{README.rdoc → README.md} +65 -55
  3. data/Thorfile +3 -2
  4. data/lib/thor/actions/create_file.rb +3 -1
  5. data/lib/thor/actions/directory.rb +4 -2
  6. data/lib/thor/actions/file_manipulation.rb +30 -7
  7. data/lib/thor/actions.rb +31 -9
  8. data/lib/thor/base.rb +38 -15
  9. data/lib/thor/core_ext/hash_with_indifferent_access.rb +1 -1
  10. data/lib/thor/group.rb +22 -20
  11. data/lib/thor/invocation.rb +45 -57
  12. data/lib/thor/parser/argument.rb +15 -15
  13. data/lib/thor/parser/arguments.rb +14 -3
  14. data/lib/thor/parser/option.rb +38 -46
  15. data/lib/thor/parser/options.rb +26 -22
  16. data/lib/thor/runner.rb +11 -16
  17. data/lib/thor/shell/basic.rb +48 -12
  18. data/lib/thor/shell/html.rb +121 -0
  19. data/lib/thor/shell.rb +7 -2
  20. data/lib/thor/task.rb +63 -51
  21. data/lib/thor/util.rb +15 -16
  22. data/lib/thor/version.rb +1 -1
  23. data/lib/thor.rb +118 -45
  24. data/spec/actions/directory_spec.rb +2 -2
  25. data/spec/actions/file_manipulation_spec.rb +7 -0
  26. data/spec/actions_spec.rb +21 -3
  27. data/spec/base_spec.rb +9 -3
  28. data/spec/fixtures/doc/block_helper.rb +3 -0
  29. data/spec/fixtures/doc/components/.empty_directory +0 -0
  30. data/spec/fixtures/group.thor +16 -4
  31. data/spec/fixtures/path with spaces +0 -0
  32. data/spec/fixtures/script.thor +48 -4
  33. data/spec/group_spec.rb +3 -3
  34. data/spec/invocation_spec.rb +1 -8
  35. data/spec/parser/option_spec.rb +2 -2
  36. data/spec/parser/options_spec.rb +27 -0
  37. data/spec/runner_spec.rb +16 -8
  38. data/spec/shell/basic_spec.rb +13 -4
  39. data/spec/shell/html_spec.rb +27 -0
  40. data/spec/shell_spec.rb +13 -0
  41. data/spec/spec_helper.rb +4 -3
  42. data/spec/task_spec.rb +7 -7
  43. data/spec/thor_spec.rb +103 -6
  44. data/spec/util_spec.rb +3 -7
  45. metadata +63 -7
data/lib/thor/group.rb CHANGED
@@ -6,7 +6,7 @@ require 'thor/base'
6
6
  # tasks.
7
7
  class Thor::Group
8
8
  class << self
9
- # The descrition for this Thor::Group. If none is provided, but a source root
9
+ # The description for this Thor::Group. If none is provided, but a source root
10
10
  # exists, tries to find the USAGE one folder above it, otherwise searches
11
11
  # in the superclass.
12
12
  #
@@ -22,21 +22,6 @@ class Thor::Group
22
22
  end
23
23
  end
24
24
 
25
- # Start works differently in Thor::Group, it simply invokes all tasks
26
- # inside the class.
27
- #
28
- def start(original_args=ARGV, config={})
29
- super do |given_args|
30
- if Thor::HELP_MAPPINGS.include?(given_args.first)
31
- help(config[:shell])
32
- return
33
- end
34
-
35
- args, opts = Thor::Options.split(given_args)
36
- new(args, opts, config).invoke
37
- end
38
- end
39
-
40
25
  # Prints help information.
41
26
  #
42
27
  # ==== Options
@@ -129,7 +114,7 @@ class Thor::Group
129
114
 
130
115
  names.each do |name|
131
116
  unless class_options.key?(name)
132
- raise ArgumentError, "You have to define the option #{name.inspect} " <<
117
+ raise ArgumentError, "You have to define the option #{name.inspect} " <<
133
118
  "before setting invoke_from_option."
134
119
  end
135
120
 
@@ -225,15 +210,32 @@ class Thor::Group
225
210
 
226
211
  protected
227
212
 
213
+ # The method responsible for dispatching given the args.
214
+ def dispatch(task, given_args, given_opts, config) #:nodoc:
215
+ if Thor::HELP_MAPPINGS.include?(given_args.first)
216
+ help(config[:shell])
217
+ return
218
+ end
219
+
220
+ args, opts = Thor::Options.split(given_args)
221
+ opts = given_opts || opts
222
+
223
+ if task
224
+ new(args, opts, config).invoke_task(all_tasks[task])
225
+ else
226
+ new(args, opts, config).invoke_all
227
+ end
228
+ end
229
+
228
230
  # The banner for this class. You can customize it if you are invoking the
229
231
  # thor class by another ways which is not the Thor::Runner.
230
232
  def banner
231
- "#{banner_base} #{self_task.formatted_usage(self, false)}"
233
+ "#{basename} #{self_task.formatted_usage(self, false)}"
232
234
  end
233
235
 
234
236
  # Represents the whole class as a task.
235
237
  def self_task #:nodoc:
236
- Thor::Task::Dynamic.new(self.namespace, class_options)
238
+ Thor::DynamicTask.new(self.namespace, class_options)
237
239
  end
238
240
 
239
241
  def baseclass #:nodoc:
@@ -241,7 +243,7 @@ class Thor::Group
241
243
  end
242
244
 
243
245
  def create_task(meth) #:nodoc:
244
- tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil)
246
+ tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil, nil)
245
247
  true
246
248
  end
247
249
  end
@@ -10,10 +10,10 @@ class Thor
10
10
  # available only in class methods invocations (i.e. in Thor::Group).
11
11
  def prepare_for_invocation(key, name) #:nodoc:
12
12
  case name
13
- when Symbol, String
14
- Thor::Util.find_class_and_task_by_namespace(name.to_s)
15
- else
16
- name
13
+ when Symbol, String
14
+ Thor::Util.find_class_and_task_by_namespace(name.to_s, !key)
15
+ else
16
+ name
17
17
  end
18
18
  end
19
19
  end
@@ -94,29 +94,34 @@ class Thor
94
94
  # invoke Rspec::RR, [], :style => :foo
95
95
  #
96
96
  def invoke(name=nil, *args)
97
+ if name.nil?
98
+ warn "[Thor] Calling invoke() without argument is deprecated. Please use invoke_all instead.\n#{caller.join("\n")}"
99
+ return invoke_all
100
+ end
101
+
97
102
  args.unshift(nil) if Array === args.first || NilClass === args.first
98
103
  task, args, opts, config = args
99
104
 
100
- object, task = _prepare_for_invocation(name, task)
101
- klass, instance = _initialize_klass_with_initializer(object, args, opts, config)
105
+ klass, task = _retrieve_class_and_task(name, task)
106
+ raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
107
+
108
+ args, opts, config = _parse_initialization_options(args, opts, config)
109
+ klass.send(:dispatch, task, args, opts, config)
110
+ end
102
111
 
103
- method_args = []
104
- current = @_invocations[klass]
112
+ # Invoke the given task if the given args.
113
+ def invoke_task(task, *args) #:nodoc:
114
+ current = @_invocations[self.class]
105
115
 
106
- iterator = proc do |_, task|
107
- unless current.include?(task.name)
108
- current << task.name
109
- task.run(instance, method_args)
110
- end
116
+ unless current.include?(task.name)
117
+ current << task.name
118
+ task.run(self, *args)
111
119
  end
120
+ end
112
121
 
113
- if task
114
- args ||= []
115
- method_args = args[Range.new(klass.arguments.size, -1)] || []
116
- iterator.call(nil, task)
117
- else
118
- klass.all_tasks.map(&iterator)
119
- end
122
+ # Invoke all tasks for the current instance.
123
+ def invoke_all #:nodoc:
124
+ self.class.all_tasks.map { |_, task| invoke_task(task) }
120
125
  end
121
126
 
122
127
  # Invokes using shell padding.
@@ -131,50 +136,33 @@ class Thor
131
136
  { :invocations => @_invocations }
132
137
  end
133
138
 
134
- # This method can receive several different types of arguments and it's then
135
- # responsible to normalize them by returning the object where the task should
136
- # be invoked and a Thor::Task object.
137
- def _prepare_for_invocation(name, sent_task=nil) #:nodoc:
138
- if name.is_a?(Thor::Task)
139
- task = name
140
- elsif task = self.class.all_tasks[name.to_s]
141
- object = self
139
+ # This method simply retrieves the class and task to be invoked.
140
+ # If the name is nil or the given name is a task in the current class,
141
+ # use the given name and return self as class. Otherwise, call
142
+ # prepare_for_invocation in the current class.
143
+ def _retrieve_class_and_task(name, sent_task=nil) #:nodoc:
144
+ case
145
+ when name.nil?
146
+ [self.class, nil]
147
+ when self.class.all_tasks[name.to_s]
148
+ [self.class, name.to_s]
142
149
  else
143
- object, task = self.class.prepare_for_invocation(nil, name)
144
- task ||= sent_task
150
+ klass, task = self.class.prepare_for_invocation(nil, name)
151
+ [klass, task || sent_task]
145
152
  end
146
-
147
- # If the object was not set, use self and use the name as task.
148
- object, task = self, name unless object
149
- return object, _validate_task(object, task)
150
- end
151
-
152
- # Check if the object given is a Thor class object and get a task object
153
- # for it.
154
- def _validate_task(object, task) #:nodoc:
155
- klass = object.is_a?(Class) ? object : object.class
156
- raise "Expected Thor class, got #{klass}" unless klass <= Thor::Base
157
-
158
- task ||= klass.default_task if klass.respond_to?(:default_task)
159
- task = klass.all_tasks[task.to_s] || Thor::Task::Dynamic.new(task) if task && !task.is_a?(Thor::Task)
160
- task
161
153
  end
162
154
 
163
155
  # Initialize klass using values stored in the @_initializer.
164
- def _initialize_klass_with_initializer(object, args, opts, config) #:nodoc:
165
- if object.is_a?(Class)
166
- klass = object
156
+ def _parse_initialization_options(args, opts, config) #:nodoc:
157
+ stored_args, stored_opts, stored_config = @_initializer
167
158
 
168
- stored_args, stored_opts, stored_config = @_initializer
169
- args ||= stored_args.dup
170
- opts ||= stored_opts.dup
159
+ args ||= stored_args.dup
160
+ opts ||= stored_opts.dup
171
161
 
172
- config ||= {}
173
- config = stored_config.merge(_shared_configuration).merge!(config)
174
- [ klass, klass.new(args, opts, config) ]
175
- else
176
- [ object.class, object ]
177
- end
162
+ config ||= {}
163
+ config = stored_config.merge(_shared_configuration).merge!(config)
164
+
165
+ [ args, opts, config ]
178
166
  end
179
167
  end
180
168
  end
@@ -31,10 +31,10 @@ class Thor
31
31
 
32
32
  def show_default?
33
33
  case default
34
- when Array, String, Hash
35
- !default.empty?
36
- else
37
- default
34
+ when Array, String, Hash
35
+ !default.empty?
36
+ else
37
+ default
38
38
  end
39
39
  end
40
40
 
@@ -45,21 +45,21 @@ class Thor
45
45
  end
46
46
 
47
47
  def valid_type?(type)
48
- VALID_TYPES.include?(type.to_sym)
48
+ self.class::VALID_TYPES.include?(type.to_sym)
49
49
  end
50
50
 
51
51
  def default_banner
52
52
  case type
53
- when :boolean
54
- nil
55
- when :string, :default
56
- human_name.upcase
57
- when :numeric
58
- "N"
59
- when :hash
60
- "key:value"
61
- when :array
62
- "one two three"
53
+ when :boolean
54
+ nil
55
+ when :string, :default
56
+ human_name.upcase
57
+ when :numeric
58
+ "N"
59
+ when :hash
60
+ "key:value"
61
+ when :array
62
+ "one two three"
63
63
  end
64
64
  end
65
65
 
@@ -51,6 +51,11 @@ class Thor
51
51
 
52
52
  private
53
53
 
54
+ def no_or_skip?(arg)
55
+ arg =~ /^--(no|skip)-([-\w]+)$/
56
+ $2
57
+ end
58
+
54
59
  def last?
55
60
  @pile.empty?
56
61
  end
@@ -114,7 +119,7 @@ class Thor
114
119
  array
115
120
  end
116
121
 
117
- # Check if the peel is numeric ofrmat and return a Float or Integer.
122
+ # Check if the peek is numeric format and return a Float or Integer.
118
123
  # Otherwise raises an error.
119
124
  #
120
125
  def parse_numeric(name)
@@ -127,10 +132,16 @@ class Thor
127
132
  $&.index('.') ? shift.to_f : shift.to_i
128
133
  end
129
134
 
130
- # Parse string, i.e., just return the current value in the pile.
135
+ # Parse string:
136
+ # for --string-arg, just return the current value in the pile
137
+ # for --no-string-arg, nil
131
138
  #
132
139
  def parse_string(name)
133
- shift
140
+ if no_or_skip?(name)
141
+ nil
142
+ else
143
+ shift
144
+ end
134
145
  end
135
146
 
136
147
  # Raises an error if @non_assigned_required array is not empty.
@@ -1,13 +1,14 @@
1
1
  class Thor
2
2
  class Option < Argument #:nodoc:
3
- attr_reader :aliases, :group
3
+ attr_reader :aliases, :group, :lazy_default
4
4
 
5
5
  VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
6
6
 
7
- def initialize(name, description=nil, required=nil, type=nil, default=nil, banner=nil, group=nil, aliases=nil)
7
+ def initialize(name, description=nil, required=nil, type=nil, default=nil, banner=nil, lazy_default=nil, group=nil, aliases=nil)
8
8
  super(name, description, required, type, default, banner)
9
- @aliases = [*aliases].compact
10
- @group = group.to_s.capitalize if group
9
+ @lazy_default = lazy_default
10
+ @group = group.to_s.capitalize if group
11
+ @aliases = [*aliases].compact
11
12
  end
12
13
 
13
14
  # This parse quick options given as method_options. It makes several
@@ -48,23 +49,22 @@ class Thor
48
49
  default = value
49
50
 
50
51
  type = case value
51
- when Symbol
52
- default = nil
53
-
54
- if VALID_TYPES.include?(value)
55
- value
56
- elsif required = (value == :required)
57
- :string
58
- end
59
- when TrueClass, FalseClass
60
- :boolean
61
- when Numeric
62
- :numeric
63
- when Hash, Array, String
64
- value.class.name.downcase.to_sym
52
+ when Symbol
53
+ default = nil
54
+ if VALID_TYPES.include?(value)
55
+ value
56
+ elsif required = (value == :required)
57
+ :string
58
+ end
59
+ when TrueClass, FalseClass
60
+ :boolean
61
+ when Numeric
62
+ :numeric
63
+ when Hash, Array, String
64
+ value.class.name.downcase.to_sym
65
65
  end
66
66
 
67
- self.new(name.to_s, nil, required, type, default, nil, nil, aliases)
67
+ self.new(name.to_s, nil, required, type, default, nil, nil, nil, aliases)
68
68
  end
69
69
 
70
70
  def switch_name
@@ -91,38 +91,30 @@ class Thor
91
91
  end
92
92
  end
93
93
 
94
- # Allow some type predicates as: boolean?, string? and etc.
95
- #
96
- def method_missing(method, *args, &block)
97
- given = method.to_s.sub(/\?$/, '').to_sym
98
- if valid_type?(given)
99
- self.type == given
100
- else
101
- super
102
- end
94
+ VALID_TYPES.each do |type|
95
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
96
+ def #{type}?
97
+ self.type == #{type.inspect}
98
+ end
99
+ RUBY
103
100
  end
104
101
 
105
- protected
106
-
107
- def validate!
108
- raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
109
- end
110
-
111
- def valid_type?(type)
112
- VALID_TYPES.include?(type.to_sym)
113
- end
102
+ protected
114
103
 
115
- def dasherized?
116
- name.index('-') == 0
117
- end
104
+ def validate!
105
+ raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
106
+ end
118
107
 
119
- def undasherize(str)
120
- str.sub(/^-{1,2}/, '')
121
- end
108
+ def dasherized?
109
+ name.index('-') == 0
110
+ end
122
111
 
123
- def dasherize(str)
124
- (str.length > 1 ? "--" : "-") + str.gsub('_', '-')
125
- end
112
+ def undasherize(str)
113
+ str.sub(/^-{1,2}/, '')
114
+ end
126
115
 
116
+ def dasherize(str)
117
+ (str.length > 1 ? "--" : "-") + str.gsub('_', '-')
118
+ end
127
119
  end
128
120
  end
@@ -53,7 +53,9 @@ class Thor
53
53
  @pile = args.dup
54
54
 
55
55
  while peek
56
- if current_is_switch?
56
+ match, is_switch = current_is_switch?
57
+
58
+ if is_switch
57
59
  case shift
58
60
  when SHORT_SQ_RE
59
61
  unshift($1.split('').map { |f| "-#{f}" })
@@ -68,7 +70,7 @@ class Thor
68
70
  switch = normalize_switch(switch)
69
71
  option = switch_option(switch)
70
72
  @assigns[option.human_name] = parse_peek(switch, option)
71
- elsif current_is_switch_formatted?
73
+ elsif match
72
74
  @unknown << shift
73
75
  else
74
76
  shift
@@ -92,15 +94,17 @@ class Thor
92
94
  #
93
95
  def current_is_switch?
94
96
  case peek
95
- when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
96
- switch?($1)
97
- when SHORT_SQ_RE
98
- $1.split('').any? { |f| switch?("-#{f}") }
97
+ when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
98
+ [true, switch?($1)]
99
+ when SHORT_SQ_RE
100
+ [true, $1.split('').any? { |f| switch?("-#{f}") }]
101
+ else
102
+ [false, false]
99
103
  end
100
104
  end
101
105
 
102
- def switch_formatted?(arg)
103
- case arg
106
+ def current_is_switch_formatted?
107
+ case peek
104
108
  when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
105
109
  true
106
110
  else
@@ -108,12 +112,8 @@ class Thor
108
112
  end
109
113
  end
110
114
 
111
- def current_is_switch_formatted?
112
- switch_formatted? peek
113
- end
114
-
115
115
  def switch?(arg)
116
- switch_option(arg) || @shorts.key?(arg)
116
+ switch_option(normalize_switch(arg))
117
117
  end
118
118
 
119
119
  def switch_option(arg)
@@ -124,22 +124,25 @@ class Thor
124
124
  end
125
125
  end
126
126
 
127
- def no_or_skip?(arg)
128
- arg =~ /^--(no|skip)-([-\w]+)$/
129
- $2
130
- end
131
-
132
127
  # Check if the given argument is actually a shortcut.
133
128
  #
134
129
  def normalize_switch(arg)
135
- @shorts.key?(arg) ? @shorts[arg] : arg
130
+ (@shorts[arg] || arg).tr('_', '-')
136
131
  end
137
132
 
138
133
  # Parse boolean values which can be given as --foo=true, --foo or --no-foo.
139
134
  #
140
135
  def parse_boolean(switch)
141
136
  if current_is_value?
142
- ["true", "TRUE", "t", "T", true].include?(shift)
137
+ if ["true", "TRUE", "t", "T", true].include?(peek)
138
+ shift
139
+ true
140
+ elsif ["false", "FALSE", "f", "F", false].include?(peek)
141
+ shift
142
+ false
143
+ else
144
+ true
145
+ end
143
146
  else
144
147
  @switches.key?(switch) || !no_or_skip?(switch)
145
148
  end
@@ -155,7 +158,9 @@ class Thor
155
158
  return nil # User set value to nil
156
159
  elsif option.string? && !option.required?
157
160
  # Return the default if there is one, else the human name
158
- return option.default || option.human_name
161
+ return option.lazy_default || option.default || option.human_name
162
+ elsif option.lazy_default
163
+ return option.lazy_default
159
164
  else
160
165
  raise MalformattedArgumentError, "No value provided for option '#{switch}'"
161
166
  end
@@ -164,6 +169,5 @@ class Thor
164
169
  @non_assigned_required.delete(option)
165
170
  send(:"parse_#{option.type}", switch)
166
171
  end
167
-
168
172
  end
169
173
  end
data/lib/thor/runner.rb CHANGED
@@ -13,10 +13,10 @@ class Thor::Runner < Thor #:nodoc:
13
13
 
14
14
  # Override Thor#help so it can give information about any class and any method.
15
15
  #
16
- def help(meth=nil)
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)
19
+ klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
20
20
  klass.start(["-h", task].compact, :shell => self.shell)
21
21
  else
22
22
  super
@@ -29,7 +29,7 @@ class Thor::Runner < Thor #:nodoc:
29
29
  def method_missing(meth, *args)
30
30
  meth = meth.to_s
31
31
  initialize_thorfiles(meth)
32
- klass, task = Thor::Util.find_class_and_task_by_namespace!(meth)
32
+ klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
33
33
  args.unshift(task) if task
34
34
  klass.start(args, :shell => self.shell)
35
35
  end
@@ -39,15 +39,15 @@ class Thor::Runner < Thor #:nodoc:
39
39
  def install(name)
40
40
  initialize_thorfiles
41
41
 
42
- # If a directory name is provided as the argument, look for a 'main.thor'
42
+ # If a directory name is provided as the argument, look for a 'main.thor'
43
43
  # task in said directory.
44
44
  begin
45
45
  if File.directory?(File.expand_path(name))
46
46
  base, package = File.join(name, "main.thor"), :directory
47
- contents = open(base).read
47
+ contents = open(base) {|input| input.read }
48
48
  else
49
49
  base, package = name, :file
50
- contents = open(name).read
50
+ contents = open(name) {|input| input.read }
51
51
  end
52
52
  rescue OpenURI::HTTPError
53
53
  raise Error, "Error opening URI '#{name}'"
@@ -139,7 +139,7 @@ class Thor::Runner < Thor #:nodoc:
139
139
  end
140
140
 
141
141
  desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
142
- method_options :substring => :boolean, :group => :string, :all => :boolean
142
+ method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean
143
143
  def list(search="")
144
144
  initialize_thorfiles
145
145
 
@@ -156,8 +156,8 @@ class Thor::Runner < Thor #:nodoc:
156
156
 
157
157
  private
158
158
 
159
- def self.banner(task)
160
- "thor " + task.formatted_usage(self, false)
159
+ def self.banner(task, all = false, subcommand = false)
160
+ "thor " + task.formatted_usage(self, all, subcommand)
161
161
  end
162
162
 
163
163
  def thor_root
@@ -199,7 +199,7 @@ class Thor::Runner < Thor #:nodoc:
199
199
  #
200
200
  def initialize_thorfiles(relevant_to=nil, skip_lookup=false)
201
201
  thorfiles(relevant_to, skip_lookup).each do |f|
202
- Thor::Util.load_thorfile(f) unless Thor::Base.subclass_files.keys.include?(File.expand_path(f))
202
+ Thor::Util.load_thorfile(f, nil, options[:debug]) unless Thor::Base.subclass_files.keys.include?(File.expand_path(f))
203
203
  end
204
204
  end
205
205
 
@@ -267,16 +267,11 @@ class Thor::Runner < Thor #:nodoc:
267
267
  raise Error, "No Thor tasks available" if klasses.empty?
268
268
  show_modules if with_modules && !thor_yaml.empty?
269
269
 
270
- # Remove subclasses
271
- klasses.dup.each do |klass|
272
- klasses -= Thor::Util.thor_classes_in(klass)
273
- end
274
-
275
270
  list = Hash.new { |h,k| h[k] = [] }
276
271
  groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
277
272
 
278
273
  # Get classes which inherit from Thor
279
- (klasses - groups).each { |k| list[k.namespace] += k.printable_tasks(false) }
274
+ (klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_tasks(false) }
280
275
 
281
276
  # Get classes which inherit from Thor::Base
282
277
  groups.map! { |k| k.printable_tasks(false).first }