update padrino.

This commit is contained in:
Thomas Reynolds 2014-02-18 18:30:29 -08:00
parent 049866ee2e
commit 3995ad850c
268 changed files with 2330 additions and 1477 deletions

View file

@ -1,6 +1,10 @@
master
===
* Update Padrino to 0.12.0. Introduces BREAKING CHANGE for Haml. Helpers which take blocks used to require `-` instead of `=` to work correctly. Now, all helpers which output content should use `=`. See: http://www.padrinorb.com/blog/upgrading-padrino-from-0-11-x-to-0-12-0-guide
* Depend on Erubis and remove support for specifying another ERb engine.
* Removed the ability to set the `sass_cache_path`.
3.2.2
===

View file

@ -22,7 +22,7 @@ gem 'asciidoctor', :require => false
platforms :ruby do
gem 'therubyracer'
gem 'redcarpet', '~> 3.0'
gem 'redcarpet', '~> 3.1'
gem 'pry', :require => false, :group => :development
gem 'pry-debugger', :require => false, :group => :development
end

View file

@ -86,8 +86,8 @@ Feature: Markdown (Redcarpet) support
Then I should see "![dust mite](http://dust.mite/image.png)"
And I should not see "<img"
When I go to "/with_toc_data.html"
Then I should see 'id="toc_0"'
And I should see 'id="toc_1"'
Then I should see 'id="first-header"'
And I should see 'id="second-header"'
When I go to "/hard_wrap.html"
Then I should see "br"
When I go to "/link.html"

View file

@ -1,22 +0,0 @@
Feature: SASS .sass-cache custom location
Scenario: Using the default location for .sass-cache folder
Given the Server is running at "sass-cache-path-default-app"
When I go to "/stylesheets/plain.css"
Then I should see "color: blue;"
# TODO::
# Not sure how to test this location, as the directory is stored outside of the app root
# during testing, but inside app root in "production"
# Then a directory named ".sass-cache" should exist
Scenario: Using a custom location for .sass-cache folder
Given the Server is running at "sass-cache-path-custom-app"
When I go to "/stylesheets/plain.css"
Then I should see "html, body, div, span, applet, object, iframe,"
Then a directory named "/tmp/middleman-core-custom-sass_cache_path" should exist

View file

@ -5,6 +5,5 @@
</head>
<body>
<%= yield %>
</body>
</html>

View file

@ -1,4 +1,4 @@
- capture_html :from_template do
= capture_html :from_template do
%h1= "I am the yielded content haml"
%p I am in the template

View file

@ -1,4 +1,4 @@
- content_for :from_template do
= content_for :from_template do
= "I am the yielded content haml <s>with html tags</s>"
%p I am in the template

View file

@ -1,2 +1 @@
<%= yield %>
Override.
<%= yield %> Override.

View file

@ -1,2 +1 @@
<%= yield %>
Override.
<%= yield %> Override.

View file

@ -1,2 +1,2 @@
- link_to "/" do
= link_to "/" do
%s haml with html tags

View file

@ -1,2 +1,2 @@
- link_to "/" do
= link_to "/" do
s slim with html tags

View file

@ -1,3 +1,3 @@
- wrap_layout :outer_haml do
= wrap_layout :outer_haml do
Inner
= yield

View file

@ -1,3 +1,3 @@
- wrap_layout :outer_slim do
= wrap_layout :outer_slim do
h3 Inner
== yield

View file

@ -1,3 +1,3 @@
- wrap_layout :master_haml do
= wrap_layout :master_haml do
Outer
= yield

View file

@ -1,3 +1,3 @@
- wrap_layout :master_slim do
= wrap_layout :master_slim do
h2 Outer
== yield

View file

@ -1,3 +0,0 @@
set :sass_cache_path, File.join('/tmp', "#{File.basename(Dir.pwd)}-custom-sass_cache_path")

View file

@ -1,4 +0,0 @@
@import "compass/reset"
red
color: blue

View file

@ -1,3 +0,0 @@
# Using default setting
# set :sass_cache_path, File.join(Dir.pwd, '.sass-cache')

View file

@ -1,4 +0,0 @@
@import "compass/reset"
red
color: blue

View file

@ -6,7 +6,7 @@ require 'i18n'
# Don't fail on invalid locale, that's not what our current
# users expect.
::I18n.config.enforce_available_locales = false
::I18n.enforce_available_locales = false
# Use ActiveSupport JSON
require 'active_support/json'

View file

@ -1,6 +1,6 @@
if !defined?(::Padrino::Helpers)
require 'vendored-middleman-deps/padrino-core-0.11.4/lib/padrino-core/support_lite'
require 'vendored-middleman-deps/padrino-helpers-0.11.4/lib/padrino-helpers'
require 'vendored-middleman-deps/padrino-core-0.12.0/lib/padrino-core/support_lite'
require 'vendored-middleman-deps/padrino-helpers-0.12.0/lib/padrino-helpers'
end
module Middleman

View file

@ -7,30 +7,28 @@ module Middleman
# once registered
def registered(app)
# Setup a default ERb engine
app.config.define_setting :erb_engine, :erb, 'The engine to use for rendering ERb templates'
app.config.define_setting :erb_engine_prefix, ::Tilt, 'The parent module for ERb template engines'
app.before_configuration do
template_extensions :erb => :html
end
# After config
app.after_configuration do
# Find the user's prefered engine
# Convert symbols to classes
if config[:erb_engine].is_a? Symbol
engine = engine.to_s
engine = engine == 'erb' ? 'ERB' : engine.camelize
config[:erb_engine] = config[:erb_engine_prefix].const_get("#{engine}Template")
end
# Tell Tilt to use the preferred engine
::Tilt.prefer(config[:erb_engine])
::Tilt.prefer(Template, :erb)
end
end
alias :included :registered
end
class Template < ::Tilt::ErubisTemplate
##
# In preamble we need a flag `__in_erb_template` and SafeBuffer for padrino apps.
#
def precompiled_preamble(locals)
original = super
"__in_erb_template = true\n" << original
#.rpartition("\n").first << "#{@outvar} = _buf = ActiveSupport::SafeBuffer.new\n"
end
end
end
end
end

View file

@ -1,6 +1,16 @@
# Require gem
require 'haml'
module SafeTemplate
def render(*)
super.html_safe
end
end
class Tilt::HamlTemplate
include SafeTemplate
end
module Middleman
module Renderers

View file

@ -14,10 +14,6 @@ module Middleman
# Default sass options
app.config.define_setting :sass, {}, 'Sass engine options'
# Location of SASS .sass-cache directory.
# @return [String]
app.config.define_setting :sass_cache_path, File.join(app.root_path, '.sass-cache'), 'Location of sass cache' # runtime compile of path
app.before_configuration do
template_extensions :scss => :css,
:sass => :css

View file

@ -1,6 +1,20 @@
# Load gem
require 'slim'
module SafeTemplate
def render(*)
super.html_safe
end
end
class Slim::Template
include SafeTemplate
def precompiled_preamble(locals)
"__in_slim_template = true\n" << super
end
end
module Middleman
module Renderers
@ -29,10 +43,9 @@ module Middleman
:context => self
}
slim_embedded = defined?(::Slim::Embedded) ? ::Slim::Embedded : ::Slim::EmbeddedEngine
::Slim::Embedded::SassEngine.disable_option_validator!
%w(sass scss markdown).each do |engine|
slim_embedded.default_options[engine.to_sym] = context_hack
::Slim::Embedded.default_options[engine.to_sym] = context_hack
end
end
end

View file

@ -1,5 +1,5 @@
module Middleman
# Current Version
# @return [String]
VERSION = '3.2.2' unless const_defined?(:VERSION)
VERSION = '3.3.0' unless const_defined?(:VERSION)
end

View file

@ -21,7 +21,6 @@ class Middleman::CoreExtensions::Compass < ::Middleman::Extension
compass_config.project_path = app.source_dir
compass_config.environment = :development
compass_config.cache = false
# compass_config.cache_path = app.config[:sass_cache_path]
compass_config.sass_dir = app.config[:css_dir]
compass_config.css_dir = app.config[:css_dir]
compass_config.javascripts_dir = app.config[:js_dir]

View file

