Merge branch 'bzr/golem' of /Users/distler/Sites/code/instiki

This commit is contained in:
Jacques Distler 2011-08-19 19:37:14 -05:00
commit a739a52f54
163 changed files with 1216 additions and 255 deletions

View file

@ -72,8 +72,6 @@ module Engines
my_content.to_s5 my_content.to_s5
else else
(t = Time.now; nil) (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, html = Maruku.new(text,
{:math_enabled => true, {:math_enabled => true,
:math_numbered => ['\\[','\\begin{equation}']}).to_html :math_numbered => ['\\[','\\begin{equation}']}).to_html

View file

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

View file

@ -26,19 +26,27 @@ class NoWikiTest < Test::Unit::TestCase
def test_sanitize_nowiki 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!', 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 end
# Here, the input is not namespace-well-formed, but the output is.
# I think that's OK.
def test_sanitize_nowiki_ill_formed def test_sanitize_nowiki_ill_formed
match(NoWiki, "<nowiki><animateColor xlink:href='#foo'/></nowiki>", match(NoWiki, "<nowiki><animateColor xlink:href='#foo'/></nowiki>",
:plain_text => "&lt;animateColor xlink:href=&#39;#foo&#39;/&gt;" :plain_text => '<animateColor href="#foo"/>'
) )
end end
def test_sanitize_nowiki_ill_formed_II def test_sanitize_nowiki_ill_formed_II
match(NoWiki, "<nowiki><animateColor xlink:href='#foo'/>\000</nowiki>", 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 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) ## 1.0.15 (June 9, 2011)
Features: 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`) - 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 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 `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: If you are using Rails 2.3, please also include:

View file

@ -40,9 +40,9 @@ begin
end end
namespace :rubygems do namespace :rubygems do
# Rubygems 1.3.5, 1.3.6, and HEAD specs # Rubygems specs by version
rubyopt = ENV["RUBYOPT"] 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}" desc "Run specs with Rubygems #{rg}"
RSpec::Core::RakeTask.new(rg) do |t| RSpec::Core::RakeTask.new(rg) do |t|
t.rspec_opts = %w(-fs --color) 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")) raise "RVM is not available" unless File.exist?(File.expand_path("~/.rvm/scripts/rvm"))
end 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}") ruby_cmd = File.expand_path("~/.rvm/bin/ruby-#{ruby}")
desc "Run specs on 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 via `bundle install /path/to/bundle`. Bundler will remember
where you installed the dependencies to on a particular where you installed the dependencies to on a particular
machine for future installs, loads, setups, etc. machine for future installs, loads, setups, etc.
5. `bin_path`: Bundler no longer generates binaries in the root 5. `bin_path`: Bundler no longer generates executables in the root
of your app. You should use `bundle exec` to execute binaries of your app. You should use `bundle exec` to execute executables
in the current context. in the current context.
### Gemfile Changes ### Gemfile Changes

View file

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

View file

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

View file

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

View file

