Compare commits

..

86 Commits

Author SHA1 Message Date
Greg Ose
730e6a273c Merge pull request #64 from github/json-sessions
Support custom serialization for Session::CookieStore
2014-05-16 00:06:44 -05:00
Greg Ose
aa1b6d1284 bump rails version 2014-05-15 22:49:19 -05:00
Greg Ose
5f6c95e29e Merge branch '2-3-github' into json-sessions 2014-05-13 15:29:47 -05:00
Charlie Somerville
7403667b89 Merge pull request #67 from github/2-3-github+cve-2014-0130
CVE-2014-0130 protection
2014-05-10 00:52:08 +10:00
Charlie Somerville
1a45ec57bf CVE-2014-0130 protection 2014-05-09 23:55:20 +10:00
Greg Ose
9070fbcffe Revert nested hash indifference, swap delete order
Upstream doesnt support nested hashes having indifferent access, we
should stay consistent. Swap order for returned value in session hash.
2014-05-07 14:27:52 -05:00
Greg Ose
364b534815 support indifferent access for hashes stored within FlashHash 2014-04-30 13:18:28 -05:00
Greg Ose
14da203564 indifferent delete 2014-04-29 10:37:53 -05:00
Greg Ose
f46a4bab08 indifferent access to flash hash 2014-04-29 10:21:16 -05:00
Greg Ose
198aa6ef99 Update tests to load flash from session value 2014-04-28 15:07:52 -05:00
Greg Ose
b3ae51c9fc Add serializer option to cookie store and use Rails 4 Hash flash
Backport for Rails 4 flash hash based on https://github.com/envato/rails_4_session_flash_backport
2014-04-28 14:42:24 -05:00
Patrick Toomey
1e6e438f6e Update RAILS_VERSION 2014-04-24 10:45:25 -05:00
Patrick Toomey
2b01f832a3 Merge pull request #63 from github/memoize-nil-check
make sure nil isn't memoized and frozen
2014-04-24 10:44:47 -05:00
Patrick Toomey
1e5fda763e backport memoiziation from 3.0 2014-04-24 10:24:04 -05:00
Dirkjan Bussink
7c3d4ec43c Bump version 2014-03-31 13:43:08 +02:00
Dirkjan Bussink
7343ed7b05 Merge pull request #53 from github/dbussink/no-toplevel-exception-rescue
We shouldn't try to rescue every type of exception here
2014-03-31 11:42:02 +00:00
Dirkjan Bussink
2a70c9691d We shouldn't try to rescue every type of exception here 2014-03-31 13:35:28 +02:00
Dirkjan Bussink
a141d9de0d bump 2.3.14.github41 2014-03-27 13:55:26 +01:00
Dirkjan Bussink
74492f43a8 Merge pull request #51 from github/dbussink/fix-logging-frozen-string-query
Dup string before changing encoding because it might be frozen
2014-03-27 12:53:25 +00:00
Dirkjan Bussink
c2894170bf Dup string before changing encoding because it might be frozen
Calling String#force_encoding! on a frozen string throws an exception.
By dupping the string we prevent this from happening.
2014-03-27 13:47:08 +01:00
Charlie Somerville
057aed6e18 Merge pull request #48 from github/2-3-kill-backtick-monkey-patch
[2.3] Kill Object#` monkey patch
2014-02-23 23:20:47 +11:00
Charlie Somerville
02fc012b42 kill Object#` monkey patch 2014-02-23 23:17:44 +11:00
Mastahyeti
4fdaf21b28 bump 2.3.14.github40 2014-02-18 15:28:32 -06:00
Ben Toews
35b871fbcd Merge pull request #47 from github/CVE-2014-0081
CVE-2014-0081
2014-02-18 15:28:00 -06:00
Mastahyeti
a5697840d6 escape format for CVE-2014-0081 2014-02-18 15:25:05 -06:00
Andy Lindeman
d0e554d231 Bumps to github39 2014-02-14 00:12:09 -05:00
Andy Lindeman
d38b7664cc github38 2014-02-13 22:36:14 -05:00
Andy Lindeman
e4cd9caf02 Merge pull request #46 from github/runtime_header
Removes the X-Runtime header from ActionController::Benchmarking
2014-02-13 22:34:18 -05:00
Andy Lindeman
89e4514704 Removes the X-Runtime header from ActionController::Benchmarking
The `Rack::Runtime` middleware now provides this header
2014-02-13 22:25:27 -05:00
Joshua Peek
0a0d975f51 github37 2014-02-11 23:36:30 -06:00
Joshua Peek
62daf4cb6f Merge pull request #45 from github/rack-session-skip
Backport Rack session skip
2014-02-11 23:34:52 -06:00
Joshua Peek
24711e1e29 Backport env['rack.session.options'][:skip] 2014-02-11 23:22:39 -06:00
Charlie Somerville
cf8f36930c Merge pull request #44 from github/builder-3.2.2
Builder 3.2.2
2014-01-20 19:55:16 -08:00
Charlie Somerville
d622643e47 fix tests 2014-01-21 14:30:00 +11:00
Charlie Somerville
3f0241a613 use assert_includes so we get a useful failure message 2014-01-21 14:28:01 +11:00
Charlie Somerville
38a7432590 add builder 3.2.2 to Gemfile.sh and use that instead of vendored copy 2014-01-21 14:22:20 +11:00
Charlie Somerville
1220d3c3ed delete vendored builder 2.1.2 2014-01-21 14:16:36 +11:00
Charlie Somerville
3d72818356 Merge pull request #43 from github/i18n-0.6.9
i18n 0.4.1
2014-01-20 18:05:20 -08:00
Charlie Somerville
221477dc21 fix this bit 2014-01-21 12:51:40 +11:00
Charlie Somerville
975155c110 use i18n 0.6.9 as a gem 2014-01-21 12:33:39 +11:00
Charlie Somerville
2931987892 delete vendored i18n-0.4.1 2014-01-21 12:25:44 +11:00
Charlie Somerville
e3290b98dd Merge pull request #42 from github/dont-test-on-1.9
Don't run tests on 1.9.3
2014-01-20 17:25:13 -08:00
Charlie Somerville
20088080a5 Don't run tests on 1.9.3 2014-01-21 12:23:05 +11:00
Dirkjan Bussink
24e348489d github36 2014-01-17 18:10:31 +01:00
Dirkjan Bussink
ba4f4f8a01 Treat mysql in the name as a mysql driver 2014-01-17 18:05:20 +01:00
Aman Gupta
ccf254b6cb build using 2.1 first 2014-01-15 01:10:18 -08:00
Mislav Marohnić
3766b1b377 github35 2014-01-13 13:58:57 -08:00
Mislav Marohnić
d3f87776a3 Merge pull request #41 from github/disable-generated-id
Disable auto-generated form field IDs by passing nil for "id" attribute
2014-01-13 13:57:38 -08:00
Mislav Marohnić
18c7c1f753 Disable auto-generated form field IDs by passing nil for "id" attribute
Previously it was not possible to opt out of auto-generated ID values
for various form fields.
2014-01-13 13:22:06 -08:00
Aman Gupta
f63b0340ff github34 2014-01-08 21:04:30 -08:00
Aman Gupta
7224ee1419 Merge pull request #37 from github/erb-freeze
Freeze ERB string literals
2014-01-08 20:33:01 -08:00
Aman Gupta
0c52ae6df3 Merge pull request #39 from github/write-fragment-fix
Fix fragment caching in mixed encoding scenario
2014-01-08 20:32:40 -08:00
Aman Gupta
f8b7cd2df7 Merge pull request #40 from github/ruby-2.1
Ruby 2.1
2014-01-08 20:32:12 -08:00
Aman Gupta
c73ba86136 use new 2.1 api 2014-01-08 18:03:55 -08:00
Aman Gupta
98fa5dd465 build on ruby 2.1 2014-01-08 17:46:13 -08:00
Mislav Marohnić
fa41bedf6b Don't rely on default encoding always being ASCII-8BIT 2014-01-08 17:41:17 -08:00
Aman Gupta
0a8282c557 freeze literals 2014-01-08 17:28:31 -08:00
Mislav Marohnić
d4a4facfcc Add test for extracting the cache fragment with mixed encodings 2014-01-08 17:12:18 -08:00
Aman Gupta
dd4146854a Fix fragment caching in mixed encodings scenario
To reduce ambiguity between char- and byte-based operations, explicitly
do byte operations when extracting the fragment that needs to be cached.
2014-01-08 16:35:55 -08:00
Charlie Somerville
cedf026a14 bump version to github33 2013-12-30 15:45:48 +11:00
Charlie Somerville
7ac3b0fa4f Merge pull request #34 from github/remove-cgi
Remove CGI support
2013-12-29 19:56:48 -08:00
Charlie Somerville
31cd7ea26d remove this NCGI stuff 2013-12-30 14:29:27 +11:00
Charlie Somerville
df387ab385 remove FastCGI crap 2013-12-30 14:28:24 +11:00
Charlie Somerville
0118959601 remove the webrick server 2013-12-30 14:26:08 +11:00
Charlie Somerville
83448c7de5 remove dispatch.rb and gateway.cgi 2013-12-30 14:23:00 +11:00
Charlie Somerville
8f99d00868 require properly 2013-12-30 14:23:00 +11:00
Charlie Somerville
987b61bd1d kill QueryExtension, it's more dead junk 2013-12-30 14:15:55 +11:00
Charlie Somerville
f05e54a9f3 remove stdinput monkey patch 2013-12-30 14:15:51 +11:00
Charlie Somerville
b9918117bb delete ActionController::CGIHandler and CgiRequest 2013-12-30 14:11:07 +11:00
Charlie Somerville
42f85d118d don't autoload CGIHandler and CgiRequest 2013-12-30 14:10:28 +11:00
Charlie Somerville
acb182d094 @output is never used anywhere, kill it 2013-12-30 14:09:20 +11:00
Charlie Somerville
6e0fcb788d remove CGI from the dispatcher 2013-12-30 14:09:00 +11:00
Charlie Somerville
fed4fafa8a Merge pull request #33 from github/dont-reload-middleware-stack-every-request
Don't reload middleware stack every request
2013-12-29 19:07:59 -08:00
Charlie Somerville
f699184047 test that we never call build_middleware_stack after initialization 2013-12-30 13:59:18 +11:00
Charlie Somerville
55d6a9f2df don't reload the middleware stack every request in development 2013-12-30 13:53:48 +11:00
Ted Nyman
e5bebc01a8 Merge pull request #32 from github/bump-to-github32
Bump to 2.3.14.github32
2013-12-03 14:53:14 -08:00
Ted Nyman
a019f07a39 Bump to 2.3.14.github32 2013-12-03 14:50:02 -08:00
Ted Nyman
d13866d75d Merge pull request #30 from github/CVE-2013-6417
CVE-2013-6417
2013-12-03 14:46:53 -08:00
Nathan Witmer
dfa2f469a4 Merge pull request #31 from github/currency-security-fix
CVE-2013-6415: Escape the unit value provided to number_to_currency
2013-12-03 14:41:51 -08:00
Nathan Witmer
bf0d43bb77 Only escape value if present 2013-12-03 14:47:38 -07:00
Nathan Witmer
72cebbcb59 Escape the unit value provided to number_to_currency
Fixes CVE-2013-6415.

Previously the values were trusted blindly allowing for potential XSS attacks.

This is different from the original upstream patch for 3.x in that return values
from other number helper methods are not marked as html_safe, so the html
escaping always applies. This requires applications to explicitly set .html_safe
on unit strings and number separators when calling number_to_currency.
2013-12-03 14:32:26 -07:00
Ted Nyman
379dd9071c Documentation for #deep_munge 2013-12-03 13:24:11 -08:00
Ted Nyman
a743f17dbd #deep_munge for CVE-2013-6417 2013-12-03 13:23:02 -08:00
Charlie Somerville
25b896611d Merge pull request #29 from github/tzinfo-json
Load timezone data from one big marshalled file
2013-12-03 00:38:50 -08:00
Charlie Somerville
b988837359 load definitions from a marshalled file 2013-12-03 19:32:36 +11:00
Charlie Somerville
890aff3b9d use vendored tzinfo 2013-12-03 18:10:11 +11:00
95 changed files with 462 additions and 5279 deletions

View File

@@ -5,3 +5,5 @@ gem install sqlite3 -v=1.3.7
gem install rack -v=1.4.5
gem install erubis -v=2.7.0
gem install json -v=1.8.0
gem install i18n -v=0.6.9
gem install builder -v=3.2.2

View File

@@ -1 +1 @@
2.3.14.github31
2.3.14.github45

View File

@@ -38,7 +38,7 @@ module ActionController
# TODO: Review explicit to see if they will automatically be handled by
# the initilizer if they are really needed.
def self.load_all!
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
[Base, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
end
autoload :Base, 'action_controller/base'
@@ -99,10 +99,6 @@ module ActionController
autoload :CookieStore, 'action_controller/session/cookie_store'
autoload :MemCacheStore, 'action_controller/session/mem_cache_store'
end
# DEPRECATE: Remove CGI support
autoload :CgiRequest, 'action_controller/cgi_process'
autoload :CGIHandler, 'action_controller/cgi_process'
end
autoload :Mime, 'action_controller/mime_type'

View File

@@ -1320,7 +1320,14 @@ module ActionController #:nodoc:
render
end
CVE_2014_0310 = Class.new(StandardError)
def perform_action
# CVE-2014-0130 protection
if action_name.include? "/"
raise CVE_2014_0310
end
if action_methods.include?(action_name)
send(action_name)
default_render unless performed?

View File

@@ -87,7 +87,6 @@ module ActionController #:nodoc:
log_message << " [#{complete_request_uri rescue "unknown"}]"
logger.info(log_message)
response.headers["X-Runtime"] = "%.0f" % ms
else
perform_action_without_benchmark
end

View File

@@ -39,9 +39,9 @@ module ActionController #:nodoc:
if cache = read_fragment(name, options)
buffer.safe_concat(cache.html_safe)
else
pos = buffer.length
pos = buffer.bytesize
block.call
write_fragment(name, buffer[pos..-1], options)
write_fragment(name, buffer.byteslice(pos..-1), options)
end
else
block.call

View File

@@ -1,10 +1,6 @@
require 'action_controller/cgi_ext/stdinput'
require 'action_controller/cgi_ext/query_extension'
require 'action_controller/cgi_ext/cookie'
class CGI #:nodoc:
include ActionController::CgiExt::Stdinput
class << self
alias :escapeHTML_fail_on_nil :escapeHTML

View File

@@ -1,4 +1,6 @@
require 'delegate'
require 'cgi'
require 'cgi/cookie'
CGI.module_eval { remove_const "Cookie" }
@@ -24,7 +26,7 @@ class CGI #:nodoc:
# * <tt>:secure</tt> - Whether this cookie is a secure cookie or not (defaults to
# +false+). Secure cookies are only transmitted to HTTPS servers.
# * <tt>:http_only</tt> - Whether this cookie can be accessed by client side scripts (e.g. document.cookie) or only over HTTP.
# More details in http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx. Defaults to +false+.
# More details in http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx. Defaults to +false+.
#
# These keywords correspond to attributes of the cookie object.
def initialize(name = '', *value)

View File

@@ -1,22 +0,0 @@
require 'cgi'
class CGI #:nodoc:
module QueryExtension
# Remove the old initialize_query method before redefining it.
remove_method :initialize_query
# Neuter CGI parameter parsing.
def initialize_query
# Fix some strange request environments.
env_table['REQUEST_METHOD'] ||= 'GET'
# POST assumes missing Content-Type is application/x-www-form-urlencoded.
if env_table['CONTENT_TYPE'].blank? && env_table['REQUEST_METHOD'] == 'POST'
env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
end
@cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
@params = {}
end
end
end

View File

@@ -1,24 +0,0 @@
require 'cgi'
module ActionController
module CgiExt
# Publicize the CGI's internal input stream so we can lazy-read
# request.body. Make it writable so we don't have to play $stdin games.
module Stdinput
def self.included(base)
base.class_eval do
remove_method :stdinput
attr_accessor :stdinput
end
base.alias_method_chain :initialize, :stdinput
end
def initialize_with_stdinput(type = nil, stdinput = $stdin)
@stdinput = stdinput
@stdinput.set_encoding(Encoding::BINARY) if @stdinput.respond_to?(:set_encoding)
initialize_without_stdinput(type || 'query')
end
end
end
end

View File

@@ -1,77 +0,0 @@
require 'action_controller/cgi_ext'
module ActionController #:nodoc:
class CGIHandler
module ProperStream
def each
while line = gets
yield line
end
end
def read(*args)
if args.empty?
super || ""
else
super
end
end
end
def self.dispatch_cgi(app, cgi, out = $stdout)
env = cgi.__send__(:env_table)
env.delete "HTTP_CONTENT_LENGTH"
cgi.stdinput.extend ProperStream
env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
env.update({
"rack.version" => [0,1],
"rack.input" => cgi.stdinput,
"rack.errors" => $stderr,
"rack.multithread" => false,
"rack.multiprocess" => true,
"rack.run_once" => false,
"rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
})
env["QUERY_STRING"] ||= ""
env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
env["REQUEST_PATH"] ||= "/"
env.delete "PATH_INFO" if env["PATH_INFO"] == ""
status, headers, body = app.call(env)
begin
out.binmode if out.respond_to?(:binmode)
out.sync = false if out.respond_to?(:sync=)
headers['Status'] = status.to_s
if headers.include?('Set-Cookie')
headers['cookie'] = headers.delete('Set-Cookie').split("\n")
end
out.write(cgi.header(headers))
body.each { |part|
out.write part
out.flush if out.respond_to?(:flush)
}
ensure
body.close if body.respond_to?(:close)
end
end
end
class CgiRequest #:nodoc:
DEFAULT_SESSION_OPTIONS = {
:database_manager => nil,
:prefix => "ruby_sess.",
:session_path => "/",
:session_key => "_session_id",
:cookie_only => true,
:session_http_only => true
}
end
end

View File

@@ -22,11 +22,6 @@ module ActionController
end
end
# DEPRECATE: Remove CGI support
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
new(output).dispatch_cgi(cgi, session_options)
end
# Add a preparation callback. Preparation callbacks are run before every
# request in development mode, and before the first request in production
# mode.
@@ -42,13 +37,7 @@ module ActionController
end
def run_prepare_callbacks
if defined?(Rails) && Rails.logger
logger = Rails.logger
else
logger = Logger.new($stderr)
end
new(logger).send :run_callbacks, :prepare_dispatch
new.send :run_callbacks, :prepare_dispatch
end
def reload_application
@@ -75,10 +64,8 @@ module ActionController
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
# DEPRECATE: Remove arguments, since they are only used by CGI
def initialize(output = $stdout, request = nil, response = nil)
@output = output
build_middleware_stack if @@cache_classes
def initialize
build_middleware_stack
end
def dispatch
@@ -96,21 +83,11 @@ module ActionController
end
end
# DEPRECATE: Remove CGI support
def dispatch_cgi(cgi, session_options)
CGIHandler.dispatch_cgi(self, cgi, @output)
end
def call(env)
if @@cache_classes
@app.call(env)
else
Reloader.run do
# When class reloading is turned on, we will want to rebuild the
# middleware stack every time we process a request. If we don't
# rebuild the middleware stack, then the stack may contain references
# to old classes metal classes, which will b0rk class reloading.
build_middleware_stack
@app.call(env)
end
end

View File

@@ -45,37 +45,74 @@ module ActionController #:nodoc:
end
def []=(k, v)
k = k.to_s
@flash[k] = v
@flash.discard(k)
v
end
def [](k)
@flash[k]
@flash[k.to_s]
end
end
class FlashHash < Hash
def self.from_session_value(value)
flash = case value
when FlashHash # Rails 2.3
value
when Hash # Rails 4.0
flashes = value['flashes'] || {}
flashes.stringify_keys!
discard = value['discard'] || []
discard = discard.map do |item|
item.kind_of?(Symbol) ? item.to_s : item
end
used = Hash[flashes.keys.map{|k| [k, discard.include?(k)] }]
new_from_values(flashes, used)
else
new
end
flash
end
def initialize #:nodoc:
super
@used = {}
end
def to_session_value
return nil if empty?
rails_3_discard_list = @used.map{|k,v| k if v}.compact
{'discard' => rails_3_discard_list, 'flashes' => Hash[to_a]}
end
def []=(k, v) #:nodoc:
k = k.to_s
keep(k)
super
super(k, v)
end
def [](k)
super(k.to_s)
end
def delete(k)
super(k.to_s)
end
def update(h) #:nodoc:
h.stringify_keys!
h.keys.each { |k| keep(k) }
super
super(h)
end
alias :merge! :update
def replace(h) #:nodoc:
@used = {}
super
super(h.stringify_keys)
end
# Sets a flash that will not be available to the next action, only to the current.
@@ -126,8 +163,7 @@ module ActionController #:nodoc:
end
def store(session, key = "flash")
return if self.empty?
session[key] = self
session[key] = to_session_value
end
private
@@ -138,11 +174,20 @@ module ActionController #:nodoc:
# use('msg', false) # marks the "msg" entry as unused (keeps it around for one more action)
def use(k=nil, v=true)
unless k.nil?
@used[k] = v
@used[k.to_s] = v
else
keys.each{ |key| use(key, v) }
end
end
def self.new_from_values(flashes, used)
new.tap do |flash_hash|
flashes.each do |k, v|
flash_hash[k] = v
end
flash_hash.instance_variable_set("@used", used)
end
end
end
module InstanceMethods #:nodoc:
@@ -168,11 +213,11 @@ module ActionController #:nodoc:
if notice = response_status_and_flash.delete(:notice)
flash[:notice] = notice
end
if other_flashes = response_status_and_flash.delete(:flash)
flash.update(other_flashes)
end
redirect_to_without_flash(options, response_status_and_flash)
end
@@ -181,19 +226,19 @@ module ActionController #:nodoc:
# to put a new one.
def flash #:doc:
if !defined?(@_flash)
@_flash = session["flash"] || FlashHash.new
@_flash = Flash::FlashHash.from_session_value(session["flash"])
@_flash.sweep
end
@_flash
end
# Convenience accessor for flash[:alert]
def alert
flash[:alert]
end
# Convenience accessor for flash[:alert]=
def alert=(message)
flash[:alert] = message
@@ -203,7 +248,7 @@ module ActionController #:nodoc:
def notice
flash[:notice]
end
# Convenience accessor for flash[:notice]=
def notice=(message)
flash[:notice] = message

View File

@@ -423,13 +423,13 @@ EOM
# Override Rack's GET method to support indifferent access
def GET
@env["action_controller.request.query_parameters"] ||= normalize_parameters(super)
@env["action_controller.request.query_parameters"] ||= deep_munge(normalize_parameters(super) || {})
end
alias_method :query_parameters, :GET
# Override Rack's POST method to support indifferent access
def POST
@env["action_controller.request.request_parameters"] ||= normalize_parameters(super)
@env["action_controller.request.request_parameters"] ||= deep_munge(normalize_parameters(super) || {})
end
alias_method :request_parameters, :POST
@@ -469,6 +469,22 @@ EOM
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
end
# Remove nils from the params hash
def deep_munge(hash)
hash.each do |k, v|
case v
when Array
v.grep(Hash) { |x| deep_munge(x) }
v.compact!
hash[k] = nil if v.empty?
when Hash
deep_munge(v)
end
end
hash
end
# Convert nested Hashs to HashWithIndifferentAccess and replace
# file upload hashs with UploadedFile objects
def normalize_parameters(value)

View File

@@ -2,7 +2,7 @@ require 'rack/utils'
module ActionController
module Session
class AbstractStore
class AbstractStore
ENV_SESSION_KEY = 'rack.session'.freeze
ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze
@@ -55,17 +55,17 @@ module ActionController
def [](key)
load_for_read!
super
super(key.to_s) || super(key)
end
def has_key?(key)
load_for_read!
super
super(key.to_s) || super(key)
end
def []=(key, value)
load_for_write!
super
super(key.to_s, value)
end
def clear
@@ -87,7 +87,9 @@ module ActionController
def delete(key)
load_for_write!
super
value = super(key)
string_value = super(key.to_s)
string_value || value
end
def data
@@ -119,7 +121,7 @@ module ActionController
end
private
def load_for_read!
load! if !loaded? && exists?
end
@@ -183,7 +185,7 @@ module ActionController
request = ActionController::Request.new(env)
return response if (options[:secure] && !request.ssl?)
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
sid = options[:id] || generate_sid
@@ -205,12 +207,12 @@ module ActionController
end
private
def prepare!(env)
env[ENV_SESSION_KEY] = SessionHash.new(self, env)
env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options)
end
def generate_sid
ActiveSupport::SecureRandom.hex(16)
end
@@ -222,7 +224,7 @@ module ActionController
[sid, session]
end
end
def extract_session_id(env)
stale_session_check! do
request = Rack::Request.new(env)
@@ -235,7 +237,7 @@ module ActionController
def current_session_id(env)
env[ENV_SESSION_OPTIONS_KEY][:id]
end
def exists?(env)
current_session_id(env).present?
end
@@ -247,11 +249,11 @@ module ActionController
def set_session(env, sid, session_data)
raise '#set_session needs to be implemented.'
end
def destroy(env)
raise '#destroy needs to be implemented.'
end
module SessionUtils
private
def stale_session_check!

View File