@ -1,16 +1,20 @@
if !defined?(::Padrino::Helpers)
require 'vendored-middleman-deps/padrino-core-0.11.4/lib/padrino-core/support_lite'
require 'vendored-middleman-deps/padrino-helpers-0.11.4/lib/padrino-helpers'
require 'vendored-middleman-deps/padrino-core-0.12.0/lib/padrino-core/support_lite'
require 'vendored-middleman-deps/padrino-helpers-0.12.0/lib/padrino-helpers'
# Don't fail on invalid locale, that's not what our current
# users expect.
::I18n.enforce_available_locales = false
end
class Padrino::Helpers::OutputHelpers::ErbHandler
# Force Erb capture not to use safebuffer
def capture_from_template(*args, &block)
self.output_buffer, _buf_was = '', self.output_buffer
captured_block = block.call(*args)
ret = eval('@_out_buf', block.binding)
raw = block.call(*args)
captured = template.instance_variable_get(:@_out_buf)
self.output_buffer = _buf_was
[ ret, captured_block ]
engine_matches?(block) ? captured : raw
end
end
@ -48,6 +52,7 @@ class Middleman::CoreExtensions::DefaultHelpers < ::Middleman::Extension
attributes = tag_attributes(options)
output = ActiveSupport::SafeBuffer.new
output.safe_concat "<#{name}#{attributes}>"
if content.respond_to?(:each) && !content.is_a?(String)
content.each { |c| output.safe_concat c; output.safe_concat ::Padrino::Helpers::TagHelpers::NEWLINE }
else
@ -59,19 +64,18 @@ class Middleman::CoreExtensions::DefaultHelpers < ::Middleman::Extension
end
def capture_html(*args, &block)
handler = auto_find_proper_handler(&block)
captured_block, captured_html = nil, ''
if handler && handler.is_type? && handler.block_is_type?(block)
captured_html, captured_block = handler.capture_from_template(*args, &block)
if handler = auto_find_proper_handler(&block)
handler.capture_from_template(*args, &block)
else
block.call(*args)
end
# invoking the block directly if there was no template
captured_html = block_given? && ( captured_block || block.call(*args) ) if captured_html.blank?
captured_html
end
def auto_find_proper_handler(&block)
engine = block_given? ? File.extname(block.source_location[0])[1..-1].to_sym : current_engine
::Padrino::Helpers::OutputHelpers.handlers.map { |h| h.new(self) }.find { |h| h.engines.include?(engine) && h.is_type? }
return if engine == :slim && engine == current_engine
handler_class = ::Padrino::Helpers::OutputHelpers.handlers[engine]
handler_class && handler_class.new(self)
end
# Disable Padrino cache buster

View file

@ -1,14 +0,0 @@
begin
require 'slim'
if defined? Padrino::Rendering
Padrino::Rendering.engine_configurations[:slim] =
{:generator => Temple::Generators::RailsOutputBuffer,
:buffer => "@_out_buf", :use_html_safe => true}
class Slim::Template
include Padrino::Rendering::SafeTemplate
end
end
rescue LoadError
end

View file

@ -1,341 +0,0 @@
require 'pathname'
module Padrino
##
# High performance source code reloader middleware
#
module Reloader
##
# This reloader is suited for use in a many environments because each file
# will only be checked once and only one system call to stat(2) is made.
#
# Please note that this will not reload files in the background, and does so
# only when explicitly invoked.
#
# The modification times for every file in a project.
MTIMES = {}
# The list of files loaded as part of a project.
LOADED_FILES = {}
# The list of object constants and classes loaded as part of the project.
LOADED_CLASSES = {}
class << self
##
# Specified folders can be excluded from the code reload detection process.
# Default excluded directories at Padrino.root are: test, spec, features, tmp, config, db and public
#
def exclude
@_exclude ||= %w(test spec tmp features config public db).map { |path| Padrino.root(path) }
end
##
# Specified constants can be excluded from the code unloading process.
#
def exclude_constants
@_exclude_constants ||= Set.new
end
##
# Specified constants can be configured to be reloaded on every request.
# Default included constants are: [none]
#
def include_constants
@_include_constants ||= Set.new
end
##
# Reload all files with changes detected.
#
def reload!
# Detect changed files
rotation do |file, mtime|
# Retrive the last modified time
new_file = MTIMES[file].nil?
previous_mtime = MTIMES[file] ||= mtime
logger.devel "Detected a new file #{file}" if new_file
# We skip to next file if it is not new and not modified
next unless new_file || mtime > previous_mtime
# Now we can reload our file
apps = mounted_apps_of(file)
if apps.present?
apps.each { |app| app.app_obj.reload! }
else
safe_load(file, :force => new_file)
# Reload also apps
Padrino.mounted_apps.each do |app|
app.app_obj.reload! if app.app_obj.dependencies.include?(file)
end
end
end
end
##
# Remove files and classes loaded with stat
#
def clear!
clear_modification_times
clear_loaded_classes
clear_loaded_files_and_features
end
##
# Returns true if any file changes are detected and populates the MTIMES cache
#
def changed?
changed = false
rotation do |file, mtime|
new_file = MTIMES[file].nil?
previous_mtime = MTIMES[file]
changed = true if new_file || mtime > previous_mtime
end
changed
end
alias :run! :changed?
##
# We lock dependencies sets to prevent reloading of protected constants
#
def lock!
klasses = ObjectSpace.classes do |klass|
klass._orig_klass_name.split('::')[0]
end
klasses = klasses | Padrino.mounted_apps.map { |app| app.app_class }
Padrino::Reloader.exclude_constants.merge(klasses)
end
##
# A safe Kernel::require which issues the necessary hooks depending on results
#
def safe_load(file, options={})
began_at = Time.now
force = options[:force]
file = figure_path(file)
reload = should_reload?(file)
m_time = modification_time(file)
return if !force && m_time && !reload
remove_loaded_file_classes(file)
remove_loaded_file_features(file)
# Duplicate objects and loaded features before load file
klasses = ObjectSpace.classes
files = Set.new($LOADED_FEATURES.dup)
reload_deps_of_file(file)
# And finally load the specified file
begin
logger.devel :loading, began_at, file if !reload
logger.debug :reload, began_at, file if reload
$LOADED_FEATURES.delete(file) if files.include?(file)
Padrino::Utils.silence_output
loaded = false
require(file)
loaded = true
update_modification_time(file)
rescue SyntaxError => e
logger.error "Cannot require #{file} due to a syntax error: #{e.message}"
ensure
Padrino::Utils.unsilence_output
new_constants = ObjectSpace.new_classes(klasses)
if loaded
process_loaded_file(:file => file,
:constants => new_constants,
:files => files)
else
logger.devel "Failed to load #{file}; removing partially defined constants"
unload_constants(new_constants)
end
end
end
##
# Returns true if the file is defined in our padrino root.
#
def figure_path(file)
return file if Pathname.new(file).absolute?
$:.each do |path|
found = File.join(path, file)
return File.expand_path(found) if File.exist?(found)
end
file
end
##
# Removes the specified class and constant.
#
def remove_constant(const)
return if exclude_constants.any? { |c| const._orig_klass_name.index(c) == 0 } &&
!include_constants.any? { |c| const._orig_klass_name.index(c) == 0 }
begin
parts = const.to_s.sub(/^::(Object)?/, 'Object::').split('::')
object = parts.pop
base = parts.empty? ? Object : Inflector.constantize(parts * '::')
base.send :remove_const, object
logger.devel "Removed constant: #{const} from #{base}"
rescue NameError; end
end
private
###
# Clear instance variables that keep track of # loaded features/files/mtimes.
#
def clear_modification_times
MTIMES.clear
end
def clear_loaded_classes
LOADED_CLASSES.each do |file, klasses|
klasses.each { |klass| remove_constant(klass) }
LOADED_CLASSES.delete(file)
end
end
def clear_loaded_files_and_features
LOADED_FILES.each do |file, dependencies|
dependencies.each { |dependency| $LOADED_FEATURES.delete(dependency) }
$LOADED_FEATURES.delete(file)
end
end
###
# Macro for mtime query.
#
def modification_time(file)
MTIMES[file]
end
###
# Macro for mtime update.
#
def update_modification_time(file)
MTIMES[file] = File.mtime(file)
end
###
# Tracks loaded file features/classes/constants:
#
def process_loaded_file(*args)
options = args.extract_options!
new_constants = options[:constants]
files = options[:files]
file = options[:file]
# Store the file details
LOADED_CLASSES[file] = new_constants
LOADED_FILES[file] = Set.new($LOADED_FEATURES) - files - [file]
# Track only features in our Padrino.root
LOADED_FILES[file].delete_if { |feature| !in_root?(feature) }
end
###
# Unloads all constants in new_constants.
#
def unload_constants(new_constants)
new_constants.each { |klass| remove_constant(klass) }
end
###
# Safe load dependencies of a file.
#
def reload_deps_of_file(file)
if features = LOADED_FILES.delete(file)
features.each { |feature| safe_load(feature, :force => true) }
end
end
##
# Check if file was changed or if force a reload.
#
def should_reload?(file)
MTIMES[file] && File.mtime(file) > MTIMES[file]
end
##
# Removes all classes declared in the specified file.
#
def remove_loaded_file_classes(file)
if klasses = LOADED_CLASSES.delete(file)
klasses.each { |klass| remove_constant(klass) }
end
end
##
# Remove all loaded fatures with our file.
#
def remove_loaded_file_features(file)
if features = LOADED_FILES[file]
features.each { |feature| $LOADED_FEATURES.delete(feature) }
end
end
##
# Return the mounted_apps providing the app location.
# Can be an array because in one app.rb we can define multiple Padrino::Application.
#
def mounted_apps_of(file)
file = figure_path(file)
Padrino.mounted_apps.find_all { |app| File.identical?(file, app.app_file) }
end
##
# Returns true if file is in our Padrino.root.
#
def in_root?(file)
# This is better but slow:
# Pathname.new(Padrino.root).find { |f| File.identical?(Padrino.root(f), figure_path(file)) }
figure_path(file).index(Padrino.root) == 0
end
##
# Searches Ruby files in your +Padrino.load_paths+ , Padrino::Application.load_paths
# and monitors them for any changes.
#
def rotation
files_for_rotation.uniq.map do |file|
file = File.expand_path(file)
next if Padrino::Reloader.exclude.any? { |base| file.index(base) == 0 } || !File.exist?(file)
yield file, File.mtime(file)
end.compact
end
##
# Creates an array of paths for use in #rotation.
#
def files_for_rotation
files = Padrino.load_paths.map { |path| Dir["#{path}/**/*.rb"] }.flatten
files = files | Padrino.mounted_apps.map { |app| app.app_file }
files = files | Padrino.mounted_apps.map { |app| app.app_obj.dependencies }.flatten
end
end # self
##
# This class acts as a Rack middleware to be added to the application stack.
# This middleware performs a check and reload for source files at the start
# of each request, but also respects a specified cool down time
# during which no further action will be taken.
#
class Rack
def initialize(app, cooldown=1)
@app = app
@cooldown = cooldown
@last = (Time.now - cooldown)
end
# Invoked in order to perform the reload as part of the request stack.
def call(env)
if @cooldown && Time.now > @last + @cooldown
Thread.list.size > 1 ? Thread.exclusive { Padrino.reload! } : Padrino.reload!
@last = Time.now
end
@app.call(env)
end
end
end
end

