mirror of
https://github.com/github/rails.git
synced 2026-01-13 00:28:26 -05:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4fdaf21b28 | ||
|
|
35b871fbcd | ||
|
|
a5697840d6 | ||
|
|
d0e554d231 | ||
|
|
d38b7664cc | ||
|
|
e4cd9caf02 | ||
|
|
89e4514704 | ||
|
|
0a0d975f51 | ||
|
|
62daf4cb6f | ||
|
|
24711e1e29 | ||
|
|
cf8f36930c | ||
|
|
d622643e47 | ||
|
|
3f0241a613 | ||
|
|
38a7432590 | ||
|
|
1220d3c3ed | ||
|
|
3d72818356 | ||
|
|
221477dc21 | ||
|
|
975155c110 | ||
|
|
2931987892 | ||
|
|
e3290b98dd | ||
|
|
20088080a5 | ||
|
|
24e348489d | ||
|
|
ba4f4f8a01 | ||
|
|
ccf254b6cb | ||
|
|
3766b1b377 | ||
|
|
d3f87776a3 | ||
|
|
18c7c1f753 | ||
|
|
f63b0340ff | ||
|
|
7224ee1419 | ||
|
|
0c52ae6df3 | ||
|
|
f8b7cd2df7 | ||
|
|
c73ba86136 | ||
|
|
98fa5dd465 | ||
|
|
fa41bedf6b | ||
|
|
0a8282c557 | ||
|
|
d4a4facfcc | ||
|
|
dd4146854a | ||
|
|
cedf026a14 | ||
|
|
7ac3b0fa4f | ||
|
|
31cd7ea26d | ||
|
|
df387ab385 | ||
|
|
0118959601 | ||
|
|
83448c7de5 | ||
|
|
8f99d00868 | ||
|
|
987b61bd1d | ||
|
|
f05e54a9f3 | ||
|
|
b9918117bb | ||
|
|
42f85d118d | ||
|
|
acb182d094 | ||
|
|
6e0fcb788d | ||
|
|
fed4fafa8a | ||
|
|
f699184047 | ||
|
|
55d6a9f2df | ||
|
|
e5bebc01a8 | ||
|
|
a019f07a39 | ||
|
|
d13866d75d | ||
|
|
dfa2f469a4 | ||
|
|
bf0d43bb77 | ||
|
|
72cebbcb59 | ||
|
|
379dd9071c | ||
|
|
a743f17dbd | ||
|
|
25b896611d | ||
|
|
b988837359 | ||
|
|
890aff3b9d |
@@ -5,3 +5,5 @@ gem install sqlite3 -v=1.3.7
|
|||||||
gem install rack -v=1.4.5
|
gem install rack -v=1.4.5
|
||||||
gem install erubis -v=2.7.0
|
gem install erubis -v=2.7.0
|
||||||
gem install json -v=1.8.0
|
gem install json -v=1.8.0
|
||||||
|
gem install i18n -v=0.6.9
|
||||||
|
gem install builder -v=3.2.2
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.3.14.github31
|
2.3.14.github40
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ module ActionController
|
|||||||
# TODO: Review explicit to see if they will automatically be handled by
|
# TODO: Review explicit to see if they will automatically be handled by
|
||||||
# the initilizer if they are really needed.
|
# the initilizer if they are really needed.
|
||||||
def self.load_all!
|
def self.load_all!
|
||||||
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
|
[Base, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
|
||||||
end
|
end
|
||||||
|
|
||||||
autoload :Base, 'action_controller/base'
|
autoload :Base, 'action_controller/base'
|
||||||
@@ -99,10 +99,6 @@ module ActionController
|
|||||||
autoload :CookieStore, 'action_controller/session/cookie_store'
|
autoload :CookieStore, 'action_controller/session/cookie_store'
|
||||||
autoload :MemCacheStore, 'action_controller/session/mem_cache_store'
|
autoload :MemCacheStore, 'action_controller/session/mem_cache_store'
|
||||||
end
|
end
|
||||||
|
|
||||||
# DEPRECATE: Remove CGI support
|
|
||||||
autoload :CgiRequest, 'action_controller/cgi_process'
|
|
||||||
autoload :CGIHandler, 'action_controller/cgi_process'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
autoload :Mime, 'action_controller/mime_type'
|
autoload :Mime, 'action_controller/mime_type'
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ module ActionController #:nodoc:
|
|||||||
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
||||||
|
|
||||||
logger.info(log_message)
|
logger.info(log_message)
|
||||||
response.headers["X-Runtime"] = "%.0f" % ms
|
|
||||||
else
|
else
|
||||||
perform_action_without_benchmark
|
perform_action_without_benchmark
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ module ActionController #:nodoc:
|
|||||||
if cache = read_fragment(name, options)
|
if cache = read_fragment(name, options)
|
||||||
buffer.safe_concat(cache.html_safe)
|
buffer.safe_concat(cache.html_safe)
|
||||||
else
|
else
|
||||||
pos = buffer.length
|
pos = buffer.bytesize
|
||||||
block.call
|
block.call
|
||||||
write_fragment(name, buffer[pos..-1], options)
|
write_fragment(name, buffer.byteslice(pos..-1), options)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
block.call
|
block.call
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
require 'action_controller/cgi_ext/stdinput'
|
|
||||||
require 'action_controller/cgi_ext/query_extension'
|
|
||||||
require 'action_controller/cgi_ext/cookie'
|
require 'action_controller/cgi_ext/cookie'
|
||||||
|
|
||||||
class CGI #:nodoc:
|
class CGI #:nodoc:
|
||||||
include ActionController::CgiExt::Stdinput
|
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
alias :escapeHTML_fail_on_nil :escapeHTML
|
alias :escapeHTML_fail_on_nil :escapeHTML
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
require 'delegate'
|
require 'delegate'
|
||||||
|
require 'cgi'
|
||||||
|
require 'cgi/cookie'
|
||||||
|
|
||||||
CGI.module_eval { remove_const "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
|
# * <tt>:secure</tt> - Whether this cookie is a secure cookie or not (defaults to
|
||||||
# +false+). Secure cookies are only transmitted to HTTPS servers.
|
# +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.
|
# * <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.
|
# These keywords correspond to attributes of the cookie object.
|
||||||
def initialize(name = '', *value)
|
def initialize(name = '', *value)
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -22,11 +22,6 @@ module ActionController
|
|||||||
end
|
end
|
||||||
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
|
# Add a preparation callback. Preparation callbacks are run before every
|
||||||
# request in development mode, and before the first request in production
|
# request in development mode, and before the first request in production
|
||||||
# mode.
|
# mode.
|
||||||
@@ -42,13 +37,7 @@ module ActionController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def run_prepare_callbacks
|
def run_prepare_callbacks
|
||||||
if defined?(Rails) && Rails.logger
|
new.send :run_callbacks, :prepare_dispatch
|
||||||
logger = Rails.logger
|
|
||||||
else
|
|
||||||
logger = Logger.new($stderr)
|
|
||||||
end
|
|
||||||
|
|
||||||
new(logger).send :run_callbacks, :prepare_dispatch
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def reload_application
|
def reload_application
|
||||||
@@ -75,10 +64,8 @@ module ActionController
|
|||||||
include ActiveSupport::Callbacks
|
include ActiveSupport::Callbacks
|
||||||
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
|
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
|
||||||
|
|
||||||
# DEPRECATE: Remove arguments, since they are only used by CGI
|
def initialize
|
||||||
def initialize(output = $stdout, request = nil, response = nil)
|
build_middleware_stack
|
||||||
@output = output
|
|
||||||
build_middleware_stack if @@cache_classes
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def dispatch
|
def dispatch
|
||||||
@@ -96,21 +83,11 @@ module ActionController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# DEPRECATE: Remove CGI support
|
|
||||||
def dispatch_cgi(cgi, session_options)
|
|
||||||
CGIHandler.dispatch_cgi(self, cgi, @output)
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
if @@cache_classes
|
if @@cache_classes
|
||||||
@app.call(env)
|
@app.call(env)
|
||||||
else
|
else
|
||||||
Reloader.run do
|
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)
|
@app.call(env)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -423,13 +423,13 @@ EOM
|
|||||||
|
|
||||||
# Override Rack's GET method to support indifferent access
|
# Override Rack's GET method to support indifferent access
|
||||||
def GET
|
def GET
|
||||||
@env["action_controller.request.query_parameters"] ||= normalize_parameters(super)
|
@env["action_controller.request.query_parameters"] ||= deep_munge(normalize_parameters(super) || {})
|
||||||
end
|
end
|
||||||
alias_method :query_parameters, :GET
|
alias_method :query_parameters, :GET
|
||||||
|
|
||||||
# Override Rack's POST method to support indifferent access
|
# Override Rack's POST method to support indifferent access
|
||||||
def POST
|
def POST
|
||||||
@env["action_controller.request.request_parameters"] ||= normalize_parameters(super)
|
@env["action_controller.request.request_parameters"] ||= deep_munge(normalize_parameters(super) || {})
|
||||||
end
|
end
|
||||||
alias_method :request_parameters, :POST
|
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))
|
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
|
||||||
end
|
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
|
# Convert nested Hashs to HashWithIndifferentAccess and replace
|
||||||
# file upload hashs with UploadedFile objects
|
# file upload hashs with UploadedFile objects
|
||||||
def normalize_parameters(value)
|
def normalize_parameters(value)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ module ActionController
|
|||||||
# Note that changing digest or secret invalidates all existing sessions!
|
# Note that changing digest or secret invalidates all existing sessions!
|
||||||
class CookieStore
|
class CookieStore
|
||||||
include AbstractStore::SessionUtils
|
include AbstractStore::SessionUtils
|
||||||
|
|
||||||
# Cookies can typically store 4096 bytes.
|
# Cookies can typically store 4096 bytes.
|
||||||
MAX = 4096
|
MAX = 4096
|
||||||
SECRET_MIN_LENGTH = 30 # characters
|
SECRET_MIN_LENGTH = 30 # characters
|
||||||
@@ -95,14 +95,21 @@ module ActionController
|
|||||||
|
|
||||||
def call(env)
|
def call(env)
|
||||||
prepare!(env)
|
prepare!(env)
|
||||||
|
|
||||||
status, headers, body = @app.call(env)
|
status, headers, body = @app.call(env)
|
||||||
|
|
||||||
session_data = env[ENV_SESSION_KEY]
|
session_data = env[ENV_SESSION_KEY]
|
||||||
options = env[ENV_SESSION_OPTIONS_KEY]
|
options = env[ENV_SESSION_OPTIONS_KEY]
|
||||||
request = ActionController::Request.new(env)
|
request = ActionController::Request.new(env)
|
||||||
|
|
||||||
if !(options[:secure] && !request.ssl?) && (!session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after])
|
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?
|
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
|
||||||
|
|
||||||
persistent_session_id!(session_data)
|
persistent_session_id!(session_data)
|
||||||
@@ -122,7 +129,7 @@ module ActionController
|
|||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def prepare!(env)
|
def prepare!(env)
|
||||||
env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env)
|
env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env)
|
||||||
env[ENV_SESSION_OPTIONS_KEY] = AbstractStore::OptionsHash.new(self, env, @default_options)
|
env[ENV_SESSION_OPTIONS_KEY] = AbstractStore::OptionsHash.new(self, env, @default_options)
|
||||||
@@ -133,7 +140,7 @@ module ActionController
|
|||||||
data = persistent_session_id!(data)
|
data = persistent_session_id!(data)
|
||||||
[data[:session_id], data]
|
[data[:session_id], data]
|
||||||
end
|
end
|
||||||
|
|
||||||
def extract_session_id(env)
|
def extract_session_id(env)
|
||||||
if data = unpacked_cookie_data(env)
|
if data = unpacked_cookie_data(env)
|
||||||
persistent_session_id!(data) unless data.empty?
|
persistent_session_id!(data) unless data.empty?
|
||||||
|
|||||||
@@ -768,7 +768,11 @@ module ActionView
|
|||||||
options = options.stringify_keys
|
options = options.stringify_keys
|
||||||
tag_value = options.delete("value")
|
tag_value = options.delete("value")
|
||||||
name_and_id = options.dup
|
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)
|
add_default_name_and_id_for_value(tag_value, name_and_id)
|
||||||
options.delete("index")
|
options.delete("index")
|
||||||
options["for"] ||= name_and_id["id"]
|
options["for"] ||= name_and_id["id"]
|
||||||
@@ -928,15 +932,15 @@ module ActionView
|
|||||||
|
|
||||||
def add_default_name_and_id(options)
|
def add_default_name_and_id(options)
|
||||||
if options.has_key?("index")
|
if options.has_key?("index")
|
||||||
options["name"] ||= tag_name_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"])
|
options["id"] = tag_id_with_index(options["index"]) unless options.has_key?("id")
|
||||||
options.delete("index")
|
options.delete("index")
|
||||||
elsif defined?(@auto_index)
|
elsif defined?(@auto_index)
|
||||||
options["name"] ||= tag_name_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)
|
options["id"] = tag_id_with_index(@auto_index) unless options.has_key?("id")
|
||||||
else
|
else
|
||||||
options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
|
options["name"] = tag_name + (options.has_key?('multiple') ? '[]' : '') unless options.has_key?("name")
|
||||||
options["id"] ||= tag_id
|
options["id"] = tag_id unless options.has_key?("id")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,8 @@ module ActionView
|
|||||||
def number_to_currency(number, options = {})
|
def number_to_currency(number, options = {})
|
||||||
options.symbolize_keys!
|
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 {}
|
defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
|
||||||
currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {}
|
currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {}
|
||||||
defaults = defaults.merge(currency)
|
defaults = defaults.merge(currency)
|
||||||
@@ -85,11 +87,13 @@ module ActionView
|
|||||||
separator = '' if precision == 0
|
separator = '' if precision == 0
|
||||||
|
|
||||||
begin
|
begin
|
||||||
format.gsub(/%n/, number_with_precision(number,
|
value = number_with_precision(number,
|
||||||
:precision => precision,
|
:precision => precision,
|
||||||
:delimiter => delimiter,
|
:delimiter => delimiter,
|
||||||
:separator => separator)
|
: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
|
rescue
|
||||||
number
|
number
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ module ActionView
|
|||||||
src << "@output_buffer.safe_append='"
|
src << "@output_buffer.safe_append='"
|
||||||
src << "\n" * @newline_pending if @newline_pending > 0
|
src << "\n" * @newline_pending if @newline_pending > 0
|
||||||
src << escape_text(text)
|
src << escape_text(text)
|
||||||
src << "';"
|
src << "'.freeze;"
|
||||||
|
|
||||||
@newline_pending = 0
|
@newline_pending = 0
|
||||||
end
|
end
|
||||||
@@ -63,7 +63,7 @@ module ActionView
|
|||||||
|
|
||||||
def flush_newline_if_pending(src)
|
def flush_newline_if_pending(src)
|
||||||
if @newline_pending > 0
|
if @newline_pending > 0
|
||||||
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';"
|
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
|
||||||
@newline_pending = 0
|
@newline_pending = 0
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -622,6 +622,19 @@ class FragmentCachingTest < ActionController::TestCase
|
|||||||
assert_equal 'generated till now -> fragment content', buffer
|
assert_equal 'generated till now -> fragment content', buffer
|
||||||
end
|
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
|
def test_html_safety
|
||||||
assert_nil @store.read('views/name')
|
assert_nil @store.read('views/name')
|
||||||
content = 'value'.html_safe
|
content = 'value'.html_safe
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class DispatcherTest < Test::Unit::TestCase
|
|||||||
def test_rebuilds_middleware_stack_on_every_request_if_in_loading_mode
|
def test_rebuilds_middleware_stack_on_every_request_if_in_loading_mode
|
||||||
dispatcher = create_dispatcher(false)
|
dispatcher = create_dispatcher(false)
|
||||||
dispatcher.instance_variable_set(:"@app", lambda { |env| })
|
dispatcher.instance_variable_set(:"@app", lambda { |env| })
|
||||||
dispatcher.expects(:build_middleware_stack).twice
|
dispatcher.expects(:build_middleware_stack).never
|
||||||
dispatcher.call(nil)
|
dispatcher.call(nil)
|
||||||
Reloader.default_lock.unlock
|
Reloader.default_lock.unlock
|
||||||
dispatcher.call(nil)
|
dispatcher.call(nil)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ class BaseRackTest < ActiveSupport::TestCase
|
|||||||
@env = {
|
@env = {
|
||||||
"HTTP_MAX_FORWARDS" => "10",
|
"HTTP_MAX_FORWARDS" => "10",
|
||||||
"SERVER_NAME" => "glu.ttono.us",
|
"SERVER_NAME" => "glu.ttono.us",
|
||||||
"FCGI_ROLE" => "RESPONDER",
|
|
||||||
"AUTH_TYPE" => "Basic",
|
"AUTH_TYPE" => "Basic",
|
||||||
"HTTP_X_FORWARDED_HOST" => "glu.ttono.us",
|
"HTTP_X_FORWARDED_HOST" => "glu.ttono.us",
|
||||||
"HTTP_ACCEPT_CHARSET" => "UTF-8",
|
"HTTP_ACCEPT_CHARSET" => "UTF-8",
|
||||||
|
|||||||
@@ -175,6 +175,10 @@ class FormHelperTest < ActionView::TestCase
|
|||||||
I18n.locale = old_locale
|
I18n.locale = old_locale
|
||||||
end
|
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
|
def test_label_with_for_attribute_as_symbol
|
||||||
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for"))
|
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for"))
|
||||||
end
|
end
|
||||||
@@ -274,6 +278,11 @@ class FormHelperTest < ActionView::TestCase
|
|||||||
hidden_field("post", "title", :value => "Something Else")
|
hidden_field("post", "title", :value => "Something Else")
|
||||||
end
|
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
|
def test_check_box
|
||||||
assert_dom_equal(
|
assert_dom_equal(
|
||||||
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
|
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
|
||||||
|
|||||||
@@ -3,6 +3,12 @@ require 'abstract_unit'
|
|||||||
class NumberHelperTest < ActionView::TestCase
|
class NumberHelperTest < ActionView::TestCase
|
||||||
tests ActionView::Helpers::NumberHelper
|
tests ActionView::Helpers::NumberHelper
|
||||||
|
|
||||||
|
def test_number_helpers_escape_delimiter_and_separator
|
||||||
|
assert_equal "$1<script></script>01", number_to_currency(1.01, :separator => "<script></script>")
|
||||||
|
assert_equal "$1<script></script>000.00", number_to_currency(1000, :delimiter => "<script></script>")
|
||||||
|
assert_equal "<script>1,000.00$</script>", number_to_currency(1000, :format => "<script>%n%u</script>")
|
||||||
|
end
|
||||||
|
|
||||||
def test_number_to_phone
|
def test_number_to_phone
|
||||||
assert_equal("555-1234", number_to_phone(5551234))
|
assert_equal("555-1234", number_to_phone(5551234))
|
||||||
assert_equal("800-555-1212", number_to_phone(8005551212))
|
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,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,892", number_to_currency(1234567891.50, {:precision => 0}))
|
||||||
assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1}))
|
assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1}))
|
||||||
assert_equal("£1234567890,50", number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""}))
|
assert_equal("£1234567890,50", number_to_currency(1234567890.50, {:unit => raw("£"), :separator => ",", :delimiter => ""}))
|
||||||
|
assert_equal("&pound;1234567890,50", number_to_currency(1234567890.50, {:unit => "£", :separator => ",", :delimiter => ""}))
|
||||||
assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50"))
|
assert_equal("$1,234,567,890.50", number_to_currency("1234567890.50"))
|
||||||
assert_equal("1,234,567,890.50 Kč", number_to_currency("1234567890.50", {:unit => "Kč", :format => "%n %u"}))
|
assert_equal("1,234,567,890.50 Kč", number_to_currency("1234567890.50", {:unit => raw("Kč"), :format => "%n %u"}))
|
||||||
#assert_equal("$x.", number_to_currency("x")) # fails due to API consolidation
|
#assert_equal("$x.", number_to_currency("x")) # fails due to API consolidation
|
||||||
assert_equal("$x", number_to_currency("x"))
|
assert_equal("$x", number_to_currency("x"))
|
||||||
assert_nil number_to_currency(nil)
|
assert_nil number_to_currency(nil)
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ module RenderTestCases
|
|||||||
|
|
||||||
# Reload and register danish language for testing
|
# Reload and register danish language for testing
|
||||||
I18n.reload!
|
I18n.reload!
|
||||||
I18n.backend.store_translations 'da', {}
|
I18n.backend.store_translations 'da', 'da' => {}
|
||||||
I18n.backend.store_translations 'pt-BR', {}
|
I18n.backend.store_translations 'pt-BR', 'pt-BR' => {}
|
||||||
|
|
||||||
# Ensure original are still the same since we are reindexing view paths
|
# Ensure original are still the same since we are reindexing view paths
|
||||||
assert_equal ORIGINAL_LOCALES, I18n.available_locales.map(&:to_s).sort
|
assert_equal ORIGINAL_LOCALES, I18n.available_locales.map(&:to_s).sort
|
||||||
|
|||||||
30
activesupport/build_marshalled_tzinfo_data.rb
Normal file
30
activesupport/build_marshalled_tzinfo_data.rb
Normal 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}"
|
||||||
@@ -1,11 +1,6 @@
|
|||||||
# Prefer gems to the bundled libs.
|
# Prefer gems to the bundled libs.
|
||||||
require 'rubygems'
|
require 'rubygems'
|
||||||
|
|
||||||
begin
|
|
||||||
gem 'builder', '~> 2.1.2'
|
|
||||||
rescue Gem::LoadError
|
|
||||||
$:.unshift "#{File.dirname(__FILE__)}/vendor/builder-2.1.2"
|
|
||||||
end
|
|
||||||
require 'builder'
|
require 'builder'
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@@ -14,17 +9,8 @@ rescue Gem::LoadError
|
|||||||
$:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.7.4"
|
$:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.7.4"
|
||||||
end
|
end
|
||||||
|
|
||||||
begin
|
$:.unshift "#{File.dirname(__FILE__)}/vendor/tzinfo-0.3.12"
|
||||||
gem 'tzinfo', '~> 0.3.12'
|
|
||||||
rescue Gem::LoadError
|
|
||||||
$:.unshift "#{File.dirname(__FILE__)}/vendor/tzinfo-0.3.12"
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
|
||||||
gem 'i18n', '>= 0.4.1'
|
|
||||||
rescue Gem::LoadError
|
|
||||||
$:.unshift "#{File.dirname(__FILE__)}/vendor/i18n-0.4.1"
|
|
||||||
end
|
|
||||||
require 'i18n'
|
require 'i18n'
|
||||||
|
|
||||||
module I18n
|
module I18n
|
||||||
|
|||||||
@@ -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'
|
|
||||||
@@ -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
|
|
||||||
@@ -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 => '&', # ampersand
|
|
||||||
60 => '<', # left angle bracket
|
|
||||||
62 => '>', # 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
|
|
||||||
@@ -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{"}, '"') # " 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
|
|
||||||
@@ -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
|
|
||||||
@@ -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 & 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 <,
|
|
||||||
# > and & 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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
module I18n
|
|
||||||
module Locale
|
|
||||||
autoload :Fallbacks, 'i18n/locale/fallbacks'
|
|
||||||
autoload :Tag, 'i18n/locale/tag'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
module I18n
|
|
||||||
VERSION = "0.4.1"
|
|
||||||
end
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
#--
|
#--
|
||||||
# Copyright (c) 2005-2006 Philip Ross
|
# Copyright (c) 2005-2006 Philip Ross
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
# in the Software without restriction, including without limitation the rights
|
# in the Software without restriction, including without limitation the rights
|
||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
# copies of the Software, and to permit persons to whom the Software is
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
# furnished to do so, subject to the following conditions:
|
# furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in all
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
# copies or substantial portions of the Software.
|
# copies or substantial portions of the Software.
|
||||||
#
|
#
|
||||||
@@ -30,4 +30,5 @@ require 'tzinfo/timezone'
|
|||||||
# require 'tzinfo/tzdataparser'
|
# require 'tzinfo/tzdataparser'
|
||||||
# require 'tzinfo/timezone_proxy'
|
# require 'tzinfo/timezone_proxy'
|
||||||
require 'tzinfo/data_timezone'
|
require 'tzinfo/data_timezone'
|
||||||
require 'tzinfo/linked_timezone'
|
require 'tzinfo/linked_timezone'
|
||||||
|
require 'tzinfo/definitions'
|
||||||
|
|||||||
BIN
activesupport/lib/active_support/vendor/tzinfo-0.3.12/tzinfo/definitions.dump
vendored
Normal file
BIN
activesupport/lib/active_support/vendor/tzinfo-0.3.12/tzinfo/definitions.dump
vendored
Normal file
Binary file not shown.
30
activesupport/lib/active_support/vendor/tzinfo-0.3.12/tzinfo/definitions.rb
vendored
Normal file
30
activesupport/lib/active_support/vendor/tzinfo-0.3.12/tzinfo/definitions.rb
vendored
Normal 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
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
#--
|
#--
|
||||||
# Copyright (c) 2005-2006 Philip Ross
|
# Copyright (c) 2005-2006 Philip Ross
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
# of this software and associated documentation files (the "Software"), to deal
|
# of this software and associated documentation files (the "Software"), to deal
|
||||||
# in the Software without restriction, including without limitation the rights
|
# in the Software without restriction, including without limitation the rights
|
||||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
# copies of the Software, and to permit persons to whom the Software is
|
# copies of the Software, and to permit persons to whom the Software is
|
||||||
# furnished to do so, subject to the following conditions:
|
# furnished to do so, subject to the following conditions:
|
||||||
#
|
#
|
||||||
# The above copyright notice and this permission notice shall be included in all
|
# The above copyright notice and this permission notice shall be included in all
|
||||||
# copies or substantial portions of the Software.
|
# copies or substantial portions of the Software.
|
||||||
#
|
#
|
||||||
@@ -24,31 +24,32 @@ require 'date'
|
|||||||
# require 'tzinfo/country'
|
# require 'tzinfo/country'
|
||||||
require 'tzinfo/time_or_datetime'
|
require 'tzinfo/time_or_datetime'
|
||||||
require 'tzinfo/timezone_period'
|
require 'tzinfo/timezone_period'
|
||||||
|
require 'tzinfo/definitions'
|
||||||
|
|
||||||
module TZInfo
|
module TZInfo
|
||||||
# Indicate a specified time in a local timezone has more than one
|
# 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
|
# 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.
|
# means to resolve the ambiguity.
|
||||||
class AmbiguousTime < StandardError
|
class AmbiguousTime < StandardError
|
||||||
end
|
end
|
||||||
|
|
||||||
# Thrown to indicate that no TimezonePeriod matching a given time could be found.
|
# Thrown to indicate that no TimezonePeriod matching a given time could be found.
|
||||||
class PeriodNotFound < StandardError
|
class PeriodNotFound < StandardError
|
||||||
end
|
end
|
||||||
|
|
||||||
# Thrown by Timezone#get if the identifier given is not valid.
|
# Thrown by Timezone#get if the identifier given is not valid.
|
||||||
class InvalidTimezoneIdentifier < StandardError
|
class InvalidTimezoneIdentifier < StandardError
|
||||||
end
|
end
|
||||||
|
|
||||||
# Thrown if an attempt is made to use a timezone created with Timezone.new(nil).
|
# Thrown if an attempt is made to use a timezone created with Timezone.new(nil).
|
||||||
class UnknownTimezone < StandardError
|
class UnknownTimezone < StandardError
|
||||||
end
|
end
|
||||||
|
|
||||||
# Timezone is the base class of all timezones. It provides a factory method
|
# 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
|
# 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:
|
# and the local time for the zone. For example:
|
||||||
#
|
#
|
||||||
# tz = TZInfo::Timezone.get('America/New_York')
|
# 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.local_to_utc(Time.utc(2005,8,29,11,35,0)).to_s
|
||||||
# puts tz.utc_to_local(1125315300).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.
|
# passed.
|
||||||
#
|
#
|
||||||
# The timezone information all comes from the tz database
|
# The timezone information all comes from the tz database
|
||||||
# (see http://www.twinsun.com/tz/tz-link.htm)
|
# (see http://www.twinsun.com/tz/tz-link.htm)
|
||||||
class Timezone
|
class Timezone
|
||||||
include Comparable
|
include Comparable
|
||||||
|
|
||||||
# Cache of loaded zones by identifier to avoid using require if a zone
|
# Cache of loaded zones by identifier to avoid using require if a zone
|
||||||
# has already been loaded.
|
# has already been loaded.
|
||||||
@@loaded_zones = {}
|
@@loaded_zones = {}
|
||||||
|
|
||||||
# Whether the timezones index has been loaded yet.
|
# Whether the timezones index has been loaded yet.
|
||||||
@@index_loaded = false
|
@@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").
|
# "America/Chicago" or "UTC").
|
||||||
#
|
#
|
||||||
# Raises InvalidTimezoneIdentifier if the timezone couldn't be found.
|
# Raises InvalidTimezoneIdentifier if the timezone couldn't be found.
|
||||||
def self.get(identifier)
|
def self.get(identifier)
|
||||||
instance = @@loaded_zones[identifier]
|
instance = @@loaded_zones[identifier]
|
||||||
unless instance
|
|
||||||
|
unless instance
|
||||||
raise InvalidTimezoneIdentifier, 'Invalid identifier' if identifier !~ /^[A-z0-9\+\-_]+(\/[A-z0-9\+\-_]+)*$/
|
raise InvalidTimezoneIdentifier, 'Invalid identifier' if identifier !~ /^[A-z0-9\+\-_]+(\/[A-z0-9\+\-_]+)*$/
|
||||||
identifier = identifier.gsub(/-/, '__m__').gsub(/\+/, '__p__')
|
identifier = identifier.gsub(/-/, '__m__').gsub(/\+/, '__p__')
|
||||||
begin
|
begin
|
||||||
# Use a temporary variable to avoid an rdoc warning
|
TZInfo::Definitions.load_all!
|
||||||
file = "tzinfo/definitions/#{identifier}"
|
|
||||||
require file
|
|
||||||
|
|
||||||
m = Definitions
|
m = Definitions
|
||||||
identifier.split(/\//).each {|part|
|
identifier.split(/\//).each {|part|
|
||||||
m = m.const_get(part)
|
m = m.const_get(part)
|
||||||
}
|
}
|
||||||
|
|
||||||
info = m.get
|
info = m.get
|
||||||
|
|
||||||
# Could make Timezone subclasses register an interest in an info
|
# Could make Timezone subclasses register an interest in an info
|
||||||
# type. Since there are currently only two however, there isn't
|
# type. Since there are currently only two however, there isn't
|
||||||
# much point.
|
# much point.
|
||||||
@@ -102,35 +102,35 @@ module TZInfo
|
|||||||
else
|
else
|
||||||
raise InvalidTimezoneIdentifier, "No handler for info type #{info.class}"
|
raise InvalidTimezoneIdentifier, "No handler for info type #{info.class}"
|
||||||
end
|
end
|
||||||
|
|
||||||
@@loaded_zones[instance.identifier] = instance
|
@@loaded_zones[instance.identifier] = instance
|
||||||
rescue LoadError, NameError => e
|
rescue LoadError, NameError => e
|
||||||
raise InvalidTimezoneIdentifier, e.message
|
raise InvalidTimezoneIdentifier, e.message
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
instance
|
instance
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a proxy for the Timezone with the given identifier. The proxy
|
# 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
|
# 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
|
# find a period or convert a time. get_proxy will not validate the
|
||||||
# identifier. If an invalid identifier is specified, no exception will be
|
# identifier. If an invalid identifier is specified, no exception will be
|
||||||
# raised until the proxy is used.
|
# raised until the proxy is used.
|
||||||
def self.get_proxy(identifier)
|
def self.get_proxy(identifier)
|
||||||
TimezoneProxy.new(identifier)
|
TimezoneProxy.new(identifier)
|
||||||
end
|
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.
|
# should always be passed in when called externally.
|
||||||
def self.new(identifier = nil)
|
def self.new(identifier = nil)
|
||||||
if identifier
|
if identifier
|
||||||
get(identifier)
|
get(identifier)
|
||||||
else
|
else
|
||||||
super()
|
super()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns an array containing all the available Timezones.
|
# Returns an array containing all the available Timezones.
|
||||||
#
|
#
|
||||||
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
|
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
|
||||||
@@ -138,14 +138,14 @@ module TZInfo
|
|||||||
def self.all
|
def self.all
|
||||||
get_proxies(all_identifiers)
|
get_proxies(all_identifiers)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns an array containing the identifiers of all the available
|
# Returns an array containing the identifiers of all the available
|
||||||
# Timezones.
|
# Timezones.
|
||||||
def self.all_identifiers
|
def self.all_identifiers
|
||||||
load_index
|
load_index
|
||||||
Indexes::Timezones.timezones
|
Indexes::Timezones.timezones
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns an array containing all the available Timezones that are based
|
# Returns an array containing all the available Timezones that are based
|
||||||
# on data (are not links to other Timezones).
|
# on data (are not links to other Timezones).
|
||||||
#
|
#
|
||||||
@@ -154,44 +154,44 @@ module TZInfo
|
|||||||
def self.all_data_zones
|
def self.all_data_zones
|
||||||
get_proxies(all_data_zone_identifiers)
|
get_proxies(all_data_zone_identifiers)
|
||||||
end
|
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)..
|
# Timezones that are based on data (are not links to other Timezones)..
|
||||||
def self.all_data_zone_identifiers
|
def self.all_data_zone_identifiers
|
||||||
load_index
|
load_index
|
||||||
Indexes::Timezones.data_timezones
|
Indexes::Timezones.data_timezones
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns an array containing all the available Timezones that are links
|
# Returns an array containing all the available Timezones that are links
|
||||||
# to other Timezones.
|
# to other Timezones.
|
||||||
#
|
#
|
||||||
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
|
# 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_linked_zones
|
def self.all_linked_zones
|
||||||
get_proxies(all_linked_zone_identifiers)
|
get_proxies(all_linked_zone_identifiers)
|
||||||
end
|
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.
|
# Timezones that are links to other Timezones.
|
||||||
def self.all_linked_zone_identifiers
|
def self.all_linked_zone_identifiers
|
||||||
load_index
|
load_index
|
||||||
Indexes::Timezones.linked_timezones
|
Indexes::Timezones.linked_timezones
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns all the Timezones defined for all Countries. This is not the
|
# 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').
|
# 'Etc/GMT').
|
||||||
#
|
#
|
||||||
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
|
# 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
|
def self.all_country_zones
|
||||||
Country.all_codes.inject([]) {|zones,country|
|
Country.all_codes.inject([]) {|zones,country|
|
||||||
zones += Country.get(country).zones
|
zones += Country.get(country).zones
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns all the zone identifiers defined for all Countries. This is not the
|
# 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
|
# 'Etc/GMT'). You can obtain a Timezone instance for a given identifier
|
||||||
# with the get method.
|
# with the get method.
|
||||||
def self.all_country_zone_identifiers
|
def self.all_country_zone_identifiers
|
||||||
@@ -199,8 +199,8 @@ module TZInfo
|
|||||||
zones += Country.get(country).zone_identifiers
|
zones += Country.get(country).zone_identifiers
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns all US Timezone instances. A shortcut for
|
# Returns all US Timezone instances. A shortcut for
|
||||||
# TZInfo::Country.get('US').zones.
|
# TZInfo::Country.get('US').zones.
|
||||||
#
|
#
|
||||||
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
|
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
|
||||||
@@ -208,35 +208,35 @@ module TZInfo
|
|||||||
def self.us_zones
|
def self.us_zones
|
||||||
Country.get('US').zones
|
Country.get('US').zones
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns all US zone identifiers. A shortcut for
|
# Returns all US zone identifiers. A shortcut for
|
||||||
# TZInfo::Country.get('US').zone_identifiers.
|
# TZInfo::Country.get('US').zone_identifiers.
|
||||||
def self.us_zone_identifiers
|
def self.us_zone_identifiers
|
||||||
Country.get('US').zone_identifiers
|
Country.get('US').zone_identifiers
|
||||||
end
|
end
|
||||||
|
|
||||||
# The identifier of the timezone, e.g. "Europe/Paris".
|
# The identifier of the timezone, e.g. "Europe/Paris".
|
||||||
def identifier
|
def identifier
|
||||||
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
|
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
|
||||||
end
|
end
|
||||||
|
|
||||||
# An alias for identifier.
|
# An alias for identifier.
|
||||||
def name
|
def name
|
||||||
# Don't use alias, as identifier gets overridden.
|
# Don't use alias, as identifier gets overridden.
|
||||||
identifier
|
identifier
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a friendlier version of the identifier.
|
# Returns a friendlier version of the identifier.
|
||||||
def to_s
|
def to_s
|
||||||
friendly_identifier
|
friendly_identifier
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns internal object state as a programmer-readable string.
|
# Returns internal object state as a programmer-readable string.
|
||||||
def inspect
|
def inspect
|
||||||
"#<#{self.class}: #{identifier}>"
|
"#<#{self.class}: #{identifier}>"
|
||||||
end
|
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
|
# omit the first part of the identifier (typically a region name) where
|
||||||
# there is more than one part.
|
# 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(false) #=> "Europe - Paris"
|
||||||
# Timezone.get('Europe/Paris').friendly_identifier(true) #=> "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(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)
|
def friendly_identifier(skip_first_part = false)
|
||||||
parts = identifier.split('/')
|
parts = identifier.split('/')
|
||||||
if parts.empty?
|
if parts.empty?
|
||||||
# shouldn't happen
|
# shouldn't happen
|
||||||
identifier
|
identifier
|
||||||
elsif parts.length == 1
|
elsif parts.length == 1
|
||||||
parts[0]
|
parts[0]
|
||||||
else
|
else
|
||||||
if skip_first_part
|
if skip_first_part
|
||||||
@@ -259,47 +259,47 @@ module TZInfo
|
|||||||
else
|
else
|
||||||
result = parts[0] + ' - '
|
result = parts[0] + ' - '
|
||||||
end
|
end
|
||||||
|
|
||||||
parts[1, parts.length - 1].reverse_each {|part|
|
parts[1, parts.length - 1].reverse_each {|part|
|
||||||
part.gsub!(/_/, ' ')
|
part.gsub!(/_/, ' ')
|
||||||
|
|
||||||
if part.index(/[a-z]/)
|
if part.index(/[a-z]/)
|
||||||
# Missing a space if a lower case followed by an upper case and the
|
# Missing a space if a lower case followed by an upper case and the
|
||||||
# name isn't McXxxx.
|
# name isn't McXxxx.
|
||||||
part.gsub!(/([^M][a-z])([A-Z])/, '\1 \2')
|
part.gsub!(/([^M][a-z])([A-Z])/, '\1 \2')
|
||||||
part.gsub!(/([M][a-bd-z])([A-Z])/, '\1 \2')
|
part.gsub!(/([M][a-bd-z])([A-Z])/, '\1 \2')
|
||||||
|
|
||||||
# Missing an apostrophe if two consecutive upper case characters.
|
# Missing an apostrophe if two consecutive upper case characters.
|
||||||
part.gsub!(/([A-Z])([A-Z])/, '\1\'\2')
|
part.gsub!(/([A-Z])([A-Z])/, '\1\'\2')
|
||||||
end
|
end
|
||||||
|
|
||||||
result << part
|
result << part
|
||||||
result << ', '
|
result << ', '
|
||||||
}
|
}
|
||||||
|
|
||||||
result.slice!(result.length - 2, 2)
|
result.slice!(result.length - 2, 2)
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the TimezonePeriod for the given UTC time. utc can either be
|
# Returns the TimezonePeriod for the given UTC time. utc can either be
|
||||||
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
|
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
|
||||||
# information in utc is ignored (it is treated as a UTC time).
|
# information in utc is ignored (it is treated as a UTC time).
|
||||||
def period_for_utc(utc)
|
def period_for_utc(utc)
|
||||||
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
|
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the set of TimezonePeriod instances that are valid for the given
|
# 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.
|
# period_for_local instead and specify how ambiguities should be resolved.
|
||||||
# Returns an empty array if no periods are found for the given time.
|
# Returns an empty array if no periods are found for the given time.
|
||||||
def periods_for_local(local)
|
def periods_for_local(local)
|
||||||
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
|
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the TimezonePeriod for the given local time. local can either be
|
# Returns the TimezonePeriod for the given local time. local can either be
|
||||||
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
|
# 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
|
# information in local is ignored (it is treated as a time in the current
|
||||||
# timezone).
|
# timezone).
|
||||||
#
|
#
|
||||||
# Warning: There are local times that have no equivalent UTC times (e.g.
|
# 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
|
# In the second case (more than one equivalent UTC time), an AmbiguousTime
|
||||||
# exception will be raised unless the optional dst parameter or block
|
# 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
|
# 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,
|
# 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))
|
# Timezone.get('America/New_York').period_for_local(DateTime.new(2004,10,31,1,30,0))
|
||||||
#
|
#
|
||||||
# would raise an AmbiguousTime exception.
|
# 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
|
# October 2004. Specifying dst=false would return the standard period
|
||||||
# from October 2004 to April 2005.
|
# 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
|
# 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
|
# 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
|
# return a single period or return nil or an empty array
|
||||||
# to cause an AmbiguousTime exception to be raised.
|
# 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)
|
results = periods_for_local(local)
|
||||||
|
|
||||||
if results.empty?
|
if results.empty?
|
||||||
raise PeriodNotFound
|
raise PeriodNotFound
|
||||||
elsif results.size < 2
|
elsif results.size < 2
|
||||||
results.first
|
results.first
|
||||||
else
|
else
|
||||||
# ambiguous result try to resolve
|
# ambiguous result try to resolve
|
||||||
|
|
||||||
if !dst.nil?
|
if !dst.nil?
|
||||||
matches = results.find_all {|period| period.dst? == dst}
|
matches = results.find_all {|period| period.dst? == dst}
|
||||||
results = matches if !matches.empty?
|
results = matches if !matches.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
if results.size < 2
|
if results.size < 2
|
||||||
results.first
|
results.first
|
||||||
else
|
else
|
||||||
# still ambiguous, try the block
|
# still ambiguous, try the block
|
||||||
|
|
||||||
if block_given?
|
if block_given?
|
||||||
results = yield results
|
results = yield results
|
||||||
end
|
end
|
||||||
|
|
||||||
if results.is_a?(TimezonePeriod)
|
if results.is_a?(TimezonePeriod)
|
||||||
results
|
results
|
||||||
elsif results && results.size == 1
|
elsif results && results.size == 1
|
||||||
results.first
|
results.first
|
||||||
else
|
else
|
||||||
raise AmbiguousTime, "#{local} is an ambiguous local time."
|
raise AmbiguousTime, "#{local} is an ambiguous local time."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Converts a time in UTC to the local timezone. utc can either be
|
# 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
|
# 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).
|
# a UTC time).
|
||||||
def utc_to_local(utc)
|
def utc_to_local(utc)
|
||||||
TimeOrDateTime.wrap(utc) {|wrapped|
|
TimeOrDateTime.wrap(utc) {|wrapped|
|
||||||
period_for_utc(wrapped).to_local(wrapped)
|
period_for_utc(wrapped).to_local(wrapped)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Converts a time in the local timezone to UTC. local can either be
|
# 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
|
# 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
|
# 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
|
# In the second case (more than one equivalent UTC time), an AmbiguousTime
|
||||||
# exception will be raised unless the optional dst parameter or block
|
# 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
|
# 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,
|
# 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))
|
# 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
|
# Specifying dst=true would return 2004-10-31 5:30:00. Specifying dst=false
|
||||||
# would return 2004-10-31 6:30:00.
|
# 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
|
# 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
|
# 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
|
# single period to use to convert the time or return nil or an empty array
|
||||||
@@ -416,21 +416,21 @@ module TZInfo
|
|||||||
else
|
else
|
||||||
period = period_for_local(wrapped, dst)
|
period = period_for_local(wrapped, dst)
|
||||||
end
|
end
|
||||||
|
|
||||||
period.to_utc(wrapped)
|
period.to_utc(wrapped)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the current time in the timezone as a Time.
|
# Returns the current time in the timezone as a Time.
|
||||||
def now
|
def now
|
||||||
utc_to_local(Time.now.utc)
|
utc_to_local(Time.now.utc)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the TimezonePeriod for the current time.
|
# Returns the TimezonePeriod for the current time.
|
||||||
def current_period
|
def current_period
|
||||||
period_for_utc(Time.now.utc)
|
period_for_utc(Time.now.utc)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the current Time and TimezonePeriod as an array. The first element
|
# Returns the current Time and TimezonePeriod as an array. The first element
|
||||||
# is the time, the second element is the period.
|
# is the time, the second element is the period.
|
||||||
def current_period_and_time
|
def current_period_and_time
|
||||||
@@ -438,19 +438,19 @@ module TZInfo
|
|||||||
period = period_for_utc(utc)
|
period = period_for_utc(utc)
|
||||||
[period.to_local(utc), period]
|
[period.to_local(utc), period]
|
||||||
end
|
end
|
||||||
|
|
||||||
alias :current_time_and_period :current_period_and_time
|
alias :current_time_and_period :current_period_and_time
|
||||||
|
|
||||||
# Converts a time in UTC to local time and returns it as a string
|
# Converts a time in UTC to local time and returns it as a string
|
||||||
# according to the given format. The formatting is identical to
|
# according to the given format. The formatting is identical to
|
||||||
# Time.strftime and DateTime.strftime, except %Z is replaced with the
|
# Time.strftime and DateTime.strftime, except %Z is replaced with the
|
||||||
# timezone abbreviation for the specified time (for example, EST or EDT).
|
# timezone abbreviation for the specified time (for example, EST or EDT).
|
||||||
def strftime(format, utc = Time.now.utc)
|
def strftime(format, utc = Time.now.utc)
|
||||||
period = period_for_utc(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)
|
local = Time.at(local).utc unless local.kind_of?(Time) || local.kind_of?(DateTime)
|
||||||
abbreviation = period.abbreviation.to_s.gsub(/%/, '%%')
|
abbreviation = period.abbreviation.to_s.gsub(/%/, '%%')
|
||||||
|
|
||||||
format = format.gsub(/(.?)%Z/) do
|
format = format.gsub(/(.?)%Z/) do
|
||||||
if $1 == '%'
|
if $1 == '%'
|
||||||
# return %%Z so the real strftime treats it as a literal %Z too
|
# return %%Z so the real strftime treats it as a literal %Z too
|
||||||
@@ -459,50 +459,50 @@ module TZInfo
|
|||||||
"#$1#{abbreviation}"
|
"#$1#{abbreviation}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local.strftime(format)
|
local.strftime(format)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Compares two Timezones based on their identifier. Returns -1 if tz is less
|
# 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.
|
# than self, 0 if tz is equal to self and +1 if tz is greater than self.
|
||||||
def <=>(tz)
|
def <=>(tz)
|
||||||
identifier <=> tz.identifier
|
identifier <=> tz.identifier
|
||||||
end
|
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.
|
# identifier of this Timezone.
|
||||||
def eql?(tz)
|
def eql?(tz)
|
||||||
self == tz
|
self == tz
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns a hash of this Timezone.
|
# Returns a hash of this Timezone.
|
||||||
def hash
|
def hash
|
||||||
identifier.hash
|
identifier.hash
|
||||||
end
|
end
|
||||||
|
|
||||||
# Dumps this Timezone for marshalling.
|
# Dumps this Timezone for marshalling.
|
||||||
def _dump(limit)
|
def _dump(limit)
|
||||||
identifier
|
identifier
|
||||||
end
|
end
|
||||||
|
|
||||||
# Loads a marshalled Timezone.
|
# Loads a marshalled Timezone.
|
||||||
def self._load(data)
|
def self._load(data)
|
||||||
Timezone.get(data)
|
Timezone.get(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
# Loads in the index of timezones if it hasn't already been loaded.
|
# Loads in the index of timezones if it hasn't already been loaded.
|
||||||
def self.load_index
|
def self.load_index
|
||||||
unless @@index_loaded
|
unless @@index_loaded
|
||||||
require 'tzinfo/indexes/timezones'
|
require 'tzinfo/indexes/timezones'
|
||||||
@@index_loaded = true
|
@@index_loaded = true
|
||||||
end
|
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.
|
# identifiers.
|
||||||
def self.get_proxies(identifiers)
|
def self.get_proxies(identifiers)
|
||||||
identifiers.collect {|identifier| get_proxy(identifier)}
|
identifiers.collect {|identifier| get_proxy(identifier)}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -505,7 +505,7 @@ class HashToXmlTest < Test::Unit::TestCase
|
|||||||
assert_equal "<person>", xml.first(8)
|
assert_equal "<person>", xml.first(8)
|
||||||
assert xml.include?(%(<street>Paulina</street>))
|
assert xml.include?(%(<street>Paulina</street>))
|
||||||
assert xml.include?(%(<name>David</name>))
|
assert xml.include?(%(<name>David</name>))
|
||||||
assert xml.include?(%(<age nil="true"></age>))
|
assert_includes xml, %(<age nil="true"/>)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_one_level_with_skipping_types
|
def test_one_level_with_skipping_types
|
||||||
@@ -513,7 +513,7 @@ class HashToXmlTest < Test::Unit::TestCase
|
|||||||
assert_equal "<person>", xml.first(8)
|
assert_equal "<person>", xml.first(8)
|
||||||
assert xml.include?(%(<street>Paulina</street>))
|
assert xml.include?(%(<street>Paulina</street>))
|
||||||
assert xml.include?(%(<name>David</name>))
|
assert xml.include?(%(<name>David</name>))
|
||||||
assert xml.include?(%(<age nil="true"></age>))
|
assert_includes xml, %(<age nil="true"/>)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_one_level_with_yielding
|
def test_one_level_with_yielding
|
||||||
@@ -618,12 +618,12 @@ class HashToXmlTest < Test::Unit::TestCase
|
|||||||
EOT
|
EOT
|
||||||
|
|
||||||
expected_topic_hash = {
|
expected_topic_hash = {
|
||||||
:title => nil,
|
:title => nil,
|
||||||
:id => nil,
|
:id => nil,
|
||||||
:approved => nil,
|
:approved => nil,
|
||||||
:written_on => nil,
|
:written_on => nil,
|
||||||
:viewed_at => nil,
|
:viewed_at => nil,
|
||||||
:content => nil,
|
:content => nil,
|
||||||
:parent_id => nil
|
:parent_id => nil
|
||||||
}.stringify_keys
|
}.stringify_keys
|
||||||
|
|
||||||
@@ -701,7 +701,7 @@ class HashToXmlTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"]
|
assert_equal expected_topic_hash, Hash.from_xml(topic_xml)["rsp"]["photos"]["photo"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_empty_array_from_xml
|
def test_empty_array_from_xml
|
||||||
blog_xml = <<-XML
|
blog_xml = <<-XML
|
||||||
<blog>
|
<blog>
|
||||||
@@ -815,13 +815,13 @@ class HashToXmlTest < Test::Unit::TestCase
|
|||||||
|
|
||||||
assert_equal expected_bacon_hash, Hash.from_xml(bacon_xml)["bacon"]
|
assert_equal expected_bacon_hash, Hash.from_xml(bacon_xml)["bacon"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_type_trickles_through_when_unknown
|
def test_type_trickles_through_when_unknown
|
||||||
product_xml = <<-EOT
|
product_xml = <<-EOT
|
||||||
<product>
|
<product>
|
||||||
<weight type="double">0.5</weight>
|
<weight type="double">0.5</weight>
|
||||||
<image type="ProductImage"><filename>image.gif</filename></image>
|
<image type="ProductImage"><filename>image.gif</filename></image>
|
||||||
|
|
||||||
</product>
|
</product>
|
||||||
EOT
|
EOT
|
||||||
|
|
||||||
@@ -830,7 +830,7 @@ class HashToXmlTest < Test::Unit::TestCase
|
|||||||
:image => {'type' => 'ProductImage', 'filename' => 'image.gif' },
|
:image => {'type' => 'ProductImage', 'filename' => 'image.gif' },
|
||||||
}.stringify_keys
|
}.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
|
end
|
||||||
|
|
||||||
def test_should_use_default_value_for_unknown_key
|
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)
|
assert_equal expected, hash.to_xml(@xml_options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_empty_string_works_for_typecast_xml_value
|
def test_empty_string_works_for_typecast_xml_value
|
||||||
assert_nothing_raised do
|
assert_nothing_raised do
|
||||||
Hash.__send__(:typecast_xml_value, "")
|
Hash.__send__(:typecast_xml_value, "")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_escaping_to_xml
|
def test_escaping_to_xml
|
||||||
hash = {
|
hash = {
|
||||||
:bare_string => 'First & Last Name',
|
:bare_string => 'First & Last Name',
|
||||||
:pre_escaped_string => 'First & Last Name'
|
:pre_escaped_string => 'First & Last Name'
|
||||||
}.stringify_keys
|
}.stringify_keys
|
||||||
|
|
||||||
expected_xml = '<person><bare-string>First & Last Name</bare-string><pre-escaped-string>First &amp; Last Name</pre-escaped-string></person>'
|
expected_xml = '<person><bare-string>First & Last Name</bare-string><pre-escaped-string>First &amp; Last Name</pre-escaped-string></person>'
|
||||||
assert_equal expected_xml, hash.to_xml(@xml_options)
|
assert_equal expected_xml, hash.to_xml(@xml_options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_unescaping_from_xml
|
def test_unescaping_from_xml
|
||||||
xml_string = '<person><bare-string>First & Last Name</bare-string><pre-escaped-string>First &amp; Last Name</pre-escaped-string></person>'
|
xml_string = '<person><bare-string>First & Last Name</bare-string><pre-escaped-string>First &amp; Last Name</pre-escaped-string></person>'
|
||||||
expected_hash = {
|
expected_hash = {
|
||||||
:bare_string => 'First & Last Name',
|
:bare_string => 'First & Last Name',
|
||||||
:pre_escaped_string => 'First & Last Name'
|
:pre_escaped_string => 'First & Last Name'
|
||||||
}.stringify_keys
|
}.stringify_keys
|
||||||
assert_equal expected_hash, Hash.from_xml(xml_string)['person']
|
assert_equal expected_hash, Hash.from_xml(xml_string)['person']
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_roundtrip_to_xml_from_xml
|
def test_roundtrip_to_xml_from_xml
|
||||||
hash = {
|
hash = {
|
||||||
:bare_string => 'First & Last Name',
|
:bare_string => 'First & Last Name',
|
||||||
:pre_escaped_string => 'First & Last Name'
|
:pre_escaped_string => 'First & Last Name'
|
||||||
}.stringify_keys
|
}.stringify_keys
|
||||||
|
|
||||||
assert_equal hash, Hash.from_xml(hash.to_xml(@xml_options))['person']
|
assert_equal hash, Hash.from_xml(hash.to_xml(@xml_options))['person']
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_to_xml_dups_options
|
def test_to_xml_dups_options
|
||||||
options = {:skip_instruct => true}
|
options = {:skip_instruct => true}
|
||||||
{}.to_xml(options)
|
{}.to_xml(options)
|
||||||
@@ -916,7 +916,7 @@ class HashToXmlTest < Test::Unit::TestCase
|
|||||||
assert alert_at.utc?
|
assert alert_at.utc?
|
||||||
assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
|
assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_datetime_xml_type_with_non_utc_time
|
def test_datetime_xml_type_with_non_utc_time
|
||||||
alert_xml = <<-XML
|
alert_xml = <<-XML
|
||||||
<alert>
|
<alert>
|
||||||
@@ -927,7 +927,7 @@ class HashToXmlTest < Test::Unit::TestCase
|
|||||||
assert alert_at.utc?
|
assert alert_at.utc?
|
||||||
assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
|
assert_equal Time.utc(2008, 2, 10, 15, 30, 45), alert_at
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_datetime_xml_type_with_far_future_date
|
def test_datetime_xml_type_with_far_future_date
|
||||||
alert_xml = <<-XML
|
alert_xml = <<-XML
|
||||||
<alert>
|
<alert>
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ task :default => :test
|
|||||||
## This is required until the regular test task
|
## This is required until the regular test task
|
||||||
## below passes. It's not ideal, but at least
|
## below passes. It's not ideal, but at least
|
||||||
## we can see the failures
|
## we can see the failures
|
||||||
task :test do
|
task :test do
|
||||||
Dir['test/**/*_test.rb'].all? do |file|
|
Dir['test/**/*_test.rb'].all? do |file|
|
||||||
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
|
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
|
||||||
system(ruby, '-Itest', file)
|
system(ruby, '-Itest', file)
|
||||||
@@ -38,7 +38,7 @@ Rake::TestTask.new("regular_test") do |t|
|
|||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
BASE_DIRS = %w(
|
BASE_DIRS = %w(
|
||||||
app
|
app
|
||||||
config/environments
|
config/environments
|
||||||
config/initializers
|
config/initializers
|
||||||
@@ -158,21 +158,10 @@ end
|
|||||||
# Copy Ties Content -----------------------------------------------------------------------
|
# Copy Ties Content -----------------------------------------------------------------------
|
||||||
|
|
||||||
desc "Make copies of all the default content of ties"
|
desc "Make copies of all the default content of ties"
|
||||||
task :copy_ties_content => [
|
task :copy_ties_content => [
|
||||||
:copy_rootfiles, :copy_dispatches, :copy_html_files, :copy_application,
|
:copy_rootfiles, :copy_html_files, :copy_application,
|
||||||
:copy_configs, :copy_binfiles, :copy_test_helpers, :copy_app_doc_readme ]
|
: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
|
task :copy_html_files do
|
||||||
HTML_FILES.each { |file| cp File.join('html', file), File.join(PKG_DESTINATION, 'public', file) }
|
HTML_FILES.each { |file| cp File.join('html', file), File.join(PKG_DESTINATION, 'public', file) }
|
||||||
end
|
end
|
||||||
@@ -187,7 +176,7 @@ task :copy_configs do
|
|||||||
socket = nil
|
socket = nil
|
||||||
require 'erb'
|
require 'erb'
|
||||||
File.open("#{PKG_DESTINATION}/config/database.yml", 'w') {|f| f.write ERB.new(IO.read("configs/databases/sqlite3.yml"), nil, '-').result(binding)}
|
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/routes.rb", "#{PKG_DESTINATION}/config/routes.rb"
|
||||||
|
|
||||||
cp "configs/initializers/backtrace_silencers.rb", "#{PKG_DESTINATION}/config/initializers/backtrace_silencers.rb"
|
cp "configs/initializers/backtrace_silencers.rb", "#{PKG_DESTINATION}/config/initializers/backtrace_silencers.rb"
|
||||||
@@ -288,15 +277,15 @@ end
|
|||||||
|
|
||||||
PKG_FILES = FileList[
|
PKG_FILES = FileList[
|
||||||
'[a-zA-Z]*',
|
'[a-zA-Z]*',
|
||||||
'bin/**/*',
|
'bin/**/*',
|
||||||
'builtin/**/*',
|
'builtin/**/*',
|
||||||
'configs/**/*',
|
'configs/**/*',
|
||||||
'doc/**/*',
|
'doc/**/*',
|
||||||
'dispatches/**/*',
|
'dispatches/**/*',
|
||||||
'environments/**/*',
|
'environments/**/*',
|
||||||
'helpers/**/*',
|
'helpers/**/*',
|
||||||
'generators/**/*',
|
'generators/**/*',
|
||||||
'html/**/*',
|
'html/**/*',
|
||||||
'lib/**/*'
|
'lib/**/*'
|
||||||
] - [ 'test' ]
|
] - [ 'test' ]
|
||||||
|
|
||||||
@@ -336,7 +325,7 @@ end
|
|||||||
|
|
||||||
# Publishing -------------------------------------------------------
|
# Publishing -------------------------------------------------------
|
||||||
desc "Publish the rails gem"
|
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
|
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'`
|
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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!
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -45,7 +45,7 @@ def find_cmd(*commands)
|
|||||||
end
|
end
|
||||||
|
|
||||||
case config["adapter"]
|
case config["adapter"]
|
||||||
when /^mysql/
|
when /mysql/
|
||||||
args = {
|
args = {
|
||||||
'host' => '--host',
|
'host' => '--host',
|
||||||
'port' => '--port',
|
'port' => '--port',
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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)
|
|
||||||
@@ -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
|
|
||||||
@@ -299,7 +299,11 @@ HELP
|
|||||||
# Evaluate any assignments in a temporary, throwaway binding.
|
# Evaluate any assignments in a temporary, throwaway binding.
|
||||||
vars = template_options[:assigns] || {}
|
vars = template_options[:assigns] || {}
|
||||||
b = template_options[:binding] || binding
|
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.
|
# Render the source file with the temporary binding.
|
||||||
ERB.new(file.read, nil, '-').result(b)
|
ERB.new(file.read, nil, '-').result(b)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
require 'rbconfig'
|
require 'rbconfig'
|
||||||
require File.dirname(__FILE__) + '/template_runner'
|
require File.dirname(__FILE__) + '/template_runner'
|
||||||
require 'digest/md5'
|
require 'digest/md5'
|
||||||
require 'active_support/secure_random'
|
require 'active_support/secure_random'
|
||||||
|
|
||||||
class AppGenerator < Rails::Generator::Base
|
class AppGenerator < Rails::Generator::Base
|
||||||
@@ -110,12 +110,12 @@ class AppGenerator < Rails::Generator::Base
|
|||||||
tmp/pids
|
tmp/pids
|
||||||
).each { |path| m.directory(path) }
|
).each { |path| m.directory(path) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_root_files(m)
|
def create_root_files(m)
|
||||||
m.file "fresh_rakefile", "Rakefile"
|
m.file "fresh_rakefile", "Rakefile"
|
||||||
m.file "README", "README"
|
m.file "README", "README"
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_app_files(m)
|
def create_app_files(m)
|
||||||
m.file "helpers/application_controller.rb", "app/controllers/application_controller.rb"
|
m.file "helpers/application_controller.rb", "app/controllers/application_controller.rb"
|
||||||
m.file "helpers/application_helper.rb", "app/helpers/application_helper.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|
|
%w( server production development test ).each do |file|
|
||||||
m.file "configs/empty.log", "log/#{file}.log", :chmod => 0666
|
m.file "configs/empty.log", "log/#{file}.log", :chmod => 0666
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_public_files(m)
|
def create_public_files(m)
|
||||||
create_dispatch_files(m)
|
create_dispatch_files(m)
|
||||||
@@ -148,14 +148,14 @@ class AppGenerator < Rails::Generator::Base
|
|||||||
create_rails_image(m)
|
create_rails_image(m)
|
||||||
create_javascript_files(m)
|
create_javascript_files(m)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_script_files(m)
|
def create_script_files(m)
|
||||||
%w(
|
%w(
|
||||||
about console dbconsole destroy generate runner server plugin
|
about console dbconsole destroy generate runner server plugin
|
||||||
performance/benchmarker performance/profiler
|
performance/benchmarker performance/profiler
|
||||||
).each do |file|
|
).each do |file|
|
||||||
m.file "bin/#{file}", "script/#{file}", {
|
m.file "bin/#{file}", "script/#{file}", {
|
||||||
:chmod => 0755,
|
:chmod => 0755,
|
||||||
:shebang => options[:shebang] == DEFAULT_SHEBANG ? nil : options[:shebang]
|
:shebang => options[:shebang] == DEFAULT_SHEBANG ? nil : options[:shebang]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@@ -172,7 +172,7 @@ class AppGenerator < Rails::Generator::Base
|
|||||||
:app_name => @app_name,
|
:app_name => @app_name,
|
||||||
:socket => options[:db] == "mysql" ? mysql_socket_location : nil }
|
:socket => options[:db] == "mysql" ? mysql_socket_location : nil }
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_routes_file(m)
|
def create_routes_file(m)
|
||||||
m.file "configs/routes.rb", "config/routes.rb"
|
m.file "configs/routes.rb", "config/routes.rb"
|
||||||
end
|
end
|
||||||
@@ -182,19 +182,19 @@ class AppGenerator < Rails::Generator::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create_initializer_files(m)
|
def create_initializer_files(m)
|
||||||
%w(
|
%w(
|
||||||
backtrace_silencers
|
backtrace_silencers
|
||||||
inflections
|
inflections
|
||||||
mime_types
|
mime_types
|
||||||
new_rails_defaults
|
new_rails_defaults
|
||||||
).each do |initializer|
|
).each do |initializer|
|
||||||
m.file "configs/initializers/#{initializer}.rb", "config/initializers/#{initializer}.rb"
|
m.file "configs/initializers/#{initializer}.rb", "config/initializers/#{initializer}.rb"
|
||||||
end
|
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) }
|
: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) }
|
:assigns => { :app_secret => ActiveSupport::SecureRandom.hex(64) }
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ class AppGenerator < Rails::Generator::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def create_environment_files(m)
|
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] }
|
:assigns => { :freeze => options[:freeze] }
|
||||||
|
|
||||||
m.file "environments/boot.rb", "config/boot.rb"
|
m.file "environments/boot.rb", "config/boot.rb"
|
||||||
@@ -218,9 +218,6 @@ class AppGenerator < Rails::Generator::Base
|
|||||||
dispatcher_options = { :chmod => 0755, :shebang => options[:shebang] }
|
dispatcher_options = { :chmod => 0755, :shebang => options[:shebang] }
|
||||||
|
|
||||||
m.file "dispatches/config.ru", "config.ru"
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -263,4 +260,4 @@ class AppGenerator < Rails::Generator::Base
|
|||||||
"/opt/lampp/var/mysql/mysql.sock" # xampp for linux
|
"/opt/lampp/var/mysql/mysql.sock" # xampp for linux
|
||||||
].find { |f| File.exist?(f) } unless RUBY_PLATFORM =~ /(:?mswin|mingw)/
|
].find { |f| File.exist?(f) } unless RUBY_PLATFORM =~ /(:?mswin|mingw)/
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ namespace :db do
|
|||||||
end
|
end
|
||||||
rescue
|
rescue
|
||||||
case config['adapter']
|
case config['adapter']
|
||||||
when /^mysql/
|
when /mysql/
|
||||||
@charset = ENV['CHARSET'] || 'utf8'
|
@charset = ENV['CHARSET'] || 'utf8'
|
||||||
@collation = ENV['COLLATION'] || 'utf8_unicode_ci'
|
@collation = ENV['COLLATION'] || 'utf8_unicode_ci'
|
||||||
begin
|
begin
|
||||||
@@ -159,7 +159,7 @@ namespace :db do
|
|||||||
task :charset => :environment do
|
task :charset => :environment do
|
||||||
config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
|
config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
|
||||||
case config['adapter']
|
case config['adapter']
|
||||||
when /^mysql/
|
when /mysql/
|
||||||
ActiveRecord::Base.establish_connection(config)
|
ActiveRecord::Base.establish_connection(config)
|
||||||
puts ActiveRecord::Base.connection.charset
|
puts ActiveRecord::Base.connection.charset
|
||||||
when 'postgresql'
|
when 'postgresql'
|
||||||
@@ -174,7 +174,7 @@ namespace :db do
|
|||||||
task :collation => :environment do
|
task :collation => :environment do
|
||||||
config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
|
config = ActiveRecord::Base.configurations[RAILS_ENV || 'development']
|
||||||
case config['adapter']
|
case config['adapter']
|
||||||
when /^mysql/
|
when /mysql/
|
||||||
ActiveRecord::Base.establish_connection(config)
|
ActiveRecord::Base.establish_connection(config)
|
||||||
puts ActiveRecord::Base.connection.collation
|
puts ActiveRecord::Base.connection.collation
|
||||||
else
|
else
|
||||||
@@ -274,7 +274,7 @@ namespace :db do
|
|||||||
task :dump => :environment do
|
task :dump => :environment do
|
||||||
abcs = ActiveRecord::Base.configurations
|
abcs = ActiveRecord::Base.configurations
|
||||||
case abcs[RAILS_ENV]["adapter"]
|
case abcs[RAILS_ENV]["adapter"]
|
||||||
when /^mysql/, "oci", "oracle"
|
when /mysql/, "oci", "oracle"
|
||||||
ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
|
ActiveRecord::Base.establish_connection(abcs[RAILS_ENV])
|
||||||
File.open("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
|
File.open("#{RAILS_ROOT}/db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump }
|
||||||
when "postgresql"
|
when "postgresql"
|
||||||
@@ -320,7 +320,7 @@ namespace :db do
|
|||||||
task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
|
task :clone_structure => [ "db:structure:dump", "db:test:purge" ] do
|
||||||
abcs = ActiveRecord::Base.configurations
|
abcs = ActiveRecord::Base.configurations
|
||||||
case abcs["test"]["adapter"]
|
case abcs["test"]["adapter"]
|
||||||
when /^mysql/
|
when /mysql/
|
||||||
ActiveRecord::Base.establish_connection(:test)
|
ActiveRecord::Base.establish_connection(:test)
|
||||||
ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0')
|
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|
|
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
|
task :purge => :environment do
|
||||||
abcs = ActiveRecord::Base.configurations
|
abcs = ActiveRecord::Base.configurations
|
||||||
case abcs["test"]["adapter"]
|
case abcs["test"]["adapter"]
|
||||||
when /^mysql/
|
when /mysql/
|
||||||
ActiveRecord::Base.establish_connection(:test)
|
ActiveRecord::Base.establish_connection(:test)
|
||||||
ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"])
|
ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"], abcs["test"])
|
||||||
when "postgresql"
|
when "postgresql"
|
||||||
@@ -408,7 +408,7 @@ end
|
|||||||
def drop_database(config)
|
def drop_database(config)
|
||||||
begin
|
begin
|
||||||
case config['adapter']
|
case config['adapter']
|
||||||
when /^mysql/
|
when /mysql/
|
||||||
ActiveRecord::Base.establish_connection(config)
|
ActiveRecord::Base.establish_connection(config)
|
||||||
ActiveRecord::Base.connection.drop_database config['database']
|
ActiveRecord::Base.connection.drop_database config['database']
|
||||||
when /^sqlite/
|
when /^sqlite/
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ namespace :rails do
|
|||||||
|
|
||||||
local = Dir["#{local_base}/**/*"].reject { |path| File.directory?(path) }
|
local = Dir["#{local_base}/**/*"].reject { |path| File.directory?(path) }
|
||||||
edge = Dir["#{edge_base}/**/*"].reject { |path| File.directory?(path) }
|
edge = Dir["#{edge_base}/**/*"].reject { |path| File.directory?(path) }
|
||||||
|
|
||||||
edge.each do |script|
|
edge.each do |script|
|
||||||
base_name = script[(edge_base.length+1)..-1]
|
base_name = script[(edge_base.length+1)..-1]
|
||||||
next if base_name == "rails"
|
next if base_name == "rails"
|
||||||
@@ -111,7 +111,7 @@ namespace :rails do
|
|||||||
|
|
||||||
desc "Update your javascripts from your current rails install"
|
desc "Update your javascripts from your current rails install"
|
||||||
task :javascripts do
|
task :javascripts do
|
||||||
require 'railties_path'
|
require 'railties_path'
|
||||||
project_dir = RAILS_ROOT + '/public/javascripts/'
|
project_dir = RAILS_ROOT + '/public/javascripts/'
|
||||||
scripts = Dir[RAILTIES_PATH + '/html/javascripts/*.js']
|
scripts = Dir[RAILTIES_PATH + '/html/javascripts/*.js']
|
||||||
scripts.reject!{|s| File.basename(s) == 'application.js'} if File.exist?(project_dir + 'application.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"
|
desc "Update config/boot.rb from your current rails install"
|
||||||
task :configs do
|
task :configs do
|
||||||
require 'railties_path'
|
require 'railties_path'
|
||||||
FileUtils.cp(RAILTIES_PATH + '/environments/boot.rb', RAILS_ROOT + '/config/boot.rb')
|
FileUtils.cp(RAILTIES_PATH + '/environments/boot.rb', RAILS_ROOT + '/config/boot.rb')
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Rename application.rb to application_controller.rb"
|
desc "Rename application.rb to application_controller.rb"
|
||||||
task :application_controller do
|
task :application_controller do
|
||||||
old_style = RAILS_ROOT + '/app/controllers/application.rb'
|
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"
|
puts "#{old_style} has been renamed to #{new_style}, update your SCM as necessary"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Generate dispatcher files in RAILS_ROOT/public"
|
desc "Generate dispatcher files in RAILS_ROOT/public"
|
||||||
task :generate_dispatchers do
|
task :generate_dispatchers do
|
||||||
require 'railties_path'
|
require 'railties_path'
|
||||||
FileUtils.cp(RAILTIES_PATH + '/dispatches/config.ru', RAILS_ROOT + '/config.ru')
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -3,5 +3,4 @@
|
|||||||
set -x
|
set -x
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
script/cibuild-on 1.9.3-p231-tcs-github
|
script/cibuild-on 2.1.0-github
|
||||||
script/cibuild-on 2.0.0-github
|
|
||||||
|
|||||||
Reference in New Issue
Block a user