@@ -37,7 +37,7 @@ module ActionController
# Note that changing digest or secret invalidates all existing sessions!
class CookieStore
include AbstractStore::SessionUtils
# Cookies can typically store 4096 bytes.
MAX = 4096
SECRET_MIN_LENGTH = 30 # characters
@@ -86,7 +86,8 @@ module ActionController
@secret = options.delete(:secret).freeze
@digest = options.delete(:digest) || 'SHA1'
@verifier = verifier_for(@secret, @digest)
@serializer = options.delete(:serializer) || Marshal
@verifier = verifier_for(@secret, @digest, @serializer)
@default_options = DEFAULT_OPTIONS.merge(options).freeze
@@ -95,14 +96,21 @@ module ActionController
def call(env)
prepare!(env)
status, headers, body = @app.call(env)
session_data = env[ENV_SESSION_KEY]
options = env[ENV_SESSION_OPTIONS_KEY]
request = ActionController::Request.new(env)
if !(options[:secure] && !request.ssl?) && (!session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after])
# Backport standard Rack::Session::Cookie behavior
# Skip writing session if env['rack.session.options'][:skip] is set
if options[:skip]
return [status, headers, body]
end
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
persistent_session_id!(session_data)
@@ -122,7 +130,7 @@ module ActionController
end
private
def prepare!(env)
env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env)
env[ENV_SESSION_OPTIONS_KEY] = AbstractStore::OptionsHash.new(self, env, @default_options)
@@ -131,13 +139,13 @@ module ActionController
def load_session(env)
data = unpacked_cookie_data(env)
data = persistent_session_id!(data)
[data[:session_id], data]
[data["session_id"] || data[:session_id], data]
end
def extract_session_id(env)
if data = unpacked_cookie_data(env)
persistent_session_id!(data) unless data.empty?
data[:session_id]
data["session_id"] || data[:session_id]
else
nil
end
@@ -207,9 +215,9 @@ module ActionController
end
end
def verifier_for(secret, digest)
def verifier_for(secret, digest, serializer)
key = secret.respond_to?(:call) ? secret.call : secret
ActiveSupport::MessageVerifier.new(key, digest: digest)
ActiveSupport::MessageVerifier.new(key, digest: digest, serializer: serializer)
end
def generate_sid
@@ -225,12 +233,12 @@ module ActionController
end
def inject_persistent_session_id(data)
requires_session_id?(data) ? { :session_id => generate_sid } : {}
requires_session_id?(data) ? { "session_id" => generate_sid } : {}
end
def requires_session_id?(data)
if data
data.respond_to?(:key?) && !data.key?(:session_id)
data.respond_to?(:key?) && !(data.key?("session_id") || data.key?(:session_id))
else
true
end

View File

@@ -219,7 +219,7 @@ module ActionController #:nodoc:
# A shortcut to the flash. Returns an empty hash if no session flash exists.
def flash
session['flash'] || {}
ActionController::Flash::FlashHash.from_session_value(session["flash"]) || {}
end
# Do we have a flash?

View File

@@ -768,7 +768,11 @@ module ActionView
options = options.stringify_keys
tag_value = options.delete("value")
name_and_id = options.dup
name_and_id["id"] = name_and_id["for"]
if name_and_id.has_key?("for")
name_and_id["id"] = name_and_id["for"]
else
name_and_id.delete("id")
end
add_default_name_and_id_for_value(tag_value, name_and_id)
options.delete("index")
options["for"] ||= name_and_id["id"]
@@ -928,15 +932,15 @@ module ActionView
def add_default_name_and_id(options)
if options.has_key?("index")
options["name"] ||= tag_name_with_index(options["index"])
options["id"] ||= tag_id_with_index(options["index"])
options["name"] = tag_name_with_index(options["index"]) unless options.has_key?("name")
options["id"] = tag_id_with_index(options["index"]) unless options.has_key?("id")
options.delete("index")
elsif defined?(@auto_index)
options["name"] ||= tag_name_with_index(@auto_index)
options["id"] ||= tag_id_with_index(@auto_index)
options["name"] = tag_name_with_index(@auto_index) unless options.has_key?("name")
options["id"] = tag_id_with_index(@auto_index) unless options.has_key?("id")
else
options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
options["id"] ||= tag_id
options["name"] = tag_name + (options.has_key?('multiple') ? '[]' : '') unless options.has_key?("name")
options["id"] = tag_id unless options.has_key?("id")
end
end

View File

@@ -73,6 +73,8 @@ module ActionView
def number_to_currency(number, options = {})
options.symbolize_keys!
options[:format] = ERB::Util.html_escape(options[:format]) if options[:format]
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {}
defaults = defaults.merge(currency)
@@ -85,11 +87,13 @@ module ActionView
separator = '' if precision == 0
begin
format.gsub(/%n/, number_with_precision(number,
value = number_with_precision(number,
:precision => precision,
:delimiter => delimiter,
:separator => separator)
).gsub(/%u/, unit).html_safe
value = ERB::Util.html_escape(value) if value
unit = ERB::Util.html_escape(unit)
format.gsub(/%n/, value).gsub(/%u/, unit).html_safe
rescue
number
end

View File

@@ -17,7 +17,7 @@ module ActionView
src << "@output_buffer.safe_append='"
src << "\n" * @newline_pending if @newline_pending > 0
src << escape_text(text)
src << "';"
src << "'.freeze;"
@newline_pending = 0
end
@@ -63,7 +63,7 @@ module ActionView
def flush_newline_if_pending(src)
if @newline_pending > 0
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';"
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
@newline_pending = 0
end
end

View File

@@ -622,6 +622,19 @@ class FragmentCachingTest < ActionController::TestCase
assert_equal 'generated till now -> fragment content', buffer
end
def test_fragment_for_bytesize
buffer = "\xC4\x8D"
buffer.force_encoding('ASCII-8BIT')
@controller.fragment_for(buffer, 'bytesize') do
buffer.force_encoding('UTF-8')
buffer << "abc"
end
assert_equal Encoding::UTF_8, buffer.encoding
assert_equal "abc", @store.read('views/bytesize')
end
def test_html_safety
assert_nil @store.read('views/name')
content = 'value'.html_safe

View File

@@ -45,7 +45,7 @@ class DispatcherTest < Test::Unit::TestCase
def test_rebuilds_middleware_stack_on_every_request_if_in_loading_mode
dispatcher = create_dispatcher(false)
dispatcher.instance_variable_set(:"@app", lambda { |env| })
dispatcher.expects(:build_middleware_stack).twice
dispatcher.expects(:build_middleware_stack).never
dispatcher.call(nil)
Reloader.default_lock.unlock
dispatcher.call(nil)

View File

@@ -5,7 +5,6 @@ class BaseRackTest < ActiveSupport::TestCase
@env = {
"HTTP_MAX_FORWARDS" => "10",
"SERVER_NAME" => "glu.ttono.us",
"FCGI_ROLE" => "RESPONDER",
"AUTH_TYPE" => "Basic",
"HTTP_X_FORWARDED_HOST" => "glu.ttono.us",
"HTTP_ACCEPT_CHARSET" => "UTF-8",

View File

@@ -175,6 +175,10 @@ class FormHelperTest < ActionView::TestCase
I18n.locale = old_locale
end
def test_label_with_for_attribute_as_nil
assert_dom_equal('<label>Title</label>', label(:post, :title, nil, :for => nil))
end
def test_label_with_for_attribute_as_symbol
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for"))
end
@@ -274,6 +278,11 @@ class FormHelperTest < ActionView::TestCase
hidden_field("post", "title", :value => "Something Else")
end
def test_text_field_with_id_as_nil
assert_dom_equal '<input name="post[title]" type="hidden" value="Hello World" />',
hidden_field("post", "title", :id => nil)
end
def test_check_box
assert_dom_equal(
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',

View File

@@ -3,6 +3,12 @@ require 'abstract_unit'
class NumberHelperTest < ActionView::TestCase
tests ActionView::Helpers::NumberHelper
def test_number_helpers_escape_delimiter_and_separator
assert_equal "$1&lt;script&gt;&lt;/script&gt;01", number_to_currency(1.01, :separator => "<script></script>")
assert_equal "$1&lt;script&gt;&lt;/script&gt;000.00", number_to_currency(1000, :delimiter => "<script></script>")
assert_equal "&lt;script&gt;1,000.00$&lt;/script&gt;", number_to_currency(1000, :format => "<script>%n%u</script>")
end
def test_number_to_phone
assert_equal("555-1234", number_to_phone(5551234))
assert_equal("800-555-1212", number_to_phone(8005551212))
@@ -24,9 +30,10 @@ class NumberHelperTest < ActionView::TestCase
assert_equal("$1,234,567,890.51", number_to_currency(1234567890.506))
assert_equal("$1,234,567,892", number_to_currency(1234567891.50, {:precision => 0}))
assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1}))
assert_equal("&pound;1234567890,50", number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}))
assert_equal("&pound;1234567890,50", number_to_currency(1234567890.50, {:unit => raw("&pound;"), :separator => ",", :delimiter => ""}))
assert_equal("&amp;pound;1234567890,50", number_to_currency(1234567890.50, {:unit => "&pound;", :separator => ",", :delimiter => ""}))
assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50"))
assert_equal("1,234,567,890.50 K&#269;", number_to_currency("1234567890.50", {:unit => "K&#269;", :format => "%n %u"}))
assert_equal("1,234,567,890.50 K&#269;", number_to_currency("1234567890.50", {:unit => raw("K&#269;"), :format => "%n %u"}))
#assert_equal("$x.", number_to_currency("x")) # fails due to API consolidation
assert_equal("$x", number_to_currency("x"))
assert_nil number_to_currency(nil)

View File

@@ -9,8 +9,8 @@ module RenderTestCases
# Reload and register danish language for testing
I18n.reload!
I18n.backend.store_translations 'da', {}
I18n.backend.store_translations 'pt-BR', {}
I18n.backend.store_translations 'da', 'da' => {}
I18n.backend.store_translations 'pt-BR', 'pt-BR' => {}
# Ensure original are still the same since we are reindexing view paths
assert_equal ORIGINAL_LOCALES, I18n.available_locales.map(&:to_s).sort

View File

@@ -195,7 +195,9 @@ module ActiveRecord
def log_info(sql, name, ms)
if @logger && @logger.debug?
name = '%s (%.1fms)' % [name || 'SQL', ms]
sql.force_encoding 'binary' if sql.respond_to?(:force_encoding)
if sql.respond_to?(:force_encoding)
sql = sql.dup.force_encoding 'binary'
end
@logger.debug(format_log_entry(name, sql.squeeze(' ')))
end
end
@@ -212,13 +214,7 @@ module ActiveRecord
log_info(sql, name, 0)
nil
end
rescue SystemExit, SignalException, NoMemoryError => e
# Don't re-wrap these exceptions. They are probably not being caused by invalid
# sql, but rather some external stimulus beyond the responsibilty of this code.
# Additionaly, wrapping these exceptions with StatementInvalid would lead to
# meaningful loss of data, such as losing SystemExit#status.
raise e
rescue Exception => e
rescue => e
# Log message and raise exception.
# Set last_verification to 0, so that connection gets verified
# upon reentering the request loop

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env ruby
Dir.chdir(File.expand_path("..", __FILE__))
$: << File.expand_path("../lib", __FILE__)
require "active_support"
ActiveSupport::TimeZone.all
def flatten_constants(mod, ary = [])
ary << mod
mod.constants.each do |const|
flatten_constants(mod.const_get(const), ary)
end
ary
end
defns = flatten_constants(TZInfo::Definitions).select { |mod|
defined?(mod.get)
}.map { |tz|
tz.get
}
file = "lib/active_support/vendor/tzinfo-0.3.12/tzinfo/definitions.dump"
data = Marshal.dump(defns)
Marshal.load(data)
File.open(file, "wb") do |f|
require "pry"
pry binding
f.write(data)
end
puts "Wrote #{data.size} bytes to #{file}"

View File

@@ -1,5 +1,4 @@
require 'active_support/core_ext/kernel/daemonizing'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/agnostics'
require 'active_support/core_ext/kernel/requires'
require 'active_support/core_ext/kernel/debugger'

View File