@ -22,7 +22,7 @@ module Bundler
def gemspec(opts = nil) def gemspec(opts = nil)
path = opts && opts[:path] || '.' path = opts && opts[:path] || '.'
name = opts && opts[:name] || '*' name = opts && opts[:name] || '{,*}'
development_group = opts && opts[:development_group] || :development development_group = opts && opts[:development_group] || :development
path = File.expand_path(path, Bundler.default_gemfile.dirname) path = File.expand_path(path, Bundler.default_gemfile.dirname)
gemspecs = Dir[File.join(path, "#{name}.gemspec")] gemspecs = Dir[File.join(path, "#{name}.gemspec")]
@ -34,7 +34,7 @@ module Bundler
gem spec.name, :path => path gem spec.name, :path => path
group(development_group) do group(development_group) do
spec.development_dependencies.each do |dep| spec.development_dependencies.each do |dep|
gem dep.name, *dep.requirement.as_list gem dep.name, *(dep.requirement.as_list + [:type => :development])
end end
end end
when 0 when 0
@ -57,20 +57,34 @@ module Bundler
dep = Dependency.new(name, version, options) 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 = @dependencies.find { |d| d.name == dep.name }
if current.requirement != dep.requirement if current.requirement != 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. " \ raise DslError, "You cannot specify the same gem twice with different version requirements. " \
"You specified: #{current.name} (#{current.requirement}) and " \ "You specified: #{current.name} (#{current.requirement}) and " \
"#{dep.name} (#{dep.requirement})" "#{dep.name} (#{dep.requirement})"
end end
end
if current.source != dep.source if current.source != 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 " \ raise DslError, "You cannot specify the same gem twice coming from different sources. You " \
"specified that #{dep.name} (#{dep.requirement}) should come from " \ "specified that #{dep.name} (#{dep.requirement}) should come from " \
"#{current.source || 'an unspecfied source'} and #{dep.source}" "#{current.source || 'an unspecfied source'} and #{dep.source}"
end end
end end
@dependencies << Dependency.new(name, version, options) end
@dependencies << dep
end end
def source(source, options = {}) def source(source, options = {})
@ -183,7 +197,7 @@ module Bundler
def _normalize_options(name, version, opts) def _normalize_options(name, version, opts)
_normalize_hash(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? if invalid_keys.any?
plural = invalid_keys.size > 1 plural = invalid_keys.size > 1
message = "You passed #{invalid_keys.map{|k| ':'+k }.join(", ")} " message = "You passed #{invalid_keys.map{|k| ':'+k }.join(", ")} "

View file

@ -16,7 +16,7 @@ module Bundler
def initialize(base, name = nil) def initialize(base, name = nil)
Bundler.ui = UI::Shell.new(Thor::Base.shell.new) Bundler.ui = UI::Shell.new(Thor::Base.shell.new)
@base = base @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 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 @spec_path = gemspecs.first
@gemspec = Bundler.load_gemspec(@spec_path) @gemspec = Bundler.load_gemspec(@spec_path)
@ -41,7 +41,7 @@ module Bundler
def build_gem def build_gem
file_name = nil file_name = nil
sh("gem build '#{spec_path}'") { |out, code| sh("gem build -V '#{spec_path}'") { |out, code|
raise out unless out[/Successfully/] raise out unless out[/Successfully/]
file_name = File.basename(built_gem_path) file_name = File.basename(built_gem_path)
FileUtils.mkdir_p(File.join(base, 'pkg')) FileUtils.mkdir_p(File.join(base, 'pkg'))
@ -70,9 +70,12 @@ module Bundler
protected protected
def rubygem_push(path) def rubygem_push(path)
out, _ = sh("gem push '#{path}'") if Pathname.new("~/.gem/credentials").expand_path.exist?
raise "Gem push failed due to lack of RubyGems.org credentials." if out[/Enter your RubyGems.org credentials/] sh("gem push '#{path}'")
Bundler.ui.confirm "Pushed #{name} #{version} to rubygems.org" 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 end
def built_gem_path def built_gem_path

View file

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

View file

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

View file

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

View file

@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.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" .SH "NAME"
\fBbundle\-install\fR \- Install the dependencies specified in your Gemfile \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile
@ -17,6 +17,7 @@
[\-\-without=GROUP1[ GROUP2\.\.\.]] [\-\-without=GROUP1[ GROUP2\.\.\.]]
[\-\-local] [\-\-deployment] [\-\-local] [\-\-deployment]
[\-\-binstubs[=DIRECTORY]] [\-\-binstubs[=DIRECTORY]]
[\-\-standalone[=GROUP1[ GROUP2\.\.\.]]]
[\-\-quiet] [\-\-quiet]
. .
.fi .fi
@ -62,6 +63,10 @@ Switches bundler\'s defaults into \fIdeployment mode\fR\. Do not use this flag o
\fB\-\-binstubs[=<directory>]\fR \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\. 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" .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\. 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...]] [--without=GROUP1[ GROUP2...]]
[--local] [--deployment] [--local] [--deployment]
[--binstubs[=DIRECTORY]] [--binstubs[=DIRECTORY]]
[--standalone[=GROUP1[ GROUP2...]]]
[--quiet] [--quiet]
@ -74,6 +75,13 @@ OPTIONS
bin/rails executable that ensures that all dependencies used bin/rails executable that ensures that all dependencies used
come from the bundled gems. 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 DEPLOYMENT MODE
Bundler's defaults are optimized for development. To switch to defaults Bundler's defaults are optimized for development. To switch to defaults
optimized for deployment, use the --deployment flag. Do not activate 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) bundle show(1)
Show the source location of a particular gem in the bundle 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) bundle console(1)
Start an IRB session in the context of the current bundle Start an IRB session in the context of the current bundle
@ -61,7 +64,11 @@ UTILITIES
Open an installed gem in the editor Open an installed gem in the editor
bundle viz(1) bundle viz(1)
Generate a visual representation of your dependencies Generate a visual representation of your dependencies <<<<<<<
HEAD
bundle benchmark(1):
bundle init(1) bundle init(1)
Generate a simple Gemfile, placed in the current directory 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 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3
. .
.TH "GEMFILE" "5" "May 2011" "" "" .TH "GEMFILE" "5" "July 2011" "" ""
. .
.SH "NAME" .SH "NAME"
\fBGemfile\fR \- A format for describing gem dependencies for Ruby programs \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 def debug
if ENV['DEBUG_RESOLVER'] if ENV['DEBUG_RESOLVER']
debug_info = yield 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 $stderr.puts debug_info
end end
end end
@ -279,14 +279,6 @@ module Bundler
end end
raise GemNotFound, message raise GemNotFound, message
else 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] @errors[current.name] = [nil, current]
end end
end end
@ -356,7 +348,8 @@ module Bundler
def search(dep) def search(dep)
if base = @base[dep.name] and base.any? 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 else
d = dep.dep d = dep.dep
end 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 # the rest of the time, the gem cannot be found because it does not exist in the known sources
else else
if requirement.required_by.first 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 else
o << "Could not find gem '#{clean_req(requirement)} in any of the sources\n" o << "Could not find gem '#{clean_req(requirement)} in any of the sources\n"
end end