View file

@ -1,80 +0,0 @@
require File.expand_path(File.dirname(__FILE__) + '/helper')
describe "Application" do
before { Padrino.clear! }
after { remove_views }
context 'CSRF protection' do
context "with CSRF protection on" do
before do
mock_app do
enable :sessions
enable :protect_from_csrf
post('/'){ 'HI' }
end
end
should "not allow requests without tokens" do
post "/"
assert_equal 403, status
end
should "allow requests with correct tokens" do
post "/", {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "a"}
assert_equal 200, status
end
should "not allow requests with incorrect tokens" do
post "/", {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "b"}
assert_equal 403, status
end
end
context "without CSRF protection on" do
before do
mock_app do
enable :sessions
disable :protect_from_csrf
post('/'){ 'HI' }
end
end
should "allows requests without tokens" do
post "/"
assert_equal 200, status
end
should "allow requests with correct tokens" do
post "/", {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "a"}
assert_equal 200, status
end
should "allow requests with incorrect tokens" do
post "/", {"authenticity_token" => "a"}, 'rack.session' => {:csrf => "b"}
assert_equal 200, status
end
end
context "with optional CSRF protection" do
before do
mock_app do
enable :sessions
enable :protect_from_csrf
set :allow_disabled_csrf, true
post('/on') { 'HI' }
post('/off', :csrf_protection => false) { 'HI' }
end
end
should "allow access to routes with csrf_protection off" do
post "/off"
assert_equal 200, status
end
should "not allow access to routes with csrf_protection on" do
post "/on"
assert_equal 403, status
end
end
end
end

View file

@ -14,14 +14,19 @@ require 'padrino-core/server'
require 'padrino-core/tasks'
require 'padrino-core/module'
PADRINO_ENV = ENV["PADRINO_ENV"] ||= ENV["RACK_ENV"] ||= "development" unless defined?(PADRINO_ENV)
if ENV["PADRINO_ENV"]
warn 'Environment variable PADRINO_ENV is deprecated. Please, use RACK_ENV.'
ENV["RACK_ENV"] ||= ENV["PADRINO_ENV"]
end
RACK_ENV = ENV["RACK_ENV"] ||= "development" unless defined?(RACK_ENV)
PADRINO_ROOT = ENV["PADRINO_ROOT"] ||= File.dirname(Padrino.first_caller) unless defined?(PADRINO_ROOT)
module Padrino
class ApplicationLoadError < RuntimeError # @private
end
extend Loader
class << self
##
# Helper method for file references.
@ -42,13 +47,13 @@ module Padrino
end
##
# Helper method that return {PADRINO_ENV}.
# Helper method that return {RACK_ENV}.
#
# @return [Symbol]
# The Padrino Environment.
#
def env
@_env ||= PADRINO_ENV.to_s.downcase.to_sym
@_env ||= RACK_ENV.to_s.downcase.to_sym
end
##
@ -61,18 +66,10 @@ module Padrino
# No applications were mounted.
#
def application
raise ApplicationLoadError, "At least one app must be mounted!" unless Padrino.mounted_apps && Padrino.mounted_apps.any?
raise ApplicationLoadError, "At least one app must be mounted!" unless Padrino.mounted_apps.present?
router = Padrino::Router.new
Padrino.mounted_apps.each { |app| app.map_onto(router) }
if middleware.present?
builder = Rack::Builder.new
middleware.each { |c,a,b| builder.use(c, *a, &b) }
builder.run(router)
builder.to_app
else
router
end
middleware.present? ? add_middleware(router) : router
end
##
@ -90,21 +87,14 @@ module Padrino
#
def configure_apps(&block)
return unless block_given?
@@_global_configurations ||= []
@@_global_configurations << block
@_global_configuration = lambda do |app|
@@_global_configurations.each do |configuration|
app.class_eval(&configuration)
end
end
global_configurations << block
end
##
# Returns project-wide configuration settings defined in
# {configure_apps} block.
# Stores global configuration blocks.
#
def apps_configuration
@_global_configuration
def global_configurations
@_global_configurations ||= []
end
##
@ -119,15 +109,21 @@ module Padrino
# @return [NilClass]
#
def set_encoding
if RUBY_VERSION < '1.9'
$KCODE='u'
else
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
end
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
nil
end
##
# Creates Rack stack with the router added to the middleware chain.
#
def add_middleware(router)
builder = Rack::Builder.new
middleware.each{ |mw,args,block| builder.use(mw, *args, &block) }
builder.run(router)
builder.to_app
end
##
# A Rack::Builder object that allows to add middlewares in front of all
# Padrino applications.
@ -161,8 +157,8 @@ module Padrino
# @yield []
# The given block will be passed to the initialized middleware.
#
def use(m, *args, &block)
middleware << [m, args, block]
def use(mw, *args, &block)
middleware << [mw, args, block]
end
##

View file