@@ -1,11 +0,0 @@
class Object
# Makes backticks behave (somewhat more) similarly on all platforms.
# On win32 `nonexistent_command` raises Errno::ENOENT; on Unix, the
# spawned shell prints a message to stderr and sets $?. We emulate
# Unix on the former but not the latter.
def `(command) #:nodoc:
super
rescue Errno::ENOENT => e
STDERR.puts "#$0: #{e}"
end
end

View File

@@ -1,3 +1,6 @@
require 'active_support/core_ext/kernel/singleton_class'
require 'active_support/core_ext/module/aliasing'
module ActiveSupport
module Memoizable
def self.memoized_ivar_for(symbol)
@@ -41,10 +44,10 @@ module ActiveSupport
end
end
def flush_cache(*syms, &block)
def flush_cache(*syms)
syms.each do |sym|
(methods + private_methods + protected_methods).each do |m|
if m.to_s =~ /^_unmemoized_(#{sym})/
if m.to_s =~ /^_unmemoized_(#{sym.to_s.gsub(/\?\Z/, '\?')})/
ivar = ActiveSupport::Memoizable.memoized_ivar_for($1)
instance_variable_get(ivar).clear if instance_variable_defined?(ivar)
end
@@ -69,7 +72,7 @@ module ActiveSupport
if instance_method(:#{symbol}).arity == 0 # if instance_method(:mime_type).arity == 0
def #{symbol}(reload = false) # def mime_type(reload = false)
if reload || !defined?(#{memoized_ivar}) || #{memoized_ivar}.empty? # if reload || !defined?(@_memoized_mime_type) || @_memoized_mime_type.empty?
#{memoized_ivar} = [#{original_method}.freeze] # @_memoized_mime_type = [_unmemoized_mime_type.freeze]
#{memoized_ivar} = [#{original_method}] # @_memoized_mime_type = [_unmemoized_mime_type]
end # end
#{memoized_ivar}[0] # @_memoized_mime_type[0]
end # end
@@ -82,7 +85,7 @@ module ActiveSupport
if !reload && #{memoized_ivar}.has_key?(args) # if !reload && @_memoized_mime_type.has_key?(args)
#{memoized_ivar}[args] # @_memoized_mime_type[args]
elsif #{memoized_ivar} # elsif @_memoized_mime_type
#{memoized_ivar}[args] = #{original_method}(*args).freeze # @_memoized_mime_type[args] = _unmemoized_mime_type(*args).freeze
#{memoized_ivar}[args] = #{original_method}(*args) # @_memoized_mime_type[args] = _unmemoized_mime_type(*args)
end # end
else # else
#{original_method}(*args) # _unmemoized_mime_type(*args)

View File

@@ -1,11 +1,6 @@
# Prefer gems to the bundled libs.
require 'rubygems'
begin
gem 'builder', '~> 2.1.2'
rescue Gem::LoadError
$:.unshift "#{File.dirname(__FILE__)}/vendor/builder-2.1.2"
end
require 'builder'
begin
@@ -14,17 +9,8 @@ rescue Gem::LoadError
$:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.7.4"
end
begin
gem 'tzinfo', '~> 0.3.12'
rescue Gem::LoadError
$:.unshift "#{File.dirname(__FILE__)}/vendor/tzinfo-0.3.12"
end
$:.unshift "#{File.dirname(__FILE__)}/vendor/tzinfo-0.3.12"
begin
gem 'i18n', '>= 0.4.1'
rescue Gem::LoadError
$:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.4.1"
end
require 'i18n'
module I18n

View File

@@ -1,13 +0,0 @@
#!/usr/bin/env ruby
#--
# Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
# All rights reserved.
# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#++
require 'builder/xmlmarkup'
require 'builder/xmlevents'

View File

@@ -1,248 +0,0 @@
#!/usr/bin/env ruby
#--
# Copyright 2004, 2005 by Jim Weirich (jim@weirichhouse.org).
# Copyright 2005 by Scott Barron (scott@elitists.net).
# All rights reserved.
#
# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#
# Much of this is taken from Jim's work in xmlbase.rb and xmlmarkup.rb.
# Documentation has also been copied and pasted and modified to reflect
# that we're building CSS here instead of XML. Jim is conducting the
# orchestra here and I'm just off in the corner playing a flute.
#++
# Provide a flexible and easy to use Builder for creating Cascading
# Style Sheets (CSS).
module Builder
# Create a Cascading Style Sheet (CSS) using Ruby.
#
# Example usage:
#
# css = Builder::CSS.new
#
# text_color = '#7F7F7F'
# preferred_fonts = 'Helvetica, Arial, sans_serif'
#
# css.comment! 'This is our stylesheet'
# css.body {
# background_color '#FAFAFA'
# font_size 'small'
# font_family preferred_fonts
# color text_color
# }
#
# css.id!('navbar') {
# width '500px'
# }
#
# css.class!('navitem') {
# color 'red'
# }
#
# css.a :hover {
# text_decoration 'underline'
# }
#
# css.div(:id => 'menu') {
# background 'green'
# }
#
# css.div(:class => 'foo') {
# background 'red'
# }
#
# This will yield the following stylesheet:
#
# /* This is our stylesheet */
# body {
# background_color: #FAFAFA;
# font_size: small;
# font_family: Helvetica, Arial, sans_serif;
# color: #7F7F7F;
# }
#
# #navbar {
# width: 500px;
# }
#
# .navitem {
# color: red;
# }
#
# a:hover {
# text_decoration: underline;
# }
#
# div#menu {
# background: green;
# }
#
# div.foo {
# background: red;
# }
#
class CSS < BasicObject
# Create a CSS builder.
#
# out:: Object receiving the markup.1 +out+ must respond to
# <tt><<</tt>.
# indent:: Number of spaces used for indentation (0 implies no
# indentation and no line breaks).
#
def initialize(indent=2)
@indent = indent
@target = []
@parts = []
@library = {}
end
def +(part)
_join_with_op! '+'
self
end
def >>(part)
_join_with_op! ''
self
end
def >(part)
_join_with_op! '>'
self
end
def |(part)
_join_with_op! ','
self
end
# Return the target of the builder
def target!
@target * ''
end
# Create a comment string in the output.
def comment!(comment_text)
@target << "/* #{comment_text} */\n"
end
def id!(arg, &block)
_start_container('#'+arg.to_s, nil, block_given?)
_css_block(block) if block
_unify_block
self
end
def class!(arg, &block)
_start_container('.'+arg.to_s, nil, block_given?)
_css_block(block) if block
_unify_block
self
end
def store!(sym, &block)
@library[sym] = block.to_proc
end
def group!(*args, &block)
args.each do |arg|
if arg.is_a?(::Symbol)
instance_eval(&@library[arg])
else
instance_eval(&arg)
end
_text ', ' unless arg == args.last
end
if block
_css_block(block)
_unify_block
end
end
def method_missing(sym, *args, &block)
sym = "#{sym}:#{args.shift}" if args.first.kind_of?(::Symbol)
if block
_start_container(sym, args.first)
_css_block(block)
_unify_block
elsif @in_block
_indent
_css_line(sym, *args)
_newline
return self
else
_start_container(sym, args.first, false)
_unify_block
end
self
end
# "Cargo culted" from Jim who also "cargo culted" it. See xmlbase.rb.
def nil?
false
end
private
def _unify_block
@target << @parts * ''
@parts = []
end
def _join_with_op!(op)
rhs, lhs = @target.pop, @target.pop
@target << "#{lhs} #{op} #{rhs}"
end
def _text(text)
@parts << text
end
def _css_block(block)
_newline
_nested_structures(block)
_end_container
_end_block
end
def _end_block
_newline
_newline
end
def _newline
_text "\n"
end
def _indent
_text ' ' * @indent
end
def _nested_structures(block)
@in_block = true
self.instance_eval(&block)
@in_block = false
end
def _start_container(sym, atts = {}, with_bracket = true)
selector = sym.to_s
selector << ".#{atts[:class]}" if atts && atts[:class]
selector << '#' + "#{atts[:id]}" if atts && atts[:id]
@parts << "#{selector}#{with_bracket ? ' {' : ''}"
end
def _end_container
@parts << "}"
end
def _css_line(sym, *args)
_text("#{sym.to_s.gsub('_','-')}: #{args * ' '};")
end
end
end

View File

@@ -1,115 +0,0 @@
#!/usr/bin/env ruby
# The XChar library is provided courtesy of Sam Ruby (See
# http://intertwingly.net/stories/2005/09/28/xchar.rb)
# --------------------------------------------------------------------
# If the Builder::XChar module is not currently defined, fail on any
# name clashes in standard library classes.
module Builder
def self.check_for_name_collision(klass, method_name, defined_constant=nil)
if klass.instance_methods.include?(method_name.to_s)
fail RuntimeError,
"Name Collision: Method '#{method_name}' is already defined in #{klass}"
end
end
end
if ! defined?(Builder::XChar)
Builder.check_for_name_collision(String, "to_xs")
Builder.check_for_name_collision(Fixnum, "xchr")
end
######################################################################
module Builder
####################################################################
# XML Character converter, from Sam Ruby:
# (see http://intertwingly.net/stories/2005/09/28/xchar.rb).
#
module XChar # :nodoc:
# See
# http://intertwingly.net/stories/2004/04/14/i18n.html#CleaningWindows
# for details.
CP1252 = { # :nodoc:
128 => 8364, # euro sign
130 => 8218, # single low-9 quotation mark
131 => 402, # latin small letter f with hook
132 => 8222, # double low-9 quotation mark
133 => 8230, # horizontal ellipsis
134 => 8224, # dagger
135 => 8225, # double dagger
136 => 710, # modifier letter circumflex accent
137 => 8240, # per mille sign
138 => 352, # latin capital letter s with caron
139 => 8249, # single left-pointing angle quotation mark
140 => 338, # latin capital ligature oe
142 => 381, # latin capital letter z with caron
145 => 8216, # left single quotation mark
146 => 8217, # right single quotation mark
147 => 8220, # left double quotation mark
148 => 8221, # right double quotation mark
149 => 8226, # bullet
150 => 8211, # en dash
151 => 8212, # em dash
152 => 732, # small tilde
153 => 8482, # trade mark sign
154 => 353, # latin small letter s with caron
155 => 8250, # single right-pointing angle quotation mark
156 => 339, # latin small ligature oe
158 => 382, # latin small letter z with caron
159 => 376, # latin capital letter y with diaeresis
}
# See http://www.w3.org/TR/REC-xml/#dt-chardata for details.
PREDEFINED = {
38 => '&amp;', # ampersand
60 => '&lt;', # left angle bracket
62 => '&gt;', # right angle bracket
}
# See http://www.w3.org/TR/REC-xml/#charsets for details.
VALID = [
0x9, 0xA, 0xD,
(0x20..0xD7FF),
(0xE000..0xFFFD),
(0x10000..0x10FFFF)
]
end
end
######################################################################
# Enhance the Fixnum class with a XML escaped character conversion.
#
class Fixnum
XChar = Builder::XChar if ! defined?(XChar)
# XML escaped version of chr
def xchr
n = XChar::CP1252[self] || self
case n when *XChar::VALID
XChar::PREDEFINED[n] or (n<128 ? n.chr : "&##{n};")
else
'*'
end
end
end
######################################################################
# Enhance the String class with a XML escaped character version of
# to_s.
#
class String
# XML escaped version of to_s
def to_xs
unpack('U*').map {|n| n.xchr}.join # ASCII, UTF-8
rescue
unpack('C*').map {|n| n.xchr}.join # ISO-8859-1, WIN-1252
end
end

View File

@@ -1,137 +0,0 @@
#!/usr/bin/env ruby
module Builder
# Generic error for builder
class IllegalBlockError < RuntimeError; end
# XmlBase is a base class for building XML builders. See
# Builder::XmlMarkup and Builder::XmlEvents for examples.
class XmlBase < BasicObject
# Create an XML markup builder.
#
# out:: Object receiving the markup. +out+ must respond to
# <tt><<</tt>.
# indent:: Number of spaces used for indentation (0 implies no
# indentation and no line breaks).
# initial:: Level of initial indentation.
#
def initialize(indent=0, initial=0)
@indent = indent
@level = initial
end
# Create a tag named +sym+. Other than the first argument which
# is the tag name, the arguments are the same as the tags
# implemented via <tt>method_missing</tt>.
def tag!(sym, *args, &block)
method_missing(sym.to_sym, *args, &block)
end
# Create XML markup based on the name of the method. This method
# is never invoked directly, but is called for each markup method
# in the markup block.
def method_missing(sym, *args, &block)
text = nil
attrs = nil
sym = "#{sym}:#{args.shift}" if args.first.kind_of?(::Symbol)
args.each do |arg|
case arg
when ::Hash
attrs ||= {}
attrs.merge!(arg)
else
text ||= ''
text << arg.to_s
end
end
if block
unless text.nil?
raise ::ArgumentError, "XmlMarkup cannot mix a text argument with a block"
end
_indent
_start_tag(sym, attrs)
_newline
_nested_structures(block)
_indent
_end_tag(sym)
_newline
elsif text.nil?
_indent
_start_tag(sym, attrs, true)
_newline
else
_indent
_start_tag(sym, attrs)
text! text
_end_tag(sym)
_newline
end
@target
end
# Append text to the output target. Escape any markup. May be
# used within the markup brackets as:
#
# builder.p { |b| b.br; b.text! "HI" } #=> <p><br/>HI</p>
def text!(text)
_text(_escape(text))
end
# Append text to the output target without escaping any markup.
# May be used within the markup brackets as:
#
# builder.p { |x| x << "<br/>HI" } #=> <p><br/>HI</p>
#
# This is useful when using non-builder enabled software that
# generates strings. Just insert the string directly into the
# builder without changing the inserted markup.
#
# It is also useful for stacking builder objects. Builders only
# use <tt><<</tt> to append to the target, so by supporting this
# method/operation builders can use other builders as their
# targets.
def <<(text)
_text(text)
end
# For some reason, nil? is sent to the XmlMarkup object. If nil?
# is not defined and method_missing is invoked, some strange kind
# of recursion happens. Since nil? won't ever be an XML tag, it
# is pretty safe to define it here. (Note: this is an example of
# cargo cult programming,
# cf. http://fishbowl.pastiche.org/2004/10/13/cargo_cult_programming).
def nil?
false
end
private
require 'builder/xchar'
def _escape(text)
text.to_xs
end
def _escape_quote(text)
_escape(text).gsub(%r{"}, '&quot;') # " WART
end
def _newline
return if @indent == 0
text! "\n"
end
def _indent
return if @indent == 0 || @level == 0
text!(" " * (@level * @indent))
end
def _nested_structures(block)
@level += 1
block.call(self)
ensure
@level -= 1
end
end
end

View File

@@ -1,63 +0,0 @@
#!/usr/bin/env ruby
#--
# Copyright 2004 by Jim Weirich (jim@weirichhouse.org).
# All rights reserved.
# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#++
require 'builder/xmlmarkup'
module Builder
# Create a series of SAX-like XML events (e.g. start_tag, end_tag)
# from the markup code. XmlEvent objects are used in a way similar
# to XmlMarkup objects, except that a series of events are generated
# and passed to a handler rather than generating character-based
# markup.
#
# Usage:
# xe = Builder::XmlEvents.new(handler)
# xe.title("HI") # Sends start_tag/end_tag/text messages to the handler.
#
# Indentation may also be selected by providing value for the
# indentation size and initial indentation level.
#
# xe = Builder::XmlEvents.new(handler, indent_size, initial_indent_level)
#
# == XML Event Handler
#
# The handler object must expect the following events.
#
# [<tt>start_tag(tag, attrs)</tt>]
# Announces that a new tag has been found. +tag+ is the name of
# the tag and +attrs+ is a hash of attributes for the tag.
#
# [<tt>end_tag(tag)</tt>]
# Announces that an end tag for +tag+ has been found.
#
# [<tt>text(text)</tt>]
# Announces that a string of characters (+text+) has been found.
# A series of characters may be broken up into more than one
# +text+ call, so the client cannot assume that a single
# callback contains all the text data.
#
class XmlEvents < XmlMarkup
def text!(text)
@target.text(text)
end
def _start_tag(sym, attrs, end_too=false)
@target.start_tag(sym, attrs)
_end_tag(sym) if end_too
end
def _end_tag(sym)
@target.end_tag(sym)
end
end
end

View File

@@ -1,328 +0,0 @@
#!/usr/bin/env ruby
#--
# Copyright 2004, 2005 by Jim Weirich (jim@weirichhouse.org).
# All rights reserved.
# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#++
# Provide a flexible and easy to use Builder for creating XML markup.
# See XmlBuilder for usage details.
require 'builder/xmlbase'
module Builder
# Create XML markup easily. All (well, almost all) methods sent to
# an XmlMarkup object will be translated to the equivalent XML
# markup. Any method with a block will be treated as an XML markup
# tag with nested markup in the block.
#
# Examples will demonstrate this easier than words. In the
# following, +xm+ is an +XmlMarkup+ object.
#
# xm.em("emphasized") # => <em>emphasized</em>
# xm.em { xmm.b("emp & bold") } # => <em><b>emph &amp; bold</b></em>
# xm.a("A Link", "href"=>"http://onestepback.org")
# # => <a href="http://onestepback.org">A Link</a>
# xm.div { br } # => <div><br/></div>
# xm.target("name"=>"compile", "option"=>"fast")
# # => <target option="fast" name="compile"\>
# # NOTE: order of attributes is not specified.
#
# xm.instruct! # <?xml version="1.0" encoding="UTF-8"?>
# xm.html { # <html>
# xm.head { # <head>
# xm.title("History") # <title>History</title>
# } # </head>
# xm.body { # <body>
# xm.comment! "HI" # <! -- HI -->
# xm.h1("Header") # <h1>Header</h1>
# xm.p("paragraph") # <p>paragraph</p>
# } # </body>
# } # </html>
#
# == Notes:
#
# * The order that attributes are inserted in markup tags is
# undefined.
#
# * Sometimes you wish to insert text without enclosing tags. Use
# the <tt>text!</tt> method to accomplish this.
#
# Example:
#
# xm.div { # <div>
# xm.text! "line"; xm.br # line<br/>
# xm.text! "another line"; xmbr # another line<br/>
# } # </div>
#
# * The special XML characters <, >, and & are converted to &lt;,
# &gt; and &amp; automatically. Use the <tt><<</tt> operation to
# insert text without modification.
#
# * Sometimes tags use special characters not allowed in ruby
# identifiers. Use the <tt>tag!</tt> method to handle these
# cases.
#
# Example:
#
# xml.tag!("SOAP:Envelope") { ... }
#
# will produce ...
#
# <SOAP:Envelope> ... </SOAP:Envelope>"
#
# <tt>tag!</tt> will also take text and attribute arguments (after
# the tag name) like normal markup methods. (But see the next
# bullet item for a better way to handle XML namespaces).
#
# * Direct support for XML namespaces is now available. If the
# first argument to a tag call is a symbol, it will be joined to
# the tag to produce a namespace:tag combination. It is easier to
# show this than describe it.
#
# xml.SOAP :Envelope do ... end
#
# Just put a space before the colon in a namespace to produce the
# right form for builder (e.g. "<tt>SOAP:Envelope</tt>" =>
# "<tt>xml.SOAP :Envelope</tt>")
#
# * XmlMarkup builds the markup in any object (called a _target_)
# that accepts the <tt><<</tt> method. If no target is given,
# then XmlMarkup defaults to a string target.
#
# Examples:
#
# xm = Builder::XmlMarkup.new
# result = xm.title("yada")
# # result is a string containing the markup.
#
# buffer = ""
# xm = Builder::XmlMarkup.new(buffer)
# # The markup is appended to buffer (using <<)
#
# xm = Builder::XmlMarkup.new(STDOUT)
# # The markup is written to STDOUT (using <<)
#
# xm = Builder::XmlMarkup.new
# x2 = Builder::XmlMarkup.new(:target=>xm)
# # Markup written to +x2+ will be send to +xm+.
#
# * Indentation is enabled by providing the number of spaces to
# indent for each level as a second argument to XmlBuilder.new.
# Initial indentation may be specified using a third parameter.
#
# Example:
#
# xm = Builder.new(:indent=>2)
# # xm will produce nicely formatted and indented XML.
#
# xm = Builder.new(:indent=>2, :margin=>4)
# # xm will produce nicely formatted and indented XML with 2
# # spaces per indent and an over all indentation level of 4.
#
# builder = Builder::XmlMarkup.new(:target=>$stdout, :indent=>2)
# builder.name { |b| b.first("Jim"); b.last("Weirich) }
# # prints:
# # <name>
# # <first>Jim</first>
# # <last>Weirich</last>
# # </name>
#
# * The instance_eval implementation which forces self to refer to
# the message receiver as self is now obsolete. We now use normal
# block calls to execute the markup block. This means that all
# markup methods must now be explicitly send to the xml builder.
# For instance, instead of
#
# xml.div { strong("text") }
#
# you need to write:
#
# xml.div { xml.strong("text") }
#
# Although more verbose, the subtle change in semantics within the
# block was found to be prone to error. To make this change a
# little less cumbersome, the markup block now gets the markup
# object sent as an argument, allowing you to use a shorter alias
# within the block.
#
# For example:
#
# xml_builder = Builder::XmlMarkup.new
# xml_builder.div { |xml|
# xml.stong("text")
# }
#
class XmlMarkup < XmlBase
# Create an XML markup builder. Parameters are specified by an
# option hash.
#
# :target=><em>target_object</em>::
# Object receiving the markup. +out+ must respond to the
# <tt><<</tt> operator. The default is a plain string target.
#
# :indent=><em>indentation</em>::
# Number of spaces used for indentation. The default is no
# indentation and no line breaks.
#
# :margin=><em>initial_indentation_level</em>::
# Amount of initial indentation (specified in levels, not
# spaces).
#
# :escape_attrs=><b>OBSOLETE</em>::
# The :escape_attrs option is no longer supported by builder
# (and will be quietly ignored). String attribute values are
# now automatically escaped. If you need unescaped attribute
# values (perhaps you are using entities in the attribute
# values), then give the value as a Symbol. This allows much
# finer control over escaping attribute values.
#
def initialize(options={})
indent = options[:indent] || 0
margin = options[:margin] || 0
super(indent, margin)
@target = options[:target] || ""
end
# Return the target of the builder.
def target!
@target
end
def comment!(comment_text)
_ensure_no_block ::Kernel.block_given?
_special("<!-- ", " -->", comment_text, nil)
end
# Insert an XML declaration into the XML markup.
#
# For example:
#
# xml.declare! :ELEMENT, :blah, "yada"
# # => <!ELEMENT blah "yada">
def declare!(inst, *args, &block)
_indent
@target << "<!#{inst}"
args.each do |arg|
case arg
when ::String
@target << %{ "#{arg}"} # " WART
when ::Symbol
@target << " #{arg}"
end
end
if ::Kernel.block_given?
@target << " ["
_newline
_nested_structures(block)
@target << "]"
end
@target << ">"
_newline
end
# Insert a processing instruction into the XML markup. E.g.
#
# For example:
#
# xml.instruct!
# #=> <?xml version="1.0" encoding="UTF-8"?>
# xml.instruct! :aaa, :bbb=>"ccc"
# #=> <?aaa bbb="ccc"?>
#
def instruct!(directive_tag=:xml, attrs={})
_ensure_no_block ::Kernel.block_given?
if directive_tag == :xml
a = { :version=>"1.0", :encoding=>"UTF-8" }
attrs = a.merge attrs
end
_special(
"<?#{directive_tag}",
"?>",
nil,
attrs,
[:version, :encoding, :standalone])
end
# Insert a CDATA section into the XML markup.
#
# For example:
#
# xml.cdata!("text to be included in cdata")
# #=> <![CDATA[text to be included in cdata]]>
#
def cdata!(text)
_ensure_no_block ::Kernel.block_given?
_special("<![CDATA[", "]]>", text, nil)
end
private
# NOTE: All private methods of a builder object are prefixed when
# a "_" character to avoid possible conflict with XML tag names.
# Insert text directly in to the builder's target.
def _text(text)
@target << text
end
# Insert special instruction.
def _special(open, close, data=nil, attrs=nil, order=[])
_indent
@target << open
@target << data if data
_insert_attributes(attrs, order) if attrs
@target << close
_newline
end
# Start an XML tag. If <tt>end_too</tt> is true, then the start
# tag is also the end tag (e.g. <br/>
def _start_tag(sym, attrs, end_too=false)
@target << "<#{sym}"
_insert_attributes(attrs)
@target << "/" if end_too
@target << ">"
end
# Insert an ending tag.
def _end_tag(sym)
@target << "</#{sym}>"
end
# Insert the attributes (given in the hash).
def _insert_attributes(attrs, order=[])
return if attrs.nil?
order.each do |k|
v = attrs[k]
@target << %{ #{k}="#{_attr_value(v)}"} if v # " WART
end
attrs.each do |k, v|
@target << %{ #{k}="#{_attr_value(v)}"} unless order.member?(k) # " WART
end
end
def _attr_value(value)
case value
when ::Symbol
value.to_s
else
_escape_quote(value.to_s)
end
end
def _ensure_no_block(got_block)
if got_block
fail IllegalBlockError,
"Blocks are not allowed on XML instructions"
end
end
end
end

View File

@@ -1,322 +0,0 @@
# encoding: utf-8
# Authors:: Sven Fuchs (http://www.artweb-design.de),
# Joshua Harvey (http://www.workingwithrails.com/person/759-joshua-harvey),
# Stephan Soller (http://www.arkanis-development.de/),
# Saimon Moore (http://saimonmoore.net),
# Matt Aimonetti (http://railsontherun.com/)
# Copyright:: Copyright (c) 2008 The Ruby i18n Team
# License:: MIT
require 'i18n/exceptions'
require 'i18n/core_ext/string/interpolate'
module I18n
autoload :Backend, 'i18n/backend'
autoload :Config, 'i18n/config'
autoload :Gettext, 'i18n/gettext'
autoload :Locale, 'i18n/locale'
class << self
# Gets I18n configuration object.
def config
Thread.current[:i18n_config] ||= I18n::Config.new
end
# Sets I18n configuration object.
def config=(value)
Thread.current[:i18n_config] = value
end
# Write methods which delegates to the configuration object
%w(locale backend default_locale available_locales default_separator
exception_handler load_path).each do |method|
module_eval <<-DELEGATORS, __FILE__, __LINE__ + 1
def #{method}
config.#{method}
end
def #{method}=(value)
config.#{method} = (value)
end
DELEGATORS
end
# Tells the backend to reload translations. Used in situations like the
# Rails development environment. Backends can implement whatever strategy
# is useful.
def reload!
config.backend.reload!
end
# Translates, pluralizes and interpolates a given key using a given locale,
# scope, and default, as well as interpolation values.
#
# *LOOKUP*
#
# Translation data is organized as a nested hash using the upper-level keys
# as namespaces. <em>E.g.</em>, ActionView ships with the translation:
# <tt>:date => {:formats => {:short => "%b %d"}}</tt>.
#
# Translations can be looked up at any level of this hash using the key argument
# and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt>
# returns the whole translations hash <tt>{:formats => {:short => "%b %d"}}</tt>.
#
# Key can be either a single key or a dot-separated key (both Strings and Symbols
# work). <em>E.g.</em>, the short format can be looked up using both:
# I18n.t 'date.formats.short'
# I18n.t :'date.formats.short'
#
# Scope can be either a single key, a dot-separated key or an array of keys
# or dot-separated keys. Keys and scopes can be combined freely. So these
# examples will all look up the same short date format:
# I18n.t 'date.formats.short'
# I18n.t 'formats.short', :scope => 'date'
# I18n.t 'short', :scope => 'date.formats'
# I18n.t 'short', :scope => %w(date formats)
#
# *INTERPOLATION*
#
# Translations can contain interpolation variables which will be replaced by
# values passed to #translate as part of the options hash, with the keys matching
# the interpolation variable names.
#
# <em>E.g.</em>, with a translation <tt>:foo => "foo %{bar}"</tt> the option
# value for the key +bar+ will be interpolated into the translation:
# I18n.t :foo, :bar => 'baz' # => 'foo baz'
#
# *PLURALIZATION*
#
# Translation data can contain pluralized translations. Pluralized translations
# are arrays of singluar/plural versions of translations like <tt>['Foo', 'Foos']</tt>.
#
# Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English
# pluralization rules. Other algorithms can be supported by custom backends.
#
# This returns the singular version of a pluralized translation:
# I18n.t :foo, :count => 1 # => 'Foo'
#
# These both return the plural version of a pluralized translation:
# I18n.t :foo, :count => 0 # => 'Foos'
# I18n.t :foo, :count => 2 # => 'Foos'
#
# The <tt>:count</tt> option can be used both for pluralization and interpolation.
# <em>E.g.</em>, with the translation
# <tt>:foo => ['%{count} foo', '%{count} foos']</tt>, count will
# be interpolated to the pluralized translation:
# I18n.t :foo, :count => 1 # => '1 foo'
#
# *DEFAULTS*
#
# This returns the translation for <tt>:foo</tt> or <tt>default</tt> if no translation was found:
# I18n.t :foo, :default => 'default'
#
# This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no
# translation for <tt>:foo</tt> was found:
# I18n.t :foo, :default => :bar
#
# Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt>
# or <tt>default</tt> if no translations for <tt>:foo</tt> and <tt>:bar</tt> were found.
# I18n.t :foo, :default => [:bar, 'default']
#
# *BULK LOOKUP*
#
# This returns an array with the translations for <tt>:foo</tt> and <tt>:bar</tt>.
# I18n.t [:foo, :bar]
#
# Can be used with dot-separated nested keys:
# I18n.t [:'baz.foo', :'baz.bar']
#
# Which is the same as using a scope option:
# I18n.t [:foo, :bar], :scope => :baz
#
# *LAMBDAS*
#
# Both translations and defaults can be given as Ruby lambdas. Lambdas will be
# called and passed the key and options.
#
# E.g. assuming the key <tt>:salutation</tt> resolves to:
# lambda { |key, options| options[:gender] == 'm' ? "Mr. %{options[:name]}" : "Mrs. %{options[:name]}" }
#
# Then <tt>I18n.t(:salutation, :gender => 'w', :name => 'Smith') will result in "Mrs. Smith".
#
# It is recommended to use/implement lambdas in an "idempotent" way. E.g. when
# a cache layer is put in front of I18n.translate it will generate a cache key
# from the argument values passed to #translate. Therefor your lambdas should
# always return the same translations/values per unique combination of argument
# values.
def translate(*args)
options = args.pop if args.last.is_a?(Hash)
key = args.shift
locale = options && options.delete(:locale) || config.locale
raises = options && options.delete(:raise)
config.backend.translate(locale, key, options || {})
rescue I18n::ArgumentError => exception
raise exception if raises
handle_exception(exception, locale, key, options)
end
alias :t :translate
def translate!(key, options = {})
translate(key, options.merge( :raise => true ))
end
alias :t! :translate!
# Transliterates UTF-8 characters to ASCII. By default this method will
# transliterate only Latin strings to an ASCII approximation:
#
# I18n.transliterate("Ærøskøbing")
# # => "AEroskobing"
#
# I18n.transliterate("日本語")
# # => "???"
#
# It's also possible to add support for per-locale transliterations. I18n
# expects transliteration rules to be stored at
# <tt>i18n.transliterate.rule</tt>.
#
# Transliteration rules can either be a Hash or a Proc. Procs must accept a
# single string argument. Hash rules inherit the default transliteration
# rules, while Procs do not.
#
# *Examples*
#
# Setting a Hash in <locale>.yml:
#
# i18n:
# transliterate:
# rule:
# ü: "ue"
# ö: "oe"
#
# Setting a Hash using Ruby:
#
# store_translations(:de, :i18n => {
# :transliterate => {
# :rule => {
# "ü" => "ue",
# "ö" => "oe"
# }
# }
# )
#
# Setting a Proc:
#
# translit = lambda {|string| MyTransliterator.transliterate(string) }
# store_translations(:xx, :i18n => {:transliterate => {:rule => translit})
#
# Transliterating strings:
#
# I18n.locale = :en
# I18n.transliterate("Jürgen") # => "Jurgen"
# I18n.locale = :de
# I18n.transliterate("Jürgen") # => "Juergen"
# I18n.transliterate("Jürgen", :locale => :en) # => "Jurgen"
# I18n.transliterate("Jürgen", :locale => :de) # => "Juergen"
def transliterate(*args)
options = args.pop if args.last.is_a?(Hash)
key = args.shift
locale = options && options.delete(:locale) || config.locale
raises = options && options.delete(:raise)
replacement = options && options.delete(:replacement)
config.backend.transliterate(locale, key, replacement)
rescue I18n::ArgumentError => exception
raise exception if raises
handle_exception(exception, locale, key, options)
end
# Localizes certain objects, such as dates and numbers to local formatting.
def localize(object, options = {})
locale = options.delete(:locale) || config.locale
format = options.delete(:format) || :default
config.backend.localize(locale, object, format, options)
end
alias :l :localize
# Executes block with given I18n.locale set.
def with_locale(tmp_locale = nil)
if tmp_locale
current_locale = self.locale
self.locale = tmp_locale
end
yield
ensure
self.locale = current_locale if tmp_locale
end
# Merges the given locale, key and scope into a single array of keys.
# Splits keys that contain dots into multiple keys. Makes sure all
# keys are Symbols.
def normalize_keys(locale, key, scope, separator = nil)
separator ||= I18n.default_separator
keys = []
keys.concat normalize_key(locale, separator)
keys.concat normalize_key(scope, separator)
keys.concat normalize_key(key, separator)
keys
end
# making these private until Ruby 1.9.2 can send to protected methods again
# see http://redmine.ruby-lang.org/repositories/revision/ruby-19?rev=24280
private
# Handles exceptions raised in the backend. All exceptions except for
# MissingTranslationData exceptions are re-raised. When a MissingTranslationData
# was caught and the option :raise is not set the handler returns an error
# message string containing the key/scope.
def default_exception_handler(exception, locale, key, options)
return exception.message if MissingTranslationData === exception
raise exception
end
# Any exceptions thrown in translate will be sent to the @@exception_handler
# which can be a Symbol, a Proc or any other Object.
#
# If exception_handler is a Symbol then it will simply be sent to I18n as
# a method call. A Proc will simply be called. In any other case the
# method #call will be called on the exception_handler object.
#
# Examples:
#
# I18n.exception_handler = :default_exception_handler # this is the default
# I18n.default_exception_handler(exception, locale, key, options) # will be called like this
#
# I18n.exception_handler = lambda { |*args| ... } # a lambda
# I18n.exception_handler.call(exception, locale, key, options) # will be called like this
#
# I18n.exception_handler = I18nExceptionHandler.new # an object
# I18n.exception_handler.call(exception, locale, key, options) # will be called like this
def handle_exception(exception, locale, key, options)
case config.exception_handler
when Symbol
send(config.exception_handler, exception, locale, key, options)
else
config.exception_handler.call(exception, locale, key, options)
end
end
# Deprecated. Will raise a warning in future versions and then finally be
# removed. Use I18n.normalize_keys instead.
def normalize_translation_keys(locale, key, scope, separator = nil)
normalize_keys(locale, key, scope, separator)
end
def normalize_key(key, separator)
normalized_key_cache[separator][key] ||=
case key
when Array
key.map { |k| normalize_key(k, separator) }.flatten
else
keys = key.to_s.split(separator)
keys.delete('')
keys.map!{ |k| k.to_sym }
keys
end
end
def normalized_key_cache
@normalized_key_cache ||= Hash.new { |h,k| h[k] = {} }
end
end
end

View File

@@ -1,20 +0,0 @@
module I18n
module Backend
autoload :ActiveRecord, 'i18n/backend/active_record'
autoload :Base, 'i18n/backend/base'
autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
autoload :Cache, 'i18n/backend/cache'
autoload :Cascade, 'i18n/backend/cascade'
autoload :Chain, 'i18n/backend/chain'
autoload :Cldr, 'i18n/backend/cldr'
autoload :Fallbacks, 'i18n/backend/fallbacks'
autoload :Flatten, 'i18n/backend/flatten'
autoload :Gettext, 'i18n/backend/gettext'
autoload :KeyValue, 'i18n/backend/key_value'
autoload :Memoize, 'i18n/backend/memoize'
autoload :Metadata, 'i18n/backend/metadata'
autoload :Pluralization, 'i18n/backend/pluralization'
autoload :Simple, 'i18n/backend/simple'
autoload :Transliterator, 'i18n/backend/transliterator'
end
end

View File

@@ -1,61 +0,0 @@
require 'i18n/backend/base'
require 'i18n/backend/active_record/translation'
module I18n
module Backend
class ActiveRecord
autoload :Missing, 'i18n/backend/active_record/missing'
autoload :StoreProcs, 'i18n/backend/active_record/store_procs'
autoload :Translation, 'i18n/backend/active_record/translation'
module Implementation
include Base, Flatten
def available_locales
begin
Translation.available_locales
rescue ::ActiveRecord::StatementInvalid
[]
end
end
def store_translations(locale, data, options = {})
escape = options.fetch(:escape, true)
flatten_translations(locale, data, escape, false).each do |key, value|
Translation.locale(locale).lookup(expand_keys(key)).delete_all
Translation.create(:locale => locale.to_s, :key => key.to_s, :value => value)
end
end
protected
def lookup(locale, key, scope = [], options = {})
key = normalize_flat_keys(locale, key, scope, options[:separator])
result = Translation.locale(locale).lookup(key).all
if result.empty?
nil
elsif result.first.key == key
result.first.value
else
chop_range = (key.size + FLATTEN_SEPARATOR.size)..-1
result = result.inject({}) do |hash, r|
hash[r.key.slice(chop_range)] = r.value
hash
end
result.deep_symbolize_keys
end
end
# For a key :'foo.bar.baz' return ['foo', 'foo.bar', 'foo.bar.baz']
def expand_keys(key)
key.to_s.split(FLATTEN_SEPARATOR).inject([]) do |keys, key|
keys << [keys.last, key].compact.join(FLATTEN_SEPARATOR)
end
end
end
include Implementation
end
end
end

View File

@@ -1,65 +0,0 @@
# This extension stores translation stub records for missing translations to
# the database.
#
# This is useful if you have a web based translation tool. It will populate
# the database with untranslated keys as the application is being used. A
# translator can then go through these and add missing translations.
#
# Example usage:
#
# I18n::Backend::Chain.send(:include, I18n::Backend::ActiveRecord::Missing)
# I18n.backend = I18nChainBackend.new(I18n::Backend::ActiveRecord.new, I18n::Backend::Simple.new)
#
# Stub records for pluralizations will also be created for each key defined
# in i18n.plural.keys.
#
# For example:
#
# # en.yml
# en:
# i18n:
# plural:
# keys: [:zero, :one, :other]
#
# # pl.yml
# pl:
# i18n:
# plural:
# keys: [:zero, :one, :few, :other]
#
# It will also persist interpolation keys in Translation#interpolations so
# translators will be able to review and use them.
module I18n
module Backend
class ActiveRecord
module Missing
def store_default_translations(locale, key, options = {})
count, scope, default, separator = options.values_at(:count, *Base::RESERVED_KEYS)
separator ||= I18n.default_separator
keys = I18n.normalize_keys(locale, key, scope, separator)[1..-1]
key = keys.join(separator || I18n.default_separator)
unless ActiveRecord::Translation.locale(locale).lookup(key).exists?
interpolations = options.reject { |name, value| Base::RESERVED_KEYS.include?(name) }.keys
keys = count ? I18n.t('i18n.plural.keys', :locale => locale).map { |k| [key, k].join(separator) } : [key]
keys.each { |key| store_default_translation(locale, key, interpolations) }
end
end
def store_default_translation(locale, key, interpolations)
translation = ActiveRecord::Translation.new :locale => locale.to_s, :key => key
translation.interpolations = interpolations
translation.save
end
def translate(locale, key, options = {})
super
rescue I18n::MissingTranslationData => e
self.store_default_translations(locale, key, options)
raise e
end
end
end
end
end

View File

@@ -1,38 +0,0 @@
# This module is intended to be mixed into the ActiveRecord backend to allow
# storing Ruby Procs as translation values in the database.
#
# I18n.backend = I18n::Backend::ActiveRecord.new
# I18n::Backend::ActiveRecord::Translation.send(:include, I18n::Backend::ActiveRecord::StoreProcs)
#
# The StoreProcs module requires the ParseTree and ruby2ruby gems and therefor
# was extracted from the original backend.
#
# ParseTree is not compatible with Ruby 1.9.
begin
require 'ruby2ruby'
require 'parse_tree'
require 'parse_tree_extensions'
rescue LoadError => e
puts "can't use StoreProcs because: #{e.message}"
end
module I18n
module Backend
class ActiveRecord
module StoreProcs
def value=(v)
case v
when Proc
write_attribute(:value, v.to_ruby)
write_attribute(:is_proc, true)
else
write_attribute(:value, v)
end
end
Translation.send(:include, self) if method(:to_s).respond_to?(:to_ruby)
end
end
end
end

View File

@@ -1,93 +0,0 @@
require 'active_record'
module I18n
module Backend
# ActiveRecord model used to store actual translations to the database.
#
# This model expects a table like the following to be already set up in
# your the database:
#
# create_table :translations do |t|
# t.string :locale
# t.string :key
# t.text :value
# t.text :interpolations
# t.boolean :is_proc, :default => false
# end
#
# This model supports to named scopes :locale and :lookup. The :locale
# scope simply adds a condition for a given locale:
#
# I18n::Backend::ActiveRecord::Translation.locale(:en).all
# # => all translation records that belong to the :en locale
#
# The :lookup scope adds a condition for looking up all translations
# that either start with the given keys (joined by an optionally given
# separator or I18n.default_separator) or that exactly have this key.
#
# # with translations present for :"foo.bar" and :"foo.baz"
# I18n::Backend::ActiveRecord::Translation.lookup(:foo)
# # => an array with both translation records :"foo.bar" and :"foo.baz"
#
# I18n::Backend::ActiveRecord::Translation.lookup([:foo, :bar])
# I18n::Backend::ActiveRecord::Translation.lookup(:"foo.bar")
# # => an array with the translation record :"foo.bar"
#
# When the StoreProcs module was mixed into this model then Procs will
# be stored to the database as Ruby code and evaluated when :value is
# called.
#
# Translation = I18n::Backend::ActiveRecord::Translation
# Translation.create \
# :locale => 'en'
# :key => 'foo'
# :value => lambda { |key, options| 'FOO' }
# Translation.find_by_locale_and_key('en', 'foo').value
# # => 'FOO'
class ActiveRecord
class Translation < ::ActiveRecord::Base
set_table_name 'translations'
attr_protected :is_proc, :interpolations
serialize :value
serialize :interpolations, Array
scope_method = ::ActiveRecord::VERSION::MAJOR == 2 ? :named_scope : :scope
send scope_method, :locale, lambda { |locale|
{ :conditions => { :locale => locale.to_s } }
}
send scope_method, :lookup, lambda { |keys, *separator|
column_name = connection.quote_column_name('key')
keys = Array(keys).map! { |key| key.to_s }
unless separator.empty?
warn "[DEPRECATION] Giving a separator to Translation.lookup is deprecated. " <<
"You can change the internal separator by overwriting FLATTEN_SEPARATOR."
end
namespace = "#{keys.last}#{I18n::Backend::Flatten::FLATTEN_SEPARATOR}%"
{ :conditions => ["#{column_name} IN (?) OR #{column_name} LIKE ?", keys, namespace] }
}
def self.available_locales
Translation.find(:all, :select => 'DISTINCT locale').map { |t| t.locale.to_sym }
end
def interpolates?(key)
self.interpolations.include?(key) if self.interpolations
end
def value
if is_proc
Kernel.eval(read_attribute(:value))
else
value = read_attribute(:value)
value == 'f' ? false : value
end
end
end
end
end
end

View File

@@ -1,237 +0,0 @@
# encoding: utf-8
require 'yaml'
require 'i18n/core_ext/hash'
module I18n
module Backend
module Base
include I18n::Backend::Transliterator
RESERVED_KEYS = [:scope, :default, :separator, :resolve]
RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
DEPRECATED_INTERPOLATION_SYNTAX_PATTERN = /(\\)?\{\{([^\}]+)\}\}/
INTERPOLATION_SYNTAX_PATTERN = /%\{([^\}]+)\}/
# 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 = I18n.load_path.flatten if filenames.empty?
filenames.each { |filename| load_file(filename) }
end
# This method receives a locale, a data hash and options for storing translations.
# Should be implemented
def store_translations(locale, data, options = {})
raise NotImplementedError
end
def translate(locale, key, options = {})
raise InvalidLocale.new(locale) unless locale
return key.map { |k| translate(locale, k, options) } if key.is_a?(Array)
entry = key && lookup(locale, key, options[:scope], options)
if options.empty?
entry = resolve(locale, key, entry, options)
else
count, default = options.values_at(:count, :default)
values = options.except(*RESERVED_KEYS)
entry = entry.nil? && default ?
default(locale, key, default, options) : resolve(locale, key, entry, options)
end
raise(I18n::MissingTranslationData.new(locale, key, options)) if entry.nil?
entry = entry.dup if entry.is_a?(String)
entry = pluralize(locale, entry, count) if count
entry = interpolate(locale, entry, values) if values
entry
end
# Acts the same as +strftime+, but uses a localized version of the
# format 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, options = {})
raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
if Symbol === format
key = format
type = object.respond_to?(:sec) ? 'time' : 'date'
format = I18n.t(:"#{type}.formats.#{key}", options.merge(:raise => true, :object => object, :locale => locale))
end
# format = resolve(locale, object, format, options)
format = format.to_s.gsub(/%[aAbBp]/) do |match|
case match
when '%a' then I18n.t(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
when '%A' then I18n.t(:"date.day_names", :locale => locale, :format => format)[object.wday]
when '%b' then I18n.t(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
when '%B' then I18n.t(:"date.month_names", :locale => locale, :format => format)[object.mon]
when '%p' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format) if object.respond_to? :hour
end
end
object.strftime(format)
end
# Returns an array of locales for which translations are available
# ignoring the reserved translation meta data key :i18n.
def available_locales
raise NotImplementedError
end
def reload!
@skip_syntax_deprecation = false
end
protected
# The method which actually looks up for the translation in the store.
def lookup(locale, key, scope = [], options = {})
raise NotImplementedError
end
# Evaluates defaults.
# If given subject is an Array, it walks the array and returns the
# first translation that can be resolved. Otherwise it tries to resolve
# the translation directly.
def default(locale, object, subject, options = {})
options = options.dup.reject { |key, value| key == :default }
case subject
when Array
subject.each do |item|
result = resolve(locale, object, item, options) and return result
end and nil
else
resolve(locale, object, subject, options)
end
end
# Resolves a translation.
# If the given subject is a Symbol, it will be translated with the
# given options. If it is a Proc then it will be evaluated. All other
# subjects will be returned directly.
def resolve(locale, object, subject, options = nil)
return subject if options[:resolve] == false
case subject
when Symbol
I18n.translate(subject, (options || {}).merge(:locale => locale, :raise => true))
when Proc
date_or_time = options.delete(:object) || object
resolve(locale, object, subject.call(date_or_time, options), options = {})
else
subject
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) && count
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) && !values.empty?
original_values = values.dup
preserve_encoding(string) do
string = string.gsub(DEPRECATED_INTERPOLATION_SYNTAX_PATTERN) do
escaped, key = $1, $2.to_sym
if escaped
"{{#{key}}}"
else
warn_syntax_deprecation!
"%{#{key}}"
end
end
keys = string.scan(INTERPOLATION_SYNTAX_PATTERN).flatten
return string if keys.empty?
values.each do |key, value|
if keys.include?(key.to_s)
value = value.call(values) if interpolate_lambda?(value, string, key)
value = value.to_s unless value.is_a?(::String)
values[key] = value
else
values.delete(key)
end
end
string % values
end
rescue KeyError => e
if string =~ RESERVED_KEYS_PATTERN
raise ReservedInterpolationKey.new($1.to_sym, string)
else
raise MissingInterpolationArgument.new(original_values, string)
end
end
def preserve_encoding(string)
if string.respond_to?(:encoding)
encoding = string.encoding
result = yield
result.force_encoding(encoding) if result.respond_to?(:force_encoding)
result
else
yield
end
end
# returns true when the given value responds to :call and the key is
# an interpolation placeholder in the given string
def interpolate_lambda?(object, string, key)
object.respond_to?(:call) && string =~ /%\{#{key}\}|%\<#{key}>.*?\d*\.?\d*[bBdiouxXeEfgGcps]\}/
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}", true)
data = send(:"load_#{type}", filename) # TODO raise a meaningful exception if this does not yield a Hash
data.each { |locale, d| store_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
def warn_syntax_deprecation! #:nodoc:
return if @skip_syntax_deprecation
warn "The {{key}} interpolation syntax in I18n messages is deprecated. Please use %{key} instead.\n#{caller.join("\n")}"
@skip_syntax_deprecation = true
end
end
end
end

View File

@@ -1,77 +0,0 @@
# encoding: utf-8
# This module allows you to easily cache all responses from the backend - thus
# speeding up the I18n aspects of your application quite a bit.
#
# To enable caching you can simply include the Cache module to the Simple
# backend - or whatever other backend you are using:
#
# I18n::Backend::Simple.send(:include, I18n::Backend::Cache)
#
# You will also need to set a cache store implementation that you want to use:
#
# I18n.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
#
# You can use any cache implementation you want that provides the same API as
# ActiveSupport::Cache (only the methods #fetch and #write are being used).
#
# The cache_key implementation assumes that you only pass values to
# I18n.translate that return a valid key from #hash (see
# http://www.ruby-doc.org/core/classes/Object.html#M000337).
module I18n
class << self
@@cache_store = nil
@@cache_namespace = nil
def cache_store
@@cache_store
end
def cache_store=(store)
@@cache_store = store
end
def cache_namespace
@@cache_namespace
end
def cache_namespace=(namespace)
@@cache_namespace = namespace
end
def perform_caching?
!cache_store.nil?
end
end
module Backend
# TODO Should the cache be cleared if new translations are stored?
module Cache
def translate(*args)
I18n.perform_caching? ? fetch(*args) { super } : super
end
protected
def fetch(*args, &block)
result = I18n.cache_store.fetch(cache_key(*args), &block)
raise result if result.is_a?(Exception)
result = result.dup if result.frozen? rescue result
result
rescue MissingTranslationData => exception
I18n.cache_store.write(cache_key(*args), exception)
raise exception
end
def cache_key(*args)
# This assumes that only simple, native Ruby values are passed to I18n.translate.
# Also, in Ruby < 1.8.7 {}.hash != {}.hash
# (see http://paulbarry.com/articles/2009/09/14/why-rails-3-will-require-ruby-1-8-7)
# If args.inspect does not work for you for some reason, patches are very welcome :)
hash = RUBY_VERSION >= "1.8.7" ? args.hash : args.inspect
keys = ['i18n', I18n.cache_namespace, hash]
keys.compact.join('-')
end
end
end
end

View File

@@ -1,57 +0,0 @@
# encoding: utf-8
# EXPERIMENTAL
#
# The Cascade module adds the ability to do cascading lookups to backends that
# are compatible to the Simple backend.
#
# By cascading lookups we mean that for any key that can not be found the
# Cascade module strips one segment off the scope part of the key and then
# tries to look up the key in that scope.
#
# E.g. when a lookup for the key :"foo.bar.baz" does not yield a result then
# the segment :bar will be stripped off the scope part :"foo.bar" and the new
# scope :foo will be used to look up the key :baz. If that does not succeed
# then the remaining scope segment :foo will be omitted, too, and again the
# key :baz will be looked up (now with no scope).
#
# To enable a cascading lookup one passes the :cascade option:
#
# I18n.t(:'foo.bar.baz', :cascade => true)
#
# This will return the first translation found for :"foo.bar.baz", :"foo.baz"
# or :baz in this order.
#
# The cascading lookup takes precedence over resolving any given defaults.
# I.e. defaults will kick in after the cascading lookups haven't succeeded.
#
# This behavior is useful for libraries like ActiveRecord validations where
# the library wants to give users a bunch of more or less fine-grained options
# of scopes for a particular key.
#
# Thanks to Clemens Kofler for the initial idea and implementation! See
# http://github.com/clemens/i18n-cascading-backend
module I18n
module Backend
module Cascade
def lookup(locale, key, scope = [], options = {})
return super unless cascade = options[:cascade]
separator = options[:separator] || I18n.default_separator
skip_root = cascade.has_key?(:skip_root) ? cascade[:skip_root] : true
step = cascade[:step]
keys = I18n.normalize_keys(nil, key, nil, separator)
offset = options[:cascade][:offset] || keys.length
scope = I18n.normalize_keys(nil, nil, scope, separator) + keys
key = scope.slice!(-offset, offset).join(separator)
begin
result = super
return result unless result.nil?
end while !scope.empty? && scope.slice!(-step, step) && (!scope.empty? || !skip_root)
end
end
end
end

View File

@@ -1,77 +0,0 @@
# encoding: utf-8
module I18n
module Backend
# Backend that chains multiple other backends and checks each of them when
# a translation needs to be looked up. This is useful when you want to use
# standard translations with a Simple backend but store custom application
# translations in a database or other backends.
#
# To use the Chain backend instantiate it and set it to the I18n module.
# You can add chained backends through the initializer or backends
# accessor:
#
# # preserves the existing Simple backend set to I18n.backend
# I18n.backend = I18n::Backend::Chain.new(I18n::Backend::ActiveRecord.new, I18n.backend)
#
# The implementation assumes that all backends added to the Chain implement
# a lookup method with the same API as Simple backend does.
class Chain
include Base
attr_accessor :backends
def initialize(*backends)
self.backends = backends
end
def reload!
backends.each { |backend| backend.reload! }
end
def store_translations(locale, data, options = {})
backends.first.store_translations(locale, data, options = {})
end
def available_locales
backends.map { |backend| backend.available_locales }.flatten.uniq
end
def translate(locale, key, options = {})
return key.map { |k| translate(locale, k, options) } if key.is_a?(Array)
default = options.delete(:default)
namespace = {}
backends.each do |backend|
begin
options.update(:default => default) if default and backend == backends.last
translation = backend.translate(locale, key, options)
if namespace_lookup?(translation, options)
namespace.update(translation)
elsif !translation.nil?
return translation
end
rescue MissingTranslationData
end
end
return namespace unless namespace.empty?
raise(I18n::MissingTranslationData.new(locale, key, options))
end
def localize(locale, object, format = :default, options = {})
backends.each do |backend|
begin
result = backend.localize(locale, object, format, options) and return result
rescue MissingTranslationData
end
end
raise(I18n::MissingTranslationData.new(locale, format, options))
end
protected
def namespace_lookup?(result, options)
result.is_a?(Hash) and not options.has_key?(:count)
end
end
end
end

View File

@@ -1,100 +0,0 @@
# encoding: utf-8
require 'cldr'
module I18n
module Backend
module Cldr
include ::Cldr::Format
def localize(locale, object, format = :default, options = {})
options[:as] ||= detect_type(object, options)
send(:"format_#{options[:as]}", locale, object, format, options)
end
def format_decimal(locale, object, format = :default, options = {})
formatter(locale, :decimal, format).apply(object, options)
end
def format_integer(locale, object, format = :default, options = {})
format_object(number, options.merge(:precision => 0))
end
def format_currency(locale, object, format = :default, options = {})
options.merge!(:currency => lookup_currency(locale, options[:currency], object)) if options[:currency].is_a?(Symbol)
formatter(locale, :currency, format).apply(object, options)
end
def format_percent(locale, object, format = :default, options = {})
formatter(locale, :percent, format).apply(object, options)
end
def format_date(locale, object, format = :default, options = {})
formatter(locale, :date, format).apply(object, options)
end
def format_time(locale, object, format = :default, options = {})
formatter(locale, :time, format).apply(object, options)
end
def format_datetime(locale, object, format = :default, options = {})
key = :"calendars.gregorian.formats.datetime.#{format}.pattern"
date = I18n.l(object, :format => options[:date_format] || format, :locale => locale, :as => :date)
time = I18n.l(object, :format => options[:time_format] || format, :locale => locale, :as => :time)
I18n.t(key, :date => date, :time => time, :locale => locale, :raise => true)
end
protected
def detect_type(object, options)
options.has_key?(:currency) ? :currency : case object
when ::Numeric
:decimal
when ::Date, ::DateTime, ::Time
object.class.name.downcase.to_sym
else
raise_unspecified_format_type!
end
end
def formatter(locale, type, format)
(@formatters ||= {})[:"#{locale}.#{type}.#{format}"] ||= begin
format = lookup_format(locale, type, format)
data = lookup_format_data(locale, type)
::Cldr::Format.const_get(type.to_s.camelize).new(format, data)
end
end
def lookup_format(locale, type, format)
key = case type
when :date, :time, :datetime
:"calendars.gregorian.formats.#{type}.#{format}.pattern"
else
:"numbers.formats.#{type}.patterns.#{format || :default}"
end
I18n.t(key, :locale => locale, :raise => true)
end
def lookup_format_data(locale, type)
key = case type
when :date, :time, :datetime
:'calendars.gregorian'
else
:'numbers.symbols'
end
I18n.t(key, :locale => locale, :raise => true)
end
def lookup_currency(locale, currency, count)
I18n.t(:"currencies.#{currency}", :locale => locale, :count => count)
end
def raise_unspecified_format_type!
raise ArgumentError.new("You have to specify a format type, e.g. :as => :number.")
end
def raise_unspecified_currency!
raise ArgumentError.new("You have to specify a currency, e.g. :currency => 'EUR'.")
end
end
end
end

View File

@@ -1,69 +0,0 @@
# encoding: utf-8
# I18n locale fallbacks are useful when you want your application to use
# translations from other locales when translations for the current locale are
# missing. E.g. you might want to use :en translations when translations in
# your applications main locale :de are missing.
#
# To enable locale fallbacks you can simply include the Fallbacks module to
# the Simple backend - or whatever other backend you are using:
#
# I18n::Backend::Simple.send(:include, I18n::Backend::Fallbacks)
module I18n
@@fallbacks = nil
class << self
# Returns the current fallbacks implementation. Defaults to +I18n::Locale::Fallbacks+.
def fallbacks
@@fallbacks ||= I18n::Locale::Fallbacks.new
end
# Sets the current fallbacks implementation. Use this to set a different fallbacks implementation.
def fallbacks=(fallbacks)
@@fallbacks = fallbacks
end
end
module Backend
module Fallbacks
# Overwrites the Base backend translate method so that it will try each
# locale given by I18n.fallbacks for the given locale. E.g. for the
# locale :"de-DE" it might try the locales :"de-DE", :de and :en
# (depends on the fallbacks implementation) until it finds a result with
# the given options. If it does not find any result for any of the
# locales it will then raise a MissingTranslationData exception as
# usual.
#
# The default option takes precedence over fallback locales
# only when it's not a String. When default contains String it
# is evaluated after fallback locales.
def translate(locale, key, options = {})
default = extract_string_default!(options) if options[:default]
I18n.fallbacks[locale].each do |fallback|
begin
result = super(fallback, key, options)
return result unless result.nil?
rescue I18n::MissingTranslationData
end
end
return super(locale, nil, options.merge(:default => default)) if default
raise(I18n::MissingTranslationData.new(locale, key, options))
end
def extract_string_default!(options)
defaults = Array(options[:default])
if index = find_first_string_default(defaults)
options[:default] = defaults[0, index]
defaults[index]
end
end
def find_first_string_default(defaults)
defaults.each_index { |ix| return ix if String === defaults[ix] }
nil
end
end
end
end

View File

@@ -1,113 +0,0 @@
module I18n
module Backend
# This module contains several helpers to assist flattening translations.
# You may want to flatten translations for:
#
# 1) speed up lookups, as in the Memoize backend;
# 2) In case you want to store translations in a data store, as in ActiveRecord backend;
#
# You can check both backends above for some examples.
# This module also keeps all links in a hash so they can be properly resolved when flattened.
module Flatten
SEPARATOR_ESCAPE_CHAR = "\001"
FLATTEN_SEPARATOR = "."
# normalize_keys the flatten way. This method is significantly faster
# and creates way less objects than the one at I18n.normalize_keys.
# It also handles escaping the translation keys.
def self.normalize_flat_keys(locale, key, scope, separator)
keys = [scope, key].flatten.compact
separator ||= I18n.default_separator
if separator != FLATTEN_SEPARATOR
keys.map! do |k|
k.to_s.tr("#{FLATTEN_SEPARATOR}#{separator}",
"#{SEPARATOR_ESCAPE_CHAR}#{FLATTEN_SEPARATOR}")
end
end
keys.join(".")
end
# Receives a string and escape the default separator.
def self.escape_default_separator(key) #:nodoc:
key.to_s.tr(FLATTEN_SEPARATOR, SEPARATOR_ESCAPE_CHAR)
end
# Shortcut to I18n::Backend::Flatten.normalize_flat_keys
# and then resolve_links.
def normalize_flat_keys(locale, key, scope, separator)
key = I18n::Backend::Flatten.normalize_flat_keys(locale, key, scope, separator)
resolve_link(locale, key)
end
# Store flattened links.
def links
@links ||= Hash.new { |h,k| h[k] = {} }
end
# Flatten keys for nested Hashes by chaining up keys:
#
# >> { "a" => { "b" => { "c" => "d", "e" => "f" }, "g" => "h" }, "i" => "j"}.wind
# => { "a.b.c" => "d", "a.b.e" => "f", "a.g" => "h", "i" => "j" }
#
def flatten_keys(hash, escape, prev_key=nil, &block)
hash.each_pair do |key, value|
key = escape_default_separator(key) if escape
curr_key = [prev_key, key].compact.join(FLATTEN_SEPARATOR).to_sym
yield curr_key, value
flatten_keys(value, escape, curr_key, &block) if value.is_a?(Hash)
end
end
# Receives a hash of translations (where the key is a locale and
# the value is another hash) and return a hash with all
# translations flattened.
#
# Nested hashes are included in the flattened hash just if subtree
# is true and Symbols are automatically stored as links.
def flatten_translations(locale, data, escape, subtree)
hash = {}
flatten_keys(data, escape) do |key, value|
if value.is_a?(Hash)
hash[key] = value if subtree
else
store_link(locale, key, value) if value.is_a?(Symbol)
hash[key] = value
end
end
hash
end
protected
def store_link(locale, key, link)
links[locale.to_sym][key.to_s] = link.to_s
end
def resolve_link(locale, key)
key, locale = key.to_s, locale.to_sym
links = self.links[locale]
if links.key?(key)
links[key]
elsif link = find_link(locale, key)
store_link(locale, key, key.gsub(*link))
else
key
end
end
def find_link(locale, key) #:nodoc:
links[locale].each do |from, to|
return [from, to] if key[0, from.length] == from
end && nil
end
def escape_default_separator(key) #:nodoc:
I18n::Backend::Flatten.escape_default_separator(key)
end
end
end
end

View File

@@ -1,75 +0,0 @@
# encoding: utf-8
require 'i18n/gettext'
require 'i18n/gettext/po_parser'
# Experimental support for using Gettext po files to store translations.
#
# To use this you can simply include the module to the Simple backend - or
# whatever other backend you are using.
#
# I18n::Backend::Simple.send(:include, I18n::Backend::Gettext)
#
# Now you should be able to include your Gettext translation (*.po) files to
# the I18n.load_path so they're loaded to the backend and you can use them as
# usual:
#
# I18n.load_path += Dir["path/to/locales/*.po"]
#
# Following the Gettext convention this implementation expects that your
# translation files are named by their locales. E.g. the file en.po would
# contain the translations for the English locale.
module I18n
module Backend
module Gettext
class PoData < Hash
def set_comment(msgid_or_sym, comment)
# ignore
end
end
protected
def load_po(filename)
locale = ::File.basename(filename, '.po').to_sym
data = normalize(locale, parse(filename))
{ locale => data }
end
def parse(filename)
GetText::PoParser.new.parse(::File.read(filename), PoData.new)
end
def normalize(locale, data)
data.inject({}) do |result, (key, value)|
unless key.nil? || key.empty?
key, value = normalize_pluralization(locale, key, value) if key.index("\000")
parts = key.split('|').reverse
normalized = parts.inject({}) do |normalized, part|
normalized = { part => normalized.empty? ? value : normalized }
end
# 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 }
result.merge!(normalized, &merger)
end
result
end
end
def normalize_pluralization(locale, key, value)
# FIXME po_parser includes \000 chars that can not be turned into Symbols
key = key.gsub("\000", I18n::Gettext::PLURAL_SEPARATOR).split(I18n::Gettext::PLURAL_SEPARATOR).first
keys = I18n::Gettext.plural_keys(locale)
values = value.split("\000")
raise "invalid number of plurals: #{values.size}, keys: #{keys.inspect}" if values.size != keys.size
result = {}
values.each_with_index { |value, ix| result[keys[ix]] = value }
[key, result]
end
end
end
end

View File

@@ -1,123 +0,0 @@
# encoding: utf-8
# The InterpolationCompiler module contains optimizations that can tremendously
# speed up the interpolation process on the Simple backend.
#
# It works by defining a pre-compiled method on stored translation Strings that
# already bring all the knowledge about contained interpolation variables etc.
# so that the actual recurring interpolation will be very fast.
#
# To enable pre-compiled interpolations you can simply include the
# InterpolationCompiler module to the Simple backend:
#
# I18n::Backend::Simple.send(:include, I18n::Backend::InterpolationCompiler)
#
# Note that InterpolationCompiler does not yield meaningful results and consequently
# should not be used with Ruby 1.9 (YARV) but improves performance everywhere else
# (jRuby, Rubinius and 1.8.7).
module I18n
module Backend
module InterpolationCompiler
module Compiler
extend self
TOKENIZER = /(%%\{[^\}]+\}|%\{[^\}]+\})/
INTERPOLATION_SYNTAX_PATTERN = /(%)?(%\{([^\}]+)\})/
def compile_if_an_interpolation(string)
if interpolated_str?(string)
string.instance_eval <<-RUBY_EVAL, __FILE__, __LINE__
def i18n_interpolate(v = {})
"#{compiled_interpolation_body(string)}"
end
RUBY_EVAL
end
string
end
def interpolated_str?(str)
str.kind_of?(::String) && str =~ INTERPOLATION_SYNTAX_PATTERN
end
protected
# tokenize("foo %{bar} baz %%{buz}") # => ["foo ", "%{bar}", " baz ", "%%{buz}"]
def tokenize(str)
str.split(TOKENIZER)
end
def compiled_interpolation_body(str)
tokenize(str).map do |token|
(matchdata = token.match(INTERPOLATION_SYNTAX_PATTERN)) ? handle_interpolation_token(token, matchdata) : escape_plain_str(token)
end.join
end
def handle_interpolation_token(interpolation, matchdata)
escaped, pattern, key = matchdata.values_at(1, 2, 3)
escaped ? pattern : compile_interpolation_token(key.to_sym)
end
def compile_interpolation_token(key)
"\#{#{interpolate_or_raise_missing(key)}}"
end
def interpolate_or_raise_missing(key)
escaped_key = escape_key_sym(key)
Base::RESERVED_KEYS.include?(key) ? reserved_key(escaped_key) : interpolate_key(escaped_key)
end
def interpolate_key(key)
[direct_key(key), nil_key(key), missing_key(key)].join('||')
end
def direct_key(key)
"((t = v[#{key}]) && t.respond_to?(:call) ? t.call : t)"
end
def nil_key(key)
"(v.has_key?(#{key}) && '')"
end
def missing_key(key)
"raise(MissingInterpolationArgument.new(#{key}, self))"
end
def reserved_key(key)
"raise(ReservedInterpolationKey.new(#{key}, self))"
end
def escape_plain_str(str)
str.gsub(/"|\\|#/) {|x| "\\#{x}"}
end
def escape_key_sym(key)
# rely on Ruby to do all the hard work :)
key.to_sym.inspect
end
end
def interpolate(locale, string, values)
if string.respond_to?(:i18n_interpolate)
string.i18n_interpolate(values)
elsif values
super
else
string
end
end
def store_translations(locale, data, options = {})
compile_all_strings_in(data)
super
end
protected
def compile_all_strings_in(data)
data.each_value do |value|
Compiler.compile_if_an_interpolation(value)
compile_all_strings_in(value) if value.kind_of?(Hash)
end
end
end
end
end

View File

@@ -1,102 +0,0 @@
# encoding: utf-8
require 'i18n/backend/base'
require 'active_support/json'
module I18n
module Backend
# This is a basic backend for key value stores. It receives on
# initialization the store, which should respond to three methods:
#
# * store#[](key) - Used to get a value
# * store#[]=(key, value) - Used to set a value
# * store#keys - Used to get all keys
#
# Since these stores only supports string, all values are converted
# to JSON before being stored, allowing it to also store booleans,
# hashes and arrays. However, this store does not support Procs.
#
# As the ActiveRecord backend, Symbols are just supported when loading
# translations from the filesystem or through explicit store translations.
#
# Also, avoid calling I18n.available_locales since it's a somehow
# expensive operation in most stores.
#
# == Example
#
# To setup I18n to use TokyoCabinet in memory is quite straightforward:
#
# require 'rufus/tokyo/cabinet' # gem install rufus-tokyo
# I18n.backend = I18n::Backend::KeyValue.new(Rufus::Tokyo::Cabinet.new('*'))
#
# == Performance
#
# You may make this backend even faster by including the Memoize module.
# However, notice that you should properly clear the cache if you change
# values directly in the key-store.
#
# == Subtrees
#
# In most backends, you are allowed to retrieve part of a translation tree:
#
# I18n.backend.store_translations :en, :foo => { :bar => :baz }
# I18n.t "foo" #=> { :bar => :baz }
#
# This backend supports this feature by default, but it slows down the storage
# of new data considerably and makes hard to delete entries. That said, you are
# allowed to disable the storage of subtrees on initialization:
#
# I18n::Backend::KeyValue.new(@store, false)
#
# This is useful if you are using a KeyValue backend chained to a Simple backend.
class KeyValue
module Implementation
attr_accessor :store
include Base, Flatten
def initialize(store, subtrees=true)
@store, @subtrees = store, subtrees
end
def store_translations(locale, data, options = {})
escape = options.fetch(:escape, true)
flatten_translations(locale, data, escape, @subtrees).each do |key, value|
key = "#{locale}.#{key}"
case value
when Hash
if @subtrees && (old_value = @store[key])
old_value = ActiveSupport::JSON.decode(old_value)
value = old_value.deep_symbolize_keys.deep_merge!(value) if old_value.is_a?(Hash)
end
when Proc
raise "Key-value stores cannot handle procs"
end
@store[key] = ActiveSupport::JSON.encode(value) unless value.is_a?(Symbol)
end
end
def available_locales
locales = @store.keys.map { |k| k =~ /\./; $` }
locales.uniq!
locales.compact!
locales.map! { |k| k.to_sym }
locales
end
protected
def lookup(locale, key, scope = [], options = {})
key = normalize_flat_keys(locale, key, scope, options[:separator])
value = @store["#{locale}.#{key}"]
value = ActiveSupport::JSON.decode(value) if value
value.is_a?(Hash) ? value.deep_symbolize_keys : value
end
end
include Implementation
end
end
end

