thor 0.9.2 → 0.9.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,64 @@
1
+ class Thor
2
+ # This class is based on the Ruby 1.9 ordered hashes.
3
+ # It keeps the semantics and most of the efficiency of normal hashes
4
+ # while also keeping track of the order in which elements were set.
5
+ class OrderedHash
6
+ Node = Struct.new(:key, :value, :next, :prev)
7
+ include Enumerable
8
+
9
+ def initialize
10
+ @hash = {}
11
+ end
12
+
13
+ def initialize_copy(other)
14
+ @hash = other.instance_variable_get('@hash').clone
15
+ end
16
+
17
+ def [](key)
18
+ @hash[key] && @hash[key].value
19
+ end
20
+
21
+ def []=(key, value)
22
+ node = Node.new(key, value)
23
+
24
+ if old = @hash[key]
25
+ if old.prev
26
+ old.prev.next = old.next
27
+ else # old is @first and @last
28
+ @first = @last = nil
29
+ end
30
+ end
31
+
32
+ if @first.nil?
33
+ @first = @last = node
34
+ else
35
+ node.prev = @last
36
+ @last.next = node
37
+ @last = node
38
+ end
39
+
40
+ @hash[key] = node
41
+ value
42
+ end
43
+
44
+ def each
45
+ return unless @first
46
+ yield [@first.key, @first.value]
47
+ node = @first
48
+ yield [node.key, node.value] while node = node.next
49
+ self
50
+ end
51
+
52
+ def values
53
+ self.map { |k, v| v }
54
+ end
55
+
56
+ def +(other)
57
+ new = clone
58
+ other.each do |key, value|
59
+ new[key] = value unless self[key]
60
+ end
61
+ new
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,260 @@
1
+ require 'thor'
2
+ require "thor/util"
3
+ require "open-uri"
4
+ require "fileutils"
5
+ require "yaml"
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
14
+
15
+ map "-T" => :list, "-i" => :install, "-u" => :update
16
+
17
+ desc "install NAME", "install a Thor file into your system tasks, optionally named for future updates"
18
+ method_options :as => :optional, :relative => :boolean
19
+ def install(name)
20
+ initialize_thorfiles
21
+ begin
22
+ contents = open(name).read
23
+ rescue OpenURI::HTTPError
24
+ raise Error, "Error opening URI `#{name}'"
25
+ rescue Errno::ENOENT
26
+ raise Error, "Error opening file `#{name}'"
27
+ end
28
+
29
+ is_uri = File.exist?(name) ? false : true
30
+
31
+ puts "Your Thorfile contains: "
32
+ puts contents
33
+ print "Do you wish to continue [y/N]? "
34
+ response = Readline.readline
35
+
36
+ return false unless response =~ /^\s*y/i
37
+
38
+ constants = Thor::Util.constants_in_contents(contents)
39
+
40
+ # name = name =~ /\.thor$/ || is_uri ? name : "#{name}.thor"
41
+
42
+ as = options["as"] || begin
43
+ first_line = contents.split("\n")[0]
44
+ (match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
45
+ end
46
+
47
+ if !as
48
+ print "Please specify a name for #{name} in the system repository [#{name}]: "
49
+ as = Readline.readline
50
+ as = name if as.empty?
51
+ end
52
+
53
+ FileUtils.mkdir_p thor_root
54
+
55
+ yaml_file = File.join(thor_root, "thor.yml")
56
+ FileUtils.touch(yaml_file)
57
+ yaml = thor_yaml
58
+
59
+ location = (options[:relative] || is_uri) ? name : File.expand_path(name)
60
+ yaml[as] = {:filename => Digest::MD5.hexdigest(name + as), :location => location, :constants => constants}
61
+
62
+ save_yaml(yaml)
63
+
64
+ puts "Storing thor file in your system repository"
65
+
66
+ File.open(File.join(thor_root, yaml[as][:filename]), "w") do |file|
67
+ file.puts contents
68
+ end
69
+
70
+ yaml[as][:filename] # Indicate sucess
71
+ end
72
+
73
+ desc "uninstall NAME", "uninstall a named Thor module"
74
+ def uninstall(name)
75
+ yaml = thor_yaml
76
+ raise Error, "Can't find module `#{name}'" unless yaml[name]
77
+
78
+ puts "Uninstalling #{name}."
79
+
80
+ file = File.join(thor_root, "#{yaml[name][:filename]}")
81
+ File.delete(file)
82
+ yaml.delete(name)
83
+ save_yaml(yaml)
84
+
85
+ puts "Done."
86
+ end
87
+
88
+ desc "update NAME", "update a Thor file from its original location"
89
+ def update(name)
90
+ yaml = thor_yaml
91
+ raise Error, "Can't find module `#{name}'" if !yaml[name] || !yaml[name][:location]
92
+
93
+ puts "Updating `#{name}' from #{yaml[name][:location]}"
94
+ old_filename = yaml[name][:filename]
95
+ options["as"] = name
96
+ filename = install(yaml[name][:location])
97
+ unless filename == old_filename
98
+ File.delete(File.join(thor_root, old_filename))
99
+ end
100
+ end
101
+
102
+ desc "installed", "list the installed Thor modules and tasks (--internal means list the built-in tasks as well)"
103
+ method_options :internal => :boolean
104
+ def installed
105
+ thor_root_glob.each do |f|
106
+ next if f =~ /thor\.yml$/
107
+ load_thorfile f unless Thor.subclass_files.keys.include?(File.expand_path(f))
108
+ end
109
+
110
+ klasses = Thor.subclasses
111
+ klasses -= [Thor, Thor::Runner] unless options['internal']
112
+ display_klasses(true, klasses)
113
+ end
114
+
115
+ desc "list [SEARCH]", "list the available thor tasks (--substring means SEARCH can be anywhere in the module)"
116
+ method_options :substring => :boolean
117
+ def list(search = "")
118
+ initialize_thorfiles
119
+ search = ".*#{search}" if options["substring"]
120
+ search = /^#{search}.*/i
121
+
122
+ display_klasses(false, Thor.subclasses.select {|k|
123
+ Thor::Util.constant_to_thor_path(k.name) =~ search})
124
+ end
125
+
126
+ # Override Thor#help so we can give info about not-yet-loaded tasks
127
+ def help(task = nil)
128
+ initialize_thorfiles(task) if task && task.include?(?:)
129
+ super
130
+ end
131
+
132
+ def method_missing(meth, *args)
133
+ meth = meth.to_s
134
+ super(meth.to_sym, *args) unless meth.include? ?:
135
+
136
+ initialize_thorfiles(meth)
137
+ task = Thor[meth]
138
+ task.parse task.klass.new, ARGV[1..-1]
139
+ end
140
+
141
+ def self.thor_root
142
+ File.join(ENV["HOME"] || ENV["APPDATA"], ".thor")
143
+ end
144
+
145
+ def self.thor_root_glob
146
+ # On Windows thor_root will be something like this:
147
+ #
148
+ # C:\Documents and Settings\james\.thor
149
+ #
150
+ # If we don't #gsub the \ character, Dir.glob will fail.
151
+ Dir["#{thor_root.gsub(/\\/, '/')}/**/*"]
152
+ end
153
+
154
+ private
155
+ def thor_root
156
+ self.class.thor_root
157
+ end
158
+
159
+ def thor_root_glob
160
+ self.class.thor_root_glob
161
+ end
162
+
163
+ def thor_yaml
164
+ yaml_file = File.join(thor_root, "thor.yml")
165
+ yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
166
+ yaml || {}
167
+ end
168
+
169
+ def save_yaml(yaml)
170
+ yaml_file = File.join(thor_root, "thor.yml")
171
+ File.open(yaml_file, "w") {|f| f.puts yaml.to_yaml }
172
+ end
173
+
174
+ def display_klasses(with_modules = false, klasses = Thor.subclasses)
175
+ klasses -= [Thor, Thor::Runner] unless with_modules
176
+ raise Error, "No Thor tasks available" if klasses.empty?
177
+
178
+ if with_modules && !(yaml = thor_yaml).empty?
179
+ max_name = yaml.max {|(xk,xv),(yk,yv)| xk.size <=> yk.size }.first.size
180
+ modules_label = "Modules"
181
+ namespaces_label = "Namespaces"
182
+ column_width = [max_name + 4, modules_label.size + 1].max
183
+
184
+ print "%-#{column_width}s" % modules_label
185
+ puts namespaces_label
186
+ print "%-#{column_width}s" % ("-" * modules_label.size)
187
+ puts "-" * namespaces_label.size
188
+
189
+ yaml.each do |name, info|
190
+ print "%-#{column_width}s" % name
191
+ puts info[:constants].map {|c| Thor::Util.constant_to_thor_path(c)}.join(", ")
192
+ end
193
+
194
+ puts
195
+ end
196
+
197
+ puts "Tasks"
198
+ puts "-----"
199
+
200
+ # Calculate the largest base class name
201
+ max_base = klasses.max do |x,y|
202
+ Thor::Util.constant_to_thor_path(x.name).size <=> Thor::Util.constant_to_thor_path(y.name).size
203
+ end.name.size
204
+
205
+ # Calculate the size of the largest option description
206
+ max_left_item = klasses.max do |x,y|
207
+ (x.maxima.usage + x.maxima.opt).to_i <=> (y.maxima.usage + y.maxima.opt).to_i
208
+ end
209
+
210
+ max_left = max_left_item.maxima.usage + max_left_item.maxima.opt
211
+
212
+ klasses.each {|k| display_tasks(k, max_base, max_left)}
213
+ end
214
+
215
+ def display_tasks(klass, max_base, max_left)
216
+ base = Thor::Util.constant_to_thor_path(klass.name)
217
+ klass.tasks.each true do |name, task|
218
+ format_string = "%-#{max_left + max_base + 5}s"
219
+ print format_string % task.formatted_usage(true)
220
+ puts task.description
221
+ end
222
+ end
223
+
224
+ def initialize_thorfiles(relevant_to = nil)
225
+ thorfiles(relevant_to).each {|f| load_thorfile f unless Thor.subclass_files.keys.include?(File.expand_path(f))}
226
+ end
227
+
228
+ def load_thorfile(path)
229
+ begin
230
+ load path
231
+ rescue Object => e
232
+ $stderr.puts "WARNING: unable to load thorfile #{path.inspect}: #{e.message}"
233
+ end
234
+ end
235
+
236
+ def thorfiles(relevant_to = nil)
237
+ path = Dir.pwd
238
+ thorfiles = []
239
+
240
+ # Look for Thorfile or *.thor in the current directory or a parent directory, until the root
241
+ while thorfiles.empty?
242
+ thorfiles = Thor::Runner.globs_for(path).map {|g| Dir[g]}.flatten
243
+ path = File.dirname(path)
244
+ break if path == "/"
245
+ end
246
+
247
+ # We want to load system-wide Thorfiles first
248
+ # so the local Thorfiles will override them.
249
+ (relevant_to ? thorfiles_relevant_to(relevant_to) :
250
+ thor_root_glob) + thorfiles - ["#{thor_root}/thor.yml"]
251
+ end
252
+
253
+ def thorfiles_relevant_to(meth)
254
+ klass_str = Thor::Util.to_constant(meth.split(":")[0...-1].join(":"))
255
+ thor_yaml.select do |k, v|
256
+ v[:constants] && v[:constants].include?(klass_str)
257
+ end.map { |k, v| File.join(thor_root, "#{v[:filename]}") }
258
+ end
259
+
260
+ end
@@ -0,0 +1,68 @@
1
+ require 'thor/error'
2
+ require 'thor/util'
3
+
4
+ class Thor
5
+ class Task < Struct.new(:meth, :description, :usage, :opts, :klass)
6
+ def self.dynamic(meth, klass)
7
+ new(meth, "A dynamically-generated task", meth.to_s, nil, klass)
8
+ end
9
+
10
+ def parse(obj, args)
11
+ list, hash = parse_args(args)
12
+ obj.options = hash
13
+ run(obj, *list)
14
+ end
15
+
16
+ def run(obj, *params)
17
+ raise NoMethodError, "the `#{meth}' task of #{obj.class} is private" if
18
+ (obj.private_methods + obj.protected_methods).include?(meth)
19
+
20
+ obj.send(meth, *params)
21
+ rescue ArgumentError => e
22
+ # backtrace sans anything in this file
23
+ backtrace = e.backtrace.reject {|frame| frame =~ /^#{Regexp.escape(__FILE__)}/}
24
+ # and sans anything that got us here
25
+ backtrace -= caller
26
+ raise e unless backtrace.empty?
27
+
28
+ # okay, they really did call it wrong
29
+ raise Error, "`#{meth}' was called incorrectly. Call as `#{formatted_usage}'"
30
+ rescue NoMethodError => e
31
+ begin
32
+ raise e unless e.message =~ /^undefined method `#{meth}' for #{Regexp.escape(obj.inspect)}$/
33
+ rescue
34
+ raise e
35
+ end
36
+ raise Error, "The #{namespace false} namespace doesn't have a `#{meth}' task"
37
+ end
38
+
39
+ def namespace(remove_default = true)
40
+ Thor::Util.constant_to_thor_path(klass, remove_default)
41
+ end
42
+
43
+ def with_klass(klass)
44
+ new = self.dup
45
+ new.klass = klass
46
+ new
47
+ end
48
+
49
+ def opts
50
+ return super unless super.kind_of? Hash
51
+ self.opts = Options.new(super)
52
+ end
53
+
54
+ def formatted_usage(namespace = false)
55
+ (namespace ? self.namespace + ':' : '') + usage +
56
+ (opts ? " " + opts.formatted_usage : "")
57
+ end
58
+
59
+ protected
60
+
61
+ def parse_args(args)
62
+ return [args, {}] unless opts
63
+ hash = opts.parse(args)
64
+ list = opts.non_opts
65
+ [list, hash]
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,22 @@
1
+ require 'thor/ordered_hash'
2
+ require 'thor/task'
3
+
4
+ class Thor::TaskHash < Thor::OrderedHash
5
+ def initialize(klass)
6
+ super()
7
+ @klass = klass
8
+ end
9
+
10
+ def each(local = false, &block)
11
+ super() { |k, t| yield k, t.with_klass(@klass) }
12
+ @klass.superclass.tasks.each { |k, t| yield k, t.with_klass(@klass) } unless local || @klass == Thor
13
+ end
14
+
15
+ def [](name)
16
+ if task = super(name) || (@klass == Thor && @klass.superclass.tasks[name])
17
+ return task.with_klass(@klass)
18
+ end
19
+
20
+ Thor::Task.dynamic(name, @klass)
21
+ end
22
+ end
@@ -2,29 +2,28 @@ require "thor"
2
2
  require "fileutils"
3
3
 
4
4
  class Thor
5
- def self.package_task
6
- self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
7
- desc "package", "package up the gem"
8
- def package
9
- FileUtils.mkdir_p(File.join(Dir.pwd, "pkg"))
10
- Gem::Builder.new(SPEC).build
11
- FileUtils.mv(SPEC.file_name, File.join(Dir.pwd, "pkg", SPEC.file_name))
12
- end
13
- RUBY
5
+ def self.package_task(spec)
6
+ desc "package", "package up the gem"
7
+ define_method :package do
8
+ FileUtils.mkdir_p(File.join(Dir.pwd, "pkg"))
9
+ Gem::Builder.new(spec).build
10
+ FileUtils.mv(spec.file_name, File.join(Dir.pwd, "pkg", spec.file_name))
11
+ end
14
12
  end
15
13
 
16
- def self.install_task
17
- package_task
14
+ def self.install_task(spec)
15
+ package_task spec
16
+
17
+ null, sudo, gem = RUBY_PLATFORM =~ /w(in)?32$/ ? ['NUL', '', 'gem.bat'] :
18
+ ['/dev/null', 'sudo', 'gem']
18
19
 
19
- self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
20
- desc "install", "install the gem"
21
- def install
22
- old_stderr, $stderr = $stderr.dup, File.open("/dev/null", "w")
23
- package
24
- $stderr = old_stderr
25
- system %{sudo gem install pkg/#{GEM}-#{GEM_VERSION} --no-rdoc --no-ri --no-update-sources}
26
- end
27
- RUBY
20
+ desc "install", "install the gem"
21
+ define_method :install do
22
+ old_stderr, $stderr = $stderr.dup, File.open(null, "w")
23
+ package
24
+ $stderr = old_stderr
25
+ system %{#{sudo} #{gem} install pkg/#{spec.name}-#{spec.version} --no-rdoc --no-ri --no-update-sources}
26
+ end
28
27
  end
29
28
 
30
29
  def self.spec_task(file_list, opts = {})
@@ -42,32 +41,34 @@ class Thor
42
41
  FileUtils.rm_rf(File.join(Dir.pwd, rcov_dir))
43
42
  end
44
43
 
45
- self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
46
- desc("#{name}", "spec task")
47
- def #{name}
48
- cmd = "ruby "
49
- if #{rcov.inspect}
50
- cmd << "-S rcov -o #{rcov_dir} #{rcov_opts.inspect[1...-1]} "
51
- end
52
- cmd << `which spec`.chomp
53
- cmd << " -- " if #{rcov.inspect}
54
- cmd << " "
55
- cmd << #{file_list.inspect}
56
- cmd << " "
57
- cmd << #{options.inspect}
58
- puts cmd if #{verbose.inspect}
59
- system(cmd)
44
+ desc(name, "spec task")
45
+ define_method(name) do
46
+ cmd = "ruby "
47
+ if rcov
48
+ cmd << "-S rcov -o #{rcov_dir} #{rcov_opts} "
60
49
  end
61
- RUBY
50
+ cmd << `which spec`.chomp
51
+ cmd << " -- " if rcov
52
+ cmd << " "
53
+ cmd << file_list
54
+ cmd << " "
55
+ cmd << options
56
+ puts cmd if verbose
57
+ system(cmd)
58
+ exit($?.exitstatus)
59
+ end
62
60
  end
63
61
 
64
62
  private
65
63
  def self.convert_task_options(opts)
66
64
  opts.map do |key, value|
67
- if value == true
65
+ case value
66
+ when true
68
67
  "--#{key}"
69
- elsif value.is_a?(Array)
68
+ when Array
70
69
  value.map {|v| "--#{key} #{v.inspect}"}.join(" ")
70
+ when nil, false
71
+ ""
71
72
  else
72
73
  "--#{key} #{value.inspect}"
73
74
  end