@ -1,12 +1,10 @@
require 'padrino-core/application/flash'
require 'padrino-core/application/rendering'
require 'padrino-core/application/routing'
require 'padrino-core/application/showexceptions'
require 'padrino-core/application/show_exceptions'
require 'padrino-core/application/authenticity_token'
module Padrino
class ApplicationSetupError < RuntimeError
end
##
# Subclasses of this become independent Padrino applications
# (stemming from Sinatra::Application).
@ -26,20 +24,24 @@ module Padrino
Padrino.logger
end
# TODO: Remove this hack after getting rid of thread-unsafe http_router:
alias_method :original_call, :call
def call(*args)
settings.init_mutex.synchronize do
instance_eval{ undef :call }
class_eval{ alias_method :call, :original_call }
instance_eval{ undef :original_call }
super(*args)
end
end
class << self
def inherited(base)
begun_at = Time.now
CALLERS_TO_IGNORE.concat(PADRINO_IGNORE_CALLERS)
base.default_configuration!
base.prerequisites.concat([
File.join(base.root, '/models.rb'),
File.join(base.root, '/models/**/*.rb'),
File.join(base.root, '/lib.rb'),
File.join(base.root, '/lib/**/*.rb')
]).uniq!
Padrino.require_dependencies(base.prerequisites)
logger.devel :setup, begun_at, base
super(base) # Loading the subclass inherited method
super(base)
end
##
@ -54,10 +56,10 @@ module Padrino
# MyApp.reload!
#
def reload!
logger.devel "Reloading #{settings}"
logger.devel "Reloading application #{settings}"
reset!
reset_router!
Padrino.require_dependencies(settings.app_file, :force => true) # Reload the app file
Padrino.require_dependencies(settings.app_file, :force => true)
require_dependencies
default_filters!
default_routes!
@ -90,6 +92,26 @@ module Padrino
router.routes
end
##
# Returns an absolute path of view in application views folder.
#
# @example
# Admin.view_path 'users/index' #=> "/home/user/test/admin/views/users/index"
#
def view_path(view)
File.expand_path(view, views)
end
##
# Returns an absolute path of application layout.
#
# @example
# Admin.layout_path :application #=> "/home/user/test/admin/views/layouts/application"
#
def layout_path(layout)
view_path("layouts/#{layout}")
end
##
# Setup the application by registering initializers, load paths and logger.
# Invoked automatically when an application is first instantiated.
@ -103,11 +125,11 @@ module Padrino
settings.default_routes!
settings.default_errors!
if defined?(I18n)
Reloader.special_files += settings.locale_path
I18n.load_path << settings.locale_path
I18n.reload!
end
@_configured = true
@_configured
end
##
@ -127,7 +149,13 @@ module Padrino
# directory that need to be added to +$LOAD_PATHS+ from this application
#
def load_paths
@_load_paths ||= %w[models lib mailers controllers helpers].map { |path| File.join(settings.root, path) }
@_load_paths ||= [
'models',
'lib',
'mailers',
'controllers',
'helpers',
].map { |path| File.join(settings.root, path) }
end
##
@ -143,8 +171,14 @@ module Padrino
#
def dependencies
[
'urls.rb', 'config/urls.rb', 'mailers/*.rb', 'mailers.rb',
'controllers/**/*.rb', 'controllers.rb', 'helpers/**/*.rb', 'helpers.rb'
'urls.rb',
'config/urls.rb',
'mailers/*.rb',
'mailers.rb',
'controllers/**/*.rb',
'controllers.rb',
'helpers/**/*.rb',
'helpers.rb',
].map { |file| Dir[File.join(settings.root, file)] }.flatten
end
@ -167,38 +201,67 @@ module Padrino
@_prerequisites ||= []
end
def default(option, *args, &block)
set(option, *args, &block) unless respond_to?(option)
end
protected
##
# Defines default settings for Padrino application.
#
def default_configuration!
# Overwriting Sinatra defaults
set :app_file, File.expand_path(caller_files.first || $0) # Assume app file is first caller
set :app_file, File.expand_path(caller_files.first || $0)
set :app_name, settings.to_s.underscore.to_sym
set :environment, Padrino.env
set :reload, Proc.new { development? }
set :logging, Proc.new { development? }
set :method_override, true
set :sessions, false
set :public_folder, Proc.new { Padrino.root('public', uri_root) }
set :views, Proc.new { File.join(root, 'views') }
set :images_path, Proc.new { File.join(public_folder, 'images') }
set :protection, true
set :haml, { :ugly => (Padrino.env == :production) } if defined?(Haml)
# Padrino specific
set :uri_root, '/'
set :app_name, settings.to_s.underscore.to_sym
set :default_builder, 'StandardFormBuilder'
# TODO: Remove this hack after getting rid of thread-unsafe http_router:
set :init_mutex, Mutex.new
# TODO: Remove this line after sinatra version up.
set :add_charset, %w[javascript xml xhtml+xml].map {|t| "application/#{t}" }
default_paths!
default_security!
global_configuration!
setup_prerequisites!
end
def setup_prerequisites!
prerequisites.concat(default_prerequisites).uniq!
Padrino.require_dependencies(prerequisites)
end
def default_paths!
set :locale_path, Proc.new { Dir.glob File.join(root, 'locale/**/*.{rb,yml}') }
set :views, Proc.new { File.join(root, 'views') }
set :uri_root, '/'
set :public_folder, Proc.new { Padrino.root('public', uri_root) }
set :images_path, Proc.new { File.join(public_folder, 'images') }
end
def default_security!
set :protection, :except => :path_traversal
set :authentication, false
set :locale_path, Proc.new { Dir[File.join(settings.root, '/locale/**/*.{rb,yml}')] }
# Authenticity token
set :sessions, false
set :protect_from_csrf, false
set :allow_disabled_csrf, false
# Load the Global Configurations
class_eval(&Padrino.apps_configuration) if Padrino.apps_configuration
end
##
# Applies global padrino configuration blocks to current application.
#
def global_configuration!
Padrino.global_configurations.each do |configuration|
class_eval(&configuration)
end
end
##
@ -220,9 +283,7 @@ module Padrino
#
def default_filters!
before do
unless @_content_type
response['Content-Type'] = 'text/html;charset=utf-8'
end
response['Content-Type'] = 'text/html;charset=utf-8' unless @_content_type
end
end
@ -249,7 +310,20 @@ module Padrino
Padrino.require_dependencies(dependencies, :force => true)
end
##
# Returns globs of default paths of application prerequisites.
#
def default_prerequisites
[
'/models.rb',
'/models/**/*.rb',
'/lib.rb',
'/lib/**/*.rb',
].map{ |glob| File.join(settings.root, glob) }
end
private
# Overrides the default middleware for Sinatra based on Padrino conventions.
# Also initializes the application after setting up the middleware.
def setup_default_middleware(builder)
@ -267,9 +341,33 @@ module Padrino
# sets up csrf protection for the app:
def setup_csrf_protection(builder)
if protect_from_csrf? && !sessions?
raise(<<-ERROR)
`protect_from_csrf` is activated, but `sessions` are not. To enable csrf
check_csrf_protection_dependency
if protect_from_csrf?
options = options_for_csrf_protection_setup
options.merge!(protect_from_csrf) if protect_from_csrf.kind_of?(Hash)
builder.use(options[:except] ? Padrino::AuthenticityToken : Rack::Protection::AuthenticityToken, options)
end
end
# returns the options used in the builder for csrf protection setup
def options_for_csrf_protection_setup
options = { :logger => logger }
if allow_disabled_csrf?
options.merge!({
:reaction => :report,
:report_key => 'protection.csrf.failed'
})
end
options
end
# throw an exception if the protect_from_csrf is active but sessions not.
def check_csrf_protection_dependency
if (protect_from_csrf? && !sessions?) && !defined?(Padrino::IGNORE_CSRF_SETUP_WARNING)
warn(<<-ERROR)
`protect_from_csrf` is activated, but `sessions` seem to be off. To enable csrf
protection, use:
enable :sessions
@ -277,19 +375,12 @@ protection, use:
or deactivate protect_from_csrf:
disable :protect_from_csrf
ERROR
end
if protect_from_csrf?
if allow_disabled_csrf?
builder.use Rack::Protection::AuthenticityToken,
:reaction => :report,
:report_key => 'protection.csrf.failed',
:logger => logger
else
builder.use Rack::Protection::AuthenticityToken,
:logger => logger
end
If you use a different session store, ignore this warning using:
# in boot.rb:
Padrino::IGNORE_CSRF_SETUP_WARNING = true
ERROR
end
end
end

View file

@ -0,0 +1,25 @@
module Padrino
class AuthenticityToken < Rack::Protection::AuthenticityToken
def initialize(app, options = {})
@app = app
@except = options[:except]
@except = Array(@except) unless @except.is_a?(Proc)
super
end
def call(env)
if except?(env)
@app.call(env)
else
super
end
end
def except?(env)
return false unless @except
path_info = env['PATH_INFO']
@except.is_a?(Proc) ? @except.call(env) : @except.any?{|path|
path.is_a?(Regexp) ? path.match(path_info) : path == path_info }
end
end
end

View file

@ -195,14 +195,16 @@ module Padrino
options[:layout] = @layout if options[:layout].nil? || options[:layout] == true
# Resolve layouts similar to in Rails
if options[:layout].nil? && !settings.templates.has_key?(:layout)
layout_path, layout_engine = *resolved_layout
layout_path = settings.fetch_layout_path(options[:layout])
is_included_extension = %w[.slim .erb .haml].include?(File.extname(layout_path.to_s))
layout_path, layout_engine = *(is_included_extension ? resolve_template(layout_path) : resolved_layout)
# We need to force layout false so sinatra don't try to render it
options[:layout] = layout_path || false
options[:layout] = false unless layout_engine == engine # TODO allow different layout engine
options[:layout] = false unless is_included_extension ? layout_engine : layout_engine == engine
options[:layout_engine] = layout_engine || engine if options[:layout]
elsif options[:layout].present?
options[:layout] = settings.fetch_layout_path(options[:layout] || @layout)
options[:layout], options[:layout_engine] = *resolve_template(settings.fetch_layout_path(options[:layout]), options)
end
# Default to original layout value if none found.
options[:layout] ||= layout_was
@ -272,15 +274,21 @@ module Padrino
end
# Resolve view path and options.
options.reverse_merge!(DEFAULT_RENDERING_OPTIONS)
options = DEFAULT_RENDERING_OPTIONS.merge(options)
view_path = options.delete(:views) || settings.views || "./views"
target_extension = File.extname(template_path)[1..-1] || "none" # explicit template extension
template_path = template_path.chomp(".#{target_extension}")
template_glob =
if respond_to?(:request) && request.controller.present?
File.join("{,#{request.controller}}", template_path)
else
template_path
end
# Generate potential template candidates
templates = Dir[File.join(view_path, template_path) + ".*"].map do |file|
templates = Dir[File.join(view_path, template_glob) + ".*"].map do |file|
template_engine = File.extname(file)[1..-1].to_sym # Retrieves engine extension
template_file = file.sub(view_path, '').chomp(".#{template_engine}").to_sym # retrieves template filename
template_file = file.squeeze('/').sub(view_path, '').chomp(".#{template_engine}").to_sym # retrieves template filename
[template_file, template_engine] unless IGNORE_FILE_PATTERN.any? { |pattern| template_engine.to_s =~ pattern }
end

View file