View File

@@ -1,48 +0,0 @@
# encoding: utf-8
#
# Memoize module simply memoizes the values returned by lookup using
# a flat hash and can tremendously speed up the lookup process in a backend.
#
# To enable it you can simply include the Memoize module to your backend:
#
# I18n::Backend::Simple.send(:include, I18n::Backend::Memoize)
#
# Notice that it's the responsibility of the backend to define whenever the
# cache should be cleaned.
module I18n
module Backend
module Memoize
def available_locales
@memoized_locales ||= super
end
def store_translations(locale, data, options = {})
reset_memoizations!(locale)
super
end
def reload!
reset_memoizations!
super
end
protected
def lookup(locale, key, scope = nil, options = {})
flat_key = I18n::Backend::Flatten.normalize_flat_keys(locale,
key, scope, options[:separator]).to_sym
flat_hash = memoized_lookup[locale.to_sym]
flat_hash.key?(flat_key) ? flat_hash[flat_key] : (flat_hash[flat_key] = super)
end
def memoized_lookup
@memoized_lookup ||= Hash.new { |h, k| h[k] = {} }
end
def reset_memoizations!(locale=nil)
@memoized_locales = nil
(locale ? memoized_lookup[locale.to_sym] : memoized_lookup).clear
end
end
end
end

