thor 0.9.9 → 0.11.5
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 +29 -4
- data/README.rdoc +234 -0
- data/Thorfile +57 -0
- data/VERSION +1 -0
- data/bin/rake2thor +4 -0
- data/bin/thor +1 -1
- data/lib/thor.rb +216 -119
- data/lib/thor/actions.rb +272 -0
- data/lib/thor/actions/create_file.rb +102 -0
- data/lib/thor/actions/directory.rb +87 -0
- data/lib/thor/actions/empty_directory.rb +133 -0
- data/lib/thor/actions/file_manipulation.rb +195 -0
- data/lib/thor/actions/inject_into_file.rb +78 -0
- data/lib/thor/base.rb +510 -0
- data/lib/thor/core_ext/hash_with_indifferent_access.rb +75 -0
- data/lib/thor/core_ext/ordered_hash.rb +100 -0
- data/lib/thor/error.rb +25 -1
- data/lib/thor/group.rb +263 -0
- data/lib/thor/invocation.rb +178 -0
- data/lib/thor/parser.rb +4 -0
- data/lib/thor/parser/argument.rb +67 -0
- data/lib/thor/parser/arguments.rb +145 -0
- data/lib/thor/parser/option.rb +132 -0
- data/lib/thor/parser/options.rb +142 -0
- data/lib/thor/rake_compat.rb +67 -0
- data/lib/thor/runner.rb +232 -242
- data/lib/thor/shell.rb +72 -0
- data/lib/thor/shell/basic.rb +220 -0
- data/lib/thor/shell/color.rb +108 -0
- data/lib/thor/task.rb +97 -60
- data/lib/thor/util.rb +230 -55
- data/spec/actions/create_file_spec.rb +170 -0
- data/spec/actions/directory_spec.rb +118 -0
- data/spec/actions/empty_directory_spec.rb +91 -0
- data/spec/actions/file_manipulation_spec.rb +242 -0
- data/spec/actions/inject_into_file_spec.rb +80 -0
- data/spec/actions_spec.rb +291 -0
- data/spec/base_spec.rb +236 -0
- data/spec/core_ext/hash_with_indifferent_access_spec.rb +43 -0
- data/spec/core_ext/ordered_hash_spec.rb +115 -0
- data/spec/fixtures/bundle/execute.rb +6 -0
- data/spec/fixtures/doc/config.rb +1 -0
- data/spec/group_spec.rb +177 -0
- data/spec/invocation_spec.rb +107 -0
- data/spec/parser/argument_spec.rb +47 -0
- data/spec/parser/arguments_spec.rb +64 -0
- data/spec/parser/option_spec.rb +212 -0
- data/spec/parser/options_spec.rb +255 -0
- data/spec/rake_compat_spec.rb +64 -0
- data/spec/runner_spec.rb +204 -0
- data/spec/shell/basic_spec.rb +206 -0
- data/spec/shell/color_spec.rb +41 -0
- data/spec/shell_spec.rb +25 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/task_spec.rb +82 -0
- data/spec/thor_spec.rb +234 -0
- data/spec/util_spec.rb +196 -0
- metadata +69 -25
- data/README.markdown +0 -76
- data/Rakefile +0 -6
- data/lib/thor/options.rb +0 -242
- data/lib/thor/ordered_hash.rb +0 -64
- data/lib/thor/task_hash.rb +0 -22
- data/lib/thor/tasks.rb +0 -77
- data/lib/thor/tasks/package.rb +0 -18
@@ -0,0 +1,142 @@
|
|
1
|
+
class Thor
|
2
|
+
# This is a modified version of Daniel Berger's Getopt::Long class, licensed
|
3
|
+
# under Ruby's license.
|
4
|
+
#
|
5
|
+
class Options < Arguments #:nodoc:
|
6
|
+
LONG_RE = /^(--\w+[-\w+]*)$/
|
7
|
+
SHORT_RE = /^(-[a-z])$/i
|
8
|
+
EQ_RE = /^(--\w+[-\w+]*|-[a-z])=(.*)$/i
|
9
|
+
SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
|
10
|
+
SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
|
11
|
+
|
12
|
+
# Receives a hash and makes it switches.
|
13
|
+
#
|
14
|
+
def self.to_switches(options)
|
15
|
+
options.map do |key, value|
|
16
|
+
case value
|
17
|
+
when true
|
18
|
+
"--#{key}"
|
19
|
+
when Array
|
20
|
+
"--#{key} #{value.map{ |v| v.inspect }.join(' ')}"
|
21
|
+
when Hash
|
22
|
+
"--#{key} #{value.map{ |k,v| "#{k}:#{v}" }.join(' ')}"
|
23
|
+
when nil, false
|
24
|
+
""
|
25
|
+
else
|
26
|
+
"--#{key} #{value.inspect}"
|
27
|
+
end
|
28
|
+
end.join(" ")
|
29
|
+
end
|
30
|
+
|
31
|
+
# Takes a hash of Thor::Option objects.
|
32
|
+
#
|
33
|
+
def initialize(options={})
|
34
|
+
options = options.values
|
35
|
+
super(options)
|
36
|
+
@shorts, @switches = {}, {}
|
37
|
+
|
38
|
+
options.each do |option|
|
39
|
+
@switches[option.switch_name] = option
|
40
|
+
|
41
|
+
option.aliases.each do |short|
|
42
|
+
@shorts[short.to_s] ||= option.switch_name
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse(args)
|
48
|
+
@pile = args.dup
|
49
|
+
|
50
|
+
while peek
|
51
|
+
if current_is_switch?
|
52
|
+
case shift
|
53
|
+
when SHORT_SQ_RE
|
54
|
+
unshift($1.split('').map { |f| "-#{f}" })
|
55
|
+
next
|
56
|
+
when EQ_RE, SHORT_NUM
|
57
|
+
unshift($2)
|
58
|
+
switch = $1
|
59
|
+
when LONG_RE, SHORT_RE
|
60
|
+
switch = $1
|
61
|
+
end
|
62
|
+
|
63
|
+
switch = normalize_switch(switch)
|
64
|
+
next unless option = switch_option(switch)
|
65
|
+
|
66
|
+
@assigns[option.human_name] = parse_peek(switch, option)
|
67
|
+
else
|
68
|
+
shift
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
check_requirement!
|
73
|
+
@assigns
|
74
|
+
end
|
75
|
+
|
76
|
+
protected
|
77
|
+
|
78
|
+
# Returns true if the current value in peek is a registered switch.
|
79
|
+
#
|
80
|
+
def current_is_switch?
|
81
|
+
case peek
|
82
|
+
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
|
83
|
+
switch?($1)
|
84
|
+
when SHORT_SQ_RE
|
85
|
+
$1.split('').any? { |f| switch?("-#{f}") }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def switch?(arg)
|
90
|
+
switch_option(arg) || @shorts.key?(arg)
|
91
|
+
end
|
92
|
+
|
93
|
+
def switch_option(arg)
|
94
|
+
if match = no_or_skip?(arg)
|
95
|
+
@switches[arg] || @switches["--#{match}"]
|
96
|
+
else
|
97
|
+
@switches[arg]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def no_or_skip?(arg)
|
102
|
+
arg =~ /^--(no|skip)-([-\w]+)$/
|
103
|
+
$2
|
104
|
+
end
|
105
|
+
|
106
|
+
# Check if the given argument is actually a shortcut.
|
107
|
+
#
|
108
|
+
def normalize_switch(arg)
|
109
|
+
@shorts.key?(arg) ? @shorts[arg] : arg
|
110
|
+
end
|
111
|
+
|
112
|
+
# Parse boolean values which can be given as --foo=true, --foo or --no-foo.
|
113
|
+
#
|
114
|
+
def parse_boolean(switch)
|
115
|
+
if current_is_value?
|
116
|
+
["true", "TRUE", "t", "T", true].include?(shift)
|
117
|
+
else
|
118
|
+
@switches.key?(switch) || !no_or_skip?(switch)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Parse the value at the peek analyzing if it requires an input or not.
|
123
|
+
#
|
124
|
+
def parse_peek(switch, option)
|
125
|
+
unless current_is_value?
|
126
|
+
if option.boolean?
|
127
|
+
# No problem for boolean types
|
128
|
+
elsif no_or_skip?(switch)
|
129
|
+
return nil # User set value to nil
|
130
|
+
elsif option.string? && !option.required?
|
131
|
+
return option.human_name # Return the option name
|
132
|
+
else
|
133
|
+
raise MalformattedArgumentError, "no value provided for option '#{switch}'"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
@non_assigned_required.delete(option)
|
138
|
+
send(:"parse_#{option.type}", switch)
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
class Thor
|
4
|
+
# Adds a compatibility layer to your Thor classes which allows you to use
|
5
|
+
# rake package tasks. For example, to use rspec rake tasks, one can do:
|
6
|
+
#
|
7
|
+
# require 'thor/rake_compat'
|
8
|
+
#
|
9
|
+
# class Default < Thor
|
10
|
+
# include Thor::RakeCompat
|
11
|
+
#
|
12
|
+
# Spec::Rake::SpecTask.new(:spec) do |t|
|
13
|
+
# t.spec_opts = ['--options', "spec/spec.opts"]
|
14
|
+
# t.spec_files = FileList['spec/**/*_spec.rb']
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
module RakeCompat
|
19
|
+
def self.rake_classes
|
20
|
+
@rake_classes ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.included(base)
|
24
|
+
# Hack. Make rakefile point to invoker, so rdoc task is generated properly.
|
25
|
+
Rake.application.instance_variable_set(:@rakefile, caller[0].match(/(.*):\d+/)[1])
|
26
|
+
self.rake_classes << base
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Object #:nodoc:
|
32
|
+
alias :rake_task :task
|
33
|
+
alias :rake_namespace :namespace
|
34
|
+
|
35
|
+
def task(*args, &block)
|
36
|
+
task = rake_task(*args, &block)
|
37
|
+
|
38
|
+
if klass = Thor::RakeCompat.rake_classes.last
|
39
|
+
non_namespaced_name = task.name.split(':').last
|
40
|
+
|
41
|
+
description = non_namespaced_name
|
42
|
+
description << task.arg_names.map{ |n| n.to_s.upcase }.join(' ')
|
43
|
+
description.strip!
|
44
|
+
|
45
|
+
klass.desc description, task.comment || non_namespaced_name
|
46
|
+
klass.class_eval <<-METHOD
|
47
|
+
def #{non_namespaced_name}(#{task.arg_names.join(', ')})
|
48
|
+
Rake::Task[#{task.name.to_sym.inspect}].invoke(#{task.arg_names.join(', ')})
|
49
|
+
end
|
50
|
+
METHOD
|
51
|
+
end
|
52
|
+
|
53
|
+
task
|
54
|
+
end
|
55
|
+
|
56
|
+
def namespace(name, &block)
|
57
|
+
if klass = Thor::RakeCompat.rake_classes.last
|
58
|
+
const_name = Thor::Util.camel_case(name.to_s).to_sym
|
59
|
+
klass.const_set(const_name, Class.new(Thor))
|
60
|
+
new_klass = klass.const_get(const_name)
|
61
|
+
Thor::RakeCompat.rake_classes << new_klass
|
62
|
+
end
|
63
|
+
|
64
|
+
rake_namespace(name, &block)
|
65
|
+
Thor::RakeCompat.rake_classes.pop
|
66
|
+
end
|
67
|
+
end
|
data/lib/thor/runner.rb
CHANGED
@@ -1,305 +1,295 @@
|
|
1
|
-
require '
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require "digest/md5"
|
7
|
-
require "readline"
|
8
|
-
|
9
|
-
class Thor::Runner < Thor
|
10
|
-
|
11
|
-
def self.globs_for(path)
|
12
|
-
["#{path}/Thorfile", "#{path}/*.thor", "#{path}/tasks/*.thor", "#{path}/lib/tasks/*.thor"]
|
13
|
-
end
|
1
|
+
require 'fileutils'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'yaml'
|
4
|
+
require 'digest/md5'
|
5
|
+
require 'pathname'
|
14
6
|
|
7
|
+
class Thor::Runner < Thor #:nodoc:
|
15
8
|
map "-T" => :list, "-i" => :install, "-u" => :update
|
16
|
-
|
17
|
-
|
18
|
-
|
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_and_task(meth)
|
16
|
+
# Send mapping -h because it works with Thor::Group too
|
17
|
+
klass.start(["-h", task].compact, :shell => self.shell)
|
18
|
+
else
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# If a task is not found on Thor::Runner, method missing is invoked and
|
24
|
+
# Thor::Runner is then responsable for finding the task in all classes.
|
25
|
+
#
|
26
|
+
def method_missing(meth, *args)
|
27
|
+
meth = meth.to_s
|
28
|
+
initialize_thorfiles(meth)
|
29
|
+
klass, task = Thor::Util.namespace_to_thor_class_and_task(meth)
|
30
|
+
args.unshift(task) if task
|
31
|
+
klass.start(args, :shell => shell)
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "install NAME", "Install an optionally named Thor file into your system tasks"
|
35
|
+
method_options :as => :string, :relative => :boolean
|
19
36
|
def install(name)
|
20
37
|
initialize_thorfiles
|
21
38
|
|
22
|
-
|
23
|
-
|
24
|
-
|
39
|
+
# If a directory name is provided as the argument, look for a 'main.thor'
|
40
|
+
# task in said directory.
|
25
41
|
begin
|
26
42
|
if File.directory?(File.expand_path(name))
|
27
43
|
base, package = File.join(name, "main.thor"), :directory
|
28
|
-
contents
|
44
|
+
contents = open(base).read
|
29
45
|
else
|
30
|
-
|
46
|
+
base, package = name, :file
|
47
|
+
contents = open(name).read
|
31
48
|
end
|
32
49
|
rescue OpenURI::HTTPError
|
33
|
-
raise Error, "Error opening URI
|
50
|
+
raise Error, "Error opening URI '#{name}'"
|
34
51
|
rescue Errno::ENOENT
|
35
|
-
raise Error, "Error opening file
|
52
|
+
raise Error, "Error opening file '#{name}'"
|
36
53
|
end
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
response = Readline.readline
|
44
|
-
|
45
|
-
return false unless response =~ /^\s*y/i
|
46
|
-
|
47
|
-
constants = Thor::Util.constants_in_contents(contents, base)
|
48
|
-
|
54
|
+
|
55
|
+
say "Your Thorfile contains:"
|
56
|
+
say contents
|
57
|
+
|
58
|
+
return false if no?("Do you wish to continue [y/N]?")
|
59
|
+
|
49
60
|
as = options["as"] || begin
|
50
61
|
first_line = contents.split("\n")[0]
|
51
62
|
(match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
|
52
63
|
end
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
as =
|
57
|
-
as =
|
64
|
+
|
65
|
+
unless as
|
66
|
+
basename = File.basename(name)
|
67
|
+
as = ask("Please specify a name for #{name} in the system repository [#{basename}]:")
|
68
|
+
as = basename if as.empty?
|
69
|
+
end
|
70
|
+
|
71
|
+
location = if options[:relative] || name =~ /^http:\/\//
|
72
|
+
name
|
73
|
+
else
|
74
|
+
File.expand_path(name)
|
58
75
|
end
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
puts "Storing thor file in your system repository"
|
72
|
-
|
73
|
-
destination = File.join(thor_root, yaml[as][:filename])
|
74
|
-
|
76
|
+
|
77
|
+
thor_yaml[as] = {
|
78
|
+
:filename => Digest::MD5.hexdigest(name + as),
|
79
|
+
:location => location,
|
80
|
+
:namespaces => Thor::Util.namespaces_in_content(contents, base)
|
81
|
+
}
|
82
|
+
|
83
|
+
save_yaml(thor_yaml)
|
84
|
+
say "Storing thor file in your system repository"
|
85
|
+
destination = File.join(thor_root, thor_yaml[as][:filename])
|
86
|
+
|
75
87
|
if package == :file
|
76
|
-
File.open(destination, "w") {|f| f.puts contents }
|
88
|
+
File.open(destination, "w") { |f| f.puts contents }
|
77
89
|
else
|
78
90
|
FileUtils.cp_r(name, destination)
|
79
91
|
end
|
80
|
-
|
81
|
-
|
92
|
+
|
93
|
+
thor_yaml[as][:filename] # Indicate success
|
82
94
|
end
|
83
|
-
|
84
|
-
desc "uninstall NAME", "
|
95
|
+
|
96
|
+
desc "uninstall NAME", "Uninstall a named Thor module"
|
85
97
|
def uninstall(name)
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
yaml.delete(name)
|
94
|
-
save_yaml(yaml)
|
95
|
-
|
98
|
+
raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
|
99
|
+
say "Uninstalling #{name}."
|
100
|
+
FileUtils.rm_rf(File.join(thor_root, "#{thor_yaml[name][:filename]}"))
|
101
|
+
|
102
|
+
thor_yaml.delete(name)
|
103
|
+
save_yaml(thor_yaml)
|
104
|
+
|
96
105
|
puts "Done."
|
97
106
|
end
|
98
|
-
|
99
|
-
desc "update NAME", "
|
107
|
+
|
108
|
+
desc "update NAME", "Update a Thor file from its original location"
|
100
109
|
def update(name)
|
101
|
-
|
102
|
-
raise Error, "Can't find module `#{name}'" if !yaml[name] || !yaml[name][:location]
|
110
|
+
raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location]
|
103
111
|
|
104
|
-
|
105
|
-
|
112
|
+
say "Updating '#{name}' from #{thor_yaml[name][:location]}"
|
113
|
+
|
114
|
+
old_filename = thor_yaml[name][:filename]
|
106
115
|
self.options = self.options.merge("as" => name)
|
107
|
-
filename
|
116
|
+
filename = install(thor_yaml[name][:location])
|
117
|
+
|
108
118
|
unless filename == old_filename
|
109
119
|
File.delete(File.join(thor_root, old_filename))
|
110
120
|
end
|
111
121
|
end
|
112
|
-
|
113
|
-
desc "installed", "
|
122
|
+
|
123
|
+
desc "installed", "List the installed Thor modules and tasks"
|
114
124
|
method_options :internal => :boolean
|
115
125
|
def installed
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
126
|
+
initialize_thorfiles(nil, true)
|
127
|
+
|
128
|
+
klasses = Thor::Base.subclasses
|
129
|
+
klasses -= [Thor, Thor::Runner] unless options["internal"]
|
120
130
|
|
121
|
-
klasses = Thor.subclasses
|
122
|
-
klasses -= [Thor, Thor::Runner] unless options['internal']
|
123
131
|
display_klasses(true, klasses)
|
124
132
|
end
|
125
|
-
|
126
|
-
desc "list [SEARCH]", "
|
127
|
-
method_options :substring => :boolean,
|
128
|
-
|
129
|
-
:all => :boolean
|
130
|
-
def list(search = "")
|
133
|
+
|
134
|
+
desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
|
135
|
+
method_options :substring => :boolean, :group => :string, :all => :boolean
|
136
|
+
def list(search="")
|
131
137
|
initialize_thorfiles
|
138
|
+
|
132
139
|
search = ".*#{search}" if options["substring"]
|
133
140
|
search = /^#{search}.*/i
|
134
|
-
group = options[:group] ||
|
141
|
+
group = options[:group] || "standard"
|
135
142
|
|
136
|
-
|
137
|
-
(options[:all] || k.
|
138
|
-
Thor::Util.constant_to_thor_path(k.name) =~ search
|
143
|
+
klasses = Thor::Base.subclasses.select do |k|
|
144
|
+
(options[:all] || k.group == group) && k.namespace =~ search
|
139
145
|
end
|
140
|
-
display_klasses(false, classes)
|
141
|
-
end
|
142
146
|
|
143
|
-
|
144
|
-
def help(task = nil)
|
145
|
-
initialize_thorfiles(task) if task && task.include?(?:)
|
146
|
-
super
|
147
|
+
display_klasses(false, klasses)
|
147
148
|
end
|
148
|
-
|
149
|
-
def method_missing(meth, *args)
|
150
|
-
meth = meth.to_s
|
151
|
-
super(meth.to_sym, *args) unless meth.include? ?:
|
152
149
|
|
153
|
-
|
154
|
-
task = Thor[meth]
|
155
|
-
task.parse task.klass.new, ARGV[1..-1]
|
156
|
-
end
|
157
|
-
|
158
|
-
def self.thor_root
|
159
|
-
File.join(ENV["HOME"] || ENV["APPDATA"], ".thor")
|
160
|
-
end
|
150
|
+
private
|
161
151
|
|
162
|
-
|
163
|
-
|
164
|
-
#
|
165
|
-
# C:\Documents and Settings\james\.thor
|
166
|
-
#
|
167
|
-
# If we don't #gsub the \ character, Dir.glob will fail.
|
168
|
-
files = Dir["#{thor_root.gsub(/\\/, '/')}/*"]
|
169
|
-
files.map! do |file|
|
170
|
-
File.directory?(file) ? File.join(file, "main.thor") : file
|
152
|
+
def thor_root
|
153
|
+
Thor::Util.thor_root
|
171
154
|
end
|
172
|
-
end
|
173
|
-
|
174
|
-
private
|
175
|
-
def thor_root
|
176
|
-
self.class.thor_root
|
177
|
-
end
|
178
155
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
yaml_file = File.join(thor_root, "thor.yml")
|
185
|
-
yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
|
186
|
-
yaml || {}
|
187
|
-
end
|
188
|
-
|
189
|
-
def save_yaml(yaml)
|
190
|
-
yaml_file = File.join(thor_root, "thor.yml")
|
191
|
-
File.open(yaml_file, "w") {|f| f.puts yaml.to_yaml }
|
192
|
-
end
|
193
|
-
|
194
|
-
def display_klasses(with_modules = false, klasses = Thor.subclasses)
|
195
|
-
klasses -= [Thor, Thor::Runner] unless with_modules
|
196
|
-
raise Error, "No Thor tasks available" if klasses.empty?
|
197
|
-
|
198
|
-
if with_modules && !(yaml = thor_yaml).empty?
|
199
|
-
max_name = yaml.max {|(xk,xv),(yk,yv)| xk.to_s.size <=> yk.to_s.size }.first.size
|
200
|
-
modules_label = "Modules"
|
201
|
-
namespaces_label = "Namespaces"
|
202
|
-
column_width = [max_name + 4, modules_label.size + 1].max
|
203
|
-
|
204
|
-
print "%-#{column_width}s" % modules_label
|
205
|
-
puts namespaces_label
|
206
|
-
print "%-#{column_width}s" % ("-" * modules_label.size)
|
207
|
-
puts "-" * namespaces_label.size
|
208
|
-
|
209
|
-
yaml.each do |name, info|
|
210
|
-
print "%-#{column_width}s" % name
|
211
|
-
puts info[:constants].map {|c| Thor::Util.constant_to_thor_path(c)}.join(", ")
|
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 || {}
|
212
161
|
end
|
213
|
-
|
214
|
-
puts
|
215
162
|
end
|
216
|
-
|
217
|
-
#
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
unless klasses.empty?
|
230
|
-
puts # add some spacing
|
231
|
-
klasses.each { |k| display_tasks(k, max_base, max_left); }
|
232
|
-
else
|
233
|
-
puts "\033[1;34mNo Thor tasks available\033[0m"
|
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 }
|
234
176
|
end
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
puts "\033[1;34m#{base}\033[0m"
|
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))
|
247
188
|
end
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
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
|
254
224
|
end
|
255
|
-
|
256
|
-
|
257
|
-
|
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
|
258
232
|
end
|
259
|
-
|
260
|
-
puts # add some spacing
|
261
233
|
end
|
262
|
-
end
|
263
234
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
def thorfiles(relevant_to = nil)
|
278
|
-
path = Dir.pwd
|
279
|
-
thorfiles = []
|
280
|
-
|
281
|
-
# Look for Thorfile or *.thor in the current directory or a parent directory, until the root
|
282
|
-
while thorfiles.empty?
|
283
|
-
thorfiles = Thor::Runner.globs_for(path).map {|g| Dir[g]}.flatten
|
284
|
-
path = File.dirname(path)
|
285
|
-
break if path == "/"
|
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]}") }
|
286
247
|
end
|
287
248
|
|
288
|
-
#
|
289
|
-
#
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
249
|
+
# Display information about the given klasses. If with_module is given,
|
250
|
+
# it shows a table with information extracted from the yaml file.
|
251
|
+
#
|
252
|
+
def display_klasses(with_modules=false, klasses=Thor.subclasses)
|
253
|
+
klasses -= [Thor, Thor::Runner] unless with_modules
|
254
|
+
raise Error, "No Thor tasks available" if klasses.empty?
|
255
|
+
|
256
|
+
if with_modules && !thor_yaml.empty?
|
257
|
+
info = []
|
258
|
+
labels = ["Modules", "Namespaces"]
|
259
|
+
|
260
|
+
info << labels
|
261
|
+
info << [ "-" * labels[0].size, "-" * labels[1].size ]
|
262
|
+
|
263
|
+
thor_yaml.each do |name, hash|
|
264
|
+
info << [ name, hash[:namespaces].join(", ") ]
|
265
|
+
end
|
266
|
+
|
267
|
+
print_table info
|
268
|
+
say ""
|
269
|
+
end
|
270
|
+
|
271
|
+
unless klasses.empty?
|
272
|
+
klasses.dup.each do |klass|
|
273
|
+
klasses -= Thor::Util.thor_classes_in(klass)
|
274
|
+
end
|
275
|
+
|
276
|
+
klasses.each { |k| display_tasks(k) }
|
277
|
+
else
|
278
|
+
say "\033[1;34mNo Thor tasks available\033[0m"
|
279
|
+
end
|
295
280
|
end
|
296
|
-
end
|
297
281
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
end
|
282
|
+
# Display tasks from the given Thor class.
|
283
|
+
#
|
284
|
+
def display_tasks(klass)
|
285
|
+
unless klass.tasks.empty?
|
286
|
+
base = klass.namespace
|
304
287
|
|
288
|
+
color = base == "default" ? :magenta : :blue
|
289
|
+
say shell.set_color(base, color, true)
|
290
|
+
say "-" * base.length
|
291
|
+
|
292
|
+
klass.help(shell, :short => true, :ident => 0, :namespace => true)
|
293
|
+
end
|
294
|
+
end
|
305
295
|
end
|