mirror of
https://github.com/github/rails.git
synced 2026-01-12 08:08:31 -05:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -1 +1 @@
|
||||
2.3.14.github31
|
||||
2.3.14.github35
|
||||
|
||||
@@ -38,7 +38,7 @@ module ActionController
|
||||
# TODO: Review explicit to see if they will automatically be handled by
|
||||
# the initilizer if they are really needed.
|
||||
def self.load_all!
|
||||
[Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
|
||||
[Base, Request, Response, Http::Headers, UrlRewriter, UrlWriter]
|
||||
end
|
||||
|
||||
autoload :Base, 'action_controller/base'
|
||||
@@ -99,10 +99,6 @@ module ActionController
|
||||
autoload :CookieStore, 'action_controller/session/cookie_store'
|
||||
autoload :MemCacheStore, 'action_controller/session/mem_cache_store'
|
||||
end
|
||||
|
||||
# DEPRECATE: Remove CGI support
|
||||
autoload :CgiRequest, 'action_controller/cgi_process'
|
||||
autoload :CGIHandler, 'action_controller/cgi_process'
|
||||
end
|
||||
|
||||
autoload :Mime, 'action_controller/mime_type'
|
||||
|
||||
@@ -39,9 +39,9 @@ module ActionController #:nodoc:
|
||||
if cache = read_fragment(name, options)
|
||||
buffer.safe_concat(cache.html_safe)
|
||||
else
|
||||
pos = buffer.length
|
||||
pos = buffer.bytesize
|
||||
block.call
|
||||
write_fragment(name, buffer[pos..-1], options)
|
||||
write_fragment(name, buffer.byteslice(pos..-1), options)
|
||||
end
|
||||
else
|
||||
block.call
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
require 'action_controller/cgi_ext/stdinput'
|
||||
require 'action_controller/cgi_ext/query_extension'
|
||||
require 'action_controller/cgi_ext/cookie'
|
||||
|
||||
class CGI #:nodoc:
|
||||
include ActionController::CgiExt::Stdinput
|
||||
|
||||
class << self
|
||||
alias :escapeHTML_fail_on_nil :escapeHTML
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
require 'delegate'
|
||||
require 'cgi'
|
||||
require 'cgi/cookie'
|
||||
|
||||
CGI.module_eval { remove_const "Cookie" }
|
||||
|
||||
@@ -24,7 +26,7 @@ class CGI #:nodoc:
|
||||
# * <tt>:secure</tt> - Whether this cookie is a secure cookie or not (defaults to
|
||||
# +false+). Secure cookies are only transmitted to HTTPS servers.
|
||||
# * <tt>:http_only</tt> - Whether this cookie can be accessed by client side scripts (e.g. document.cookie) or only over HTTP.
|
||||
# More details in http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx. Defaults to +false+.
|
||||
# More details in http://msdn2.microsoft.com/en-us/library/system.web.httpcookie.httponly.aspx. Defaults to +false+.
|
||||
#
|
||||
# These keywords correspond to attributes of the cookie object.
|
||||
def initialize(name = '', *value)
|
||||
|
||||
@@ -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
|
||||
|
||||
# DEPRECATE: Remove CGI support
|
||||
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
|
||||
new(output).dispatch_cgi(cgi, session_options)
|
||||
end
|
||||
|
||||
# Add a preparation callback. Preparation callbacks are run before every
|
||||
# request in development mode, and before the first request in production
|
||||
# mode.
|
||||
@@ -42,13 +37,7 @@ module ActionController
|
||||
end
|
||||
|
||||
def run_prepare_callbacks
|
||||
if defined?(Rails) && Rails.logger
|
||||
logger = Rails.logger
|
||||
else
|
||||
logger = Logger.new($stderr)
|
||||
end
|
||||
|
||||
new(logger).send :run_callbacks, :prepare_dispatch
|
||||
new.send :run_callbacks, :prepare_dispatch
|
||||
end
|
||||
|
||||
def reload_application
|
||||
@@ -75,10 +64,8 @@ module ActionController
|
||||
include ActiveSupport::Callbacks
|
||||
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
|
||||
|
||||
# DEPRECATE: Remove arguments, since they are only used by CGI
|
||||
def initialize(output = $stdout, request = nil, response = nil)
|
||||
@output = output
|
||||
build_middleware_stack if @@cache_classes
|
||||
def initialize
|
||||
build_middleware_stack
|
||||
end
|
||||
|
||||
def dispatch
|
||||
@@ -96,21 +83,11 @@ module ActionController
|
||||
end
|
||||
end
|
||||
|
||||
# DEPRECATE: Remove CGI support
|
||||
def dispatch_cgi(cgi, session_options)
|
||||
CGIHandler.dispatch_cgi(self, cgi, @output)
|
||||
end
|
||||
|
||||
def call(env)
|
||||
if @@cache_classes
|
||||
@app.call(env)
|
||||
else
|
||||
Reloader.run do
|
||||
# When class reloading is turned on, we will want to rebuild the
|
||||
# middleware stack every time we process a request. If we don't
|
||||
# rebuild the middleware stack, then the stack may contain references
|
||||
# to old classes metal classes, which will b0rk class reloading.
|
||||
build_middleware_stack
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -423,13 +423,13 @@ EOM
|
||||
|
||||
# Override Rack's GET method to support indifferent access
|
||||
def GET
|
||||
@env["action_controller.request.query_parameters"] ||= normalize_parameters(super)
|
||||
@env["action_controller.request.query_parameters"] ||= deep_munge(normalize_parameters(super) || {})
|
||||
end
|
||||
alias_method :query_parameters, :GET
|
||||
|
||||
# Override Rack's POST method to support indifferent access
|
||||
def POST
|
||||
@env["action_controller.request.request_parameters"] ||= normalize_parameters(super)
|
||||
@env["action_controller.request.request_parameters"] ||= deep_munge(normalize_parameters(super) || {})
|
||||
end
|
||||
alias_method :request_parameters, :POST
|
||||
|
||||
@@ -469,6 +469,22 @@ EOM
|
||||
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
|
||||
end
|
||||
|
||||
# Remove nils from the params hash
|
||||
def deep_munge(hash)
|
||||
hash.each do |k, v|
|
||||
case v
|
||||
when Array
|
||||
v.grep(Hash) { |x| deep_munge(x) }
|
||||
v.compact!
|
||||
hash[k] = nil if v.empty?
|
||||
when Hash
|
||||
deep_munge(v)
|
||||
end
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
|
||||
# Convert nested Hashs to HashWithIndifferentAccess and replace
|
||||
# file upload hashs with UploadedFile objects
|
||||
def normalize_parameters(value)
|
||||
|
||||
@@ -768,7 +768,11 @@ module ActionView
|
||||
options = options.stringify_keys
|
||||
tag_value = options.delete("value")
|
||||
name_and_id = options.dup
|
||||
name_and_id["id"] = name_and_id["for"]
|
||||
if name_and_id.has_key?("for")
|
||||
name_and_id["id"] = name_and_id["for"]
|
||||
else
|
||||
name_and_id.delete("id")
|
||||
end
|
||||
add_default_name_and_id_for_value(tag_value, name_and_id)
|
||||
options.delete("index")
|
||||
options["for"] ||= name_and_id["id"]
|
||||
@@ -928,15 +932,15 @@ module ActionView
|
||||
|
||||
def add_default_name_and_id(options)
|
||||
if options.has_key?("index")
|
||||
options["name"] ||= tag_name_with_index(options["index"])
|
||||
options["id"] ||= tag_id_with_index(options["index"])
|
||||
options["name"] = tag_name_with_index(options["index"]) unless options.has_key?("name")
|
||||
options["id"] = tag_id_with_index(options["index"]) unless options.has_key?("id")
|
||||
options.delete("index")
|
||||
elsif defined?(@auto_index)
|
||||
options["name"] ||= tag_name_with_index(@auto_index)
|
||||
options["id"] ||= tag_id_with_index(@auto_index)
|
||||
options["name"] = tag_name_with_index(@auto_index) unless options.has_key?("name")
|
||||
options["id"] = tag_id_with_index(@auto_index) unless options.has_key?("id")
|
||||
else
|
||||
options["name"] ||= tag_name + (options.has_key?('multiple') ? '[]' : '')
|
||||
options["id"] ||= tag_id
|
||||
options["name"] = tag_name + (options.has_key?('multiple') ? '[]' : '') unless options.has_key?("name")
|
||||
options["id"] = tag_id unless options.has_key?("id")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -85,11 +85,13 @@ module ActionView
|
||||
separator = '' if precision == 0
|
||||
|
||||
begin
|
||||
format.gsub(/%n/, number_with_precision(number,
|
||||
value = number_with_precision(number,
|
||||
:precision => precision,
|
||||
:delimiter => delimiter,
|
||||
:separator => separator)
|
||||
).gsub(/%u/, unit).html_safe
|
||||
value = ERB::Util.html_escape(value) if value
|
||||
unit = ERB::Util.html_escape(unit)
|
||||
format.gsub(/%n/, value).gsub(/%u/, unit).html_safe
|
||||
rescue
|
||||
number
|
||||
end
|
||||
|
||||
@@ -17,7 +17,7 @@ module ActionView
|
||||
src << "@output_buffer.safe_append='"
|
||||
src << "\n" * @newline_pending if @newline_pending > 0
|
||||
src << escape_text(text)
|
||||
src << "';"
|
||||
src << "'.freeze;"
|
||||
|
||||
@newline_pending = 0
|
||||
end
|
||||
@@ -63,7 +63,7 @@ module ActionView
|
||||
|
||||
def flush_newline_if_pending(src)
|
||||
if @newline_pending > 0
|
||||
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';"
|
||||
src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;"
|
||||
@newline_pending = 0
|
||||
end
|
||||
end
|
||||
|
||||
@@ -622,6 +622,19 @@ class FragmentCachingTest < ActionController::TestCase
|
||||
assert_equal 'generated till now -> fragment content', buffer
|
||||
end
|
||||
|
||||
def test_fragment_for_bytesize
|
||||
buffer = "\xC4\x8D"
|
||||
buffer.force_encoding('ASCII-8BIT')
|
||||
|
||||
@controller.fragment_for(buffer, 'bytesize') do
|
||||
buffer.force_encoding('UTF-8')
|
||||
buffer << "abc"
|
||||
end
|
||||
|
||||
assert_equal Encoding::UTF_8, buffer.encoding
|
||||
assert_equal "abc", @store.read('views/bytesize')
|
||||
end
|
||||
|
||||
def test_html_safety
|
||||
assert_nil @store.read('views/name')
|
||||
content = 'value'.html_safe
|
||||
|
||||
@@ -45,7 +45,7 @@ class DispatcherTest < Test::Unit::TestCase
|
||||
def test_rebuilds_middleware_stack_on_every_request_if_in_loading_mode
|
||||
dispatcher = create_dispatcher(false)
|
||||
dispatcher.instance_variable_set(:"@app", lambda { |env| })
|
||||
dispatcher.expects(:build_middleware_stack).twice
|
||||
dispatcher.expects(:build_middleware_stack).never
|
||||
dispatcher.call(nil)
|
||||
Reloader.default_lock.unlock
|
||||
dispatcher.call(nil)
|
||||
|
||||
@@ -5,7 +5,6 @@ class BaseRackTest < ActiveSupport::TestCase
|
||||
@env = {
|
||||
"HTTP_MAX_FORWARDS" => "10",
|
||||
"SERVER_NAME" => "glu.ttono.us",
|
||||
"FCGI_ROLE" => "RESPONDER",
|
||||
"AUTH_TYPE" => "Basic",
|
||||
"HTTP_X_FORWARDED_HOST" => "glu.ttono.us",
|
||||
"HTTP_ACCEPT_CHARSET" => "UTF-8",
|
||||
|
||||
@@ -175,6 +175,10 @@ class FormHelperTest < ActionView::TestCase
|
||||
I18n.locale = old_locale
|
||||
end
|
||||
|
||||
def test_label_with_for_attribute_as_nil
|
||||
assert_dom_equal('<label>Title</label>', label(:post, :title, nil, :for => nil))
|
||||
end
|
||||
|
||||
def test_label_with_for_attribute_as_symbol
|
||||
assert_dom_equal('<label for="my_for">Title</label>', label(:post, :title, nil, :for => "my_for"))
|
||||
end
|
||||
@@ -274,6 +278,11 @@ class FormHelperTest < ActionView::TestCase
|
||||
hidden_field("post", "title", :value => "Something Else")
|
||||
end
|
||||
|
||||
def test_text_field_with_id_as_nil
|
||||
assert_dom_equal '<input name="post[title]" type="hidden" value="Hello World" />',
|
||||
hidden_field("post", "title", :id => nil)
|
||||
end
|
||||
|
||||
def test_check_box
|
||||
assert_dom_equal(
|
||||
'<input name="post[secret]" type="hidden" value="0" /><input checked="checked" id="post_secret" name="post[secret]" type="checkbox" value="1" />',
|
||||
|
||||
@@ -24,9 +24,10 @@ class NumberHelperTest < ActionView::TestCase
|
||||
assert_equal("$1,234,567,890.51", number_to_currency(1234567890.506))
|
||||
assert_equal("$1,234,567,892", number_to_currency(1234567891.50, {:precision => 0}))
|
||||
assert_equal("$1,234,567,890.5", number_to_currency(1234567890.50, {:precision => 1}))
|
||||
assert_equal("£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 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"))
|
||||
assert_nil number_to_currency(nil)
|
||||
|
||||
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}"
|
||||
@@ -14,11 +14,7 @@ rescue Gem::LoadError
|
||||
$:.unshift "#{File.dirname(__FILE__)}/vendor/memcache-client-1.7.4"
|
||||
end
|
||||
|
||||
begin
|
||||
gem 'tzinfo', '~> 0.3.12'
|
||||
rescue Gem::LoadError
|
||||
$:.unshift "#{File.dirname(__FILE__)}/vendor/tzinfo-0.3.12"
|
||||
end
|
||||
$:.unshift "#{File.dirname(__FILE__)}/vendor/tzinfo-0.3.12"
|
||||
|
||||
begin
|
||||
gem 'i18n', '>= 0.4.1'
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#--
|
||||
# Copyright (c) 2005-2006 Philip Ross
|
||||
#
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
@@ -30,4 +30,5 @@ require 'tzinfo/timezone'
|
||||
# require 'tzinfo/tzdataparser'
|
||||
# require 'tzinfo/timezone_proxy'
|
||||
require 'tzinfo/data_timezone'
|
||||
require 'tzinfo/linked_timezone'
|
||||
require 'tzinfo/linked_timezone'
|
||||
require 'tzinfo/definitions'
|
||||
|
||||
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
|
||||
#
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
@@ -24,31 +24,32 @@ require 'date'
|
||||
# require 'tzinfo/country'
|
||||
require 'tzinfo/time_or_datetime'
|
||||
require 'tzinfo/timezone_period'
|
||||
require 'tzinfo/definitions'
|
||||
|
||||
module TZInfo
|
||||
# Indicate a specified time in a local timezone has more than one
|
||||
# possible time in UTC. This happens when switching from daylight savings time
|
||||
# possible time in UTC. This happens when switching from daylight savings time
|
||||
# to normal time where the clocks are rolled back. Thrown by period_for_local
|
||||
# and local_to_utc when using an ambiguous time and not specifying any
|
||||
# and local_to_utc when using an ambiguous time and not specifying any
|
||||
# means to resolve the ambiguity.
|
||||
class AmbiguousTime < StandardError
|
||||
end
|
||||
|
||||
|
||||
# Thrown to indicate that no TimezonePeriod matching a given time could be found.
|
||||
class PeriodNotFound < StandardError
|
||||
end
|
||||
|
||||
|
||||
# Thrown by Timezone#get if the identifier given is not valid.
|
||||
class InvalidTimezoneIdentifier < StandardError
|
||||
end
|
||||
|
||||
|
||||
# Thrown if an attempt is made to use a timezone created with Timezone.new(nil).
|
||||
class UnknownTimezone < StandardError
|
||||
end
|
||||
|
||||
|
||||
# Timezone is the base class of all timezones. It provides a factory method
|
||||
# get to access timezones by identifier. Once a specific Timezone has been
|
||||
# retrieved, DateTimes, Times and timestamps can be converted between the UTC
|
||||
# retrieved, DateTimes, Times and timestamps can be converted between the UTC
|
||||
# and the local time for the zone. For example:
|
||||
#
|
||||
# tz = TZInfo::Timezone.get('America/New_York')
|
||||
@@ -56,42 +57,41 @@ module TZInfo
|
||||
# puts tz.local_to_utc(Time.utc(2005,8,29,11,35,0)).to_s
|
||||
# puts tz.utc_to_local(1125315300).to_s
|
||||
#
|
||||
# Each time conversion method returns an object of the same type it was
|
||||
# Each time conversion method returns an object of the same type it was
|
||||
# passed.
|
||||
#
|
||||
# The timezone information all comes from the tz database
|
||||
# (see http://www.twinsun.com/tz/tz-link.htm)
|
||||
class Timezone
|
||||
include Comparable
|
||||
|
||||
|
||||
# Cache of loaded zones by identifier to avoid using require if a zone
|
||||
# has already been loaded.
|
||||
@@loaded_zones = {}
|
||||
|
||||
|
||||
# Whether the timezones index has been loaded yet.
|
||||
@@index_loaded = false
|
||||
|
||||
# Returns a timezone by its identifier (e.g. "Europe/London",
|
||||
|
||||
# Returns a timezone by its identifier (e.g. "Europe/London",
|
||||
# "America/Chicago" or "UTC").
|
||||
#
|
||||
# Raises InvalidTimezoneIdentifier if the timezone couldn't be found.
|
||||
def self.get(identifier)
|
||||
instance = @@loaded_zones[identifier]
|
||||
unless instance
|
||||
|
||||
unless instance
|
||||
raise InvalidTimezoneIdentifier, 'Invalid identifier' if identifier !~ /^[A-z0-9\+\-_]+(\/[A-z0-9\+\-_]+)*$/
|
||||
identifier = identifier.gsub(/-/, '__m__').gsub(/\+/, '__p__')
|
||||
begin
|
||||
# Use a temporary variable to avoid an rdoc warning
|
||||
file = "tzinfo/definitions/#{identifier}"
|
||||
require file
|
||||
|
||||
TZInfo::Definitions.load_all!
|
||||
|
||||
m = Definitions
|
||||
identifier.split(/\//).each {|part|
|
||||
m = m.const_get(part)
|
||||
}
|
||||
|
||||
|
||||
info = m.get
|
||||
|
||||
|
||||
# Could make Timezone subclasses register an interest in an info
|
||||
# type. Since there are currently only two however, there isn't
|
||||
# much point.
|
||||
@@ -102,35 +102,35 @@ module TZInfo
|
||||
else
|
||||
raise InvalidTimezoneIdentifier, "No handler for info type #{info.class}"
|
||||
end
|
||||
|
||||
@@loaded_zones[instance.identifier] = instance
|
||||
|
||||
@@loaded_zones[instance.identifier] = instance
|
||||
rescue LoadError, NameError => e
|
||||
raise InvalidTimezoneIdentifier, e.message
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
instance
|
||||
end
|
||||
|
||||
|
||||
# Returns a proxy for the Timezone with the given identifier. The proxy
|
||||
# will cause the real timezone to be loaded when an attempt is made to
|
||||
# find a period or convert a time. get_proxy will not validate the
|
||||
# identifier. If an invalid identifier is specified, no exception will be
|
||||
# raised until the proxy is used.
|
||||
# will cause the real timezone to be loaded when an attempt is made to
|
||||
# find a period or convert a time. get_proxy will not validate the
|
||||
# identifier. If an invalid identifier is specified, no exception will be
|
||||
# raised until the proxy is used.
|
||||
def self.get_proxy(identifier)
|
||||
TimezoneProxy.new(identifier)
|
||||
end
|
||||
|
||||
# If identifier is nil calls super(), otherwise calls get. An identfier
|
||||
|
||||
# If identifier is nil calls super(), otherwise calls get. An identfier
|
||||
# should always be passed in when called externally.
|
||||
def self.new(identifier = nil)
|
||||
if identifier
|
||||
if identifier
|
||||
get(identifier)
|
||||
else
|
||||
super()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Returns an array containing all the available Timezones.
|
||||
#
|
||||
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
|
||||
@@ -138,14 +138,14 @@ module TZInfo
|
||||
def self.all
|
||||
get_proxies(all_identifiers)
|
||||
end
|
||||
|
||||
# Returns an array containing the identifiers of all the available
|
||||
|
||||
# Returns an array containing the identifiers of all the available
|
||||
# Timezones.
|
||||
def self.all_identifiers
|
||||
load_index
|
||||
Indexes::Timezones.timezones
|
||||
end
|
||||
|
||||
|
||||
# Returns an array containing all the available Timezones that are based
|
||||
# on data (are not links to other Timezones).
|
||||
#
|
||||
@@ -154,44 +154,44 @@ module TZInfo
|
||||
def self.all_data_zones
|
||||
get_proxies(all_data_zone_identifiers)
|
||||
end
|
||||
|
||||
# Returns an array containing the identifiers of all the available
|
||||
|
||||
# Returns an array containing the identifiers of all the available
|
||||
# Timezones that are based on data (are not links to other Timezones)..
|
||||
def self.all_data_zone_identifiers
|
||||
load_index
|
||||
Indexes::Timezones.data_timezones
|
||||
end
|
||||
|
||||
|
||||
# Returns an array containing all the available Timezones that are links
|
||||
# to other Timezones.
|
||||
#
|
||||
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
|
||||
# definitions until a conversion is actually required.
|
||||
def self.all_linked_zones
|
||||
get_proxies(all_linked_zone_identifiers)
|
||||
get_proxies(all_linked_zone_identifiers)
|
||||
end
|
||||
|
||||
# Returns an array containing the identifiers of all the available
|
||||
|
||||
# Returns an array containing the identifiers of all the available
|
||||
# Timezones that are links to other Timezones.
|
||||
def self.all_linked_zone_identifiers
|
||||
load_index
|
||||
Indexes::Timezones.linked_timezones
|
||||
end
|
||||
|
||||
|
||||
# Returns all the Timezones defined for all Countries. This is not the
|
||||
# complete set of Timezones as some are not country specific (e.g.
|
||||
# complete set of Timezones as some are not country specific (e.g.
|
||||
# 'Etc/GMT').
|
||||
#
|
||||
#
|
||||
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
|
||||
# definitions until a conversion is actually required.
|
||||
# definitions until a conversion is actually required.
|
||||
def self.all_country_zones
|
||||
Country.all_codes.inject([]) {|zones,country|
|
||||
zones += Country.get(country).zones
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
# Returns all the zone identifiers defined for all Countries. This is not the
|
||||
# complete set of zone identifiers as some are not country specific (e.g.
|
||||
# complete set of zone identifiers as some are not country specific (e.g.
|
||||
# 'Etc/GMT'). You can obtain a Timezone instance for a given identifier
|
||||
# with the get method.
|
||||
def self.all_country_zone_identifiers
|
||||
@@ -199,8 +199,8 @@ module TZInfo
|
||||
zones += Country.get(country).zone_identifiers
|
||||
}
|
||||
end
|
||||
|
||||
# Returns all US Timezone instances. A shortcut for
|
||||
|
||||
# Returns all US Timezone instances. A shortcut for
|
||||
# TZInfo::Country.get('US').zones.
|
||||
#
|
||||
# Returns TimezoneProxy objects to avoid the overhead of loading Timezone
|
||||
@@ -208,35 +208,35 @@ module TZInfo
|
||||
def self.us_zones
|
||||
Country.get('US').zones
|
||||
end
|
||||
|
||||
# Returns all US zone identifiers. A shortcut for
|
||||
|
||||
# Returns all US zone identifiers. A shortcut for
|
||||
# TZInfo::Country.get('US').zone_identifiers.
|
||||
def self.us_zone_identifiers
|
||||
Country.get('US').zone_identifiers
|
||||
end
|
||||
|
||||
|
||||
# The identifier of the timezone, e.g. "Europe/Paris".
|
||||
def identifier
|
||||
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
|
||||
end
|
||||
|
||||
|
||||
# An alias for identifier.
|
||||
def name
|
||||
# Don't use alias, as identifier gets overridden.
|
||||
identifier
|
||||
end
|
||||
|
||||
|
||||
# Returns a friendlier version of the identifier.
|
||||
def to_s
|
||||
friendly_identifier
|
||||
end
|
||||
|
||||
|
||||
# Returns internal object state as a programmer-readable string.
|
||||
def inspect
|
||||
"#<#{self.class}: #{identifier}>"
|
||||
end
|
||||
|
||||
# Returns a friendlier version of the identifier. Set skip_first_part to
|
||||
|
||||
# Returns a friendlier version of the identifier. Set skip_first_part to
|
||||
# omit the first part of the identifier (typically a region name) where
|
||||
# there is more than one part.
|
||||
#
|
||||
@@ -245,13 +245,13 @@ module TZInfo
|
||||
# Timezone.get('Europe/Paris').friendly_identifier(false) #=> "Europe - Paris"
|
||||
# Timezone.get('Europe/Paris').friendly_identifier(true) #=> "Paris"
|
||||
# Timezone.get('America/Indiana/Knox').friendly_identifier(false) #=> "America - Knox, Indiana"
|
||||
# Timezone.get('America/Indiana/Knox').friendly_identifier(true) #=> "Knox, Indiana"
|
||||
# Timezone.get('America/Indiana/Knox').friendly_identifier(true) #=> "Knox, Indiana"
|
||||
def friendly_identifier(skip_first_part = false)
|
||||
parts = identifier.split('/')
|
||||
if parts.empty?
|
||||
# shouldn't happen
|
||||
identifier
|
||||
elsif parts.length == 1
|
||||
elsif parts.length == 1
|
||||
parts[0]
|
||||
else
|
||||
if skip_first_part
|
||||
@@ -259,47 +259,47 @@ module TZInfo
|
||||
else
|
||||
result = parts[0] + ' - '
|
||||
end
|
||||
|
||||
|
||||
parts[1, parts.length - 1].reverse_each {|part|
|
||||
part.gsub!(/_/, ' ')
|
||||
|
||||
|
||||
if part.index(/[a-z]/)
|
||||
# Missing a space if a lower case followed by an upper case and the
|
||||
# name isn't McXxxx.
|
||||
part.gsub!(/([^M][a-z])([A-Z])/, '\1 \2')
|
||||
part.gsub!(/([M][a-bd-z])([A-Z])/, '\1 \2')
|
||||
|
||||
|
||||
# Missing an apostrophe if two consecutive upper case characters.
|
||||
part.gsub!(/([A-Z])([A-Z])/, '\1\'\2')
|
||||
end
|
||||
|
||||
|
||||
result << part
|
||||
result << ', '
|
||||
}
|
||||
|
||||
|
||||
result.slice!(result.length - 2, 2)
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Returns the TimezonePeriod for the given UTC time. utc can either be
|
||||
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
|
||||
# information in utc is ignored (it is treated as a UTC time).
|
||||
def period_for_utc(utc)
|
||||
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
|
||||
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
|
||||
# information in utc is ignored (it is treated as a UTC time).
|
||||
def period_for_utc(utc)
|
||||
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
|
||||
end
|
||||
|
||||
|
||||
# Returns the set of TimezonePeriod instances that are valid for the given
|
||||
# local time as an array. If you just want a single period, use
|
||||
# local time as an array. If you just want a single period, use
|
||||
# period_for_local instead and specify how ambiguities should be resolved.
|
||||
# Returns an empty array if no periods are found for the given time.
|
||||
def periods_for_local(local)
|
||||
raise UnknownTimezone, 'TZInfo::Timezone constructed directly'
|
||||
end
|
||||
|
||||
|
||||
# Returns the TimezonePeriod for the given local time. local can either be
|
||||
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
|
||||
# information in local is ignored (it is treated as a time in the current
|
||||
# a DateTime, Time or integer timestamp (Time.to_i). Any timezone
|
||||
# information in local is ignored (it is treated as a time in the current
|
||||
# timezone).
|
||||
#
|
||||
# Warning: There are local times that have no equivalent UTC times (e.g.
|
||||
@@ -312,70 +312,70 @@ module TZInfo
|
||||
#
|
||||
# In the second case (more than one equivalent UTC time), an AmbiguousTime
|
||||
# exception will be raised unless the optional dst parameter or block
|
||||
# handles the ambiguity.
|
||||
# handles the ambiguity.
|
||||
#
|
||||
# If the ambiguity is due to a transition from daylight savings time to
|
||||
# standard time, the dst parameter can be used to select whether the
|
||||
# standard time, the dst parameter can be used to select whether the
|
||||
# daylight savings time or local time is used. For example,
|
||||
#
|
||||
# Timezone.get('America/New_York').period_for_local(DateTime.new(2004,10,31,1,30,0))
|
||||
#
|
||||
# would raise an AmbiguousTime exception.
|
||||
#
|
||||
# Specifying dst=true would the daylight savings period from April to
|
||||
# Specifying dst=true would the daylight savings period from April to
|
||||
# October 2004. Specifying dst=false would return the standard period
|
||||
# from October 2004 to April 2005.
|
||||
#
|
||||
# If the dst parameter does not resolve the ambiguity, and a block is
|
||||
# If the dst parameter does not resolve the ambiguity, and a block is
|
||||
# specified, it is called. The block must take a single parameter - an
|
||||
# array of the periods that need to be resolved. The block can select and
|
||||
# return a single period or return nil or an empty array
|
||||
# to cause an AmbiguousTime exception to be raised.
|
||||
def period_for_local(local, dst = nil)
|
||||
def period_for_local(local, dst = nil)
|
||||
results = periods_for_local(local)
|
||||
|
||||
|
||||
if results.empty?
|
||||
raise PeriodNotFound
|
||||
elsif results.size < 2
|
||||
results.first
|
||||
else
|
||||
# ambiguous result try to resolve
|
||||
|
||||
|
||||
if !dst.nil?
|
||||
matches = results.find_all {|period| period.dst? == dst}
|
||||
results = matches if !matches.empty?
|
||||
results = matches if !matches.empty?
|
||||
end
|
||||
|
||||
|
||||
if results.size < 2
|
||||
results.first
|
||||
else
|
||||
# still ambiguous, try the block
|
||||
|
||||
|
||||
if block_given?
|
||||
results = yield results
|
||||
end
|
||||
|
||||
|
||||
if results.is_a?(TimezonePeriod)
|
||||
results
|
||||
elsif results && results.size == 1
|
||||
results.first
|
||||
else
|
||||
else
|
||||
raise AmbiguousTime, "#{local} is an ambiguous local time."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Converts a time in UTC to the local timezone. utc can either be
|
||||
# a DateTime, Time or timestamp (Time.to_i). The returned time has the same
|
||||
# type as utc. Any timezone information in utc is ignored (it is treated as
|
||||
# type as utc. Any timezone information in utc is ignored (it is treated as
|
||||
# a UTC time).
|
||||
def utc_to_local(utc)
|
||||
TimeOrDateTime.wrap(utc) {|wrapped|
|
||||
period_for_utc(wrapped).to_local(wrapped)
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
# Converts a time in the local timezone to UTC. local can either be
|
||||
# a DateTime, Time or timestamp (Time.to_i). The returned time has the same
|
||||
# type as local. Any timezone information in local is ignored (it is treated
|
||||
@@ -391,10 +391,10 @@ module TZInfo
|
||||
#
|
||||
# In the second case (more than one equivalent UTC time), an AmbiguousTime
|
||||
# exception will be raised unless the optional dst parameter or block
|
||||
# handles the ambiguity.
|
||||
# handles the ambiguity.
|
||||
#
|
||||
# If the ambiguity is due to a transition from daylight savings time to
|
||||
# standard time, the dst parameter can be used to select whether the
|
||||
# standard time, the dst parameter can be used to select whether the
|
||||
# daylight savings time or local time is used. For example,
|
||||
#
|
||||
# Timezone.get('America/New_York').local_to_utc(DateTime.new(2004,10,31,1,30,0))
|
||||
@@ -404,7 +404,7 @@ module TZInfo
|
||||
# Specifying dst=true would return 2004-10-31 5:30:00. Specifying dst=false
|
||||
# would return 2004-10-31 6:30:00.
|
||||
#
|
||||
# If the dst parameter does not resolve the ambiguity, and a block is
|
||||
# If the dst parameter does not resolve the ambiguity, and a block is
|
||||
# specified, it is called. The block must take a single parameter - an
|
||||
# array of the periods that need to be resolved. The block can return a
|
||||
# single period to use to convert the time or return nil or an empty array
|
||||
@@ -416,21 +416,21 @@ module TZInfo
|
||||
else
|
||||
period = period_for_local(wrapped, dst)
|
||||
end
|
||||
|
||||
|
||||
period.to_utc(wrapped)
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
# Returns the current time in the timezone as a Time.
|
||||
def now
|
||||
utc_to_local(Time.now.utc)
|
||||
end
|
||||
|
||||
# Returns the TimezonePeriod for the current time.
|
||||
|
||||
# Returns the TimezonePeriod for the current time.
|
||||
def current_period
|
||||
period_for_utc(Time.now.utc)
|
||||
end
|
||||
|
||||
|
||||
# Returns the current Time and TimezonePeriod as an array. The first element
|
||||
# is the time, the second element is the period.
|
||||
def current_period_and_time
|
||||
@@ -438,19 +438,19 @@ module TZInfo
|
||||
period = period_for_utc(utc)
|
||||
[period.to_local(utc), period]
|
||||
end
|
||||
|
||||
|
||||
alias :current_time_and_period :current_period_and_time
|
||||
|
||||
# Converts a time in UTC to local time and returns it as a string
|
||||
# according to the given format. The formatting is identical to
|
||||
# Converts a time in UTC to local time and returns it as a string
|
||||
# according to the given format. The formatting is identical to
|
||||
# Time.strftime and DateTime.strftime, except %Z is replaced with the
|
||||
# timezone abbreviation for the specified time (for example, EST or EDT).
|
||||
def strftime(format, utc = Time.now.utc)
|
||||
# timezone abbreviation for the specified time (for example, EST or EDT).
|
||||
def strftime(format, utc = Time.now.utc)
|
||||
period = period_for_utc(utc)
|
||||
local = period.to_local(utc)
|
||||
local = period.to_local(utc)
|
||||
local = Time.at(local).utc unless local.kind_of?(Time) || local.kind_of?(DateTime)
|
||||
abbreviation = period.abbreviation.to_s.gsub(/%/, '%%')
|
||||
|
||||
|
||||
format = format.gsub(/(.?)%Z/) do
|
||||
if $1 == '%'
|
||||
# return %%Z so the real strftime treats it as a literal %Z too
|
||||
@@ -459,50 +459,50 @@ module TZInfo
|
||||
"#$1#{abbreviation}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local.strftime(format)
|
||||
end
|
||||
|
||||
|
||||
# Compares two Timezones based on their identifier. Returns -1 if tz is less
|
||||
# than self, 0 if tz is equal to self and +1 if tz is greater than self.
|
||||
def <=>(tz)
|
||||
identifier <=> tz.identifier
|
||||
end
|
||||
|
||||
# Returns true if and only if the identifier of tz is equal to the
|
||||
|
||||
# Returns true if and only if the identifier of tz is equal to the
|
||||
# identifier of this Timezone.
|
||||
def eql?(tz)
|
||||
self == tz
|
||||
end
|
||||
|
||||
|
||||
# Returns a hash of this Timezone.
|
||||
def hash
|
||||
identifier.hash
|
||||
end
|
||||
|
||||
|
||||
# Dumps this Timezone for marshalling.
|
||||
def _dump(limit)
|
||||
identifier
|
||||
end
|
||||
|
||||
|
||||
# Loads a marshalled Timezone.
|
||||
def self._load(data)
|
||||
Timezone.get(data)
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
# Loads in the index of timezones if it hasn't already been loaded.
|
||||
def self.load_index
|
||||
unless @@index_loaded
|
||||
require 'tzinfo/indexes/timezones'
|
||||
@@index_loaded = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Returns an array of proxies corresponding to the given array of
|
||||
|
||||
# Returns an array of proxies corresponding to the given array of
|
||||
# identifiers.
|
||||
def self.get_proxies(identifiers)
|
||||
identifiers.collect {|identifier| get_proxy(identifier)}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,7 +25,7 @@ task :default => :test
|
||||
## This is required until the regular test task
|
||||
## below passes. It's not ideal, but at least
|
||||
## we can see the failures
|
||||
task :test do
|
||||
task :test do
|
||||
Dir['test/**/*_test.rb'].all? do |file|
|
||||
ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME'))
|
||||
system(ruby, '-Itest', file)
|
||||
@@ -38,7 +38,7 @@ Rake::TestTask.new("regular_test") do |t|
|
||||
end
|
||||
|
||||
|
||||
BASE_DIRS = %w(
|
||||
BASE_DIRS = %w(
|
||||
app
|
||||
config/environments
|
||||
config/initializers
|
||||
@@ -158,21 +158,10 @@ end
|
||||
# Copy Ties Content -----------------------------------------------------------------------
|
||||
|
||||
desc "Make copies of all the default content of ties"
|
||||
task :copy_ties_content => [
|
||||
:copy_rootfiles, :copy_dispatches, :copy_html_files, :copy_application,
|
||||
task :copy_ties_content => [
|
||||
:copy_rootfiles, :copy_html_files, :copy_application,
|
||||
:copy_configs, :copy_binfiles, :copy_test_helpers, :copy_app_doc_readme ]
|
||||
|
||||
task :copy_dispatches do
|
||||
copy_with_rewritten_ruby_path("dispatches/dispatch.rb", "#{PKG_DESTINATION}/public/dispatch.rb")
|
||||
chmod 0755, "#{PKG_DESTINATION}/public/dispatch.rb"
|
||||
|
||||
copy_with_rewritten_ruby_path("dispatches/dispatch.rb", "#{PKG_DESTINATION}/public/dispatch.cgi")
|
||||
chmod 0755, "#{PKG_DESTINATION}/public/dispatch.cgi"
|
||||
|
||||
copy_with_rewritten_ruby_path("dispatches/dispatch.fcgi", "#{PKG_DESTINATION}/public/dispatch.fcgi")
|
||||
chmod 0755, "#{PKG_DESTINATION}/public/dispatch.fcgi"
|
||||
end
|
||||
|
||||
task :copy_html_files do
|
||||
HTML_FILES.each { |file| cp File.join('html', file), File.join(PKG_DESTINATION, 'public', file) }
|
||||
end
|
||||
@@ -187,7 +176,7 @@ task :copy_configs do
|
||||
socket = nil
|
||||
require 'erb'
|
||||
File.open("#{PKG_DESTINATION}/config/database.yml", 'w') {|f| f.write ERB.new(IO.read("configs/databases/sqlite3.yml"), nil, '-').result(binding)}
|
||||
|
||||
|
||||
cp "configs/routes.rb", "#{PKG_DESTINATION}/config/routes.rb"
|
||||
|
||||
cp "configs/initializers/backtrace_silencers.rb", "#{PKG_DESTINATION}/config/initializers/backtrace_silencers.rb"
|
||||
@@ -288,15 +277,15 @@ end
|
||||
|
||||
PKG_FILES = FileList[
|
||||
'[a-zA-Z]*',
|
||||
'bin/**/*',
|
||||
'bin/**/*',
|
||||
'builtin/**/*',
|
||||
'configs/**/*',
|
||||
'doc/**/*',
|
||||
'dispatches/**/*',
|
||||
'environments/**/*',
|
||||
'helpers/**/*',
|
||||
'generators/**/*',
|
||||
'html/**/*',
|
||||
'configs/**/*',
|
||||
'doc/**/*',
|
||||
'dispatches/**/*',
|
||||
'environments/**/*',
|
||||
'helpers/**/*',
|
||||
'generators/**/*',
|
||||
'html/**/*',
|
||||
'lib/**/*'
|
||||
] - [ 'test' ]
|
||||
|
||||
@@ -336,7 +325,7 @@ end
|
||||
|
||||
# Publishing -------------------------------------------------------
|
||||
desc "Publish the rails gem"
|
||||
task :pgem => [:gem] do
|
||||
task :pgem => [:gem] do
|
||||
Rake::SshFilePublisher.new("gems.rubyonrails.org", "/u/sites/gems/gems", "pkg", "#{PKG_FILE_NAME}.gem").upload
|
||||
`ssh gems.rubyonrails.org '/u/sites/gems/gemupdate.sh'`
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
vars = template_options[:assigns] || {}
|
||||
b = template_options[:binding] || binding
|
||||
vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
|
||||
if b.respond_to?(:local_variable_set)
|
||||
vars.each { |k,v| b.local_variable_set(k, v) }
|
||||
else
|
||||
vars.each { |k,v| eval "#{k} = vars[:#{k}] || vars['#{k}']", b }
|
||||
end
|
||||
|
||||
# Render the source file with the temporary binding.
|
||||
ERB.new(file.read, nil, '-').result(b)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'rbconfig'
|
||||
require File.dirname(__FILE__) + '/template_runner'
|
||||
require 'digest/md5'
|
||||
require 'digest/md5'
|
||||
require 'active_support/secure_random'
|
||||
|
||||
class AppGenerator < Rails::Generator::Base
|
||||
@@ -110,12 +110,12 @@ class AppGenerator < Rails::Generator::Base
|
||||
tmp/pids
|
||||
).each { |path| m.directory(path) }
|
||||
end
|
||||
|
||||
|
||||
def create_root_files(m)
|
||||
m.file "fresh_rakefile", "Rakefile"
|
||||
m.file "README", "README"
|
||||
end
|
||||
|
||||
|
||||
def create_app_files(m)
|
||||
m.file "helpers/application_controller.rb", "app/controllers/application_controller.rb"
|
||||
m.file "helpers/application_helper.rb", "app/helpers/application_helper.rb"
|
||||
@@ -138,7 +138,7 @@ class AppGenerator < Rails::Generator::Base
|
||||
%w( server production development test ).each do |file|
|
||||
m.file "configs/empty.log", "log/#{file}.log", :chmod => 0666
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_public_files(m)
|
||||
create_dispatch_files(m)
|
||||
@@ -148,14 +148,14 @@ class AppGenerator < Rails::Generator::Base
|
||||
create_rails_image(m)
|
||||
create_javascript_files(m)
|
||||
end
|
||||
|
||||
|
||||
def create_script_files(m)
|
||||
%w(
|
||||
%w(
|
||||
about console dbconsole destroy generate runner server plugin
|
||||
performance/benchmarker performance/profiler
|
||||
).each do |file|
|
||||
m.file "bin/#{file}", "script/#{file}", {
|
||||
:chmod => 0755,
|
||||
m.file "bin/#{file}", "script/#{file}", {
|
||||
:chmod => 0755,
|
||||
:shebang => options[:shebang] == DEFAULT_SHEBANG ? nil : options[:shebang]
|
||||
}
|
||||
end
|
||||
@@ -172,7 +172,7 @@ class AppGenerator < Rails::Generator::Base
|
||||
:app_name => @app_name,
|
||||
:socket => options[:db] == "mysql" ? mysql_socket_location : nil }
|
||||
end
|
||||
|
||||
|
||||
def create_routes_file(m)
|
||||
m.file "configs/routes.rb", "config/routes.rb"
|
||||
end
|
||||
@@ -182,19 +182,19 @@ class AppGenerator < Rails::Generator::Base
|
||||
end
|
||||
|
||||
def create_initializer_files(m)
|
||||
%w(
|
||||
backtrace_silencers
|
||||
inflections
|
||||
mime_types
|
||||
%w(
|
||||
backtrace_silencers
|
||||
inflections
|
||||
mime_types
|
||||
new_rails_defaults
|
||||
).each do |initializer|
|
||||
m.file "configs/initializers/#{initializer}.rb", "config/initializers/#{initializer}.rb"
|
||||
end
|
||||
|
||||
m.template "configs/initializers/session_store.rb", "config/initializers/session_store.rb",
|
||||
m.template "configs/initializers/session_store.rb", "config/initializers/session_store.rb",
|
||||
:assigns => { :app_name => @app_name, :app_secret => ActiveSupport::SecureRandom.hex(64) }
|
||||
|
||||
m.template "configs/initializers/cookie_verification_secret.rb", "config/initializers/cookie_verification_secret.rb",
|
||||
m.template "configs/initializers/cookie_verification_secret.rb", "config/initializers/cookie_verification_secret.rb",
|
||||
:assigns => { :app_secret => ActiveSupport::SecureRandom.hex(64) }
|
||||
end
|
||||
|
||||
@@ -203,7 +203,7 @@ class AppGenerator < Rails::Generator::Base
|
||||
end
|
||||
|
||||
def create_environment_files(m)
|
||||
m.template "environments/environment.rb", "config/environment.rb",
|
||||
m.template "environments/environment.rb", "config/environment.rb",
|
||||
:assigns => { :freeze => options[:freeze] }
|
||||
|
||||
m.file "environments/boot.rb", "config/boot.rb"
|
||||
@@ -218,9 +218,6 @@ class AppGenerator < Rails::Generator::Base
|
||||
dispatcher_options = { :chmod => 0755, :shebang => options[:shebang] }
|
||||
|
||||
m.file "dispatches/config.ru", "config.ru"
|
||||
m.file "dispatches/dispatch.rb", "public/dispatch.rb", dispatcher_options
|
||||
m.file "dispatches/dispatch.rb", "public/dispatch.cgi", dispatcher_options
|
||||
m.file "dispatches/dispatch.fcgi", "public/dispatch.fcgi", dispatcher_options
|
||||
end
|
||||
end
|
||||
|
||||
@@ -263,4 +260,4 @@ class AppGenerator < Rails::Generator::Base
|
||||
"/opt/lampp/var/mysql/mysql.sock" # xampp for linux
|
||||
].find { |f| File.exist?(f) } unless RUBY_PLATFORM =~ /(:?mswin|mingw)/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace :rails do
|
||||
|
||||
local = Dir["#{local_base}/**/*"].reject { |path| File.directory?(path) }
|
||||
edge = Dir["#{edge_base}/**/*"].reject { |path| File.directory?(path) }
|
||||
|
||||
|
||||
edge.each do |script|
|
||||
base_name = script[(edge_base.length+1)..-1]
|
||||
next if base_name == "rails"
|
||||
@@ -111,7 +111,7 @@ namespace :rails do
|
||||
|
||||
desc "Update your javascripts from your current rails install"
|
||||
task :javascripts do
|
||||
require 'railties_path'
|
||||
require 'railties_path'
|
||||
project_dir = RAILS_ROOT + '/public/javascripts/'
|
||||
scripts = Dir[RAILTIES_PATH + '/html/javascripts/*.js']
|
||||
scripts.reject!{|s| File.basename(s) == 'application.js'} if File.exist?(project_dir + 'application.js')
|
||||
@@ -120,10 +120,10 @@ namespace :rails do
|
||||
|
||||
desc "Update config/boot.rb from your current rails install"
|
||||
task :configs do
|
||||
require 'railties_path'
|
||||
require 'railties_path'
|
||||
FileUtils.cp(RAILTIES_PATH + '/environments/boot.rb', RAILS_ROOT + '/config/boot.rb')
|
||||
end
|
||||
|
||||
|
||||
desc "Rename application.rb to application_controller.rb"
|
||||
task :application_controller do
|
||||
old_style = RAILS_ROOT + '/app/controllers/application.rb'
|
||||
@@ -133,14 +133,11 @@ namespace :rails do
|
||||
puts "#{old_style} has been renamed to #{new_style}, update your SCM as necessary"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
desc "Generate dispatcher files in RAILS_ROOT/public"
|
||||
task :generate_dispatchers do
|
||||
require 'railties_path'
|
||||
FileUtils.cp(RAILTIES_PATH + '/dispatches/config.ru', RAILS_ROOT + '/config.ru')
|
||||
FileUtils.cp(RAILTIES_PATH + '/dispatches/dispatch.fcgi', RAILS_ROOT + '/public/dispatch.fcgi')
|
||||
FileUtils.cp(RAILTIES_PATH + '/dispatches/dispatch.rb', RAILS_ROOT + '/public/dispatch.rb')
|
||||
FileUtils.cp(RAILTIES_PATH + '/dispatches/dispatch.rb', RAILS_ROOT + '/public/dispatch.cgi')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -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
|
||||
@@ -4,4 +4,4 @@ set -x
|
||||
set -e
|
||||
|
||||
script/cibuild-on 1.9.3-p231-tcs-github
|
||||
script/cibuild-on 2.0.0-github
|
||||
script/cibuild-on 2.1.0-github
|
||||
|
||||
Reference in New Issue
Block a user