@ -40,15 +40,18 @@ begin
def render(*args)
app = args.first
app_class = app.class
@padrino_app = app.kind_of?(Padrino::Application) ||
(app_class.respond_to?(:erb) && app_class.erb[:engine_class] == Padrino::Erubis::SafeBufferTemplate)
@is_padrino_app = app.kind_of?(Padrino::Application) ||
(app_class.respond_to?(:erb) && app_class.erb[:engine_class] == Padrino::Erubis::SafeBufferTemplate)
super
end
##
# In preamble we need a flag `__in_erb_template` and SafeBuffer for padrino apps.
#
def precompiled_preamble(locals)
buf = @padrino_app ? "ActiveSupport::SafeBuffer.new" : "''"
old_postamble = super.split("\n")[0..-2]
[old_postamble, "#{@outvar} = _buf = (#{@outvar} || #{buf})"].join("\n")
original = super
return original unless @is_padrino_app
"__in_erb_template = true\n" << original.rpartition("\n").first << "#{@outvar} = _buf = ActiveSupport::SafeBuffer.new\n"
end
end
end
@ -57,8 +60,9 @@ begin
Tilt.prefer(Padrino::Erubis::Template, :erb)
if defined? Padrino::Rendering
Padrino::Rendering.engine_configurations[:erb] =
{:engine_class => Padrino::Erubis::SafeBufferTemplate}
Padrino::Rendering.engine_configurations[:erb] = {
:engine_class => Padrino::Erubis::SafeBufferTemplate,
}
end
rescue LoadError
end

View file

@ -17,8 +17,9 @@ begin
end
if defined? Padrino::Rendering
Padrino::Rendering.engine_configurations[:haml] =
{:escape_html => true}
Padrino::Rendering.engine_configurations[:haml] = {
:escape_html => true,
}
class Tilt::HamlTemplate
include Padrino::Rendering::SafeTemplate

View file

@ -0,0 +1,21 @@
begin
require 'slim'
if defined? Padrino::Rendering
Padrino::Rendering.engine_configurations[:slim] = {
:generator => Temple::Generators::RailsOutputBuffer,
:buffer => "@_out_buf",
:use_html_safe => true,
:disable_capture => true,
}
class Slim::Template
include Padrino::Rendering::SafeTemplate
def precompiled_preamble(locals)
"__in_slim_template = true\n" << super
end
end
end
rescue LoadError
end

View file

