diff options
author | Hongli Lai (Phusion) <hongli@phusion.nl> | 2008-09-04 23:01:40 +0200 |
---|---|---|
committer | Hongli Lai (Phusion) <hongli@phusion.nl> | 2008-09-04 23:01:40 +0200 |
commit | c480c1db1f302ab28a255c5423326e51d27ec5ed (patch) | |
tree | ac2afe4deb5ea436d53e421c14650bc627480f45 /activesupport | |
parent | 08704c442d15b16511214731dd94108b737ef407 (diff) | |
parent | d7bd01f543d18e37f9c353d847bda3456bc337c3 (diff) | |
download | rails-c480c1db1f302ab28a255c5423326e51d27ec5ed.tar.gz rails-c480c1db1f302ab28a255c5423326e51d27ec5ed.tar.bz2 rails-c480c1db1f302ab28a255c5423326e51d27ec5ed.zip |
Merge branch 'master' of git@github.com:lifo/docrails
Diffstat (limited to 'activesupport')
61 files changed, 1360 insertions, 1367 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG index 8d3b136d80..0170b95b1b 100644 --- a/activesupport/CHANGELOG +++ b/activesupport/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* Fix Ruby's Time marshaling bug in pre-1.9 versions of Ruby: utc instances are now correctly unmarshaled with a utc zone instead of the system local zone [#900 state:resolved] [Luca Guidi, Geoff Buesing] + * Add Array#in_groups which splits or iterates over the array in specified number of groups. #579. [Adrian Mugnolo] Example: a = (1..10).to_a diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 51067e910e..7e34a871e3 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -21,8 +21,6 @@ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #++ -$:.unshift(File.dirname(__FILE__)) - require 'active_support/vendor' require 'active_support/basic_object' require 'active_support/inflector' @@ -30,7 +28,6 @@ require 'active_support/callbacks' require 'active_support/core_ext' -require 'active_support/clean_logger' require 'active_support/buffered_logger' require 'active_support/gzip' @@ -39,7 +36,6 @@ require 'active_support/cache' require 'active_support/dependencies' require 'active_support/deprecation' -require 'active_support/typed_array' require 'active_support/ordered_hash' require 'active_support/ordered_options' require 'active_support/option_merger' @@ -58,9 +54,9 @@ require 'active_support/base64' require 'active_support/time_with_zone' -I18n.backend.populate do - require 'active_support/locale/en-US.rb' -end +require 'active_support/secure_random' + +I18n.load_translations File.dirname(__FILE__) + '/active_support/locale/en-US.yml' Inflector = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Inflector', 'ActiveSupport::Inflector') Dependencies = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('Dependencies', 'ActiveSupport::Dependencies') diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb index cedc1afe7f..6553d72b4f 100644 --- a/activesupport/lib/active_support/buffered_logger.rb +++ b/activesupport/lib/active_support/buffered_logger.rb @@ -33,11 +33,10 @@ module ActiveSupport attr_accessor :level attr_reader :auto_flushing - attr_reader :buffer def initialize(log, level = DEBUG) @level = level - @buffer = [] + @buffer = {} @auto_flushing = 1 @guard = Mutex.new @@ -60,9 +59,7 @@ module ActiveSupport # If a newline is necessary then create a new message ending with a newline. # Ensures that the original message is not mutated. message = "#{message}\n" unless message[-1] == ?\n - @guard.synchronize do - buffer << message - end + buffer << message auto_flush message end @@ -96,8 +93,8 @@ module ActiveSupport def flush @guard.synchronize do unless buffer.empty? - old_buffer = @buffer - @buffer = [] + old_buffer = buffer + clear_buffer @log.write(old_buffer.join) end end @@ -113,5 +110,13 @@ module ActiveSupport def auto_flush flush if buffer.size >= @auto_flushing end + + def buffer + @buffer[Thread.current] ||= [] + end + + def clear_buffer + @buffer[Thread.current] = [] + end end end diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 437679cc05..659bde64f0 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -17,7 +17,7 @@ module ActiveSupport ensure_cache_path(File.dirname(real_file_path(name))) File.atomic_write(real_file_path(name), cache_path) { |f| Marshal.dump(value, f) } rescue => e - RAILS_DEFAULT_LOGGER.error "Couldn't create cache directory: #{name} (#{e.message})" if RAILS_DEFAULT_LOGGER + logger.error "Couldn't create cache directory: #{name} (#{e.message})" if logger end def delete(name, options = nil) @@ -67,4 +67,4 @@ module ActiveSupport end end end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index a44f877414..f3e4b8c13b 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -3,51 +3,63 @@ module ActiveSupport class MemoryStore < Store def initialize @data = {} - @mutex = Mutex.new + @guard = Monitor.new end def fetch(key, options = {}) - @mutex.synchronize do + @guard.synchronize do super end end def read(name, options = nil) - super - @data[name] + @guard.synchronize do + super + @data[name] + end end def write(name, value, options = nil) - super - @data[name] = value + @guard.synchronize do + super + @data[name] = value.freeze + end end def delete(name, options = nil) - @data.delete(name) + @guard.synchronize do + @data.delete(name) + end end def delete_matched(matcher, options = nil) - @data.delete_if { |k,v| k =~ matcher } + @guard.synchronize do + @data.delete_if { |k,v| k =~ matcher } + end end def exist?(name,options = nil) - @data.has_key?(name) + @guard.synchronize do + @data.has_key?(name) + end end def increment(key, amount = 1) - @mutex.synchronize do + @guard.synchronize do super end end def decrement(key, amount = 1) - @mutex.synchronize do + @guard.synchronize do super end end def clear - @data.clear + @guard.synchronize do + @data.clear + end end end end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 9c59b7ac76..7b905930bb 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -96,15 +96,12 @@ module ActiveSupport end end - def |(chain) - if chain.is_a?(CallbackChain) - chain.each { |callback| self | callback } + # TODO: Decompose into more Array like behavior + def replace_or_append!(chain) + if index = index(chain) + self[index] = chain else - if (found_callback = find(chain)) && (index = index(chain)) - self[index] = chain - else - self << chain - end + self << chain end self end @@ -157,6 +154,14 @@ module ActiveSupport self.class.new(@kind, @method, @options.dup) end + def hash + if @identifier + @identifier.hash + else + @method.hash + end + end + def call(*args, &block) evaluate_method(method, *args, &block) if should_run_callback?(*args) rescue LocalJumpError diff --git a/activesupport/lib/active_support/clean_logger.rb b/activesupport/lib/active_support/clean_logger.rb deleted file mode 100644 index b4c27ebc9d..0000000000 --- a/activesupport/lib/active_support/clean_logger.rb +++ /dev/null @@ -1,127 +0,0 @@ -require 'logger' -require 'active_support/core_ext/class/attribute_accessors' - -# Extensions to the built in Ruby logger. -# -# If you want to use the default log formatter as defined in the Ruby core, then you -# will need to set the formatter for the logger as in: -# -# logger.formatter = Formatter.new -# -# You can then specify the datetime format, for example: -# -# logger.datetime_format = "%Y-%m-%d" -# -# Note: This logger is deprecated in favor of ActiveSupport::BufferedLogger -class Logger - # Set to false to disable the silencer - cattr_accessor :silencer - self.silencer = true - - # Silences the logger for the duration of the block. - def silence(temporary_level = Logger::ERROR) - if silencer - begin - old_logger_level, self.level = level, temporary_level - yield self - ensure - self.level = old_logger_level - end - else - yield self - end - end - - alias :old_datetime_format= :datetime_format= - # Logging date-time format (string passed to +strftime+). Ignored if the formatter - # does not respond to datetime_format=. - def datetime_format=(datetime_format) - formatter.datetime_format = datetime_format if formatter.respond_to?(:datetime_format=) - end - - alias :old_datetime_format :datetime_format - # Get the logging datetime format. Returns nil if the formatter does not support - # datetime formatting. - def datetime_format - formatter.datetime_format if formatter.respond_to?(:datetime_format) - end - - alias :old_formatter :formatter if method_defined?(:formatter) - # Get the current formatter. The default formatter is a SimpleFormatter which only - # displays the log message - def formatter - @formatter ||= SimpleFormatter.new - end - - unless const_defined? :Formatter - class Formatter - Format = "%s, [%s#%d] %5s -- %s: %s\n" - - attr_accessor :datetime_format - - def initialize - @datetime_format = nil - end - - def call(severity, time, progname, msg) - Format % [severity[0..0], format_datetime(time), $$, severity, progname, - msg2str(msg)] - end - - private - def format_datetime(time) - if @datetime_format.nil? - time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d " % time.usec - else - time.strftime(@datetime_format) - end - end - - def msg2str(msg) - case msg - when ::String - msg - when ::Exception - "#{ msg.message } (#{ msg.class })\n" << - (msg.backtrace || []).join("\n") - else - msg.inspect - end - end - end - end - - # Simple formatter which only displays the message. - class SimpleFormatter < Logger::Formatter - # This method is invoked when a log event occurs - def call(severity, timestamp, progname, msg) - "#{String === msg ? msg : msg.inspect}\n" - end - end - - private - alias old_format_message format_message - - # Ruby 1.8.3 transposed the msg and progname arguments to format_message. - # We can't test RUBY_VERSION because some distributions don't keep Ruby - # and its standard library in sync, leading to installations of Ruby 1.8.2 - # with Logger from 1.8.3 and vice versa. - if method_defined?(:formatter=) - def format_message(severity, timestamp, progname, msg) - formatter.call(severity, timestamp, progname, msg) - end - else - def format_message(severity, timestamp, msg, progname) - formatter.call(severity, timestamp, progname, msg) - end - - attr_writer :formatter - public :formatter= - - alias old_format_datetime format_datetime - def format_datetime(datetime) datetime end - - alias old_msg2str msg2str - def msg2str(msg) msg end - end -end diff --git a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb index 8724a492bf..e6143a274b 100644 --- a/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb +++ b/activesupport/lib/active_support/core_ext/class/inheritable_attributes.rb @@ -91,14 +91,14 @@ class Class # :nodoc: def inheritable_attributes @inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES end - + def write_inheritable_attribute(key, value) if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES) @inheritable_attributes = {} end inheritable_attributes[key] = value end - + def write_inheritable_array(key, elements) write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil? write_inheritable_attribute(key, read_inheritable_attribute(key) + elements) @@ -112,7 +112,7 @@ class Class # :nodoc: def read_inheritable_attribute(key) inheritable_attributes[key] end - + def reset_inheritable_attributes @inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES end @@ -123,7 +123,7 @@ class Class # :nodoc: def inherited_with_inheritable_attributes(child) inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes) - + if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES) new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES else @@ -131,7 +131,7 @@ class Class # :nodoc: memo.update(key => value.duplicable? ? value.dup : value) end end - + child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes) end diff --git a/activesupport/lib/active_support/core_ext/date/behavior.rb b/activesupport/lib/active_support/core_ext/date/behavior.rb index 011cc17cbf..bd378eb375 100644 --- a/activesupport/lib/active_support/core_ext/date/behavior.rb +++ b/activesupport/lib/active_support/core_ext/date/behavior.rb @@ -1,3 +1,5 @@ +require 'date' + module ActiveSupport #:nodoc: module CoreExtensions #:nodoc: module Date #:nodoc: @@ -7,6 +9,33 @@ module ActiveSupport #:nodoc: def acts_like_date? true end + + # Date memoizes some instance methods using metaprogramming to wrap + # the methods with one that caches the result in an instance variable. + # + # If a Date is frozen but the memoized method hasn't been called, the + # first call will result in a frozen object error since the memo + # instance variable is uninitialized. + # + # Work around by eagerly memoizing before freezing. + # + # Ruby 1.9 uses a preinitialized instance variable so it's unaffected. + # This hack is as close as we can get to feature detection: + begin + ::Date.today.freeze.jd + rescue => frozen_object_error + if frozen_object_error.message =~ /frozen/ + def freeze #:nodoc: + self.class.private_instance_methods(false).each do |m| + if m.to_s =~ /\A__\d+__\Z/ + instance_variable_set(:"@#{m}", [send(m)]) + end + end + + super + end + end + end end end end diff --git a/activesupport/lib/active_support/core_ext/duplicable.rb b/activesupport/lib/active_support/core_ext/duplicable.rb index adbbfd8c60..8f49ddfd9e 100644 --- a/activesupport/lib/active_support/core_ext/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/duplicable.rb @@ -35,3 +35,9 @@ class Numeric #:nodoc: false end end + +class Class #:nodoc: + def duplicable? + false + end +end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index e451e9933a..fd94bc051f 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -64,8 +64,28 @@ module Enumerable end end + # Iterates over a collection, passing the current element *and* the + # +memo+ to the block. Handy for building up hashes or + # reducing collections down to one object. Examples: + # + # %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase } #=> {'foo' => 'FOO', 'bar' => 'BAR'} + # + # *Note* that you can't use immutable objects like numbers, true or false as + # the memo. You would think the following returns 120, but since the memo is + # never changed, it does not. + # + # (1..5).each_with_object(1) { |value, memo| memo *= value } # => 1 + # + def each_with_object(memo, &block) + returning memo do |memo| + each do |element| + block.call(element, memo) + end + end + end unless [].respond_to?(:each_with_object) + # Convert an enumerable to a hash. Examples: - # + # # people.index_by(&:login) # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...} # people.index_by { |person| "#{person.first_name} #{person.last_name}" } diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb index c74d6cf884..4007a647be 100644 --- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb +++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb @@ -2,12 +2,12 @@ module Kernel unless respond_to?(:debugger) # Starts a debugging session if ruby-debug has been loaded (call script/server --debugger to do load it). def debugger - RAILS_DEFAULT_LOGGER.info "\n***** Debugger requested, but was not available: Start server with --debugger to enable *****\n" + Rails.logger.info "\n***** Debugger requested, but was not available: Start server with --debugger to enable *****\n" end end - + def breakpoint - RAILS_DEFAULT_LOGGER.info "\n***** The 'breakpoint' command has been renamed 'debugger' -- please change *****\n" + Rails.logger.info "\n***** The 'breakpoint' command has been renamed 'debugger' -- please change *****\n" debugger end -end
\ No newline at end of file +end diff --git a/activesupport/lib/active_support/core_ext/logger.rb b/activesupport/lib/active_support/core_ext/logger.rb index 9c1fd274ac..c622554860 100644 --- a/activesupport/lib/active_support/core_ext/logger.rb +++ b/activesupport/lib/active_support/core_ext/logger.rb @@ -12,5 +12,132 @@ class Logger end_eval end [:debug, :info, :error, :fatal].each {|level| define_around_helper(level) } +end -end
\ No newline at end of file + +require 'logger' + +# Extensions to the built in Ruby logger. +# +# If you want to use the default log formatter as defined in the Ruby core, then you +# will need to set the formatter for the logger as in: +# +# logger.formatter = Formatter.new +# +# You can then specify the datetime format, for example: +# +# logger.datetime_format = "%Y-%m-%d" +# +# Note: This logger is deprecated in favor of ActiveSupport::BufferedLogger +class Logger + # Set to false to disable the silencer + cattr_accessor :silencer + self.silencer = true + + # Silences the logger for the duration of the block. + def silence(temporary_level = Logger::ERROR) + if silencer + begin + old_logger_level, self.level = level, temporary_level + yield self + ensure + self.level = old_logger_level + end + else + yield self + end + end + + alias :old_datetime_format= :datetime_format= + # Logging date-time format (string passed to +strftime+). Ignored if the formatter + # does not respond to datetime_format=. + def datetime_format=(datetime_format) + formatter.datetime_format = datetime_format if formatter.respond_to?(:datetime_format=) + end + + alias :old_datetime_format :datetime_format + # Get the logging datetime format. Returns nil if the formatter does not support + # datetime formatting. + def datetime_format + formatter.datetime_format if formatter.respond_to?(:datetime_format) + end + + alias :old_formatter :formatter if method_defined?(:formatter) + # Get the current formatter. The default formatter is a SimpleFormatter which only + # displays the log message + def formatter + @formatter ||= SimpleFormatter.new + end + + unless const_defined? :Formatter + class Formatter + Format = "%s, [%s#%d] %5s -- %s: %s\n" + + attr_accessor :datetime_format + + def initialize + @datetime_format = nil + end + + def call(severity, time, progname, msg) + Format % [severity[0..0], format_datetime(time), $$, severity, progname, + msg2str(msg)] + end + + private + def format_datetime(time) + if @datetime_format.nil? + time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d " % time.usec + else + time.strftime(@datetime_format) + end + end + + def msg2str(msg) + case msg + when ::String + msg + when ::Exception + "#{ msg.message } (#{ msg.class })\n" << + (msg.backtrace || []).join("\n") + else + msg.inspect + end + end + end + end + + # Simple formatter which only displays the message. + class SimpleFormatter < Logger::Formatter + # This method is invoked when a log event occurs + def call(severity, timestamp, progname, msg) + "#{String === msg ? msg : msg.inspect}\n" + end + end + + private + alias old_format_message format_message + + # Ruby 1.8.3 transposed the msg and progname arguments to format_message. + # We can't test RUBY_VERSION because some distributions don't keep Ruby + # and its standard library in sync, leading to installations of Ruby 1.8.2 + # with Logger from 1.8.3 and vice versa. + if method_defined?(:formatter=) + def format_message(severity, timestamp, progname, msg) + formatter.call(severity, timestamp, progname, msg) + end + else + def format_message(severity, timestamp, msg, progname) + formatter.call(severity, timestamp, progname, msg) + end + + attr_writer :formatter + public :formatter= + + alias old_format_datetime format_datetime + def format_datetime(datetime) datetime end + + alias old_msg2str msg2str + def msg2str(msg) msg end + end +end diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index 34fcbd124b..da8d28ec13 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -7,7 +7,17 @@ require 'active_support/core_ext/module/introspection' require 'active_support/core_ext/module/loading' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/model_naming' +require 'active_support/core_ext/module/synchronization' + +module ActiveSupport + module CoreExtensions + # Various extensions for the Ruby core Module class. + module Module + # Nothing here. Only defined for API documentation purposes. + end + end +end class Module - include ActiveSupport::CoreExt::Module::ModelNaming + include ActiveSupport::CoreExtensions::Module end diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb index 1894e3b0a2..e640f64520 100644 --- a/activesupport/lib/active_support/core_ext/module/aliasing.rb +++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb @@ -1,70 +1,74 @@ -class Module - # Encapsulates the common pattern of: - # - # alias_method :foo_without_feature, :foo - # alias_method :foo, :foo_with_feature - # - # With this, you simply do: - # - # alias_method_chain :foo, :feature - # - # And both aliases are set up for you. - # - # Query and bang methods (foo?, foo!) keep the same punctuation: - # - # alias_method_chain :foo?, :feature - # - # is equivalent to - # - # alias_method :foo_without_feature?, :foo? - # alias_method :foo?, :foo_with_feature? - # - # so you can safely chain foo, foo?, and foo! with the same feature. - def alias_method_chain(target, feature) - # Strip out punctuation on predicates or bang methods since - # e.g. target?_without_feature is not a valid method name. - aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 - yield(aliased_target, punctuation) if block_given? - - with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}" - - alias_method without_method, target - alias_method target, with_method - - case - when public_method_defined?(without_method) - public target - when protected_method_defined?(without_method) - protected target - when private_method_defined?(without_method) - private target - end - end +module ActiveSupport + module CoreExtensions + module Module + # Encapsulates the common pattern of: + # + # alias_method :foo_without_feature, :foo + # alias_method :foo, :foo_with_feature + # + # With this, you simply do: + # + # alias_method_chain :foo, :feature + # + # And both aliases are set up for you. + # + # Query and bang methods (foo?, foo!) keep the same punctuation: + # + # alias_method_chain :foo?, :feature + # + # is equivalent to + # + # alias_method :foo_without_feature?, :foo? + # alias_method :foo?, :foo_with_feature? + # + # so you can safely chain foo, foo?, and foo! with the same feature. + def alias_method_chain(target, feature) + # Strip out punctuation on predicates or bang methods since + # e.g. target?_without_feature is not a valid method name. + aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 + yield(aliased_target, punctuation) if block_given? + + with_method, without_method = "#{aliased_target}_with_#{feature}#{punctuation}", "#{aliased_target}_without_#{feature}#{punctuation}" - # Allows you to make aliases for attributes, which includes - # getter, setter, and query methods. - # - # Example: - # - # class Content < ActiveRecord::Base - # # has a title attribute - # end - # - # class Email < Content - # alias_attribute :subject, :title - # end - # - # e = Email.find(1) - # e.title # => "Superstars" - # e.subject # => "Superstars" - # e.subject? # => true - # e.subject = "Megastars" - # e.title # => "Megastars" - def alias_attribute(new_name, old_name) - module_eval <<-STR, __FILE__, __LINE__+1 - def #{new_name}; self.#{old_name}; end - def #{new_name}?; self.#{old_name}?; end - def #{new_name}=(v); self.#{old_name} = v; end - STR + alias_method without_method, target + alias_method target, with_method + + case + when public_method_defined?(without_method) + public target + when protected_method_defined?(without_method) + protected target + when private_method_defined?(without_method) + private target + end + end + + # Allows you to make aliases for attributes, which includes + # getter, setter, and query methods. + # + # Example: + # + # class Content < ActiveRecord::Base + # # has a title attribute + # end + # + # class Email < Content + # alias_attribute :subject, :title + # end + # + # e = Email.find(1) + # e.title # => "Superstars" + # e.subject # => "Superstars" + # e.subject? # => true + # e.subject = "Megastars" + # e.title # => "Megastars" + def alias_attribute(new_name, old_name) + module_eval <<-STR, __FILE__, __LINE__+1 + def #{new_name}; self.#{old_name}; end + def #{new_name}?; self.#{old_name}?; end + def #{new_name}=(v); self.#{old_name} = v; end + STR + end + end end end diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb index 45f3e4bf5c..8beaff4b82 100644 --- a/activesupport/lib/active_support/core_ext/module/introspection.rb +++ b/activesupport/lib/active_support/core_ext/module/introspection.rb @@ -1,86 +1,90 @@ -class Module - # Returns the name of the module containing this one. - # - # p M::N.parent_name # => "M" - def parent_name - unless defined? @parent_name - @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil - end - @parent_name - end +module ActiveSupport + module CoreExtensions + module Module + # Returns the name of the module containing this one. + # + # p M::N.parent_name # => "M" + def parent_name + unless defined? @parent_name + @parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil + end + @parent_name + end - # Returns the module which contains this one according to its name. - # - # module M - # module N - # end - # end - # X = M::N - # - # p M::N.parent # => M - # p X.parent # => M - # - # The parent of top-level and anonymous modules is Object. - # - # p M.parent # => Object - # p Module.new.parent # => Object - # - def parent - parent_name ? parent_name.constantize : Object - end + # Returns the module which contains this one according to its name. + # + # module M + # module N + # end + # end + # X = M::N + # + # p M::N.parent # => M + # p X.parent # => M + # + # The parent of top-level and anonymous modules is Object. + # + # p M.parent # => Object + # p Module.new.parent # => Object + # + def parent + parent_name ? parent_name.constantize : Object + end - # Returns all the parents of this module according to its name, ordered from - # nested outwards. The receiver is not contained within the result. - # - # module M - # module N - # end - # end - # X = M::N - # - # p M.parents # => [Object] - # p M::N.parents # => [M, Object] - # p X.parents # => [M, Object] - # - def parents - parents = [] - if parent_name - parts = parent_name.split('::') - until parts.empty? - parents << (parts * '::').constantize - parts.pop + # Returns all the parents of this module according to its name, ordered from + # nested outwards. The receiver is not contained within the result. + # + # module M + # module N + # end + # end + # X = M::N + # + # p M.parents # => [Object] + # p M::N.parents # => [M, Object] + # p X.parents # => [M, Object] + # + def parents + parents = [] + if parent_name + parts = parent_name.split('::') + until parts.empty? + parents << (parts * '::').constantize + parts.pop + end + end + parents << Object unless parents.include? Object + parents end - end - parents << Object unless parents.include? Object - parents - end - if RUBY_VERSION < '1.9' - # Returns the constants that have been defined locally by this object and - # not in an ancestor. This method is exact if running under Ruby 1.9. In - # previous versions it may miss some constants if their definition in some - # ancestor is identical to their definition in the receiver. - def local_constants - inherited = {} + if RUBY_VERSION < '1.9' + # Returns the constants that have been defined locally by this object and + # not in an ancestor. This method is exact if running under Ruby 1.9. In + # previous versions it may miss some constants if their definition in some + # ancestor is identical to their definition in the receiver. + def local_constants + inherited = {} + + ancestors.each do |anc| + next if anc == self + anc.constants.each { |const| inherited[const] = anc.const_get(const) } + end - ancestors.each do |anc| - next if anc == self - anc.constants.each { |const| inherited[const] = anc.const_get(const) } + constants.select do |const| + !inherited.key?(const) || inherited[const].object_id != const_get(const).object_id + end + end + else + def local_constants #:nodoc: + constants(false) + end end - constants.select do |const| - !inherited.key?(const) || inherited[const].object_id != const_get(const).object_id + # Returns the names of the constants defined locally rather than the + # constants themselves. See <tt>local_constants</tt>. + def local_constant_names + local_constants.map { |c| c.to_s } end end - else - def local_constants #:nodoc: - constants(false) - end - end - - # Returns the names of the constants defined locally rather than the - # constants themselves. See <tt>local_constants</tt>. - def local_constant_names - local_constants.map { |c| c.to_s } end end diff --git a/activesupport/lib/active_support/core_ext/module/model_naming.rb b/activesupport/lib/active_support/core_ext/module/model_naming.rb index 5518f5417b..3ec4f3ba11 100644 --- a/activesupport/lib/active_support/core_ext/module/model_naming.rb +++ b/activesupport/lib/active_support/core_ext/module/model_naming.rb @@ -11,12 +11,12 @@ module ActiveSupport end end - module CoreExt + module CoreExtensions module Module - module ModelNaming - def model_name - @model_name ||= ModelName.new(name) - end + # Returns an ActiveSupport::ModelName object for module. It can be + # used to retrieve all kinds of naming-related information. + def model_name + @model_name ||= ModelName.new(name) end end end diff --git a/activesupport/lib/active_support/core_ext/module/synchronization.rb b/activesupport/lib/active_support/core_ext/module/synchronization.rb new file mode 100644 index 0000000000..6253594dfa --- /dev/null +++ b/activesupport/lib/active_support/core_ext/module/synchronization.rb @@ -0,0 +1,36 @@ +class Module + # Synchronize access around a method, delegating synchronization to a + # particular mutex. A mutex (either a Mutex, or any object that responds to + # #synchronize and yields to a block) must be provided as a final :with option. + # The :with option should be a symbol or string, and can represent a method, + # constant, or instance or class variable. + # Example: + # class SharedCache + # @@lock = Mutex.new + # def expire + # ... + # end + # synchronize :expire, :with => :@@lock + # end + def synchronize(*methods) + options = methods.extract_options! + unless options.is_a?(Hash) && with = options[:with] + raise ArgumentError, "Synchronization needs a mutex. Supply an options hash with a :with key as the last argument (e.g. synchronize :hello, :with => :@mutex)." + end + + methods.flatten.each do |method| + aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1 + if instance_methods.include?("#{aliased_method}_without_synchronization#{punctuation}") + raise ArgumentError, "#{method} is already synchronized. Double synchronization is not currently supported." + end + module_eval(<<-EOS, __FILE__, __LINE__) + def #{aliased_method}_with_synchronization#{punctuation}(*args, &block) + #{with}.synchronize do + #{aliased_method}_without_synchronization#{punctuation}(*args, &block) + end + end + EOS + alias_method_chain method, :synchronization + end + end +end
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/object/misc.rb b/activesupport/lib/active_support/core_ext/object/misc.rb index 8384a12327..06a7d05702 100644 --- a/activesupport/lib/active_support/core_ext/object/misc.rb +++ b/activesupport/lib/active_support/core_ext/object/misc.rb @@ -1,9 +1,4 @@ class Object - unless respond_to?(:send!) - # Anticipating Ruby 1.9 neutering send - alias send! send - end - # A Ruby-ized realization of the K combinator, courtesy of Mikael Brockman. # # def foo diff --git a/activesupport/lib/active_support/core_ext/range/blockless_step.rb b/activesupport/lib/active_support/core_ext/range/blockless_step.rb index 39dac85636..6fa1eb5bee 100644 --- a/activesupport/lib/active_support/core_ext/range/blockless_step.rb +++ b/activesupport/lib/active_support/core_ext/range/blockless_step.rb @@ -8,7 +8,7 @@ module ActiveSupport #:nodoc: end if RUBY_VERSION < '1.9' - def step_with_blockless(value, &block) + def step_with_blockless(value = 1, &block) if block_given? step_without_blockless(value, &block) else @@ -18,7 +18,7 @@ module ActiveSupport #:nodoc: end end else - def step_with_blockless(value, &block) + def step_with_blockless(value = 1, &block) if block_given? step_without_blockless(value, &block) else diff --git a/activesupport/lib/active_support/core_ext/rexml.rb b/activesupport/lib/active_support/core_ext/rexml.rb new file mode 100644 index 0000000000..af8ce3af47 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/rexml.rb @@ -0,0 +1,35 @@ +require 'rexml/document' +require 'rexml/entity' + +# Fixes the rexml vulnerability disclosed at: +# http://www.ruby-lang.org/en/news/2008/08/23/dos-vulnerability-in-rexml/ +# This fix is identical to rexml-expansion-fix version 1.0.1 + +unless REXML::VERSION > "3.1.7.2" + module REXML + class Entity < Child + undef_method :unnormalized + def unnormalized + document.record_entity_expansion! if document + v = value() + return nil if v.nil? + @unnormalized = Text::unnormalize(v, parent) + @unnormalized + end + end + class Document < Element + @@entity_expansion_limit = 10_000 + def self.entity_expansion_limit= val + @@entity_expansion_limit = val + end + + def record_entity_expansion! + @number_of_expansions ||= 0 + @number_of_expansions += 1 + if @number_of_expansions > @@entity_expansion_limit + raise "Number of entity expansions exceeded, processing aborted." + end + end + end + end +end diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb index ea50511a96..2006cf4946 100644 --- a/activesupport/lib/active_support/core_ext/time.rb +++ b/activesupport/lib/active_support/core_ext/time.rb @@ -1,11 +1,32 @@ require 'date' require 'time' -# Ruby 1.8-cvs and 1.9 define private Time#to_date class Time + # Ruby 1.8-cvs and 1.9 define private Time#to_date %w(to_date to_datetime).each do |method| public method if private_instance_methods.include?(method) end + + # Pre-1.9 versions of Ruby have a bug with marshaling Time instances, where utc instances are + # unmarshaled in the local zone, instead of utc. We're layering behavior on the _dump and _load + # methods so that utc instances can be flagged on dump, and coerced back to utc on load. + if RUBY_VERSION < '1.9' + class << self + alias_method :_original_load, :_load + def _load(marshaled_time) + time = _original_load(marshaled_time) + utc = time.send(:remove_instance_variable, '@marshal_with_utc_coercion') + utc ? time.utc : time + end + end + + alias_method :_original_dump, :_dump + def _dump(*args) + obj = self.frozen? ? self.dup : self + obj.instance_variable_set('@marshal_with_utc_coercion', utc?) + obj._original_dump(*args) + end + end end require 'active_support/core_ext/time/behavior' diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 079ecdd48e..9d8eb73908 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -78,7 +78,7 @@ module ActiveSupport #:nodoc: # # Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 def in_time_zone(zone = ::Time.zone) - ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.send!(:get_zone, zone)) + ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.__send__(:get_zone, zone)) end end end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index a3f5f799a2..3d871eec11 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -39,6 +39,10 @@ module ActiveSupport #:nodoc: mattr_accessor :explicitly_unloadable_constants self.explicitly_unloadable_constants = [] + # The logger is used for generating information on the action run-time (including benchmarking) if available. + # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers. + mattr_accessor :logger + # Set to true to enable logging of const_missing and file loads mattr_accessor :log_activity self.log_activity = false @@ -584,7 +588,7 @@ module ActiveSupport #:nodoc: protected def log_call(*args) - if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER && log_activity + if logger && log_activity arg_str = args.collect { |arg| arg.inspect } * ', ' /in `([a-z_\?\!]+)'/ =~ caller(1).first selector = $1 || '<unknown>' @@ -593,8 +597,8 @@ module ActiveSupport #:nodoc: end def log(msg) - if defined?(RAILS_DEFAULT_LOGGER) && RAILS_DEFAULT_LOGGER && log_activity - RAILS_DEFAULT_LOGGER.debug "Dependencies: #{msg}" + if logger && log_activity + logger.debug "Dependencies: #{msg}" end end end diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index ebdaf86146..950bca60a6 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -109,7 +109,7 @@ module ActiveSupport end def deprecation_horizon - '2.0' + '2.3' end end @@ -162,6 +162,22 @@ module ActiveSupport end end + class DeprecatedObjectProxy < DeprecationProxy + def initialize(object, message) + @object = object + @message = message + end + + private + def target + @object + end + + def warn(callstack, called, args) + ActiveSupport::Deprecation.warn(@message, callstack) + end + end + # Stand-in for <tt>@request</tt>, <tt>@attributes</tt>, <tt>@params</tt>, etc. # which emits deprecation warnings on any method call (except +inspect+). class DeprecatedInstanceVariableProxy < DeprecationProxy #:nodoc: @@ -185,6 +201,10 @@ module ActiveSupport @new_const = new_const end + def class + target.class + end + private def target @new_const.to_s.constantize diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb index c2738b39fc..7ae9e0c6ab 100644 --- a/activesupport/lib/active_support/inflector.rb +++ b/activesupport/lib/active_support/inflector.rb @@ -39,12 +39,16 @@ module ActiveSupport # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression. # The replacement should always be a string that may include references to the matched data from the rule. def plural(rule, replacement) + @uncountables.delete(rule) if rule.is_a?(String) + @uncountables.delete(replacement) @plurals.insert(0, [rule, replacement]) end # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression. # The replacement should always be a string that may include references to the matched data from the rule. def singular(rule, replacement) + @uncountables.delete(rule) if rule.is_a?(String) + @uncountables.delete(replacement) @singulars.insert(0, [rule, replacement]) end @@ -55,6 +59,8 @@ module ActiveSupport # irregular 'octopus', 'octopi' # irregular 'person', 'people' def irregular(singular, plural) + @uncountables.delete(singular) + @uncountables.delete(plural) if singular[0,1].upcase == plural[0,1].upcase plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1]) singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1]) @@ -173,7 +179,7 @@ module ActiveSupport if first_letter_in_uppercase lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } else - lower_case_and_underscored_word.first + camelize(lower_case_and_underscored_word)[1..-1] + lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1] end end @@ -273,32 +279,47 @@ module ActiveSupport underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") end - # Tries to find a constant with the name specified in the argument string: - # - # "Module".constantize # => Module - # "Test::Unit".constantize # => Test::Unit - # - # The name is assumed to be the one of a top-level constant, no matter whether - # it starts with "::" or not. No lexical context is taken into account: - # - # C = 'outside' - # module M - # C = 'inside' - # C # => 'inside' - # "C".constantize # => 'outside', same as ::C - # end - # - # NameError is raised when the name is not in CamelCase or the constant is - # unknown. - def constantize(camel_cased_word) - names = camel_cased_word.split('::') - names.shift if names.empty? || names.first.empty? + # Ruby 1.9 introduces an inherit argument for Module#const_get and + # #const_defined? and changes their default behavior. + if Module.method(:const_get).arity == 1 + # Tries to find a constant with the name specified in the argument string: + # + # "Module".constantize # => Module + # "Test::Unit".constantize # => Test::Unit + # + # The name is assumed to be the one of a top-level constant, no matter whether + # it starts with "::" or not. No lexical context is taken into account: + # + # C = 'outside' + # module M + # C = 'inside' + # C # => 'inside' + # "C".constantize # => 'outside', same as ::C + # end + # + # NameError is raised when the name is not in CamelCase or the constant is + # unknown. + def constantize(camel_cased_word) + names = camel_cased_word.split('::') + names.shift if names.empty? || names.first.empty? + + constant = Object + names.each do |name| + constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name) + end + constant + end + else + def constantize(camel_cased_word) #:nodoc: + names = camel_cased_word.split('::') + names.shift if names.empty? || names.first.empty? - constant = Object - names.each do |name| - constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name) + constant = Object + names.each do |name| + constant = constant.const_get(name, false) || constant.const_missing(name) + end + constant end - constant end # Turns a number into an ordinal string used to denote the position in an diff --git a/activesupport/lib/active_support/locale/en-US.rb b/activesupport/lib/active_support/locale/en-US.rb deleted file mode 100644 index 51324a90bf..0000000000 --- a/activesupport/lib/active_support/locale/en-US.rb +++ /dev/null @@ -1,28 +0,0 @@ -I18n.backend.store_translations :'en-US', { - :support => { - :array => { - :sentence_connector => 'and' - } - }, - :date => { - :formats => { - :default => "%Y-%m-%d", - :short => "%b %d", - :long => "%B %d, %Y", - }, - :day_names => Date::DAYNAMES, - :abbr_day_names => Date::ABBR_DAYNAMES, - :month_names => Date::MONTHNAMES, - :abbr_month_names => Date::ABBR_MONTHNAMES, - :order => [:year, :month, :day] - }, - :time => { - :formats => { - :default => "%a, %d %b %Y %H:%M:%S %z", - :short => "%d %b %H:%M", - :long => "%B %d, %Y %H:%M", - }, - :am => 'am', - :pm => 'pm' - } -}
\ No newline at end of file diff --git a/activesupport/lib/active_support/locale/en-US.yml b/activesupport/lib/active_support/locale/en-US.yml new file mode 100644 index 0000000000..60ecb1d42a --- /dev/null +++ b/activesupport/lib/active_support/locale/en-US.yml @@ -0,0 +1,31 @@ +en-US: + date: + formats: + # Use the strftime parameters for formats. + # When no format has been given, it uses default. + # You can provide other formats here if you like! + default: "%Y-%m-%d" + short: "%b %d" + long: "%B %d, %Y" + + day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday] + abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat] + + # Don't forget the nil at the beginning; there's no such thing as a 0th month + month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December] + abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec] + # Used in date_select and datime_select. + order: [ :year, :month, :day ] + + time: + formats: + default: "%a, %d %b %Y %H:%M:%S %z" + short: "%d %b %H:%M" + long: "%B %d, %Y %H:%M" + am: "am" + pm: "pm" + +# Used in array.to_sentence. + support: + array: + sentence_connector: "and" diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb index c77bca1ac9..b563b093ed 100644 --- a/activesupport/lib/active_support/option_merger.rb +++ b/activesupport/lib/active_support/option_merger.rb @@ -11,7 +11,7 @@ module ActiveSupport private def method_missing(method, *arguments, &block) arguments << (arguments.last.respond_to?(:to_hash) ? @options.deep_merge(arguments.pop) : @options.dup) - @context.send!(method, *arguments, &block) + @context.__send__(method, *arguments, &block) end end end diff --git a/activesupport/lib/active_support/secure_random.rb b/activesupport/lib/active_support/secure_random.rb new file mode 100644 index 0000000000..688165f9a3 --- /dev/null +++ b/activesupport/lib/active_support/secure_random.rb @@ -0,0 +1,197 @@ +begin + require 'openssl' +rescue LoadError +end + +begin + require 'securerandom' +rescue LoadError +end + +module ActiveSupport + if defined?(::SecureRandom) + # Use Ruby 1.9's SecureRandom library whenever possible. + SecureRandom = ::SecureRandom # :nodoc: + else + # = Secure random number generator interface. + # + # This library is an interface for secure random number generator which is + # suitable for generating session key in HTTP cookies, etc. + # + # It supports following secure random number generators. + # + # * openssl + # * /dev/urandom + # * Win32 + # + # *Note*: This module is based on the SecureRandom library from Ruby 1.9, + # revision 18786, August 23 2008. It's 100% interface-compatible with Ruby 1.9's + # SecureRandom library. + # + # == Example + # + # # random hexadecimal string. + # p SecureRandom.hex(10) #=> "52750b30ffbc7de3b362" + # p SecureRandom.hex(10) #=> "92b15d6c8dc4beb5f559" + # p SecureRandom.hex(11) #=> "6aca1b5c58e4863e6b81b8" + # p SecureRandom.hex(12) #=> "94b2fff3e7fd9b9c391a2306" + # p SecureRandom.hex(13) #=> "39b290146bea6ce975c37cfc23" + # ... + # + # # random base64 string. + # p SecureRandom.base64(10) #=> "EcmTPZwWRAozdA==" + # p SecureRandom.base64(10) #=> "9b0nsevdwNuM/w==" + # p SecureRandom.base64(10) #=> "KO1nIU+p9DKxGg==" + # p SecureRandom.base64(11) #=> "l7XEiFja+8EKEtY=" + # p SecureRandom.base64(12) #=> "7kJSM/MzBJI+75j8" + # p SecureRandom.base64(13) #=> "vKLJ0tXBHqQOuIcSIg==" + # ... + # + # # random binary string. + # p SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301" + # p SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337" + # ... + module SecureRandom + # SecureRandom.random_bytes generates a random binary string. + # + # The argument n specifies the length of the result string. + # + # If n is not specified, 16 is assumed. + # It may be larger in future. + # + # If secure random number generator is not available, + # NotImplementedError is raised. + def self.random_bytes(n=nil) + n ||= 16 + + if defined? OpenSSL::Random + return OpenSSL::Random.random_bytes(n) + end + + if !defined?(@has_urandom) || @has_urandom + flags = File::RDONLY + flags |= File::NONBLOCK if defined? File::NONBLOCK + flags |= File::NOCTTY if defined? File::NOCTTY + flags |= File::NOFOLLOW if defined? File::NOFOLLOW + begin + File.open("/dev/urandom", flags) {|f| + unless f.stat.chardev? + raise Errno::ENOENT + end + @has_urandom = true + ret = f.readpartial(n) + if ret.length != n + raise NotImplementedError, "Unexpected partial read from random device" + end + return ret + } + rescue Errno::ENOENT + @has_urandom = false + end + end + + if !defined?(@has_win32) + begin + require 'Win32API' + + crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext", 'PPPII', 'L') + @crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom", 'LIP', 'L') + + hProvStr = " " * 4 + prov_rsa_full = 1 + crypt_verifycontext = 0xF0000000 + + if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full, crypt_verifycontext) == 0 + raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}" + end + @hProv, = hProvStr.unpack('L') + + @has_win32 = true + rescue LoadError + @has_win32 = false + end + end + if @has_win32 + bytes = " " * n + if @crypt_gen_random.call(@hProv, bytes.size, bytes) == 0 + raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}" + end + return bytes + end + + raise NotImplementedError, "No random device" + end + + # SecureRandom.hex generates a random hex string. + # + # The argument n specifies the length of the random length. + # The length of the result string is twice of n. + # + # If n is not specified, 16 is assumed. + # It may be larger in future. + # + # If secure random number generator is not available, + # NotImplementedError is raised. + def self.hex(n=nil) + random_bytes(n).unpack("H*")[0] + end + + # SecureRandom.base64 generates a random base64 string. + # + # The argument n specifies the length of the random length. + # The length of the result string is about 4/3 of n. + # + # If n is not specified, 16 is assumed. + # It may be larger in future. + # + # If secure random number generator is not available, + # NotImplementedError is raised. + def self.base64(n=nil) + [random_bytes(n)].pack("m*").delete("\n") + end + + # SecureRandom.random_number generates a random number. + # + # If an positive integer is given as n, + # SecureRandom.random_number returns an integer: + # 0 <= SecureRandom.random_number(n) < n. + # + # If 0 is given or an argument is not given, + # SecureRandom.random_number returns an float: + # 0.0 <= SecureRandom.random_number() < 1.0. + def self.random_number(n=0) + if 0 < n + hex = n.to_s(16) + hex = '0' + hex if (hex.length & 1) == 1 + bin = [hex].pack("H*") + mask = bin[0].ord + mask |= mask >> 1 + mask |= mask >> 2 + mask |= mask >> 4 + begin + rnd = SecureRandom.random_bytes(bin.length) + rnd[0] = (rnd[0].ord & mask).chr + end until rnd < bin + rnd.unpack("H*")[0].hex + else + # assumption: Float::MANT_DIG <= 64 + i64 = SecureRandom.random_bytes(8).unpack("Q")[0] + Math.ldexp(i64 >> (64-Float::MANT_DIG), -Float::MANT_DIG) + end + end + + # Following code is based on David Garamond's GUID library for Ruby. + def self.lastWin32ErrorMessage # :nodoc: + get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L') + format_message = Win32API.new("kernel32", "FormatMessageA", 'LPLLPLPPPPPPPP', 'L') + format_message_ignore_inserts = 0x00000200 + format_message_from_system = 0x00001000 + + code = get_last_error.call + msg = "\0" * 1024 + len = format_message.call(format_message_ignore_inserts + format_message_from_system, 0, code, 0, msg, 1024, nil, nil, nil, nil, nil, nil, nil, nil) + msg[0, len].tr("\r", '').chomp + end + end + end +end diff --git a/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb b/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb index 70a44eab8c..63d1ba6507 100644 --- a/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb +++ b/activesupport/lib/active_support/testing/core_ext/test/unit/assertions.rb @@ -36,7 +36,11 @@ module Test # post :delete, :id => ... # end def assert_difference(expressions, difference = 1, message = nil, &block) - expression_evaluations = Array(expressions).collect{ |expression| lambda { eval(expression, block.send!(:binding)) } } + expression_evaluations = Array(expressions).map do |expression| + lambda do + eval(expression, block.__send__(:binding)) + end + end original_values = expression_evaluations.inject([]) { |memo, expression| memo << expression.call } yield diff --git a/activesupport/lib/active_support/testing/performance.rb b/activesupport/lib/active_support/testing/performance.rb index 70a7f84023..f996c40793 100644 --- a/activesupport/lib/active_support/testing/performance.rb +++ b/activesupport/lib/active_support/testing/performance.rb @@ -17,15 +17,15 @@ module ActiveSupport else { :benchmark => false, :runs => 1, - :min_percent => 0.02, + :min_percent => 0.01, :metrics => [:process_time, :memory, :objects], :formats => [:flat, :graph_html, :call_tree], :output => 'tmp/performance' } - end + end.freeze def self.included(base) - base.class_inheritable_hash :profile_options - base.profile_options = DEFAULTS.dup + base.superclass_delegating_accessor :profile_options + base.profile_options = DEFAULTS end def full_test_name @@ -39,12 +39,12 @@ module ActiveSupport @_result = result run_warmup - profile_options[:metrics].each do |metric_name| - if klass = Metrics[metric_name.to_sym] - run_profile(klass.new) - result.add_run - else - $stderr.puts '%20s: unsupported' % metric_name.to_s + 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 @@ -164,7 +164,14 @@ module ActiveSupport end class Profiler < Performer + def initialize(*args) + super + @supported = @metric.measure_mode rescue false + end + def run + return unless @supported + RubyProf.measure_mode = @metric.measure_mode RubyProf.start RubyProf.pause @@ -173,7 +180,17 @@ module ActiveSupport @total = @data.threads.values.sum(0) { |method_infos| method_infos.sort.last.total_time } end + def report + if @supported + super + else + '%20s: unsupported' % @metric.name + end + end + def record + return unless @supported + klasses = profile_options[:formats].map { |f| RubyProf.const_get("#{f.to_s.camelize}Printer") }.compact klasses.each do |klass| @@ -202,8 +219,7 @@ module ActiveSupport module Metrics def self.[](name) - klass = const_get(name.to_s.camelize) - klass if klass::Mode + const_get(name.to_s.camelize) rescue NameError nil end @@ -250,6 +266,16 @@ module ActiveSupport 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 @@ -310,7 +336,7 @@ module ActiveSupport RubyProf.measure_memory / 1024.0 end - # Ruby 1.8 + adymo patch + # Ruby 1.8 + railsbench patch elsif GC.respond_to?(:allocated_size) def measure GC.allocated_size / 1024.0 @@ -322,11 +348,27 @@ module ActiveSupport 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 def format(measurement) @@ -341,10 +383,23 @@ module ActiveSupport def measure RubyProf.measure_allocations end + + # 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 def format(measurement) diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 4866fa0dc8..75591b7c34 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -275,7 +275,7 @@ module ActiveSupport end def marshal_load(variables) - initialize(variables[0].utc, ::Time.send!(:get_zone, variables[1]), variables[2].utc) + initialize(variables[0].utc, ::Time.__send__(:get_zone, variables[1]), variables[2].utc) end # Ensure proxy class responds to all methods that underlying time instance responds to. diff --git a/activesupport/lib/active_support/typed_array.rb b/activesupport/lib/active_support/typed_array.rb deleted file mode 100644 index 1a4d8a8faf..0000000000 --- a/activesupport/lib/active_support/typed_array.rb +++ /dev/null @@ -1,31 +0,0 @@ -module ActiveSupport - class TypedArray < Array - def self.type_cast(obj) - obj - end - - def initialize(*args) - super(*args).map! { |obj| self.class.type_cast(obj) } - end - - def <<(obj) - super(self.class.type_cast(obj)) - end - - def concat(array) - super(array.map! { |obj| self.class.type_cast(obj) }) - end - - def insert(index, obj) - super(index, self.class.type_cast(obj)) - end - - def push(*objs) - super(*objs.map { |obj| self.class.type_cast(obj) }) - end - - def unshift(*objs) - super(*objs.map { |obj| self.class.type_cast(obj) }) - end - end -end diff --git a/activesupport/lib/active_support/vendor.rb b/activesupport/lib/active_support/vendor.rb index 381471b833..acd94af783 100644 --- a/activesupport/lib/active_support/vendor.rb +++ b/activesupport/lib/active_support/vendor.rb @@ -29,6 +29,6 @@ end # begin # gem 'i18n', '~> 0.0.1' # rescue Gem::LoadError - $:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.0.1/lib" + $:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.0.1" require 'i18n' # end
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/MIT-LICENSE b/activesupport/lib/active_support/vendor/i18n-0.0.1/MIT-LICENSE deleted file mode 100755 index ed8e9ee66d..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/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.0.1/README.textile b/activesupport/lib/active_support/vendor/i18n-0.0.1/README.textile deleted file mode 100644 index 2b14a99c0f..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/README.textile +++ /dev/null @@ -1,18 +0,0 @@ -h1. Ruby I18n gem - -I18n and localization solution for Ruby. - -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.0.1/i18n.gemspec b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.gemspec deleted file mode 100644 index 81ad0b598d..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.gemspec +++ /dev/null @@ -1,24 +0,0 @@ -Gem::Specification.new do |s| - s.name = "i18n" - s.version = "0.0.1" - s.date = "2008-06-13" - s.summary = "Internationalization for Ruby" - s.email = "rails-patch-i18n@googlegroups.com" - s.homepage = "http://groups.google.com/group/rails-patch-i18n" - s.description = "Add Internationalization to your Ruby application." - s.has_rdoc = false - s.authors = ['Sven Fuchs', 'Matt Aimonetti', 'Stephan Soller', 'Saimon Moore'] - s.files = [ - "lib/i18n/backend/minimal.rb", - "lib/i18n/core_ext.rb", - "lib/i18n/localization.rb", - "lib/i18n/translation.rb", - "lib/i18n.rb", - "LICENSE", - "README", - "spec/core_ext_spec.rb", - "spec/i18n_spec.rb", - "spec/spec.opts", - "spec/spec_helper.rb" - ] -end
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb index 1bb65263a3..0988ea8f44 100755 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n.rb +++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n.rb @@ -9,14 +9,14 @@ require 'i18n/backend/simple' require 'i18n/exceptions' module I18n - @@backend = Backend::Simple + @@backend = nil @@default_locale = 'en-US' @@exception_handler = :default_exception_handler class << self # Returns the current backend. Defaults to +Backend::Simple+. def backend - @@backend + @@backend ||= Backend::Simple.new end # Sets the current backend. Used to set a custom backend. @@ -49,16 +49,14 @@ module I18n @@exception_handler = exception_handler end - # Allow client libraries to pass a block that populates the translation - # storage. Decoupled for backends like a db backend that persist their - # translations, so the backend can decide whether/when to yield or not. - def populate(&block) - backend.populate(&block) - end - - # Stores translations for the given locale in the backend. - def store_translations(locale, data) - backend.store_translations locale, data + # Allows client libraries to pass arguments that specify a source for + # translation data to be loaded by the backend. 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.) + def load_translations(*args) + backend.load_translations(*args) end # Translates, pluralizes and interpolates a given key using a given locale, diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb new file mode 100644 index 0000000000..da89b30c54 --- /dev/null +++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/backend/simple.rb @@ -0,0 +1,192 @@ +require 'strscan' + +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) || default(locale, default, options) || raise(I18n::MissingTranslationData.new(locale, key, options)) + 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 + + protected + + 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 + keys = I18n.send :normalize_translation_keys, locale, key, scope + keys.inject(translations){|result, k| result[k.to_sym] or return nil } + 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 = string.gsub(/%d/, '{{count}}').gsub(/%s/, '{{value}}') + + if string.respond_to?(:force_encoding) + original_encoding = string.encoding + string.force_encoding(Encoding::BINARY) + end + + result = 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 + + result.force_encoding(original_encoding) if original_encoding + result + 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) + 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.0.1/lib/i18n/exceptions.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/exceptions.rb index 6c1fc6d9b6..0f3eff1071 100644 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n/exceptions.rb +++ b/activesupport/lib/active_support/vendor/i18n-0.0.1/i18n/exceptions.rb @@ -42,4 +42,12 @@ module I18n 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
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n/backend/simple.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n/backend/simple.rb deleted file mode 100644 index b8be1cecfb..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/lib/i18n/backend/simple.rb +++ /dev/null @@ -1,154 +0,0 @@ -require 'strscan' - -module I18n - module Backend - module Simple - @@translations = {} - - class << self - # Allow client libraries to pass a block that populates the translation - # storage. Decoupled for backends like a db backend that persist their - # translations, so the backend can decide whether/when to yield or not. - def populate(&block) - yield - 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) || default(locale, default, options) || raise(I18n::MissingTranslationData.new(locale, key, options)) - entry = pluralize entry, count - entry = interpolate 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' - 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 - - 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 - - protected - - # 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 - keys = I18n.send :normalize_translation_keys, locale, key, scope - keys.inject(@@translations){|result, k| result[k.to_sym] or return nil } - 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 - 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(entry, count) - return entry unless entry.is_a?(Array) and count - raise InvalidPluralizationData.new(entry, count) unless entry.size == 2 - entry[count == 1 ? 0 : 1] - 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(string, values = {}) - return string if !string.is_a?(String) - - map = {'%d' => '{{count}}', '%s' => '{{value}}'} # TODO deprecate this? - string.gsub!(/#{map.keys.join('|')}/){|key| map[key]} - - s = StringScanner.new string.dup - while s.skip_until(/\{\{/) - s.string[s.pos - 3, 1] = '' and next if s.pre_match[-1, 1] == '\\' - start_pos = s.pos - 2 - key = s.scan_until(/\}\}/)[0..-3] - end_pos = s.pos - 1 - - raise ReservedInterpolationKey.new(key, string) if %w(scope default).include?(key) - raise MissingInterpolationArgument.new(key, string) unless values.has_key? key.to_sym - - s.string[start_pos..end_pos] = values[key.to_sym].to_s - s.unscan - end - s.string - 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 -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/test/all.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/test/all.rb deleted file mode 100644 index 048b62f8c3..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/test/all.rb +++ /dev/null @@ -1,4 +0,0 @@ -dir = File.dirname(__FILE__) -require dir + '/i18n_test.rb' -require dir + '/simple_backend_test.rb' -require dir + '/i18n_exceptions_test.rb' diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/test/i18n_exceptions_test.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/test/i18n_exceptions_test.rb deleted file mode 100644 index 1ea1601681..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/test/i18n_exceptions_test.rb +++ /dev/null @@ -1,100 +0,0 @@ -$:.unshift "lib" - -require 'rubygems' -require 'test/unit' -require 'mocha' -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-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-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.store_translations 'de-DE', :bar => nil - I18n.backend.translate 'de-DE', :foo, :scope => :bar - end - - def force_invalid_pluralization_data - I18n.store_translations 'de-DE', :foo => [:bar] - I18n.backend.translate 'de-DE', :foo, :count => 1 - end - - def force_missing_interpolation_argument - I18n.store_translations 'de-DE', :foo => "{{bar}}" - I18n.backend.translate 'de-DE', :foo, :baz => 'baz' - end - - def force_reserved_interpolation_key - I18n.store_translations 'de-DE', :foo => "{{scope}}" - I18n.backend.translate 'de-DE', :foo, :baz => 'baz' - end -end
\ No newline at end of file diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/test/i18n_test.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/test/i18n_test.rb deleted file mode 100644 index bbb1316b49..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/test/i18n_test.rb +++ /dev/null @@ -1,141 +0,0 @@ -$:.unshift "lib" - -require 'rubygems' -require 'test/unit' -require 'mocha' -require 'i18n' -require 'active_support' - -class I18nTest < Test::Unit::TestCase - def setup - I18n.store_translations :'en-US', { - :currency => { - :format => { - :separator => '.', - :delimiter => ',', - } - } - } - end - - def test_uses_simple_backend_set_by_default - assert_equal I18n::Backend::Simple, I18n.backend - end - - def test_can_set_backend - assert_nothing_raised{ I18n.backend = self } - assert_equal self, I18n.backend - I18n.backend = I18n::Backend::Simple - end - - def test_uses_en_us_as_default_locale_by_default - assert_equal 'en-US', I18n.default_locale - end - - def test_can_set_default_locale - assert_nothing_raised{ I18n.default_locale = 'de-DE' } - assert_equal 'de-DE', I18n.default_locale - I18n.default_locale = 'en-US' - 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-DE' } - assert_equal 'de-DE', I18n.locale - assert_equal 'de-DE', Thread.current[:locale] - I18n.locale = 'en-US' - 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-DE', :foo, {} - I18n.translate :foo, :locale => 'de-DE' - end - - def test_delegates_localize_to_backend - I18n.backend.expects(:localize).with 'de-DE', :whatever, :default - I18n.localize :whatever, :locale => 'de-DE' - end - - def test_delegates_store_translations_to_backend - I18n.backend.expects(:store_translations).with 'de-DE', {:foo => :bar} - I18n.store_translations 'de-DE', {:foo => :bar} - end - - def test_delegates_populate_to_backend - I18n.backend.expects(:populate) # can't specify a block here as an expected argument - I18n.populate{ } - end - - def test_populate_yields_the_block - tmp = nil - I18n.populate do tmp = 'yielded' end - assert_equal 'yielded', tmp - end - - def test_translate_given_no_locale_uses_i18n_locale - I18n.backend.expects(:translate).with 'en-US', :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-DE', :precision, :scope => :"currency.format") - I18n.with_options :locale => 'de-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-US, no key", I18n.t - # end - - def test_translate_given_a_bogus_key_raises_missing_translation_data - assert_equal "translation missing: en-US, bogus", I18n.t(:bogus) - end - - def test_localize_nil_raises_argument_error - assert_raises(I18n::ArgumentError) { I18n.l nil } - end - - def test_localize_object_raises_argument_error - assert_raises(I18n::ArgumentError) { I18n.l Object.new } - end -end diff --git a/activesupport/lib/active_support/vendor/i18n-0.0.1/test/simple_backend_test.rb b/activesupport/lib/active_support/vendor/i18n-0.0.1/test/simple_backend_test.rb deleted file mode 100644 index c94d742e2d..0000000000 --- a/activesupport/lib/active_support/vendor/i18n-0.0.1/test/simple_backend_test.rb +++ /dev/null @@ -1,376 +0,0 @@ -$:.unshift "lib" - -require 'rubygems' -require 'test/unit' -require 'mocha' -require 'i18n' - -module I18nSimpleBackendTestSetup - def setup_backend - @backend = I18n::Backend::Simple - @backend.send :class_variable_set, :@@translations, {} - @backend.store_translations 'en-US', :foo => {:bar => 'bar', :baz => 'baz'} - end - alias :setup :setup_backend - - def add_datetime_translations - @backend.store_translations :'de-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 => ['less than 1 second', 'less than {{count}} seconds'], - :x_seconds => ['1 second', '{{count}} seconds'], - :less_than_x_minutes => ['less than a minute', 'less than {{count}} minutes'], - :x_minutes => ['1 minute', '{{count}} minutes'], - :about_x_hours => ['about 1 hour', 'about {{count}} hours'], - :x_days => ['1 day', '{{count}} days'], - :about_x_months => ['about 1 month', 'about {{count}} months'], - :x_months => ['1 month', '{{count}} months'], - :about_x_years => ['about 1 year', 'about {{count}} year'], - :over_x_years => ['over 1 year', 'over {{count}} years'] - } - } - } - end -end - -class I18nSimpleBackendTranslationsTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def test_store_translations_adds_translations # no, really :-) - @backend.store_translations :'en-US', :foo => 'bar' - assert_equal Hash[:'en-US', {:foo => 'bar'}], @backend.send(:class_variable_get, :@@translations) - end - - def test_store_translations_deep_merges_translations - @backend.store_translations :'en-US', :foo => {:bar => 'bar'} - @backend.store_translations :'en-US', :foo => {:baz => 'baz'} - assert_equal Hash[:'en-US', {:foo => {:bar => 'bar', :baz => 'baz'}}], @backend.send(:class_variable_get, :@@translations) - end - - def test_store_translations_forces_locale_to_sym - @backend.store_translations 'en-US', :foo => 'bar' - assert_equal Hash[:'en-US', {:foo => 'bar'}], @backend.send(:class_variable_get, :@@translations) - end - - def test_store_translations_covert_key_symbols - @backend.send :class_variable_set, :@@translations, {} # reset translations - @backend.store_translations :'en-US', 'foo' => {'bar' => 'baz'} - assert_equal Hash[:'en-US', {:foo => {:bar => 'baz'}}], - @backend.send(:class_variable_get, :@@translations) - end -end - -class I18nSimpleBackendTranslateTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def test_translate_calls_lookup_with_locale_given - @backend.expects(:lookup).with('de-DE', :bar, [:foo]).returns 'bar' - @backend.translate 'de-DE', :bar, :scope => [:foo] - end - - def test_translate_given_a_symbol_as_a_default_translates_the_symbol - assert_equal 'bar', @backend.translate('en-US', nil, :scope => [:foo], :default => :bar) - end - - def test_translate_given_an_array_as_default_uses_the_first_match - assert_equal 'bar', @backend.translate('en-US', :does_not_exist, :scope => [:foo], :default => [:does_not_exist_2, :bar]) - end - - def test_translate_an_array_of_keys_translates_all_of_them - assert_equal %w(bar baz), @backend.translate('en-US', [:bar, :baz], :scope => [:foo]) - end - - def test_translate_calls_pluralize - @backend.expects(:pluralize).with 'bar', 1 - @backend.translate 'en-US', :bar, :scope => [:foo], :count => 1 - end - - def test_translate_calls_interpolate - @backend.expects(:interpolate).with 'bar', {} - @backend.translate 'en-US', :bar, :scope => [:foo] - end - - def test_translate_calls_interpolate_including_count_as_a_value - @backend.expects(:interpolate).with 'bar', {:count => 1} - @backend.translate 'en-US', :bar, :scope => [:foo], :count => 1 - end - - def test_given_no_keys_it_returns_the_default - assert_equal 'default', @backend.translate('en-US', nil, :default => 'default') - end - - def test_translate_given_nil_as_a_locale_raises_an_argument_error - assert_raises(I18n::InvalidLocale){ @backend.translate nil, :bar } - end - - def test_translate_with_a_bogus_key_and_no_default_raises_missing_translation_data - assert_raises(I18n::MissingTranslationData){ @backend.translate 'de-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-US', nil) - end - - def test_lookup_given_nested_keys_looks_up_a_nested_hash_value - assert_equal 'bar', @backend.send(:lookup, 'en-US', :bar, [:foo]) - end -end - -class I18nSimpleBackendPluralizeTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def test_pluralize_given_nil_returns_the_given_entry - assert_equal ['bar', 'bars'], @backend.send(:pluralize, ['bar', 'bars'], nil) - end - - def test_pluralize_given_0_returns_plural_string - assert_equal 'bars', @backend.send(:pluralize, ['bar', 'bars'], 0) - end - - def test_pluralize_given_1_returns_singular_string - assert_equal 'bar', @backend.send(:pluralize, ['bar', 'bars'], 1) - end - - def test_pluralize_given_2_returns_plural_string - assert_equal 'bars', @backend.send(:pluralize, ['bar', 'bars'], 2) - end - - def test_pluralize_given_3_returns_plural_string - assert_equal 'bars', @backend.send(:pluralize, ['bar', 'bars'], 3) - end - - def test_interpolate_given_invalid_pluralization_data_raises_invalid_pluralization_data - assert_raises(I18n::InvalidPluralizationData){ @backend.send(:pluralize, ['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, 'Hi {{name}}!', :name => 'David') - end - - def test_interpolate_given_nil_as_a_string_returns_nil - assert_nil @backend.send(:interpolate, nil, :name => 'David') - end - - def test_interpolate_given_an_non_string_as_a_string_returns_nil - assert_equal [], @backend.send(:interpolate, [], :name => 'David') - end - - def test_interpolate_given_a_values_hash_with_nil_values_interpolates_the_string - assert_equal 'Hi !', @backend.send(:interpolate, 'Hi {{name}}!', {:name => nil}) - end - - def test_interpolate_given_an_empty_values_hash_raises_missing_interpolation_argument - assert_raises(I18n::MissingInterpolationArgument) { @backend.send(:interpolate, 'Hi {{name}}!', {}) } - end - - def test_interpolate_given_a_string_containing_a_reserved_key_raises_reserved_interpolation_key - assert_raises(I18n::ReservedInterpolationKey) { @backend.send(:interpolate, '{{default}}', {:default => nil}) } - end -end - -class I18nSimpleBackendLocalizeDateTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def setup - @backend = I18n::Backend::Simple - 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-DE', @date, :short) - end - - def test_translate_given_the_long_format_it_uses_it - assert_equal '01. Januar 2008', @backend.localize('de-DE', @date, :long) - end - - def test_translate_given_the_default_format_it_uses_it - assert_equal '01.01.2008', @backend.localize('de-DE', @date, :default) - end - - def test_translate_given_a_day_name_format_it_returns_a_day_name - assert_equal 'Dienstag', @backend.localize('de-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-DE', @date, '%a') - end - - def test_translate_given_a_month_name_format_it_returns_a_month_name - assert_equal 'Januar', @backend.localize('de-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-DE', @date, '%b') - end - - def test_translate_given_no_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de-DE', @date } - end - - def test_translate_given_an_unknown_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de-DE', @date, '%x' } - end - - def test_localize_nil_raises_argument_error - assert_raises(I18n::ArgumentError) { @backend.localize 'de-DE', nil } - end - - def test_localize_object_raises_argument_error - assert_raises(I18n::ArgumentError) { @backend.localize 'de-DE', Object.new } - end -end - -class I18nSimpleBackendLocalizeDateTimeTest < Test::Unit::TestCase - include I18nSimpleBackendTestSetup - - def setup - @backend = I18n::Backend::Simple - 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-DE', @morning, :short) - end - - def test_translate_given_the_long_format_it_uses_it - assert_equal '01. Januar 2008 06:00', @backend.localize('de-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-DE', @morning, :default) - end - - def test_translate_given_a_day_name_format_it_returns_the_correct_day_name - assert_equal 'Dienstag', @backend.localize('de-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-DE', @morning, '%a') - end - - def test_translate_given_a_month_name_format_it_returns_the_correct_month_name - assert_equal 'Januar', @backend.localize('de-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-DE', @morning, '%b') - end - - def test_translate_given_a_meridian_indicator_format_it_returns_the_correct_meridian_indicator - assert_equal 'am', @backend.localize('de-DE', @morning, '%p') - assert_equal 'pm', @backend.localize('de-DE', @evening, '%p') - end - - def test_translate_given_no_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de-DE', @morning } - end - - def test_translate_given_an_unknown_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de-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 - add_datetime_translations - @morning = Time.parse '2008-01-01 6:00 UTC' - @evening = Time.parse '2008-01-01 18:00 UTC' - end - - def teardown - ENV['TZ'] = @old_timezone - end - - def test_translate_given_the_short_format_it_uses_it - assert_equal '01. Jan 06:00', @backend.localize('de-DE', @morning, :short) - end - - def test_translate_given_the_long_format_it_uses_it - assert_equal '01. Januar 2008 06:00', @backend.localize('de-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-DE', @morning, :default) - # end - - def test_translate_given_a_day_name_format_it_returns_the_correct_day_name - assert_equal 'Dienstag', @backend.localize('de-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-DE', @morning, '%a') - end - - def test_translate_given_a_month_name_format_it_returns_the_correct_month_name - assert_equal 'Januar', @backend.localize('de-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-DE', @morning, '%b') - end - - def test_translate_given_a_meridian_indicator_format_it_returns_the_correct_meridian_indicator - assert_equal 'am', @backend.localize('de-DE', @morning, '%p') - assert_equal 'pm', @backend.localize('de-DE', @evening, '%p') - end - - def test_translate_given_no_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de-DE', @morning } - end - - def test_translate_given_an_unknown_format_it_does_not_fail - assert_nothing_raised{ @backend.localize 'de-DE', @morning, '%x' } - end -end - -class I18nSimpleBackendHelperMethodsTest < Test::Unit::TestCase - def setup - @backend = I18n::Backend::Simple - 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 diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb index 97649518b7..28dd34334f 100644 --- a/activesupport/test/buffered_logger_test.rb +++ b/activesupport/test/buffered_logger_test.rb @@ -70,7 +70,7 @@ class BufferedLoggerTest < Test::Unit::TestCase end @logger.flush - assert !@output.string.empty?, @logger.buffer.size + assert !@output.string.empty?, @logger.send(:buffer).size end define_method "test_disabling_auto_flush_with_#{disable.inspect}_should_flush_at_max_buffer_size_as_failsafe" do @@ -83,10 +83,10 @@ class BufferedLoggerTest < Test::Unit::TestCase end @logger.info 'there it is.' - assert !@output.string.empty?, @logger.buffer.size + assert !@output.string.empty?, @logger.send(:buffer).size end end - + def test_should_know_if_its_loglevel_is_below_a_given_level ActiveSupport::BufferedLogger::Severity.constants.each do |level| @logger.level = ActiveSupport::BufferedLogger::Severity.const_get(level) - 1 @@ -105,7 +105,7 @@ class BufferedLoggerTest < Test::Unit::TestCase @logger.info 'there it is.' assert !@output.string.empty?, @output.string end - + def test_should_create_the_log_directory_if_it_doesnt_exist tmp_directory = File.join(File.dirname(__FILE__), "tmp") log_file = File.join(tmp_directory, "development.log") @@ -115,4 +115,26 @@ class BufferedLoggerTest < Test::Unit::TestCase ensure FileUtils.rm_rf(tmp_directory) end + + def test_logger_should_maintain_separate_buffers_for_each_thread + @logger.auto_flushing = false + + a = Thread.new do + @logger.info("a"); Thread.pass; + @logger.info("b"); Thread.pass; + @logger.info("c"); @logger.flush + end + + b = Thread.new do + @logger.info("x"); Thread.pass; + @logger.info("y"); Thread.pass; + @logger.info("z"); @logger.flush + end + + a.join + b.join + + assert @output.string.include?("a\nb\nc\n") + assert @output.string.include?("x\ny\nz\n") + end end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index c5f7fb7fdd..9ea9389448 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -97,3 +97,33 @@ class FileStoreTest < Test::Unit::TestCase File.delete("foo.cache") end end + +class MemoryStoreTest < Test::Unit::TestCase + def setup + @cache = ActiveSupport::Cache.lookup_store(:memory_store) + end + + def test_should_read_and_write + @cache.write('foo', 'bar') + assert_equal 'bar', @cache.read('foo') + end + + def test_fetch_without_cache_miss + @cache.write('foo', 'bar') + assert_equal 'bar', @cache.fetch('foo') { 'baz' } + end + + def test_fetch_with_cache_miss + assert_equal 'baz', @cache.fetch('foo') { 'baz' } + end + + def test_fetch_with_forced_cache_miss + @cache.fetch('foo', :force => true) { 'bar' } + end + + def test_store_objects_should_be_immutable + @cache.write('foo', 'bar') + assert_raise(ActiveSupport::FrozenObjectError) { @cache.read('foo').gsub!(/.*/, 'baz') } + assert_equal 'bar', @cache.read('foo') + end +end diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 7f71ca2262..25b8eecef5 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -134,10 +134,10 @@ class CallbackChainTest < Test::Unit::TestCase assert_equal :bacon, @chain.find(:bacon).method end - def test_union - assert_equal [:bacon, :lettuce, :tomato], (@chain | Callback.new(:make, :bacon)).map(&:method) - assert_equal [:bacon, :lettuce, :tomato, :turkey], (@chain | CallbackChain.build(:make, :bacon, :lettuce, :tomato, :turkey)).map(&:method) - assert_equal [:bacon, :lettuce, :tomato, :turkey, :mayo], (@chain | Callback.new(:make, :mayo)).map(&:method) + def test_replace_or_append + assert_equal [:bacon, :lettuce, :tomato], (@chain.replace_or_append!(Callback.new(:make, :bacon))).map(&:method) + assert_equal [:bacon, :lettuce, :tomato, :turkey], (@chain.replace_or_append!(Callback.new(:make, :turkey))).map(&:method) + assert_equal [:bacon, :lettuce, :tomato, :turkey, :mayo], (@chain.replace_or_append!(Callback.new(:make, :mayo))).map(&:method) end def test_delete diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index ddfe1f904f..0f3cf4c75c 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -198,10 +198,6 @@ class DateExtCalculationsTest < Test::Unit::TestCase assert_equal Time.local(2005,2,21,23,59,59), Date.new(2005,2,21).end_of_day end - def test_date_acts_like_date - assert Date.new.acts_like_date? - end - def test_xmlschema with_env_tz 'US/Eastern' do assert_match(/^1980-02-28T00:00:00-05:?00$/, Date.new(1980, 2, 28).xmlschema) @@ -245,3 +241,15 @@ class DateExtCalculationsTest < Test::Unit::TestCase old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end end + +class DateExtBehaviorTest < Test::Unit::TestCase + def test_date_acts_like_date + assert Date.new.acts_like_date? + end + + def test_freeze_doesnt_clobber_memoized_instance_methods + assert_nothing_raised do + Date.today.freeze.inspect + end + end +end diff --git a/activesupport/test/core_ext/duplicable_test.rb b/activesupport/test/core_ext/duplicable_test.rb index 3ccfedccd7..8b6127f31e 100644 --- a/activesupport/test/core_ext/duplicable_test.rb +++ b/activesupport/test/core_ext/duplicable_test.rb @@ -1,7 +1,7 @@ require 'abstract_unit' class DuplicableTest < Test::Unit::TestCase - NO = [nil, false, true, :symbol, 1, 2.3, BigDecimal.new('4.56')] + NO = [nil, false, true, :symbol, 1, 2.3, BigDecimal.new('4.56'), Class.new] YES = ['1', Object.new, /foo/, [], {}, Time.now] def test_duplicable diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 2315d8f3db..deb9b7544d 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -58,6 +58,11 @@ class EnumerableTests < Test::Unit::TestCase assert_equal Payment.new(0), [].sum(Payment.new(0)) end + def test_each_with_object + result = %w(foo bar).each_with_object({}) { |str, hsh| hsh[str] = str.upcase } + assert_equal({'foo' => 'FOO', 'bar' => 'BAR'}, result) + end + def test_index_by payments = [ Payment.new(5), Payment.new(15), Payment.new(10) ] assert_equal({ 5 => payments[0], 15 => payments[1], 10 => payments[2] }, diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index fc8ed45358..7a414e946f 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -62,7 +62,7 @@ class HashExtTest < Test::Unit::TestCase @symbols = @symbols.with_indifferent_access @mixed = @mixed.with_indifferent_access - assert_equal 'a', @strings.send!(:convert_key, :a) + assert_equal 'a', @strings.__send__(:convert_key, :a) assert_equal 1, @strings.fetch('a') assert_equal 1, @strings.fetch(:a.to_s) @@ -75,9 +75,9 @@ class HashExtTest < Test::Unit::TestCase hashes.each do |name, hash| method_map.sort_by { |m| m.to_s }.each do |meth, expected| - assert_equal(expected, hash.send!(meth, 'a'), + assert_equal(expected, hash.__send__(meth, 'a'), "Calling #{name}.#{meth} 'a'") - assert_equal(expected, hash.send!(meth, :a), + assert_equal(expected, hash.__send__(meth, :a), "Calling #{name}.#{meth} :a") end end @@ -733,7 +733,7 @@ class HashToXmlTest < Test::Unit::TestCase def test_empty_string_works_for_typecast_xml_value assert_nothing_raised do - Hash.send!(:typecast_xml_value, "") + Hash.__send__(:typecast_xml_value, "") end end @@ -839,6 +839,27 @@ class QueryTest < Test::Unit::TestCase :person => {:id => [20, 10]} end + def test_expansion_count_is_limited + assert_raises RuntimeError do + attack_xml = <<-EOT + <?xml version="1.0" encoding="UTF-8"?> + <!DOCTYPE member [ + <!ENTITY a "&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;"> + <!ENTITY b "&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;"> + <!ENTITY c "&d;&d;&d;&d;&d;&d;&d;&d;&d;&d;"> + <!ENTITY d "&e;&e;&e;&e;&e;&e;&e;&e;&e;&e;"> + <!ENTITY e "&f;&f;&f;&f;&f;&f;&f;&f;&f;&f;"> + <!ENTITY f "&g;&g;&g;&g;&g;&g;&g;&g;&g;&g;"> + <!ENTITY g "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> + ]> + <member> + &a; + </member> + EOT + Hash.from_xml(attack_xml) + end + end + private def assert_query_equal(expected, actual, message = nil) assert_equal expected.split('&'), actual.to_query.split('&') diff --git a/activesupport/test/core_ext/module/synchronization_test.rb b/activesupport/test/core_ext/module/synchronization_test.rb new file mode 100644 index 0000000000..b1d4bc5e06 --- /dev/null +++ b/activesupport/test/core_ext/module/synchronization_test.rb @@ -0,0 +1,85 @@ +require 'abstract_unit' + +class SynchronizationTest < Test::Unit::TestCase + def setup + @target = Class.new + @target.cattr_accessor :mutex, :instance_writer => false + @target.mutex = Mutex.new + @instance = @target.new + end + + def test_synchronize_aliases_method_chain_with_synchronize + @target.module_eval do + attr_accessor :value + synchronize :value, :with => :mutex + end + assert @instance.respond_to?(:value_with_synchronization) + assert @instance.respond_to?(:value_without_synchronization) + end + + def test_synchronize_does_not_change_behavior + @target.module_eval do + attr_accessor :value + synchronize :value, :with => :mutex + end + expected = "some state" + @instance.value = expected + assert_equal expected, @instance.value + end + + def test_synchronize_with_no_mutex_raises_an_argument_error + assert_raises(ArgumentError) do + @target.synchronize :to_s + end + end + + def test_double_synchronize_raises_an_argument_error + @target.synchronize :to_s, :with => :mutex + assert_raises(ArgumentError) do + @target.synchronize :to_s, :with => :mutex + end + end + + def dummy_sync + dummy = Object.new + def dummy.synchronize + @sync_count ||= 0 + @sync_count += 1 + yield + end + def dummy.sync_count; @sync_count; end + dummy + end + + def test_mutex_is_entered_during_method_call + @target.mutex = dummy_sync + @target.synchronize :to_s, :with => :mutex + @instance.to_s + @instance.to_s + assert_equal 2, @target.mutex.sync_count + end + + def test_can_synchronize_method_with_punctuation + @target.module_eval do + def dangerous? + @dangerous + end + def dangerous! + @dangerous = true + end + end + @target.synchronize :dangerous?, :dangerous!, :with => :mutex + @instance.dangerous! + assert @instance.dangerous? + end + + def test_can_synchronize_singleton_methods + @target.mutex = dummy_sync + class << @target + synchronize :to_s, :with => :mutex + end + assert @target.respond_to?(:to_s_without_synchronization) + assert_nothing_raised { @target.to_s; @target.to_s } + assert_equal 2, @target.mutex.sync_count + end +end
\ No newline at end of file diff --git a/activesupport/test/core_ext/object_and_class_ext_test.rb b/activesupport/test/core_ext/object_and_class_ext_test.rb index b0a746fdc7..e88dcb52d5 100644 --- a/activesupport/test/core_ext/object_and_class_ext_test.rb +++ b/activesupport/test/core_ext/object_and_class_ext_test.rb @@ -108,11 +108,6 @@ class ClassExtTest < Test::Unit::TestCase end class ObjectTests < Test::Unit::TestCase - def test_send_bang_aliases_send_before_19 - assert_respond_to 'a', :send! - assert_equal 1, 'a'.send!(:size) - end - def test_suppress_re_raises assert_raises(LoadError) { suppress(ArgumentError) {raise LoadError} } end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 19a30f1730..c9f959ef32 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -32,6 +32,10 @@ class StringInflectionsTest < Test::Unit::TestCase end end + def test_camelize_lower + assert_equal('capital', 'Capital'.camelize(:lower)) + end + def test_underscore CamelToUnderscore.each do |camel, underscore| assert_equal(underscore, camel.underscore) diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index 8740497b3d..4749950f25 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -625,3 +625,37 @@ class TimeExtCalculationsTest < Test::Unit::TestCase old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ') end end + +class TimeExtMarshalingTest < Test::Unit::TestCase + def test_marshaling_with_utc_instance + t = Time.utc(2000) + marshaled = Marshal.dump t + unmarshaled = Marshal.load marshaled + assert_equal t, unmarshaled + assert_equal t.zone, unmarshaled.zone + end + + def test_marshaling_with_local_instance + t = Time.local(2000) + marshaled = Marshal.dump t + unmarshaled = Marshal.load marshaled + assert_equal t, unmarshaled + assert_equal t.zone, unmarshaled.zone + end + + def test_marshaling_with_frozen_utc_instance + t = Time.utc(2000).freeze + marshaled = Marshal.dump t + unmarshaled = Marshal.load marshaled + assert_equal t, unmarshaled + assert_equal t.zone, unmarshaled.zone + end + + def test_marshaling_with_frozen_local_instance + t = Time.local(2000).freeze + marshaled = Marshal.dump t + unmarshaled = Marshal.load marshaled + assert_equal t, unmarshaled + assert_equal t.zone, unmarshaled.zone + end +end diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 39c9c74c94..18ad784837 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -146,42 +146,42 @@ class DependenciesTest < Test::Unit::TestCase def test_directories_manifest_as_modules_unless_const_defined with_loading 'autoloading_fixtures' do assert_kind_of Module, ModuleFolder - Object.send! :remove_const, :ModuleFolder + Object.__send__ :remove_const, :ModuleFolder end end def test_module_with_nested_class with_loading 'autoloading_fixtures' do assert_kind_of Class, ModuleFolder::NestedClass - Object.send! :remove_const, :ModuleFolder + Object.__send__ :remove_const, :ModuleFolder end end def test_module_with_nested_inline_class with_loading 'autoloading_fixtures' do assert_kind_of Class, ModuleFolder::InlineClass - Object.send! :remove_const, :ModuleFolder + Object.__send__ :remove_const, :ModuleFolder end end def test_directories_may_manifest_as_nested_classes with_loading 'autoloading_fixtures' do assert_kind_of Class, ClassFolder - Object.send! :remove_const, :ClassFolder + Object.__send__ :remove_const, :ClassFolder end end def test_class_with_nested_class with_loading 'autoloading_fixtures' do assert_kind_of Class, ClassFolder::NestedClass - Object.send! :remove_const, :ClassFolder + Object.__send__ :remove_const, :ClassFolder end end def test_class_with_nested_inline_class with_loading 'autoloading_fixtures' do assert_kind_of Class, ClassFolder::InlineClass - Object.send! :remove_const, :ClassFolder + Object.__send__ :remove_const, :ClassFolder end end @@ -190,7 +190,7 @@ class DependenciesTest < Test::Unit::TestCase assert_kind_of Class, ClassFolder::ClassFolderSubclass assert_kind_of Class, ClassFolder assert_equal 'indeed', ClassFolder::ClassFolderSubclass::ConstantInClassFolder - Object.send! :remove_const, :ClassFolder + Object.__send__ :remove_const, :ClassFolder end end @@ -199,7 +199,7 @@ class DependenciesTest < Test::Unit::TestCase sibling = ModuleFolder::NestedClass.class_eval "NestedSibling" assert defined?(ModuleFolder::NestedSibling) assert_equal ModuleFolder::NestedSibling, sibling - Object.send! :remove_const, :ModuleFolder + Object.__send__ :remove_const, :ModuleFolder end end @@ -208,7 +208,7 @@ class DependenciesTest < Test::Unit::TestCase assert ! defined?(ModuleFolder) assert_raises(NameError) { ModuleFolder::Object } assert_raises(NameError) { ModuleFolder::NestedClass::Object } - Object.send! :remove_const, :ModuleFolder + Object.__send__ :remove_const, :ModuleFolder end end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 6c0c14e866..8eebe1be25 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -34,6 +34,13 @@ class InflectorTest < Test::Unit::TestCase end end + def test_overwrite_previous_inflectors + assert_equal("series", ActiveSupport::Inflector.singularize("series")) + ActiveSupport::Inflector.inflections.singular "series", "serie" + assert_equal("serie", ActiveSupport::Inflector.singularize("series")) + ActiveSupport::Inflector.inflections.uncountable "series" # Return to normal + end + MixtureToTitleCase.each do |before, titleized| define_method "test_titleize_#{before}" do assert_equal(titleized, ActiveSupport::Inflector.titleize(before)) @@ -46,6 +53,10 @@ class InflectorTest < Test::Unit::TestCase end end + def test_camelize_with_lower_downcases_the_first_letter + assert_equal('capital', ActiveSupport::Inflector.camelize('Capital', false)) + end + def test_underscore CamelToUnderscore.each do |camel, underscore| assert_equal(underscore, ActiveSupport::Inflector.underscore(camel)) diff --git a/activesupport/test/secure_random_test.rb b/activesupport/test/secure_random_test.rb new file mode 100644 index 0000000000..b0b6c21a81 --- /dev/null +++ b/activesupport/test/secure_random_test.rb @@ -0,0 +1,15 @@ +require 'abstract_unit' + +class SecureRandomTest < Test::Unit::TestCase + def test_random_bytes + b1 = ActiveSupport::SecureRandom.random_bytes(64) + b2 = ActiveSupport::SecureRandom.random_bytes(64) + assert_not_equal b1, b2 + end + + def test_hex + b1 = ActiveSupport::SecureRandom.hex(64) + b2 = ActiveSupport::SecureRandom.hex(64) + assert_not_equal b1, b2 + end +end diff --git a/activesupport/test/typed_array_test.rb b/activesupport/test/typed_array_test.rb deleted file mode 100644 index 023f3a1b84..0000000000 --- a/activesupport/test/typed_array_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -require 'abstract_unit' - -class TypedArrayTest < Test::Unit::TestCase - class StringArray < ActiveSupport::TypedArray - def self.type_cast(obj) - obj.to_s - end - end - - def setup - @array = StringArray.new - end - - def test_string_array_initialize - assert_equal ["1", "2", "3"], StringArray.new([1, "2", :"3"]) - end - - def test_string_array_append - @array << 1 - @array << "2" - @array << :"3" - assert_equal ["1", "2", "3"], @array - end - - def test_string_array_concat - @array.concat([1, "2"]) - @array.concat([:"3"]) - assert_equal ["1", "2", "3"], @array - end - - def test_string_array_insert - @array.insert(0, 1) - @array.insert(1, "2") - @array.insert(2, :"3") - assert_equal ["1", "2", "3"], @array - end - - def test_string_array_push - @array.push(1) - @array.push("2") - @array.push(:"3") - assert_equal ["1", "2", "3"], @array - end - - def test_string_array_unshift - @array.unshift(:"3") - @array.unshift("2") - @array.unshift(1) - assert_equal ["1", "2", "3"], @array - end -end |