diff options
Diffstat (limited to 'activesupport/lib')
36 files changed, 701 insertions, 1933 deletions
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 0478ae4ebc..f2baa5a56a 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -34,7 +34,41 @@ module ActiveSupport end end -require 'active_support/autoload' +require "active_support/dependencies/autoload" + +module ActiveSupport + extend ActiveSupport::Autoload + + # TODO: Narrow this list down + eager_autoload do + autoload :BacktraceCleaner + autoload :Base64 + autoload :BasicObject + autoload :Benchmarkable + autoload :BufferedLogger + autoload :Cache + autoload :Callbacks + autoload :Concern + autoload :Configurable + autoload :DeprecatedCallbacks + autoload :Deprecation + autoload :Gzip + autoload :Inflector + autoload :Memoizable + autoload :MessageEncryptor + autoload :MessageVerifier + autoload :Multibyte + autoload :OptionMerger + autoload :OrderedHash + autoload :OrderedOptions + autoload :Notifications + autoload :Rescuable + autoload :SecureRandom + autoload :StringInquirer + autoload :XmlMini + end +end + require 'active_support/vendor' require 'i18n' diff --git a/activesupport/lib/active_support/autoload.rb b/activesupport/lib/active_support/autoload.rb deleted file mode 100644 index 63f7338a68..0000000000 --- a/activesupport/lib/active_support/autoload.rb +++ /dev/null @@ -1,28 +0,0 @@ -module ActiveSupport - autoload :BacktraceCleaner, 'active_support/backtrace_cleaner' - autoload :Base64, 'active_support/base64' - autoload :BasicObject, 'active_support/basic_object' - autoload :Benchmarkable, 'active_support/benchmarkable' - autoload :BufferedLogger, 'active_support/buffered_logger' - autoload :Cache, 'active_support/cache' - autoload :Callbacks, 'active_support/callbacks' - autoload :Concern, 'active_support/concern' - autoload :Configurable, 'active_support/configurable' - autoload :DependencyModule, 'active_support/dependency_module' - autoload :DeprecatedCallbacks, 'active_support/deprecated_callbacks' - autoload :Deprecation, 'active_support/deprecation' - autoload :Gzip, 'active_support/gzip' - autoload :Inflector, 'active_support/inflector' - autoload :Memoizable, 'active_support/memoizable' - autoload :MessageEncryptor, 'active_support/message_encryptor' - autoload :MessageVerifier, 'active_support/message_verifier' - autoload :Multibyte, 'active_support/multibyte' - autoload :OptionMerger, 'active_support/option_merger' - autoload :OrderedHash, 'active_support/ordered_hash' - autoload :OrderedOptions, 'active_support/ordered_options' - autoload :Notifications, 'active_support/notifications' - autoload :Rescuable, 'active_support/rescuable' - autoload :SecureRandom, 'active_support/secure_random' - autoload :StringInquirer, 'active_support/string_inquirer' - autoload :XmlMini, 'active_support/xml_mini' -end diff --git a/activesupport/lib/active_support/core_ext/class/removal.rb b/activesupport/lib/active_support/core_ext/class/removal.rb index 2dea3c24d5..652be4ed78 100644 --- a/activesupport/lib/active_support/core_ext/class/removal.rb +++ b/activesupport/lib/active_support/core_ext/class/removal.rb @@ -2,7 +2,11 @@ require 'active_support/core_ext/object/extending' require 'active_support/core_ext/module/introspection' class Class #:nodoc: - + + def reachable? + eval("defined?(::#{self}) && ::#{self}.equal?(self)") + end + # Unassociates the class with its subclasses and removes the subclasses # themselves. # diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 3dd61334d0..2b76b93153 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -1,3 +1,4 @@ +require 'date' require 'active_support/duration' require 'active_support/core_ext/time/zones' diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index f6c870035b..90ab1eb281 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -1,3 +1,4 @@ +require 'date' require 'active_support/inflector' class Date diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index 5f01bc4fd6..47a31839a6 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -78,7 +78,18 @@ class DateTime # Converts self to a floating-point number of seconds since the Unix epoch def to_f - days_since_unix_epoch = self - ::DateTime.civil(1970) - (days_since_unix_epoch * 86_400).to_f + seconds_since_unix_epoch.to_f + end + + # Converts self to an integer number of seconds since the Unix epoch + def to_i + seconds_since_unix_epoch.to_i + end + + private + + def seconds_since_unix_epoch + seconds_per_day = 86_400 + (self - ::DateTime.civil(1970)) * seconds_per_day end end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index b11c916f61..d0821a7c68 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -101,6 +101,11 @@ module Enumerable size = block_given? ? select(&block).size : self.size size > 1 end + + # The negative of the Enumerable#include?. Returns true if the collection does not include the object. + def exclude?(object) + !include?(object) + end end class Range #:nodoc: diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb index e4df8fe338..22749229a3 100644 --- a/activesupport/lib/active_support/core_ext/logger.rb +++ b/activesupport/lib/active_support/core_ext/logger.rb @@ -137,10 +137,10 @@ class Logger attr_writer :formatter public :formatter= - alias old_format_datetime format_datetime if method_defined?(:format_datetime) + alias old_format_datetime format_datetime def format_datetime(datetime) datetime end - alias old_msg2str msg2str if method_defined?(:msg2str) + alias old_msg2str msg2str def msg2str(msg) msg end end end diff --git a/activesupport/lib/active_support/core_ext/object/extending.rb b/activesupport/lib/active_support/core_ext/object/extending.rb index 0cc74c8298..de8121f274 100644 --- a/activesupport/lib/active_support/core_ext/object/extending.rb +++ b/activesupport/lib/active_support/core_ext/object/extending.rb @@ -1,46 +1,53 @@ -class Object - def remove_subclasses_of(*superclasses) #:nodoc: - Class.remove_class(*subclasses_of(*superclasses)) - end - - begin - ObjectSpace.each_object(Class.new) {} - - # Exclude this class unless it's a subclass of our supers and is defined. - # We check defined? in case we find a removed class that has yet to be - # garbage collected. This also fails for anonymous classes -- please - # submit a patch if you have a workaround. - def subclasses_of(*superclasses) #:nodoc: +class Class + # Rubinius + if defined?(Class.__subclasses__) + def descendents subclasses = [] - - superclasses.each do |sup| - ObjectSpace.each_object(class << sup; self; end) do |k| - if k != sup && (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id")) - subclasses << k - end - end - end - + __subclasses__.each {|k| subclasses << k; subclasses.concat k.descendents } subclasses end - rescue RuntimeError - # JRuby and any implementations which cannot handle the objectspace traversal - # above fall back to this implementation - def subclasses_of(*superclasses) #:nodoc: - subclasses = [] + else + # MRI + begin + ObjectSpace.each_object(Class.new) {} - superclasses.each do |sup| + def descendents + subclasses = [] + ObjectSpace.each_object(class << self; self; end) do |k| + subclasses << k unless k == self + end + subclasses + end + # JRuby + rescue StandardError + def descendents + subclasses = [] ObjectSpace.each_object(Class) do |k| - if superclasses.any? { |superclass| k < superclass } && - (k.name.blank? || eval("defined?(::#{k}) && ::#{k}.object_id == k.object_id")) - subclasses << k - end + subclasses << k if k < self end subclasses.uniq! + subclasses end - subclasses end end +end + +class Object + def remove_subclasses_of(*superclasses) #:nodoc: + Class.remove_class(*subclasses_of(*superclasses)) + end + + # Exclude this class unless it's a subclass of our supers and is defined. + # We check defined? in case we find a removed class that has yet to be + # garbage collected. This also fails for anonymous classes -- please + # submit a patch if you have a workaround. + def subclasses_of(*superclasses) #:nodoc: + subclasses = [] + superclasses.each do |klass| + subclasses.concat klass.descendents.select {|k| k.name.blank? || k.reachable?} + end + subclasses + end def extended_by #:nodoc: ancestors = class << self; ancestors end diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb index a5e2260791..7ca763cbad 100644 --- a/activesupport/lib/active_support/core_ext/object/to_param.rb +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -38,7 +38,7 @@ class Hash # ==== Examples # { :name => 'David', :nationality => 'Danish' }.to_query # => "name=David&nationality=Danish" # - # { :name => 'David', :nationality => 'Danish' }.to_query('user') # => "user%5Bname%5D=David&user%5Bnationality%5D=Danish" + # { :name => 'David', :nationality => 'Danish' }.to_query('user') # => "user[name]=David&user[nationality]=Danish" def to_param(namespace = nil) collect do |key, value| value.to_query(namespace ? "#{namespace}[#{key}]" : key) diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb index 3f1540f685..c9981895b4 100644 --- a/activesupport/lib/active_support/core_ext/object/to_query.rb +++ b/activesupport/lib/active_support/core_ext/object/to_query.rb @@ -7,7 +7,7 @@ class Object # Note: This method is defined as a default implementation for all Objects for Hash#to_query to work. def to_query(key) require 'cgi' unless defined?(CGI) && defined?(CGI::escape) - "#{CGI.escape(key.to_s)}=#{CGI.escape(to_param.to_s)}" + "#{CGI.escape(key.to_s).gsub(/%(5B|5D)/n) { [$1].pack('H*') }}=#{CGI.escape(to_param.to_s)}" end end @@ -15,7 +15,7 @@ class Array # Converts an array into a string suitable for use as a URL query string, # using the given +key+ as the param name. # - # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding" + # ['Rails', 'coding'].to_query('hobbies') # => "hobbies[]=Rails&hobbies[]=coding" def to_query(key) prefix = "#{key}[]" collect { |value| value.to_query(prefix) }.join '&' diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index 2cca4763f4..3e6ab0ebd2 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -1,43 +1,29 @@ class String - def html_safe? - defined?(@_rails_html_safe) && @_rails_html_safe - end + attr_accessor :_rails_html_safe + alias html_safe? _rails_html_safe def html_safe! @_rails_html_safe = true self end - + def html_safe dup.html_safe! end - + alias original_plus + def +(other) result = original_plus(other) - if html_safe? && also_html_safe?(other) - result.html_safe! - else - result - end + result._rails_html_safe = html_safe? && other.html_safe? + result end - + alias original_concat << + alias safe_concat << def <<(other) + @_rails_html_safe = false unless other.html_safe? result = original_concat(other) - unless html_safe? && also_html_safe?(other) - @_rails_html_safe = false - end - result end - - def concat(other) - self << other - end - - private - def also_html_safe?(other) - other.respond_to?(:html_safe?) && other.html_safe? - end - + + alias concat << end
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 4f4492f0fd..703b89ffd0 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -1,4 +1,5 @@ require 'active_support/duration' +require 'active_support/core_ext/date/acts_like' require 'active_support/core_ext/date/calculations' class Time diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb new file mode 100644 index 0000000000..44edb89ad5 --- /dev/null +++ b/activesupport/lib/active_support/dependencies/autoload.rb @@ -0,0 +1,49 @@ +require "active_support/inflector/methods" + +module ActiveSupport + module Autoload + @@autoloads = {} + @@under_path = nil + @@at_path = nil + @@eager_autoload = false + + def autoload(const_name, path = @@at_path) + full = [self.name, @@under_path, const_name.to_s, path].compact.join("::") + location = path || Inflector.underscore(full) + + if @@eager_autoload + @@autoloads[const_name] = location + end + super const_name, location + end + + def autoload_under(path) + @@under_path, old_path = path, @@under_path + yield + ensure + @@under_path = old_path + end + + def autoload_at(path) + @@at_path, old_path = path, @@at_path + yield + ensure + @@at_path = old_path + end + + def eager_autoload + old_eager, @@eager_autoload = @@eager_autoload, true + yield + ensure + @@eager_autoload = old_eager + end + + def self.eager_autoload! + @@autoloads.values.each { |file| require file } + end + + def autoloads + @@autoloads + end + end +end diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 3c15056c41..c8415d5449 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -65,6 +65,15 @@ module ActiveSupport ESCAPED_CHARS = { + "\x00" => '\u0000', "\x01" => '\u0001', "\x02" => '\u0002', + "\x03" => '\u0003', "\x04" => '\u0004', "\x05" => '\u0005', + "\x06" => '\u0006', "\x07" => '\u0007', "\x0B" => '\u000B', + "\x0E" => '\u000E', "\x0F" => '\u000F', "\x10" => '\u0010', + "\x11" => '\u0011', "\x12" => '\u0012', "\x13" => '\u0013', + "\x14" => '\u0014', "\x15" => '\u0015', "\x16" => '\u0016', + "\x17" => '\u0017', "\x18" => '\u0018', "\x19" => '\u0019', + "\x1A" => '\u001A', "\x1B" => '\u001B', "\x1C" => '\u001C', + "\x1D" => '\u001D', "\x1E" => '\u001E', "\x1F" => '\u001F', "\010" => '\b', "\f" => '\f', "\n" => '\n', @@ -86,9 +95,9 @@ module ActiveSupport def escape_html_entities_in_json=(value) self.escape_regex = \ if @escape_html_entities_in_json = value - /[\010\f\n\r\t"\\><&]/ + /[\x00-\x1F"\\><&]/ else - /[\010\f\n\r\t"\\]/ + /[\x00-\x1F"\\]/ end end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index e2540cd598..d9bfcbfcab 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -1,7 +1,4 @@ -require 'thread' require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/secure_random' module ActiveSupport # Notifications provides an instrumentation API for Ruby. To instrument an @@ -41,173 +38,42 @@ module ActiveSupport # to subscribers in a thread. You can use any queue implementation you want. # module Notifications - mattr_accessor :queue, :listener + autoload :Instrumenter, 'active_support/notifications/instrumenter' + autoload :Event, 'active_support/notifications/instrumenter' + autoload :Fanout, 'active_support/notifications/fanout' class << self - delegate :instrument, :transaction_id, :transaction, :to => :instrumenter + attr_writer :notifier + delegate :publish, :subscribe, :instrument, :to => :notifier - def instrumenter - Thread.current[:notifications_instrumeter] ||= Instrumenter.new(publisher) - end - - def publisher - @publisher ||= Publisher.new(queue) - end - - def subscriber - @subscriber ||= Subscriber.new(queue) - end - - def subscribe(pattern=nil, options={}, &block) - with = options[:with] || listener - subscriber.bind(with, pattern).subscribe(&block) + def notifier + @notifier ||= Notifier.new end end - class Instrumenter - def initialize(publisher) - @publisher = publisher - @id = random_id - end - - def transaction - @id, old_id = random_id, @id - yield - ensure - @id = old_id - end - - def transaction_id - @id - end - - def instrument(name, payload={}) - time = Time.now - result = yield if block_given? - ensure - @publisher.publish(name, time, Time.now, result, @id, payload) - end - - private - def random_id - SecureRandom.hex(10) - end - end - - class Publisher - def initialize(queue) + class Notifier + def initialize(queue = Fanout.new) @queue = queue end def publish(*args) @queue.publish(*args) end - end - - class Subscriber - def initialize(queue) - @queue = queue - end - - def bind(listener, pattern) - @listener = listener - @pattern = pattern - self - end - def subscribe - @queue.subscribe(@listener, @pattern) do |*args| - yield(*args) - end + def subscribe(pattern = nil, &block) + @queue.bind(pattern).subscribe(&block) end - end - - class Event - attr_reader :name, :time, :end, :transaction_id, :result, :payload - def initialize(name, start, ending, result, transaction_id, payload) - @name = name - @payload = payload.dup - @time = start - @transaction_id = transaction_id - @end = ending - @result = result + def wait + @queue.wait end - def duration - @duration ||= 1000.0 * (@end - @time) - end + delegate :instrument, :to => :current_instrumenter - def parent_of?(event) - start = (self.time - event.time) * 1000 - start <= 0 && (start + duration >= event.duration) - end - end - - class AsyncListener - def initialize(pattern, &block) - @pattern = pattern - @subscriber = block - @queue = Queue.new - Thread.new { consume } - end - - def publish(name, *args) - if !@pattern || @pattern === name.to_s - @queue << args.unshift(name) - end - end - - def consume - while args = @queue.shift - @subscriber.call(*args) - end - end - - def drained? - @queue.size.zero? - end - end - - class SyncListener - def initialize(pattern, &block) - @pattern = pattern - @subscriber = block - end - - def publish(name, *args) - if !@pattern || @pattern === name.to_s - @subscriber.call(*args.unshift(name)) + private + def current_instrumenter + Thread.current[:"instrumentation_#{object_id}"] ||= Notifications::Instrumenter.new(self) end - end - - def drained? - true - end - end - - # This is a default queue implementation that ships with Notifications. It - # consumes events in a thread and publish them to all registered subscribers. - # - class LittleFanout - def initialize - @listeners = [] - end - - def publish(*args) - @listeners.each { |l| l.publish(*args) } - end - - def subscribe(listener, pattern=nil, &block) - @listeners << listener.new(pattern, &block) - end - - def drained? - @listeners.all? &:drained? - end end end - - Notifications.queue = Notifications::LittleFanout.new - Notifications.listener = Notifications::AsyncListener end diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb new file mode 100644 index 0000000000..bb07e4765c --- /dev/null +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -0,0 +1,101 @@ +require 'thread' + +module ActiveSupport + module Notifications + # This is a default queue implementation that ships with Notifications. It + # consumes events in a thread and publish them to all registered subscribers. + # + class Fanout + def initialize(sync = false) + @subscriber_klass = sync ? Subscriber : AsyncSubscriber + @subscribers = [] + end + + def bind(pattern) + Binding.new(self, pattern) + end + + def subscribe(pattern = nil, &block) + @subscribers << @subscriber_klass.new(pattern, &block) + end + + def publish(*args) + @subscribers.each { |s| s.publish(*args) } + end + + def wait + sleep(0.05) until @subscribers.all?(&:drained?) + end + + # Used for internal implementation only. + class Binding #:nodoc: + def initialize(queue, pattern) + @queue = queue + @pattern = + case pattern + when Regexp, NilClass + pattern + else + /^#{Regexp.escape(pattern.to_s)}/ + end + end + + def subscribe(&block) + @queue.subscribe(@pattern, &block) + end + end + + class Subscriber #:nodoc: + def initialize(pattern, &block) + @pattern = pattern + @block = block + end + + def publish(*args) + push(*args) if matches?(args.first) + end + + def drained? + true + end + + private + def matches?(name) + !@pattern || @pattern =~ name.to_s + end + + def push(*args) + @block.call(*args) + end + end + + # Used for internal implementation only. + class AsyncSubscriber < Subscriber #:nodoc: + def initialize(pattern, &block) + super + @events = Queue.new + start_consumer + end + + def drained? + @events.empty? + end + + private + def start_consumer + Thread.new { consume } + end + + def consume + while args = @events.shift + @block.call(*args) + end + end + + def push(*args) + @events << args + end + end + end + end +end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb new file mode 100644 index 0000000000..fb95422af2 --- /dev/null +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -0,0 +1,47 @@ +require 'active_support/secure_random' +require 'active_support/core_ext/module/delegation' + +module ActiveSupport + module Notifications + class Instrumenter + def initialize(notifier) + @id = unique_id + @notifier = notifier + end + + def instrument(name, payload={}) + time = Time.now + result = yield if block_given? + ensure + @notifier.publish(name, time, Time.now, result, @id, payload) + end + + private + def unique_id + SecureRandom.hex(10) + end + end + + class Event + attr_reader :name, :time, :end, :transaction_id, :result, :payload + + def initialize(name, start, ending, result, transaction_id, payload) + @name = name + @payload = payload.dup + @time = start + @transaction_id = transaction_id + @end = ending + @result = result + end + + def duration + @duration ||= 1000.0 * (@end - @time) + end + + def parent_of?(event) + start = (self.time - event.time) * 1000 + start <= 0 && (start + duration >= event.duration) + end + end + end +end diff --git a/activesupport/lib/active_support/ruby/shim.rb b/activesupport/lib/active_support/ruby/shim.rb index f811239077..1e49ccdade 100644 --- a/activesupport/lib/active_support/ruby/shim.rb +++ b/activesupport/lib/active_support/ruby/shim.rb @@ -14,5 +14,6 @@ require 'active_support/core_ext/date_time/conversions' require 'active_support/core_ext/enumerable' require 'active_support/core_ext/process/daemon' require 'active_support/core_ext/string/conversions' +require 'active_support/core_ext/string/interpolation' require 'active_support/core_ext/rexml' require 'active_support/core_ext/time/conversions' diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index c75b59c284..81f7e3c9df 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -1,10 +1,25 @@ module ActiveSupport module Testing + class RemoteError < StandardError + + attr_reader :message, :backtrace + + def initialize(exception) + @message = "caught #{exception.class.name}: #{exception.message}" + @backtrace = exception.backtrace + end + end + class ProxyTestResult def initialize @calls = [] end + def add_error(e) + e = Test::Unit::Error.new(e.test_name, RemoteError.new(e.exception)) + @calls << [:add_error, e] + end + def __replay__(result) @calls.each do |name, args| result.send(name, *args) diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index ab34f975f6..66e32fa5d7 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -1,450 +1,454 @@ -require 'ruby-prof' - -require 'fileutils' -require 'rails/version' - -module ActiveSupport - module Testing - module Performance - DEFAULTS = - if benchmark = ARGV.include?('--benchmark') # HAX for rake test - { :benchmark => true, - :runs => 4, - :metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time], - :output => 'tmp/performance' } - else - { :benchmark => false, - :runs => 1, - :min_percent => 0.01, - :metrics => [:process_time, :memory, :objects], - :formats => [:flat, :graph_html, :call_tree], - :output => 'tmp/performance' } - end.freeze - - def self.included(base) - base.superclass_delegating_accessor :profile_options - base.profile_options = DEFAULTS - end +begin + require 'ruby-prof' + + require 'fileutils' + require 'rails/version' + require 'active_support/core_ext/class/delegating_attributes' + + module ActiveSupport + module Testing + module Performance + DEFAULTS = + if benchmark = ARGV.include?('--benchmark') # HAX for rake test + { :benchmark => true, + :runs => 4, + :metrics => [:wall_time, :memory, :objects, :gc_runs, :gc_time], + :output => 'tmp/performance' } + else + { :benchmark => false, + :runs => 1, + :min_percent => 0.01, + :metrics => [:process_time, :memory, :objects], + :formats => [:flat, :graph_html, :call_tree], + :output => 'tmp/performance' } + end.freeze + + def self.included(base) + base.superclass_delegating_accessor :profile_options + base.profile_options = DEFAULTS + end - def full_test_name - "#{self.class.name}##{method_name}" - end + def full_test_name + "#{self.class.name}##{method_name}" + end - def run(result) - return if method_name =~ /^default_test$/ + def run(result) + return if method_name =~ /^default_test$/ - yield(self.class::STARTED, name) - @_result = result + yield(self.class::STARTED, name) + @_result = result - run_warmup - if profile_options && metrics = profile_options[:metrics] - metrics.each do |metric_name| - if klass = Metrics[metric_name.to_sym] - run_profile(klass.new) - result.add_run + run_warmup + if profile_options && metrics = profile_options[:metrics] + metrics.each do |metric_name| + if klass = Metrics[metric_name.to_sym] + run_profile(klass.new) + result.add_run + end end end - end - yield(self.class::FINISHED, name) - end + yield(self.class::FINISHED, name) + end - def run_test(metric, mode) - run_callbacks :setup - setup - metric.send(mode) { __send__ @method_name } - rescue ::Test::Unit::AssertionFailedError => e - add_failure(e.message, e.backtrace) - rescue StandardError, ScriptError - add_error($!) - ensure - begin - teardown - run_callbacks :teardown, :enumerator => :reverse_each + def run_test(metric, mode) + run_callbacks :setup + setup + metric.send(mode) { __send__ @method_name } rescue ::Test::Unit::AssertionFailedError => e add_failure(e.message, e.backtrace) rescue StandardError, ScriptError add_error($!) + ensure + begin + teardown + run_callbacks :teardown, :enumerator => :reverse_each + rescue ::Test::Unit::AssertionFailedError => e + add_failure(e.message, e.backtrace) + rescue StandardError, ScriptError + add_error($!) + end end - end - protected - def run_warmup - GC.start + protected + def run_warmup + GC.start - time = Metrics::Time.new - run_test(time, :benchmark) - puts "%s (%s warmup)" % [full_test_name, time.format(time.total)] + time = Metrics::Time.new + run_test(time, :benchmark) + puts "%s (%s warmup)" % [full_test_name, time.format(time.total)] - GC.start - end - - def run_profile(metric) - klass = profile_options[:benchmark] ? Benchmarker : Profiler - performer = klass.new(self, metric) + GC.start + end - performer.run - puts performer.report - performer.record - end + def run_profile(metric) + klass = profile_options[:benchmark] ? Benchmarker : Profiler + performer = klass.new(self, metric) - class Performer - delegate :run_test, :profile_options, :full_test_name, :to => :@harness + performer.run + puts performer.report + performer.record + end - def initialize(harness, metric) - @harness, @metric = harness, metric - end + class Performer + delegate :run_test, :profile_options, :full_test_name, :to => :@harness - def report - rate = @total / profile_options[:runs] - '%20s: %s' % [@metric.name, @metric.format(rate)] - end + def initialize(harness, metric) + @harness, @metric = harness, metric + end - protected - def output_filename - "#{profile_options[:output]}/#{full_test_name}_#{@metric.name}" + def report + rate = @total / profile_options[:runs] + '%20s: %s' % [@metric.name, @metric.format(rate)] end - end - class Benchmarker < Performer - def run - profile_options[:runs].to_i.times { run_test(@metric, :benchmark) } - @total = @metric.total + protected + def output_filename + "#{profile_options[:output]}/#{full_test_name}_#{@metric.name}" + end end - def record - avg = @metric.total / profile_options[:runs].to_i - now = Time.now.utc.xmlschema - with_output_file do |file| - file.puts "#{avg},#{now},#{environment}" + class Benchmarker < Performer + def run + profile_options[:runs].to_i.times { run_test(@metric, :benchmark) } + @total = @metric.total end - end - def environment - unless defined? @env - app = "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/ - - rails = Rails::VERSION::STRING - if File.directory?('vendor/rails/.git') - Dir.chdir('vendor/rails') do - rails += ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/ - end + def record + avg = @metric.total / profile_options[:runs].to_i + now = Time.now.utc.xmlschema + with_output_file do |file| + file.puts "#{avg},#{now},#{environment}" end - - ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby' - ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" - - @env = [app, rails, ruby, RUBY_PLATFORM] * ',' end - @env - end + def environment + unless defined? @env + app = "#{$1}.#{$2}" if File.directory?('.git') && `git branch -v` =~ /^\* (\S+)\s+(\S+)/ - protected - HEADER = 'measurement,created_at,app,rails,ruby,platform' + rails = Rails::VERSION::STRING + if File.directory?('vendor/rails/.git') + Dir.chdir('vendor/rails') do + rails += ".#{$1}.#{$2}" if `git branch -v` =~ /^\* (\S+)\s+(\S+)/ + end + end - def with_output_file - fname = output_filename + ruby = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby' + ruby += "-#{RUBY_VERSION}.#{RUBY_PATCHLEVEL}" - if new = !File.exist?(fname) - FileUtils.mkdir_p(File.dirname(fname)) + @env = [app, rails, ruby, RUBY_PLATFORM] * ',' end - File.open(fname, 'ab') do |file| - file.puts(HEADER) if new - yield file - end + @env end - def output_filename - "#{super}.csv" - end - end + protected + HEADER = 'measurement,created_at,app,rails,ruby,platform' - class Profiler < Performer - def initialize(*args) - super - @supported = @metric.measure_mode rescue false - end + def with_output_file + fname = output_filename + + if new = !File.exist?(fname) + FileUtils.mkdir_p(File.dirname(fname)) + end - def run - return unless @supported + File.open(fname, 'ab') do |file| + file.puts(HEADER) if new + yield file + end + end - RubyProf.measure_mode = @metric.measure_mode - RubyProf.start - RubyProf.pause - profile_options[:runs].to_i.times { run_test(@metric, :profile) } - @data = RubyProf.stop - @total = @data.threads.values.sum(0) { |method_infos| method_infos.sort.last.total_time } + def output_filename + "#{super}.csv" + end end - def report - if @supported + class Profiler < Performer + def initialize(*args) super - else - '%20s: unsupported' % @metric.name + @supported = @metric.measure_mode rescue false end - end - def record - return unless @supported + def run + return unless @supported - klasses = profile_options[:formats].map { |f| RubyProf.const_get("#{f.to_s.camelize}Printer") }.compact + RubyProf.measure_mode = @metric.measure_mode + RubyProf.start + RubyProf.pause + profile_options[:runs].to_i.times { run_test(@metric, :profile) } + @data = RubyProf.stop + @total = @data.threads.values.sum(0) { |method_infos| method_infos.sort.last.total_time } + end - klasses.each do |klass| - fname = output_filename(klass) - FileUtils.mkdir_p(File.dirname(fname)) - File.open(fname, 'wb') do |file| - klass.new(@data).print(file, profile_options.slice(:min_percent)) + def report + if @supported + super + else + '%20s: unsupported' % @metric.name end end - end - protected - def output_filename(printer_class) - suffix = - case printer_class.name.demodulize - when 'FlatPrinter'; 'flat.txt' - when 'GraphPrinter'; 'graph.txt' - when 'GraphHtmlPrinter'; 'graph.html' - when 'CallTreePrinter'; 'tree.txt' - else printer_class.name.sub(/Printer$/, '').underscore - end + def record + return unless @supported + + klasses = profile_options[:formats].map { |f| RubyProf.const_get("#{f.to_s.camelize}Printer") }.compact - "#{super()}_#{suffix}" + klasses.each do |klass| + fname = output_filename(klass) + FileUtils.mkdir_p(File.dirname(fname)) + File.open(fname, 'wb') do |file| + klass.new(@data).print(file, profile_options.slice(:min_percent)) + end + end end - end - module Metrics - def self.[](name) - const_get(name.to_s.camelize) - rescue NameError - nil + protected + def output_filename(printer_class) + suffix = + case printer_class.name.demodulize + when 'FlatPrinter'; 'flat.txt' + when 'GraphPrinter'; 'graph.txt' + when 'GraphHtmlPrinter'; 'graph.html' + when 'CallTreePrinter'; 'tree.txt' + else printer_class.name.sub(/Printer$/, '').underscore + end + + "#{super()}_#{suffix}" + end end - class Base - attr_reader :total - - def initialize - @total = 0 + module Metrics + def self.[](name) + const_get(name.to_s.camelize) + rescue NameError + nil end - def name - @name ||= self.class.name.demodulize.underscore - end + class Base + attr_reader :total - def measure_mode - self.class::Mode - end + def initialize + @total = 0 + end - def measure - 0 - end + def name + @name ||= self.class.name.demodulize.underscore + end - def benchmark - with_gc_stats do - before = measure - yield - @total += (measure - before) + def measure_mode + self.class::Mode end - end - def profile - RubyProf.resume - yield - ensure - RubyProf.pause - end + def measure + 0 + end - protected - if GC.respond_to?(:enable_stats) - def with_gc_stats - GC.enable_stats - yield - ensure - GC.disable_stats - end - elsif defined?(GC::Profiler) - def with_gc_stats - GC.start - GC.disable - GC::Profiler.enable - yield - ensure - GC::Profiler.disable - GC.enable - end - else - def with_gc_stats + def benchmark + with_gc_stats do + before = measure yield + @total += (measure - before) end end - end - class Time < Base - def measure - ::Time.now.to_f - end - - def format(measurement) - if measurement < 2 - '%d ms' % (measurement * 1000) - else - '%.2f sec' % measurement + def profile + RubyProf.resume + yield + ensure + RubyProf.pause end - end - end - - class ProcessTime < Time - Mode = RubyProf::PROCESS_TIME - - def measure - RubyProf.measure_process_time - end - end - - class WallTime < Time - Mode = RubyProf::WALL_TIME - def measure - RubyProf.measure_wall_time + protected + if GC.respond_to?(:enable_stats) + def with_gc_stats + GC.enable_stats + yield + ensure + GC.disable_stats + end + elsif defined?(GC::Profiler) + def with_gc_stats + GC.start + GC.disable + GC::Profiler.enable + yield + ensure + GC::Profiler.disable + GC.enable + end + else + def with_gc_stats + yield + end + end end - end - class CpuTime < Time - Mode = RubyProf::CPU_TIME if RubyProf.const_defined?(:CPU_TIME) - - def initialize(*args) - # FIXME: yeah my CPU is 2.33 GHz - RubyProf.cpu_frequency = 2.33e9 - super - end + class Time < Base + def measure + ::Time.now.to_f + end - def measure - RubyProf.measure_cpu_time + def format(measurement) + if measurement < 2 + '%d ms' % (measurement * 1000) + else + '%.2f sec' % measurement + end + end end - end - class Memory < Base - Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY) + class ProcessTime < Time + Mode = RubyProf::PROCESS_TIME - # ruby-prof wrapper - if RubyProf.respond_to?(:measure_memory) def measure - RubyProf.measure_memory / 1024.0 + RubyProf.measure_process_time end + end - # Ruby 1.8 + railsbench patch - elsif GC.respond_to?(:allocated_size) - def measure - GC.allocated_size / 1024.0 - end + class WallTime < Time + Mode = RubyProf::WALL_TIME - # Ruby 1.8 + lloyd patch - elsif GC.respond_to?(:heap_info) def measure - GC.heap_info['heap_current_memory'] / 1024.0 + RubyProf.measure_wall_time end + end - # Ruby 1.9 with total_malloc_allocated_size patch - elsif GC.respond_to?(:malloc_total_allocated_size) - def measure - GC.total_malloc_allocated_size / 1024.0 - end + class CpuTime < Time + Mode = RubyProf::CPU_TIME if RubyProf.const_defined?(:CPU_TIME) - # Ruby 1.9 unpatched - elsif GC.respond_to?(:malloc_allocated_size) - def measure - GC.malloc_allocated_size / 1024.0 + def initialize(*args) + # FIXME: yeah my CPU is 2.33 GHz + RubyProf.cpu_frequency = 2.33e9 + super end - # Ruby 1.9 + GC profiler patch - elsif defined?(GC::Profiler) def measure - GC.enable - GC.start - kb = GC::Profiler.data.last[:HEAP_USE_SIZE] / 1024.0 - GC.disable - kb + RubyProf.measure_cpu_time end end - def format(measurement) - '%.2f KB' % measurement - end - end + class Memory < Base + Mode = RubyProf::MEMORY if RubyProf.const_defined?(:MEMORY) - class Objects < Base - Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS) + # ruby-prof wrapper + if RubyProf.respond_to?(:measure_memory) + def measure + RubyProf.measure_memory / 1024.0 + end - if RubyProf.respond_to?(:measure_allocations) - def measure - RubyProf.measure_allocations - end + # Ruby 1.8 + railsbench patch + elsif GC.respond_to?(:allocated_size) + def measure + GC.allocated_size / 1024.0 + end - # Ruby 1.8 + railsbench patch - elsif ObjectSpace.respond_to?(:allocated_objects) - def measure - ObjectSpace.allocated_objects + # Ruby 1.8 + lloyd patch + elsif GC.respond_to?(:heap_info) + def measure + GC.heap_info['heap_current_memory'] / 1024.0 + end + + # Ruby 1.9 with total_malloc_allocated_size patch + elsif GC.respond_to?(:malloc_total_allocated_size) + def measure + GC.total_malloc_allocated_size / 1024.0 + end + + # Ruby 1.9 unpatched + elsif GC.respond_to?(:malloc_allocated_size) + def measure + GC.malloc_allocated_size / 1024.0 + end + + # Ruby 1.9 + GC profiler patch + elsif defined?(GC::Profiler) + def measure + GC.enable + GC.start + kb = GC::Profiler.data.last[:HEAP_USE_SIZE] / 1024.0 + GC.disable + kb + end end - # Ruby 1.9 + GC profiler patch - elsif defined?(GC::Profiler) - def measure - GC.enable - GC.start - last = GC::Profiler.data.last - count = last[:HEAP_LIVE_OBJECTS] + last[:HEAP_FREE_OBJECTS] - GC.disable - count + def format(measurement) + '%.2f KB' % measurement end end - def format(measurement) - measurement.to_i.to_s - end - end + class Objects < Base + Mode = RubyProf::ALLOCATIONS if RubyProf.const_defined?(:ALLOCATIONS) - class GcRuns < Base - Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS) + if RubyProf.respond_to?(:measure_allocations) + def measure + RubyProf.measure_allocations + end - if RubyProf.respond_to?(:measure_gc_runs) - def measure - RubyProf.measure_gc_runs - end - elsif GC.respond_to?(:collections) - def measure - GC.collections - end - elsif GC.respond_to?(:heap_info) - def measure - GC.heap_info['num_gc_passes'] + # Ruby 1.8 + railsbench patch + elsif ObjectSpace.respond_to?(:allocated_objects) + def measure + ObjectSpace.allocated_objects + end + + # Ruby 1.9 + GC profiler patch + elsif defined?(GC::Profiler) + def measure + GC.enable + GC.start + last = GC::Profiler.data.last + count = last[:HEAP_LIVE_OBJECTS] + last[:HEAP_FREE_OBJECTS] + GC.disable + count + end end - end - def format(measurement) - measurement.to_i.to_s + def format(measurement) + measurement.to_i.to_s + end end - end - class GcTime < Base - Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME) + class GcRuns < Base + Mode = RubyProf::GC_RUNS if RubyProf.const_defined?(:GC_RUNS) - if RubyProf.respond_to?(:measure_gc_time) - def measure - RubyProf.measure_gc_time + if RubyProf.respond_to?(:measure_gc_runs) + def measure + RubyProf.measure_gc_runs + end + elsif GC.respond_to?(:collections) + def measure + GC.collections + end + elsif GC.respond_to?(:heap_info) + def measure + GC.heap_info['num_gc_passes'] + end end - elsif GC.respond_to?(:time) - def measure - GC.time + + def format(measurement) + measurement.to_i.to_s end end - def format(measurement) - '%d ms' % (measurement / 1000) + class GcTime < Base + Mode = RubyProf::GC_TIME if RubyProf.const_defined?(:GC_TIME) + + if RubyProf.respond_to?(:measure_gc_time) + def measure + RubyProf.measure_gc_time + end + elsif GC.respond_to?(:time) + def measure + GC.time + end + end + + def format(measurement) + '%d ms' % (measurement / 1000) + end end end end end end -end +rescue LoadError +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb index eb5080888c..1e46491d83 100644 --- a/activesupport/lib/active_support/vendor.rb +++ b/activesupport/lib/active_support/vendor.rb @@ -4,7 +4,7 @@ def ActiveSupport.requirable?(file) $LOAD_PATH.any? { |p| Dir.glob("#{p}/#{file}.*").any? } end -[%w(builder 2.1.2), %w(i18n 0.1.3), %w(memcache-client 1.7.5), %w(tzinfo 0.3.15)].each do |lib, version| +[%w(builder 2.1.2), %w(memcache-client 1.7.5), %w(tzinfo 0.3.15)].each do |lib, version| # If the lib is not already requirable unless ActiveSupport.requirable? lib # Try to activate a gem ~> satisfying the requested version first. diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/MIT-LICENSE b/activesupport/lib/active_support/vendor/i18n-0.1.3/MIT-LICENSE deleted file mode 100755 index ed8e9ee66d..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/MIT-LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -Copyright (c) 2008 The Ruby I18n team - -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 diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/README.textile b/activesupport/lib/active_support/vendor/i18n-0.1.3/README.textile deleted file mode 100644 index a07fc8426d..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/README.textile +++ /dev/null @@ -1,20 +0,0 @@ -h1. Ruby I18n gem - -I18n and localization solution for Ruby. - -For information please refer to http://rails-i18n.org - -h2. Authors - -* "Matt Aimonetti":http://railsontherun.com -* "Sven Fuchs":http://www.artweb-design.de -* "Joshua Harvey":http://www.workingwithrails.com/person/759-joshua-harvey -* "Saimon Moore":http://saimonmoore.net -* "Stephan Soller":http://www.arkanis-development.de - -h2. License - -MIT License. See the included MIT-LICENCE file. - - - diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/Rakefile b/activesupport/lib/active_support/vendor/i18n-0.1.3/Rakefile deleted file mode 100644 index 2164e13e69..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/Rakefile +++ /dev/null @@ -1,5 +0,0 @@ -task :default => [:test] - -task :test do - ruby "test/all.rb" -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/i18n.gemspec b/activesupport/lib/active_support/vendor/i18n-0.1.3/i18n.gemspec deleted file mode 100644 index f102689a6f..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/i18n.gemspec +++ /dev/null @@ -1,27 +0,0 @@ -Gem::Specification.new do |s| - s.name = "i18n" - s.version = "0.1.3" - s.date = "2009-01-09" - s.summary = "Internationalization support for Ruby" - s.email = "rails-i18n@googlegroups.com" - s.homepage = "http://rails-i18n.org" - s.description = "Add Internationalization support to your Ruby application." - s.has_rdoc = false - s.authors = ['Sven Fuchs', 'Joshua Harvey', 'Matt Aimonetti', 'Stephan Soller', 'Saimon Moore'] - s.files = [ - 'i18n.gemspec', - 'lib/i18n/backend/simple.rb', - 'lib/i18n/exceptions.rb', - 'lib/i18n.rb', - 'MIT-LICENSE', - 'README.textile' - ] - s.test_files = [ - 'test/all.rb', - 'test/i18n_exceptions_test.rb', - 'test/i18n_test.rb', - 'test/locale/en.rb', - 'test/locale/en.yml', - 'test/simple_backend_test.rb' - ] -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n.rb deleted file mode 100755 index 1b49debc05..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n.rb +++ /dev/null @@ -1,204 +0,0 @@ -#-- -# Authors:: Matt Aimonetti (http://railsontherun.com/), -# Sven Fuchs (http://www.artweb-design.de), -# Joshua Harvey (http://www.workingwithrails.com/person/759-joshua-harvey), -# Saimon Moore (http://saimonmoore.net), -# Stephan Soller (http://www.arkanis-development.de/) -# Copyright:: Copyright (c) 2008 The Ruby i18n Team -# License:: MIT -#++ - -module I18n - autoload :ArgumentError, 'i18n/exceptions' - module Backend - autoload :Simple, 'i18n/backend/simple' - end - - @@backend = nil - @@load_path = nil - @@default_locale = :'en' - @@exception_handler = :default_exception_handler - - class << self - # Returns the current backend. Defaults to +Backend::Simple+. - def backend - @@backend ||= Backend::Simple.new - end - - # Sets the current backend. Used to set a custom backend. - def backend=(backend) - @@backend = backend - end - - # Returns the current default locale. Defaults to :'en' - def default_locale - @@default_locale - end - - # Sets the current default locale. Used to set a custom default locale. - def default_locale=(locale) - @@default_locale = locale - end - - # Returns the current locale. Defaults to I18n.default_locale. - def locale - Thread.current[:locale] ||= default_locale - end - - # Sets the current locale pseudo-globally, i.e. in the Thread.current hash. - def locale=(locale) - Thread.current[:locale] = locale - end - - # Returns an array of locales for which translations are available - def available_locales - backend.available_locales - end - - # Sets the exception handler. - def exception_handler=(exception_handler) - @@exception_handler = exception_handler - end - - # Allow clients to register paths providing translation data sources. The - # backend defines acceptable sources. - # - # E.g. the provided SimpleBackend accepts a list of paths to translation - # files which are either named *.rb and contain plain Ruby Hashes or are - # named *.yml and contain YAML data. So for the SimpleBackend clients may - # register translation files like this: - # I18n.load_path << 'path/to/locale/en.yml' - def load_path - @@load_path ||= [] - end - - # Sets the load path instance. Custom implementations are expected to - # behave like a Ruby Array. - def load_path=(load_path) - @@load_path = load_path - end - - # Tells the backend to reload translations. Used in situations like the - # Rails development environment. Backends can implement whatever strategy - # is useful. - def reload! - backend.reload! - end - - # Translates, pluralizes and interpolates a given key using a given locale, - # scope, and default, as well as interpolation values. - # - # *LOOKUP* - # - # Translation data is organized as a nested hash using the upper-level keys - # as namespaces. <em>E.g.</em>, ActionView ships with the translation: - # <tt>:date => {:formats => {:short => "%b %d"}}</tt>. - # - # Translations can be looked up at any level of this hash using the key argument - # and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt> - # returns the whole translations hash <tt>{:formats => {:short => "%b %d"}}</tt>. - # - # Key can be either a single key or a dot-separated key (both Strings and Symbols - # work). <em>E.g.</em>, the short format can be looked up using both: - # I18n.t 'date.formats.short' - # I18n.t :'date.formats.short' - # - # Scope can be either a single key, a dot-separated key or an array of keys - # or dot-separated keys. Keys and scopes can be combined freely. So these - # examples will all look up the same short date format: - # I18n.t 'date.formats.short' - # I18n.t 'formats.short', :scope => 'date' - # I18n.t 'short', :scope => 'date.formats' - # I18n.t 'short', :scope => %w(date formats) - # - # *INTERPOLATION* - # - # Translations can contain interpolation variables which will be replaced by - # values passed to #translate as part of the options hash, with the keys matching - # the interpolation variable names. - # - # <em>E.g.</em>, with a translation <tt>:foo => "foo {{bar}}"</tt> the option - # value for the key +bar+ will be interpolated into the translation: - # I18n.t :foo, :bar => 'baz' # => 'foo baz' - # - # *PLURALIZATION* - # - # Translation data can contain pluralized translations. Pluralized translations - # are arrays of singluar/plural versions of translations like <tt>['Foo', 'Foos']</tt>. - # - # Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English - # pluralization rules. Other algorithms can be supported by custom backends. - # - # This returns the singular version of a pluralized translation: - # I18n.t :foo, :count => 1 # => 'Foo' - # - # These both return the plural version of a pluralized translation: - # I18n.t :foo, :count => 0 # => 'Foos' - # I18n.t :foo, :count => 2 # => 'Foos' - # - # The <tt>:count</tt> option can be used both for pluralization and interpolation. - # <em>E.g.</em>, with the translation - # <tt>:foo => ['{{count}} foo', '{{count}} foos']</tt>, count will - # be interpolated to the pluralized translation: - # I18n.t :foo, :count => 1 # => '1 foo' - # - # *DEFAULTS* - # - # This returns the translation for <tt>:foo</tt> or <tt>default</tt> if no translation was found: - # I18n.t :foo, :default => 'default' - # - # This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no - # translation for <tt>:foo</tt> was found: - # I18n.t :foo, :default => :bar - # - # Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> - # or <tt>default</tt> if no translations for <tt>:foo</tt> and <tt>:bar</tt> were found. - # I18n.t :foo, :default => [:bar, 'default'] - # - # <b>BULK LOOKUP</b> - # - # This returns an array with the translations for <tt>:foo</tt> and <tt>:bar</tt>. - # I18n.t [:foo, :bar] - # - # Can be used with dot-separated nested keys: - # I18n.t [:'baz.foo', :'baz.bar'] - # - # Which is the same as using a scope option: - # I18n.t [:foo, :bar], :scope => :baz - def translate(key, options = {}) - locale = options.delete(:locale) || I18n.locale - backend.translate(locale, key, options) - rescue I18n::ArgumentError => e - raise e if options[:raise] - send(@@exception_handler, e, locale, key, options) - end - alias :t :translate - - # Localizes certain objects, such as dates and numbers to local formatting. - def localize(object, options = {}) - locale = options[:locale] || I18n.locale - format = options[:format] || :default - backend.localize(locale, object, format) - end - alias :l :localize - - protected - # Handles exceptions raised in the backend. All exceptions except for - # MissingTranslationData exceptions are re-raised. When a MissingTranslationData - # was caught and the option :raise is not set the handler returns an error - # message string containing the key/scope. - def default_exception_handler(exception, locale, key, options) - return exception.message if MissingTranslationData === exception - raise exception - end - - # Merges the given locale, key and scope into a single array of keys. - # Splits keys that contain dots into multiple keys. Makes sure all - # keys are Symbols. - def normalize_translation_keys(locale, key, scope) - keys = [locale] + Array(scope) + [key] - keys = keys.map { |k| k.to_s.split(/\./) } - keys.flatten.map { |k| k.to_sym } - end - end -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n/backend/simple.rb deleted file mode 100644 index c32cc76f34..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n/backend/simple.rb +++ /dev/null @@ -1,215 +0,0 @@ -require 'i18n/exceptions' - -module I18n - module Backend - class Simple - INTERPOLATION_RESERVED_KEYS = %w(scope default) - MATCH = /(\\\\)?\{\{([^\}]+)\}\}/ - - # Accepts a list of paths to translation files. Loads translations from - # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml - # for details. - def load_translations(*filenames) - filenames.each { |filename| load_file(filename) } - end - - # Stores translations for the given locale in memory. - # This uses a deep merge for the translations hash, so existing - # translations will be overwritten by new ones only at the deepest - # level of the hash. - def store_translations(locale, data) - merge_translations(locale, data) - end - - def translate(locale, key, options = {}) - raise InvalidLocale.new(locale) if locale.nil? - return key.map { |k| translate(locale, k, options) } if key.is_a? Array - - reserved = :scope, :default - count, scope, default = options.values_at(:count, *reserved) - options.delete(:default) - values = options.reject { |name, value| reserved.include?(name) } - - entry = lookup(locale, key, scope) - if entry.nil? - entry = default(locale, default, options) - if entry.nil? - raise(I18n::MissingTranslationData.new(locale, key, options)) - end - end - entry = pluralize(locale, entry, count) - entry = interpolate(locale, entry, values) - entry - end - - # Acts the same as +strftime+, but returns a localized version of the - # formatted date string. Takes a key from the date/time formats - # translations as a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>). - def localize(locale, object, format = :default) - raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime) - - type = object.respond_to?(:sec) ? 'time' : 'date' - # TODO only translate these if format is a String? - formats = translate(locale, :"#{type}.formats") - format = formats[format.to_sym] if formats && formats[format.to_sym] - # TODO raise exception unless format found? - format = format.to_s.dup - - # TODO only translate these if the format string is actually present - # TODO check which format strings are present, then bulk translate then, then replace them - format.gsub!(/%a/, translate(locale, :"date.abbr_day_names")[object.wday]) - format.gsub!(/%A/, translate(locale, :"date.day_names")[object.wday]) - format.gsub!(/%b/, translate(locale, :"date.abbr_month_names")[object.mon]) - format.gsub!(/%B/, translate(locale, :"date.month_names")[object.mon]) - format.gsub!(/%p/, translate(locale, :"time.#{object.hour < 12 ? :am : :pm}")) if object.respond_to? :hour - object.strftime(format) - end - - def initialized? - @initialized ||= false - end - - # Returns an array of locales for which translations are available - def available_locales - init_translations unless initialized? - translations.keys - end - - def reload! - @initialized = false - @translations = nil - end - - protected - def init_translations - load_translations(*I18n.load_path.flatten) - @initialized = true - end - - def translations - @translations ||= {} - end - - # Looks up a translation from the translations hash. Returns nil if - # eiher key is nil, or locale, scope or key do not exist as a key in the - # nested translations hash. Splits keys or scopes containing dots - # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as - # <tt>%w(currency format)</tt>. - def lookup(locale, key, scope = []) - return unless key - init_translations unless initialized? - keys = I18n.send(:normalize_translation_keys, locale, key, scope) - keys.inject(translations) do |result, k| - if (x = result[k.to_sym]).nil? - return nil - else - x - end - end - end - - # Evaluates a default translation. - # If the given default is a String it is used literally. If it is a Symbol - # it will be translated with the given options. If it is an Array the first - # translation yielded will be returned. - # - # <em>I.e.</em>, <tt>default(locale, [:foo, 'default'])</tt> will return +default+ if - # <tt>translate(locale, :foo)</tt> does not yield a result. - def default(locale, default, options = {}) - case default - when String then default - when Symbol then translate locale, default, options - when Array then default.each do |obj| - result = default(locale, obj, options.dup) and return result - end and nil - end - rescue MissingTranslationData - nil - end - - # Picks a translation from an array according to English pluralization - # rules. It will pick the first translation if count is not equal to 1 - # and the second translation if it is equal to 1. Other backends can - # implement more flexible or complex pluralization rules. - def pluralize(locale, entry, count) - return entry unless entry.is_a?(Hash) and count - # raise InvalidPluralizationData.new(entry, count) unless entry.is_a?(Hash) - key = :zero if count == 0 && entry.has_key?(:zero) - key ||= count == 1 ? :one : :other - raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key) - entry[key] - end - - # Interpolates values into a given string. - # - # interpolate "file {{file}} opened by \\{{user}}", :file => 'test.txt', :user => 'Mr. X' - # # => "file test.txt opened by {{user}}" - # - # Note that you have to double escape the <tt>\\</tt> when you want to escape - # the <tt>{{...}}</tt> key in a string (once for the string and once for the - # interpolation). - def interpolate(locale, string, values = {}) - return string unless string.is_a?(String) - - string.gsub(MATCH) do - escaped, pattern, key = $1, $2, $2.to_sym - - if escaped - pattern - elsif INTERPOLATION_RESERVED_KEYS.include?(pattern) - raise ReservedInterpolationKey.new(pattern, string) - elsif !values.include?(key) - raise MissingInterpolationArgument.new(pattern, string) - else - values[key].to_s - end - end - end - - # Loads a single translations file by delegating to #load_rb or - # #load_yml depending on the file extension and directly merges the - # data to the existing translations. Raises I18n::UnknownFileType - # for all other file extensions. - def load_file(filename) - type = File.extname(filename).tr('.', '').downcase - raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}") - data = send :"load_#{type}", filename # TODO raise a meaningful exception if this does not yield a Hash - data.each { |locale, d| merge_translations(locale, d) } - end - - # Loads a plain Ruby translations file. eval'ing the file must yield - # a Hash containing translation data with locales as toplevel keys. - def load_rb(filename) - eval(IO.read(filename), binding, filename) - end - - # Loads a YAML translations file. The data must have locales as - # toplevel keys. - def load_yml(filename) - require 'yaml' unless defined? :YAML - YAML::load(IO.read(filename)) - end - - # Deep merges the given translations hash with the existing translations - # for the given locale - def merge_translations(locale, data) - locale = locale.to_sym - translations[locale] ||= {} - data = deep_symbolize_keys(data) - - # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809 - merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 } - translations[locale].merge!(data, &merger) - end - - # Return a new hash with all keys and nested keys converted to symbols. - def deep_symbolize_keys(hash) - hash.inject({}) { |result, (key, value)| - value = deep_symbolize_keys(value) if value.is_a? Hash - result[(key.to_sym rescue key) || key] = value - result - } - end - end - end -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n/exceptions.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n/exceptions.rb deleted file mode 100644 index 6897055d6d..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/lib/i18n/exceptions.rb +++ /dev/null @@ -1,53 +0,0 @@ -module I18n - class ArgumentError < ::ArgumentError; end - - class InvalidLocale < ArgumentError - attr_reader :locale - def initialize(locale) - @locale = locale - super "#{locale.inspect} is not a valid locale" - end - end - - class MissingTranslationData < ArgumentError - attr_reader :locale, :key, :options - def initialize(locale, key, options) - @key, @locale, @options = key, locale, options - keys = I18n.send(:normalize_translation_keys, locale, key, options[:scope]) - keys << 'no key' if keys.size < 2 - super "translation missing: #{keys.join(', ')}" - end - end - - class InvalidPluralizationData < ArgumentError - attr_reader :entry, :count - def initialize(entry, count) - @entry, @count = entry, count - super "translation data #{entry.inspect} can not be used with :count => #{count}" - end - end - - class MissingInterpolationArgument < ArgumentError - attr_reader :key, :string - def initialize(key, string) - @key, @string = key, string - super "interpolation argument #{key} missing in #{string.inspect}" - end - end - - class ReservedInterpolationKey < ArgumentError - attr_reader :key, :string - def initialize(key, string) - @key, @string = key, string - super "reserved key #{key.inspect} used in #{string.inspect}" - end - end - - class UnknownFileType < ArgumentError - attr_reader :type, :filename - def initialize(type, filename) - @type, @filename = type, filename - super "can not load translations from #{filename}, the file type #{type} is not known" - end - end -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/all.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/test/all.rb deleted file mode 100644 index 353712da49..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/all.rb +++ /dev/null @@ -1,5 +0,0 @@ -dir = File.dirname(__FILE__) -require dir + '/i18n_test.rb' -require dir + '/simple_backend_test.rb' -require dir + '/i18n_exceptions_test.rb' -# *require* dir + '/custom_backend_test.rb'
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_exceptions_test.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_exceptions_test.rb deleted file mode 100644 index 4e78e71b34..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_exceptions_test.rb +++ /dev/null @@ -1,99 +0,0 @@ -$:.unshift "lib" - -require 'rubygems' -require 'test/unit' -require 'i18n' -require 'active_support' - -class I18nExceptionsTest < Test::Unit::TestCase - def test_invalid_locale_stores_locale - force_invalid_locale - rescue I18n::ArgumentError => e - assert_nil e.locale - end - - def test_invalid_locale_message - force_invalid_locale - rescue I18n::ArgumentError => e - assert_equal 'nil is not a valid locale', e.message - end - - def test_missing_translation_data_stores_locale_key_and_options - force_missing_translation_data - rescue I18n::ArgumentError => e - options = {:scope => :bar} - assert_equal 'de', e.locale - assert_equal :foo, e.key - assert_equal options, e.options - end - - def test_missing_translation_data_message - force_missing_translation_data - rescue I18n::ArgumentError => e - assert_equal 'translation missing: de, bar, foo', e.message - end - - def test_invalid_pluralization_data_stores_entry_and_count - force_invalid_pluralization_data - rescue I18n::ArgumentError => e - assert_equal [:bar], e.entry - assert_equal 1, e.count - end - - def test_invalid_pluralization_data_message - force_invalid_pluralization_data - rescue I18n::ArgumentError => e - assert_equal 'translation data [:bar] can not be used with :count => 1', e.message - end - - def test_missing_interpolation_argument_stores_key_and_string - force_missing_interpolation_argument - rescue I18n::ArgumentError => e - assert_equal 'bar', e.key - assert_equal "{{bar}}", e.string - end - - def test_missing_interpolation_argument_message - force_missing_interpolation_argument - rescue I18n::ArgumentError => e - assert_equal 'interpolation argument bar missing in "{{bar}}"', e.message - end - - def test_reserved_interpolation_key_stores_key_and_string - force_reserved_interpolation_key - rescue I18n::ArgumentError => e - assert_equal 'scope', e.key - assert_equal "{{scope}}", e.string - end - - def test_reserved_interpolation_key_message - force_reserved_interpolation_key - rescue I18n::ArgumentError => e - assert_equal 'reserved key "scope" used in "{{scope}}"', e.message - end - - private - def force_invalid_locale - I18n.backend.translate nil, :foo - end - - def force_missing_translation_data - I18n.backend.store_translations 'de', :bar => nil - I18n.backend.translate 'de', :foo, :scope => :bar - end - - def force_invalid_pluralization_data - I18n.backend.store_translations 'de', :foo => [:bar] - I18n.backend.translate 'de', :foo, :count => 1 - end - - def force_missing_interpolation_argument - I18n.backend.store_translations 'de', :foo => "{{bar}}" - I18n.backend.translate 'de', :foo, :baz => 'baz' - end - - def force_reserved_interpolation_key - I18n.backend.store_translations 'de', :foo => "{{scope}}" - I18n.backend.translate 'de', :foo, :baz => 'baz' - end -end
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb deleted file mode 100644 index 2835ec4eab..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/i18n_test.rb +++ /dev/null @@ -1,124 +0,0 @@ -$:.unshift "lib" - -require 'rubygems' -require 'test/unit' -require 'i18n' -require 'active_support' - -class I18nTest < Test::Unit::TestCase - def setup - I18n.backend.store_translations :'en', { - :currency => { - :format => { - :separator => '.', - :delimiter => ',', - } - } - } - end - - def test_uses_simple_backend_set_by_default - assert I18n.backend.is_a?(I18n::Backend::Simple) - end - - def test_can_set_backend - assert_nothing_raised{ I18n.backend = self } - assert_equal self, I18n.backend - I18n.backend = I18n::Backend::Simple.new - end - - def test_uses_en_us_as_default_locale_by_default - assert_equal 'en', I18n.default_locale - end - - def test_can_set_default_locale - assert_nothing_raised{ I18n.default_locale = 'de' } - assert_equal 'de', I18n.default_locale - I18n.default_locale = 'en' - end - - def test_uses_default_locale_as_locale_by_default - assert_equal I18n.default_locale, I18n.locale - end - - def test_can_set_locale_to_thread_current - assert_nothing_raised{ I18n.locale = 'de' } - assert_equal 'de', I18n.locale - assert_equal 'de', Thread.current[:locale] - I18n.locale = 'en' - end - - def test_can_set_exception_handler - assert_nothing_raised{ I18n.exception_handler = :custom_exception_handler } - I18n.exception_handler = :default_exception_handler # revert it - end - - def test_uses_custom_exception_handler - I18n.exception_handler = :custom_exception_handler - I18n.expects(:custom_exception_handler) - I18n.translate :bogus - I18n.exception_handler = :default_exception_handler # revert it - end - - def test_delegates_translate_to_backend - I18n.backend.expects(:translate).with 'de', :foo, {} - I18n.translate :foo, :locale => 'de' - end - - def test_delegates_localize_to_backend - I18n.backend.expects(:localize).with 'de', :whatever, :default - I18n.localize :whatever, :locale => 'de' - end - - def test_translate_given_no_locale_uses_i18n_locale - I18n.backend.expects(:translate).with 'en', :foo, {} - I18n.translate :foo - end - - def test_translate_on_nested_symbol_keys_works - assert_equal ".", I18n.t(:'currency.format.separator') - end - - def test_translate_with_nested_string_keys_works - assert_equal ".", I18n.t('currency.format.separator') - end - - def test_translate_with_array_as_scope_works - assert_equal ".", I18n.t(:separator, :scope => ['currency.format']) - end - - def test_translate_with_array_containing_dot_separated_strings_as_scope_works - assert_equal ".", I18n.t(:separator, :scope => ['currency.format']) - end - - def test_translate_with_key_array_and_dot_separated_scope_works - assert_equal [".", ","], I18n.t(%w(separator delimiter), :scope => 'currency.format') - end - - def test_translate_with_dot_separated_key_array_and_scope_works - assert_equal [".", ","], I18n.t(%w(format.separator format.delimiter), :scope => 'currency') - end - - def test_translate_with_options_using_scope_works - I18n.backend.expects(:translate).with('de', :precision, :scope => :"currency.format") - I18n.with_options :locale => 'de', :scope => :'currency.format' do |locale| - locale.t :precision - end - end - - # def test_translate_given_no_args_raises_missing_translation_data - # assert_equal "translation missing: en, no key", I18n.t - # end - - def test_translate_given_a_bogus_key_raises_missing_translation_data - assert_equal "translation missing: en, bogus", I18n.t(:bogus) - end - - def test_localize_nil_raises_argument_error - assert_raise(I18n::ArgumentError) { I18n.l nil } - end - - def test_localize_object_raises_argument_error - assert_raise(I18n::ArgumentError) { I18n.l Object.new } - end -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/locale/en.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/test/locale/en.rb deleted file mode 100644 index 6044ce10d9..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/locale/en.rb +++ /dev/null @@ -1 +0,0 @@ -{:'en-Ruby' => {:foo => {:bar => "baz"}}}
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/locale/en.yml b/activesupport/lib/active_support/vendor/i18n-0.1.3/test/locale/en.yml deleted file mode 100644 index 0b298c9c0e..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/locale/en.yml +++ /dev/null @@ -1,3 +0,0 @@ -en-Yaml: - foo: - bar: baz
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb b/activesupport/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb deleted file mode 100644 index a1696c77f6..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.1.3/test/simple_backend_test.rb +++ /dev/null @@ -1,567 +0,0 @@ -# encoding: utf-8 -$:.unshift "lib" - -require 'rubygems' -require 'test/unit' -require 'i18n' -require 'time' -require 'yaml' - -module I18nSimpleBackendTestSetup - def setup_backend - # backend_reset_translations! - @backend = I18n::Backend::Simple.new - @backend.store_translations 'en', :foo => {:bar => 'bar', :baz => 'baz'} - @locale_dir = File.dirname(__FILE__) + '/locale' - end - alias :setup :setup_backend - - # def backend_reset_translations! - # I18n::Backend::Simple::ClassMethods.send :class_variable_set, :@@translations, {} - # end - - def backend_get_translations - # I18n::Backend::Simple::ClassMethods.send :class_variable_get, :@@translations - @backend.instance_variable_get :@translations - end - - def add_datetime_translations - @backend.store_translations :'de', { - :date => { - :formats => { - :default => "%d.%m.%Y", - :short => "%d. %b", - :long => "%d. %B %Y", - }, - :day_names => %w(Sonntag Montag Dienstag Mittwoch Donnerstag Freitag Samstag), - :abbr_day_names => %w(So Mo Di Mi Do Fr Sa), - :month_names => %w(Januar Februar März April Mai Juni Juli August September Oktober November Dezember).unshift(nil), - :abbr_month_names => %w(Jan Feb Mar Apr Mai Jun Jul Aug Sep Okt Nov Dez).unshift(nil), - :order => [:day, :month, :year] - }, - :time => { - :formats => { - :default => "%a, %d. %b %Y %H:%M:%S %z", - :short => "%d. %b %H:%M", - :long => "%d. %B %Y %H:%M", - }, - :am => 'am', - :pm => 'pm' - }, - :datetime => { - :distance_in_words => { - :half_a_minute => 'half a minute', - :less_than_x_seconds => { - :one => 'less than 1 second', - :other => 'less than {{count}} seconds' - }, - :x_seconds => { - :one => '1 second', - :other => '{{count}} seconds' - }, - :less_than_x_minutes => { - :one => 'less than a minute', - :other => 'less than {{count}} minutes' - }, - :x_minutes => { - :one => '1 minute', - :other => '{{count}} minutes' - }, - :about_x_hours => { - :one => 'about 1 hour', - :other => 'about {{count}} hours' - }, - :x_days => { - :one => '1 day', - :other => '{{count}} days' - }, - :about_x_months => { - :one => 'about 1 month', - :other => 'about {{count}} months' - }, - :x_months => { - :one => '1 month', - :other => '{{count}} months' - }, - :about_x_years => { - :one => 'about 1 year', - :other => 'about {{count}} year' - }, - :over_x_years => { - :one => 'over 1 year', - :other => 'over {{count}} years' - } - } - } - } - end -end - -class I18nSimpleBackendTranslationsTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def test_store_translations_adds_translations # no, really :-) - @backend.store_translations :'en', :foo => 'bar' - assert_equal Hash[:'en', {:foo => 'bar'}], backend_get_translations - end - - def test_store_translations_deep_merges_translations - @backend.store_translations :'en', :foo => {:bar => 'bar'} - @backend.store_translations :'en', :foo => {:baz => 'baz'} - assert_equal Hash[:'en', {:foo => {:bar => 'bar', :baz => 'baz'}}], backend_get_translations - end - - def test_store_translations_forces_locale_to_sym - @backend.store_translations 'en', :foo => 'bar' - assert_equal Hash[:'en', {:foo => 'bar'}], backend_get_translations - end - - def test_store_translations_converts_keys_to_symbols - # backend_reset_translations! - @backend.store_translations 'en', 'foo' => {'bar' => 'bar', 'baz' => 'baz'} - assert_equal Hash[:'en', {:foo => {:bar => 'bar', :baz => 'baz'}}], backend_get_translations - end -end - -class I18nSimpleBackendAvailableLocalesTest < Test::Unit::TestCase - def test_available_locales - @backend = I18n::Backend::Simple.new - @backend.store_translations 'de', :foo => 'bar' - @backend.store_translations 'en', :foo => 'foo' - - assert_equal ['de', 'en'], @backend.available_locales.map{|locale| locale.to_s }.sort - end -end - -class I18nSimpleBackendTranslateTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def test_translate_calls_lookup_with_locale_given - @backend.expects(:lookup).with('de', :bar, [:foo]).returns 'bar' - @backend.translate 'de', :bar, :scope => [:foo] - end - - def test_given_no_keys_it_returns_the_default - assert_equal 'default', @backend.translate('en', nil, :default => 'default') - end - - def test_translate_given_a_symbol_as_a_default_translates_the_symbol - assert_equal 'bar', @backend.translate('en', nil, :scope => [:foo], :default => :bar) - end - - def test_translate_given_an_array_as_default_uses_the_first_match - assert_equal 'bar', @backend.translate('en', :does_not_exist, :scope => [:foo], :default => [:does_not_exist_2, :bar]) - end - - def test_translate_given_an_array_of_inexistent_keys_it_raises_missing_translation_data - assert_raise I18n::MissingTranslationData do - @backend.translate('en', :does_not_exist, :scope => [:foo], :default => [:does_not_exist_2, :does_not_exist_3]) - end - end - - def test_translate_an_array_of_keys_translates_all_of_them - assert_equal %w(bar baz), @backend.translate('en', [:bar, :baz], :scope => [:foo]) - end - - def test_translate_calls_pluralize - @backend.expects(:pluralize).with 'en', 'bar', 1 - @backend.translate 'en', :bar, :scope => [:foo], :count => 1 - end - - def test_translate_calls_interpolate - @backend.expects(:interpolate).with 'en', 'bar', {} - @backend.translate 'en', :bar, :scope => [:foo] - end - - def test_translate_calls_interpolate_including_count_as_a_value - @backend.expects(:interpolate).with 'en', 'bar', {:count => 1} - @backend.translate 'en', :bar, :scope => [:foo], :count => 1 - end - - def test_translate_given_nil_as_a_locale_raises_an_argument_error - assert_raise(I18n::InvalidLocale){ @backend.translate nil, :bar } - end - - def test_translate_with_a_bogus_key_and_no_default_raises_missing_translation_data - assert_raise(I18n::MissingTranslationData){ @backend.translate 'de', :bogus } - end -end - -class I18nSimpleBackendLookupTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - # useful because this way we can use the backend with no key for interpolation/pluralization - def test_lookup_given_nil_as_a_key_returns_nil - assert_nil @backend.send(:lookup, 'en', nil) - end - - def test_lookup_given_nested_keys_looks_up_a_nested_hash_value - assert_equal 'bar', @backend.send(:lookup, 'en', :bar, [:foo]) - end -end - -class I18nSimpleBackendPluralizeTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def test_pluralize_given_nil_returns_the_given_entry - entry = {:one => 'bar', :other => 'bars'} - assert_equal entry, @backend.send(:pluralize, nil, entry, nil) - end - - def test_pluralize_given_0_returns_zero_string_if_zero_key_given - assert_equal 'zero', @backend.send(:pluralize, nil, {:zero => 'zero', :one => 'bar', :other => 'bars'}, 0) - end - - def test_pluralize_given_0_returns_plural_string_if_no_zero_key_given - assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 0) - end - - def test_pluralize_given_1_returns_singular_string - assert_equal 'bar', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 1) - end - - def test_pluralize_given_2_returns_plural_string - assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 2) - end - - def test_pluralize_given_3_returns_plural_string - assert_equal 'bars', @backend.send(:pluralize, nil, {:one => 'bar', :other => 'bars'}, 3) - end - - def test_interpolate_given_incomplete_pluralization_data_raises_invalid_pluralization_data - assert_raise(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, {:one => 'bar'}, 2) } - end - - # def test_interpolate_given_a_string_raises_invalid_pluralization_data - # assert_raise(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, 'bar', 2) } - # end - # - # def test_interpolate_given_an_array_raises_invalid_pluralization_data - # assert_raise(I18n::InvalidPluralizationData){ @backend.send(:pluralize, nil, ['bar'], 2) } - # end -end - -class I18nSimpleBackendInterpolateTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def test_interpolate_given_a_value_hash_interpolates_the_values_to_the_string - assert_equal 'Hi David!', @backend.send(:interpolate, nil, 'Hi {{name}}!', :name => 'David') - end - - def test_interpolate_given_a_value_hash_interpolates_into_unicode_string - assert_equal 'Häi David!', @backend.send(:interpolate, nil, 'Häi {{name}}!', :name => 'David') - end - - def test_interpolate_given_an_unicode_value_hash_interpolates_to_the_string - assert_equal 'Hi ゆきひろ!', @backend.send(:interpolate, nil, 'Hi {{name}}!', :name => 'ゆきひろ') - end - - def test_interpolate_given_an_unicode_value_hash_interpolates_into_unicode_string - assert_equal 'こんにちは、ゆきひろさん!', @backend.send(:interpolate, nil, 'こんにちは、{{name}}さん!', :name => 'ゆきひろ') - end - - if Kernel.const_defined?(:Encoding) - def test_interpolate_given_a_non_unicode_multibyte_value_hash_interpolates_into_a_string_with_the_same_encoding - assert_equal euc_jp('Hi ゆきひろ!'), @backend.send(:interpolate, nil, 'Hi {{name}}!', :name => euc_jp('ゆきひろ')) - end - - def test_interpolate_given_an_unicode_value_hash_into_a_non_unicode_multibyte_string_raises_encoding_compatibility_error - assert_raise(Encoding::CompatibilityError) do - @backend.send(:interpolate, nil, euc_jp('こんにちは、{{name}}さん!'), :name => 'ゆきひろ') - end - end - - def test_interpolate_given_a_non_unicode_multibyte_value_hash_into_an_unicode_string_raises_encoding_compatibility_error - assert_raise(Encoding::CompatibilityError) do - @backend.send(:interpolate, nil, 'こんにちは、{{name}}さん!', :name => euc_jp('ゆきひろ')) - end - end - end - - def test_interpolate_given_nil_as_a_string_returns_nil - assert_nil @backend.send(:interpolate, nil, nil, :name => 'David') - end - - def test_interpolate_given_an_non_string_as_a_string_returns_nil - assert_equal [], @backend.send(:interpolate, nil, [], :name => 'David') - end - - def test_interpolate_given_a_values_hash_with_nil_values_interpolates_the_string - assert_equal 'Hi !', @backend.send(:interpolate, nil, 'Hi {{name}}!', {:name => nil}) - end - - def test_interpolate_given_an_empty_values_hash_raises_missing_interpolation_argument - assert_raise(I18n::MissingInterpolationArgument) { @backend.send(:interpolate, nil, 'Hi {{name}}!', {}) } - end - - def test_interpolate_given_a_string_containing_a_reserved_key_raises_reserved_interpolation_key - assert_raise(I18n::ReservedInterpolationKey) { @backend.send(:interpolate, nil, '{{default}}', {:default => nil}) } - end - - private - - def euc_jp(string) - string.encode!(Encoding::EUC_JP) - end -end - -class I18nSimpleBackendLocalizeDateTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def setup - @backend = I18n::Backend::Simple.new - add_datetime_translations - @date = Date.new 2008, 1, 1 - end - - def test_translate_given_the_short_format_it_uses_it - assert_equal '01. Jan', @backend.localize('de', @date, :short) - end - - def test_translate_given_the_long_format_it_uses_it - assert_equal '01. Januar 2008', @backend.localize('de', @date, :long) - end - - def test_translate_given_the_default_format_it_uses_it - assert_equal '01.01.2008', @backend.localize('de', @date, :default) - end - - def test_translate_given_a_day_name_format_it_returns_a_day_name - assert_equal 'Dienstag', @backend.localize('de', @date, '%A') - end - - def test_translate_given_an_abbr_day_name_format_it_returns_an_abbrevated_day_name - assert_equal 'Di', @backend.localize('de', @date, '%a') - end - - def test_translate_given_a_month_name_format_it_returns_a_month_name - assert_equal 'Januar', @backend.localize('de', @date, '%B') - end - - def test_translate_given_an_abbr_month_name_format_it_returns_an_abbrevated_month_name - assert_equal 'Jan', @backend.localize('de', @date, '%b') - end - - def test_translate_given_no_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de', @date } - end - - def test_translate_given_an_unknown_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de', @date, '%x' } - end - - def test_localize_nil_raises_argument_error - assert_raise(I18n::ArgumentError) { @backend.localize 'de', nil } - end - - def test_localize_object_raises_argument_error - assert_raise(I18n::ArgumentError) { @backend.localize 'de', Object.new } - end -end - -class I18nSimpleBackendLocalizeDateTimeTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def setup - @backend = I18n::Backend::Simple.new - add_datetime_translations - @morning = DateTime.new 2008, 1, 1, 6 - @evening = DateTime.new 2008, 1, 1, 18 - end - - def test_translate_given_the_short_format_it_uses_it - assert_equal '01. Jan 06:00', @backend.localize('de', @morning, :short) - end - - def test_translate_given_the_long_format_it_uses_it - assert_equal '01. Januar 2008 06:00', @backend.localize('de', @morning, :long) - end - - def test_translate_given_the_default_format_it_uses_it - assert_equal 'Di, 01. Jan 2008 06:00:00 +0000', @backend.localize('de', @morning, :default) - end - - def test_translate_given_a_day_name_format_it_returns_the_correct_day_name - assert_equal 'Dienstag', @backend.localize('de', @morning, '%A') - end - - def test_translate_given_an_abbr_day_name_format_it_returns_the_correct_abbrevated_day_name - assert_equal 'Di', @backend.localize('de', @morning, '%a') - end - - def test_translate_given_a_month_name_format_it_returns_the_correct_month_name - assert_equal 'Januar', @backend.localize('de', @morning, '%B') - end - - def test_translate_given_an_abbr_month_name_format_it_returns_the_correct_abbrevated_month_name - assert_equal 'Jan', @backend.localize('de', @morning, '%b') - end - - def test_translate_given_a_meridian_indicator_format_it_returns_the_correct_meridian_indicator - assert_equal 'am', @backend.localize('de', @morning, '%p') - assert_equal 'pm', @backend.localize('de', @evening, '%p') - end - - def test_translate_given_no_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de', @morning } - end - - def test_translate_given_an_unknown_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de', @morning, '%x' } - end -end - -class I18nSimpleBackendLocalizeTimeTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def setup - @old_timezone, ENV['TZ'] = ENV['TZ'], 'UTC' - @backend = I18n::Backend::Simple.new - add_datetime_translations - @morning = Time.parse '2008-01-01 6:00 UTC' - @evening = Time.parse '2008-01-01 18:00 UTC' - end - - def teardown - @old_timezone ? ENV['TZ'] = @old_timezone : ENV.delete('TZ') - end - - def test_translate_given_the_short_format_it_uses_it - assert_equal '01. Jan 06:00', @backend.localize('de', @morning, :short) - end - - def test_translate_given_the_long_format_it_uses_it - assert_equal '01. Januar 2008 06:00', @backend.localize('de', @morning, :long) - end - - # TODO Seems to break on Windows because ENV['TZ'] is ignored. What's a better way to do this? - # def test_translate_given_the_default_format_it_uses_it - # assert_equal 'Di, 01. Jan 2008 06:00:00 +0000', @backend.localize('de', @morning, :default) - # end - - def test_translate_given_a_day_name_format_it_returns_the_correct_day_name - assert_equal 'Dienstag', @backend.localize('de', @morning, '%A') - end - - def test_translate_given_an_abbr_day_name_format_it_returns_the_correct_abbrevated_day_name - assert_equal 'Di', @backend.localize('de', @morning, '%a') - end - - def test_translate_given_a_month_name_format_it_returns_the_correct_month_name - assert_equal 'Januar', @backend.localize('de', @morning, '%B') - end - - def test_translate_given_an_abbr_month_name_format_it_returns_the_correct_abbrevated_month_name - assert_equal 'Jan', @backend.localize('de', @morning, '%b') - end - - def test_translate_given_a_meridian_indicator_format_it_returns_the_correct_meridian_indicator - assert_equal 'am', @backend.localize('de', @morning, '%p') - assert_equal 'pm', @backend.localize('de', @evening, '%p') - end - - def test_translate_given_no_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de', @morning } - end - - def test_translate_given_an_unknown_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de', @morning, '%x' } - end -end - -class I18nSimpleBackendHelperMethodsTest < Test::Unit::TestCase - def setup - @backend = I18n::Backend::Simple.new - end - - def test_deep_symbolize_keys_works - result = @backend.send :deep_symbolize_keys, 'foo' => {'bar' => {'baz' => 'bar'}} - expected = {:foo => {:bar => {:baz => 'bar'}}} - assert_equal expected, result - end -end - -class I18nSimpleBackendLoadTranslationsTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def test_load_translations_with_unknown_file_type_raises_exception - assert_raise(I18n::UnknownFileType) { @backend.load_translations "#{@locale_dir}/en.xml" } - end - - def test_load_translations_with_ruby_file_type_does_not_raise_exception - assert_nothing_raised { @backend.load_translations "#{@locale_dir}/en.rb" } - end - - def test_load_rb_loads_data_from_ruby_file - data = @backend.send :load_rb, "#{@locale_dir}/en.rb" - assert_equal({:'en-Ruby' => {:foo => {:bar => "baz"}}}, data) - end - - def test_load_rb_loads_data_from_yaml_file - data = @backend.send :load_yml, "#{@locale_dir}/en.yml" - assert_equal({'en-Yaml' => {'foo' => {'bar' => 'baz'}}}, data) - end - - def test_load_translations_loads_from_different_file_formats - @backend = I18n::Backend::Simple.new - @backend.load_translations "#{@locale_dir}/en.rb", "#{@locale_dir}/en.yml" - expected = { - :'en-Ruby' => {:foo => {:bar => "baz"}}, - :'en-Yaml' => {:foo => {:bar => "baz"}} - } - assert_equal expected, backend_get_translations - end -end - -class I18nSimpleBackendLoadPathTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def teardown - I18n.load_path = [] - end - - def test_nested_load_paths_do_not_break_locale_loading - @backend = I18n::Backend::Simple.new - I18n.load_path = [[File.dirname(__FILE__) + '/locale/en.yml']] - assert_nil backend_get_translations - assert_nothing_raised { @backend.send :init_translations } - assert_not_nil backend_get_translations - end - - def test_adding_arrays_of_filenames_to_load_path_do_not_break_locale_loading - @backend = I18n::Backend::Simple.new - I18n.load_path << Dir[File.dirname(__FILE__) + '/locale/*.{rb,yml}'] - assert_nil backend_get_translations - assert_nothing_raised { @backend.send :init_translations } - assert_not_nil backend_get_translations - end -end - -class I18nSimpleBackendReloadTranslationsTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def setup - @backend = I18n::Backend::Simple.new - I18n.load_path = [File.dirname(__FILE__) + '/locale/en.yml'] - assert_nil backend_get_translations - @backend.send :init_translations - end - - def teardown - I18n.load_path = [] - end - - def test_setup - assert_not_nil backend_get_translations - end - - def test_reload_translations_unloads_translations - @backend.reload! - assert_nil backend_get_translations - end - - def test_reload_translations_uninitializes_translations - @backend.reload! - assert_equal @backend.initialized?, false - end -end diff --git a/activesupport/lib/active_support/whiny_nil.rb b/activesupport/lib/active_support/whiny_nil.rb index c4aaba7ab3..4f6ff7d3b5 100644 --- a/activesupport/lib/active_support/whiny_nil.rb +++ b/activesupport/lib/active_support/whiny_nil.rb @@ -43,10 +43,7 @@ class NilClass private def method_missing(method, *args, &block) - # Ruby 1.9.2: disallow explicit coercion via method_missing. - if method == :to_ary || method == :to_str - super - elsif klass = METHOD_CLASS_MAP[method] + if klass = METHOD_CLASS_MAP[method] raise_nil_warning_for klass, method, caller else super |