@ -15,6 +15,19 @@ class Sinatra::Request
end
end
##
# This patches Sinatra to accept UTF-8 urls on JRuby 1.7.6
#
if RUBY_ENGINE == 'jruby' && defined?(JRUBY_VERSION) && JRUBY_VERSION > '1.7.4'
class Sinatra::Base
class << self
alias_method :old_generate_method, :generate_method
def generate_method(method_name, &block)
old_generate_method(method_name.to_sym, &block)
end
end
end
end
class HttpRouter
def rewrite_partial_path_info(env, request); end
@ -61,7 +74,7 @@ class HttpRouter
class Route
VALID_HTTP_VERBS.replace %w[GET POST PUT PATCH DELETE HEAD OPTIONS LINK UNLINK]
attr_accessor :use_layout, :controller, :action, :cache, :cache_key, :cache_expires_in, :parent
attr_accessor :use_layout, :controller, :action, :cache, :cache_key, :cache_expires, :parent
def before_filters(&block)
@_before_filters ||= []
@ -172,6 +185,7 @@ class HttpRouter
env['router.request'] = request
env['router.params'] ||= {}
#{"env['router.params'].merge!(Hash[#{param_names.inspect}.zip(request.params)])" if dynamic?}
env['router.params'] = env['router.params'].with_indifferent_access
@router.rewrite#{"_partial" if route.match_partially}_path_info(env, request)
response = @router.process_destination_path(#{path_ivar}, env)
return response unless router.pass_on_response(response)
@ -193,7 +207,7 @@ module Padrino
def apply?(request)
detect = @args.any? do |arg|
case arg
when Symbol then request.route_obj && (request.route_obj.name == arg or request.route_obj.name == [@scoped_controller, arg].flatten.join("_").to_sym)
when Symbol then request.route_obj && (request.route_obj.name == arg or request.route_obj.name == [@scoped_controller, arg].flatten.join(" ").to_sym)
else arg === request.path_info
end
end || @options.any? do |name, val|
@ -500,7 +514,13 @@ module Padrino
def compiled_router
if @deferred_routes
deferred_routes.each { |routes| routes.each { |(route, dest)| route.to(dest) } }
deferred_routes.each do |routes|
routes.each do |(route, dest)|
route.to(dest)
route.before_filters.flatten!
route.after_filters.flatten!
end
end
@deferred_routes = nil
router.sort!
end
@ -557,10 +577,10 @@ module Padrino
def url(*args)
params = args.extract_options! # parameters is hash at end
names, params_array = args.partition{|a| a.is_a?(Symbol)}
name = names.join("_").to_sym # route name is concatenated with underscores
name = names[0, 2].join(" ").to_sym # route name is concatenated with underscores
if params.is_a?(Hash)
params[:format] = params[:format].to_s unless params[:format].nil?
params = value_to_param(params)
params = value_to_param(params.symbolize_keys)
end
url =
if params_array.empty?
@ -568,10 +588,7 @@ module Padrino
else
compiled_router.path(name, *(params_array << params))
end
url[0,0] = conform_uri(uri_root) if defined?(uri_root)
url[0,0] = conform_uri(ENV['RACK_BASE_URI']) if ENV['RACK_BASE_URI']
url = "/" if url.blank?
url
rebase_url(url)
rescue HttpRouter::InvalidRouteException
route_error = "route mapping for url(#{name.inspect}) could not be found!"
raise Padrino::Routing::UnrecognizedException.new(route_error)
@ -586,6 +603,17 @@ module Padrino
route('HEAD', path, *args, &block)
end
def rebase_url(url)
if url.start_with?('/')
new_url = ''
new_url << conform_uri(uri_root) if defined?(uri_root)
new_url << conform_uri(ENV['RACK_BASE_URI']) if ENV['RACK_BASE_URI']
new_url << url
else
url.blank? ? '/' : url
end
end
private
# Parse params from the url method
def value_to_param(value)
@ -702,8 +730,8 @@ module Padrino
invoke_hook(:padrino_route_added, route, verb, path, args, options, block)
# Add Application defaults.
route.before_filters.concat(@filters[:before])
route.after_filters.concat(@filters[:after])
route.before_filters << @filters[:before]
route.after_filters << @filters[:after]
if @_controller
route.use_layout = @layout
route.controller = Array(@_controller)[0].to_s
@ -786,7 +814,7 @@ module Padrino
name = options.delete(:name) if name.nil? && options.key?(:name)
if name
controller_name = controller.join("_")
name = "#{controller_name}_#{name}".to_sym unless controller_name.blank?
name = "#{controller_name} #{name}".to_sym unless controller_name.blank?
end
# Merge in option defaults.
@ -856,9 +884,10 @@ module Padrino
def provides(*types)
@_use_format = true
condition do
mime_types = types.map { |t| mime_type(t) }.compact
url_format = params[:format].to_sym if params[:format]
accepts = request.accept.map(&:to_str)
mime_types = types.map { |t| mime_type(t) }.compact
url_format = params[:format].to_sym if params[:format]
accepts = request.accept.map(&:to_str)
accepts.clear if accepts == ["*/*"]
# Per rfc2616-sec14:
# Assume */* if no ACCEPT header is given.
@ -890,7 +919,12 @@ module Padrino
if matched_format
@_content_type = url_format || accept_format || :html
content_type(@_content_type, :charset => 'utf-8')
if @_content_type != :json
content_type(@_content_type, :charset => 'utf-8')
else
content_type(@_content_type)
end
end
matched_format
@ -924,17 +958,24 @@ module Padrino
# url(:show, :id => 1)
# url(:show, :name => :test)
# url(:show, 1)
# url("/foo")
# url("/foo", false, false)
#
# @see Padrino::Routing::ClassMethods#url
#
def url(*args)
# Delegate to Sinatra 1.2 for simple url("/foo")
# http://www.sinatrarb.com/intro#Generating%20URLs
return super if args.first.is_a?(String) && !args[1].is_a?(Hash)
# Delegate to Padrino named route URL generation.
settings.url(*args)
if args.first.is_a?(String)
url_path = settings.rebase_url(args.shift)
if args.empty?
url_path
else
# Delegate sinatra-style urls to Sinatra. Ex: url("/foo", false, false)
# http://www.sinatrarb.com/intro#Generating%20URLs
super url_path, *args
end
else
# Delegate to Padrino named route URL generation.
settings.url(*args)
end
end
alias :url_for :url
@ -944,9 +985,15 @@ module Padrino
# @example
# absolute_url(:show, :id => 1) # => http://example.com/show?id=1
# absolute_url(:show, 24) # => https://example.com/admin/show/24
# absolute_url('/foo/bar') # => https://example.com/admin/foo/bar
# absolute_url('baz') # => https://example.com/admin/foo/baz
#
def absolute_url( *args )
uri url(*args), true, false
def absolute_url(*args)
url_path = args.shift
if url_path.is_a?(String) && !url_path.start_with?('/')
url_path = request.env['PATH_INFO'].rpartition('/').first << '/' << url_path
end
uri url(url_path, *args), true, false
end
def recognize_path(path)
@ -958,10 +1005,12 @@ module Padrino
#
def current_path(*path_params)
if path_params.last.is_a?(Hash)
path_params[-1] = params.merge(path_params[-1])
path_params[-1] = params.merge(path_params[-1].with_indifferent_access)
else
path_params << params
end
path_params[-1] = path_params[-1].symbolize_keys
@route.path(*path_params)
end

View file

@ -5,7 +5,7 @@ module Padrino
%r{lib/padrino-.*$},
%r{/padrino-.*/(lib|bin)},
%r{/bin/padrino$},
%r{/sinatra(/(base|main|showexceptions))?\.rb$},
%r{/sinatra(/(base|main|show_?exceptions))?\.rb$},
%r{lib/tilt.*\.rb$},
%r{lib/rack.*\.rb$},
%r{lib/mongrel.*\.rb$},

View file

@ -18,11 +18,19 @@ module Padrino
method_option :daemonize, :type => :boolean, :aliases => "-d", :desc => "Run daemonized in the background."
method_option :pid, :type => :string, :aliases => "-i", :desc => "File to store pid."
method_option :debug, :type => :boolean, :desc => "Set debugging flags."
method_option :options, :type => :array, :aliases => "-O", :desc => "--options NAME=VALUE NAME2=VALUE2'. pass VALUE to the server as option NAME. If no VALUE, sets it to true. Run '#{$0} --server_options"
method_option :server_options, :type => :boolean, :desc => "Tells the current server handler's options that can be used with --options"
def start
prepare :start
require File.expand_path("../adapter", __FILE__)
require File.expand_path('config/boot.rb')
Padrino::Cli::Adapter.start(options)
if options[:server_options]
puts server_options(options)
else
Padrino::Cli::Adapter.start(options)
end
end
desc "stop", "Stops the Padrino application (alternatively use 'st')."
@ -116,7 +124,7 @@ module Padrino
help(task.to_s)
raise SystemExit
end
ENV["PADRINO_ENV"] ||= ENV["RACK_ENV"] ||= options.environment.to_s
ENV["RACK_ENV"] ||= options.environment.to_s
chdir(options.chdir)
unless File.exist?('config/boot.rb')
puts "=> Could not find boot file in: #{options.chdir}/config/boot.rb !!!"
@ -124,6 +132,29 @@ module Padrino
end
end
# https://github.com/rack/rack/blob/master/lib/rack/server.rb\#L100
def server_options(options)
begin
info = []
server = Rack::Handler.get(options[:server]) || Rack::Handler.default(options)
if server && server.respond_to?(:valid_options)
info << ""
info << "Server-specific options for #{server.name}:"
has_options = false
server.valid_options.each do |name, description|
next if name.to_s.match(/^(Host|Port)[^a-zA-Z]/) # ignore handler's host and port options, we do our own.
info << " -O %-21s %s" % [name, description]
has_options = true
end
return "" if !has_options
end
info.join("\n")
rescue NameError
return "Warning: Could not find handler specified (#{options[:server] || 'default'}) to determine handler-specific options"
end
end
protected
def self.banner(task=nil, *args)

View file

@ -16,6 +16,16 @@ task :environment do
end
end
# Loads skeleton Padrino environment, no models, no application settings.
task :skeleton do
module Padrino::Reloader
def self.safe_load(file, options)
super unless file.include?('/models/')
end
end
require File.expand_path('config/boot.rb', Rake.application.original_dir)
end
desc "Generate a secret key"
task :secret do
shell.say SecureRandom.hex(32)

View file

@ -1,5 +1,5 @@
module Padrino
class << self
module Loader
##
# Hooks to be called before a load/reload.
#
@ -40,17 +40,6 @@ module Padrino
@_after_load
end
##
# The used +$LOAD_PATHS+ from Padrino.
#
# @return [Array<String>]
# The load paths used by Padrino.
#
def load_paths
@_load_paths_was = %w(lib models shared).map { |path| Padrino.root(path) }
@_load_paths ||= @_load_paths_was
end
##
# Requires necessary dependencies as well as application files from root
# lib and models.
@ -60,21 +49,18 @@ module Padrino
#
def load!
return false if loaded?
t = Time.now
began_at = Time.now
@_called_from = first_caller
Padrino.set_encoding
Padrino.set_load_paths(*load_paths)
Padrino::Logger.setup! # Initialize our logger
Padrino.require_dependencies("#{root}/config/database.rb", :nodeps => true) # Be sure to don't remove constants from dbs.
Padrino::Reloader.lock! # Now we can remove constant from here to down
Padrino.before_load.each(&:call) # Run before hooks
Padrino.dependency_paths.each { |path| Padrino.require_dependencies(path) }
Padrino.after_load.each(&:call) # Run after hooks
Padrino::Reloader.run!
set_encoding
set_load_paths(*load_paths)
Logger.setup!
require_dependencies("#{root}/config/database.rb")
Reloader.lock!
before_load.each(&:call)
require_dependencies(*dependency_paths)
after_load.each(&:call)
logger.devel "Loaded Padrino in #{Time.now - began_at} seconds"
Thread.current[:padrino_loaded] = true
Padrino.logger.devel "Loaded Padrino in #{Time.now - t} seconds"
end
##
@ -83,14 +69,14 @@ module Padrino
# @return [NilClass]
#
def clear!
Padrino.clear_middleware!
Padrino.mounted_apps.clear
clear_middleware!
mounted_apps.clear
@_load_paths = nil
@_dependency_paths = nil
@_global_configuration = nil
Padrino.before_load.clear
Padrino.after_load.clear
Padrino::Reloader.clear!
before_load.clear
after_load.clear
Reloader.clear!
Thread.current[:padrino_loaded] = nil
end
@ -98,10 +84,10 @@ module Padrino
# Method for reloading required applications and their files.
#
def reload!
return unless Padrino::Reloader.changed?
Padrino.before_load.each(&:call) # Run before hooks
Padrino::Reloader.reload! # detects the modified files
Padrino.after_load.each(&:call) # Run after hooks
return unless Reloader.changed?
before_load.each(&:call)
Reloader.reload!
after_load.each(&:call)
end
##
@ -147,38 +133,55 @@ module Padrino
# require_dependencies("#{Padrino.root}/lib/**/*.rb")
#
def require_dependencies(*paths)
options = paths.extract_options!
# Extract all files to load
files = paths.flatten.map { |path| Dir[path] }.flatten.uniq.sort
options = paths.extract_options!.merge( :cyclic => true )
files = paths.flatten.map{|path| Dir[path].sort_by{|v| v.count('/') }}.flatten.uniq
while files.present?
errors, failed = [], []
error, fatal, loaded = nil, nil, nil
size_at_start = files.size
# Now we try to require our dependencies, we dup files
# so we don't perform delete on the original array during
# iteration, this prevent problems with Rubinus
files.dup.each do |file|
begin
Padrino::Reloader.safe_load(file, options.dup)
Reloader.safe_load(file, options)
files.delete(file)
loaded = true
rescue NameError, LoadError => e
Padrino.logger.devel "Problem while loading #{file}: #{e}"
errors << e
failed << file
logger.devel "Cyclic dependency reload for #{e.class}: #{e.message}"
error = e
rescue Exception => e
raise e
fatal = e
break
end
end
# Stop processing if nothing loads or if everything has loaded
raise errors.last if files.size == size_at_start && files.present?
break if files.empty?
if fatal || !loaded
e = fatal || error
logger.error "#{e.class}: #{e.message}; #{e.backtrace.first}"
raise e
end
end
end
##
# Concat to +$LOAD_PATH+ the given paths.
#
# @param [Array<String>] paths
# The paths to concat.
#
def set_load_paths(*paths)
load_paths.concat(paths).uniq!
$LOAD_PATH.concat(paths).uniq!
end
##
# The used +$LOAD_PATHS+ from Padrino.
#
# @return [Array<String>]
# The load paths used by Padrino.
#
def load_paths
@_load_paths ||= load_paths_was.dup
end
##
# Returns default list of path globs to load as dependencies.
# Appends custom dependency patterns to the be loaded for Padrino.
@ -190,35 +193,32 @@ module Padrino
# Padrino.dependency_paths << "#{Padrino.root}/uploaders/*.rb"
#
def dependency_paths
@_dependency_paths ||= (dependency_paths_was + Array(module_paths))
end
##
# Concat to +$LOAD_PATH+ the given paths.
#
# @param [Array<String>] paths
# The paths to concat.
#
def set_load_paths(*paths)
$:.concat(paths); load_paths.concat(paths)
$:.uniq!; load_paths.uniq!
@_dependency_paths ||= dependency_paths_was + module_paths
end
private
def module_paths
Padrino.modules.map(&:dependency_paths).flatten!
modules.map(&:dependency_paths).flatten
end
def load_paths_was
@_load_paths_was ||= [
"#{root}/lib",
"#{root}/models",
"#{root}/shared",
].freeze
end
def dependency_paths_was
[
@_dependency_paths_was ||= [
"#{root}/config/database.rb",
"#{root}/lib/**/*.rb",
"#{root}/shared/lib/**/*.rb",
"#{root}/models/**/*.rb",
"#{root}/shared/lib/**/*.rb",
"#{root}/shared/models/**/*.rb",
"#{root}/config/apps.rb"
]
].freeze
end
end
end

View file

@ -11,7 +11,7 @@ nl:
day_names: [zondag, maandag, dinsdag, woensdag, donderdag, vrijdag, zaterdag]
abbr_day_names: [zo, ma, di, wo, do, vr, za]
month_names: [~, januari, februari, maart, april, mei, juni, juli, augustus, september, oktober, november]
month_names: [~, januari, februari, maart, april, mei, juni, juli, augustus, september, oktober, november, december]
abbr_month_names: [~, jan, feb, maa, apr, mei, jun, jul, aug, sep, okt, nov, dec]
order:
- day

View file

@ -4,9 +4,9 @@ zh_cn:
# Use the strftime parameters for formats.
# When no format has been given, it uses default.
# You can provide other formats here if you like!
default: "%Y 年 %m 月 %d 日"
short: "%b 月 %d 日"
long: "公元 %Y 年 %B 月 %d 日"
default: "%Y年%m月%d日"
short: "%b月%d日"
long: "%Y年%B月%d日"
only_day: "%e"
day_names: [星期日, 星期一, 星期二, 星期三, 星期四, 星期五, 星期六]
@ -20,9 +20,9 @@ zh_cn:
time:
formats:
default: "%Y 年 %b 月 %d 日 %H:%M:%S %z"
short: "%d 月 %b 日 %H:%M"
long: "%Y 年 %B 月 %d 日 %H 时 %M 分"
default: "%Y年%b月%d日 %H:%M:%S %z"
short: "%b月%d日 %H:%M"
long: "%Y年%B%d日 %H时%M分"
am: "上午"
pm: "下午"

View file

@ -52,10 +52,10 @@ module Padrino
# :devel:: Development-related information that is unnecessary in debug mode
#
Levels = {
:fatal => 7,
:error => 6,
:warn => 4,
:info => 3,
:fatal => 4,
:error => 3,
:warn => 2,
:info => 1,
:debug => 0,
:devel => -1,
} unless defined?(Levels)
@ -194,13 +194,13 @@ module Padrino
#
def colorize(string, *colors)
colors.each do |c|
string = string.send(c)
string = string.colorize(c)
end
string
end
def stylized_level(level)
style = ColoredLevels[level].map { |c| "\e[%dm" % String.colors[c] } * ''
style = ColoredLevels[level].map { |c| "\e[%dm" % String::Colorizer.colors[c] } * ''
[style, super, "\e[0m"] * ''
end
end
@ -280,7 +280,7 @@ module Padrino
stream = case config[:stream]
when :to_file
FileUtils.mkdir_p(Padrino.root('log')) unless File.exists?(Padrino.root('log'))
FileUtils.mkdir_p(Padrino.root('log')) unless File.exist?(Padrino.root('log'))
File.new(Padrino.root('log', "#{Padrino.env}.log"), 'a+')
when :null then StringIO.new
when :stdout then $stdout

View file

@ -8,10 +8,11 @@ module Padrino
# Mounter.new("blog_app", :app_file => "/path/to/blog/app.rb").to("/blog")
#
class Mounter
DEFAULT_CASCADE = [404, 405]
class MounterException < RuntimeError
end
attr_accessor :name, :uri_root, :app_file, :app_class, :app_root, :app_obj, :app_host
attr_accessor :name, :uri_root, :app_file, :app_class, :app_root, :app_obj, :app_host, :cascade
##
# @param [String, Padrino::Application] name
@ -31,8 +32,9 @@ module Padrino
@app_file = options[:app_file] || locate_app_file
@app_obj = options[:app_obj] || app_constant || locate_app_object
ensure_app_file! || ensure_app_object!
@app_root = options[:app_root] || File.dirname(@app_file)
@app_root = options[:app_root] || (@app_obj.respond_to?(:root) && @app_obj.root || File.dirname(@app_file))
@uri_root = "/"
@cascade = options[:cascade] ? true == options[:cascade] ? DEFAULT_CASCADE.dup : Array(options[:cascade]) : []
Padrino::Reloader.exclude_constants << @app_class
end
@ -82,11 +84,12 @@ module Padrino
def map_onto(router)
app_data, app_obj = self, @app_obj
app_obj.set :uri_root, app_data.uri_root
app_obj.set :app_name, app_data.name
app_obj.set :app_name, app_data.app_obj.app_name.to_s
app_obj.set :app_file, app_data.app_file unless ::File.exist?(app_obj.app_file)
app_obj.set :root, app_data.app_root unless app_data.app_root.blank?
app_obj.set :public_folder, Padrino.root('public', app_data.uri_root) unless File.exists?(app_obj.public_folder)
app_obj.set :public_folder, Padrino.root('public', app_data.uri_root) unless File.exist?(app_obj.public_folder)
app_obj.set :static, File.exist?(app_obj.public_folder) if app_obj.nil?
app_obj.set :cascade, app_data.cascade
app_obj.setup_application! # Initializes the app here with above settings.
router.map(:to => app_obj, :path => app_data.uri_root, :host => app_data.app_host)
end
@ -106,7 +109,9 @@ module Padrino
#
def named_routes
app_obj.routes.map { |route|
name_array = "(#{route.name.to_s.split("_").map { |piece| %Q[:#{piece}] }.join(", ")})"
route_name = route.name.to_s
route_name.sub!(/^#{route.controller} /, "") if route.controller
name_array = "(#{route.controller ? %Q[:#{route.controller}] + ", " : ""}:#{route_name})"
request_method = route.request_methods.first
next if route.name.blank? || request_method == 'HEAD'
original_path = route.original_path.is_a?(Regexp) ? route.original_path.inspect : route.original_path

View file

@ -0,0 +1,250 @@
require 'pathname'
require 'padrino-core/reloader/rack'
require 'padrino-core/reloader/storage'
module Padrino
##
# High performance source code reloader middleware
#
module Reloader
##
# This reloader is suited for use in a many environments because each file
# will only be checked once and only one system call to stat(2) is made.
#
# Please note that this will not reload files in the background, and does so
# only when explicitly invoked.
#
extend self
# The modification times for every file in a project.
MTIMES = {}
##
# Specified folders can be excluded from the code reload detection process.
# Default excluded directories at Padrino.root are: test, spec, features, tmp, config, db and public
#
def exclude
@_exclude ||= Set.new %w(test spec tmp features config public db).map{ |path| Padrino.root(path) }
end
##
# Specified constants can be excluded from the code unloading process.
#
def exclude_constants
@_exclude_constants ||= Set.new
end
##
# Specified constants can be configured to be reloaded on every request.
# Default included constants are: [none]
#
def include_constants
@_include_constants ||= Set.new
end
##
# Reload apps and files with changes detected.
#
def reload!
rotation do |file|
next unless file_changed?(file)
reload_special(file) || reload_regular(file)
end
end
##
# Remove files and classes loaded with stat
#
def clear!
MTIMES.clear
Storage.clear!
end
##
# Returns true if any file changes are detected.
#
def changed?
rotation do |file|
break true if file_changed?(file)
end
end
##
# We lock dependencies sets to prevent reloading of protected constants
#
def lock!
klasses = ObjectSpace.classes do |klass|
klass._orig_klass_name.split('::').first
end
klasses |= Padrino.mounted_apps.map(&:app_class)
exclude_constants.merge(klasses)
end
##
# A safe Kernel::require which issues the necessary hooks depending on results
#
def safe_load(file, options={})
began_at = Time.now
file = figure_path(file)
return unless options[:force] || file_changed?(file)
Storage.prepare(file) # might call #safe_load recursively
logger.devel(file_new?(file) ? :loading : :reload, began_at, file)
begin
with_silence{ require(file) }
Storage.commit(file)
update_modification_time(file)
rescue Exception => e
unless options[:cyclic]
logger.error "#{e.class}: #{e.message}; #{e.backtrace.first}"
logger.error "Failed to load #{file}; removing partially defined constants"
end
Storage.rollback(file)
raise e
end
end
##
# Removes the specified class and constant.
#
def remove_constant(const)
return if constant_excluded?(const)
base, _, object = const.to_s.rpartition('::')
base = base.empty? ? Object : base.constantize
base.send :remove_const, object
logger.devel "Removed constant #{const} from #{base}"
rescue NameError
end
##
# Returns the list of special tracked files for Reloader.
#
def special_files
@special_files ||= Set.new
end
##
# Sets the list of special tracked files for Reloader.
#
def special_files=(files)
@special_files = Set.new(files)
end
private
##
# Returns absolute path of the file.
#
def figure_path(file)
return file if Pathname.new(file).absolute?
$LOAD_PATH.each do |path|
found = File.join(path, file)
return File.expand_path(found) if File.file?(found)
end
file
end
##
# Reloads the file if it's special. For now it's only I18n locale files.
#
def reload_special(file)
return unless special_files.any?{ |f| File.identical?(f, file) }
if defined?(I18n)
began_at = Time.now
I18n.reload!
update_modification_time(file)
logger.devel :reload, began_at, file
end
true
end
##
# Reloads ruby file and applications dependent on it.
#
def reload_regular(file)
apps = mounted_apps_of(file)
if apps.present?
apps.each { |app| app.app_obj.reload! }
update_modification_time(file)
else
safe_load(file)
reloadable_apps.each do |app|
app.app_obj.reload! if app.app_obj.dependencies.include?(file)
end
end
end
###
# Macro for mtime update.
#
def update_modification_time(file)
MTIMES[file] = File.mtime(file)
end
###
# Returns true if the file is new or it's modification time changed.
#
def file_changed?(file)
file_new?(file) || File.mtime(file) > MTIMES[file]
end
###
# Returns true if the file is new.
#
def file_new?(file)
MTIMES[file].nil?
end
##
# Return the mounted_apps providing the app location.
# Can be an array because in one app.rb we can define multiple Padrino::Application.
#
def mounted_apps_of(file)
Padrino.mounted_apps.select { |app| File.identical?(file, app.app_file) }
end
##
# Searches Ruby files in your +Padrino.load_paths+ , Padrino::Application.load_paths
# and monitors them for any changes.
#
def rotation
files_for_rotation.each do |file|
file = File.expand_path(file)
next if Reloader.exclude.any? { |base| file.start_with?(base) } || !File.file?(file)
yield file
end
nil
end
##
# Creates an array of paths for use in #rotation.
#
def files_for_rotation
files = Set.new
Padrino.load_paths.each{ |path| files += Dir.glob("#{path}/**/*.rb") }
reloadable_apps.each do |app|
files << app.app_file
files += app.app_obj.dependencies
end
files + special_files
end
def constant_excluded?(const)
(exclude_constants - include_constants).any?{ |c| const._orig_klass_name.start_with?(c) }
end
def reloadable_apps
Padrino.mounted_apps.select{ |app| app.app_obj.respond_to?(:reload) && app.app_obj.reload? }
end
##
# Disables output, yields block, switches output back.
#
def with_silence
verbosity_level, $-v = $-v, nil
yield
ensure
$-v = verbosity_level
end
end
end

View file

@ -0,0 +1,26 @@
module Padrino
module Reloader
##
# This class acts as a Rack middleware to be added to the application stack.
# This middleware performs a check and reload for source files at the start
# of each request, but also respects a specified cool down time
# during which no further action will be taken.
#
class Rack
def initialize(app, cooldown=1)
@app = app
@cooldown = cooldown
@last = (Time.now - cooldown)
end
# Invoked in order to perform the reload as part of the request stack.
def call(env)
if @cooldown && Time.now > @last + @cooldown
Thread.list.size > 1 ? Thread.exclusive { Padrino.reload! } : Padrino.reload!
@last = Time.now
end
@app.call(env)
end
end
end
end

View file

@ -0,0 +1,55 @@
module Padrino
module Reloader
module Storage
extend self
def clear!
files.each_key do |file|
remove(file)
$LOADED_FEATURES.delete(file)
end
@files = {}
end
def remove(name)
file = files[name] || return
file[:constants].each{ |constant| Reloader.remove_constant(constant) }
file[:features].each{ |feature| $LOADED_FEATURES.delete(feature) }
files.delete(name)
end
def prepare(name)
file = remove(name)
@old_entries ||= {}
@old_entries[name] = {
:constants => ObjectSpace.classes,
:features => old_features = Set.new($LOADED_FEATURES.dup)
}
features = file && file[:features] || []
features.each{ |feature| Reloader.safe_load(feature, :force => true) }
$LOADED_FEATURES.delete(name) if old_features.include?(name)
end
def commit(name)
entry = {
:constants => ObjectSpace.new_classes(@old_entries[name][:constants]),
:features => Set.new($LOADED_FEATURES) - @old_entries[name][:features] - [name]
}
files[name] = entry
@old_entries.delete(name)
end
def rollback(name)
new_constants = ObjectSpace.new_classes(@old_entries[name][:constants])
new_constants.each{ |klass| Reloader.remove_constant(klass) }
@old_entries.delete(name)
end
private
def files
@files ||= {}
end
end
end
end

View file

@ -62,14 +62,15 @@ module Padrino
host = Regexp.new("^#{Regexp.quote(host)}$", true, 'n') unless host.nil? || host.is_a?(Regexp)
@mapping << [host, path, match, app]
sort!
end
# The call handler setup to route a request given the mappings specified.
def call(env)
began_at = Time.now
path_info = env["PATH_INFO"].to_s
script_name = env['SCRIPT_NAME']
http_host = env['HTTP_HOST']
last_result = nil
@mapping.each do |host, path, match, app|
next unless host.nil? || http_host =~ host
@ -78,18 +79,16 @@ module Padrino
rest = "/" if rest.empty?
return app.call(
env.merge(
'SCRIPT_NAME' => (script_name + path),
'PATH_INFO' => rest))
last_result = app.call(env.merge('SCRIPT_NAME' => script_name + path, 'PATH_INFO' => rest))
cascade_setting = app.respond_to?(:cascade) ? app.cascade : true
cascade_statuses = cascade_setting.respond_to?(:include?) ? cascade_setting : Mounter::DEFAULT_CASCADE
break unless cascade_setting && cascade_statuses.include?(last_result[0])
end
last_result || begin
Padrino::Logger::Rack.new(nil,'/').send(:log, env, 404, {}, began_at) if logger.debug?
[404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path_info}"]]
end
[404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{path_info}"]]
end
private
def sort!
@mapping = @mapping.sort_by { |h, p, m, a| -p.size }
end
end
end

View file

@ -31,6 +31,11 @@ module Padrino
FileUtils.mkdir_p(File.dirname(options[:pid]))
end
options[:server] = detect_rack_handler if options[:server].blank?
if options[:options].is_a?(Array)
parsed_server_options = options.delete(:options).map { |opt| opt.split('=', 2) }.flatten
server_options = Hash[*parsed_server_options].symbolize_keys!
options.merge!(server_options)
end
new(options, app).start
end

View file

@ -2,8 +2,9 @@
# This file loads certain extensions required by Padrino from ActiveSupport.
#
require 'active_support/core_ext/module/aliasing' # alias_method_chain
require 'active_support/core_ext/hash/keys' # symbolize_keys
require 'active_support/core_ext/hash/reverse_merge' # reverse_merge
require 'active_support/core_ext/hash/keys' # symbolize_keys
require 'active_support/core_ext/hash/indifferent_access' # params[:foo]
require 'active_support/core_ext/hash/slice' # slice
require 'active_support/core_ext/object/blank' # present?
require 'active_support/core_ext/array/extract_options' # extract_options
@ -199,27 +200,41 @@ end
# puts help.red.bold
#
class String
def self.colors
@_colors ||= {
:clear => 0,
:bold => 1,
:black => 30,
:red => 31,
:green => 32,
:yellow => 33,
:blue => 34,
:magenta => 35,
:cyan => 36,
:white => 37
}
# colorize(:red)
def colorize(color)
Colorizer.send(color, self)
end
colors.each do |color, value|
define_method(color) do
["\e[", value.to_s, "m", self, "\e[", self.class.colors[:clear], "m"] * ''
# Used to colorize strings for the shell
class Colorizer
# Returns colors integer mapping
def self.colors
@_colors ||= {
:clear => 0,
:bold => 1,
:black => 30,
:red => 31,
:green => 32,
:yellow => 33,
:blue => 34,
:magenta => 35,
:cyan => 36,
:white => 37
}
end
# Defines class level color methods
# i.e Colorizer.red("hello")
class << self
Colorizer.colors.each do |color, value|
define_method(color) do |target|
"\e[#{value}m" << target << "\e[0m"
end
end
end
end
# Strip unnecessary indentation of the front of a string
def undent
gsub(/^.{#{slice(/^ +/).size}}/, '')
end
@ -242,19 +257,3 @@ I18n.load_path += Dir["#{File.dirname(__FILE__)}/locale/*.yml"] if defined?(I18n
# Used to determine if this file has already been required
#
module SupportLite; end
module Padrino
class Utils
###
# Silences output verbosity level so load
# errors are not visible when safe_load(file)
#
def self.silence_output
@verbosity_level, $-v = $-v, nil
end
def self.unsilence_output
$-v = @verbosity_level
end
end
end

View file

@ -6,7 +6,7 @@
#
module Padrino
# The version constant for the current version of Padrino.
VERSION = '0.11.4' unless defined?(Padrino::VERSION)
VERSION = '0.12.0' unless defined?(Padrino::VERSION)
#
# The current Padrino version.

View file

@ -14,6 +14,7 @@ Gem::Specification.new do |s|
s.required_rubygems_version = ">= 1.3.6"
s.version = Padrino.version
s.date = Time.now.strftime("%Y-%m-%d")
s.license = "MIT"
s.extra_rdoc_files = Dir["*.rdoc"]
s.files = `git ls-files`.split("\n")
@ -38,6 +39,6 @@ Gem::Specification.new do |s|
end
s.add_dependency("http_router", "~> 0.11.0")
s.add_dependency("thor", "~> 0.17.0")
s.add_dependency("activesupport", ">= 3.1", "< 4.0")
s.add_dependency("activesupport", ">= 3.1")
s.add_dependency("rack-protection", ">= 1.5.0")
end

View file

@ -0,0 +1,8 @@
require 'resolv'
SystemDemo.helpers do
def resolv
Resolv.name
end
end

Some files were not shown because too many files have changed in this diff Show more