提交 f32c3709 编写于 作者: E Emilio Tagua

Merge commit 'rails/master'

module ActionView #:nodoc:
module Helpers #:nodoc:
autoload :ActiveModelHelper, 'action_view/helpers/active_model_helper'
autoload :AjaxHelper, 'action_view/helpers/ajax_helper'
autoload :AssetTagHelper, 'action_view/helpers/asset_tag_helper'
autoload :AtomFeedHelper, 'action_view/helpers/atom_feed_helper'
autoload :BenchmarkHelper, 'action_view/helpers/benchmark_helper'
......
module ActionView
module Helpers
module AjaxHelper
include UrlHelper
def link_to_remote(name, url, options = {})
html = options.delete(:html) || {}
update = options.delete(:update)
if update.is_a?(Hash)
html["data-update-success"] = update[:success]
html["data-update-failure"] = update[:failure]
else
html["data-update-success"] = update
end
html["data-update-position"] = options.delete(:position)
html["data-method"] = options.delete(:method)
html["data-remote"] = "true"
html.merge!(options)
url = url_for(url) if url.is_a?(Hash)
link_to(name, url, html)
end
def button_to_remote(name, options = {}, html_options = {})
url = options.delete(:url)
url = url_for(url) if url.is_a?(Hash)
html_options.merge!(:type => "button", :value => name,
:"data-url" => url)
tag(:input, html_options)
end
module Rails2Compatibility
def set_callbacks(options, html)
[:complete, :failure, :success, :interactive, :loaded, :loading].each do |type|
html["data-#{type}-code"] = options.delete(type.to_sym)
end
options.each do |option, value|
if option.is_a?(Integer)
html["data-#{option}-code"] = options.delete(option)
end
end
end
def link_to_remote(name, url, options = nil)
if !options && url.is_a?(Hash) && url.key?(:url)
url, options = url.delete(:url), url
end
set_callbacks(options, options[:html] ||= {})
super
end
def button_to_remote(name, options = {}, html_options = {})
set_callbacks(options, html_options)
super
end
end
end
end
end
\ No newline at end of file
......@@ -126,7 +126,8 @@ def render_with_record_collection
end
class Game < Struct.new(:name, :id)
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
def to_param
id.to_s
end
......
require 'abstract_unit'
class Comment
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
def save; @id = 1 end
......
......@@ -4,7 +4,8 @@ class WorkshopsController < ActionController::Base
end
class Workshop
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_accessor :id, :new_record
def initialize(id, new_record)
......
require "abstract_unit"
class AjaxTestCase < ActiveSupport::TestCase
include ActionView::Helpers::AjaxHelper
include ActionView::Helpers::TagHelper
def assert_html(html, matches)
matches.each do |match|
assert_match Regexp.new(Regexp.escape(match)), html
end
end
def self.assert_callbacks_work(&blk)
define_method(:assert_callbacks_work, &blk)
[:complete, :failure, :success, :interactive, :loaded, :loading, 404].each do |callback|
test "#{callback} callback" do
markup = assert_callbacks_work(callback)
assert_html markup, %W(data-#{callback}-code="undoRequestCompleted\(request\)")
end
end
end
end
class LinkToRemoteTest < AjaxTestCase
def url_for(hash)
"/blog/destroy/4"
end
def link(options = {})
link_to_remote("Delete this post", "/blog/destroy/3", options)
end
test "with no update" do
assert_html link, %w(href="/blog/destroy/3" Delete\ this\ post data-remote="true")
end
test "basic" do
assert_html link(:update => "#posts"),
%w(data-update-success="#posts")
end
test "using a url hash" do
link = link_to_remote("Delete this post", {:controller => :blog}, :update => "#posts")
assert_html link, %w(href="/blog/destroy/4" data-update-success="#posts")
end
test "with :html options" do
expected = %{<a href="/blog/destroy/3" data-custom="me" data-update-success="#posts">Delete this post</a>}
assert_equal expected, link(:update => "#posts", :html => {"data-custom" => "me"})
end
test "with a hash for :update" do
link = link(:update => {:success => "#posts", :failure => "#error"})
assert_match /data-update-success="#posts"/, link
assert_match /data-update-failure="#error"/, link
end
test "with positional parameters" do
link = link(:position => :top, :update => "#posts")
assert_match /data\-update\-position="top"/, link
end
test "with an optional method" do
link = link(:method => "delete")
assert_match /data-method="delete"/, link
end
class LegacyLinkToRemoteTest < AjaxTestCase
include ActionView::Helpers::AjaxHelper::Rails2Compatibility
def link(options)
link_to_remote("Delete this post", "/blog/destroy/3", options)
end
test "basic link_to_remote with :url =>" do
expected = %{<a href="/blog/destroy/3" data-update-success="#posts">Delete this post</a>}
assert_equal expected,
link_to_remote("Delete this post", :url => "/blog/destroy/3", :update => "#posts")
end
assert_callbacks_work do |callback|
link(callback => "undoRequestCompleted(request)")
end
end
end
class ButtonToRemoteTest < AjaxTestCase
def button(options, html = {})
button_to_remote("Remote outpost", options, html)
end
def url_for(*)
"/whatnot"
end
class StandardTest < ButtonToRemoteTest
test "basic" do
button = button({:url => {:action => "whatnot"}}, {:class => "fine"})
[/input/, /class="fine"/, /type="button"/, /value="Remote outpost"/,
/data-url="\/whatnot"/].each do |match|
assert_match match, button
end
end
end
class LegacyButtonToRemoteTest < ButtonToRemoteTest
include ActionView::Helpers::AjaxHelper::Rails2Compatibility
assert_callbacks_work do |callback|
button(callback => "undoRequestCompleted(request)")
end
end
end
\ No newline at end of file
require "active_model"
class Customer < Struct.new(:name, :id)
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
def to_param
id.to_s
......@@ -16,7 +17,8 @@ class GoodCustomer < Customer
module Quiz
class Question < Struct.new(:name, :id)
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
def to_param
id.to_s
......
......@@ -5,15 +5,18 @@ class ActiveRecordHelperTest < ActionView::TestCase
silence_warnings do
class Post < Struct.new(:title, :author_name, :body, :secret, :written_on)
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
end
class User < Struct.new(:email)
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
end
class Column < Struct.new(:type, :name, :human_name)
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
end
end
......
require 'abstract_unit'
class Scroll < Struct.new(:id, :to_param, :title, :body, :updated_at, :created_at)
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
def new_record?
true
......
......@@ -2,7 +2,8 @@
silence_warnings do
class Post < Struct.new(:title, :author_name, :body, :secret, :written_on, :cost)
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
alias_method :secret?, :secret
......@@ -25,7 +26,8 @@ def tags_attributes=(attributes); end
end
class Comment
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :post_id
......@@ -43,7 +45,8 @@ def relevances_attributes=(attributes); end
end
class Tag
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :post_id
......@@ -61,7 +64,8 @@ def relevances_attributes=(attributes); end
end
class CommentRelevance
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :comment_id
......@@ -75,7 +79,8 @@ def value
end
class TagRelevance
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :tag_id
......
require 'abstract_unit'
require 'active_model'
Bunny = Struct.new(:Bunny, :id)
Bunny.extend ActiveModel::APICompliant
class Bunny < Struct.new(:Bunny, :id)
extend ActiveModel::Naming
include ActiveModel::Conversion
end
class Author
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
def save; @id = 1 end
......@@ -16,7 +19,8 @@ def name
end
class Article
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_reader :id
attr_reader :author_id
def save; @id = 1; @author_id = 1 end
......
require 'abstract_unit'
class Post
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
def id
45
end
......
......@@ -41,7 +41,7 @@ def test_homepage_url
def test_link_to_person
person = mock(:name => "David")
person.class.extend ActiveModel::APICompliant
person.class.extend ActiveModel::Naming
expects(:mocha_mock_path).with(person).returns("/people/1")
assert_equal '<a href="/people/1">David</a>', link_to_person(person)
end
......
......@@ -494,7 +494,8 @@ def with_restful_routing
end
class Workshop
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_accessor :id, :new_record
def initialize(id, new_record)
......@@ -511,7 +512,8 @@ def to_s
end
class Session
extend ActiveModel::APICompliant
extend ActiveModel::Naming
include ActiveModel::Conversion
attr_accessor :id, :workshop_id, :new_record
def initialize(id, new_record)
......
$:.push "activesupport/lib"
$:.push "activemodel/lib"
require "active_model/validations"
require "active_model/deprecated_error_methods"
require "active_model/errors"
require "active_model/naming"
require 'activemodel'
class Person
include ActiveModel::Conversion
include ActiveModel::Validations
extend ActiveModel::Naming
validates_presence_of :name
attr_accessor :name
def initialize(attributes = {})
@name = attributes[:name]
end
def persist
@persisted = true
end
def new_record?
@persisted
end
def to_model() self end
end
person1 = Person.new
......@@ -33,4 +26,4 @@ def to_model() self end
person1.errors
person2 = Person.new(:name => "matz")
p person2.valid?
\ No newline at end of file
p person2.valid?
......@@ -26,9 +26,7 @@
require 'active_support'
module ActiveModel
autoload :APICompliant, 'active_model/api_compliant'
autoload :Attributes, 'active_model/attributes'
autoload :Base, 'active_model/base'
autoload :Conversion, 'active_model/conversion'
autoload :DeprecatedErrorMethods, 'active_model/deprecated_error_methods'
autoload :Errors, 'active_model/errors'
autoload :Name, 'active_model/naming'
......
module ActiveModel
module APICompliant
include Naming
def self.extended(klass)
klass.class_eval do
include Validations
include InstanceMethods
end
end
module InstanceMethods
def to_model
if respond_to?(:new_record?)
self.class.class_eval { def to_model() self end }
to_model
else
raise "In order to be ActiveModel API compliant, you need to define " \
"a new_record? method, which should return true if it has not " \
"yet been persisted."
end
end
end
end
end
\ No newline at end of file
require 'active_support/core_ext/object/instance_variables'
module ActiveModel
module Attributes
def self.append_features(base)
unless base.instance_methods.include?('attributes')
super
else
false
end
end
def attributes
instance_values
end
def read_attribute(attr_name)
instance_variable_get(:"@#{attr_name}")
end
def write_attribute(attr_name, value)
instance_variable_set(:"@#{attr_name}", value)
end
end
end
module ActiveModel
class Base
include Observing
# disabled, until they're tested
# include Callbacks
# include Validations
end
end
\ No newline at end of file
module ActiveModel
# Include ActiveModel::Conversion if your object "acts like an ActiveModel model".
module Conversion
def to_model
self
end
end
end
require 'observer'
require 'singleton'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/string/inflections'
module ActiveModel
module Observing
......@@ -39,8 +40,25 @@ def instantiate_observers
observers.each { |o| instantiate_observer(o) }
end
# Wraps methods with before and after notifications.
#
# wrap_with_notifications :create, :save, :update, :destroy
def wrap_with_notifications(*methods)
methods.each do |method|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{method}_with_notifications(*args, &block)
notify_observers(:before_#{method})
result = #{method}_without_notifications(*args, &block)
notify_observers(:after_#{method})
result
end
EOS
alias_method_chain(method, :notifications)
end
end
protected
def instantiate_observer(observer)
def instantiate_observer(observer) #:nodoc:
# string/symbol
if observer.respond_to?(:to_sym)
observer = observer.to_s.camelize.constantize.instance
......@@ -60,12 +78,72 @@ def inherited(subclass)
end
private
def notify(method) #:nodoc:
# Fires notifications to model's observers
#
# def save
# notify_observers(:before_save)
# ...
# notify_observers(:after_save)
# end
def notify_observers(method)
self.class.changed
self.class.notify_observers(method, self)
end
end
# Observer classes respond to lifecycle callbacks to implement trigger-like
# behavior outside the original class. This is a great way to reduce the
# clutter that normally comes when the model class is burdened with
# functionality that doesn't pertain to the core responsibility of the
# class. Example:
#
# class CommentObserver < ActiveModel::Observer
# def after_save(comment)
# Notifications.deliver_comment("admin@do.com", "New comment was posted", comment)
# end
# end
#
# This Observer sends an email when a Comment#save is finished.
#
# class ContactObserver < ActiveModel::Observer
# def after_create(contact)
# contact.logger.info('New contact added!')
# end
#
# def after_destroy(contact)
# contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
# end
# end
#
# This Observer uses logger to log when specific callbacks are triggered.
#
# == Observing a class that can't be inferred
#
# Observers will by default be mapped to the class with which they share a name. So CommentObserver will
# be tied to observing Comment, ProductManagerObserver to ProductManager, and so on. If you want to name your observer
# differently than the class you're interested in observing, you can use the Observer.observe class method which takes
# either the concrete class (Product) or a symbol for that class (:product):
#
# class AuditObserver < ActiveModel::Observer
# observe :account
#
# def after_update(account)
# AuditTrail.new(account, "UPDATED")
# end
# end
#
# If the audit observer needs to watch more than one kind of object, this can be specified with multiple arguments:
#
# class AuditObserver < ActiveModel::Observer
# observe :account, :balance
#
# def after_update(record)
# AuditTrail.new(record, "UPDATED")
# end
# end
#
# The AuditObserver will now act on both updates to Account and Balance by treating them both as records.
#
class Observer
include Singleton
......@@ -77,6 +155,15 @@ def observe(*models)
define_method(:observed_classes) { models }
end
# Returns an array of Classes to observe.
#
# You can override this instead of using the +observe+ helper.
#
# class AuditObserver < ActiveModel::Observer
# def self.observed_classes
# [AccountObserver, BalanceObserver]
# end
# end
def observed_classes
Array.wrap(observed_class)
end
......@@ -97,7 +184,7 @@ def initialize
observed_classes.each { |klass| add_observer!(klass) }
end
def observed_classes
def observed_classes #:nodoc:
self.class.observed_classes
end
......@@ -114,7 +201,7 @@ def observed_class_inherited(subclass) #:nodoc:
end
protected
def add_observer!(klass)
def add_observer!(klass) #:nodoc:
klass.add_observer(self)
end
end
......
......@@ -5,7 +5,6 @@ module ActiveModel
module Serializers
module JSON
extend ActiveSupport::Concern
include ActiveModel::Attributes
included do
extend ActiveModel::Naming
......
......@@ -5,7 +5,6 @@ module ActiveModel
module Serializers
module Xml
extend ActiveSupport::Concern
include ActiveModel::Attributes
class Serializer < ActiveModel::Serializer #:nodoc:
class Attribute #:nodoc:
......
require 'cases/helper'
class AttributesTest < ActiveModel::TestCase
class Person
include ActiveModel::Attributes
attr_accessor :name
end
test "reads attribute" do
p = Person.new
assert_equal nil, p.read_attribute(:name)
p.name = "Josh"
assert_equal "Josh", p.read_attribute(:name)
end
test "writes attribute" do
p = Person.new
assert_equal nil, p.name
p.write_attribute(:name, "Josh")
assert_equal "Josh", p.name
end
test "returns all attributes" do
p = Person.new
p.name = "Josh"
assert_equal({"name" => "Josh"}, p.attributes)
end
end
require 'cases/helper'
class ObservedModel < ActiveModel::Base
class ObservedModel
include ActiveModel::Observing
class Observer
end
end
......@@ -17,7 +19,8 @@ def on_spec(record)
end
end
class Foo < ActiveModel::Base
class Foo
include ActiveModel::Observing
end
class ObservingTest < ActiveModel::TestCase
......
......@@ -3,6 +3,10 @@
class Contact
include ActiveModel::Serializers::JSON
def attributes
instance_values
end
end
class JsonSerializationTest < ActiveModel::TestCase
......
......@@ -3,6 +3,10 @@
class Contact
include ActiveModel::Serializers::Xml
def attributes
instance_values
end
end
class XmlSerializationTest < ActiveModel::TestCase
......
......@@ -2528,13 +2528,6 @@ def to_param
(id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
end
# Returns the ActiveRecord object when asked for its
# ActiveModel-compliant representation, because ActiveRecord is
# ActiveModel-compliant.
def to_model
self
end
# Returns a cache key that can be used to identify this record.
#
# ==== Examples
......@@ -3215,6 +3208,7 @@ def clone_attribute_value(reader_method, attribute_name)
include Dirty
include Callbacks, ActiveModel::Observing, Timestamp
include Associations, AssociationPreload, NamedScope
include ActiveModel::Conversion
# AutosaveAssociation needs to be included before Transactions, because we want
# #save_with_autosave_associations to be wrapped inside a transaction.
......
......@@ -349,7 +349,7 @@ def callback(method)
result = send(method)
end
notify(method)
notify_observers(method)
return result
end
......
......@@ -34,6 +34,7 @@ module ActiveResource
autoload :Connection, 'active_resource/connection'
autoload :CustomMethods, 'active_resource/custom_methods'
autoload :Formats, 'active_resource/formats'
autoload :Observing, 'active_resource/observing'
autoload :Validations, 'active_resource/validations'
autoload :HttpMock, 'active_resource/http_mock'
end
......@@ -804,8 +804,7 @@ def dup
# my_company.size = 10
# my_company.save # sends PUT /companies/1 (update)
def save
notify(:before_save)
(new? ? create : update).tap { notify(:after_save) }
new? ? create : update
end
# Deletes the resource from the remote service.
......@@ -821,8 +820,7 @@ def save
# new_person.destroy
# Person.find(new_id) # 404 (Resource Not Found)
def destroy
notify(:before_destroy)
connection.delete(element_path, self.class.headers).tap { notify(:after_destroy) }
connection.delete(element_path, self.class.headers)
end
# Evaluates to <tt>true</tt> if this resource is not <tt>new?</tt> and is
......@@ -997,20 +995,16 @@ def connection(refresh = false)
# Update the resource on the remote service.
def update
notify(:before_update)
connection.put(element_path(prefix_options), encode, self.class.headers).tap do |response|
load_attributes_from_response(response)
notify(:after_update)
end
end
# Create (i.e., \save to the remote service) the \new resource.
def create
notify(:before_create)
connection.post(collection_path, encode, self.class.headers).tap do |response|
self.id = id_from_response(response)
load_attributes_from_response(response)
notify(:after_create)
end
end
......@@ -1093,7 +1087,6 @@ def method_missing(method_symbol, *arguments) #:nodoc:
class Base
extend ActiveModel::Naming
include CustomMethods, Validations
include ActiveModel::Observing
include CustomMethods, Observing, Validations
end
end
module ActiveResource
module Observing
extend ActiveSupport::Concern
include ActiveModel::Observing
included do
wrap_with_notifications :create, :save, :update, :destroy
end
end
end
......@@ -19,7 +19,8 @@ module ActiveSupport
class TestCase < ::Test::Unit::TestCase
if defined? MiniTest
Assertion = MiniTest::Assertion
alias_method :method_name, :name
alias_method :method_name, :name if method_defined? :name
alias_method :method_name, :__name__ if method_defined? :__name__
else
# TODO: Figure out how to get the Rails::BacktraceFilter into minitest/unit
if defined?(Rails) && ENV['BACKTRACE'].nil?
......
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
$LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info"
require 'commands/about'
\ No newline at end of file
require 'commands/about'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/console'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/dbconsole'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/destroy'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/generate'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../config/boot'
require File.expand_path('../../../config/boot', __FILE__)
require 'commands/performance/benchmarker'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../config/boot'
require File.expand_path('../../../config/boot', __FILE__)
require 'commands/performance/profiler'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/plugin'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/runner'
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require File.expand_path('../../config/boot', __FILE__)
require 'commands/server'
Copyright (c) 2009 Engine Yard
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
require 'rubygems' unless ENV['NO_RUBYGEMS']
require 'rake/gempackagetask'
require 'rubygems/specification'
require 'date'
require 'spec/rake/spectask'
spec = Gem::Specification.new do |s|
s.name = "bundler"
s.version = "0.0.1"
s.author = "Your Name"
s.email = "Your Email"
s.homepage = "http://example.com"
s.description = s.summary = "A gem that provides..."
s.platform = Gem::Platform::RUBY
s.has_rdoc = true
s.extra_rdoc_files = ["README", "LICENSE"]
s.summary = ""
# Uncomment this to add a dependency
# s.add_dependency "foo"
s.bindir = "bin"
s.executables = %w( gem_bundler )
s.require_path = 'lib'
s.files = %w(LICENSE README Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
end
task :default => :spec
desc "Run specs"
Spec::Rake::SpecTask.new do |t|
t.spec_files = FileList['spec/**/*_spec.rb']
t.spec_opts = %w(-fs --color)
end
Rake::GemPackageTask.new(spec) do |pkg|
pkg.gem_spec = spec
end
desc "install the gem locally"
task :install => [:package] do
sh %{sudo gem install pkg/#{GEM}-#{GEM_VERSION}}
end
desc "create a gemspec file"
task :make_spec do
File.open("#{GEM}.gemspec", "w") do |file|
file.puts spec.to_ruby
end
end
\ No newline at end of file
#!/usr/bin/env ruby
require "optparse"
require "bundler"
options = {}
parser = OptionParser.new do |op|
op.banner = "Usage: gem_bundler [OPTIONS] [PATH]"
op.on("-m", "--manifest MANIFEST") do |manifest|
options[:manifest] = manifest
end
op.on_tail("-h", "--help", "Show this message") do
puts op
exit
end
end
parser.parse!
options[:path] = ARGV.shift
unless options[:path]
puts parser
puts %(
[PATH] must be specified
)
exit
end
unless options[:manifest] && File.exist?(options[:manifest])
puts parser
puts %(
MANIFEST must be a valid manifest file
)
exit
end
Bundler.run(options)
\ No newline at end of file
require 'logger'
require 'set'
# Required elements of rubygems
require "rubygems/remote_fetcher"
require "rubygems/installer"
require "bundler/gem_bundle"
require "bundler/installer"
require "bundler/finder"
require "bundler/gem_specification"
require "bundler/resolver"
require "bundler/manifest"
require "bundler/dependency"
require "bundler/runtime"
require "bundler/cli"
module Bundler
VERSION = "0.5.0"
def self.run(options = {})
manifest = ManifestBuilder.load(options[:path], options[:manifest])
manifest.install
end
end
\ No newline at end of file
module Bundler
module CLI
def default_manifest
current = Pathname.new(Dir.pwd)
begin
manifest = current.join("Gemfile")
return manifest.to_s if File.exist?(manifest)
current = current.parent
end until current.root?
nil
end
module_function :default_manifest
def default_path
Pathname.new(File.dirname(default_manifest)).join("vendor").join("gems").to_s
end
module_function :default_path
end
end
\ No newline at end of file
module Bundler
class Dependency
attr_reader :name, :version, :require_as, :only, :except
def initialize(name, options = {})
options.each do |k, v|
options[k.to_s] = v
end
@name = name
@version = options["version"] || ">= 0"
@require_as = Array(options["require_as"] || name)
@only = Array(options["only"]).map {|e| e.to_s } if options["only"]
@except = Array(options["except"]).map {|e| e.to_s } if options["except"]
end
def in?(environment)
environment = environment.to_s
return false unless !@only || @only.include?(environment)
return false if @except && @except.include?(environment)
true
end
def to_s
to_gem_dependency.to_s
end
def to_gem_dependency
@gem_dep ||= Gem::Dependency.new(name, version)
end
end
end
\ No newline at end of file
module Bundler
class Finder
def initialize(*sources)
@results = {}
@index = Hash.new { |h,k| h[k] = {} }
sources.each { |source| fetch(source) }
end
def resolve(*dependencies)
resolved = Resolver.resolve(dependencies, self)
resolved && GemBundle.new(resolved.all_specs)
end
def fetch(source)
deflated = Gem::RemoteFetcher.fetcher.fetch_path("#{source}/Marshal.4.8.Z")
inflated = Gem.inflate deflated
append(Marshal.load(inflated), source)
rescue Gem::RemoteFetcher::FetchError => e
raise ArgumentError, "#{source} is not a valid source: #{e.message}"
end
def append(index, source)
index.gems.values.each do |spec|
next unless Gem::Platform.match(spec.platform)
spec.source = source
@index[spec.name][spec.version] ||= spec
end
self
end
def search(dependency)
@results[dependency.hash] ||= begin
possibilities = @index[dependency.name].values
possibilities.select do |spec|
dependency =~ spec
end.sort_by {|s| s.version }
end
end
end
end
\ No newline at end of file
module Bundler
class GemBundle < Array
def download(directory)
FileUtils.mkdir_p(directory)
current = Dir[File.join(directory, "cache", "*.gem*")]
each do |spec|
cached = File.join(directory, "cache", "#{spec.full_name}.gem")
unless File.file?(cached)
Gem::RemoteFetcher.fetcher.download(spec, spec.source, directory)
end
current.delete(cached)
end
current.each { |file| File.delete(file) }
self
end
end
end
\ No newline at end of file
module Gem
class Specification
attribute :source
def source=(source)
@source = source.is_a?(URI) ? source : URI.parse(source)
raise ArgumentError, "The source must be an absolute URI" unless @source.absolute?
end
end
end
\ No newline at end of file
module Bundler
class Installer
def initialize(path)
if !File.directory?(path)
raise ArgumentError, "#{path} is not a directory"
elsif !File.directory?(File.join(path, "cache"))
raise ArgumentError, "#{path} is not a valid environment (it does not contain a cache directory)"
end
@path = path
@gems = Dir[(File.join(path, "cache", "*.gem"))]
end
def install(options = {})
bin_dir = options[:bin_dir] ||= File.join(@path, "bin")
specs = Dir[File.join(@path, "specifications", "*.gemspec")]
gems = Dir[File.join(@path, "gems", "*")]
@gems.each do |gem|
name = File.basename(gem).gsub(/\.gem$/, '')
installed = specs.any? { |g| File.basename(g) == "#{name}.gemspec" } &&
gems.any? { |g| File.basename(g) == name }
unless installed
installer = Gem::Installer.new(gem, :install_dir => @path,
:ignore_dependencies => true,
:env_shebang => true,
:wrappers => true,
:bin_dir => bin_dir)
installer.install
end
# remove this spec
specs.delete_if { |g| File.basename(g) == "#{name}.gemspec"}
gems.delete_if { |g| File.basename(g) == name }
end
(specs + gems).each do |path|
FileUtils.rm_rf(path)
end
end
end
end
\ No newline at end of file
require "rubygems/source_index"
require "pathname"
module Bundler
class VersionConflict < StandardError; end
class Manifest
attr_reader :sources, :dependencies, :path
def initialize(sources, dependencies, path)
sources.map! {|s| s.is_a?(URI) ? s : URI.parse(s) }
@sources, @dependencies, @path = sources, dependencies, Pathname.new(path)
end
def fetch
return if all_gems_installed?
finder = Finder.new(*sources)
unless bundle = finder.resolve(*gem_dependencies)
gems = @dependencies.map {|d| " #{d.to_s}" }.join("\n")
raise VersionConflict, "No compatible versions could be found for:\n#{gems}"
end
bundle.download(@path)
end
def install(options = {})
fetch
installer = Installer.new(@path)
installer.install # options come here
create_load_paths_files(File.join(@path, "environments"))
create_fake_rubygems(File.join(@path, "environments"))
end
def activate(environment = "default")
require File.join(@path, "environments", "#{environment}.rb")
end
def require_all
dependencies.each do |dep|
dep.require_as.each {|file| require file }
end
end
def gems_for(environment)
deps = dependencies.select { |d| d.in?(environment) }
deps.map! { |d| d.to_gem_dependency }
index = Gem::SourceIndex.from_gems_in(File.join(@path, "specifications"))
Resolver.resolve(deps, index).all_specs
end
def environments
envs = dependencies.map {|dep| Array(dep.only) + Array(dep.except) }.flatten
envs << "default"
end
private
def gem_dependencies
@gem_dependencies ||= dependencies.map { |d| d.to_gem_dependency }
end
def all_gems_installed?
gem_versions = {}
Dir[File.join(@path, "cache", "*.gem")].each do |file|
file =~ /\/([^\/]+)-([\d\.]+)\.gem$/
name, version = $1, $2
gem_versions[name] = Gem::Version.new(version)
end
gem_dependencies.all? do |dep|
gem_versions[dep.name] &&
dep.version_requirements.satisfied_by?(gem_versions[dep.name])
end
end
def create_load_paths_files(path)
FileUtils.mkdir_p(path)
environments.each do |environment|
gem_specs = gems_for(environment)
File.open(File.join(path, "#{environment}.rb"), "w") do |file|
file.puts <<-RUBY_EVAL
module Bundler
def self.rubygems_required
#{create_gem_stubs(path, gem_specs)}
end
end
RUBY_EVAL
file.puts "$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__))"
load_paths_for_specs(gem_specs).each do |load_path|
file.puts "$LOAD_PATH.unshift #{load_path.inspect}"
end
end
end
end
def create_gem_stubs(path, gem_specs)
gem_specs.map do |spec|
path = File.expand_path(File.join(path, '..', 'specifications', "#{spec.full_name}.gemspec"))
%{
Gem.loaded_specs["#{spec.name}"] = eval(File.read("#{path}"))
}
end.join("\n")
end
def create_fake_rubygems(path)
File.open(File.join(path, "rubygems.rb"), "w") do |file|
file.puts <<-RUBY_EVAL
$:.delete File.expand_path(File.dirname(__FILE__))
load "rubygems.rb"
if defined?(Bundler) && Bundler.respond_to?(:rubygems_required)
Bundler.rubygems_required
end
RUBY_EVAL
end
end
def load_paths_for_specs(specs)
load_paths = []
specs.each do |spec|
load_paths << File.join(spec.full_gem_path, spec.bindir) if spec.bindir
spec.require_paths.each do |path|
load_paths << File.join(spec.full_gem_path, path)
end
end
load_paths
end
end
end
\ No newline at end of file
require 'bundler/resolver/inspect'
require 'bundler/resolver/search'
require 'bundler/resolver/engine'
require 'bundler/resolver/stack'
require 'bundler/resolver/state'
module Bundler
module Resolver
def self.resolve(deps, source_index = Gem.source_index, logger = nil)
unless logger
logger = Logger.new($stderr)
logger.datetime_format = ""
logger.level = ENV["GEM_RESOLVER_DEBUG"] ? Logger::DEBUG : Logger::ERROR
end
Engine.resolve(deps, source_index, logger)
end
end
end
\ No newline at end of file
module Bundler
module Resolver
module Builders
def build_index(&block)
index = Gem::SourceIndex.new
IndexBuilder.run(index, &block) if block_given?
index
end
def build_spec(name, version, &block)
spec = Gem::Specification.new
spec.instance_variable_set(:@name, name)
spec.instance_variable_set(:@version, Gem::Version.new(version))
DepBuilder.run(spec, &block) if block_given?
spec
end
def build_dep(name, requirements, type = :runtime)
Gem::Dependency.new(name, requirements, type)
end
class IndexBuilder
include Builders
def self.run(index, &block)
new(index).run(&block)
end
def initialize(index)
@index = index
end
def run(&block)
instance_eval(&block)
end
def add_spec(*args, &block)
@index.add_spec(build_spec(*args, &block))
end
end
class DepBuilder
def self.run(spec, &block)
new(spec).run(&block)
end
def initialize(spec)
@spec = spec
end
def run(&block)
instance_eval(&block)
end
def runtime(name, requirements)
@spec.add_runtime_dependency(name, requirements)
end
end
end
end
end
\ No newline at end of file
module Bundler
module Resolver
class ClosedSet < Set
end
class Engine
include Search, Inspect
def self.resolve(deps, source_index, logger)
new(deps, source_index, logger).resolve
end
def initialize(deps, source_index, logger)
@deps, @source_index, @logger = deps, source_index, logger
logger.debug "searching for #{gem_resolver_inspect(@deps)}"
end
attr_reader :deps, :source_index, :logger, :solution
def resolve
state = State.initial(self, [], Stack.new, Stack.new([[[], @deps.dup]]))
if solution = search(state)
logger.info "got the solution with #{solution.all_specs.size} specs"
solution.dump(Logger::INFO)
solution
end
end
def open
@open ||= []
end
def closed
@closed ||= ClosedSet.new
end
end
end
end
\ No newline at end of file
module Bundler
module Resolver
module Inspect
def gem_resolver_inspect(o)
case o
when Gem::Specification
"#<Spec: #{o.full_name}>"
when Array
'[' + o.map {|x| gem_resolver_inspect(x)}.join(", ") + ']'
when Set
gem_resolver_inspect(o.to_a)
when Hash
'{' + o.map {|k,v| "#{gem_resolver_inspect(k)} => #{gem_resolver_inspect(v)}"}.join(", ") + '}'
when Stack
o.gem_resolver_inspect
else
o.inspect
end
end
module_function :gem_resolver_inspect
end
end
end
\ No newline at end of file
module Bundler
module Resolver
module Search
def search(initial, max_depth = (1.0 / 0.0))
if initial.goal_met?
return initial
end
open << initial
while open.any?
current = open.pop
closed << current
new = []
current.each_possibility do |attempt|
unless closed.include?(attempt)
if attempt.goal_met?
return attempt
elsif attempt.depth < max_depth
new << attempt
end
end
end
new.reverse.each do |state|
open << state
end
end
nil
end
def open
raise "implement #open in #{self.class}"
end
def closed
raise "implement #closed in #{self.class}"
end
module Node
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def initial(*data)
new(0, *data)
end
end
def initialize(depth)
@depth = depth
end
attr_reader :depth
def child(*data)
self.class.new(@depth + 1, *data)
end
def each_possibility
raise "implement #each_possibility on #{self.class}"
end
def goal_met?
raise "implement #goal_met? on #{self.class}"
end
end
end
end
end
\ No newline at end of file
module Bundler
module Resolver
class Stack
def initialize(initial = [])
@data = []
initial.each do |(path,value)|
self[path] = value
end
end
def last
@data.last
end
def []=(path, value)
raise ArgumentError, "#{path.inspect} already has a value" if key?(path)
@data << [path.dup, value]
end
def [](path)
if key?(path)
_, value = @data.find do |(k,v)|
k == path
end
value
else
raise "No value for #{path.inspect}"
end
end
def key?(path)
@data.any? do |(k,v)|
k == path
end
end
def each
@data.each do |(k,v)|
yield k, v
end
end
def map
@data.map do |(k,v)|
yield k, v
end
end
def each_value
@data.each do |(k,v)|
yield v
end
end
def dup
self.class.new(@data.dup)
end
def to_s
@data.to_s
end
def inspect
@data.inspect
end
def gem_resolver_inspect
Inspect.gem_resolver_inspect(@data)
end
end
end
end
\ No newline at end of file
module Bundler
module Resolver
class State
include Search::Node, Inspect
def initialize(depth, engine, path, spec_stack, dep_stack)
super(depth)
@engine, @path, @spec_stack, @dep_stack = engine, path, spec_stack, dep_stack
end
attr_reader :path
def logger
@engine.logger
end
def goal_met?
logger.info "checking if goal is met"
dump
no_duplicates?
all_deps.all? do |dep|
dependency_satisfied?(dep)
end
end
def no_duplicates?
names = []
all_specs.each do |s|
if names.include?(s.name)
raise "somehow got duplicates for #{s.name}"
end
names << s.name
end
end
def dependency_satisfied?(dep)
all_specs.any? do |spec|
spec.satisfies_requirement?(dep)
end
end
def each_possibility(&block)
index, dep = remaining_deps.first
if dep
logger.warn "working on #{dep} for #{spec_name}"
handle_dep(index, dep, &block)
else
logger.warn "no dependencies left for #{spec_name}"
jump_to_parent(&block)
end
end
def handle_dep(index, dep)
specs = @engine.source_index.search(dep)
specs.reverse.each do |s|
logger.info "attempting with spec: #{s.full_name}"
new_path = @path + [index]
new_spec_stack = @spec_stack.dup
new_dep_stack = @dep_stack.dup
new_spec_stack[new_path] = s
new_dep_stack[new_path] = s.runtime_dependencies.sort_by do |dep|
@engine.source_index.search(dep).size
end
yield child(@engine, new_path, new_spec_stack, new_dep_stack)
end
end
def jump_to_parent
if @path.empty?
dump
logger.warn "at the end"
return
end
logger.info "jumping to parent for #{spec_name}"
new_path = @path[0..-2]
new_spec_stack = @spec_stack.dup
new_dep_stack = @dep_stack.dup
yield child(@engine, new_path, new_spec_stack, new_dep_stack)
end
def remaining_deps
remaining_deps_for(@path)
end
def remaining_deps_for(path)
no_duplicates?
remaining = []
@dep_stack[path].each_with_index do |dep,i|
remaining << [i, dep] unless all_specs.find {|s| s.name == dep.name}
end
remaining
end
def deps
@dep_stack[@path]
end
def spec
@spec_stack[@path]
end
def spec_name
@path.empty? ? "<top>" : spec.full_name
end
def all_deps
all_deps = Set.new
@dep_stack.each_value do |deps|
all_deps.merge(deps)
end
all_deps.to_a
end
def all_specs
@spec_stack.map do |path,spec|
spec
end
end
def dump(level = Logger::DEBUG)
logger.add level, "v" * 80
logger.add level, "path: #{@path.inspect}"
logger.add level, "deps: (#{deps.size})"
deps.map do |dep|
logger.add level, gem_resolver_inspect(dep)
end
logger.add level, "remaining_deps: (#{remaining_deps.size})"
remaining_deps.each do |dep|
logger.add level, gem_resolver_inspect(dep)
end
logger.add level, "dep_stack: "
@dep_stack.each do |path,deps|
logger.add level, "#{path.inspect} (#{deps.size})"
deps.each do |dep|
logger.add level, "-> #{gem_resolver_inspect(dep)}"
end
end
logger.add level, "spec_stack: "
@spec_stack.each do |path,spec|
logger.add level, "#{path.inspect}: #{gem_resolver_inspect(spec)}"
end
logger.add level, "^" * 80
end
def to_dot
io = StringIO.new
io.puts 'digraph deps {'
io.puts ' fontname = "Courier";'
io.puts ' mincross = 4.0;'
io.puts ' ratio = "auto";'
dump_to_dot(io, "<top>", [])
io.puts '}'
io.string
end
def dump_to_dot(io, name, path)
@dep_stack[path].each_with_index do |dep,i|
new_path = path + [i]
spec_name = all_specs.find {|x| x.name == dep.name}.full_name
io.puts ' "%s" -> "%s";' % [name, dep.to_s]
io.puts ' "%s" -> "%s";' % [dep.to_s, spec_name]
if @spec_stack.key?(new_path)
dump_to_dot(io, spec_name, new_path)
end
end
end
end
end
end
\ No newline at end of file
module Bundler
class ManifestBuilder
attr_reader :sources
def self.build(path, string)
builder = new(path)
builder.instance_eval(string)
builder.to_manifest
end
def self.load(path, file)
string = File.read(file)
build(path, string)
end
def initialize(path)
@path = path
@sources = %w(http://gems.rubyforge.org)
@dependencies = []
end
def to_manifest
Manifest.new(@sources, @dependencies, @path)
end
def source(source)
@sources << source
end
def gem(name, *args)
options = args.last.is_a?(Hash) ? args.pop : {}
version = args.last
@dependencies << Dependency.new(name, options.merge(:version => version))
end
end
end
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册