View File

@@ -1,65 +0,0 @@
# I18n translation metadata is useful when you want to access information
# about how a translation was looked up, pluralized or interpolated in
# your application.
#
# msg = I18n.t(:message, :default => 'Hi!', :scope => :foo)
# msg.translation_metadata
# # => { :key => :message, :scope => :foo, :default => 'Hi!' }
#
# If a :count option was passed to #translate it will be set to the metadata.
# Likewise, if any interpolation variables were passed they will also be set.
#
# To enable translation metadata you can simply include the Metadata module
# into the Simple backend class - or whatever other backend you are using:
#
# I18n::Backend::Simple.send(:include, I18n::Backend::Metadata)
#
module I18n
module Backend
module Metadata
class << self
def included(base)
Object.class_eval do
def translation_metadata
@translation_metadata ||= {}
end
def translation_metadata=(translation_metadata)
@translation_metadata = translation_metadata
end
end unless Object.method_defined?(:translation_metadata)
end
end
def translate(locale, key, options = {})
metadata = {
:locale => locale,
:key => key,
:scope => options[:scope],
:default => options[:default],
:separator => options[:separator],
:values => options.reject { |name, value| Base::RESERVED_KEYS.include?(name) }
}
with_metadata(metadata) { super }
end
def interpolate(locale, entry, values = {})
metadata = entry.translation_metadata.merge(:original => entry)
with_metadata(metadata) { super }
end
def pluralize(locale, entry, count)
with_metadata(:count => count) { super }
end
protected
def with_metadata(metadata, &block)
result = yield
result.translation_metadata = result.translation_metadata.merge(metadata) if result
result
end
end
end
end

View File

@@ -1,57 +0,0 @@
# encoding: utf-8
# I18n locale fallbacks are useful when you want your application to use
# translations from other locales when translations for the current locale are
# missing. E.g. you might want to use :en translations when translations in
# your applications main locale :de are missing.
#
# To enable locale specific pluralizations you can simply include the
# Pluralization module to the Simple backend - or whatever other backend you
# are using.
#
# I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
#
# You also need to make sure to provide pluralization algorithms to the
# backend, i.e. include them to your I18n.load_path accordingly.
module I18n
module Backend
module Pluralization
# Overwrites the Base backend translate method so that it will check the
# translation meta data space (:i18n) for a locale specific pluralization
# rule and use it to pluralize the given entry. I.e. the library expects
# pluralization rules to be stored at I18n.t(:'i18n.plural.rule')
#
# Pluralization rules are expected to respond to #call(entry, count) and
# return a pluralization key. Valid keys depend on the translation data
# hash (entry) but it is generally recommended to follow CLDR's style,
# i.e., return one of the keys :zero, :one, :few, :many, :other.
#
# The :zero key is always picked directly when count equals 0 AND the
# translation data has the key :zero. This way translators are free to
# either pick a special :zero translation even for languages where the
# pluralizer does not return a :zero key.
def pluralize(locale, entry, count)
return entry unless entry.is_a?(Hash) and count
pluralizer = pluralizer(locale)
if pluralizer.respond_to?(:call)
key = count == 0 && entry.has_key?(:zero) ? :zero : pluralizer.call(count)
raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
entry[key]
else
super
end
end
protected
def pluralizers
@pluralizers ||= {}
end
def pluralizer(locale)
pluralizers[locale] ||= I18n.t(:'i18n.plural.rule', :locale => locale, :resolve => false)
end
end
end
end

View File

