Make Sanitizer::safe_xhtml_sanitize use Nokogiri

Also, update Bundler to 1.0.18.
This commit is contained in:
Jacques Distler 2011-08-19 19:32:53 -05:00
parent 4b2448b09a
commit 1e352e28a1
163 changed files with 1216 additions and 255 deletions

View file

@ -72,8 +72,6 @@ module Engines
my_content.to_s5
else
(t = Time.now; nil)
puts "text is #{text.class}"
puts "text responds to concat_with_safety" if text.respond_to?(:concat_with_safety)
html = Maruku.new(text,
{:math_enabled => true,
:math_numbered => ['\\[','\\begin{equation}']}).to_html

View file

@ -9,6 +9,7 @@ module Sanitizer
require 'node'
require 'instiki_stringsupport'
require 'set'
require 'nokogiri'
acceptable_elements = Set.new %w[a abbr acronym address area article aside
audio b big blockquote br button canvas caption center cite code
@ -227,9 +228,9 @@ module Sanitizer
# (REXML trees are always utf-8 encoded.)
def safe_xhtml_sanitize(html, options = {})
sanitized = xhtml_sanitize(html.purify)
doc = REXML::Document.new("<div xmlns='http://www.w3.org/1999/xhtml'>#{sanitized}</div>")
sanitized = doc.to_s.gsub(/\A<div xmlns='http:\/\/www.w3.org\/1999\/xhtml'>(.*)<\/div>\Z/m, '\1')
rescue REXML::ParseException
doc = Nokogiri::XML::Document.parse("<div xmlns='http://www.w3.org/1999/xhtml'>#{sanitized}</div>", nil, (options[:encoding] || 'UTF-8'), 0)
sanitized = doc.root.children.to_xml(:indent => (options[:indent] || 2), :save_with => 2 )
rescue Nokogiri::XML::SyntaxError
sanitized = sanitized.escapeHTML
end

View file

@ -26,19 +26,27 @@ class NoWikiTest < Test::Unit::TestCase
def test_sanitize_nowiki
match(NoWiki, 'This sentence contains <nowiki>[[test]]&<a href="a&b">shebang</a> <script>alert("xss!");</script> *foo*</nowiki>. Do not touch!',
:plain_text => "[[test]]&amp;<a href='a&amp;b'>shebang</a> &lt;script&gt;alert(&quot;xss!&quot;);&lt;/script&gt; *foo*"
:plain_text => "[[test]]&amp;<a href=\"a&amp;b\">shebang</a> &lt;script&gt;alert(\"xss!\");&lt;/script&gt; *foo*"
)
end
# Here, the input is not namespace-well-formed, but the output is.
# I think that's OK.
def test_sanitize_nowiki_ill_formed
match(NoWiki, "<nowiki><animateColor xlink:href='#foo'/></nowiki>",
:plain_text => "&lt;animateColor xlink:href=&#39;#foo&#39;/&gt;"
:plain_text => '<animateColor href="#foo"/>'
)
end
def test_sanitize_nowiki_ill_formed_II
match(NoWiki, "<nowiki><animateColor xlink:href='#foo'/>\000</nowiki>",
:plain_text => %(&lt;animateColor xlink:href=&#39;#foo&#39;/&gt;)
:plain_text => '<animateColor href="#foo"/>'
)
end
def test_sanitize_nowiki_ill_formed_III
match(NoWiki, "<nowiki><animateColor xlink:href='#foo' xmlns:xlink='http://www.w3.org/1999/xlink'/>\000</nowiki>",
:plain_text => '<animateColor xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#foo"/>'
)
end

View file

@ -1,19 +0,0 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-BENCHMARK" "1" "May 2011" "" ""
.
.SH "NAME"
\fBbundle\-benchmark\fR \- Display the time taken for each gem to be loaded
.
.SH "SYNOPSIS"
\fBbundle benchmark\fR [group]
.
.SH "DESCRIPTION"
This command loads all your required dependencies as per Bundler\.setup, and displays the total time spent in requiring each gem and its dependencies\.
.
.P
Use this command to track down problems with excessive boot time in your application, or to optimize specific groups in your Gemfile for fast setup\.
.
.SH "GROUP OPTION"
To test the load times for gems in a specific group, pass the group as an argument to \fBbundle benchmark\fR\. Omitting this option loads all dependencies in your Gemfile\.

View file

@ -1,27 +0,0 @@
BUNDLE-BENCHMARK(1) BUNDLE-BENCHMARK(1)
NAME
bundle-benchmark - Display the time taken for each gem to be loaded
SYNOPSIS
bundle benchmark [group]
DESCRIPTION
This command loads all your required dependencies as per Bundler.setup,
and displays the total time spent in requiring each gem and its depen-
dencies.
Use this command to track down problems with excessive boot time in
your application, or to optimize specific groups in your Gemfile for
fast setup.
GROUP OPTION
To test the load times for gems in a specific group, pass the group as
an argument to bundle benchmark. Omitting this option loads all depen-
dencies in your Gemfile.
May 2011 BUNDLE-BENCHMARK(1)

View file

@ -1 +0,0 @@
require 'bundler/gem_tasks'

View file

@ -1,3 +0,0 @@
class Thor
VERSION = "0.14.0".freeze
end

View file

@ -1,22 +0,0 @@
require "spec_helper"
describe "bundle pack with gems" do
describe "when there are only gemsources" do
before :each do
gemfile <<-G
gem 'rack'
G
system_gems "rack-1.0.0"
bundle :pack
end
it "locks the gemfile" do
bundled_app("Gemfile.lock").should exist
end
it "caches the gems" do
bundled_app("vendor/cache/rack-1.0.0.gem").should exist
end
end
end

View file

@ -1,3 +1,45 @@
## 1.0.18 (Aug 16, 2011)
Bugfixes:
- Fix typo in DEBUG_RESOLVER (@geemus)
- Fixes rake 0.9.x warning (@mtylty, #1333)
Features:
- Run the bundle install earlier in a Capistrano deployment (@cgriego, #1300)
- Support hidden gemspec (@trans, @cldwalker, #827)
- Make fetch_specs faster (@zeha, #1294)
- Allow overriding development deps loaded by #gemspec (@lgierth, #1245)
## 1.0.17 (Aug 8, 2011)
Bugfixes:
- Fix rake issues with rubygems 1.3.x (#1342)
- Fixed invalid byte sequence error while installing gem on Ruby 1.9 (#1341)
## 1.0.16 (Aug 8, 2011)
Features:
- Performance fix for MRI 1.9 (@efficientcloud, #1288)
- Shortcuts (like `bundle i`) for all commands (@amatsuda)
- Correcly identify missing child dependency in error message
Bugfixes:
- Allow Windows network share paths with forward slashes (@mtscout6, #1253)
- Check for rubygems.org credentials so `rake release` doesn't hang (#980)
- Find cached prerelease gems on rubygems 1.3.x (@dburt, #1202)
- Fix `bundle install --without` on kiji (@tmm1, #1287)
- Get rid of warning in ruby 1.9.3 (@smartinez87, #1231)
Documentation:
- Documentation for `gem ..., :require => false` (@kmayer, #1292)
- Gems provide "executables", they are rarely also binaries (@fxn, #1242)
## 1.0.15 (June 9, 2011)
Features:

View file

@ -50,6 +50,7 @@ Instructions that allow the Bundler team to reproduce your issue are vitally imp
- What version of Rubygems you are using (run `gem -v`)
- Whether you are using RVM, and if so what version (run `rvm -v`)
- Whether you have the `rubygems-bundler` gem, which can break gem binares
- Whether you have the `open_gem` gem, which can cause rake activation conflicts
If you are using Rails 2.3, please also include:

View file

@ -40,9 +40,9 @@ begin
end
namespace :rubygems do
# Rubygems 1.3.5, 1.3.6, and HEAD specs
# Rubygems specs by version
rubyopt = ENV["RUBYOPT"]
%w(master v1.3.6 v1.3.7 v1.4.2 v1.5.3 v1.6.2 v1.7.2 v1.8.3).each do |rg|
%w(master v1.3.6 v1.3.7 v1.4.2 v1.5.3 v1.6.2 v1.7.2 v1.8.7).each do |rg|
desc "Run specs with Rubygems #{rg}"
RSpec::Core::RakeTask.new(rg) do |t|
t.rspec_opts = %w(-fs --color)
@ -95,7 +95,7 @@ begin
raise "RVM is not available" unless File.exist?(File.expand_path("~/.rvm/scripts/rvm"))
end
%w(1.8.6-p399 1.8.7-p302 1.9.2-p0).each do |ruby|
%w(1.8.6-p420 1.8.7-p334 1.9.2-p180).each do |ruby|
ruby_cmd = File.expand_path("~/.rvm/bin/ruby-#{ruby}")
desc "Run specs on Ruby #{ruby}"

View file

@ -61,8 +61,8 @@ Bundler 0.9 removes the following Bundler 0.8 Gemfile APIs:
via `bundle install /path/to/bundle`. Bundler will remember
where you installed the dependencies to on a particular
machine for future installs, loads, setups, etc.
5. `bin_path`: Bundler no longer generates binaries in the root
of your app. You should use `bundle exec` to execute binaries
5. `bin_path`: Bundler no longer generates executables in the root
of your app. You should use `bundle exec` to execute executables
in the current context.
### Gemfile Changes

View file

@ -5,7 +5,7 @@
require 'bundler/deployment'
Capistrano::Configuration.instance(:must_exist).load do
after "deploy:update_code", "bundle:install"
after "deploy:finalize_update", "bundle:install"
Bundler::Deployment.define_task(self, :task, :except => { :no_release => true })
set :rake, lambda { "#{fetch(:bundle_cmd, "bundle")} exec rake" }
end

View file

@ -1,6 +1,4 @@
$:.unshift File.expand_path('../vendor', __FILE__)
require 'thor'
require 'thor/actions'
require 'bundler/vendored_thor'
require 'rubygems/user_interaction'
require 'rubygems/config_file'
@ -16,7 +14,7 @@ module Bundler
Bundler.rubygems.ui = UI::RGProxy.new(Bundler.ui)
end
check_unknown_options! unless ARGV.include?("exec") || ARGV.include?("config")
check_unknown_options!
default_task :install
class_option "no-color", :type => :boolean, :banner => "Disable colorization in output"
@ -154,10 +152,10 @@ module Bundler
opts = options.dup
opts[:without] ||= []
if opts[:without].size == 1
opts[:without].map!{|g| g.split(" ") }
opts[:without] = opts[:without].map{|g| g.split(" ") }
opts[:without].flatten!
end
opts[:without].map!{|g| g.to_sym }
opts[:without] = opts[:without].map{|g| g.to_sym }
# Can't use Bundler.settings for this because settings needs gemfile.dirname
ENV['BUNDLE_GEMFILE'] = File.expand_path(opts[:gemfile]) if opts[:gemfile]
@ -349,7 +347,7 @@ module Bundler
exit 126
rescue Errno::ENOENT
Bundler.ui.error "bundler: command not found: #{ARGV.first}"
Bundler.ui.warn "Install missing gem binaries with `bundle install`"
Bundler.ui.warn "Install missing gem executables with `bundle install`"
exit 127
end
end

View file

@ -24,7 +24,8 @@ module Bundler
}.freeze
def initialize(name, version, options = {}, &blk)
super(name, version)
type = options["type"] || :runtime
super(name, version, type)
@autorequire = nil
@groups = Array(options["group"] || :default).map { |g| g.to_sym }

View file

@ -22,7 +22,7 @@ module Bundler
def gemspec(opts = nil)
path = opts && opts[:path] || '.'
name = opts && opts[:name] || '*'
name = opts && opts[:name] || '{,*}'
development_group = opts && opts[:development_group] || :development
path = File.expand_path(path, Bundler.default_gemfile.dirname)
gemspecs = Dir[File.join(path, "#{name}.gemspec")]
@ -34,7 +34,7 @@ module Bundler
gem spec.name, :path => path
group(development_group) do
spec.development_dependencies.each do |dep|
gem dep.name, *dep.requirement.as_list
gem dep.name, *(dep.requirement.as_list + [:type => :development])
end
end
when 0
@ -57,20 +57,34 @@ module Bundler
dep = Dependency.new(name, version, options)
# if there's already a dependency with this name we try to prefer one
if current = @dependencies.find { |d| d.name == dep.name }
if current.requirement != dep.requirement
raise DslError, "You cannot specify the same gem twice with different version requirements. " \
"You specified: #{current.name} (#{current.requirement}) and " \
"#{dep.name} (#{dep.requirement})"
if current.type == :development
@dependencies.delete current
elsif dep.type == :development
return
else
raise DslError, "You cannot specify the same gem twice with different version requirements. " \
"You specified: #{current.name} (#{current.requirement}) and " \
"#{dep.name} (#{dep.requirement})"
end
end
if current.source != dep.source
raise DslError, "You cannot specify the same gem twice coming from different sources. You " \
"specified that #{dep.name} (#{dep.requirement}) should come from " \
"#{current.source || 'an unspecfied source'} and #{dep.source}"
if current.type == :development
@dependencies.delete current
elsif dep.type == :development
return
else
raise DslError, "You cannot specify the same gem twice coming from different sources. You " \
"specified that #{dep.name} (#{dep.requirement}) should come from " \
"#{current.source || 'an unspecfied source'} and #{dep.source}"
end
end
end
@dependencies << Dependency.new(name, version, options)
@dependencies << dep
end
def source(source, options = {})
@ -183,7 +197,7 @@ module Bundler
def _normalize_options(name, version, opts)
_normalize_hash(opts)
invalid_keys = opts.keys - %w(group groups git path name branch ref tag require submodules platform platforms)
invalid_keys = opts.keys - %w(group groups git path name branch ref tag require submodules platform platforms type)
if invalid_keys.any?
plural = invalid_keys.size > 1
message = "You passed #{invalid_keys.map{|k| ':'+k }.join(", ")} "

View file

@ -16,7 +16,7 @@ module Bundler
def initialize(base, name = nil)
Bundler.ui = UI::Shell.new(Thor::Base.shell.new)
@base = base
gemspecs = name ? [File.join(base, "#{name}.gemspec")] : Dir[File.join(base, "*.gemspec")]
gemspecs = name ? [File.join(base, "#{name}.gemspec")] : Dir[File.join(base, "{,*}.gemspec")]
raise "Unable to determine name from existing gemspec. Use :name => 'gemname' in #install_tasks to manually set it." unless gemspecs.size == 1
@spec_path = gemspecs.first
@gemspec = Bundler.load_gemspec(@spec_path)
@ -41,7 +41,7 @@ module Bundler
def build_gem
file_name = nil
sh("gem build '#{spec_path}'") { |out, code|
sh("gem build -V '#{spec_path}'") { |out, code|
raise out unless out[/Successfully/]
file_name = File.basename(built_gem_path)
FileUtils.mkdir_p(File.join(base, 'pkg'))
@ -70,9 +70,12 @@ module Bundler
protected
def rubygem_push(path)
out, _ = sh("gem push '#{path}'")
raise "Gem push failed due to lack of RubyGems.org credentials." if out[/Enter your RubyGems.org credentials/]
Bundler.ui.confirm "Pushed #{name} #{version} to rubygems.org"
if Pathname.new("~/.gem/credentials").expand_path.exist?
sh("gem push '#{path}'")
Bundler.ui.confirm "Pushed #{name} #{version} to rubygems.org"
else
raise "Your rubygems.org credentials aren't set. Run `gem push` to set them."
end
end
def built_gem_path

View file

@ -78,10 +78,13 @@ module Bundler
end
end
def use(other)
def use(other, override_dupes = false)
return unless other
other.each do |s|
next if search_by_spec(s).any?
if (dupes = search_by_spec(s)) && dupes.any?
next unless override_dupes
@specs[s.name] -= dupes
end
@specs[s.name] << s
end
self
@ -101,10 +104,15 @@ module Bundler
end
end
def same_version?(a, b)
regex = /^(.*?)(?:\.0)*$/
a.to_s[regex, 1] == b.to_s[regex, 1]
if RUBY_VERSION < '1.9'
def same_version?(a, b)
regex = /^(.*?)(?:\.0)*$/
a.to_s[regex, 1] == b.to_s[regex, 1]
end
else
def same_version?(a, b)
a == b
end
end
def spec_satisfies_dependency?(spec, dep)

View file

@ -58,13 +58,16 @@ module Bundler
private
def to_ary
nil
end
def method_missing(method, *args, &blk)
if Gem::Specification.new.respond_to?(method)
raise "LazySpecification has not been materialized yet (calling :#{method} #{args.inspect})" unless @specification
@specification.send(method, *args, &blk)
else
super
end
raise "LazySpecification has not been materialized yet (calling :#{method} #{args.inspect})" unless @specification
return super unless respond_to?(method)
@specification.send(method, *args, &blk)
end
end

View file

@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE" "1" "May 2011" "" ""
.TH "BUNDLE" "1" "July 2011" "" ""
.
.SH "NAME"
\fBbundle\fR \- Ruby Dependency Management
@ -65,6 +65,10 @@ Show all of the gems in the current bundle
Show the source location of a particular gem in the bundle
.
.TP
\fBbundle outdated(1)\fR
Show all of the outdated gems in the current bundle
.
.TP
\fBbundle console(1)\fR
Start an IRB session in the context of the current bundle
.
@ -74,7 +78,11 @@ Open an installed gem in the editor
.
.TP
\fBbundle viz(1)\fR
Generate a visual representation of your dependencies
Generate a visual representation of your dependencies <<<<<<< HEAD
.
.TP
\fBbundle benchmark(1)\fR:
.
.TP
\fBbundle init(1)\fR

View file

@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-INSTALL" "1" "May 2011" "" ""
.TH "BUNDLE\-INSTALL" "1" "July 2011" "" ""
.
.SH "NAME"
\fBbundle\-install\fR \- Install the dependencies specified in your Gemfile
@ -17,6 +17,7 @@
[\-\-without=GROUP1[ GROUP2\.\.\.]]
[\-\-local] [\-\-deployment]
[\-\-binstubs[=DIRECTORY]]
[\-\-standalone[=GROUP1[ GROUP2\.\.\.]]]
[\-\-quiet]
.
.fi
@ -62,6 +63,10 @@ Switches bundler\'s defaults into \fIdeployment mode\fR\. Do not use this flag o
\fB\-\-binstubs[=<directory>]\fR
Create a directory (defaults to \fBbin\fR) containing an executable that runs in the context of the bundle\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all dependencies used come from the bundled gems\.
.
.TP
\fB\-\-standalone[=<list>]\fR
Make a bundle that can work without Ruby Gems or Bundler at runtime\. It takes a space separated list of groups to install\. It creates a \fBbundle\fR directory and installs the bundle there\. It also generates a \fBbundle/bundler/setup\.rb\fR file to replace Bundler\'s own setup\.
.
.SH "DEPLOYMENT MODE"
Bundler\'s defaults are optimized for development\. To switch to defaults optimized for deployment, use the \fB\-\-deployment\fR flag\. Do not activate deployment mode on development machines, as it will cause in an error when the Gemfile is modified\.
.

View file

@ -14,6 +14,7 @@ SYNOPSIS
[--without=GROUP1[ GROUP2...]]
[--local] [--deployment]
[--binstubs[=DIRECTORY]]
[--standalone[=GROUP1[ GROUP2...]]]
[--quiet]
@ -74,6 +75,13 @@ OPTIONS
bin/rails executable that ensures that all dependencies used
come from the bundled gems.
--standalone[=<list>]
Make a bundle that can work without Ruby Gems or Bundler at run-
time. It takes a space separated list of groups to install. It
creates a bundle directory and installs the bundle there. It
also generates a bundle/bundler/setup.rb file to replace
Bundler's own setup.
DEPLOYMENT MODE
Bundler's defaults are optimized for development. To switch to defaults
optimized for deployment, use the --deployment flag. Do not activate
@ -335,4 +343,4 @@ CONSERVATIVE UPDATING
May 2011 BUNDLE-INSTALL(1)
July 2011 BUNDLE-INSTALL(1)

View file

@ -54,6 +54,9 @@ UTILITIES
bundle show(1)
Show the source location of a particular gem in the bundle
bundle outdated(1)
Show all of the outdated gems in the current bundle
bundle console(1)
Start an IRB session in the context of the current bundle
@ -61,7 +64,11 @@ UTILITIES
Open an installed gem in the editor
bundle viz(1)
Generate a visual representation of your dependencies
Generate a visual representation of your dependencies <<<<<<<
HEAD
bundle benchmark(1):
bundle init(1)
Generate a simple Gemfile, placed in the current directory
@ -83,4 +90,4 @@ OBSOLETE
May 2011 BUNDLE(1)
July 2011 BUNDLE(1)

View file

@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "GEMFILE" "5" "May 2011" "" ""
.TH "GEMFILE" "5" "July 2011" "" ""
.
.SH "NAME"
\fBGemfile\fR \- A format for describing gem dependencies for Ruby programs

View file

@ -342,4 +342,4 @@ SOURCE PRIORITY
May 2011 GEMFILE(5)
July 2011 GEMFILE(5)

View file

@ -145,7 +145,7 @@ module Bundler
def debug
if ENV['DEBUG_RESOLVER']
debug_info = yield
debug_info = debug_info.inpsect unless debug_info.is_a?(String)
debug_info = debug_info.inspect unless debug_info.is_a?(String)
$stderr.puts debug_info
end
end
@ -279,14 +279,6 @@ module Bundler
end
raise GemNotFound, message
else
if @missing_gems[current] >= 5
message = "Bundler could not find find gem #{current.required_by.last},"
message << "which is required by gem #{current}."
raise GemNotFound, message
end
@missing_gems[current] += 1
debug { " Could not find #{current} by #{current.required_by.last}" }
@errors[current.name] = [nil, current]
end
end
@ -356,7 +348,8 @@ module Bundler
def search(dep)
if base = @base[dep.name] and base.any?
d = Gem::Dependency.new(base.first.name, *[dep.requirement.as_list, base.first.version].flatten)
reqs = [dep.requirement.as_list, base.first.version.to_s].flatten.compact
d = Gem::Dependency.new(base.first.name, *reqs)
else
d = dep.dep
end
@ -456,7 +449,8 @@ module Bundler
# the rest of the time, the gem cannot be found because it does not exist in the known sources
else
if requirement.required_by.first
o << "Could not find gem '#{clean_req(requirement)}', required by '#{clean_req(requirement.required_by.first)}', in any of the sources\n"
o << "Could not find gem '#{clean_req(requirement)}', which is required by "
o << "gem '#{clean_req(requirement.required_by.first)}', in any of the sources."
else
o << "Could not find gem '#{clean_req(requirement)} in any of the sources\n"
end

View file

@ -123,7 +123,7 @@ module Bundler
if executables.include? File.basename(caller.first.split(':').first)
return
end
opts = reqs.last.is_a?(Hash) ? reqs.pop : {}
reqs.pop if reqs.last.is_a?(Hash)
unless dep.respond_to?(:name) && dep.respond_to?(:requirement)
dep = Gem::Dependency.new(dep, reqs)
@ -250,7 +250,17 @@ module Bundler
Gem.clear_paths
end
# Rubygems versions 1.3.6 through 1.6.2
# This backports the correct segment generation code from Rubygems 1.4+
# by monkeypatching it into the method in Rubygems 1.3.6 and 1.3.7.
def backport_segment_generation
Gem::Version.send(:define_method, :segments) do
@segments ||= @version.scan(/[0-9]+|[a-z]+/i).map do |s|
/^\d+$/ =~ s ? s.to_i : s
end
end
end
# Rubygems 1.4 through 1.6
class Legacy < RubygemsIntegration
def stub_rubygems(specs)
stub_source_index137(specs)
@ -265,6 +275,14 @@ module Bundler
end
end
# Rubygems versions 1.3.6 and 1.3.7
class Ancient < Legacy
def initialize
super
backport_segment_generation
end
end
# Rubygems 1.7
class Transitional < Legacy
def stub_rubygems(specs)
@ -313,8 +331,10 @@ module Bundler
@rubygems = RubygemsIntegration::AlmostModern.new
elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.7.0')
@rubygems = RubygemsIntegration::Transitional.new
else # Rubygems 1.3.6 through 1.6.2
elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.4.0')
@rubygems = RubygemsIntegration::Legacy.new
else # Rubygems 1.3.6 and 1.3.7
@rubygems = RubygemsIntegration::Ancient.new
end
class << self

View file

@ -158,11 +158,17 @@ module Bundler
end
def fetch_specs
Index.build do |idx|
idx.use installed_specs
idx.use cached_specs if @allow_cached || @allow_remote
idx.use remote_specs if @allow_remote
# remote_specs usually generates a way larger Index than the other
# sources, and large_idx.use small_idx is way faster than
# small_idx.use large_idx.
if @allow_remote
idx = remote_specs.dup
else
idx = Index.new
end
idx.use(cached_specs, :override_dupes) if @allow_cached || @allow_remote
idx.use(installed_specs, :override_dupes)
idx
end
def installed_specs
@ -280,7 +286,7 @@ module Bundler
attr_writer :name
attr_accessor :version
DEFAULT_GLOB = "{,*/}*.gemspec"
DEFAULT_GLOB = "{,*,*/*}.gemspec"
def initialize(options)
@options = options
@ -362,9 +368,9 @@ module Bundler
s.relative_loaded_from = "#{@name}.gemspec"
s.authors = ["no one"]
if expanded_path.join("bin").exist?
binaries = expanded_path.join("bin").children
binaries.reject!{|p| File.directory?(p) }
s.executables = binaries.map{|c| c.basename.to_s }
executables = expanded_path.join("bin").children
executables.reject!{|p| File.directory?(p) }
s.executables = executables.map{|c| c.basename.to_s }
end
end
end
@ -585,7 +591,7 @@ module Bundler
end
def base_name
File.basename(uri.sub(%r{^(\w+://)?([^/:]+:)},''), ".git")
File.basename(uri.sub(%r{^(\w+://)?([^/:]+:)?(//\w*/)?(\w*/)*},''),".git")
end
def shortref_for_display(ref)

View file

@ -1,21 +1,18 @@
require 'tsort'
require 'forwardable'
module Bundler
class SpecSet
extend Forwardable
include TSort, Enumerable
def_delegators :@specs, :<<, :length, :add, :remove
def_delegators :sorted, :each
def initialize(specs)
@specs = specs.sort_by { |s| s.name }
end
def each
sorted.each { |s| yield s }
end
def length
@specs.length
end
def for(dependencies, skip = [], check = false, match_current_platform = false)
handled, deps, specs = {}, dependencies.dup, []
skip << 'bundler'
@ -68,6 +65,10 @@ module Bundler
value
end
def sort!
self
end
def to_a
sorted.dup
end

View file

@ -0,0 +1 @@
require "bundler/gem_tasks"

View file

@ -17,4 +17,8 @@ Gem::Specification.new do |s|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
# specify any dependencies here; for example:
# s.add_development_dependency "rspec"
# s.add_runtime_dependency "rest-client"
end

View file

@ -18,6 +18,23 @@ class Thor
end
end
# Registers another Thor subclass as a command.
#
# ==== Parameters
# klass<Class>:: Thor subclass to register
# command<String>:: Subcommand name to use
# usage<String>:: Short usage for the subcommand
# description<String>:: Description for the subcommand
def register(klass, subcommand_name, usage, description, options={})
if klass <= Thor::Group
desc usage, description, options
define_method(subcommand_name) { invoke klass }
else
desc usage, description, options
subcommand subcommand_name, klass
end
end
# Defines the usage and the description of the next task.
#
# ==== Parameters
@ -252,8 +269,7 @@ class Thor
# the namespace should be displayed as arguments.
#
def banner(task, namespace = nil, subcommand = false)
base = File.basename($0).split(" ").first
"#{base} #{task.formatted_usage(self, $thor_runner, subcommand)}"
"#{basename} #{task.formatted_usage(self, $thor_runner, subcommand)}"
end
def baseclass #:nodoc:
@ -295,17 +311,40 @@ class Thor
# Receives a task name (can be nil), and try to get a map from it.
# If a map can't be found use the sent name or the default task.
def normalize_task_name(meth) #:nodoc:
meth = map[meth.to_s] || meth || default_task
meth = map[meth.to_s] || find_subcommand_and_update_argv(meth) || meth || default_task
meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
end
# terrible hack that overwrites ARGV
def find_subcommand_and_update_argv(subcmd_name) #:nodoc:
return unless subcmd_name
cmd = find_subcommand(subcmd_name)
ARGV[0] = cmd if cmd
cmd
end
def find_subcommand(subcmd_name)
possibilities = find_subcommand_possibilities subcmd_name
if possibilities.size > 1
raise "Ambiguous subcommand #{subcmd_name} matches [#{possibilities.join(', ')}]"
elsif possibilities.size < 1
return nil
end
possibilities.first
end
def find_subcommand_possibilities(subcmd_name)
len = subcmd_name.length
all_tasks.map {|t| t.first}.select { |n| subcmd_name == n[0, len] }
end
def subcommand_help(cmd)
desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
class_eval <<-RUBY
def help(task = nil, subcommand = true); super; end
RUBY
end
end
include Thor::Base

View file

@ -1,10 +1,12 @@
require 'fileutils'
require 'uri'
require 'thor/core_ext/file_binary_read'
Dir[File.join(File.dirname(__FILE__), "actions", "*.rb")].each do |action|
require action
end
require 'thor/actions/create_file'
require 'thor/actions/create_link'
require 'thor/actions/directory'
require 'thor/actions/empty_directory'
require 'thor/actions/file_manipulation'
require 'thor/actions/inject_into_file'
class Thor
module Actions
@ -158,13 +160,23 @@ class Thor
#
def inside(dir='', config={}, &block)
verbose = config.fetch(:verbose, false)
pretend = options[:pretend]
say_status :inside, dir, verbose
shell.padding += 1 if verbose
@destination_stack.push File.expand_path(dir, destination_root)
FileUtils.mkdir_p(destination_root) unless File.exist?(destination_root)
FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
# If the directory doesnt exist and we're not pretending
if !File.exist?(destination_root) && !pretend
FileUtils.mkdir_p(destination_root)
end
if pretend
# In pretend mode, just yield down to the block
block.arity == 1 ? yield(destination_root) : yield
else
FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
end
@destination_stack.pop
shell.padding -= 1 if verbose
@ -210,7 +222,7 @@ class Thor
#
# ==== Parameters
# command<String>:: the command to be executed.
# config<Hash>:: give :verbose => false to not log the status. Specify :with
# config<Hash>:: give :verbose => false to not log the status, :capture => true to hide to output. Specify :with
# to append an executable to command executation.
#
# ==== Example
@ -231,7 +243,10 @@ class Thor
end
say_status :run, desc, config.fetch(:verbose, true)
`#{command}` unless options[:pretend]
unless options[:pretend]
config[:capture] ? `#{command}` : system("#{command}")
end
end
# Executes a ruby script (taking into account WIN32 platform quirks).
@ -251,8 +266,9 @@ class Thor
# ==== Parameters
# task<String>:: the task to be invoked
# args<Array>:: arguments to the task
# config<Hash>:: give :verbose => false to not log the status. Other options
# are given as parameter to Thor.
# config<Hash>:: give :verbose => false to not log the status, :capture => true to hide to output.
# Other options are given as parameter to Thor.
#
#
# ==== Examples
#
@ -266,12 +282,13 @@ class Thor
config = args.last.is_a?(Hash) ? args.pop : {}
verbose = config.key?(:verbose) ? config.delete(:verbose) : true
pretend = config.key?(:pretend) ? config.delete(:pretend) : false
capture = config.key?(:capture) ? config.delete(:capture) : false
args.unshift task
args.push Thor::Options.to_switches(config)
command = args.join(' ').strip
run command, :with => :thor, :verbose => verbose, :pretend => pretend
run command, :with => :thor, :verbose => verbose, :pretend => pretend, :capture => capture
end
protected

View file

@ -18,7 +18,7 @@ class Thor
# "vhost.name = #{hostname}"
# end
#
# create_file "config/apach.conf", "your apache config"
# create_file "config/apache.conf", "your apache config"
#
def create_file(destination, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
@ -27,7 +27,7 @@ class Thor
end
alias :add_file :create_file
# AddFile is a subset of Template, which instead of rendering a file with
# CreateFile is a subset of Template, which instead of rendering a file with
# ERB, it gets the content from the user.
#
class CreateFile < EmptyDirectory #:nodoc:

View file

@ -0,0 +1,57 @@
require 'thor/actions/create_file'
class Thor
module Actions
# Create a new file relative to the destination root from the given source.
#
# ==== Parameters
# destination<String>:: the relative path to the destination root.
# source<String|NilClass>:: the relative path to the source root.
# config<Hash>:: give :verbose => false to not log the status.
# :: give :symbolic => false for hard link.
#
# ==== Examples
#
# create_link "config/apache.conf", "/etc/apache.conf"
#
def create_link(destination, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
source = args.first
action CreateLink.new(self, destination, source, config)
end
alias :add_link :create_link
# CreateLink is a subset of CreateFile, which instead of taking a block of
# data, just takes a source string from the user.
#
class CreateLink < CreateFile #:nodoc:
attr_reader :data
# Checks if the content of the file at the destination is identical to the rendered result.
#
# ==== Returns
# Boolean:: true if it is identical, false otherwise.
#
def identical?
exists? && File.identical?(render, destination)
end
def invoke!
invoke_with_conflict_check do
FileUtils.mkdir_p(File.dirname(destination))
# Create a symlink by default
config[:symbolic] ||= true
File.unlink(destination) if exists?
if config[:symbolic]
File.symlink(render, destination)
else
File.link(render, destination)
end
end
given_destination
end
end
end
end

View file

@ -21,7 +21,7 @@ class Thor
# directory "doc"
#
# It will create a doc directory in the destination with the following
# files (assuming that the app_name is "blog"):
# files (assuming that the `app_name` method returns the value "blog"):
#
# doc/
# components/
@ -70,7 +70,7 @@ class Thor
lookup = config[:recursive] ? File.join(source, '**') : source
lookup = File.join(lookup, '{*,.[a-z]*}')
Dir[lookup].each do |file_source|
Dir[lookup].sort.each do |file_source|
next if File.directory?(file_source)
file_destination = File.join(given_destination, file_source.gsub(source, '.'))
file_destination.gsub!('/./', '/')

View file

@ -30,6 +30,28 @@ class Thor
end
end
# Links the file from the relative source to the relative destination. If
# the destination is not given it's assumed to be equal to the source.
#
# ==== Parameters
# source<String>:: the relative path to the source root.
# destination<String>:: the relative path to the destination root.
# config<Hash>:: give :verbose => false to not log the status.
#
# ==== Examples
#
# link_file "README", "doc/README"
#
# link_file "doc/README"
#
def link_file(source, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
destination = args.first || source
source = File.expand_path(find_in_source_paths(source.to_s))
create_link destination, source, config
end
# Gets the content at the given address and places it at the given relative
# destination. If a block is given instead of destination, the content of
# the url is yielded and used as location.
@ -51,7 +73,7 @@ class Thor
config = args.last.is_a?(Hash) ? args.pop : {}
destination = args.first
source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^http\:\/\//
source = File.expand_path(find_in_source_paths(source.to_s)) unless source =~ /^https?\:\/\//
render = open(source) {|input| input.binmode.read }
destination ||= if block_given?
@ -80,13 +102,13 @@ class Thor
#
def template(source, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
destination = args.first || source
destination = args.first || source.sub(/\.tt$/, '')
source = File.expand_path(find_in_source_paths(source.to_s))
context = instance_eval('binding')
create_file destination, nil, config do
content = ERB.new(::File.binread(source), nil, '-').result(context)
content = ERB.new(::File.binread(source), nil, '-', '@output_buffer').result(context)
content = block.call(content) if block
content
end
@ -110,7 +132,7 @@ class Thor
FileUtils.chmod_R(mode, path) unless options[:pretend]
end
# Prepend text to a file. Since it depends on inject_into_file, it's reversible.
# Prepend text to a file. Since it depends on insert_into_file, it's reversible.
#
# ==== Parameters
# path<String>:: path of the file to be changed
@ -119,19 +141,20 @@ class Thor
#
# ==== Example
#
# prepend_file 'config/environments/test.rb', 'config.gem "rspec"'
# prepend_to_file 'config/environments/test.rb', 'config.gem "rspec"'
#
# prepend_file 'config/environments/test.rb' do
# prepend_to_file 'config/environments/test.rb' do
# 'config.gem "rspec"'
# end
#
def prepend_file(path, *args, &block)
def prepend_to_file(path, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
config.merge!(:after => /\A/)
inject_into_file(path, *(args << config), &block)
insert_into_file(path, *(args << config), &block)
end
alias_method :prepend_file, :prepend_to_file
# Append text to a file. Since it depends on inject_into_file, it's reversible.
# Append text to a file. Since it depends on insert_into_file, it's reversible.
#
# ==== Parameters
# path<String>:: path of the file to be changed
@ -140,20 +163,21 @@ class Thor
#
# ==== Example
#
# append_file 'config/environments/test.rb', 'config.gem "rspec"'
# append_to_file 'config/environments/test.rb', 'config.gem "rspec"'
#
# append_file 'config/environments/test.rb' do
# append_to_file 'config/environments/test.rb' do
# 'config.gem "rspec"'
# end
#
def append_file(path, *args, &block)
def append_to_file(path, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
config.merge!(:before => /\z/)
inject_into_file(path, *(args << config), &block)
insert_into_file(path, *(args << config), &block)
end
alias_method :append_file, :append_to_file
# Injects text right after the class definition. Since it depends on
# inject_into_file, it's reversible.
# insert_into_file, it's reversible.
#
# ==== Parameters
# path<String>:: path of the file to be changed
@ -172,7 +196,7 @@ class Thor
def inject_into_class(path, klass, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {}
config.merge!(:after => /class #{klass}\n|class #{klass} .*\n/)
inject_into_file(path, *(args << config), &block)
insert_into_file(path, *(args << config), &block)
end
# Run a regular expression replacement on a file.
@ -225,5 +249,22 @@ class Thor
end
alias :remove_dir :remove_file
private
attr_accessor :output_buffer
def concat(string)
@output_buffer.concat(string)
end
def capture(*args, &block)
with_output_buffer { block.call(*args) }
end
def with_output_buffer(buf = '') #:nodoc:
self.output_buffer, old_buffer = buf, output_buffer
yield
output_buffer
ensure
self.output_buffer = old_buffer
end
end
end

View file

@ -10,19 +10,19 @@ class Thor
# destination<String>:: Relative path to the destination root
# data<String>:: Data to add to the file. Can be given as a block.
# config<Hash>:: give :verbose => false to not log the status and the flag
# for injection (:after or :before) or :force => true for
# for injection (:after or :before) or :force => true for
# insert two or more times the same content.
#
#
# ==== Examples
#
# inject_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
# insert_into_file "config/environment.rb", "config.gem :thor", :after => "Rails::Initializer.run do |config|\n"
#
# inject_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do
# insert_into_file "config/environment.rb", :after => "Rails::Initializer.run do |config|\n" do
# gems = ask "Which gems would you like to add?"
# gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
# end
#
def inject_into_file(destination, *args, &block)
def insert_into_file(destination, *args, &block)
if block_given?
data, config = block, args.shift
else
@ -30,6 +30,7 @@ class Thor
end
action InjectIntoFile.new(self, destination, data, config)
end
alias_method :inject_into_file, :insert_into_file
class InjectIntoFile < EmptyDirectory #:nodoc:
attr_reader :replacement, :flag, :behavior
@ -76,12 +77,16 @@ class Thor
protected
def say_status(behavior)
status = if flag == /\A/
behavior == :invoke ? :prepend : :unprepend
elsif flag == /\z/
behavior == :invoke ? :append : :unappend
status = if behavior == :invoke
if flag == /\A/
:prepend
elsif flag == /\z/
:append
else
:insert
end
else
behavior == :invoke ? :inject : :deinject
:subtract
end
super(status, config[:verbose])

View file

@ -94,8 +94,6 @@ class Thor
end
module ClassMethods
attr_accessor :debugging
def attr_reader(*) #:nodoc:
no_tasks { super }
end
@ -384,14 +382,29 @@ class Thor
# script.invoke(:task, first_arg, second_arg, third_arg)
#
def start(given_args=ARGV, config={})
self.debugging = given_args.delete("--debug")
config[:shell] ||= Thor::Base.shell.new
dispatch(nil, given_args.dup, nil, config)
rescue Thor::Error => e
debugging ? (raise e) : config[:shell].error(e.message)
ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
exit(1) if exit_on_failure?
end
# Allows to use private methods from parent in child classes as tasks.
#
# ==== Paremeters
# names<Array>:: Method names to be used as tasks
#
# ==== Examples
#
# public_task :foo
# public_task :foo, :bar, :baz
#
def public_task(*names)
names.each do |name|
class_eval "def #{name}(*); super end"
end
end
def handle_no_task_error(task) #:nodoc:
if $thor_runner
raise UndefinedTaskError, "Could not find task #{task.inspect} in #{namespace.inspect} namespace."
@ -531,6 +544,13 @@ class Thor
false
end
#
# The basename of the program invoking the thor class.
#
def basename
File.basename($0).split(' ').first
end
# SIGNATURE: Sets the baseclass. This is where the superclass lookup
# finishes.
def baseclass #:nodoc:

View file

@ -0,0 +1,273 @@
require 'thor/base'
# Thor has a special class called Thor::Group. The main difference to Thor class
# is that it invokes all tasks at once. It also include some methods that allows
# invocations to be done at the class method, which are not available to Thor
# tasks.
class Thor::Group
class << self
# The description for this Thor::Group. If none is provided, but a source root
# exists, tries to find the USAGE one folder above it, otherwise searches
# in the superclass.
#
# ==== Parameters
# description<String>:: The description for this Thor::Group.
#
def desc(description=nil)
case description
when nil
@desc ||= from_superclass(:desc, nil)
else
@desc = description
end
end
# Prints help information.
#
# ==== Options
# short:: When true, shows only usage.
#
def help(shell)
shell.say "Usage:"
shell.say " #{banner}\n"
shell.say
class_options_help(shell)
shell.say self.desc if self.desc
end
# Stores invocations for this class merging with superclass values.
#
def invocations #:nodoc:
@invocations ||= from_superclass(:invocations, {})
end
# Stores invocation blocks used on invoke_from_option.
#
def invocation_blocks #:nodoc:
@invocation_blocks ||= from_superclass(:invocation_blocks, {})
end
# Invoke the given namespace or class given. It adds an instance
# method that will invoke the klass and task. You can give a block to
# configure how it will be invoked.
#
# The namespace/class given will have its options showed on the help
# usage. Check invoke_from_option for more information.
#
def invoke(*names, &block)
options = names.last.is_a?(Hash) ? names.pop : {}
verbose = options.fetch(:verbose, true)
names.each do |name|
invocations[name] = false
invocation_blocks[name] = block if block_given?
class_eval <<-METHOD, __FILE__, __LINE__
def _invoke_#{name.to_s.gsub(/\W/, '_')}
klass, task = self.class.prepare_for_invocation(nil, #{name.inspect})
if klass
say_status :invoke, #{name.inspect}, #{verbose.inspect}
block = self.class.invocation_blocks[#{name.inspect}]
_invoke_for_class_method klass, task, &block
else
say_status :error, %(#{name.inspect} [not found]), :red
end
end
METHOD
end
end
# Invoke a thor class based on the value supplied by the user to the
# given option named "name". A class option must be created before this
# method is invoked for each name given.
#
# ==== Examples
#
# class GemGenerator < Thor::Group
# class_option :test_framework, :type => :string
# invoke_from_option :test_framework
# end
#
# ==== Boolean options
#
# In some cases, you want to invoke a thor class if some option is true or
# false. This is automatically handled by invoke_from_option. Then the
# option name is used to invoke the generator.
#
# ==== Preparing for invocation
#
# In some cases you want to customize how a specified hook is going to be
# invoked. You can do that by overwriting the class method
# prepare_for_invocation. The class method must necessarily return a klass
# and an optional task.
#
# ==== Custom invocations
#
# You can also supply a block to customize how the option is giong to be
# invoked. The block receives two parameters, an instance of the current
# class and the klass to be invoked.
#
def invoke_from_option(*names, &block)
options = names.last.is_a?(Hash) ? names.pop : {}
verbose = options.fetch(:verbose, :white)
names.each do |name|
unless class_options.key?(name)
raise ArgumentError, "You have to define the option #{name.inspect} " <<
"before setting invoke_from_option."
end
invocations[name] = true
invocation_blocks[name] = block if block_given?
class_eval <<-METHOD, __FILE__, __LINE__
def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')}
return unless options[#{name.inspect}]
value = options[#{name.inspect}]
value = #{name.inspect} if TrueClass === value
klass, task = self.class.prepare_for_invocation(#{name.inspect}, value)
if klass
say_status :invoke, value, #{verbose.inspect}
block = self.class.invocation_blocks[#{name.inspect}]
_invoke_for_class_method klass, task, &block
else
say_status :error, %(\#{value} [not found]), :red
end
end
METHOD
end
end
# Remove a previously added invocation.
#
# ==== Examples
#
# remove_invocation :test_framework
#
def remove_invocation(*names)
names.each do |name|
remove_task(name)
remove_class_option(name)
invocations.delete(name)
invocation_blocks.delete(name)
end
end
# Overwrite class options help to allow invoked generators options to be
# shown recursively when invoking a generator.
#
def class_options_help(shell, groups={}) #:nodoc:
get_options_from_invocations(groups, class_options) do |klass|
klass.send(:get_options_from_invocations, groups, class_options)
end
super(shell, groups)
end
# Get invocations array and merge options from invocations. Those
# options are added to group_options hash. Options that already exists
# in base_options are not added twice.
#
def get_options_from_invocations(group_options, base_options) #:nodoc:
invocations.each do |name, from_option|
value = if from_option
option = class_options[name]
option.type == :boolean ? name : option.default
else
name
end
next unless value
klass, task = prepare_for_invocation(name, value)
next unless klass && klass.respond_to?(:class_options)
value = value.to_s
human_name = value.respond_to?(:classify) ? value.classify : value
group_options[human_name] ||= []
group_options[human_name] += klass.class_options.values.select do |option|
base_options[option.name.to_sym].nil? && option.group.nil? &&
!group_options.values.flatten.any? { |i| i.name == option.name }
end
yield klass if block_given?
end
end
# Returns tasks ready to be printed.
def printable_tasks(*)
item = []
item << banner
item << (desc ? "# #{desc.gsub(/\s+/m,' ')}" : "")
[item]
end
def handle_argument_error(task, error) #:nodoc:
raise error, "#{task.name.inspect} was called incorrectly. Are you sure it has arity equals to 0?"
end
protected
# The method responsible for dispatching given the args.
def dispatch(task, given_args, given_opts, config) #:nodoc:
if Thor::HELP_MAPPINGS.include?(given_args.first)
help(config[:shell])
return
end
args, opts = Thor::Options.split(given_args)
opts = given_opts || opts
if task
new(args, opts, config).invoke_task(all_tasks[task])
else
new(args, opts, config).invoke_all
end
end
# The banner for this class. You can customize it if you are invoking the
# thor class by another ways which is not the Thor::Runner.
def banner
"#{basename} #{self_task.formatted_usage(self, false)}"
end
# Represents the whole class as a task.
def self_task #:nodoc:
Thor::DynamicTask.new(self.namespace, class_options)
end
def baseclass #:nodoc:
Thor::Group
end
def create_task(meth) #:nodoc:
tasks[meth.to_s] = Thor::Task.new(meth, nil, nil, nil, nil)
true
end
end
include Thor::Base
protected
# Shortcut to invoke with padding and block handling. Use internally by
# invoke and invoke_from_option class methods.
def _invoke_for_class_method(klass, task=nil, *args, &block) #:nodoc:
with_padding do
if block
case block.arity
when 3
block.call(self, klass, task)
when 2
block.call(self, klass)
when 1
instance_exec(klass, &block)
end
else
invoke klass, task, *args
end
end
end
end

View file

@ -28,7 +28,7 @@ class Thor
@switches = arguments
arguments.each do |argument|
if argument.default
if argument.default != nil
@assigns[argument.human_name] = argument.default
elsif argument.required?
@non_assigned_required << argument
@ -94,7 +94,7 @@ class Thor
hash = {}
while current_is_value? && peek.include?(?:)
key, value = shift.split(':')
key, value = shift.split(':',2)
hash[key] = value
end
hash

View file

@ -37,7 +37,7 @@ class Thor
# string (--foo=value) or booleans (just --foo).
#
# By default all options are optional, unless :required is given.
#
#
def self.parse(key, value)
if key.is_a?(Array)
name, *aliases = key

View file

@ -53,7 +53,9 @@ class Thor
@pile = args.dup
while peek
if current_is_switch?
match, is_switch = current_is_switch?
if is_switch
case shift
when SHORT_SQ_RE
unshift($1.split('').map { |f| "-#{f}" })
@ -68,7 +70,7 @@ class Thor
switch = normalize_switch(switch)
option = switch_option(switch)
@assigns[option.human_name] = parse_peek(switch, option)
elsif current_is_switch_formatted?
elsif match
@unknown << shift
else
shift
@ -83,7 +85,9 @@ class Thor
end
def check_unknown!
raise UnknownArgumentError, "Unknown switches '#{@unknown.join(', ')}'" unless @unknown.empty?
unless ARGV.include?("exec") || ARGV.include?("config")
raise UnknownArgumentError, "Unknown switches '#{@unknown.join(', ')}'" unless @unknown.empty?
end
end
protected
@ -92,15 +96,17 @@ class Thor
#
def current_is_switch?
case peek
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
switch?($1)
when SHORT_SQ_RE
$1.split('').any? { |f| switch?("-#{f}") }
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
[true, switch?($1)]
when SHORT_SQ_RE
[true, $1.split('').any? { |f| switch?("-#{f}") }]
else
[false, false]
end
end
def switch_formatted?(arg)
case arg
def current_is_switch_formatted?
case peek
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
true
else
@ -108,12 +114,8 @@ class Thor
end
end
def current_is_switch_formatted?
switch_formatted? peek
end
def switch?(arg)
switch_option(arg) || @shorts.key?(arg)
switch_option(normalize_switch(arg))
end
def switch_option(arg)
@ -127,7 +129,7 @@ class Thor
# Check if the given argument is actually a shortcut.
#
def normalize_switch(arg)
@shorts.key?(arg) ? @shorts[arg] : arg
(@shorts[arg] || arg).tr('_', '-')
end
# Parse boolean values which can be given as --foo=true, --foo or --no-foo.
@ -169,6 +171,5 @@ class Thor
@non_assigned_required.delete(option)
send(:"parse_#{option.type}", switch)
end
end
end

View file

@ -0,0 +1,66 @@
require 'rake'
class Thor
# Adds a compatibility layer to your Thor classes which allows you to use
# rake package tasks. For example, to use rspec rake tasks, one can do:
#
# require 'thor/rake_compat'
#
# class Default < Thor
# include Thor::RakeCompat
#
# Spec::Rake::SpecTask.new(:spec) do |t|
# t.spec_opts = ['--options', "spec/spec.opts"]
# t.spec_files = FileList['spec/**/*_spec.rb']
# end
# end
#
module RakeCompat
def self.rake_classes
@rake_classes ||= []
end
def self.included(base)
# Hack. Make rakefile point to invoker, so rdoc task is generated properly.
rakefile = File.basename(caller[0].match(/(.*):\d+/)[1])
Rake.application.instance_variable_set(:@rakefile, rakefile)
self.rake_classes << base
end
end
end
class Object #:nodoc:
alias :rake_task :task
alias :rake_namespace :namespace
def task(*args, &block)
task = rake_task(*args, &block)
if klass = Thor::RakeCompat.rake_classes.last
non_namespaced_name = task.name.split(':').last
description = non_namespaced_name
description << task.arg_names.map{ |n| n.to_s.upcase }.join(' ')
description.strip!
klass.desc description, task.comment || non_namespaced_name
klass.send :define_method, non_namespaced_name do |*args|
Rake::Task[task.name.to_sym].invoke(*args)
end
end
task
end
def namespace(name, &block)
if klass = Thor::RakeCompat.rake_classes.last
const_name = Thor::Util.camel_case(name.to_s).to_sym
klass.const_set(const_name, Class.new(Thor))
new_klass = klass.const_get(const_name)
Thor::RakeCompat.rake_classes << new_klass
end
rake_namespace(name, &block)
Thor::RakeCompat.rake_classes.pop
end
end

View file

@ -0,0 +1,309 @@
require 'thor'
require 'thor/group'
require 'thor/core_ext/file_binary_read'
require 'fileutils'
require 'open-uri'
require 'yaml'
require 'digest/md5'
require 'pathname'
class Thor::Runner < Thor #:nodoc:
map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
# Override Thor#help so it can give information about any class and any method.
#
def help(meth = nil)
if meth && !self.respond_to?(meth)
initialize_thorfiles(meth)
klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
klass.start(["-h", task].compact, :shell => self.shell)
else
super
end
end
# If a task is not found on Thor::Runner, method missing is invoked and
# Thor::Runner is then responsable for finding the task in all classes.
#
def method_missing(meth, *args)
meth = meth.to_s
initialize_thorfiles(meth)
klass, task = Thor::Util.find_class_and_task_by_namespace(meth)
args.unshift(task) if task
klass.start(args, :shell => self.shell)
end
desc "install NAME", "Install an optionally named Thor file into your system tasks"
method_options :as => :string, :relative => :boolean, :force => :boolean
def install(name)
initialize_thorfiles
# If a directory name is provided as the argument, look for a 'main.thor'
# task in said directory.
begin
if File.directory?(File.expand_path(name))
base, package = File.join(name, "main.thor"), :directory
contents = open(base) {|input| input.read }
else
base, package = name, :file
contents = open(name) {|input| input.read }
end
rescue OpenURI::HTTPError
raise Error, "Error opening URI '#{name}'"
rescue Errno::ENOENT
raise Error, "Error opening file '#{name}'"
end
say "Your Thorfile contains:"
say contents
unless options["force"]
return false if no?("Do you wish to continue [y/N]?")
end
as = options["as"] || begin
first_line = contents.split("\n")[0]
(match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
end
unless as
basename = File.basename(name)
as = ask("Please specify a name for #{name} in the system repository [#{basename}]:")
as = basename if as.empty?
end
location = if options[:relative] || name =~ /^https?:\/\//
name
else
File.expand_path(name)
end
thor_yaml[as] = {
:filename => Digest::MD5.hexdigest(name + as),
:location => location,
:namespaces => Thor::Util.namespaces_in_content(contents, base)
}
save_yaml(thor_yaml)
say "Storing thor file in your system repository"
destination = File.join(thor_root, thor_yaml[as][:filename])
if package == :file
File.open(destination, "w") { |f| f.puts contents }
else
FileUtils.cp_r(name, destination)
end
thor_yaml[as][:filename] # Indicate success
end
desc "version", "Show Thor version"
def version
require 'thor/version'
say "Thor #{Thor::VERSION}"
end
desc "uninstall NAME", "Uninstall a named Thor module"
def uninstall(name)
raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
say "Uninstalling #{name}."
FileUtils.rm_rf(File.join(thor_root, "#{thor_yaml[name][:filename]}"))
thor_yaml.delete(name)
save_yaml(thor_yaml)
puts "Done."
end
desc "update NAME", "Update a Thor file from its original location"
def update(name)
raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location]
say "Updating '#{name}' from #{thor_yaml[name][:location]}"
old_filename = thor_yaml[name][:filename]
self.options = self.options.merge("as" => name)
filename = install(thor_yaml[name][:location])
unless filename == old_filename
File.delete(File.join(thor_root, old_filename))
end
end
desc "installed", "List the installed Thor modules and tasks"
method_options :internal => :boolean
def installed
initialize_thorfiles(nil, true)
display_klasses(true, options["internal"])
end
desc "list [SEARCH]", "List the available thor tasks (--substring means .*SEARCH)"
method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean
def list(search="")
initialize_thorfiles
search = ".*#{search}" if options["substring"]
search = /^#{search}.*/i
group = options[:group] || "standard"
klasses = Thor::Base.subclasses.select do |k|
(options[:all] || k.group == group) && k.namespace =~ search
end
display_klasses(false, false, klasses)
end
private
def self.banner(task, all = false, subcommand = false)
"thor " + task.formatted_usage(self, all, subcommand)
end
def thor_root
Thor::Util.thor_root
end
def thor_yaml
@thor_yaml ||= begin
yaml_file = File.join(thor_root, "thor.yml")
yaml = YAML.load_file(yaml_file) if File.exists?(yaml_file)
yaml || {}
end
end
# Save the yaml file. If none exists in thor root, creates one.
#
def save_yaml(yaml)
yaml_file = File.join(thor_root, "thor.yml")
unless File.exists?(yaml_file)
FileUtils.mkdir_p(thor_root)
yaml_file = File.join(thor_root, "thor.yml")
FileUtils.touch(yaml_file)
end
File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml }
end
def self.exit_on_failure?
true
end
# Load the thorfiles. If relevant_to is supplied, looks for specific files
# in the thor_root instead of loading them all.
#
# By default, it also traverses the current path until find Thor files, as
# described in thorfiles. This look up can be skipped by suppliying
# skip_lookup true.
#
def initialize_thorfiles(relevant_to=nil, skip_lookup=false)
thorfiles(relevant_to, skip_lookup).each do |f|
Thor::Util.load_thorfile(f, nil, options[:debug]) unless Thor::Base.subclass_files.keys.include?(File.expand_path(f))
end
end
# Finds Thorfiles by traversing from your current directory down to the root
# directory of your system. If at any time we find a Thor file, we stop.
#
# We also ensure that system-wide Thorfiles are loaded first, so local
# Thorfiles can override them.
#
# ==== Example
#
# If we start at /Users/wycats/dev/thor ...
#
# 1. /Users/wycats/dev/thor
# 2. /Users/wycats/dev
# 3. /Users/wycats <-- we find a Thorfile here, so we stop
#
# Suppose we start at c:\Documents and Settings\james\dev\thor ...
#
# 1. c:\Documents and Settings\james\dev\thor
# 2. c:\Documents and Settings\james\dev
# 3. c:\Documents and Settings\james
# 4. c:\Documents and Settings
# 5. c:\ <-- no Thorfiles found!
#
def thorfiles(relevant_to=nil, skip_lookup=false)
thorfiles = []
unless skip_lookup
Pathname.pwd.ascend do |path|
thorfiles = Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten
break unless thorfiles.empty?
end
end
files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Thor::Util.thor_root_glob)
files += thorfiles
files -= ["#{thor_root}/thor.yml"]
files.map! do |file|
File.directory?(file) ? File.join(file, "main.thor") : file
end
end
# Load thorfiles relevant to the given method. If you provide "foo:bar" it
# will load all thor files in the thor.yaml that has "foo" e "foo:bar"
# namespaces registered.
#
def thorfiles_relevant_to(meth)
lookup = [ meth, meth.split(":")[0...-1].join(":") ]
files = thor_yaml.select do |k, v|
v[:namespaces] && !(v[:namespaces] & lookup).empty?
end
files.map { |k, v| File.join(thor_root, "#{v[:filename]}") }
end
# Display information about the given klasses. If with_module is given,
# it shows a table with information extracted from the yaml file.
#
def display_klasses(with_modules=false, show_internal=false, klasses=Thor::Base.subclasses)
klasses -= [Thor, Thor::Runner, Thor::Group] unless show_internal
raise Error, "No Thor tasks available" if klasses.empty?
show_modules if with_modules && !thor_yaml.empty?
list = Hash.new { |h,k| h[k] = [] }
groups = klasses.select { |k| k.ancestors.include?(Thor::Group) }
# Get classes which inherit from Thor
(klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_tasks(false) }
# Get classes which inherit from Thor::Base
groups.map! { |k| k.printable_tasks(false).first }
list["root"] = groups
# Order namespaces with default coming first
list = list.sort{ |a,b| a[0].sub(/^default/, '') <=> b[0].sub(/^default/, '') }
list.each { |n, tasks| display_tasks(n, tasks) unless tasks.empty? }
end
def display_tasks(namespace, list) #:nodoc:
list.sort!{ |a,b| a[0] <=> b[0] }
say shell.set_color(namespace, :blue, true)
say "-" * namespace.size
print_table(list, :truncate => true)
say
end
def show_modules #:nodoc:
info = []
labels = ["Modules", "Namespaces"]
info << labels
info << [ "-" * labels[0].size, "-" * labels[1].size ]
thor_yaml.each do |name, hash|
info << [ name, hash[:namespaces].join(", ") ]
end
print_table info
say ""
end
end

View file

@ -11,6 +11,21 @@ class Thor
@base, @padding = nil, 0
end
# Mute everything that's inside given block
#
def mute
@mute = true
yield
ensure
@mute = false
end
# Check if base is muted
#
def mute?
@mute
end
# Sets the output padding, not allowing less than zero values.
#
def padding=(value)
@ -24,7 +39,7 @@ class Thor
#
def ask(statement, color=nil)
say("#{statement} ", color)
$stdin.gets.strip
stdin.gets.strip
end
# Say (print) something to the user. If the sentence ends with a whitespace
@ -41,11 +56,11 @@ class Thor
spaces = " " * padding
if force_new_line
$stdout.puts(spaces + message)
stdout.puts(spaces + message)
else
$stdout.print(spaces + message)
stdout.print(spaces + message)
end
$stdout.flush
stdout.flush
end
# Say a status with the given color and appends the message. Since this
@ -61,15 +76,15 @@ class Thor
status = status.to_s.rjust(12)
status = set_color status, color, true if color
$stdout.puts "#{status}#{spaces}#{message}"
$stdout.flush
stdout.puts "#{status}#{spaces}#{message}"
stdout.flush
end
# Make a question the to user and returns true if the user replies "y" or
# "yes".
#
def yes?(statement, color=nil)
ask(statement, color) =~ is?(:yes)
!!(ask(statement, color) =~ is?(:yes))
end
# Make a question the to user and returns true if the user replies "n" or
@ -113,7 +128,7 @@ class Thor
end
sentence = truncate(sentence, options[:truncate]) if options[:truncate]
$stdout.puts sentence
stdout.puts sentence
end
end
@ -139,9 +154,9 @@ class Thor
paras.each do |para|
para.split("\n").each do |line|
$stdout.puts line.insert(0, " " * ident)
stdout.puts line.insert(0, " " * ident)
end
$stdout.puts unless para == paras.last
stdout.puts unless para == paras.last
end
end
@ -180,12 +195,12 @@ class Thor
end
# Called if something goes wrong during the execution. This is used by Thor
# internally and should not be used inside your scripts. If someone went
# internally and should not be used inside your scripts. If something went
# wrong, you can always raise an exception. If you raise a Thor::Error, it
# will be rescued and wrapped in the method below.
#
def error(statement)
$stderr.puts statement
stderr.puts statement
end
# Apply color to the given string with optional bold. Disabled in the
@ -197,6 +212,18 @@ class Thor
protected
def stdout
$stdout
end
def stdin
$stdin
end
def stderr
$stderr
end
def is?(value) #:nodoc:
value = value.to_s
@ -229,7 +256,7 @@ HELP
end
def quiet? #:nodoc:
base && base.options[:quiet]
mute? || (base && base.options[:quiet])
end
# This code was copied from Rake, available under MIT-LICENSE

View file

@ -65,10 +65,9 @@ class Thor
@required_options ||= options.map{ |_, o| o.usage if o.required? }.compact.sort.join(" ")
end
# Given a target, checks if this class name is not a private/protected method.
# Given a target, checks if this class name is a public method.
def public_method?(instance) #:nodoc:
collection = instance.private_methods + instance.protected_methods
(collection & [name.to_s, name.to_sym]).empty?
!(instance.public_methods & [name.to_s, name.to_sym]).empty?
end
def sans_backtrace(backtrace, caller) #:nodoc:
@ -111,4 +110,4 @@ class Thor
end
end
end
end
end

View file

@ -8,11 +8,11 @@ class Thor
#
# 1) Methods to convert thor namespaces to constants and vice-versa.
#
# Thor::Utils.namespace_from_thor_class(Foo::Bar::Baz) #=> "foo:bar:baz"
# Thor::Util.namespace_from_thor_class(Foo::Bar::Baz) #=> "foo:bar:baz"
#
# 2) Loading thor files and sandboxing:
#
# Thor::Utils.load_thorfile("~/.thor/foo")
# Thor::Util.load_thorfile("~/.thor/foo")
#
module Util

View file

@ -0,0 +1,3 @@
class Thor
VERSION = "0.14.6".freeze
end

View file

@ -0,0 +1,7 @@
if defined?(Thor)
Bundler.ui.warn "Thor has already been required. " +
"This may cause Bundler to malfunction in unexpected ways."
end
$:.unshift File.expand_path('../vendor', __FILE__)
require 'thor'
require 'thor/actions'

View file

@ -2,5 +2,5 @@ module Bundler
# We're doing this because we might write tests that deal
# with other versions of bundler and we are unsure how to
# handle this better.
VERSION = "1.0.15" unless defined?(::Bundler::VERSION)
VERSION = "1.0.18" unless defined?(::Bundler::VERSION)
end

View file

@ -4,6 +4,8 @@
# include the vlad:bundle:install task in your vlad:deploy task.
require 'bundler/deployment'
include Rake::DSL if defined? Rake::DSL
namespace :vlad do
Bundler::Deployment.define_task(Rake::RemoteTask, :remote_task, :roles => :app)
end

Some files were not shown because too many files have changed in this diff Show more