Merge remote-tracking branch 'origin/v3-stable'

Conflicts:
	CHANGELOG.md
	middleman-core/lib/middleman-core/core_extensions/rendering.rb
	middleman-core/lib/middleman-core/extensions.rb
	middleman-core/lib/middleman-core/version.rb
	middleman-core/lib/middleman-more/core_extensions/compass.rb
	middleman-core/lib/middleman-more/core_extensions/default_helpers.rb
	middleman-core/middleman-core.gemspec
This commit is contained in:
Ben Hollis 2014-02-22 18:11:37 -08:00
commit 7b46fd6524
278 changed files with 2390 additions and 1479 deletions

View file

@ -3,13 +3,24 @@ master
* Asciidoc information now available with the `asciidoc` local, which is a normal hash. * Asciidoc information now available with the `asciidoc` local, which is a normal hash.
* Remove `page` template local. Use `current_resource` instead. * Remove `page` template local. Use `current_resource` instead.
* Dropped support for `page` & `proxy` blocks. * Dropped support for providing a block to `page` & `proxy`.
* Dropped support for instance variables inside templates. * Dropped support for instance variables inside templates.
* Moved all rendering into `TemplateRenderer` and `FileRenderer` * Moved all rendering into `TemplateRenderer` and `FileRenderer`
* Placed all template evaluation inside the `TemplateContext` class * Placed all template evaluation inside the `TemplateContext` class
* Remove deprecated `request` instance * Remove deprecated `request` instance
* Remove old module-style extension support * Remove old module-style extension support
* Placed all `config.rb` evaluation inside the `ConfigContext` class * Placed all `config.rb` evaluation inside the `ConfigContext` class
* 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
===
* Specify the full path to the NEWLINE constant
* Refactor some internals which were dependent on certain order of operations
* Updated i18n dep
* Updated Uglifier dep
3.2.1 3.2.1
=== ===

View file

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

View file

@ -64,6 +64,8 @@ module Middleman::Cli
opts[:glob] = options['glob'] if options.has_key?('glob') opts[:glob] = options['glob'] if options.has_key?('glob')
opts[:clean] = options['clean'] opts[:clean] = options['clean']
self.class.shared_instance.run_hook :before_build, self
action BuildAction.new(self, opts) action BuildAction.new(self, opts)
self.class.shared_instance.run_hook :after_build, self self.class.shared_instance.run_hook :after_build, self

View file

@ -34,7 +34,7 @@ module Middleman::Cli
config[:environment] = opts[:environment].to_sym config[:environment] = opts[:environment].to_sym
end end
logger(opts[:debug] ? 0 : 1, opts[:instrumenting] || false) ::Middleman::Logger.singleton(opts[:debug] ? 0 : 1, opts[:instrumenting] || false)
end end
# TODO: get file watcher / reload! working in console # TODO: get file watcher / reload! working in console

View file

@ -43,7 +43,7 @@ module Middleman::Cli
:desc => 'Skip Git ignores and keeps' :desc => 'Skip Git ignores and keeps'
# The init task # The init task
# @param [String] name # @param [String] name
def init(name) def init(name = '.')
key = options[:template].to_sym key = options[:template].to_sym
unless ::Middleman::Templates.registered.has_key?(key) unless ::Middleman::Templates.registered.has_key?(key)
raise Thor::Error.new "Unknown project template '#{key}'" raise Thor::Error.new "Unknown project template '#{key}'"

View file

@ -24,7 +24,7 @@
# page "/admin/*" # page "/admin/*"
# end # end
# Proxy pages (http://middlemanapp.com/dynamic-pages/) # Proxy pages (http://middlemanapp.com/basics/dynamic-pages/)
# proxy "/this-page-has-no-template.html", "/template-file.html", :locals => { # proxy "/this-page-has-no-template.html", "/template-file.html", :locals => {
# :which_fake_page => "Rendering a fake page with a local variable" } # :which_fake_page => "Rendering a fake page with a local variable" }

View file