View file

@ -157,6 +157,7 @@ module Gem
alias eql? == alias eql? ==
end end
end end
module Bundler module Bundler

View file

@ -123,7 +123,7 @@ module Bundler
if executables.include? File.basename(caller.first.split(':').first) if executables.include? File.basename(caller.first.split(':').first)
return return
end 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) unless dep.respond_to?(:name) && dep.respond_to?(:requirement)
dep = Gem::Dependency.new(dep, reqs) dep = Gem::Dependency.new(dep, reqs)
@ -250,7 +250,17 @@ module Bundler
Gem.clear_paths Gem.clear_paths
end 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 class Legacy < RubygemsIntegration
def stub_rubygems(specs) def stub_rubygems(specs)
stub_source_index137(specs) stub_source_index137(specs)
@ -265,6 +275,14 @@ module Bundler
end end
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 # Rubygems 1.7
class Transitional < Legacy class Transitional < Legacy
def stub_rubygems(specs) def stub_rubygems(specs)
@ -313,8 +331,10 @@ module Bundler
@rubygems = RubygemsIntegration::AlmostModern.new @rubygems = RubygemsIntegration::AlmostModern.new
elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.7.0') elsif Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.7.0')
@rubygems = RubygemsIntegration::Transitional.new @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 @rubygems = RubygemsIntegration::Legacy.new
else # Rubygems 1.3.6 and 1.3.7
@rubygems = RubygemsIntegration::Ancient.new
end end
class << self class << self

View file

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

View file

@ -1,21 +1,18 @@
require 'tsort' require 'tsort'
require 'forwardable'
module Bundler module Bundler
class SpecSet class SpecSet
extend Forwardable
include TSort, Enumerable include TSort, Enumerable
def_delegators :@specs, :<<, :length, :add, :remove
def_delegators :sorted, :each
def initialize(specs) def initialize(specs)
@specs = specs.sort_by { |s| s.name } @specs = specs.sort_by { |s| s.name }
end end
def each
sorted.each { |s| yield s }
end
def length
@specs.length
end
def for(dependencies, skip = [], check = false, match_current_platform = false) def for(dependencies, skip = [], check = false, match_current_platform = false)
handled, deps, specs = {}, dependencies.dup, [] handled, deps, specs = {}, dependencies.dup, []
skip << 'bundler' skip << 'bundler'
@ -68,6 +65,10 @@ module Bundler
value value
end end
def sort!
self
end
def to_a def to_a
sorted.dup sorted.dup
end 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.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"] s.require_paths = ["lib"]
# specify any dependencies here; for example:
# s.add_development_dependency "rspec"
# s.add_runtime_dependency "rest-client"
end end