@@ -1,87 +0,0 @@
# encoding: utf-8
module I18n
module Backend
# A simple backend that reads translations from YAML files and stores them in
# an in-memory hash. Relies on the Base backend.
#
# The implementation is provided by a Implementation module allowing to easily
# extend Simple backend's behavior by including modules. E.g.:
#
# module I18n::Backend::Pluralization
# def pluralize(*args)
# # extended pluralization logic
# super
# end
# end
#
# I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
class Simple
module Implementation
include Base
def initialized?
@initialized ||= false
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, options = {})
locale = locale.to_sym
translations[locale] ||= {}
data = data.deep_symbolize_keys
translations[locale].deep_merge!(data)
end
# Get available locales from the translations hash
def available_locales
init_translations unless initialized?
translations.inject([]) do |locales, (locale, data)|
locales << locale unless (data.keys - [:i18n]).empty?
locales
end
end
# Clean up translations hash and set initialized to false on reload!
def reload!
@initialized = false
@translations = nil
super
end
protected
def init_translations
load_translations
@initialized = true
end
def translations
@translations ||= {}
end
# Looks up a translation from the translations hash. Returns nil if
# eiher key is nil, or locale, scope or key do not exist as a key in the
# nested translations hash. Splits keys or scopes containing dots
# into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
# <tt>%w(currency format)</tt>.
def lookup(locale, key, scope = [], options = {})
init_translations unless initialized?
keys = I18n.normalize_keys(locale, key, scope, options[:separator])
keys.inject(translations) do |result, key|
key = key.to_sym
return nil unless result.is_a?(Hash) && result.has_key?(key)
result = result[key]
result = resolve(locale, key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
result
end
end
end
include Implementation
end
end
end

View File

@@ -1,98 +0,0 @@
# encoding: utf-8
module I18n
module Backend
module Transliterator
DEFAULT_REPLACEMENT_CHAR = "?"
# Given a locale and a UTF-8 string, return the locale's ASCII
# approximation for the string.
def transliterate(locale, string, replacement = nil)
@transliterators ||= {}
@transliterators[locale] ||= Transliterator.get I18n.t(:'i18n.transliterate.rule',
:locale => locale, :resolve => false, :default => {})
@transliterators[locale].transliterate(string, replacement)
end
# Get a transliterator instance.
def self.get(rule = nil)
if !rule || rule.kind_of?(Hash)
HashTransliterator.new(rule)
elsif rule.kind_of? Proc
ProcTransliterator.new(rule)
else
raise I18n::ArgumentError, "Transliteration rule must be a proc or a hash."
end
end
# A transliterator which accepts a Proc as its transliteration rule.
class ProcTransliterator
def initialize(rule)
@rule = rule
end
def transliterate(string, replacement = nil)
@rule.call(string)
end
end
# A transliterator which accepts a Hash of characters as its translation
# rule.
class HashTransliterator
DEFAULT_APPROXIMATIONS = {
"À"=>"A", "Á"=>"A", "Â"=>"A", "Ã"=>"A", "Ä"=>"A", "Å"=>"A", "Æ"=>"AE",
"Ç"=>"C", "È"=>"E", "É"=>"E", "Ê"=>"E", "Ë"=>"E", "Ì"=>"I", "Í"=>"I",
"Î"=>"I", "Ï"=>"I", "Ð"=>"D", "Ñ"=>"N", "Ò"=>"O", "Ó"=>"O", "Ô"=>"O",
"Õ"=>"O", "Ö"=>"O", "×"=>"x", "Ø"=>"O", "Ù"=>"U", "Ú"=>"U", "Û"=>"U",
"Ü"=>"U", "Ý"=>"Y", "Þ"=>"Th", "ß"=>"ss", "à"=>"a", "á"=>"a", "â"=>"a",
"ã"=>"a", "ä"=>"a", "å"=>"a", "æ"=>"ae", "ç"=>"c", "è"=>"e", "é"=>"e",
"ê"=>"e", "ë"=>"e", "ì"=>"i", "í"=>"i", "î"=>"i", "ï"=>"i", "ð"=>"d",
"ñ"=>"n", "ò"=>"o", "ó"=>"o", "ô"=>"o", "õ"=>"o", "ö"=>"o", "ø"=>"o",
"ù"=>"u", "ú"=>"u", "û"=>"u", "ü"=>"u", "ý"=>"y", "þ"=>"th", "ÿ"=>"y",
"Ā"=>"A", "ā"=>"a", "Ă"=>"A", "ă"=>"a", "Ą"=>"A", "ą"=>"a", "Ć"=>"C",
"ć"=>"c", "Ĉ"=>"C", "ĉ"=>"c", "Ċ"=>"C", "ċ"=>"c", "Č"=>"C", "č"=>"c",
"Ď"=>"D", "ď"=>"d", "Đ"=>"D", "đ"=>"d", "Ē"=>"E", "ē"=>"e", "Ĕ"=>"E",
"ĕ"=>"e", "Ė"=>"E", "ė"=>"e", "Ę"=>"E", "ę"=>"e", "Ě"=>"E", "ě"=>"e",
"Ĝ"=>"G", "ĝ"=>"g", "Ğ"=>"G", "ğ"=>"g", "Ġ"=>"G", "ġ"=>"g", "Ģ"=>"G",
"ģ"=>"g", "Ĥ"=>"H", "ĥ"=>"h", "Ħ"=>"H", "ħ"=>"h", "Ĩ"=>"I", "ĩ"=>"i",
"Ī"=>"I", "ī"=>"i", "Ĭ"=>"I", "ĭ"=>"i", "Į"=>"I", "į"=>"i", "İ"=>"I",
"ı"=>"i", "IJ"=>"IJ", "ij"=>"ij", "Ĵ"=>"J", "ĵ"=>"j", "Ķ"=>"K", "ķ"=>"k",
"ĸ"=>"k", "Ĺ"=>"L", "ĺ"=>"l", "Ļ"=>"L", "ļ"=>"l", "Ľ"=>"L", "ľ"=>"l",
"Ŀ"=>"L", "ŀ"=>"l", "Ł"=>"L", "ł"=>"l", "Ń"=>"N", "ń"=>"n", "Ņ"=>"N",
"ņ"=>"n", "Ň"=>"N", "ň"=>"n", "ʼn"=>"'n", "Ŋ"=>"NG", "ŋ"=>"ng",
"Ō"=>"O", "ō"=>"o", "Ŏ"=>"O", "ŏ"=>"o", "Ő"=>"O", "ő"=>"o", "Œ"=>"OE",
"œ"=>"oe", "Ŕ"=>"R", "ŕ"=>"r", "Ŗ"=>"R", "ŗ"=>"r", "Ř"=>"R", "ř"=>"r",
"Ś"=>"S", "ś"=>"s", "Ŝ"=>"S", "ŝ"=>"s", "Ş"=>"S", "ş"=>"s", "Š"=>"S",
"š"=>"s", "Ţ"=>"T", "ţ"=>"t", "Ť"=>"T", "ť"=>"t", "Ŧ"=>"T", "ŧ"=>"t",
"Ũ"=>"U", "ũ"=>"u", "Ū"=>"U", "ū"=>"u", "Ŭ"=>"U", "ŭ"=>"u", "Ů"=>"U",
"ů"=>"u", "Ű"=>"U", "ű"=>"u", "Ų"=>"U", "ų"=>"u", "Ŵ"=>"W", "ŵ"=>"w",
"Ŷ"=>"Y", "ŷ"=>"y", "Ÿ"=>"Y", "Ź"=>"Z", "ź"=>"z", "Ż"=>"Z", "ż"=>"z",
"Ž"=>"Z", "ž"=>"z"
}
def initialize(rule = nil)
@rule = rule
add DEFAULT_APPROXIMATIONS
add rule if rule
end
def transliterate(string, replacement = nil)
string.gsub(/[^\x00-\x7f]/u) do |char|
approximations[char] || replacement || DEFAULT_REPLACEMENT_CHAR
end
end
private
def approximations
@approximations ||= {}
end
# Add transliteration rules to the approximations hash.
def add(hash)
hash.keys.each {|key| hash[key.to_s] = hash.delete(key).to_s}
approximations.merge! hash
end
end
end
end
end

View File

@@ -1,84 +0,0 @@
module I18n
class Config
# The only configuration value that is not global and scoped to thread is :locale.
# It defaults to the default_locale.
def locale
@locale ||= default_locale
end
# Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
def locale=(locale)
@locale = locale.to_sym rescue nil
end
# Returns the current backend. Defaults to +Backend::Simple+.
def backend
@@backend ||= Backend::Simple.new
end
# Sets the current backend. Used to set a custom backend.
def backend=(backend)
@@backend = backend
end
# Returns the current default locale. Defaults to :'en'
def default_locale
@@default_locale ||= :en
end
# Sets the current default locale. Used to set a custom default locale.
def default_locale=(locale)
@@default_locale = locale.to_sym rescue nil
end
# Returns an array of locales for which translations are available.
# Unless you explicitely set the these through I18n.available_locales=
# the call will be delegated to the backend and memoized on the I18n module.
def available_locales
@@available_locales ||= backend.available_locales
end
# Sets the available locales.
def available_locales=(locales)
@@available_locales = locales
end
# Returns the current default scope separator. Defaults to '.'
def default_separator
@@default_separator ||= '.'
end
# Sets the current default scope separator.
def default_separator=(separator)
@@default_separator = separator
end
# Return the current exception handler. Defaults to :default_exception_handler.
def exception_handler
@@exception_handler ||= :default_exception_handler
end
# Sets the exception handler.
def exception_handler=(exception_handler)
@@exception_handler = exception_handler
end
# Allow clients to register paths providing translation data sources. The
# backend defines acceptable sources.
#
# E.g. the provided SimpleBackend accepts a list of paths to translation
# files which are either named *.rb and contain plain Ruby Hashes or are
# named *.yml and contain YAML data. So for the SimpleBackend clients may
# register translation files like this:
# I18n.load_path << 'path/to/locale/en.yml'
def load_path
@@load_path ||= []
end
# Sets the load path instance. Custom implementations are expected to
# behave like a Ruby Array.
def load_path=(load_path)
@@load_path = load_path
end
end
end

View File

@@ -1,29 +0,0 @@
class Hash
def slice(*keep_keys)
h = {}
keep_keys.each { |key| h[key] = fetch(key) }
h
end unless Hash.method_defined?(:slice)
def except(*less_keys)
slice(*keys - less_keys)
end unless Hash.method_defined?(:except)
def deep_symbolize_keys
inject({}) { |result, (key, value)|
value = value.deep_symbolize_keys if value.is_a?(Hash)
result[(key.to_sym rescue key) || key] = value
result
}
end unless Hash.method_defined?(:deep_symbolize_keys)
# deep_merge_hash! by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
MERGER = proc do |key, v1, v2|
Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2
end
def deep_merge!(data)
merge!(data, &MERGER)
end unless Hash.method_defined?(:deep_merge!)
end

View File

@@ -1,98 +0,0 @@
# encoding: utf-8
=begin
heavily based on Masao Mutoh's gettext String interpolation extension
http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb
Copyright (C) 2005-2009 Masao Mutoh
You may redistribute it and/or modify it under the same license terms as Ruby.
=end
begin
raise ArgumentError if ("a %{x}" % {:x=>'b'}) != 'a b'
rescue ArgumentError
# KeyError is raised by String#% when the string contains a named placeholder
# that is not contained in the given arguments hash. Ruby 1.9 includes and
# raises this exception natively. We define it to mimic Ruby 1.9's behaviour
# in Ruby 1.8.x
class KeyError < IndexError
def initialize(message = nil)
super(message || "key not found")
end
end unless defined?(KeyError)
# Extension for String class. This feature is included in Ruby 1.9 or later but not occur TypeError.
#
# String#% method which accept "named argument". The translator can know
# the meaning of the msgids using "named argument" instead of %s/%d style.
class String
# For older ruby versions, such as ruby-1.8.5
alias :bytesize :size unless instance_methods.find {|m| m.to_s == 'bytesize'}
alias :interpolate_without_ruby_19_syntax :% # :nodoc:
INTERPOLATION_PATTERN = Regexp.union(
/%\{(\w+)\}/, # matches placeholders like "%{foo}"
/%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
)
INTERPOLATION_PATTERN_WITH_ESCAPE = Regexp.union(
/%%/,
INTERPOLATION_PATTERN
)
# % uses self (i.e. the String) as a format specification and returns the
# result of applying it to the given arguments. In other words it interpolates
# the given arguments to the string according to the formats the string
# defines.
#
# There are three ways to use it:
#
# * Using a single argument or Array of arguments.
#
# This is the default behaviour of the String class. See Kernel#sprintf for
# more details about the format string.
#
# Example:
#
# "%d %s" % [1, "message"]
# # => "1 message"
#
# * Using a Hash as an argument and unformatted, named placeholders.
#
# When you pass a Hash as an argument and specify placeholders with %{foo}
# it will interpret the hash values as named arguments.
#
# Example:
#
# "%{firstname}, %{lastname}" % {:firstname => "Masao", :lastname => "Mutoh"}
# # => "Masao Mutoh"
#
# * Using a Hash as an argument and formatted, named placeholders.
#
# When you pass a Hash as an argument and specify placeholders with %<foo>d
# it will interpret the hash values as named arguments and format the value
# according to the formatting instruction appended to the closing >.
#
# Example:
#
# "%<integer>d, %<float>.1f" % { :integer => 10, :float => 43.4 }
# # => "10, 43.3"
def %(args)
if args.kind_of?(Hash)
dup.gsub(INTERPOLATION_PATTERN_WITH_ESCAPE) do |match|
if match == '%%'
'%'
else
key = ($1 || $2).to_sym
raise KeyError unless args.has_key?(key)
$3 ? sprintf("%#{$3}", args[key]) : args[key]
end
end
elsif self =~ INTERPOLATION_PATTERN
raise ArgumentError.new('one hash required')
else
result = gsub(/%([{<])/, '%%\1')
result.send :'interpolate_without_ruby_19_syntax', args
end
end
end
end

View File

@@ -1,61 +0,0 @@
# encoding: utf-8
class KeyError < IndexError
def initialize(message = nil)
super(message || "key not found")
end
end unless defined?(KeyError)
module I18n
class ArgumentError < ::ArgumentError; end
class InvalidLocale < ArgumentError
attr_reader :locale
def initialize(locale)
@locale = locale
super "#{locale.inspect} is not a valid locale"
end
end
class MissingTranslationData < ArgumentError
attr_reader :locale, :key, :options
def initialize(locale, key, opts = nil)
@key, @locale, @options = key, locale, opts || {}
keys = I18n.normalize_keys(locale, key, options[:scope])
keys << 'no key' if keys.size < 2
super "translation missing: #{keys.join(', ')}"
end
end
class InvalidPluralizationData < ArgumentError
attr_reader :entry, :count
def initialize(entry, count)
@entry, @count = entry, count
super "translation data #{entry.inspect} can not be used with :count => #{count}"
end
end
class MissingInterpolationArgument < ArgumentError
attr_reader :values, :string
def initialize(values, string)
@values, @string = values, string
super "missing interpolation argument in #{string.inspect} (#{values.inspect} given)"
end
end
class ReservedInterpolationKey < ArgumentError
attr_reader :key, :string
def initialize(key, string)
@key, @string = key, string
super "reserved key #{key.inspect} used in #{string.inspect}"
end
end
class UnknownFileType < ArgumentError
attr_reader :type, :filename
def initialize(type, filename)
@type, @filename = type, filename
super "can not load translations from #{filename}, the file type #{type} is not known"
end
end
end

View File

@@ -1,27 +0,0 @@
# encoding: utf-8
module I18n
module Gettext
PLURAL_SEPARATOR = "\001"
CONTEXT_SEPARATOR = "\004"
autoload :Helpers, 'i18n/gettext/helpers'
@@plural_keys = { :en => [:one, :other] }
class << self
# returns an array of plural keys for the given locale so that we can
# convert from gettext's integer-index based style
# TODO move this information to the pluralization module
def plural_keys(locale)
@@plural_keys[locale] || @@plural_keys[:en]
end
def extract_scope(msgid, separator)
scope = msgid.to_s.split(separator)
msgid = scope.pop
[scope, msgid]
end
end
end
end

View File

@@ -1,65 +0,0 @@
# encoding: utf-8
require 'i18n/gettext'
module I18n
module Gettext
# Implements classical Gettext style accessors. To use this include the
# module to the global namespace or wherever you want to use it.
#
# include I18n::Helpers::Gettext
module Helpers
def gettext(msgid, options = {})
I18n.t(msgid, { :default => msgid, :separator => '|' }.merge(options))
end
alias _ gettext
def sgettext(msgid, separator = '|')
scope, msgid = I18n::Gettext.extract_scope(msgid, separator)
I18n.t(msgid, :scope => scope, :default => msgid, :separator => separator)
end
alias s_ sgettext
def pgettext(msgctxt, msgid)
separator = I18n::Gettext::CONTEXT_SEPARATOR
sgettext([msgctxt, msgid].join(separator), separator)
end
alias p_ pgettext
def ngettext(msgid, msgid_plural, n = 1)
nsgettext(msgid, msgid_plural, n)
end
alias n_ ngettext
# Method signatures:
# nsgettext('Fruits|apple', 'apples', 2)
# nsgettext(['Fruits|apple', 'apples'], 2)
def nsgettext(msgid, msgid_plural, n = 1, separator = '|')
if msgid.is_a?(Array)
msgid, msgid_plural, n, separator = msgid[0], msgid[1], msgid_plural, n
separator = '|' unless separator.is_a?(::String)
end
scope, msgid = I18n::Gettext.extract_scope(msgid, separator)
default = { :one => msgid, :other => msgid_plural }
I18n.t(msgid, :default => default, :count => n, :scope => scope, :separator => separator)
end
alias ns_ nsgettext
# Method signatures:
# npgettext('Fruits', 'apple', 'apples', 2)
# npgettext('Fruits', ['apple', 'apples'], 2)
def npgettext(msgctxt, msgid, msgid_plural, n = 1)
separator = I18n::Gettext::CONTEXT_SEPARATOR
if msgid.is_a?(Array)
msgid_plural, msgid, n = msgid[1], [msgctxt, msgid[0]].join(separator), msgid_plural
else
msgid = [msgctxt, msgid].join(separator)
end
nsgettext(msgid, msgid_plural, n, separator)
end
alias np_ npgettext
end
end
end

View File

@@ -1,329 +0,0 @@
=begin
poparser.rb - Generate a .mo
Copyright (C) 2003-2009 Masao Mutoh <mutoh at highway.ne.jp>
You may redistribute it and/or modify it under the same
license terms as Ruby.
=end
#MODIFIED
# removed include GetText etc
# added stub translation method _(x)
require 'racc/parser'
module GetText
class PoParser < Racc::Parser
def _(x)
x
end
module_eval <<'..end src/poparser.ry modeval..id7a99570e05', 'src/poparser.ry', 108
def unescape(orig)
ret = orig.gsub(/\\n/, "\n")
ret.gsub!(/\\t/, "\t")
ret.gsub!(/\\r/, "\r")
ret.gsub!(/\\"/, "\"")
ret
end
def parse(str, data, ignore_fuzzy = true)
@comments = []
@data = data
@fuzzy = false
@msgctxt = ""
$ignore_fuzzy = ignore_fuzzy
str.strip!
@q = []
until str.empty? do
case str
when /\A\s+/
str = $'
when /\Amsgctxt/
@q.push [:MSGCTXT, $&]
str = $'
when /\Amsgid_plural/
@q.push [:MSGID_PLURAL, $&]
str = $'
when /\Amsgid/
@q.push [:MSGID, $&]
str = $'
when /\Amsgstr/
@q.push [:MSGSTR, $&]
str = $'
when /\A\[(\d+)\]/
@q.push [:PLURAL_NUM, $1]
str = $'
when /\A\#~(.*)/
$stderr.print _("Warning: obsolete msgid exists.\n")
$stderr.print " #{$&}\n"
@q.push [:COMMENT, $&]
str = $'
when /\A\#(.*)/
@q.push [:COMMENT, $&]
str = $'
when /\A\"(.*)\"/
@q.push [:STRING, $1]
str = $'
else
#c = str[0,1]
#@q.push [:STRING, c]
str = str[1..-1]
end
end
@q.push [false, '$end']
if $DEBUG
@q.each do |a,b|
puts "[#{a}, #{b}]"
end
end
@yydebug = true if $DEBUG
do_parse
if @comments.size > 0
@data.set_comment(:last, @comments.join("\n"))
end
@data
end
def next_token
@q.shift
end
def on_message(msgid, msgstr)
if msgstr.size > 0
@data[msgid] = msgstr
@data.set_comment(msgid, @comments.join("\n"))
end
@comments.clear
@msgctxt = ""
end
def on_comment(comment)
@fuzzy = true if (/fuzzy/ =~ comment)
@comments << comment
end
..end src/poparser.ry modeval..id7a99570e05
##### racc 1.4.5 generates ###
racc_reduce_table = [
0, 0, :racc_error,
0, 10, :_reduce_none,
2, 10, :_reduce_none,
2, 10, :_reduce_none,
2, 10, :_reduce_none,
2, 12, :_reduce_5,
1, 13, :_reduce_none,
1, 13, :_reduce_none,
4, 15, :_reduce_8,
5, 16, :_reduce_9,
2, 17, :_reduce_10,
1, 17, :_reduce_none,
3, 18, :_reduce_12,
1, 11, :_reduce_13,
2, 14, :_reduce_14,
1, 14, :_reduce_15 ]
racc_reduce_n = 16
racc_shift_n = 26
racc_action_table = [
3, 13, 5, 7, 9, 15, 16, 17, 20, 17,
13, 17, 13, 13, 11, 17, 23, 20, 13, 17 ]
racc_action_check = [
1, 16, 1, 1, 1, 12, 12, 12, 18, 18,
7, 14, 15, 9, 3, 19, 20, 21, 23, 25 ]
racc_action_pointer = [
nil, 0, nil, 14, nil, nil, nil, 3, nil, 6,
nil, nil, 0, nil, 4, 5, -6, nil, 2, 8,
8, 11, nil, 11, nil, 12 ]
racc_action_default = [
-1, -16, -2, -16, -3, -13, -4, -16, -6, -16,
-7, 26, -16, -15, -5, -16, -16, -14, -16, -8,
-16, -9, -11, -16, -10, -12 ]
racc_goto_table = [
12, 22, 14, 4, 24, 6, 2, 8, 18, 19,
10, 21, 1, nil, nil, nil, 25 ]
racc_goto_check = [
5, 9, 5, 3, 9, 4, 2, 6, 5, 5,
7, 8, 1, nil, nil, nil, 5 ]
racc_goto_pointer = [
nil, 12, 5, 2, 4, -7, 6, 9, -7, -17 ]
racc_goto_default = [
nil, nil, nil, nil, nil, nil, nil, nil, nil, nil ]
racc_token_table = {
false => 0,
Object.new => 1,
:COMMENT => 2,
:MSGID => 3,
:MSGCTXT => 4,
:MSGID_PLURAL => 5,
:MSGSTR => 6,
:STRING => 7,
:PLURAL_NUM => 8 }
racc_use_result_var = true
racc_nt_base = 9
Racc_arg = [
racc_action_table,
racc_action_check,
racc_action_default,
racc_action_pointer,
racc_goto_table,
racc_goto_check,
racc_goto_default,
racc_goto_pointer,
racc_nt_base,
racc_reduce_table,
racc_token_table,
racc_shift_n,
racc_reduce_n,
racc_use_result_var ]
Racc_token_to_s_table = [
'$end',
'error',
'COMMENT',
'MSGID',
'MSGCTXT',
'MSGID_PLURAL',
'MSGSTR',
'STRING',
'PLURAL_NUM',
'$start',
'msgfmt',
'comment',
'msgctxt',
'message',
'string_list',
'single_message',
'plural_message',
'msgstr_plural',
'msgstr_plural_line']
Racc_debug_parser = true
##### racc system variables end #####
# reduce 0 omitted
# reduce 1 omitted
# reduce 2 omitted
# reduce 3 omitted
# reduce 4 omitted
module_eval <<'.,.,', 'src/poparser.ry', 25
def _reduce_5( val, _values, result )
@msgctxt = unescape(val[1]) + "\004"
result
end
.,.,
# reduce 6 omitted
# reduce 7 omitted
module_eval <<'.,.,', 'src/poparser.ry', 48
def _reduce_8( val, _values, result )
if @fuzzy and $ignore_fuzzy
if val[1] != ""
$stderr.print _("Warning: fuzzy message was ignored.\n")
$stderr.print " msgid '#{val[1]}'\n"
else
on_message('', unescape(val[3]))
end
@fuzzy = false
else
on_message(@msgctxt + unescape(val[1]), unescape(val[3]))
end
result = ""
result
end
.,.,
module_eval <<'.,.,', 'src/poparser.ry', 65
def _reduce_9( val, _values, result )
if @fuzzy and $ignore_fuzzy
if val[1] != ""
$stderr.print _("Warning: fuzzy message was ignored.\n")
$stderr.print "msgid = '#{val[1]}\n"
else
on_message('', unescape(val[3]))
end
@fuzzy = false
else
on_message(@msgctxt + unescape(val[1]) + "\000" + unescape(val[3]), unescape(val[4]))
end
result = ""
result
end
.,.,
module_eval <<'.,.,', 'src/poparser.ry', 76
def _reduce_10( val, _values, result )
if val[0].size > 0
result = val[0] + "\000" + val[1]
else
result = ""
end
result
end
.,.,
# reduce 11 omitted
module_eval <<'.,.,', 'src/poparser.ry', 84
def _reduce_12( val, _values, result )
result = val[2]
result
end
.,.,
module_eval <<'.,.,', 'src/poparser.ry', 91
def _reduce_13( val, _values, result )
on_comment(val[0])
result
end
.,.,
module_eval <<'.,.,', 'src/poparser.ry', 99
def _reduce_14( val, _values, result )
result = val.delete_if{|item| item == ""}.join
result
end
.,.,
module_eval <<'.,.,', 'src/poparser.ry', 103
def _reduce_15( val, _values, result )
result = val[0]
result
end
.,.,
def _reduce_none( val, _values, result )
result
end
end # class PoParser
end # module GetText

View File

@@ -1,6 +0,0 @@
module I18n
module Locale
autoload :Fallbacks, 'i18n/locale/fallbacks'
autoload :Tag, 'i18n/locale/tag'
end
end

View File

@@ -1,98 +0,0 @@
# encoding: utf-8
# Locale Fallbacks
#
# Extends the I18n module to hold a fallbacks instance which is set to an
# instance of I18n::Locale::Fallbacks by default but can be swapped with a
# different implementation.
#
# Locale fallbacks will compute a number of fallback locales for a given locale.
# For example:
#
# <pre><code>
# I18n.fallbacks[:"es-MX"] # => [:"es-MX", :es, :en] </code></pre>
#
# Locale fallbacks always fall back to
#
# * all parent locales of a given locale (e.g. :es for :"es-MX") first,
# * the current default locales and all of their parents second
#
# The default locales are set to [I18n.default_locale] by default but can be
# set to something else.
#
# One can additionally add any number of additional fallback locales manually.
# These will be added before the default locales to the fallback chain. For
# example:
#
# # using the default locale as default fallback locale
#
# I18n.default_locale = :"en-US"
# I18n.fallbacks = I18n::Fallbacks.new(:"de-AT" => :"de-DE")
# I18n.fallbacks[:"de-AT"] # => [:"de-AT", :"de-DE", :de, :"en-US", :en]
#
# # using a custom locale as default fallback locale
#
# I18n.fallbacks = I18n::Fallbacks.new(:"en-GB", :"de-AT" => :de, :"de-CH" => :de)
# I18n.fallbacks[:"de-AT"] # => [:"de-AT", :de, :"en-GB", :en]
# I18n.fallbacks[:"de-CH"] # => [:"de-CH", :de, :"en-GB", :en]
#
# # mapping fallbacks to an existing instance
#
# # people speaking Catalan also speak Spanish as spoken in Spain
# fallbacks = I18n.fallbacks
# fallbacks.map(:ca => :"es-ES")
# fallbacks[:ca] # => [:ca, :"es-ES", :es, :"en-US", :en]
#
# # people speaking Arabian as spoken in Palestine also speak Hebrew as spoken in Israel
# fallbacks.map(:"ar-PS" => :"he-IL")
# fallbacks[:"ar-PS"] # => [:"ar-PS", :ar, :"he-IL", :he, :"en-US", :en]
# fallbacks[:"ar-EG"] # => [:"ar-EG", :ar, :"en-US", :en]
#
# # people speaking Sami as spoken in Finnland also speak Swedish and Finnish as spoken in Finnland
# fallbacks.map(:sms => [:"se-FI", :"fi-FI"])
# fallbacks[:sms] # => [:sms, :"se-FI", :se, :"fi-FI", :fi, :"en-US", :en]
module I18n
module Locale
class Fallbacks < Hash
def initialize(*mappings)
@map = {}
map(mappings.pop) if mappings.last.is_a?(Hash)
self.defaults = mappings.empty? ? [I18n.default_locale.to_sym] : mappings
end
def defaults=(defaults)
@defaults = defaults.map { |default| compute(default, false) }.flatten
end
attr_reader :defaults
def [](locale)
raise InvalidLocale.new(locale) if locale.nil?
locale = locale.to_sym
super || store(locale, compute(locale))
end
def map(mappings)
mappings.each do |from, to|
from, to = from.to_sym, Array(to)
to.each do |to|
@map[from] ||= []
@map[from] << to.to_sym
end
end
end
protected
def compute(tags, include_defaults = true)
result = Array(tags).collect do |tag|
tags = I18n::Locale::Tag.tag(tag).self_and_parents.map! { |t| t.to_sym }
tags.each { |tag| tags += compute(@map[tag]) if @map[tag] }
tags
end.flatten
result.push(*defaults) if include_defaults
result.uniq
end
end
end
end

View File

@@ -1,28 +0,0 @@
# encoding: utf-8
module I18n
module Locale
module Tag
autoload :Parents, 'i18n/locale/tag/parents'
autoload :Rfc4646, 'i18n/locale/tag/rfc4646'
autoload :Simple, 'i18n/locale/tag/simple'
class << self
# Returns the current locale tag implementation. Defaults to +I18n::Locale::Tag::Simple+.
def implementation
@@implementation ||= Simple
end
# Sets the current locale tag implementation. Use this to set a different locale tag implementation.
def implementation=(implementation)
@@implementation = implementation
end
# Factory method for locale tags. Delegates to the current locale tag implementation.
def tag(tag)
implementation.tag(tag)
end
end
end
end
end

View File

@@ -1,24 +0,0 @@
# encoding: utf-8
module I18n
module Locale
module Tag
module Parents
def parent
@parent ||= begin
segs = to_a.compact
segs.length > 1 ? self.class.tag(*segs[0..(segs.length-2)].join('-')) : nil
end
end
def self_and_parents
@self_and_parents ||= [self] + parents
end
def parents
@parents ||= ([parent] + (parent ? parent.parents : [])).compact
end
end
end
end
end

View File

@@ -1,76 +0,0 @@
# encoding: utf-8
# RFC 4646/47 compliant Locale tag implementation that parses locale tags to
# subtags such as language, script, region, variant etc.
#
# For more information see by http://en.wikipedia.org/wiki/IETF_language_tag
#
# Rfc4646::Parser does not implement grandfathered tags.
module I18n
module Locale
module Tag
RFC4646_SUBTAGS = [ :language, :script, :region, :variant, :extension, :privateuse, :grandfathered ]
RFC4646_FORMATS = { :language => :downcase, :script => :capitalize, :region => :upcase, :variant => :downcase }
class Rfc4646 < Struct.new(*RFC4646_SUBTAGS)
class << self
# Parses the given tag and returns a Tag instance if it is valid.
# Returns false if the given tag is not valid according to RFC 4646.
def tag(tag)
matches = parser.match(tag)
new(*matches) if matches
end
def parser
@@parser ||= Rfc4646::Parser
end
def parser=(parser)
@@parser = parser
end
end
include Parents
RFC4646_FORMATS.each do |name, format|
define_method(name) { self[name].send(format) unless self[name].nil? }
end
def to_sym
to_s.to_sym
end
def to_s
@tag ||= to_a.compact.join("-")
end
def to_a
members.collect { |attr| self.send(attr) }
end
module Parser
PATTERN = %r{\A(?:
([a-z]{2,3}(?:(?:-[a-z]{3}){0,3})?|[a-z]{4}|[a-z]{5,8}) # language
(?:-([a-z]{4}))? # script
(?:-([a-z]{2}|\d{3}))? # region
(?:-([0-9a-z]{5,8}|\d[0-9a-z]{3}))* # variant
(?:-([0-9a-wyz](?:-[0-9a-z]{2,8})+))* # extension
(?:-(x(?:-[0-9a-z]{1,8})+))?| # privateuse subtag
(x(?:-[0-9a-z]{1,8})+)| # privateuse tag
/* ([a-z]{1,3}(?:-[0-9a-z]{2,8}){1,2}) */ # grandfathered
)\z}xi
class << self
def match(tag)
c = PATTERN.match(tag.to_s).captures
c[0..4] << (c[5].nil? ? c[6] : c[5]) << c[7] # TODO c[7] is grandfathered, throw a NotImplemented exception here?
rescue
false
end
end
end
end
end
end
end

View File

@@ -1,41 +0,0 @@
# encoding: utf-8
# Simple Locale tag implementation that computes subtags by simply splitting
# the locale tag at '-' occurences.
module I18n
module Locale
module Tag
class Simple
class << self
def tag(tag)
new(tag)
end
end
include Parents
attr_reader :tag
def initialize(*tag)
@tag = tag.join('-').to_sym
end
def subtags
@subtags = tag.to_s.split('-').map { |subtag| subtag.to_s }
end
def to_sym
tag
end
def to_s
tag.to_s
end
def to_a
subtags
end
end
end
end
end

View File

@@ -1,3 +0,0 @@
module I18n
VERSION = "0.4.1"
end

View File

@@ -1,13 +1,13 @@
#--
# Copyright (c) 2005-2006 Philip Ross
#
#
# 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.
#
@@ -30,4 +30,5 @@ require 'tzinfo/timezone'
# require 'tzinfo/tzdataparser'
# require 'tzinfo/timezone_proxy'
require 'tzinfo/data_timezone'
require 'tzinfo/linked_timezone'
require 'tzinfo/linked_timezone'
require 'tzinfo/definitions'

View File

@@ -0,0 +1,30 @@
require "tzinfo/data_timezone_info"
require "tzinfo/linked_timezone_info"
require "tzinfo/timezone_definition"
module TZInfo
module Definitions
def self.load_all!
return true if @loaded
@loaded = true
defns = Marshal.load(File.read(File.expand_path("../definitions.dump", __FILE__)))
defns.each do |defn|
tz_mod = defn.instance_variable_get(:@identifier).split("/").reduce(TZInfo::Definitions) { |mod, name|
if mod.const_defined?(name)
mod.const_get(name)
else
mod.const_set(name, Module.new)
end
}
def tz_mod.get
@timezone
end
tz_mod.instance_variable_set(:@timezone, defn)
end
end
end
end

View File

@@ -1,13 +1,13 @@
#--
# Copyright (c) 2005-2006 Philip Ross
#
#
# 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.
#
@@ -24,31 +24,32 @@ require 'date'
# require 'tzinfo/country'
require 'tzinfo/time_or_datetime'
require 'tzinfo/timezone_period'
require 'tzinfo/definitions'
module TZInfo
# Indicate a specified time in a local timezone has more than one
# possible time in UTC. This happens when switching from daylight savings time
# possible time in UTC. This happens when switching from daylight savings time
# to normal time where the clocks are rolled back. Thrown by period_for_local
# and local_to_utc when using an ambiguous time and not specifying any
# and local_to_utc when using an ambiguous time and not specifying any
# means to resolve the ambiguity.
class AmbiguousTime < StandardError
end
# Thrown to indicate that no TimezonePeriod matching a given time could be found.
class PeriodNotFound < StandardError
end
# Thrown by Timezone#get if the identifier given is not valid.
class InvalidTimezoneIdentifier < StandardError
end
# Thrown if an attempt is made to use a timezone created with Timezone.new(nil).
class UnknownTimezone < StandardError
end
# Timezone is the base class of all timezones. It provides a factory method
# get to access timezones by identifier. Once a specific Timezone has been
# retrieved, DateTimes, Times and timestamps can be converted between the UTC
# retrieved, DateTimes, Times and timestamps can be converted between the UTC
# and the local time for the zone. For example:
#
# tz = TZInfo::Timezone.get('America/New_York')
@@ -56,42 +57,41 @@ module TZInfo
# puts tz.local_to_utc(Time.utc(2005,8,29,11,35,0)).to_s
# puts tz.utc_to_local(1125315300).to_s
#
# Each time conversion method returns an object of the same type it was
# Each time conversion method returns an object of the same type it was
# passed.
#
# The timezone information all comes from the tz database
# (see http://www.twinsun.com/tz/tz-link.htm)
class Timezone
include Comparable
# Cache of loaded zones by identifier to avoid using require if a zone
# has already been loaded.
@@loaded_zones = {}
# Whether the timezones index has been loaded yet.
@@index_loaded = false
# Returns a timezone by its identifier (e.g. "Europe/London",
# Returns a timezone by its identifier (e.g. "Europe/London",
# "America/Chicago" or "UTC").
#
# Raises InvalidTimezoneIdentifier if the timezone couldn't be found.
def self.get(identifier)
instance = @@loaded_zones[identifier]
unless instance
unless instance
raise InvalidTimezoneIdentifier, 'Invalid identifier' if identifier !~ /^[A-z0-9\+\-_]+(\/[A-z0-9\+\-_]+)*$/
identifier = identifier.gsub(/-/, '__m__').gsub(/\+/, '__p__')
begin
# Use a temporary variable to avoid an rdoc warning
file = "tzinfo/definitions/#{identifier}"
require file
TZInfo::Definitions.load_all!
m = Definitions
identifier.split(/\//).each {|part|
m = m.const_get(part)
}
info = m.get
# Could make Timezone subclasses register an interest in an info
# type. Since there are currently only two however, there isn't
# much point.
@@ -102,35 +102,35 @@ module TZInfo
else
raise InvalidTimezoneIdentifier, "No handler for info type #{info.class}"
end
@@loaded_zones[instance.identifier] = instance
@@loaded_zones[instance.identifier] = instance
rescue LoadError, NameError => e
raise InvalidTimezoneIdentifier, e.message
end
end
instance
end
# Returns a proxy for the Timezone with the given identifier. The proxy
# will cause the real timezone to be loaded when an attempt is made to
# find a period or convert a time. get_proxy will not validate the
# identifier. If an invalid identifier is specified, no exception will be
# raised until the proxy is used.
# will cause the real timezone to be loaded when an attempt is made to
# find a period or convert a time. get_proxy will not validate the
# identifier. If an invalid identifier is specified, no exception will be
# raised until the proxy is used.
def self.get_proxy(identifier)
TimezoneProxy.new(identifier)
end
# If identifier is nil calls super(), otherwise calls get. An identfier
# If identifier is nil calls super(), otherwise calls get. An identfier
# should always be passed in when called externally.
def self.new(identifier = nil)
if identifier
if identifier
get(identifier)
else
super()
end
end
# Returns an array containing all the available Timezones.
#
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
@@ -138,14 +138,14 @@ module TZInfo
def self.all
get_proxies(all_identifiers)
end
# Returns an array containing the identifiers of all the available
# Returns an array containing the identifiers of all the available
# Timezones.
def self.all_identifiers
load_index
Indexes::Timezones.timezones
end
# Returns an array containing all the available Timezones that are based
# on data (are not links to other Timezones).
#
@@ -154,44 +154,44 @@ module TZInfo
def self.all_data_zones
get_proxies(all_data_zone_identifiers)
end
# Returns an array containing the identifiers of all the available
# Returns an array containing the identifiers of all the available
# Timezones that are based on data (are not links to other Timezones)..
def self.all_data_zone_identifiers
load_index
Indexes::Timezones.data_timezones
end
# Returns an array containing all the available Timezones that are links
# to other Timezones.
#
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
# definitions until a conversion is actually required.
def self.all_linked_zones
get_proxies(all_linked_zone_identifiers)
get_proxies(all_linked_zone_identifiers)
end
# Returns an array containing the identifiers of all the available
# Returns an array containing the identifiers of all the available
# Timezones that are links to other Timezones.
def self.all_linked_zone_identifiers
load_index
Indexes::Timezones.linked_timezones
end
# Returns all the Timezones defined for all Countries. This is not the
# complete set of Timezones as some are not country specific (e.g.
# complete set of Timezones as some are not country specific (e.g.
# 'Etc/GMT').
#
#
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
# definitions until a conversion is actually required.
# definitions until a conversion is actually required.
def self.all_country_zones
Country.all_codes.inject([]) {|zones,country|
zones += Country.get(country).zones
}
end
# Returns all the zone identifiers defined for all Countries. This is not the
# complete set of zone identifiers as some are not country specific (e.g.
# complete set of zone identifiers as some are not country specific (e.g.
# 'Etc/GMT'). You can obtain a Timezone instance for a given identifier
# with the get method.
def self.all_country_zone_identifiers
@@ -199,8 +199,8 @@ module TZInfo
zones += Country.get(country).zone_identifiers
}
end
# Returns all US Timezone instances. A shortcut for
# Returns all US Timezone instances. A shortcut for
# TZInfo::Country.get('US').zones.
#
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
@@ -208,35 +208,35 @@ module TZInfo
def self.us_zones
Country.get('US').zones
end
# Returns all US zone identifiers. A shortcut for
# Returns all US zone identifiers. A shortcut for
# TZInfo::Country.get('US').zone_identifiers.
def self.us_zone_identifiers
Country.get('US').zone_identifiers
end
# The identifier of the timezone, e.g. "Europe/Paris".
def identifier
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
end
# An alias for identifier.
def name
# Don't use alias, as identifier gets overridden.
identifier
end
# Returns a friendlier version of the identifier.
def to_s
friendly_identifier
end
# Returns internal object state as a programmer-readable string.
def inspect
"#<#{self.class}: #{identifier}>"
end
# Returns a friendlier version of the identifier. Set skip_first_part to
# Returns a friendlier version of the identifier. Set skip_first_part to
# omit the first part of the identifier (typically a region name) where
# there is more than one part.
#
@@ -245,13 +245,13 @@ module TZInfo
# Timezone.get('Europe/Paris').friendly_identifier(false) #=> "Europe - Paris"
# Timezone.get('Europe/Paris').friendly_identifier(true) #=> "Paris"
# Timezone.get('America/Indiana/Knox').friendly_identifier(false) #=> "America - Knox, Indiana"
# Timezone.get('America/Indiana/Knox').friendly_identifier(true) #=> "Knox, Indiana"
# Timezone.get('America/Indiana/Knox').friendly_identifier(true) #=> "Knox, Indiana"
def friendly_identifier(skip_first_part = false)
parts = identifier.split('/')
if parts.empty?
# shouldn't happen
identifier
elsif parts.length == 1
elsif parts.length == 1
parts[0]
else
if skip_first_part
@@ -259,47 +259,47 @@ module TZInfo
else
result = parts[0] + ' - '
end
parts[1, parts.length - 1].reverse_each {|part|
part.gsub!(/_/, ' ')
if part.index(/[a-z]/)
# Missing a space if a lower case followed by an upper case and the
# name isn't McXxxx.
part.gsub!(/([^M][a-z])([A-Z])/, '\1 \2')
part.gsub!(/([M][a-bd-z])([A-Z])/, '\1 \2')
# Missing an apostrophe if two consecutive upper case characters.
part.gsub!(/([A-Z])([A-Z])/, '\1\'\2')
end
result << part
result << ', '
}
result.slice!(result.length - 2, 2)
result
end
end
# Returns the TimezonePeriod for the given UTC time. utc can either be
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
# information in utc is ignored (it is treated as a UTC time).
def period_for_utc(utc)
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
# information in utc is ignored (it is treated as a UTC time).
def period_for_utc(utc)
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
end
# Returns the set of TimezonePeriod instances that are valid for the given
# local time as an array. If you just want a single period, use
# local time as an array. If you just want a single period, use
# period_for_local instead and specify how ambiguities should be resolved.
# Returns an empty array if no periods are found for the given time.
def periods_for_local(local)
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
end
# Returns the TimezonePeriod for the given local time. local can either be
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
# information in local is ignored (it is treated as a time in the current
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
# information in local is ignored (it is treated as a time in the current
# timezone).
#
# Warning: There are local times that have no equivalent UTC times (e.g.
@@ -312,70 +312,70 @@ module TZInfo
#
# In the second case (more than one equivalent UTC time), an AmbiguousTime
# exception will be raised unless the optional dst parameter or block
# handles the ambiguity.
# handles the ambiguity.
#
# If the ambiguity is due to a transition from daylight savings time to
# standard time, the dst parameter can be used to select whether the
# standard time, the dst parameter can be used to select whether the
# daylight savings time or local time is used. For example,
#
# Timezone.get('America/New_York').period_for_local(DateTime.new(2004,10,31,1,30,0))
#
# would raise an AmbiguousTime exception.
#
# Specifying dst=true would the daylight savings period from April to
# Specifying dst=true would the daylight savings period from April to
# October 2004. Specifying dst=false would return the standard period
# from October 2004 to April 2005.
#
# If the dst parameter does not resolve the ambiguity, and a block is
# If the dst parameter does not resolve the ambiguity, and a block is
# specified, it is called. The block must take a single parameter - an
# array of the periods that need to be resolved. The block can select and
# return a single period or return nil or an empty array
# to cause an AmbiguousTime exception to be raised.
def period_for_local(local, dst = nil)
def period_for_local(local, dst = nil)
results = periods_for_local(local)
if results.empty?
raise PeriodNotFound
elsif results.size < 2
results.first
else
# ambiguous result try to resolve
if !dst.nil?
matches = results.find_all {|period| period.dst? == dst}
results = matches if !matches.empty?
results = matches if !matches.empty?
end
if results.size < 2
results.first
else
# still ambiguous, try the block
if block_given?
results = yield results
end
if results.is_a?(TimezonePeriod)
results
elsif results && results.size == 1
results.first
else
else
raise AmbiguousTime, "#{local} is an ambiguous local time."
end
end
end
end
end
# Converts a time in UTC to the local timezone. utc can either be
# a DateTime, Time or timestamp (Time.to_i). The returned time has the same
# type as utc. Any timezone information in utc is ignored (it is treated as
# type as utc. Any timezone information in utc is ignored (it is treated as
# a UTC time).
def utc_to_local(utc)
TimeOrDateTime.wrap(utc) {|wrapped|
period_for_utc(wrapped).to_local(wrapped)
}
end
# Converts a time in the local timezone to UTC. local can either be
# a DateTime, Time or timestamp (Time.to_i). The returned time has the same
# type as local. Any timezone information in local is ignored (it is treated
@@ -391,10 +391,10 @@ module TZInfo
#
# In the second case (more than one equivalent UTC time), an AmbiguousTime
# exception will be raised unless the optional dst parameter or block
# handles the ambiguity.
# handles the ambiguity.
#
# If the ambiguity is due to a transition from daylight savings time to
# standard time, the dst parameter can be used to select whether the
# standard time, the dst parameter can be used to select whether the
# daylight savings time or local time is used. For example,
#
# Timezone.get('America/New_York').local_to_utc(DateTime.new(2004,10,31,1,30,0))
@@ -404,7 +404,7 @@ module TZInfo
# Specifying dst=true would return 2004-10-31 5:30:00. Specifying dst=false
# would return 2004-10-31 6:30:00.
#
# If the dst parameter does not resolve the ambiguity, and a block is
# If the dst parameter does not resolve the ambiguity, and a block is
# specified, it is called. The block must take a single parameter - an
# array of the periods that need to be resolved. The block can return a
# single period to use to convert the time or return nil or an empty array
@@ -416,21 +416,21 @@ module TZInfo
else
period = period_for_local(wrapped, dst)
end
period.to_utc(wrapped)
}
end
# Returns the current time in the timezone as a Time.
def now
utc_to_local(Time.now.utc)
end
# Returns the TimezonePeriod for the current time.
# Returns the TimezonePeriod for the current time.
def current_period
period_for_utc(Time.now.utc)
end
# Returns the current Time and TimezonePeriod as an array. The first element
# is the time, the second element is the period.
def current_period_and_time
@@ -438,19 +438,19 @@ module TZInfo
period = period_for_utc(utc)
[period.to_local(utc), period]
end
alias :current_time_and_period :current_period_and_time
# Converts a time in UTC to local time and returns it as a string
# according to the given format. The formatting is identical to
# Converts a time in UTC to local time and returns it as a string
# according to the given format. The formatting is identical to
# Time.strftime and DateTime.strftime, except %Z is replaced with the
# timezone abbreviation for the specified time (for example, EST or EDT).
def strftime(format, utc = Time.now.utc)
# timezone abbreviation for the specified time (for example, EST or EDT).
def strftime(format, utc = Time.now.utc)
period = period_for_utc(utc)
local = period.to_local(utc)
local = period.to_local(utc)
local = Time.at(local).utc unless local.kind_of?(Time) || local.kind_of?(DateTime)
abbreviation = period.abbreviation.to_s.gsub(/%/, '%%')
format = format.gsub(/(.?)%Z/) do
if $1 == '%'
# return %%Z so the real strftime treats it as a literal %Z too
@@ -459,50 +459,50 @@ module TZInfo
"#$1#{abbreviation}"
end
end
local.strftime(format)
end
# Compares two Timezones based on their identifier. Returns -1 if tz is less
# than self, 0 if tz is equal to self and +1 if tz is greater than self.
def <=>(tz)
identifier <=> tz.identifier
end
# Returns true if and only if the identifier of tz is equal to the
# Returns true if and only if the identifier of tz is equal to the
# identifier of this Timezone.
def eql?(tz)
self == tz
end
# Returns a hash of this Timezone.
def hash
identifier.hash
end
# Dumps this Timezone for marshalling.
def _dump(limit)
identifier
end
# Loads a marshalled Timezone.
def self._load(data)
Timezone.get(data)
end
private
# Loads in the index of timezones if it hasn't already been loaded.
def self.load_index
unless @@index_loaded
require 'tzinfo/indexes/timezones'
@@index_loaded = true
end
end
end
# Returns an array of proxies corresponding to the given array of
# Returns an array of proxies corresponding to the given array of
# identifiers.
def self.get_proxies(identifiers)
identifiers.collect {|identifier| get_proxy(identifier)}
end
end
end
end