@ -16,6 +16,16 @@ Feature: Middleman CLI
| source/stylesheets/all.css | | source/stylesheets/all.css |
| source/stylesheets/normalize.css | | source/stylesheets/normalize.css |
Scenario: Create a new project in the current directory
Given a directory named "MY_PROJECT"
When I cd to "MY_PROJECT"
And I run `middleman init`
Then the exit status should be 0
And the following files should exist:
| Gemfile |
| config.rb |
| source/index.html.erb |
Scenario: Create a new project (alias i) Scenario: Create a new project (alias i)
When I run `middleman i MY_PROJECT` When I run `middleman i MY_PROJECT`
Then a directory named "MY_PROJECT" should exist Then a directory named "MY_PROJECT" should exist

View file

@ -28,3 +28,11 @@ Feature: Layouts dir
When I go to "/index.html" When I go to "/index.html"
Then I should see "contents of the layout" Then I should see "contents of the layout"
Scenario: Prefer a layout in the layouts_dir to one with the same name in the root
Given a fixture app "layouts-dir-app"
And a file named "config.rb" with:
"""
"""
And the Server is running
When I go to "/ambiguous.html"
Then I should see "contents of the layout in layouts_dir"

View file

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

View file

@ -1,4 +1,4 @@
- capture_html :from_template do = capture_html :from_template do
%h1= "I am the yielded content haml" %h1= "I am the yielded content haml"
%p I am in the template %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>" = "I am the yielded content haml <s>with html tags</s>"
%p I am in the template %p I am in the template

View file

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

View file

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

View file

@ -0,0 +1,5 @@
---
layout: other
---
Hello

View file

@ -0,0 +1,3 @@
contents of the layout in layouts_dir
<%= yield %>

View file

@ -0,0 +1,3 @@
contents of the layout in the root
<%= yield %>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,3 @@
- wrap_layout :master_slim do = wrap_layout :master_slim do
h2 Outer h2 Outer
== yield == 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

@ -3,7 +3,7 @@ require 'i18n'
# Don't fail on invalid locale, that's not what our current # Don't fail on invalid locale, that's not what our current
# users expect. # users expect.
::I18n.config.enforce_available_locales = false ::I18n.enforce_available_locales = false
# Use ActiveSupport JSON # Use ActiveSupport JSON
require 'active_support/json' require 'active_support/json'
@ -41,6 +41,9 @@ module Middleman
# Ready (all loading and parsing of extensions complete) hook # Ready (all loading and parsing of extensions complete) hook
define_hook :ready define_hook :ready
# Runs before the build is started
define_hook :before_build
# Runs after the build is finished # Runs after the build is finished
define_hook :after_build define_hook :after_build

View file

@ -75,6 +75,7 @@ module Middleman
# Bind app hooks to local methods # Bind app hooks to local methods
bind_before_configuration bind_before_configuration
bind_after_configuration bind_after_configuration
bind_before_build
bind_after_build bind_after_build
end end
@ -136,6 +137,19 @@ module Middleman
end end
end end
def bind_before_build
ext = self
if ext.respond_to?(:before_build)
@klass.before_build do |builder|
if ext.method(:before_build).arity === 1
ext.before_build(builder)
else
ext.before_build
end
end
end
end
def bind_after_build def bind_after_build
ext = self ext = self
if ext.respond_to?(:after_build) if ext.respond_to?(:after_build)

View file

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

View file

@ -7,30 +7,28 @@ module Middleman
# once registered # once registered
def registered(app) 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 app.before_configuration do
template_extensions :erb => :html template_extensions :erb => :html
end end
# After config # After config
app.after_configuration do app.after_configuration do
# Find the user's prefered engine ::Tilt.prefer(Template, :erb)
# 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])
end end
end end
alias :included :registered alias :included :registered
end 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 end
end end

View file

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

View file

@ -14,10 +14,6 @@ module Middleman
# Default sass options # Default sass options
app.config.define_setting :sass, {}, 'Sass engine 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 app.before_configuration do
template_extensions :scss => :css, template_extensions :scss => :css,
:sass => :css :sass => :css

View file

@ -1,6 +1,20 @@
# Load gem # Load gem
require 'slim' 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 Middleman
module Renderers module Renderers

View file

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

View file