View file

@ -18,6 +18,23 @@ class Thor
end end
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. # Defines the usage and the description of the next task.
# #
# ==== Parameters # ==== Parameters
@ -252,8 +269,7 @@ class Thor
# the namespace should be displayed as arguments. # the namespace should be displayed as arguments.
# #
def banner(task, namespace = nil, subcommand = false) def banner(task, namespace = nil, subcommand = false)
base = File.basename($0).split(" ").first "#{basename} #{task.formatted_usage(self, $thor_runner, subcommand)}"
"#{base} #{task.formatted_usage(self, $thor_runner, subcommand)}"
end end
def baseclass #:nodoc: def baseclass #:nodoc:
@ -295,17 +311,40 @@ class Thor
# Receives a task name (can be nil), and try to get a map from it. # 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. # If a map can't be found use the sent name or the default task.
def normalize_task_name(meth) #:nodoc: 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 meth.to_s.gsub('-','_') # treat foo-bar > foo_bar
end 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) def subcommand_help(cmd)
desc "help [COMMAND]", "Describe subcommands or one specific subcommand" desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
class_eval <<-RUBY class_eval <<-RUBY
def help(task = nil, subcommand = true); super; end def help(task = nil, subcommand = true); super; end
RUBY RUBY
end end
end end
include Thor::Base include Thor::Base

View file

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

View file

@ -18,7 +18,7 @@ class Thor
# "vhost.name = #{hostname}" # "vhost.name = #{hostname}"
# end # end
# #
# create_file "config/apach.conf", "your apache config" # create_file "config/apache.conf", "your apache config"
# #
def create_file(destination, *args, &block) def create_file(destination, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {} config = args.last.is_a?(Hash) ? args.pop : {}
@ -27,7 +27,7 @@ class Thor
end end
alias :add_file :create_file 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. # ERB, it gets the content from the user.
# #
class CreateFile < EmptyDirectory #:nodoc: 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" # directory "doc"
# #
# It will create a doc directory in the destination with the following # 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/ # doc/
# components/ # components/
@ -70,7 +70,7 @@ class Thor
lookup = config[:recursive] ? File.join(source, '**') : source lookup = config[:recursive] ? File.join(source, '**') : source
lookup = File.join(lookup, '{*,.[a-z]*}') 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) next if File.directory?(file_source)
file_destination = File.join(given_destination, file_source.gsub(source, '.')) file_destination = File.join(given_destination, file_source.gsub(source, '.'))
file_destination.gsub!('/./', '/') file_destination.gsub!('/./', '/')

View file

