josevalim-thor 0.10.0
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.
- data/CHANGELOG.rdoc +73 -0
- data/LICENSE +20 -0
- data/README.markdown +76 -0
- data/Rakefile +6 -0
- data/bin/rake2thor +87 -0
- data/bin/thor +7 -0
- data/lib/thor.rb +229 -0
- data/lib/thor/actions.rb +147 -0
- data/lib/thor/actions/commands.rb +61 -0
- data/lib/thor/actions/copy_file.rb +32 -0
- data/lib/thor/actions/create_file.rb +48 -0
- data/lib/thor/actions/directory.rb +36 -0
- data/lib/thor/actions/empty_directory.rb +30 -0
- data/lib/thor/actions/get.rb +58 -0
- data/lib/thor/actions/gsub_file.rb +77 -0
- data/lib/thor/actions/inject_into_file.rb +93 -0
- data/lib/thor/actions/template.rb +37 -0
- data/lib/thor/actions/templater.rb +163 -0
- data/lib/thor/base.rb +447 -0
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +59 -0
- data/lib/thor/core_ext/ordered_hash.rb +133 -0
- data/lib/thor/error.rb +27 -0
- data/lib/thor/group.rb +90 -0
- data/lib/thor/option.rb +210 -0
- data/lib/thor/options.rb +282 -0
- data/lib/thor/runner.rb +296 -0
- data/lib/thor/shell/basic.rb +198 -0
- data/lib/thor/task.rb +85 -0
- data/lib/thor/tasks.rb +3 -0
- data/lib/thor/tasks/install.rb +35 -0
- data/lib/thor/tasks/package.rb +31 -0
- data/lib/thor/tasks/spec.rb +70 -0
- data/lib/thor/util.rb +209 -0
- metadata +93 -0
data/lib/thor/options.rb
ADDED
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'thor/option'
|
2
|
+
|
3
|
+
class Thor
|
4
|
+
|
5
|
+
# This is a modified version of Daniel Berger's Getopt::Long class, licensed
|
6
|
+
# under Ruby's license.
|
7
|
+
#
|
8
|
+
class Options
|
9
|
+
NUMERIC = /(\d*\.\d+|\d+)/
|
10
|
+
LONG_RE = /^(--\w+[-\w+]*)$/
|
11
|
+
SHORT_RE = /^(-[a-z])$/i
|
12
|
+
EQ_RE = /^(--\w+[-\w+]*|-[a-z])=(.*)$/i
|
13
|
+
SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
|
14
|
+
SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
|
15
|
+
|
16
|
+
# Receives a hash and makes it switches.
|
17
|
+
#
|
18
|
+
def self.to_switches(options)
|
19
|
+
options.map do |key, value|
|
20
|
+
case value
|
21
|
+
when true
|
22
|
+
"--#{key}"
|
23
|
+
when Array
|
24
|
+
"--#{key} #{value.map{ |v| v.inspect }.join(' ')}"
|
25
|
+
when Hash
|
26
|
+
"--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}"
|
27
|
+
when nil, false
|
28
|
+
""
|
29
|
+
else
|
30
|
+
"--#{key} #{value.inspect}"
|
31
|
+
end
|
32
|
+
end.join(" ")
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :arguments, :options, :trailing
|
36
|
+
|
37
|
+
# Takes an array of switches. Each array consists of up to three
|
38
|
+
# elements that indicate the name and type of switch. Returns a hash
|
39
|
+
# containing each switch name, minus the '-', as a key. The value
|
40
|
+
# for each key depends on the type of switch and/or the value provided
|
41
|
+
# by the user.
|
42
|
+
#
|
43
|
+
# The long switch _must_ be provided. The short switch defaults to the
|
44
|
+
# first letter of the short switch. The default type is :boolean.
|
45
|
+
#
|
46
|
+
# Example:
|
47
|
+
#
|
48
|
+
# opts = Thor::Options.new(
|
49
|
+
# "--debug" => true,
|
50
|
+
# ["--verbose", "-v"] => true,
|
51
|
+
# ["--level", "-l"] => :numeric
|
52
|
+
# ).parse(args)
|
53
|
+
#
|
54
|
+
def initialize(switches={})
|
55
|
+
@arguments, @shorts, @options = [], {}, {}
|
56
|
+
@non_assigned_required, @non_assigned_arguments, @trailing = [], [], []
|
57
|
+
|
58
|
+
@switches = switches.values.inject({}) do |mem, option|
|
59
|
+
@non_assigned_required << option if option.required?
|
60
|
+
|
61
|
+
if option.argument?
|
62
|
+
@non_assigned_arguments << option
|
63
|
+
elsif option.default
|
64
|
+
@options[option.human_name] = option.default
|
65
|
+
end
|
66
|
+
|
67
|
+
# If there are no shortcuts specified, generate one using the first character
|
68
|
+
shorts = option.aliases.dup
|
69
|
+
shorts << "-" + option.human_name[0,1] if shorts.empty? and option.human_name.length > 1
|
70
|
+
shorts.each { |short| @shorts[short.to_s] ||= option.switch_name }
|
71
|
+
|
72
|
+
mem[option.switch_name] = option
|
73
|
+
mem
|
74
|
+
end
|
75
|
+
|
76
|
+
remove_duplicated_shortcuts!
|
77
|
+
end
|
78
|
+
|
79
|
+
def parse(args)
|
80
|
+
@pile, @trailing = args, []
|
81
|
+
|
82
|
+
while peek
|
83
|
+
if current_is_switch?
|
84
|
+
case shift
|
85
|
+
when SHORT_SQ_RE
|
86
|
+
unshift($1.split('').map { |f| "-#{f}" })
|
87
|
+
next
|
88
|
+
when EQ_RE, SHORT_NUM
|
89
|
+
unshift($2)
|
90
|
+
switch = $1
|
91
|
+
when LONG_RE, SHORT_RE
|
92
|
+
switch = $1
|
93
|
+
end
|
94
|
+
|
95
|
+
switch = normalize_switch(switch)
|
96
|
+
option = switch_option(switch)
|
97
|
+
|
98
|
+
next if option.nil? || option.argument?
|
99
|
+
|
100
|
+
check_requirement!(switch, option)
|
101
|
+
parse_option(switch, option, @options)
|
102
|
+
else
|
103
|
+
unless @non_assigned_arguments.empty?
|
104
|
+
argument = @non_assigned_arguments.shift
|
105
|
+
parse_option(argument.switch_name, argument, @options)
|
106
|
+
@arguments << @options.delete(argument.human_name)
|
107
|
+
else
|
108
|
+
@trailing << shift
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
assign_arguments_default_values!
|
114
|
+
check_validity!
|
115
|
+
@options
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def peek
|
121
|
+
@pile.first
|
122
|
+
end
|
123
|
+
|
124
|
+
def shift
|
125
|
+
@pile.shift
|
126
|
+
end
|
127
|
+
|
128
|
+
def unshift(arg)
|
129
|
+
unless arg.kind_of?(Array)
|
130
|
+
@pile.unshift(arg)
|
131
|
+
else
|
132
|
+
@pile = arg + @pile
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns true if the current peek is a switch.
|
137
|
+
#
|
138
|
+
def current_is_switch?
|
139
|
+
case peek
|
140
|
+
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
|
141
|
+
switch?($1)
|
142
|
+
when SHORT_SQ_RE
|
143
|
+
$1.split('').any? { |f| switch?("-#{f}") }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# Check if the given argument matches with a switch.
|
148
|
+
#
|
149
|
+
def switch?(arg)
|
150
|
+
switch_option(arg) || @shorts.key?(arg)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Returns the option object for the given switch.
|
154
|
+
#
|
155
|
+
def switch_option(arg)
|
156
|
+
if arg =~ /^--no-(\w+)$/
|
157
|
+
@switches[arg] || @switches["--#{$1}"]
|
158
|
+
else
|
159
|
+
@switches[arg]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Check if the given argument is actually a shortcut.
|
164
|
+
#
|
165
|
+
def normalize_switch(arg)
|
166
|
+
@shorts.key?(arg) ? @shorts[arg] : arg
|
167
|
+
end
|
168
|
+
|
169
|
+
# Receives switch, option and the current values hash and assign the next
|
170
|
+
# value to it. At the end, remove the option from the array where non
|
171
|
+
# assigned requireds are kept.
|
172
|
+
#
|
173
|
+
def parse_option(switch, option, hash)
|
174
|
+
human_name = option.human_name
|
175
|
+
|
176
|
+
case option.type
|
177
|
+
when :default
|
178
|
+
hash[human_name] = peek.nil? || peek.to_s =~ /^-/ || shift
|
179
|
+
when :boolean
|
180
|
+
if !@switches.key?(switch) && switch =~ /^--no-(\w+)$/
|
181
|
+
hash[$1] = false
|
182
|
+
else
|
183
|
+
hash[human_name] = true
|
184
|
+
end
|
185
|
+
when :string
|
186
|
+
hash[human_name] = shift
|
187
|
+
when :numeric
|
188
|
+
hash[human_name] = parse_numeric(switch)
|
189
|
+
when :hash
|
190
|
+
hash[human_name] = parse_hash
|
191
|
+
when :array
|
192
|
+
hash[human_name] = parse_array
|
193
|
+
end
|
194
|
+
|
195
|
+
@non_assigned_required.delete(option)
|
196
|
+
end
|
197
|
+
|
198
|
+
# Runs through the argument array getting strings that contains ":" and
|
199
|
+
# mark it as a hash:
|
200
|
+
#
|
201
|
+
# [ "name:string", "age:integer" ]
|
202
|
+
#
|
203
|
+
# Becomes:
|
204
|
+
#
|
205
|
+
# { "name" => "string", "age" => "integer" }
|
206
|
+
#
|
207
|
+
def parse_hash
|
208
|
+
hash = {}
|
209
|
+
|
210
|
+
while peek && peek !~ /^\-/
|
211
|
+
key, value = shift.split(':')
|
212
|
+
hash[key] = value
|
213
|
+
end
|
214
|
+
|
215
|
+
hash
|
216
|
+
end
|
217
|
+
|
218
|
+
# Runs through the argument array getting all strings until no string is
|
219
|
+
# found or a switch is found.
|
220
|
+
#
|
221
|
+
# ["a", "b", "c"]
|
222
|
+
#
|
223
|
+
# And returns it as an array:
|
224
|
+
#
|
225
|
+
# ["a", "b", "c"]
|
226
|
+
#
|
227
|
+
def parse_array
|
228
|
+
array = []
|
229
|
+
|
230
|
+
while peek && peek !~ /^\-/
|
231
|
+
array << shift
|
232
|
+
end
|
233
|
+
|
234
|
+
array
|
235
|
+
end
|
236
|
+
|
237
|
+
# Check if the peel is numeric ofrmat and return a Float or Integer.
|
238
|
+
# Otherwise raises an error.
|
239
|
+
#
|
240
|
+
def parse_numeric(switch)
|
241
|
+
unless peek =~ NUMERIC && $& == peek
|
242
|
+
raise MalformattedArgumentError, "expected numeric value for '#{switch}'; got #{peek.inspect}"
|
243
|
+
end
|
244
|
+
$&.index('.') ? shift.to_f : shift.to_i
|
245
|
+
end
|
246
|
+
|
247
|
+
# Raises an error if the option requires an input but it's not present.
|
248
|
+
#
|
249
|
+
def check_requirement!(switch, option)
|
250
|
+
if option.input_required?
|
251
|
+
raise RequiredArgumentMissingError, "no value provided for argument '#{switch}'" if peek.nil?
|
252
|
+
raise MalformattedArgumentError, "cannot pass switch '#{peek}' as an argument" if switch?(peek)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
# Raises an error if @required array is not empty after parsing.
|
257
|
+
#
|
258
|
+
def check_validity!
|
259
|
+
unless @non_assigned_required.empty?
|
260
|
+
switch_names = @non_assigned_required.map{ |o| o.switch_name }.join(', ')
|
261
|
+
raise RequiredArgumentMissingError, "no value provided for required arguments '#{switch_names}'"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Assign default values to the argument hash.
|
266
|
+
#
|
267
|
+
def assign_arguments_default_values!
|
268
|
+
@non_assigned_arguments.each do |option|
|
269
|
+
@arguments << option.default
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# Remove shortcuts that happen to coincide with any of the main switches
|
274
|
+
#
|
275
|
+
def remove_duplicated_shortcuts!
|
276
|
+
@shorts.keys.each do |short|
|
277
|
+
@shorts.delete(short) if @switches.key?(short)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
end
|
data/lib/thor/runner.rb
ADDED
@@ -0,0 +1,296 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'yaml'
|
4
|
+
require 'digest/md5'
|
5
|
+
require 'pathname'
|
6
|
+
|
7
|
+
class Thor::Runner < Thor
|
8
|
+
map "-T" => :list, "-i" => :install, "-u" => :update
|
9
|
+
|
10
|
+
# Override Thor#help so it can give information about any class and any method.
|
11
|
+
#
|
12
|
+
def help(meth=nil)
|
13
|
+
if meth && !self.respond_to?(meth)
|
14
|
+
initialize_thorfiles(meth)
|
15
|
+
klass, task = Thor::Util.namespace_to_thor_class(meth)
|
16
|
+
klass.start(["-h", task].compact, :shell => self.shell) # send mapping -h because it works with Thor::Group too
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# If a task is not found on Thor::Runner, method missing is invoked and
|
23
|
+
# Thor::Runner is then responsable for finding the task in all classes.
|
24
|
+
#
|
25
|
+
def method_missing(meth, *args)
|
26
|
+
meth = meth.to_s
|
27
|
+
initialize_thorfiles(meth)
|
28
|
+
klass, task = Thor::Util.namespace_to_thor_class(meth)
|
29
|
+
args.unshift(task) if task
|
30
|
+
klass.start(args, :shell => self.shell)
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "install NAME", "Install a Thor file into your system tasks, optionally named for future updates"
|
34
|
+
method_options :as => :optional, :relative => :boolean
|
35
|
+
def install(name)
|
36
|
+
initialize_thorfiles
|
37
|
+
|
38
|
+
# If a directory name is provided as the argument, look for a 'main.thor'
|
39
|
+
# task in said directory.
|
40
|
+
begin
|
41
|
+
if File.directory?(File.expand_path(name))
|
42
|
+
base, package = File.join(name, "main.thor"), :directory
|
43
|
+
contents = open(base).read
|
44
|
+
else
|
45
|
+
base, package = name, :file
|
46
|
+
contents = open(name).read
|
47
|
+
end
|
48
|
+
rescue OpenURI::HTTPError
|
49
|
+
raise Error, "Error opening URI '#{name}'"
|
50
|
+
rescue Errno::ENOENT
|
51
|
+
raise Error, "Error opening file '#{name}'"
|
52
|
+
end
|
53
|
+
|
54
|
+
say "Your Thorfile contains:"
|
55
|
+
say contents
|
56
|
+
|
57
|
+
return false if no?("Do you wish to continue [y/N]?")
|
58
|
+
|
59
|
+
as = options["as"] || begin
|
60
|
+
first_line = contents.split("\n")[0]
|
61
|
+
(match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
|
62
|
+
end
|
63
|
+
|
64
|
+
unless as
|
65
|
+
basename = File.basename(name)
|
66
|
+
as = ask("Please specify a name for #{name} in the system repository [#{basename}]:")
|
67
|
+
as = basename if as.empty?
|
68
|
+
end
|
69
|
+
|
70
|
+
location = if options[:relative] || name =~ /^http:\/\//
|
71
|
+
name
|
72
|
+
else
|
73
|
+
File.expand_path(name)
|
74
|
+
end
|
75
|
+
|
76
|
+
thor_yaml[as] = {
|
77
|
+
:filename => Digest::MD5.hexdigest(name + as),
|
78
|
+
:location => location,
|
79
|
+
:namespaces => Thor::Util.namespaces_in_contents(contents, base)
|
80
|
+
}
|
81
|
+
|
82
|
+
save_yaml(thor_yaml)
|
83
|
+
say "Storing thor file in your system repository"
|
84
|
+
destination = File.join(thor_root, thor_yaml[as][:filename])
|
85
|
+
|
86
|
+
if package == :file
|
87
|
+
File.open(destination, "w") { |f| f.puts contents }
|
88
|
+
else
|
89
|
+
FileUtils.cp_r(name, destination)
|
90
|
+
end
|
91
|
+
|
92
|
+
thor_yaml[as][:filename] # Indicate success
|
93
|
+
end
|
94
|
+
|
95
|
+
desc "uninstall NAME", "Uninstall a named Thor module"
|
96
|
+
def uninstall(name)
|
97
|
+
raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
|
98
|
+
say "Uninstalling #{name}."
|
99
|
+
FileUtils.rm_rf(File.join(thor_root, "#{thor_yaml[name][:filename]}"))
|
100
|
+
|
101
|
+
thor_yaml.delete(name)
|
102
|
+
save_yaml(thor_yaml)
|
103
|
+
|
104
|
+
puts "Done."
|
105
|
+
end
|
106
|
+
|
107
|
+
desc "update NAME", "Update a Thor file from its original location"
|
108
|
+
def update(name)
|
109
|
+
raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location]
|
110
|
+
|
111
|
+
say "Updating '#{name}' from #{thor_yaml[name][:location]}"
|
112
|
+
|
113
|
+
old_filename = thor_yaml[name][:filename]
|
114
|
+
self.options = self.options.merge("as" => name)
|
115
|
+
filename = install(thor_yaml[name][:location])
|
116
|
+
|
117
|
+
unless filename == old_filename
|
118
|
+
File.delete(File.join(thor_root, old_filename))
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
desc "installed", "List the installed Thor modules and tasks"
|
123
|
+
method_options :internal => :boolean
|
124
|
+
def installed
|
125
|
+
initialize_thorfiles(nil, true)
|
126
|
+
|
127
|
+
klasses = Thor::Base.subclasses
|
128
|
+
klasses -= [Thor, Thor::Runner] unless options["internal"]
|
129
|
+
|
130
|
+
display_klasses(true, klasses)
|
131
|
+
end
|
132
|
+
|
133
|
+
desc "list [SEARCH]",
|
134
|
+
"List the available thor tasks (--substring means SEARCH anywhere in the namespace)"
|
135
|
+
method_options :substring => :boolean, :group => :optional, :all => :boolean
|
136
|
+
def list(search="")
|
137
|
+
initialize_thorfiles
|
138
|
+
|
139
|
+
search = ".*#{search}" if options["substring"]
|
140
|
+
search = /^#{search}.*/i
|
141
|
+
group = options[:group] || "standard"
|
142
|
+
|
143
|
+
klasses = Thor::Base.subclasses.select do |k|
|
144
|
+
(options[:all] || k.group_name == group) && k.namespace =~ search
|
145
|
+
end
|
146
|
+
|
147
|
+
display_klasses(false, klasses)
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def thor_root
|
153
|
+
Thor::Util.thor_root
|
154
|
+
end
|
155
|
+
|
156
|
+
def thor_yaml
|
157
|
+
@thor_yaml ||= begin
|
158
|
+
yaml_file = File.join(thor_root, "thor.yml")
|
159
|
+
yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
|
160
|
+
yaml || {}
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Save the yaml file. If none exists in thor root, creates one.
|
165
|
+
#
|
166
|
+
def save_yaml(yaml)
|
167
|
+
yaml_file = File.join(thor_root, "thor.yml")
|
168
|
+
|
169
|
+
unless File.exists?(yaml_file)
|
170
|
+
FileUtils.mkdir_p(thor_root)
|
171
|
+
yaml_file = File.join(thor_root, "thor.yml")
|
172
|
+
FileUtils.touch(yaml_file)
|
173
|
+
end
|
174
|
+
|
175
|
+
File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml }
|
176
|
+
end
|
177
|
+
|
178
|
+
# Load the thorfiles. If relevant_to is supplied, looks for specific files
|
179
|
+
# in the thor_root instead of loading them all.
|
180
|
+
#
|
181
|
+
# By default, it also traverses the current path until find Thor files, as
|
182
|
+
# described in thorfiles. This look up can be skipped by suppliying
|
183
|
+
# skip_lookup true.
|
184
|
+
#
|
185
|
+
def initialize_thorfiles(relevant_to=nil, skip_lookup=false)
|
186
|
+
thorfiles(relevant_to, skip_lookup).each do |f|
|
187
|
+
Thor::Util.load_thorfile(f) unless Thor::Base.subclass_files.keys.include?(File.expand_path(f))
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Finds Thorfiles by traversing from your current directory down to the root
|
192
|
+
# directory of your system. If at any time we find a Thor file, we stop.
|
193
|
+
#
|
194
|
+
# We also ensure that system-wide Thorfiles are loaded first, so local
|
195
|
+
# Thorfiles can override them.
|
196
|
+
#
|
197
|
+
# ==== Example
|
198
|
+
#
|
199
|
+
# If we start at /Users/wycats/dev/thor ...
|
200
|
+
#
|
201
|
+
# 1. /Users/wycats/dev/thor
|
202
|
+
# 2. /Users/wycats/dev
|
203
|
+
# 3. /Users/wycats <-- we find a Thorfile here, so we stop
|
204
|
+
#
|
205
|
+
# Suppose we start at c:\Documents and Settings\james\dev\thor ...
|
206
|
+
#
|
207
|
+
# 1. c:\Documents and Settings\james\dev\thor
|
208
|
+
# 2. c:\Documents and Settings\james\dev
|
209
|
+
# 3. c:\Documents and Settings\james
|
210
|
+
# 4. c:\Documents and Settings
|
211
|
+
# 5. c:\ <-- no Thorfiles found!
|
212
|
+
#
|
213
|
+
def thorfiles(relevant_to=nil, skip_lookup=false)
|
214
|
+
# Deal with deprecated thor when :namespaces: is available as constants
|
215
|
+
save_yaml(thor_yaml) if Thor::Util.convert_constants_to_namespaces(thor_yaml)
|
216
|
+
|
217
|
+
thorfiles = []
|
218
|
+
|
219
|
+
unless skip_lookup
|
220
|
+
Pathname.pwd.ascend do |path|
|
221
|
+
thorfiles = Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten
|
222
|
+
break unless thorfiles.empty?
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Thor::Util.thor_root_glob)
|
227
|
+
files += thorfiles
|
228
|
+
files -= ["#{thor_root}/thor.yml"]
|
229
|
+
|
230
|
+
files.map! do |file|
|
231
|
+
File.directory?(file) ? File.join(file, "main.thor") : file
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Load thorfiles relevant to the given method. If you provide "foo:bar" it
|
236
|
+
# will load all thor files in the thor.yaml that has "foo" e "foo:bar"
|
237
|
+
# namespaces registered.
|
238
|
+
#
|
239
|
+
def thorfiles_relevant_to(meth)
|
240
|
+
lookup = [ meth, meth.split(":")[0...-1].join(":") ]
|
241
|
+
|
242
|
+
files = thor_yaml.select do |k, v|
|
243
|
+
v[:namespaces] && !(v[:namespaces] & lookup).empty?
|
244
|
+
end
|
245
|
+
|
246
|
+
files.map! { |k, v| File.join(thor_root, "#{v[:filename]}") }
|
247
|
+
files
|
248
|
+
end
|
249
|
+
|
250
|
+
# Display information about the given klasses. If with_module is given,
|
251
|
+
# it shows a table with information extracted from the yaml file.
|
252
|
+
#
|
253
|
+
def display_klasses(with_modules=false, klasses=Thor.subclasses)
|
254
|
+
klasses -= [Thor, Thor::Runner] unless with_modules
|
255
|
+
raise Error, "No Thor tasks available" if klasses.empty?
|
256
|
+
|
257
|
+
if with_modules && !thor_yaml.empty?
|
258
|
+
info = []
|
259
|
+
labels = ["Modules", "Namespaces"]
|
260
|
+
|
261
|
+
info << labels
|
262
|
+
info << [ "-" * labels[0].size, "-" * labels[1].size ]
|
263
|
+
|
264
|
+
thor_yaml.each do |name, hash|
|
265
|
+
info << [ name, hash[:namespaces].join(", ") ]
|
266
|
+
end
|
267
|
+
|
268
|
+
print_table info
|
269
|
+
say ""
|
270
|
+
end
|
271
|
+
|
272
|
+
unless klasses.empty?
|
273
|
+
klasses.each { |k| display_tasks(k) }
|
274
|
+
else
|
275
|
+
say "\033[1;34mNo Thor tasks available\033[0m"
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
# Display tasks from the given Thor class.
|
280
|
+
#
|
281
|
+
def display_tasks(klass)
|
282
|
+
unless klass.tasks.empty?
|
283
|
+
base = klass.namespace
|
284
|
+
|
285
|
+
if base == "default"
|
286
|
+
say "\033[1;35m#{base}\033[0m"
|
287
|
+
else
|
288
|
+
say "\033[1;34m#{base}\033[0m"
|
289
|
+
end
|
290
|
+
say "-" * base.length
|
291
|
+
|
292
|
+
klass.help(shell, :short => true, :namespace => true)
|
293
|
+
say
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|