@ -1,16 +1,20 @@
if !defined?(::Padrino::Helpers) if !defined?(::Padrino::Helpers)
require 'vendored-middleman-deps/padrino-core-0.11.4/lib/padrino-core/support_lite' require 'vendored-middleman-deps/padrino-core-0.12.0/lib/padrino-core/support_lite'
require 'vendored-middleman-deps/padrino-helpers-0.11.4/lib/padrino-helpers' 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 end
class Padrino::Helpers::OutputHelpers::ErbHandler class Padrino::Helpers::OutputHelpers::ErbHandler
# Force Erb capture not to use safebuffer # Force Erb capture not to use safebuffer
def capture_from_template(*args, &block) def capture_from_template(*args, &block)
self.output_buffer, _buf_was = '', self.output_buffer self.output_buffer, _buf_was = '', self.output_buffer
captured_block = block.call(*args) raw = block.call(*args)
ret = eval('@_out_buf', block.binding) captured = template.instance_variable_get(:@_out_buf)
self.output_buffer = _buf_was self.output_buffer = _buf_was
[ ret, captured_block ] engine_matches?(block) ? captured : raw
end end
end end
@ -49,6 +53,7 @@ class Middleman::CoreExtensions::DefaultHelpers < ::Middleman::Extension
attributes = tag_attributes(options) attributes = tag_attributes(options)
output = ActiveSupport::SafeBuffer.new output = ActiveSupport::SafeBuffer.new
output.safe_concat "<#{name}#{attributes}>" output.safe_concat "<#{name}#{attributes}>"
if content.respond_to?(:each) && !content.is_a?(String) if content.respond_to?(:each) && !content.is_a?(String)
content.each { |c| output.safe_concat c; output.safe_concat ::Padrino::Helpers::TagHelpers::NEWLINE } content.each { |c| output.safe_concat c; output.safe_concat ::Padrino::Helpers::TagHelpers::NEWLINE }
else else

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

View file