@ -30,6 +30,28 @@ class Thor
end end
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 # 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 # destination. If a block is given instead of destination, the content of
# the url is yielded and used as location. # the url is yielded and used as location.
@ -51,7 +73,7 @@ class Thor
config = args.last.is_a?(Hash) ? args.pop : {} config = args.last.is_a?(Hash) ? args.pop : {}
destination = args.first 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 } render = open(source) {|input| input.binmode.read }
destination ||= if block_given? destination ||= if block_given?
@ -80,13 +102,13 @@ class Thor
# #
def template(source, *args, &block) def template(source, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {} 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)) source = File.expand_path(find_in_source_paths(source.to_s))
context = instance_eval('binding') context = instance_eval('binding')
create_file destination, nil, config do 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 = block.call(content) if block
content content
end end
@ -110,7 +132,7 @@ class Thor
FileUtils.chmod_R(mode, path) unless options[:pretend] FileUtils.chmod_R(mode, path) unless options[:pretend]
end 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 # ==== Parameters
# path<String>:: path of the file to be changed # path<String>:: path of the file to be changed
@ -119,19 +141,20 @@ class Thor
# #
# ==== Example # ==== 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"' # 'config.gem "rspec"'
# end # end
# #
def prepend_file(path, *args, &block) def prepend_to_file(path, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {} config = args.last.is_a?(Hash) ? args.pop : {}
config.merge!(:after => /\A/) config.merge!(:after => /\A/)
inject_into_file(path, *(args << config), &block) insert_into_file(path, *(args << config), &block)
end 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 # ==== Parameters
# path<String>:: path of the file to be changed # path<String>:: path of the file to be changed
@ -140,20 +163,21 @@ class Thor
# #
# ==== Example # ==== 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"' # 'config.gem "rspec"'
# end # end
# #
def append_file(path, *args, &block) def append_to_file(path, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {} config = args.last.is_a?(Hash) ? args.pop : {}
config.merge!(:before => /\z/) config.merge!(:before => /\z/)
inject_into_file(path, *(args << config), &block) insert_into_file(path, *(args << config), &block)
end end
alias_method :append_file, :append_to_file
# Injects text right after the class definition. Since it depends on # Injects text right after the class definition. Since it depends on
# inject_into_file, it's reversible. # insert_into_file, it's reversible.
# #
# ==== Parameters # ==== Parameters
# path<String>:: path of the file to be changed # path<String>:: path of the file to be changed
@ -172,7 +196,7 @@ class Thor
def inject_into_class(path, klass, *args, &block) def inject_into_class(path, klass, *args, &block)
config = args.last.is_a?(Hash) ? args.pop : {} config = args.last.is_a?(Hash) ? args.pop : {}
config.merge!(:after => /class #{klass}\n|class #{klass} .*\n/) config.merge!(:after => /class #{klass}\n|class #{klass} .*\n/)
inject_into_file(path, *(args << config), &block) insert_into_file(path, *(args << config), &block)
end end
# Run a regular expression replacement on a file. # Run a regular expression replacement on a file.
@ -225,5 +249,22 @@ class Thor
end end
alias :remove_dir :remove_file 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
end end

View file

@ -15,14 +15,14 @@ class Thor
# #
# ==== Examples # ==== 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 = ask "Which gems would you like to add?"
# gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n") # gems.split(" ").map{ |gem| " config.gem :#{gem}" }.join("\n")
# end # end
# #
def inject_into_file(destination, *args, &block) def insert_into_file(destination, *args, &block)
if block_given? if block_given?
data, config = block, args.shift data, config = block, args.shift
else else
@ -30,6 +30,7 @@ class Thor
end end
action InjectIntoFile.new(self, destination, data, config) action InjectIntoFile.new(self, destination, data, config)
end end
alias_method :inject_into_file, :insert_into_file
class InjectIntoFile < EmptyDirectory #:nodoc: class InjectIntoFile < EmptyDirectory #:nodoc:
attr_reader :replacement, :flag, :behavior attr_reader :replacement, :flag, :behavior
@ -76,12 +77,16 @@ class Thor
protected protected
def say_status(behavior) def say_status(behavior)
status = if flag == /\A/ status = if behavior == :invoke
behavior == :invoke ? :prepend : :unprepend if flag == /\A/
:prepend
elsif flag == /\z/ elsif flag == /\z/
behavior == :invoke ? :append : :unappend :append
else else
behavior == :invoke ? :inject : :deinject :insert
end
else
:subtract
end end
super(status, config[:verbose]) super(status, config[:verbose])

View file

@ -94,8 +94,6 @@ class Thor
end end
module ClassMethods module ClassMethods
attr_accessor :debugging
def attr_reader(*) #:nodoc: def attr_reader(*) #:nodoc:
no_tasks { super } no_tasks { super }
end end
@ -384,14 +382,29 @@ class Thor
# script.invoke(:task, first_arg, second_arg, third_arg) # script.invoke(:task, first_arg, second_arg, third_arg)
# #
def start(given_args=ARGV, config={}) def start(given_args=ARGV, config={})
self.debugging = given_args.delete("--debug")
config[:shell] ||= Thor::Base.shell.new config[:shell] ||= Thor::Base.shell.new
dispatch(nil, given_args.dup, nil, config) dispatch(nil, given_args.dup, nil, config)
rescue Thor::Error => e 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? exit(1) if exit_on_failure?
end 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: def handle_no_task_error(task) #:nodoc:
if $thor_runner if $thor_runner
raise UndefinedTaskError, "Could not find task #{task.inspect} in #{namespace.inspect} namespace." raise UndefinedTaskError, "Could not find task #{task.inspect} in #{namespace.inspect} namespace."
@ -531,6 +544,13 @@ class Thor
false false
end 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 # SIGNATURE: Sets the baseclass. This is where the superclass lookup
# finishes. # finishes.
def baseclass #:nodoc: 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 @switches = arguments
arguments.each do |argument| arguments.each do |argument|
if argument.default if argument.default != nil
@assigns[argument.human_name] = argument.default @assigns[argument.human_name] = argument.default
elsif argument.required? elsif argument.required?
@non_assigned_required << argument @non_assigned_required << argument
@ -94,7 +94,7 @@ class Thor
hash = {} hash = {}
while current_is_value? && peek.include?(?:) while current_is_value? && peek.include?(?:)
key, value = shift.split(':') key, value = shift.split(':',2)
hash[key] = value hash[key] = value
end end
hash hash

View file

@ -53,7 +53,9 @@ class Thor
@pile = args.dup @pile = args.dup
while peek while peek
if current_is_switch? match, is_switch = current_is_switch?
if is_switch
case shift case shift
when SHORT_SQ_RE when SHORT_SQ_RE
unshift($1.split('').map { |f| "-#{f}" }) unshift($1.split('').map { |f| "-#{f}" })
@ -68,7 +70,7 @@ class Thor
switch = normalize_switch(switch) switch = normalize_switch(switch)
option = switch_option(switch) option = switch_option(switch)
@assigns[option.human_name] = parse_peek(switch, option) @assigns[option.human_name] = parse_peek(switch, option)
elsif current_is_switch_formatted? elsif match
@unknown << shift @unknown << shift
else else
shift shift
@ -83,8 +85,10 @@ class Thor
end end
def check_unknown! def check_unknown!
unless ARGV.include?("exec") || ARGV.include?("config")
raise UnknownArgumentError, "Unknown switches '#{@unknown.join(', ')}'" unless @unknown.empty? raise UnknownArgumentError, "Unknown switches '#{@unknown.join(', ')}'" unless @unknown.empty?
end end
end
protected protected
@ -93,14 +97,16 @@ class Thor
def current_is_switch? def current_is_switch?
case peek case peek
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
switch?($1) [true, switch?($1)]
when SHORT_SQ_RE when SHORT_SQ_RE
$1.split('').any? { |f| switch?("-#{f}") } [true, $1.split('').any? { |f| switch?("-#{f}") }]
else
[false, false]
end end
end end
def switch_formatted?(arg) def current_is_switch_formatted?
case arg case peek
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
true true
else else
@ -108,12 +114,8 @@ class Thor
end end
end end
def current_is_switch_formatted?
switch_formatted? peek
end
def switch?(arg) def switch?(arg)
switch_option(arg) || @shorts.key?(arg) switch_option(normalize_switch(arg))
end end
def switch_option(arg) def switch_option(arg)
@ -127,7 +129,7 @@ class Thor
# Check if the given argument is actually a shortcut. # Check if the given argument is actually a shortcut.
# #
def normalize_switch(arg) def normalize_switch(arg)
@shorts.key?(arg) ? @shorts[arg] : arg (@shorts[arg] || arg).tr('_', '-')
end end
# Parse boolean values which can be given as --foo=true, --foo or --no-foo. # 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) @non_assigned_required.delete(option)
send(:"parse_#{option.type}", switch) send(:"parse_#{option.type}", switch)
end end
end 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 @base, @padding = nil, 0
end 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. # Sets the output padding, not allowing less than zero values.
# #
def padding=(value) def padding=(value)
@ -24,7 +39,7 @@ class Thor
# #
def ask(statement, color=nil) def ask(statement, color=nil)
say("#{statement} ", color) say("#{statement} ", color)
$stdin.gets.strip stdin.gets.strip
end end
# Say (print) something to the user. If the sentence ends with a whitespace # Say (print) something to the user. If the sentence ends with a whitespace
@ -41,11 +56,11 @@ class Thor
spaces = " " * padding spaces = " " * padding
if force_new_line if force_new_line
$stdout.puts(spaces + message) stdout.puts(spaces + message)
else else
$stdout.print(spaces + message) stdout.print(spaces + message)
end end
$stdout.flush stdout.flush
end end
# Say a status with the given color and appends the message. Since this # 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 = status.to_s.rjust(12)
status = set_color status, color, true if color status = set_color status, color, true if color
$stdout.puts "#{status}#{spaces}#{message}" stdout.puts "#{status}#{spaces}#{message}"
$stdout.flush stdout.flush
end end
# Make a question the to user and returns true if the user replies "y" or # Make a question the to user and returns true if the user replies "y" or
# "yes". # "yes".
# #
def yes?(statement, color=nil) def yes?(statement, color=nil)
ask(statement, color) =~ is?(:yes) !!(ask(statement, color) =~ is?(:yes))
end end
# Make a question the to user and returns true if the user replies "n" or # Make a question the to user and returns true if the user replies "n" or
@ -113,7 +128,7 @@ class Thor
end end
sentence = truncate(sentence, options[:truncate]) if options[:truncate] sentence = truncate(sentence, options[:truncate]) if options[:truncate]
$stdout.puts sentence stdout.puts sentence
end end
end end
@ -139,9 +154,9 @@ class Thor
paras.each do |para| paras.each do |para|
para.split("\n").each do |line| para.split("\n").each do |line|
$stdout.puts line.insert(0, " " * ident) stdout.puts line.insert(0, " " * ident)
end end
$stdout.puts unless para == paras.last stdout.puts unless para == paras.last
end end
end end
@ -180,12 +195,12 @@ class Thor
end end
# Called if something goes wrong during the execution. This is used by Thor # 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 # wrong, you can always raise an exception. If you raise a Thor::Error, it
# will be rescued and wrapped in the method below. # will be rescued and wrapped in the method below.
# #
def error(statement) def error(statement)
$stderr.puts statement stderr.puts statement
end end
# Apply color to the given string with optional bold. Disabled in the # Apply color to the given string with optional bold. Disabled in the
@ -197,6 +212,18 @@ class Thor
protected protected
def stdout
$stdout
end
def stdin
$stdin
end
def stderr
$stderr
end
def is?(value) #:nodoc: def is?(value) #:nodoc:
value = value.to_s value = value.to_s
@ -229,7 +256,7 @@ HELP
end end
def quiet? #:nodoc: def quiet? #:nodoc:
base && base.options[:quiet] mute? || (base && base.options[:quiet])
end end
# This code was copied from Rake, available under MIT-LICENSE # 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(" ") @required_options ||= options.map{ |_, o| o.usage if o.required? }.compact.sort.join(" ")
end 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: def public_method?(instance) #:nodoc:
collection = instance.private_methods + instance.protected_methods !(instance.public_methods & [name.to_s, name.to_sym]).empty?
(collection & [name.to_s, name.to_sym]).empty?
end end
def sans_backtrace(backtrace, caller) #:nodoc: def sans_backtrace(backtrace, caller) #:nodoc:

View file

@ -8,11 +8,11 @@ class Thor
# #
# 1) Methods to convert thor namespaces to constants and vice-versa. # 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: # 2) Loading thor files and sandboxing:
# #
# Thor::Utils.load_thorfile("~/.thor/foo") # Thor::Util.load_thorfile("~/.thor/foo")
# #
module Util 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 # We're doing this because we might write tests that deal
# with other versions of bundler and we are unsure how to # with other versions of bundler and we are unsure how to
# handle this better. # handle this better.
VERSION = "1.0.15" unless defined?(::Bundler::VERSION) VERSION = "1.0.18" unless defined?(::Bundler::VERSION)
end end

View file

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

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