View File

@@ -505,7 +505,7 @@ class HashToXmlTest < Test::Unit::TestCase
assert_equal "<person>", xml.first(8)
assert xml.include?(%(<street>Paulina</street>))
assert xml.include?(%(<name>David</name>))
assert xml.include?(%(<age nil="true"></age>))
assert_includes xml, %(<age nil="true"/>)
end
def test_one_level_with_skipping_types
@@ -513,7 +513,7 @@ class HashToXmlTest < Test::Unit::TestCase
assert_equal "<person>", xml.first(8)
assert xml.include?(%(<street>Paulina</street>))
assert xml.include?(%(<name>David</name>))
assert xml.include?(%(<age nil="true"></age>))
assert_includes xml, %(<age nil="true"/>)
end
def test_one_level_with_yielding
@@ -618,12 +618,12 @@ class HashToXmlTest < Test::Unit::TestCase
EOT
expected_topic_hash = {
:title => nil,
:title => nil,
:id => nil,
:approved => nil,
:written_on => nil,
:viewed_at => nil,
:content => nil,
:content => nil,
:parent_id => nil
}.stringify_keys
@@ -701,7 +701,7 @@ class HashToXmlTest < Test::Unit::TestCase
assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"]
end
def test_empty_array_from_xml
blog_xml = <<-XML
<blog>
@@ -815,13 +815,13 @@ class HashToXmlTest < Test::Unit::TestCase
assert_equal expected_bacon_hash, Hash.from_xml(bacon_xml)["bacon"]
end
def test_type_trickles_through_when_unknown
product_xml = <<-EOT
<product>
<weight type="double">0.5</weight>
<image type="ProductImage"><filename>image.gif</filename></image>
</product>
EOT
@@ -830,7 +830,7 @@ class HashToXmlTest < Test::Unit::TestCase
:image => {'type' => 'ProductImage', 'filename' => 'image.gif' },
}.stringify_keys
assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"]
assert_equal expected_product_hash, Hash.from_xml(product_xml)["product"]
end
def test_should_use_default_value_for_unknown_key
@@ -864,41 +864,41 @@ class HashToXmlTest < Test::Unit::TestCase
assert_equal expected, hash.to_xml(@xml_options)
end
end
def test_empty_string_works_for_typecast_xml_value
def test_empty_string_works_for_typecast_xml_value
assert_nothing_raised do
Hash.__send__(:typecast_xml_value, "")
end
end
def test_escaping_to_xml
hash = {
:bare_string => 'First & Last Name',
hash = {
:bare_string => 'First & Last Name',
:pre_escaped_string => 'First &amp; Last Name'
}.stringify_keys
expected_xml = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
assert_equal expected_xml, hash.to_xml(@xml_options)
end
def test_unescaping_from_xml
xml_string = '<person><bare-string>First &amp; Last Name</bare-string><pre-escaped-string>First &amp;amp; Last Name</pre-escaped-string></person>'
expected_hash = {
:bare_string => 'First & Last Name',
expected_hash = {
:bare_string => 'First & Last Name',
:pre_escaped_string => 'First &amp; Last Name'
}.stringify_keys
assert_equal expected_hash, Hash.from_xml(xml_string)['person']
end
def test_roundtrip_to_xml_from_xml
hash = {
:bare_string => 'First & Last Name',
hash = {
:bare_string => 'First & Last Name',
:pre_escaped_string => 'First &amp; Last Name'
}.stringify_keys
assert_equal hash, Hash.from_xml(hash.to_xml(@xml_options))['person']
end
def test_to_xml_dups_options
options = {:skip_instruct => true}
{}.to_xml(options)
@@ -916,7 +916,7 @@ class HashToXmlTest < Test::Unit::TestCase
assert alert_at.utc?
assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
end
def test_datetime_xml_type_with_non_utc_time
alert_xml = <<-XML
<alert>
@@ -927,7 +927,7 @@ class HashToXmlTest < Test::Unit::TestCase
assert alert_at.utc?
assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
end
def test_datetime_xml_type_with_far_future_date
alert_xml = <<-XML
<alert>

View File

@@ -1,3 +1,4 @@
require 'abstract_unit'
require 'active_support'
require 'test/unit'

View File

@@ -4,12 +4,13 @@ class MemoizableTest < Test::Unit::TestCase
class Person
extend ActiveSupport::Memoizable
attr_reader :name_calls, :age_calls, :is_developer_calls
attr_reader :name_calls, :age_calls, :is_developer_calls, :name_query_calls
def initialize
@name_calls = 0
@age_calls = 0
@is_developer_calls = 0
@name_query_calls = 0
end
def name
@@ -18,6 +19,7 @@ class MemoizableTest < Test::Unit::TestCase
end
def name?
@name_query_calls += 1
true
end
memoize :name?
@@ -123,6 +125,13 @@ class MemoizableTest < Test::Unit::TestCase
end
end
def test_memoization_flush_with_punctuation
assert_equal true, @person.name?
@person.flush_cache(:name?)
3.times { assert_equal true, @person.name? }
assert_equal 2, @person.name_query_calls
end
def test_memoization_with_nil_value
assert_equal nil, @person.age
assert_equal 1, @person.age_calls
@@ -131,13 +140,7 @@ class MemoizableTest < Test::Unit::TestCase
assert_equal 1, @person.age_calls
end
def test_memorized_results_are_immutable
assert_equal "Josh", @person.name
assert_raise(ActiveSupport::FrozenObjectError) { @person.name.gsub!("Josh", "Gosh") }
end
def test_reloadable
counter = @calculator.counter
assert_equal 1, @calculator.counter
assert_equal 2, @calculator.counter(:reload)
assert_equal 2, @calculator.counter

View File

@@ -25,7 +25,7 @@ task :default => :test
## This is required until the regular test task
## below passes. It's not ideal, but at least
## we can see the failures
task :test do
task :test do
Dir['test/**/*_test.rb'].all? do |file|
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
system(ruby, '-Itest', file)
@@ -38,7 +38,7 @@ Rake::TestTask.new("regular_test") do |t|
end
BASE_DIRS = %w(
BASE_DIRS = %w(
app
config/environments
config/initializers
@@ -158,21 +158,10 @@ end
# Copy Ties Content -----------------------------------------------------------------------
desc "Make copies of all the default content of ties"
task :copy_ties_content => [
:copy_rootfiles, :copy_dispatches, :copy_html_files, :copy_application,
task :copy_ties_content => [
:copy_rootfiles, :copy_html_files, :copy_application,
:copy_configs, :copy_binfiles, :copy_test_helpers, :copy_app_doc_readme ]
task :copy_dispatches do
copy_with_rewritten_ruby_path("dispatches/dispatch.rb", "#{PKG_DESTINATION}/public/dispatch.rb")
chmod 0755, "#{PKG_DESTINATION}/public/dispatch.rb"
copy_with_rewritten_ruby_path("dispatches/dispatch.rb", "#{PKG_DESTINATION}/public/dispatch.cgi")
chmod 0755, "#{PKG_DESTINATION}/public/dispatch.cgi"
copy_with_rewritten_ruby_path("dispatches/dispatch.fcgi", "#{PKG_DESTINATION}/public/dispatch.fcgi")
chmod 0755, "#{PKG_DESTINATION}/public/dispatch.fcgi"
end
task :copy_html_files do
HTML_FILES.each { |file| cp File.join('html', file), File.join(PKG_DESTINATION, 'public', file) }
end
@@ -187,7 +176,7 @@ task :copy_configs do
socket = nil
require 'erb'
File.open("#{PKG_DESTINATION}/config/database.yml", 'w') {|f| f.write ERB.new(IO.read("configs/databases/sqlite3.yml"), nil, '-').result(binding)}
cp "configs/routes.rb", "#{PKG_DESTINATION}/config/routes.rb"
cp "configs/initializers/backtrace_silencers.rb", "#{PKG_DESTINATION}/config/initializers/backtrace_silencers.rb"
@@ -288,15 +277,15 @@ end
PKG_FILES = FileList[
'[a-zA-Z]*',
'bin/**/*',
'bin/**/*',
'builtin/**/*',
'configs/**/*',
'doc/**/*',
'dispatches/**/*',
'environments/**/*',
'helpers/**/*',
'generators/**/*',
'html/**/*',
'configs/**/*',
'doc/**/*',
'dispatches/**/*',
'environments/**/*',
'helpers/**/*',
'generators/**/*',
'html/**/*',
'lib/**/*'
] - [ 'test' ]
@@ -336,7 +325,7 @@ end
# Publishing -------------------------------------------------------
desc "Publish the rails gem"
task :pgem => [:gem] do
task :pgem => [:gem] do
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
end

View File

@@ -1,24 +0,0 @@
#!/usr/bin/env ruby
#
# You may specify the path to the FastCGI crash log (a log of unhandled
# exceptions which forced the FastCGI instance to exit, great for debugging)
# and the number of requests to process before running garbage collection.
#
# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log
# and the GC period is nil (turned off). A reasonable number of requests
# could range from 10-100 depending on the memory footprint of your app.
#
# Example:
# # Default log path, normal GC behavior.
# RailsFCGIHandler.process!
#
# # Default log path, 50 requests between GC.
# RailsFCGIHandler.process! nil, 50
#
# # Custom log path, normal GC behavior.
# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log'
#
require File.dirname(__FILE__) + "/../config/environment"
require 'fcgi_handler'
RailsFCGIHandler.process!

View File

@@ -1,10 +0,0 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT)
# If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like:
# "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired
require "dispatcher"
ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun)
Dispatcher.dispatch

View File

@@ -1,97 +0,0 @@
#!/usr/bin/env ruby
require 'drb'
# This file includes an experimental gateway CGI implementation. It will work
# only on platforms which support both fork and sockets.
#
# To enable it edit public/.htaccess and replace dispatch.cgi with gateway.cgi.
#
# Next, create the directory log/drb_gateway and grant the apache user rw access
# to said directory.
#
# On the next request to your server, the gateway tracker should start up, along
# with a few listener processes. This setup should provide you with much better
# speeds than dispatch.cgi.
#
# Keep in mind that the first request made to the server will be slow, as the
# tracker and listeners will have to load. Also, the tracker and listeners will
# shutdown after a period if inactivity. You can set this value below -- the
# default is 90 seconds.
TrackerSocket = File.expand_path(File.join(File.dirname(__FILE__), '../log/drb_gateway/tracker.sock'))
DieAfter = 90 # Seconds
Listeners = 3
def message(s)
$stderr.puts "gateway.cgi: #{s}" if ENV && ENV["DEBUG_GATEWAY"]
end
def listener_socket(number)
File.expand_path(File.join(File.dirname(__FILE__), "../log/drb_gateway/listener_#{number}.sock"))
end
unless File.exist? TrackerSocket
message "Starting tracker and #{Listeners} listeners"
fork do
Process.setsid
STDIN.reopen "/dev/null"
STDOUT.reopen "/dev/null", "a"
root = File.expand_path(File.dirname(__FILE__) + '/..')
message "starting tracker"
fork do
ARGV.clear
ARGV << TrackerSocket << Listeners.to_s << DieAfter.to_s
load File.join(root, 'script', 'tracker')
end
message "starting listeners"
require File.join(root, 'config/environment.rb')
Listeners.times do |number|
fork do
ARGV.clear
ARGV << listener_socket(number) << DieAfter.to_s
load File.join(root, 'script', 'listener')
end
end
end
message "waiting for tracker and listener to arise..."
ready = false
10.times do
sleep 0.5
break if (ready = File.exist?(TrackerSocket) && File.exist?(listener_socket(0)))
end
if ready
message "tracker and listener are ready"
else
message "Waited 5 seconds, listener and tracker not ready... dropping request"
Kernel.exit 1
end
end
DRb.start_service
message "connecting to tracker"
tracker = DRbObject.new_with_uri("drbunix:#{TrackerSocket}")
input = $stdin.read
$stdin.close
env = ENV.inspect
output = nil
tracker.with_listener do |number|
message "connecting to listener #{number}"
socket = listener_socket(number)
listener = DRbObject.new_with_uri("drbunix:#{socket}")
output = listener.process(env, input)
message "listener #{number} has finished, writing output"
end
$stdout.write output
$stdout.flush
$stdout.close

View File

@@ -45,7 +45,7 @@ def find_cmd(*commands)
end
case config["adapter"]
when /^mysql/
when /mysql/
args = {
'host' => '--host',
'port' => '--port',

View File

@@ -1,86 +0,0 @@
#!/usr/bin/env ruby
require 'stringio'
require 'fileutils'
require 'fcgi_handler'
def message(s)
$stderr.puts "listener: #{s}" if ENV && ENV["DEBUG_GATEWAY"]
end
class RemoteCGI < CGI
attr_accessor :stdinput, :stdoutput, :env_table
def initialize(env_table, input = nil, output = nil)
self.env_table = env_table
self.stdinput = input || StringIO.new
self.stdoutput = output || StringIO.new
super()
end
def out(stream) # Ignore the requested output stream
super(stdoutput)
end
end
class Listener
include DRbUndumped
def initialize(timeout, socket_path)
@socket = File.expand_path(socket_path)
@mutex = Mutex.new
@active = false
@timeout = timeout
@handler = RailsFCGIHandler.new
@handler.extend DRbUndumped
message 'opening socket'
DRb.start_service("drbunix:#{@socket}", self)
message 'entering process loop'
@handler.process! self
end
def each_cgi(&cgi_block)
@cgi_block = cgi_block
message 'entering idle loop'
loop do
sleep @timeout rescue nil
die! unless @active
@active = false
end
end
def process(env, input)
message 'received request'
@mutex.synchronize do
@active = true
message 'creating input stream'
input_stream = StringIO.new(input)
message 'building CGI instance'
cgi = RemoteCGI.new(eval(env), input_stream)
message 'yielding to fcgi handler'
@cgi_block.call cgi
message 'yield finished -- sending output'
cgi.stdoutput.seek(0)
output = cgi.stdoutput.read
return output
end
end
def die!
message 'shutting down'
DRb.stop_service
FileUtils.rm_f @socket
Kernel.exit 0
end
end
socket_path = ARGV.shift
timeout = (ARGV.shift || 90).to_i
Listener.new(timeout, socket_path)

View File

@@ -1,69 +0,0 @@
#!/usr/bin/env ruby
require 'drb'
require 'thread'
def message(s)
$stderr.puts "tracker: #{s}" if ENV && ENV["DEBUG_GATEWAY"]
end
class Tracker
include DRbUndumped
def initialize(instances, socket_path)
@instances = instances
@socket = File.expand_path(socket_path)
@active = false
@listeners = []
@instances.times { @listeners << Mutex.new }
message "using #{@listeners.length} listeners"
message "opening socket at #{@socket}"
@service = DRb.start_service("drbunix://#{@socket}", self)
end
def with_listener
message "listener requested"
mutex = has_lock = index = nil
3.times do
@listeners.each_with_index do |mutex, index|
has_lock = mutex.try_lock
break if has_lock
end
break if has_lock
sleep 0.05
end
if has_lock
message "obtained listener #{index}"
@active = true
begin yield index
ensure
mutex.unlock
message "released listener #{index}"
end
else
message "dropping request because no listeners are available!"
end
end
def background(check_interval = nil)
if check_interval
loop do
sleep check_interval
message "Idle for #{check_interval}, shutting down" unless @active
@active = false
Kernel.exit 0
end
else DRb.thread.join
end
end
end
socket_path = ARGV.shift
instances = ARGV.shift.to_i
t = Tracker.new(instances, socket_path)
t.background(ARGV.first ? ARGV.shift.to_i : 90)

View File

@@ -1,239 +0,0 @@
require 'fcgi'
require 'logger'
require 'dispatcher'
require 'rbconfig'
class RailsFCGIHandler
SIGNALS = {
'HUP' => :reload,
'INT' => :exit_now,
'TERM' => :exit_now,
'USR1' => :exit,
'USR2' => :restart
}
GLOBAL_SIGNALS = SIGNALS.keys - %w(USR1)
attr_reader :when_ready
attr_accessor :log_file_path
attr_accessor :gc_request_period
# Initialize and run the FastCGI instance, passing arguments through to new.
def self.process!(*args, &block)
new(*args, &block).process!
end
# Initialize the FastCGI instance with the path to a crash log
# detailing unhandled exceptions (default RAILS_ROOT/log/fastcgi.crash.log)
# and the number of requests to process between garbage collection runs
# (default nil for normal GC behavior.) Optionally, pass a block which
# takes this instance as an argument for further configuration.
def initialize(log_file_path = nil, gc_request_period = nil)
self.log_file_path = log_file_path || "#{RAILS_ROOT}/log/fastcgi.crash.log"
self.gc_request_period = gc_request_period
# Yield for additional configuration.
yield self if block_given?
# Safely install signal handlers.
install_signal_handlers
@app = Dispatcher.new
# Start error timestamp at 11 seconds ago.
@last_error_on = Time.now - 11
end
def process!(provider = FCGI)
mark_features!
dispatcher_log :info, 'starting'
process_each_request provider
dispatcher_log :info, 'stopping gracefully'
rescue Exception => error
case error
when SystemExit
dispatcher_log :info, 'stopping after explicit exit'
when SignalException
dispatcher_error error, 'stopping after unhandled signal'
else
# Retry if exceptions occur more than 10 seconds apart.
if Time.now - @last_error_on > 10
@last_error_on = Time.now
dispatcher_error error, 'retrying after unhandled exception'
retry
else
dispatcher_error error, 'stopping after unhandled exception within 10 seconds of the last'
end
end
end
protected
def process_each_request(provider)
request = nil
catch :exit do
provider.each do |request|
process_request(request)
case when_ready
when :reload
reload!
when :restart
close_connection(request)
restart!
when :exit
close_connection(request)
throw :exit
end
end
end
rescue SignalException => signal
raise unless signal.message == 'SIGUSR1'
close_connection(request)
end
def process_request(request)
@processing, @when_ready = true, nil
gc_countdown
with_signal_handler 'USR1' do
begin
::Rack::Handler::FastCGI.serve(request, @app)
rescue SignalException, SystemExit
raise
rescue Exception => error
dispatcher_error error, 'unhandled dispatch error'
end
end
ensure
@processing = false
end
def logger
@logger ||= Logger.new(@log_file_path)
end
def dispatcher_log(level, msg)
time_str = Time.now.strftime("%d/%b/%Y:%H:%M:%S")
logger.send(level, "[#{time_str} :: #{$$}] #{msg}")
rescue Exception => log_error # Logger errors
STDERR << "Couldn't write to #{@log_file_path.inspect}: #{msg}\n"
STDERR << " #{log_error.class}: #{log_error.message}\n"
end
def dispatcher_error(e, msg = "")
error_message =
"Dispatcher failed to catch: #{e} (#{e.class})\n" +
" #{e.backtrace.join("\n ")}\n#{msg}"
dispatcher_log(:error, error_message)
end
def install_signal_handlers
GLOBAL_SIGNALS.each { |signal| install_signal_handler(signal) }
end
def install_signal_handler(signal, handler = nil)
if SIGNALS.include?(signal) && self.class.method_defined?(name = "#{SIGNALS[signal]}_handler")
handler ||= method(name).to_proc
begin
trap(signal, handler)
rescue ArgumentError
dispatcher_log :warn, "Ignoring unsupported signal #{signal}."
end
else
dispatcher_log :warn, "Ignoring unsupported signal #{signal}."
end
end
def with_signal_handler(signal)
install_signal_handler(signal)
yield
ensure
install_signal_handler(signal, 'DEFAULT')
end
def exit_now_handler(signal)
dispatcher_log :info, "asked to stop immediately"
exit
end
def exit_handler(signal)
dispatcher_log :info, "asked to stop ASAP"
if @processing
@when_ready = :exit
else
throw :exit
end
end
def reload_handler(signal)
dispatcher_log :info, "asked to reload ASAP"
if @processing
@when_ready = :reload
else
reload!
end
end
def restart_handler(signal)
dispatcher_log :info, "asked to restart ASAP"
if @processing
@when_ready = :restart
else
restart!
end
end
def restart!
config = ::Config::CONFIG
ruby = File::join(config['bindir'], config['ruby_install_name']) + config['EXEEXT']
command_line = [ruby, $0, ARGV].flatten.join(' ')
dispatcher_log :info, "restarted"
# close resources as they won't be closed by
# the OS when using exec
logger.close rescue nil
Rails.logger.close rescue nil
exec(command_line)
end
def reload!
run_gc! if gc_request_period
restore!
@when_ready = nil
dispatcher_log :info, "reloaded"
end
# Make a note of $" so we can safely reload this instance.
def mark_features!
@features = $".clone
end
def restore!
$".replace @features
Dispatcher.reset_application!
ActionController::Routing::Routes.reload
end
def run_gc!
@gc_request_countdown = gc_request_period
GC.enable; GC.start; GC.disable
end
def gc_countdown
if gc_request_period
@gc_request_countdown ||= gc_request_period
@gc_request_countdown -= 1
run_gc! if @gc_request_countdown <= 0
end
end
def close_connection(request)
request.finish if request
end
end

View File

@@ -299,7 +299,11 @@ HELP
# Evaluate any assignments in a temporary, throwaway binding.
vars = template_options[:assigns] || {}
b = template_options[:binding] || binding
vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
if b.respond_to?(:local_variable_set)
vars.each { |k,v| b.local_variable_set(k, v) }
else
vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
end
# Render the source file with the temporary binding.
ERB.new(file.read, nil, '-').result(b)

View File

@@ -1,6 +1,6 @@
require 'rbconfig'
require File.dirname(__FILE__) + '/template_runner'
require 'digest/md5'
require 'digest/md5'
require 'active_support/secure_random'
class AppGenerator < Rails::Generator::Base
@@ -110,12 +110,12 @@ class AppGenerator < Rails::Generator::Base
tmp/pids
).each { |path| m.directory(path) }
end
def create_root_files(m)
m.file "fresh_rakefile", "Rakefile"
m.file "README", "README"
end
def create_app_files(m)
m.file "helpers/application_controller.rb", "app/controllers/application_controller.rb"
m.file "helpers/application_helper.rb", "app/helpers/application_helper.rb"
@@ -138,7 +138,7 @@ class AppGenerator < Rails::Generator::Base
%w( server production development test ).each do |file|
m.file "configs/empty.log", "log/#{file}.log", :chmod => 0666
end
end
end
def create_public_files(m)
create_dispatch_files(m)
@@ -148,14 +148,14 @@ class AppGenerator < Rails::Generator::Base
create_rails_image(m)
create_javascript_files(m)
end
def create_script_files(m)
%w(
%w(
about console dbconsole destroy generate runner server plugin
performance/benchmarker performance/profiler
).each do |file|
m.file "bin/#{file}", "script/#{file}", {
:chmod => 0755,
m.file "bin/#{file}", "script/#{file}", {
:chmod => 0755,
:shebang => options[:shebang] == DEFAULT_SHEBANG ? nil : options[:shebang]
}
end
@@ -172,7 +172,7 @@ class AppGenerator < Rails::Generator::Base
:app_name => @app_name,
:socket => options[:db] == "mysql" ? mysql_socket_location : nil }
end
def create_routes_file(m)
m.file "configs/routes.rb", "config/routes.rb"
end
@@ -182,19 +182,19 @@ class AppGenerator < Rails::Generator::Base
end
def create_initializer_files(m)
%w(
backtrace_silencers
inflections
mime_types
%w(
backtrace_silencers
inflections
mime_types
new_rails_defaults
).each do |initializer|
m.file "configs/initializers/#{initializer}.rb", "config/initializers/#{initializer}.rb"
end
m.template "configs/initializers/session_store.rb", "config/initializers/session_store.rb",
m.template "configs/initializers/session_store.rb", "config/initializers/session_store.rb",
:assigns => { :app_name => @app_name, :app_secret => ActiveSupport::SecureRandom.hex(64) }
m.template "configs/initializers/cookie_verification_secret.rb", "config/initializers/cookie_verification_secret.rb",
m.template "configs/initializers/cookie_verification_secret.rb", "config/initializers/cookie_verification_secret.rb",
:assigns => { :app_secret => ActiveSupport::SecureRandom.hex(64) }
end
@@ -203,7 +203,7 @@ class AppGenerator < Rails::Generator::Base
end
def create_environment_files(m)
m.template "environments/environment.rb", "config/environment.rb",
m.template "environments/environment.rb", "config/environment.rb",
:assigns => { :freeze => options[:freeze] }
m.file "environments/boot.rb", "config/boot.rb"
@@ -218,9 +218,6 @@ class AppGenerator < Rails::Generator::Base
dispatcher_options = { :chmod => 0755, :shebang => options[:shebang] }
m.file "dispatches/config.ru", "config.ru"
m.file "dispatches/dispatch.rb", "public/dispatch.rb", dispatcher_options
m.file "dispatches/dispatch.rb", "public/dispatch.cgi", dispatcher_options
m.file "dispatches/dispatch.fcgi", "public/dispatch.fcgi", dispatcher_options
end
end
@@ -263,4 +260,4 @@ class AppGenerator < Rails::Generator::Base
"/opt/lampp/var/mysql/mysql.sock" # xampp for linux
].find { |f| File.exist?(f) } unless RUBY_PLATFORM =~ /(:?mswin|mingw)/
end
end
end

View File

@@ -53,7 +53,7 @@ namespace :db do
end
rescue
case config['adapter']
when /^mysql/
when /mysql/
@charset = ENV['CHARSET'] || 'utf8'
@collation = ENV['COLLATION'] || 'utf8_unicode_ci'
begin
@@ -159,7 +159,7 @@ namespace :db do
task :charset => :environment do
config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
case config['adapter']
when /^mysql/
when /mysql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.charset
when 'postgresql'
@@ -174,7 +174,7 @@ namespace :db do
task :collation => :environment do
config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
case config['adapter']
when /^mysql/
when /mysql/
ActiveRecord::Base.establish_connection(config)
puts ActiveRecord::Base.connection.collation
else
@@ -274,7 +274,7 @@ namespace :db do
task :dump => :environment do
abcs = ActiveRecord::Base.configurations
case abcs[RAILS_ENV]["adapter"]
when /^mysql/, "oci", "oracle"
when /mysql/, "oci", "oracle"
ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
File.open("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
when "postgresql"
@@ -320,7 +320,7 @@ namespace :db do
task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
abcs = ActiveRecord::Base.configurations
case abcs["test"]["adapter"]
when /^mysql/
when /mysql/
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
IO.readlines("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table|
@@ -354,7 +354,7 @@ namespace :db do
task :purge => :environment do
abcs = ActiveRecord::Base.configurations
case abcs["test"]["adapter"]
when /^mysql/
when /mysql/
ActiveRecord::Base.establish_connection(:test)
ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"])
when "postgresql"
@@ -408,7 +408,7 @@ end
def drop_database(config)
begin
case config['adapter']
when /^mysql/
when /mysql/
ActiveRecord::Base.establish_connection(config)
ActiveRecord::Base.connection.drop_database config['database']
when /^sqlite/

View File

@@ -97,7 +97,7 @@ namespace :rails do
local = Dir["#{local_base}/**/*"].reject { |path| File.directory?(path) }
edge = Dir["#{edge_base}/**/*"].reject { |path| File.directory?(path) }
edge.each do |script|
base_name = script[(edge_base.length+1)..-1]
next if base_name == "rails"
@@ -111,7 +111,7 @@ namespace :rails do
desc "Update your javascripts from your current rails install"
task :javascripts do
require 'railties_path'
require 'railties_path'
project_dir = RAILS_ROOT + '/public/javascripts/'
scripts = Dir[RAILTIES_PATH + '/html/javascripts/*.js']
scripts.reject!{|s| File.basename(s) == 'application.js'} if File.exist?(project_dir + 'application.js')
@@ -120,10 +120,10 @@ namespace :rails do
desc "Update config/boot.rb from your current rails install"
task :configs do
require 'railties_path'
require 'railties_path'
FileUtils.cp(RAILTIES_PATH + '/environments/boot.rb', RAILS_ROOT + '/config/boot.rb')
end
desc "Rename application.rb to application_controller.rb"
task :application_controller do
old_style = RAILS_ROOT + '/app/controllers/application.rb'
@@ -133,14 +133,11 @@ namespace :rails do
puts "#{old_style} has been renamed to #{new_style}, update your SCM as necessary"
end
end
desc "Generate dispatcher files in RAILS_ROOT/public"
task :generate_dispatchers do
require 'railties_path'
FileUtils.cp(RAILTIES_PATH + '/dispatches/config.ru', RAILS_ROOT + '/config.ru')
FileUtils.cp(RAILTIES_PATH + '/dispatches/dispatch.fcgi', RAILS_ROOT + '/public/dispatch.fcgi')
FileUtils.cp(RAILTIES_PATH + '/dispatches/dispatch.rb', RAILS_ROOT + '/public/dispatch.rb')
FileUtils.cp(RAILTIES_PATH + '/dispatches/dispatch.rb', RAILS_ROOT + '/public/dispatch.cgi')
end
end
end

View File

@@ -1,156 +0,0 @@
# Donated by Florian Gross
require 'webrick'
require 'cgi'
require 'stringio'
require 'dispatcher'
include WEBrick
class CGI #:nodoc:
def stdinput
@stdin || $stdin
end
def env_table
@env_table || ENV
end
def initialize(type = "query", table = nil, stdin = nil)
@env_table, @stdin = table, stdin
if defined?(MOD_RUBY) && !ENV.key?("GATEWAY_INTERFACE")
Apache.request.setup_cgi_env
end
extend QueryExtension
@multipart = false
if defined?(CGI_PARAMS)
warn "do not use CGI_PARAMS and CGI_COOKIES"
@params = CGI_PARAMS.dup
@cookies = CGI_COOKIES.dup
else
initialize_query() # set @params, @cookies
end
@output_cookies = nil
@output_hidden = nil
end
end
# A custom dispatch servlet for use with WEBrick. It dispatches requests
# (using the Rails Dispatcher) to the appropriate controller/action. By default,
# it restricts WEBrick to a managing a single Rails request at a time, but you
# can change this behavior by setting ActionController::Base.allow_concurrency
# to true.
class DispatchServlet < WEBrick::HTTPServlet::AbstractServlet
# Start the WEBrick server with the given options, mounting the
# DispatchServlet at <tt>/</tt>.
def self.dispatch(options = {})
Socket.do_not_reverse_lookup = true # patch for OS X
params = { :Port => options[:port].to_i,
:ServerType => options[:server_type],
:BindAddress => options[:ip] }
params[:MimeTypes] = options[:mime_types] if options[:mime_types]
server = WEBrick::HTTPServer.new(params)
server.mount('/', DispatchServlet, options)
trap("INT") { server.shutdown }
server.start
end
def initialize(server, options) #:nodoc:
@server_options = options
@file_handler = WEBrick::HTTPServlet::FileHandler.new(server, options[:server_root])
# Change to the RAILS_ROOT, since Webrick::Daemon.start does a Dir::cwd("/")
# OPTIONS['working_directory'] is an absolute path of the RAILS_ROOT, set in railties/lib/commands/servers/webrick.rb
Dir.chdir(OPTIONS['working_directory']) if defined?(OPTIONS) && File.directory?(OPTIONS['working_directory'])
super
end
def service(req, res) #:nodoc:
unless handle_file(req, res)
unless handle_dispatch(req, res)
raise WEBrick::HTTPStatus::NotFound, "`#{req.path}' not found."
end
end
end
def handle_file(req, res) #:nodoc:
begin
req = req.dup
path = req.path.dup
# Add .html if the last path piece has no . in it
path << '.html' if path != '/' && (%r{(^|/)[^./]+$} =~ path)
path.gsub!('+', ' ') # Unescape + since FileHandler doesn't do so.
req.instance_variable_set(:@path_info, path) # Set the modified path...
@file_handler.send(:service, req, res)
return true
rescue HTTPStatus::PartialContent, HTTPStatus::NotModified => err
res.set_error(err)
return true
rescue => err
return false
end
end
def handle_dispatch(req, res, origin = nil) #:nodoc:
data = StringIO.new
Dispatcher.dispatch(
CGI.new("query", create_env_table(req, origin), StringIO.new(req.body || "")),
ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS,
data
)
header, body = extract_header_and_body(data)
set_charset(header)
assign_status(res, header)
res.cookies.concat(header.delete('set-cookie') || [])
header.each { |key, val| res[key] = val.join(", ") }
res.body = body
return true
rescue => err
p err, err.backtrace
return false
end
private
def create_env_table(req, origin)
env = req.meta_vars.clone
env.delete "SCRIPT_NAME"
env["QUERY_STRING"] = req.request_uri.query
env["REQUEST_URI"] = origin if origin
return env
end
def extract_header_and_body(data)
data.rewind
data = data.read
raw_header, body = *data.split(/^[\xd\xa]{2}/on, 2)
header = WEBrick::HTTPUtils::parse_header(raw_header)
return header, body
end
def set_charset(header)
ct = header["content-type"]
if ct.any? { |x| x =~ /^text\// } && ! ct.any? { |x| x =~ /charset=/ }
ch = @server_options[:charset] || "UTF-8"
ct.find { |x| x =~ /^text\// } << ("; charset=" + ch)
end
end
def assign_status(res, header)
if /^(\d+)/ =~ header['status'][0]
res.status = $1.to_i
header.delete('status')
end
end
end

View File

@@ -1,266 +0,0 @@
require 'abstract_unit'
if RUBY_VERSION < '1.9.0'
uses_gem "fcgi", "0.8.7" do
require 'action_controller'
require 'fcgi_handler'
Dispatcher.middleware.clear
class RailsFCGIHandlerTest < Test::Unit::TestCase
def setup
@log = StringIO.new
@handler = RailsFCGIHandler.new(@log)
end
def test_process_restart
request = mock
FCGI.stubs(:each).yields(request)
@handler.expects(:process_request).once
@handler.expects(:dispatcher_error).never
@handler.expects(:when_ready).returns(:restart)
@handler.expects(:close_connection).with(request)
@handler.expects(:reload!).never
@handler.expects(:restart!)
@handler.process!
end
def test_process_exit
request = mock
FCGI.stubs(:each).yields(request)
@handler.expects(:process_request).once
@handler.expects(:dispatcher_error).never
@handler.expects(:when_ready).returns(:exit)
@handler.expects(:close_connection).with(request)
@handler.expects(:reload!).never
@handler.expects(:restart!).never
@handler.process!
end
def test_process_with_system_exit_exception
request = mock
FCGI.stubs(:each).yields(request)
@handler.expects(:process_request).once.raises(SystemExit)
@handler.stubs(:dispatcher_log)
@handler.expects(:dispatcher_log).with(:info, regexp_matches(/^stopping/))
@handler.expects(:dispatcher_error).never
@handler.expects(:when_ready).never
@handler.expects(:close_connection).never
@handler.expects(:reload!).never
@handler.expects(:restart!).never
@handler.process!
end
def test_restart_handler_outside_request
@handler.expects(:dispatcher_log).with(:info, "asked to restart ASAP")
@handler.expects(:restart!).once
@handler.send(:restart_handler, nil)
assert_equal nil, @handler.when_ready
end
def test_install_signal_handler_should_log_on_bad_signal
@handler.stubs(:trap).raises(ArgumentError)
@handler.expects(:dispatcher_log).with(:warn, "Ignoring unsupported signal CHEESECAKE.")
@handler.send(:install_signal_handler, "CHEESECAKE", nil)
end
def test_reload
@handler.expects(:restore!)
@handler.expects(:dispatcher_log).with(:info, "reloaded")
@handler.send(:reload!)
assert_nil @handler.when_ready
end
def test_reload_runs_gc_when_gc_request_period_set
@handler.expects(:run_gc!)
@handler.expects(:restore!)
@handler.expects(:dispatcher_log).with(:info, "reloaded")
@handler.gc_request_period = 10
@handler.send(:reload!)
end
def test_reload_doesnt_run_gc_if_gc_request_period_isnt_set
@handler.expects(:run_gc!).never
@handler.expects(:restore!)
@handler.expects(:dispatcher_log).with(:info, "reloaded")
@handler.send(:reload!)
end
def test_restart!
@handler.expects(:dispatcher_log).with(:info, "restarted")
@handler.expects(:exec).returns('restarted')
assert_equal 'restarted', @handler.send(:restart!)
end
def test_restore!
$".expects(:replace)
Dispatcher.expects(:reset_application!)
ActionController::Routing::Routes.expects(:reload)
@handler.send(:restore!)
end
def test_uninterrupted_processing
request = mock
FCGI.expects(:each).yields(request)
@handler.expects(:process_request).with(request)
@handler.process!
assert_nil @handler.when_ready
end
end
class RailsFCGIHandlerSignalsTest < Test::Unit::TestCase
class ::RailsFCGIHandler
attr_accessor :signal
alias_method :old_gc_countdown, :gc_countdown
def gc_countdown
signal ? Process.kill(signal, $$) : old_gc_countdown
end
end
def setup
@log = StringIO.new
@handler = RailsFCGIHandler.new(@log)
@dispatcher = mock
Dispatcher.stubs(:new).returns(@dispatcher)
end
def test_interrupted_via_HUP_when_not_in_request
request = mock
FCGI.expects(:each).once.yields(request)
@handler.expects(:signal).times(2).returns('HUP')
@handler.expects(:reload!).once
@handler.expects(:close_connection).never
@handler.expects(:exit).never
@handler.process!
assert_equal :reload, @handler.when_ready
end
def test_interrupted_via_USR1_when_not_in_request
request = mock
FCGI.expects(:each).once.yields(request)
@handler.expects(:signal).times(2).returns('USR1')
@handler.expects(:exit_handler).never
@handler.expects(:reload!).never
@handler.expects(:close_connection).with(request).once
@handler.expects(:exit).never
@handler.process!
assert_nil @handler.when_ready
end
def test_restart_via_USR2_when_in_request
request = mock
FCGI.expects(:each).once.yields(request)
@handler.expects(:signal).times(2).returns('USR2')
@handler.expects(:exit_handler).never
@handler.expects(:reload!).never
@handler.expects(:close_connection).with(request).once
@handler.expects(:exit).never
@handler.expects(:restart!).once
@handler.process!
assert_equal :restart, @handler.when_ready
end
def test_interrupted_via_TERM
request = mock
FCGI.expects(:each).once.yields(request)
::Rack::Handler::FastCGI.expects(:serve).once.returns('TERM')
@handler.expects(:reload!).never
@handler.expects(:close_connection).never
@handler.process!
assert_nil @handler.when_ready
end
def test_runtime_exception_in_fcgi
error = RuntimeError.new('foo')
FCGI.expects(:each).times(2).raises(error)
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^retrying/))
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^stopping/))
@handler.process!
end
def test_runtime_error_in_dispatcher
request = mock
error = RuntimeError.new('foo')
FCGI.expects(:each).once.yields(request)
::Rack::Handler::FastCGI.expects(:serve).once.raises(error)
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^unhandled/))
@handler.process!
end
def test_signal_exception_in_fcgi
error = SignalException.new('USR2')
FCGI.expects(:each).once.raises(error)
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^stopping/))
@handler.process!
end
def test_signal_exception_in_dispatcher
request = mock
error = SignalException.new('USR2')
FCGI.expects(:each).once.yields(request)
::Rack::Handler::FastCGI.expects(:serve).once.raises(error)
@handler.expects(:dispatcher_error).with(error, regexp_matches(/^stopping/))
@handler.process!
end
end
class RailsFCGIHandlerPeriodicGCTest < Test::Unit::TestCase
def setup
@log = StringIO.new
end
def teardown
GC.enable
end
def test_normal_gc
@handler = RailsFCGIHandler.new(@log)
assert_nil @handler.gc_request_period
# When GC is enabled, GC.disable disables and returns false.
assert_equal false, GC.disable
end
def test_periodic_gc
@handler = RailsFCGIHandler.new(@log, 10)
assert_equal 10, @handler.gc_request_period
request = mock
FCGI.expects(:each).times(10).yields(request)
@handler.expects(:run_gc!).never
9.times { @handler.process! }
@handler.expects(:run_gc!).once
@handler.process!
assert_nil @handler.when_ready
end
end
end # uses_gem "fcgi"
end # exclude 1.9

View File

@@ -3,5 +3,4 @@
set -x
set -e
script/cibuild-on 1.9.3-p231-tcs-github
script/cibuild-on 2.0.0-github
script/cibuild-on 2.1.0-github