@ -1,12 +1,10 @@
require 'padrino-core/application/flash' require 'padrino-core/application/flash'
require 'padrino-core/application/rendering' require 'padrino-core/application/rendering'
require 'padrino-core/application/routing' require 'padrino-core/application/routing'
require 'padrino-core/application/showexceptions' require 'padrino-core/application/show_exceptions'
require 'padrino-core/application/authenticity_token'
module Padrino module Padrino
class ApplicationSetupError < RuntimeError
end
## ##
# Subclasses of this become independent Padrino applications # Subclasses of this become independent Padrino applications
# (stemming from Sinatra::Application). # (stemming from Sinatra::Application).
@ -26,20 +24,24 @@ module Padrino
Padrino.logger Padrino.logger
end 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 class << self
def inherited(base) def inherited(base)
begun_at = Time.now begun_at = Time.now
CALLERS_TO_IGNORE.concat(PADRINO_IGNORE_CALLERS) CALLERS_TO_IGNORE.concat(PADRINO_IGNORE_CALLERS)
base.default_configuration! 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 logger.devel :setup, begun_at, base
super(base) # Loading the subclass inherited method super(base)
end end
## ##
@ -54,10 +56,10 @@ module Padrino
# MyApp.reload! # MyApp.reload!
# #
def reload! def reload!
logger.devel "Reloading #{settings}" logger.devel "Reloading application #{settings}"
reset! reset!
reset_router! reset_router!
Padrino.require_dependencies(settings.app_file, :force => true) # Reload the app file Padrino.require_dependencies(settings.app_file, :force => true)
require_dependencies require_dependencies
default_filters! default_filters!
default_routes! default_routes!
@ -90,6 +92,26 @@ module Padrino
router.routes router.routes
end 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. # Setup the application by registering initializers, load paths and logger.
# Invoked automatically when an application is first instantiated. # Invoked automatically when an application is first instantiated.
@ -103,11 +125,11 @@ module Padrino
settings.default_routes! settings.default_routes!
settings.default_errors! settings.default_errors!
if defined?(I18n) if defined?(I18n)
Reloader.special_files += settings.locale_path
I18n.load_path << settings.locale_path I18n.load_path << settings.locale_path
I18n.reload! I18n.reload!
end end
@_configured = true @_configured = true
@_configured
end end
## ##
@ -127,7 +149,13 @@ module Padrino
# directory that need to be added to +$LOAD_PATHS+ from this application # directory that need to be added to +$LOAD_PATHS+ from this application
# #
def load_paths 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 end
## ##
@ -143,8 +171,14 @@ module Padrino
# #
def dependencies def dependencies
[ [
'urls.rb', 'config/urls.rb', 'mailers/*.rb', 'mailers.rb', 'urls.rb',
'controllers/**/*.rb', 'controllers.rb', 'helpers/**/*.rb', 'helpers.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 ].map { |file| Dir[File.join(settings.root, file)] }.flatten
end end
@ -167,38 +201,67 @@ module Padrino
@_prerequisites ||= [] @_prerequisites ||= []
end end
def default(option, *args, &block)
set(option, *args, &block) unless respond_to?(option)
end
protected protected
## ##
# Defines default settings for Padrino application. # Defines default settings for Padrino application.
# #
def default_configuration! def default_configuration!
# Overwriting Sinatra defaults set :app_file, File.expand_path(caller_files.first || $0)
set :app_file, File.expand_path(caller_files.first || $0) # Assume app file is first caller set :app_name, settings.to_s.underscore.to_sym
set :environment, Padrino.env set :environment, Padrino.env
set :reload, Proc.new { development? } set :reload, Proc.new { development? }
set :logging, Proc.new { development? } set :logging, Proc.new { development? }
set :method_override, true 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' 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 :authentication, false
set :sessions, false
set :locale_path, Proc.new { Dir[File.join(settings.root, '/locale/**/*.{rb,yml}')] }
# Authenticity token
set :protect_from_csrf, false set :protect_from_csrf, false
set :allow_disabled_csrf, false set :allow_disabled_csrf, false
# Load the Global Configurations end
class_eval(&Padrino.apps_configuration) if Padrino.apps_configuration
##
# Applies global padrino configuration blocks to current application.
#
def global_configuration!
Padrino.global_configurations.each do |configuration|
class_eval(&configuration)
end
end end
## ##
@ -220,9 +283,7 @@ module Padrino
# #
def default_filters! def default_filters!
before do before do
unless @_content_type response['Content-Type'] = 'text/html;charset=utf-8' unless @_content_type
response['Content-Type'] = 'text/html;charset=utf-8'
end
end end
end end
@ -249,7 +310,20 @@ module Padrino
Padrino.require_dependencies(dependencies, :force => true) Padrino.require_dependencies(dependencies, :force => true)
end 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 private
# Overrides the default middleware for Sinatra based on Padrino conventions. # Overrides the default middleware for Sinatra based on Padrino conventions.
# Also initializes the application after setting up the middleware. # Also initializes the application after setting up the middleware.
def setup_default_middleware(builder) def setup_default_middleware(builder)
@ -267,9 +341,33 @@ module Padrino
# sets up csrf protection for the app: # sets up csrf protection for the app:
def setup_csrf_protection(builder) def setup_csrf_protection(builder)
if protect_from_csrf? && !sessions? check_csrf_protection_dependency
raise(<<-ERROR)
`protect_from_csrf` is activated, but `sessions` are not. To enable csrf 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: protection, use:
enable :sessions enable :sessions
@ -277,19 +375,12 @@ protection, use:
or deactivate protect_from_csrf: or deactivate protect_from_csrf:
disable :protect_from_csrf disable :protect_from_csrf
ERROR
end
if protect_from_csrf? If you use a different session store, ignore this warning using:
if allow_disabled_csrf?
builder.use Rack::Protection::AuthenticityToken, # in boot.rb:
:reaction => :report, Padrino::IGNORE_CSRF_SETUP_WARNING = true
:report_key => 'protection.csrf.failed', ERROR
:logger => logger
else
builder.use Rack::Protection::AuthenticityToken,
:logger => logger
end
end end
end 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 options[:layout] = @layout if options[:layout].nil? || options[:layout] == true
# Resolve layouts similar to in Rails # Resolve layouts similar to in Rails
if options[:layout].nil? && !settings.templates.has_key?(:layout) 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 # We need to force layout false so sinatra don't try to render it
options[:layout] = layout_path || false 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] options[:layout_engine] = layout_engine || engine if options[:layout]
elsif options[:layout].present? 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 end
# Default to original layout value if none found. # Default to original layout value if none found.
options[:layout] ||= layout_was options[:layout] ||= layout_was
@ -272,15 +274,21 @@ module Padrino
end end
# Resolve view path and options. # 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" view_path = options.delete(:views) || settings.views || "./views"
target_extension = File.extname(template_path)[1..-1] || "none" # explicit template extension target_extension = File.extname(template_path)[1..-1] || "none" # explicit template extension
template_path = template_path.chomp(".#{target_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 # 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_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 } [template_file, template_engine] unless IGNORE_FILE_PATTERN.any? { |pattern| template_engine.to_s =~ pattern }
end end

View file

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

View file

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

View file

@ -5,7 +5,7 @@ module Padrino
%r{lib/padrino-.*$}, %r{lib/padrino-.*$},
%r{/padrino-.*/(lib|bin)}, %r{/padrino-.*/(lib|bin)},
%r{/bin/padrino$}, %r{/bin/padrino$},
%r{/sinatra(/(base|main|showexceptions))?\.rb$}, %r{/sinatra(/(base|main|show_?exceptions))?\.rb$},
%r{lib/tilt.*\.rb$}, %r{lib/tilt.*\.rb$},
%r{lib/rack.*\.rb$}, %r{lib/rack.*\.rb$},
%r{lib/mongrel.*\.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 :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 :pid, :type => :string, :aliases => "-i", :desc => "File to store pid."
method_option :debug, :type => :boolean, :desc => "Set debugging flags." 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 def start
prepare :start prepare :start
require File.expand_path("../adapter", __FILE__) require File.expand_path("../adapter", __FILE__)
require File.expand_path('config/boot.rb') 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 end
desc "stop", "Stops the Padrino application (alternatively use 'st')." desc "stop", "Stops the Padrino application (alternatively use 'st')."
@ -116,7 +124,7 @@ module Padrino
help(task.to_s) help(task.to_s)
raise SystemExit raise SystemExit
end end
ENV["PADRINO_ENV"] ||= ENV["RACK_ENV"] ||= options.environment.to_s ENV["RACK_ENV"] ||= options.environment.to_s
chdir(options.chdir) chdir(options.chdir)
unless File.exist?('config/boot.rb') unless File.exist?('config/boot.rb')
puts "=> Could not find boot file in: #{options.chdir}/config/boot.rb !!!" puts "=> Could not find boot file in: #{options.chdir}/config/boot.rb !!!"
@ -124,6 +132,29 @@ module Padrino
end end
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 protected
def self.banner(task=nil, *args) def self.banner(task=nil, *args)

View file

@ -16,6 +16,16 @@ task :environment do
end end
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" desc "Generate a secret key"
task :secret do task :secret do
shell.say SecureRandom.hex(32) shell.say SecureRandom.hex(32)

View file

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

View file

@ -11,7 +11,7 @@ nl:
day_names: [zondag, maandag, dinsdag, woensdag, donderdag, vrijdag, zaterdag] day_names: [zondag, maandag, dinsdag, woensdag, donderdag, vrijdag, zaterdag]
abbr_day_names: [zo, ma, di, wo, do, vr, za] 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] abbr_month_names: [~, jan, feb, maa, apr, mei, jun, jul, aug, sep, okt, nov, dec]
order: order:
- day - day

View file

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

View file

@ -52,10 +52,10 @@ module Padrino
# :devel:: Development-related information that is unnecessary in debug mode # :devel:: Development-related information that is unnecessary in debug mode
# #
Levels = { Levels = {
:fatal => 7, :fatal => 4,
:error => 6, :error => 3,
:warn => 4, :warn => 2,
:info => 3, :info => 1,
:debug => 0, :debug => 0,
:devel => -1, :devel => -1,
} unless defined?(Levels) } unless defined?(Levels)
@ -194,13 +194,13 @@ module Padrino
# #
def colorize(string, *colors) def colorize(string, *colors)
colors.each do |c| colors.each do |c|
string = string.send(c) string = string.colorize(c)
end end
string string
end end
def stylized_level(level) 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"] * '' [style, super, "\e[0m"] * ''
end end
end end
@ -280,7 +280,7 @@ module Padrino
stream = case config[:stream] stream = case config[:stream]
when :to_file 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+') File.new(Padrino.root('log', "#{Padrino.env}.log"), 'a+')
when :null then StringIO.new when :null then StringIO.new
when :stdout then $stdout 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") # Mounter.new("blog_app", :app_file => "/path/to/blog/app.rb").to("/blog")
# #
class Mounter class Mounter
DEFAULT_CASCADE = [404, 405]
class MounterException < RuntimeError class MounterException < RuntimeError
end 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 # @param [String, Padrino::Application] name
@ -31,8 +32,9 @@ module Padrino
@app_file = options[:app_file] || locate_app_file @app_file = options[:app_file] || locate_app_file
@app_obj = options[:app_obj] || app_constant || locate_app_object @app_obj = options[:app_obj] || app_constant || locate_app_object
ensure_app_file! || ensure_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 = "/" @uri_root = "/"
@cascade = options[:cascade] ? true == options[:cascade] ? DEFAULT_CASCADE.dup : Array(options[:cascade]) : []
Padrino::Reloader.exclude_constants << @app_class Padrino::Reloader.exclude_constants << @app_class
end end
@ -82,11 +84,12 @@ module Padrino
def map_onto(router) def map_onto(router)
app_data, app_obj = self, @app_obj app_data, app_obj = self, @app_obj
app_obj.set :uri_root, app_data.uri_root 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 :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 :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 :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. 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) router.map(:to => app_obj, :path => app_data.uri_root, :host => app_data.app_host)
end end
@ -106,7 +109,9 @@ module Padrino
# #
def named_routes def named_routes
app_obj.routes.map { |route| 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 request_method = route.request_methods.first
next if route.name.blank? || request_method == 'HEAD' next if route.name.blank? || request_method == 'HEAD'
original_path = route.original_path.is_a?(Regexp) ? route.original_path.inspect : route.original_path 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) host = Regexp.new("^#{Regexp.quote(host)}$", true, 'n') unless host.nil? || host.is_a?(Regexp)
@mapping << [host, path, match, app] @mapping << [host, path, match, app]
sort!
end end
# The call handler setup to route a request given the mappings specified. # The call handler setup to route a request given the mappings specified.
def call(env) def call(env)
began_at = Time.now
path_info = env["PATH_INFO"].to_s path_info = env["PATH_INFO"].to_s
script_name = env['SCRIPT_NAME'] script_name = env['SCRIPT_NAME']
http_host = env['HTTP_HOST'] http_host = env['HTTP_HOST']
last_result = nil
@mapping.each do |host, path, match, app| @mapping.each do |host, path, match, app|
next unless host.nil? || http_host =~ host next unless host.nil? || http_host =~ host
@ -78,18 +79,16 @@ module Padrino
rest = "/" if rest.empty? rest = "/" if rest.empty?
return app.call( last_result = app.call(env.merge('SCRIPT_NAME' => script_name + path, 'PATH_INFO' => rest))
env.merge(
'SCRIPT_NAME' => (script_name + path), cascade_setting = app.respond_to?(:cascade) ? app.cascade : true
'PATH_INFO' => rest)) 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 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 end
end end

View file

@ -31,6 +31,11 @@ module Padrino
FileUtils.mkdir_p(File.dirname(options[:pid])) FileUtils.mkdir_p(File.dirname(options[:pid]))
end end
options[:server] = detect_rack_handler if options[:server].blank? 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 new(options, app).start
end end

View file

@ -2,8 +2,9 @@
# This file loads certain extensions required by Padrino from ActiveSupport. # 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/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/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/hash/slice' # slice
require 'active_support/core_ext/object/blank' # present? require 'active_support/core_ext/object/blank' # present?
require 'active_support/core_ext/array/extract_options' # extract_options require 'active_support/core_ext/array/extract_options' # extract_options
@ -199,27 +200,41 @@ end
# puts help.red.bold # puts help.red.bold
# #
class String class String
def self.colors # colorize(:red)
@_colors ||= { def colorize(color)
:clear => 0, Colorizer.send(color, self)
:bold => 1,
:black => 30,
:red => 31,
:green => 32,
:yellow => 33,
:blue => 34,
:magenta => 35,
:cyan => 36,
:white => 37
}
end end
colors.each do |color, value| # Used to colorize strings for the shell
define_method(color) do class Colorizer
["\e[", value.to_s, "m", self, "\e[", self.class.colors[:clear], "m"] * '' # 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
end end
# Strip unnecessary indentation of the front of a string
def undent def undent
gsub(/^.{#{slice(/^ +/).size}}/, '') gsub(/^.{#{slice(/^ +/).size}}/, '')
end 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 # Used to determine if this file has already been required
# #
module SupportLite; end 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 module Padrino
# The version constant for the current version of 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. # The current Padrino version.

View file

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

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