reintroduce a vendored copy of padrino helpers, fix automatic image sizes, begin move to pure ruby asset_url pipeline

This commit is contained in:
Thomas Reynolds 2011-11-18 16:17:14 -08:00
parent 0d0eda71c2
commit a5e0dd49a9
181 changed files with 16403 additions and 64 deletions

View file

@ -2,56 +2,16 @@ module Middleman::CoreExtensions::Assets
class << self
def registered(app)
# Disable Padrino cache buster until explicitly enabled
app.set :asset_stamp, false
# app.set :asset_stamp, false
app.extend ClassMethods
app.helpers Helpers
app.register_asset_handler :base do |path, prefix, request|
path.include?("://") ? path : File.join(app.http_prefix || "/", prefix, path)
end
app.send :include, InstanceMethod
end
alias :included :registered
end
module ClassMethods
def register_asset_handler(handler_name, &block)
@asset_handler_map ||= []
@asset_handler_stack ||= []
if block_given?
@asset_handler_stack << block
@asset_handler_map << handler_name
end
end
def asset_handler_get_url(path, prefix="", request=nil)
@asset_handler_map ||= []
@asset_handler_stack ||= []
@asset_handler_stack.last.call(path, prefix, request)
end
def before_asset_handler(position, *args)
@asset_handler_map ||= []
@asset_handler_stack ||= []
current_index = @asset_handler_map.index(position)
return nil unless current_index
previous = current_index - 1
if (previous >= 0) && (previous < @asset_handler_map.length)
@asset_handler_stack[previous].call(*args)
else
nil
end
end
end
module Helpers
module InstanceMethod
def asset_url(path, prefix="")
self.class.asset_handler_get_url(path, prefix, request)
path.include?("://") ? path : File.join(self.http_prefix || "/", prefix, path)
end
end
end

View file

@ -1,7 +1,26 @@
require "middleman/vendor/padrino-core-0.10.5/lib/padrino-core/support_lite"
require 'i18n'
require 'enumerator'
require 'active_support/core_ext/string/conversions' # to_date
require 'active_support/core_ext/float/rounding' # round
require 'active_support/option_merger' # with_options
require 'active_support/core_ext/object/with_options' # with_options
require 'active_support/inflector' # humanize
FileSet.glob_require('../vendor/padrino-helpers-0.10.5/lib/padrino-helpers/**/*.rb', __FILE__)
module Middleman::CoreExtensions::DefaultHelpers
class << self
def registered(app)
# Middleman Helpers
app.helpers ::Padrino::Helpers::OutputHelpers
app.helpers ::Padrino::Helpers::TagHelpers
app.helpers ::Padrino::Helpers::AssetTagHelpers
app.helpers ::Padrino::Helpers::FormHelpers
app.helpers ::Padrino::Helpers::FormatHelpers
app.helpers ::Padrino::Helpers::RenderHelpers
app.helpers ::Padrino::Helpers::NumberHelpers
app.helpers ::Padrino::Helpers::TranslationHelpers
app.helpers Helpers
end
alias :included :registered

View file

@ -7,12 +7,12 @@ module Middleman::Features::AssetHost
end
end
app.register_asset_handler :asset_host do |path, prefix, request|
original_output = app.before_asset_handler(:asset_host, path, prefix, request)
app.register_asset_handler :asset_host do |path, prefix|
original_output = self.before_asset_handler(:asset_host, path, prefix)
valid_extensions = %w(.png .gif .jpg .jpeg .js .css)
asset_prefix = app.asset_host.call(original_output)
asset_prefix = self.asset_host.call(original_output)
File.join(asset_prefix, original_output)
end

View file

@ -3,25 +3,28 @@ module Middleman::Features::AutomaticImageSizes
def registered(app)
require "middleman/features/automatic_image_sizes/fastimage"
app.helpers Helpers
app.send :include, InstanceMethods
end
alias :included :registered
end
module Helpers
module InstanceMethods
def image_tag(path, params={})
if (!params[:width] || !params[:height]) && !path.include?("://")
if !params.has_key?(:width) && !params.has_key?(:height) && !path.include?("://")
params[:alt] ||= ""
http_prefix = settings.http_images_path rescue settings.images_dir
http_prefix = self.http_images_path rescue self.images_dir
begin
real_path = File.join(settings.views, settings.images_dir, path)
if File.exists? real_path
dimensions = ::FastImage.size(real_path, :raise_on_failure => true)
params[:width] ||= dimensions[0]
params[:height] ||= dimensions[1]
real_path = File.join(self.views, self.images_dir, path)
full_path = File.expand_path(real_path, self.root)
http_prefix = self.http_images_path rescue self.images_dir
if File.exists? full_path
dimensions = ::FastImage.size(full_path, :raise_on_failure => true)
params[:width] = dimensions[0]
params[:height] = dimensions[1]
end
rescue
# $stderr.puts params.inspect
end
end

View file

@ -1,8 +1,8 @@
module Middleman::Features::CacheBuster
class << self
def registered(app)
app.register_asset_handler :cache_buster do |path, prefix, request|
http_path = app.before_asset_handler(:cache_buster, path, prefix, request)
app.register_asset_handler :cache_buster do |path, prefix|
http_path = app.before_asset_handler(:cache_buster, path, prefix)
if http_path.include?("://") || !%w(.css .png .jpg .js .gif).include?(File.extname(http_path))
http_path

View file

@ -5,20 +5,20 @@ module Middleman::Features::RelativeAssets
config.relative_assets = true
end
app.register_asset_handler :relative_assets do |path, prefix, request|
app.register_asset_handler :relative_assets do |path, prefix|
begin
prefix = app.images_dir if prefix == app.http_images_path
prefix = self.images_dir if prefix == self.http_images_path
rescue
end
if path.include?("://")
app.before_asset_handler(:relative_assets, path, prefix, request)
self.before_asset_handler(:relative_assets, path, prefix)
elsif path[0,1] == "/"
path
else
path = File.join(prefix, path) if prefix.length > 0
request_path = request.path_info.dup
request_path << app.index_file if path.match(%r{/$})
request_path = @request_path.dup
request_path << self.index_file if path.match(%r{/$})
request_path.gsub!(%r{^/}, '')
parts = request_path.split('/')

View file

@ -0,0 +1,5 @@
lib/**/*.rb
bin/*
-
README.rdoc
LICENSE.txt

View file

@ -0,0 +1,22 @@
## MAC OS
.DS_Store
## TEXTMATE
*.tmproj
tmtags
## EMACS
*~
\#*
.\#*
## VIM
*.swp
## PROJECT::GENERAL
coverage
rdoc
pkg
## PROJECT::SPECIFIC
test/tmp/*

View file

@ -0,0 +1 @@
--title 'Padrino Core Documentation' --protected

View file

@ -0,0 +1,20 @@
Copyright (c) 2011 Padrino
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,294 @@
= Padrino (padrino-core)
Padrino is the godfather of Sinatra.
== Preface
Padrino is a ruby framework built upon the excellent {Sinatra Microframework}[http://www.sinatrarb.com].
Sinatra is a DSL for creating simple web applications in Ruby with speed and minimal effort.
This framework tries hard to make it as fun and easy as possible to code much more advanced web applications by
building upon the Sinatra philosophies and foundation.
== Introduction
Many people love Sinatra's simplicity and lightweight but often quickly come to miss a great deal
of functionality provided by other web frameworks such as Rails when building non-trivial applications.
Our goal with this framework is to match the essence of Sinatra and at the same time create a standard library
of tools, helpers and components that will make Sinatra suitable for more complex applications.
Here is a brief overview of functionality provided by the Padrino framework:
Agnostic:: Full support for many popular testing, templating, mocking, and data storage choices.
Generators:: Create Padrino applications, models, controllers i.e: padrino-gen project.
Mountable:: Unlike other ruby frameworks, principally designed for mounting multiple apps.
Routing:: Full url named routes, named params, respond_to support, before/after filter support.
Tag Helpers:: View helpers such as: tag, content_tag, input_tag.
Asset Helpers:: View helpers such as: link_to, image_tag, javascript_include_tag.
Form Helpers:: Builder support such as: form_tag, form_for, field_set_tag, text_field.
Text Helpers:: Useful formatting like: time_ago_in_words, js_escape_html, sanitize_html.
Mailer:: Fast and simple delivery support for sending emails (akin to ActionMailer).
Admin:: Builtin Admin interface (like Django)
Logging:: Provide a unified logger that can interact with your ORM or any library.
Reloading:: Automatically reloads server code during development.
Localization:: Full support of I18n language localization and can auto-set user's locale.
Keep in mind, the user will be able to pull in these components
{seperately into existing Sinatra applications}[http://www.padrinorb.com/guides/standalone-usage-in-sinatra]
or use them altogether for a comprehensive upgrade to Sinatra (a full-stack Padrino application).
== Installation
To install the padrino framework, simply grab the latest version from gemcutter:
$ sudo gem install padrino
This will install the necessary padrino gems to get you started.
Now you are ready to use this gem to enhance your sinatra projects or to create new Padrino applications.
For a more detailed look at Padrino installation,
check out the {Installation Guide}[http://www.padrinorb.com/guides/installation].
== Usage
Padrino is a framework which builds on the existing functionality and Sinatra and provides a variety of
additional tools and helpers to build upon that foundation. This README and Padrino documentation in general will focus
on the enhancements to the core Sinatra functionality. To use Padrino, one should be familiar with the basic
usage of Sinatra itself.
Please check out the
{Understanding Sinatra}[http://www.padrinorb.com/guides/underlying-sinatra-overview] guide
to learn more about these fundamentals.
For information on how to use a specific gem in isolation within an existing Sinatra project, checkout the guide for
{Using Padrino in Sinatra}[http://www.padrinorb.com/guides/standalone-usage-in-sinatra].
== Getting Started
Once a developer understands Sinatra, Padrino is quite easy to get comfortable with since Padrino is simply a superset
of existing Sinatra Functionality! Best way to get started with building Padrino applications is to read following resources:
* {Blog Tutorial}[http://www.padrinorb.com/guides/blog-tutorial] - Step-by-step guide to building a blog application with Padrino.
* {Quick Overview}[http://www.padrinorb.com/guides/basic-projects] - Outlines basic generation commands.
* {Padrino Examples}[http://www.padrinorb.com/guides/examples] - List of known Padrino applications which can serve as examples.
== Enhanced Base Application (padrino-core)
Sinatra has support for classes which can be extended to create an application: <tt>Sinatra::Base</tt> and <tt>Sinatra::Application</tt>
These classes can be extended in order to create a Sinatra web application. These classes provide support for all the basic
functionality afforded by Sinatra.
Padrino has support for an enhanced base application class <tt>Padrino::Application</tt>. <tt>Padrino::Application</tt>
expands the capabilities of Sinatra::Application and automatically provides the resulting application access to all of
the padrino framework's functionalities.
=== Simple Application Definition
Let us first take a look at the simplest possible Padrino application:
# app.rb
PADRINO_ROOT = File.dirname(__FILE__) unless defined? PADRINO_ROOT
require 'padrino'
Padrino.load!
class SimpleApp < Padrino::Application
get '/' do
'Hello world'
end
# and for read better we can divide with controllers
controller '/admin' do
get '/foo' do
'Url is /admin/foo'
end
end
end
=== Enhanced Route Definitions and Controllers
For a complete overview of the Padrino routing and controller system,
check out the {Routing and Controller guide}[http://www.padrinorb.com/guides/controllers].
Suppose we wanted to add additional routes to our Padrino application, and we want to organize the routes
within a more structured layout. Simply add a <tt>controllers</tt> or <tt>app/controllers</tt> folder and create a file as such:
# Simple Example
SimpleApp.controllers do
get "/test" do
"Text to return"
end
end
You can also do more complex route alias definitions:
# app/controllers/example.rb
SimpleApp.controllers :posts do
get :index do
...
end
get :show, :with => :id do
# url generated is '/posts/show/:id'
# access params[:id]
end
end
as well as mapping the route aliases to an explicit url:
# app/controllers/example.rb
SimpleApp.controllers do
get :index, :map => '/index' do
...
end
get :account, :map => '/the/accounts/:name/and/:id' do
# access params[:name] and params[:index]
end
end
and even configure the respond_to for each route:
# app/controllers/example.rb
SimpleApp.controllers :admin do
get :show, :with => :id, :provides => :js do
"Url is /admin/show/#{params[:id]}.#{params[:format]}"
end
get :other, with => [:id, :name], respond_to => [:html, :json] do
case content_type
when :js then ... end
when :json then ... end
end
end
end
or auto lookup for current locale or content_type
# app/controllers/example.rb
SimpleApp.controllers :admin do
get :show, :with => :id, :provides => [html, :js] do
render "admin/show"
end
end
When you visit :+show+ and your I18n.locale == :ru Padrino try to look for "admin/show.ru.js.*" if nothing match that path
they try "admin/show.ru.*" then "admin/show.js.*" if none match return "admin/show.erb" (or other engine i.e. haml)
For a complete overview of the routing and controller system, check out the
{Routing and Controller guide}[http://www.padrinorb.com/guides/controllers].
=== Rendering
Unlike Sinatra, Padrino supports automatic template lookups such as:
# searches for 'account/index.{erb,haml,...}
render 'account/index'
This render does not require any template engine to be specified and will choose the first one that is discovered.
The existing render function works as well if an engine type should be specified:
# example.haml
render :haml, 'account/index'
For a complete overview of the Padrino rendering system, check out the
{Routing and Controller guide}[http://www.padrinorb.com/guides/controllers].
=== Layout
With Padrino you can (like rails do) use for your custom layout, disable it
class SimpleApp < Padrino::Application
# Disable layouts
disable layout
# Use the layout located in views/layouts/custom.haml
layout :custom
For a complete overview of the layout functionality,
check out the {Routing and Controller guide}[http://www.padrinorb.com/guides/controllers].
=== Mounting Applications
Padrino applications are all automatically mountable into other Padrino projects. This means that a given Padrino
project directory can easily mount multiple applications. This allows for better organization of complex applications,
re-usable applications that can be applied (i.e admin, auth, blog) and even more flexibility.
You can think of mountable applications as a 'full-featured' merb slice or rails engine. Instead of a separate construct,
any application can simply be packaged and mounted into another project.
Padrino stores application mounting information by default within <tt>config/apps.rb</tt>. This file is intended
to keep all information regarding what applications are mounted to which uri's.
For a complete look at mounting applications within a Padrino project,
check out the guide on {Mounting Applications}[http://www.padrinorb.com/guides/mounting-applications].
=== Auto Load Paths
Padrino also intelligently supports requiring useful files within your application automatically and provides
functionality for easily splitting up your application into separate files. Padrino automatically requires <tt>config/database.rb</tt>
as a convention for establishing database connection. Also, any files within the <tt>lib</tt> folder will be required
automatically by Padrino.
For a complete overview of auto-loaded paths within Padrino,
check out the {Padrino Development Guide}[http://www.padrinorb.com/guides/development-and-terminal-commands].
=== Application Logging
Padrino also supports robust logging capabilities. By default, logging information will
go to the STDOUT in development (for use in a console) and in an environment-specific log file <tt>log/development.log</tt>
in test and production environments.
To use the logger within a Padrino application, simply refer to the <tt>logger</tt> method accessible
within your app and any controller or views:
# controllers/example.rb
SimpleApp.controllers do
get("/test") { logger.info "This is a test" }
end
For a complete overview of Padrino logger functionality, check out the
{Padrino Development Guide}[http://www.padrinorb.com/guides/development-and-terminal-commands].
=== Development Reloader
Padrino applications also have the enabled ability to automatically reload all changing application files without
the need to restart the server. Through the use of a customized Rack middleware, all files on the 'load path'
are monitored and reloaded whenever changes are applied.
This makes rapid development much easier and provides a better alternative to 'shotgun' or 'rerun'
which requires the application server to be restarted which makes requests take much longer to complete.
For a complete overview of code reloading in development,
check out the {Padrino Development Guide}[http://www.padrinorb.com/guides/development-and-terminal-commands].
=== Terminal Commands
Padrino also comes equipped with multiple useful terminal commands which can be activated to perform
common tasks such as starting / stopping the application, executing the unit tests or activating an irb session.
The following commands are available:
# starts the app server (non-daemonized)
$ padrino start
# starts the app server (daemonized) with given port, environment and adapter
$ padrino start -d -p 3000 -e development -a thin
# Stops a daemonized app server
$ padrino stop
# Bootup the Padrino console (irb)
$ padrino console
# Run/List tasks
$ padrino rake
You can also create custom rake tasks as well. Using these commands can simplify common tasks
making development that much smoother.
For a complete overview of Padrino terminal commands, check out the
{Padrino Commands Guide}[http://www.padrinorb.com/guides/development-and-terminal-commands].
== Copyright
Copyright (c) 2011 Padrino. See LICENSE for details.

View file

@ -0,0 +1,5 @@
# coding:utf-8
RAKE_ROOT = __FILE__
require 'rubygems'
require File.expand_path(File.dirname(__FILE__) + '/../gem_rake_helper')

View file

@ -0,0 +1,9 @@
#!/usr/bin/env ruby
require 'rubygems' unless defined?(Gem)
require 'bundler/setup' if %w(Gemfile .components).all? { |f| File.exist?(f) }
padrino_core_path = File.expand_path('../../lib', __FILE__)
$:.unshift(padrino_core_path) if File.directory?(padrino_core_path) && !$:.include?(padrino_core_path)
require 'padrino-core/cli/base'
Padrino::Cli::Base.start(ARGV)

View file

@ -0,0 +1,167 @@
require 'sinatra/base'
require 'padrino-core/support_lite' unless defined?(SupportLite)
FileSet.glob_require('padrino-core/application/*.rb', __FILE__)
FileSet.glob_require('padrino-core/*.rb', __FILE__)
# The Padrino environment (falls back to the rack env or finally develop)
PADRINO_ENV = ENV["PADRINO_ENV"] ||= ENV["RACK_ENV"] ||= "development" unless defined?(PADRINO_ENV)
# The Padrino project root path (falls back to the first caller)
PADRINO_ROOT = ENV["PADRINO_ROOT"] ||= File.dirname(Padrino.first_caller) unless defined?(PADRINO_ROOT)
module Padrino
class ApplicationLoadError < RuntimeError # @private
end
class << self
##
# Helper method for file references.
#
# @param [Array<String>] args
# The directories to join to {PADRINO_ROOT}.
#
# @return [String]
# The absolute path.
#
# @example
# # Referencing a file in config called settings.yml
# Padrino.root("config", "settings.yml")
# # returns PADRINO_ROOT + "/config/setting.yml"
#
def root(*args)
File.expand_path(File.join(PADRINO_ROOT, *args))
end
##
# Helper method that return {PADRINO_ENV}.
#
# @return [Symbol]
# The Padrino Environment.
#
def env
@_env ||= PADRINO_ENV.to_s.downcase.to_sym
end
##
# The resulting rack builder mapping each 'mounted' application.
#
# @return [Padrino::Router]
# The router for the application.
#
# @raise [ApplicationLoadError]
# No applications were mounted.
#
def application
raise ApplicationLoadError, "At least one app must be mounted!" unless Padrino.mounted_apps && Padrino.mounted_apps.any?
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
end
##
# Configure Global Project Settings for mounted apps. These can be overloaded
# in each individual app's own personal configuration. This can be used like:
#
# @yield []
# The given block will be called to configure each application.
#
# @example
# Padrino.configure_apps do
# enable :sessions
# disable :raise_errors
# end
#
def configure_apps(&block)
@_global_configuration = block if block_given?
end
##
# Returns project-wide configuration settings defined in
# {configure_apps} block.
#
def apps_configuration
@_global_configuration
end
##
# Set +Encoding.default_internal+ and +Encoding.default_external+
# to +Encoding::UFT_8+.
#
# Please note that in +1.9.2+ with some template engines like +haml+
# you should turn off Encoding.default_internal to prevent problems.
#
# @see https://github.com/rtomayko/tilt/issues/75
#
# @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
nil
end
##
# Determines whether the dependencies are locked by Bundler.
# otherwise return nil
#
# @return [:locked, :unlocked, nil]
# Returns +:locked+ if the +Gemfile.lock+ file exists, or +:unlocked+
# if only the +Gemfile+ exists.
#
# @deprecated Will be removed in 1.0.0
#
def bundle
return :locked if File.exist?(root('Gemfile.lock'))
return :unlocked if File.exist?(root("Gemfile"))
end
##
# A Rack::Builder object that allows to add middlewares in front of all
# Padrino applications.
#
# @return [Array<Array<Class, Array, Proc>>]
# The middleware classes.
#
def middleware
@middleware ||= []
end
##
# Clears all previously configured middlewares.
#
# @return [Array]
# An empty array
#
def clear_middleware!
@middleware = []
end
##
# Convenience method for adding a Middleware to the whole padrino app.
#
# @param [Class] m
# The middleware class.
#
# @param [Array] args
# The arguments for the middleware.
#
# @yield []
# The given block will be passed to the initialized middleware.
#
def use(m, *args, &block)
middleware << [m, args, block]
end
end # self
end # Padrino

View file

@ -0,0 +1,270 @@
module Padrino
class ApplicationSetupError < RuntimeError # @private
end
##
# Subclasses of this become independent Padrino applications (stemming from Sinatra::Application)
# These subclassed applications can be easily mounted into other Padrino applications as well.
#
class Application < Sinatra::Base
# Support for advanced routing, controllers, url_for
register Padrino::Routing
##
# Returns the logger for this application.
#
# @return [Padrino::Logger] Logger associated with this app.
#
def logger
Padrino.logger
end
class << self
def inherited(base) # @private
logger.devel "Setup #{base}"
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)
super(base) # Loading the subclass inherited method
end
##
# Reloads the application files from all defined load paths
#
# This method is used from our Padrino Reloader during development mode
# in order to reload the source files.
#
# @return [TrueClass]
#
# @example
# MyApp.reload!
#
def reload!
logger.devel "Reloading #{self}"
@_dependencies = nil # Reset dependencies
reset! # Reset sinatra app
reset_router! # Reset all routes
Padrino.require_dependencies(self.app_file, :force => true) # Reload the app file
require_dependencies # Reload dependencies
default_filters! # Reload filters
default_routes! # Reload default routes
default_errors! # Reload our errors
I18n.reload! if defined?(I18n) # Reload also our translations
true
end
##
# Resets application routes to only routes not defined by the user
#
# @return [TrueClass]
#
# @example
# MyApp.reset_routes!
#
def reset_routes!
reset_router!
default_routes!
true
end
##
# Returns the routes of our app.
#
# @example
# MyApp.routes
#
def routes
router.routes
end
##
# Setup the application by registering initializers, load paths and logger
# Invoked automatically when an application is first instantiated
#
# @return [TrueClass]
#
def setup_application!
return if @_configured
self.require_dependencies
self.default_filters!
self.default_routes!
self.default_errors!
if defined?(I18n)
I18n.load_path << self.locale_path
I18n.reload!
end
@_configured = true
@_configured
end
##
# Run the Padrino app as a self-hosted server using
# Thin, Mongrel or WEBrick (in that order)
#
# @see Padrino::Server#start
#
def run!(options={})
return unless Padrino.load!
Padrino.mount(self.to_s).to("/")
Padrino.run!(options)
end
##
# @return [Array]
# 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(self.root, path) }
end
##
# Returns default list of path globs to load as dependencies
# Appends custom dependency patterns to the be loaded for your Application
#
# @return [Array]
# list of path globs to load as dependencies
#
# @example
# MyApp.dependencies << "#{Padrino.root}/uploaders/**/*.rb"
# MyApp.dependencies << Padrino.root('other_app', 'controllers.rb')
#
def dependencies
@_dependencies ||= [
"urls.rb", "config/urls.rb", "mailers/*.rb", "mailers.rb",
"controllers/**/*.rb", "controllers.rb", "helpers/**/*.rb", "helpers.rb"
].map { |file| Dir[File.join(self.root, file)] }.flatten
end
##
# An array of file to load before your app.rb, basically are files wich our app depends on.
#
# By default we look for files:
#
# # List of default files that we are looking for:
# yourapp/models.rb
# yourapp/models/**/*.rb
# yourapp/lib.rb
# yourapp/lib/**/*.rb
#
# @example Adding a custom perequisite
# MyApp.prerequisites << Padrino.root('my_app', 'custom_model.rb')
#
def prerequisites
@_prerequisites ||= []
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 :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, "images") }
set :protection, false
# Padrino specific
set :uri_root, "/"
set :app_name, self.to_s.underscore.to_sym
set :default_builder, 'StandardFormBuilder'
set :flash, defined?(Sinatra::Flash) || defined?(Rack::Flash)
set :authentication, false
# Padrino locale
set :locale_path, Proc.new { Dir[File.join(self.root, "/locale/**/*.{rb,yml}")] }
# Load the Global Configurations
class_eval(&Padrino.apps_configuration) if Padrino.apps_configuration
end
##
# We need to add almost __sinatra__ images.
#
def default_routes!
configure :development do
get '*__sinatra__/:image.png' do
content_type :png
filename = File.dirname(__FILE__) + "/images/#{params[:image]}.png"
send_file filename
end
end
end
##
# This filter it's used for know the format of the request, and automatically set the content type.
#
def default_filters!
before do
unless @_content_type
@_content_type = :html
response['Content-Type'] = 'text/html;charset=utf-8'
end
end
end
##
# This log errors for production environments
#
def default_errors!
configure :production do
error ::Exception do
boom = env['sinatra.error']
logger.error ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n ")
response.status = 500
content_type 'text/html'
'<h1>Internal Server Error</h1>'
end unless errors.has_key?(::Exception)
end
end
##
# Requires all files within the application load paths
#
def require_dependencies
Padrino.set_load_paths(*load_paths)
Padrino.require_dependencies(dependencies, :force => true)
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)
setup_sessions builder
setup_flash builder
builder.use Padrino::ShowExceptions if show_exceptions?
builder.use Padrino::Logger::Rack, uri_root if Padrino.logger && logging?
builder.use Padrino::Reloader::Rack if reload?
builder.use Rack::MethodOverride if method_override?
builder.use Rack::Head
setup_protection builder
setup_application!
end
# TODO Remove this in a few versions (rack-flash deprecation)
# Move register Sinatra::Flash into setup_default_middleware
# Initializes flash using sinatra-flash or rack-flash
def setup_flash(builder)
register Sinatra::Flash if flash? && defined?(Sinatra::Flash)
if defined?(Rack::Flash) && !defined?(Sinatra::Flash)
logger.warn %Q{
[Deprecation] In Gemfile, 'rack-flash' should be replaced with 'sinatra-flash'!
Rack-Flash is not compatible with later versions of Rack and should be replaced.
}
builder.use Rack::Flash, :sweep => true if flash?
end
end
end # self
end # Application
end # Padrino

View file

@ -0,0 +1,292 @@
require 'padrino-core/support_lite' unless defined?(SupportLite)
module Padrino
##
# Padrino enhances the Sinatra 'render' method to have support for
# automatic template engine detection, enhanced layout functionality,
# locale enabled rendering, among other features.
#
module Rendering
##
# Exception responsible for when an expected template did not exist.
#
class TemplateNotFound < RuntimeError
end
##
# This is an array of file patterns to ignore. If your editor add a
# suffix during editing to your files please add it like:
#
# @example
# Padrino::Rendering::IGNORE_FILE_PATTERN << /~$/
#
IGNORE_FILE_PATTERN = [
/~$/ # This is for Gedit
] unless defined?(IGNORE_FILE_PATTERN)
##
# Default rendering options used in the #render-method.
#
DEFAULT_RENDERING_OPTIONS = { :strict_format => false, :raise_exceptions => true } unless defined?(DEFAULT_RENDERING_OPTIONS)
class << self
##
# Main class that register this extension.
#
def registered(app)
app.send(:include, InstanceMethods)
app.extend(ClassMethods)
end
alias :included :registered
end
##
# Class methods responsible for rendering templates as part of a request.
#
module ClassMethods
##
# Use layout like rails does or if a block given then like sinatra.
# If used without a block, sets the current layout for the route.
#
# By default, searches in your:
#
# +app+/+views+/+layouts+/+application+.(+haml+|+erb+|+xxx+)
# +app+/+views+/+layout_name+.(+haml+|+erb+|+xxx+)
#
# If you define +layout+ :+custom+ then searches for your layouts in
# +app+/+views+/+layouts+/+custom+.(+haml+|+erb+|+xxx+)
# +app+/+views+/+custom+.(+haml+|+erb+|+xxx+)
#
# @param [Symbol] name (:layout)
# The layout to use.
#
# @yield []
#
def layout(name=:layout, &block)
return super(name, &block) if block_given?
@layout = name
end
##
# Returns the cached template file to render for a given url, content_type and locale.
#
# @param [Array<template_path, content_type, locale>] render_options
#
def fetch_template_file(render_options)
(@_cached_templates ||= {})[render_options]
end
##
# Caches the template file for the given rendering options
#
# @param [String] template_file
# The path of the template file.
#
# @param [Array<template_path, content_type, locale>] render_options
#
def cache_template_file!(template_file, render_options)
(@_cached_templates ||= {})[render_options] = template_file || []
end
##
# Returns the cached layout path.
#
# @param [Symbol, nil] given_layout
# The requested layout.
#
def fetch_layout_path(given_layout=nil)
layout_name = given_layout || @layout || :application
@_cached_layout ||= {}
cached_layout_path = @_cached_layout[layout_name]
return cached_layout_path if cached_layout_path
has_layout_at_root = Dir["#{views}/#{layout_name}.*"].any?
layout_path = has_layout_at_root ? layout_name.to_sym : File.join('layouts', layout_name.to_s).to_sym
@_cached_layout[layout_name] = layout_path unless reload_templates?
layout_path
end
end
# Instance methods that allow enhanced rendering to function properly in Padrino.
module InstanceMethods
attr_reader :current_engine
##
# Get/Set the content_type
#
# @param [String, nil] type
# The Content-Type to use.
#
# @param [Symbol, nil] type.
# Look and parse the given symbol to the matched Content-Type.
#
# @param [Hash] params
# Additional params to append to the Content-Type.
#
# @example
# case content_type
# when :js then do_some
# when :css then do_another
# end
#
# content_type :js
# # => set the response with 'application/javascript' Content-Type
# content_type 'text/html'
#
# # => set directly the Content-Type to 'text/html'
#
def content_type(type=nil, params={})
unless type.nil?
super(type, params)
@_content_type = type
end
@_content_type
end
private
##
# Enhancing Sinatra render functionality for:
#
# * Using layout similar to rails
# * Use render 'path/to/my/template' (without symbols)
# * Use render 'path/to/my/template' (with engine lookup)
# * Use render 'path/to/template.haml' (with explicit engine lookup)
# * Use render 'path/to/template', :layout => false
# * Use render 'path/to/template', :layout => false, :engine => 'haml'
# * Use render { :a => 1, :b => 2, :c => 3 } # => return a json string
#
def render(engine, data=nil, options={}, locals={}, &block)
# If engine is a hash then render data converted to json
content_type(:json, :charset => 'utf-8') and return engine.to_json if engine.is_a?(Hash)
# If engine is nil, ignore engine parameter and shift up all arguments
# render nil, "index", { :layout => true }, { :localvar => "foo" }
engine, data, options = data, options, locals if engine.nil? && data
# Data is a hash of options when no engine isn't explicit
# render "index", { :layout => true }, { :localvar => "foo" }
# Data is options, and options is locals in this case
data, options, locals = nil, data, options if data.is_a?(Hash)
# If data is unassigned then this is a likely a template to be resolved
# This means that no engine was explicitly defined
data, engine = *resolve_template(engine, options.dup) if data.nil?
# Setup root
root = settings.respond_to?(:root) ? settings.root : ""
# Use @layout if it exists
options[:layout] = @layout if options[:layout].nil?
# Resolve layouts similar to in Rails
if (options[:layout].nil? || options[:layout] == true) && !settings.templates.has_key?(:layout)
layout_path, layout_engine = *resolved_layout
options[:layout] = layout_path || false # We need to force layout false so sinatra don't try to render it
options[:layout] = false unless layout_engine == engine # TODO allow different layout engine
options[:layout_engine] = layout_engine || engine if options[:layout]
elsif options[:layout].present?
options[:layout] = settings.fetch_layout_path(options[:layout] || @layout)
end
# Cleanup the template
@current_engine, engine_was = engine, @current_engine
@_out_buf, _buf_was = "", @_out_buf
# Pass arguments to Sinatra render method
super(engine, data, options.dup, locals, &block)
ensure
@current_engine = engine_was
@_out_buf = _buf_was
end
##
# Returns the located layout tuple to be used for the rendered template
# (if available).
#
# @example
# resolve_layout
# # => ["/layouts/custom", :erb]
# # => [nil, nil]
#
def resolved_layout
located_layout = resolve_template(settings.fetch_layout_path, :raise_exceptions => false, :strict_format => true)
located_layout ? located_layout : [nil, nil]
end
##
# Returns the template path and engine that match content_type (if present),
# I18n.locale.
#
# @param [String] template_path
# The path of the template.
#
# @param [Hash] options
# Additional options.
#
# @option options [Boolean] :strict_format (false)
# The resolved template must match the content_type of the request.
#
# @option options [Boolean] :raise_exceptions (false)
# Raises a {TemplateNotFound} exception if the template cannot be located.
#
# @return [Array<Symbol, Symbol>]
# The path and format of the template.
#
# @raise [TemplateNotFound]
# The template could not be found.
#
# @example
# get "/foo", :provides => [:html, :js] do; render 'path/to/foo'; end
# # If you request "/foo.js" with I18n.locale == :ru => [:"/path/to/foo.ru.js", :erb]
# # If you request "/foo" with I18n.locale == :de => [:"/path/to/foo.de.haml", :haml]
#
def resolve_template(template_path, options={})
began_at = Time.now
# Fetch cached template for rendering options
template_path = template_path.to_s[0] == ?/ ? template_path.to_s : "/#{template_path}"
rendering_options = [template_path, content_type, locale]
cached_template = settings.fetch_template_file(rendering_options)
if cached_template
logger.debug :cached, began_at, cached_template[0] if settings.logging? && defined?(logger)
return cached_template
end
# Resolve view path and options
options.reverse_merge!(DEFAULT_RENDERING_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}")
# Generate potential template candidates
templates = Dir[File.join(view_path, template_path) + ".*"].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, template_engine] unless IGNORE_FILE_PATTERN.any? { |pattern| template_engine.to_s =~ pattern }
end
# Check if we have a simple content type
simple_content_type = [:html, :plain].include?(content_type)
# Resolve final template to render
located_template =
templates.find { |file, e| file.to_s == "#{template_path}.#{locale}.#{content_type}" } ||
templates.find { |file, e| file.to_s == "#{template_path}.#{locale}" && simple_content_type } ||
templates.find { |file, e| File.extname(file.to_s) == ".#{target_extension}" or e.to_s == target_extension.to_s } ||
templates.find { |file, e| file.to_s == "#{template_path}.#{content_type}" } ||
templates.find { |file, e| file.to_s == "#{template_path}" && simple_content_type } ||
(!options[:strict_format] && templates.first) # If not strict, fall back to the first located template
raise TemplateNotFound, "Template '#{template_path}' not found in '#{view_path}'!" if !located_template && options[:raise_exceptions]
settings.cache_template_file!(located_template, rendering_options) unless settings.reload_templates?
logger.debug :template, began_at, located_template[0] if located_template && settings.logging? && defined?(logger)
located_template
end
##
# Return the I18n.locale if I18n is defined.
#
def locale
I18n.locale if defined?(I18n)
end
end # InstanceMethods
end # Rendering
end # Padrino

View file

@ -0,0 +1,934 @@
require 'http_router' unless defined?(HttpRouter)
require 'padrino-core/support_lite' unless defined?(SupportLite)
##
# Adds to Sinatra +controller+ informations
#
# @private
class Sinatra::Request
attr_accessor :route_obj
def controller
route_obj && route_obj.controller
end
end
##
# HttpRouter adapter
#
# @private
class HttpRouter
def rewrite_partial_path_info(env, request); end
def rewrite_path_info(env, request); end
def process_destination_path(path, env)
Thread.current['padrino.instance'].instance_eval do
request.route_obj = path.route
@_response_buffer = nil
@route = path.route
@params ||= {}
@params.update(env['router.params'])
@block_params = if path.route.is_a?(HttpRouter::RegexRoute)
params_list = env['router.request'].extra_env['router.regex_match'].to_a
params_list.shift
@params[:captures] = params_list
params_list
else
env['router.request'].params
end
# Provide access to the current controller to the request
# Now we can eval route, but because we have "throw halt" we need to be
# (en)sure to reset old layout and run controller after filters.
original_params = @params
parent_layout = @layout
successful = false
begin
filter! :before
(@route.before_filters - settings.filters[:before]).each { |block| instance_eval(&block) }
@layout = path.route.use_layout if path.route.use_layout
@route.custom_conditions.each { |block| pass if block.bind(self).call == false } if @route.custom_conditions
@block_params = @block_params[0, @route.dest.arity] if @route.dest.arity > 0
halt_response = catch(:halt) { route_eval { @route.dest[self, @block_params] } }
@_response_buffer = halt_response.is_a?(Array) ? halt_response.last : halt_response
successful = true
halt halt_response
ensure
(@route.after_filters - settings.filters[:after]).each { |block| instance_eval(&block) } if successful
@layout = parent_layout
@params = original_params
end
end
end
# @private
class Route
attr_accessor :use_layout, :controller, :cache, :cache_key, :cache_expires_in
def before_filters(&block)
@_before_filters ||= []
@_before_filters << block if block_given?
@_before_filters
end
def after_filters(&block)
@_after_filters ||= []
@_after_filters << block if block_given?
@_after_filters
end
def custom_conditions(&block)
@_custom_conditions ||= []
@_custom_conditions << block if block_given?
@_custom_conditions
end
end
end
module Padrino
class Filter # @private
attr_reader :block
def initialize(mode, scoped_controller, options, args, &block)
@mode, @scoped_controller, @options, @args, @block = mode, scoped_controller, options, args, block
end
def apply?(request)
detect = @args.any? do |arg|
case arg
when Symbol then request.route_obj && (request.route_obj.named == arg or request.route_obj.named == [@scoped_controller, arg].flatten.join("_").to_sym)
else arg === request.path_info
end
end || @options.any? do |name, val|
case name
when :agent then val === request.user_agent
else val === request.send(name)
end
end
detect ^ !@mode
end
def to_proc
if @args.empty? && @options.empty?
block
else
filter = self
proc { instance_eval(&filter.block) if filter.apply?(request) }
end
end
end
##
# Padrino provides advanced routing definition support to make routes and
# url generation much easier. This routing system supports named route
# aliases and easy access to url paths. The benefits of this is that instead
# of having to hard-code route urls into every area of your application, now
# we can just define the urls in a single spot and then attach an alias
# which can be used to refer to the url throughout the application.
#
module Routing
# Defines common content-type alias mappings
CONTENT_TYPE_ALIASES = { :htm => :html } unless defined?(CONTENT_TYPE_ALIASES)
# Defines the available route priorities supporting route deferrals.
ROUTE_PRIORITY = {:high => 0, :normal => 1, :low => 2} unless defined?(ROUTE_PRIORITY)
# Raised when a route was invalid or cannot be processed.
class UnrecognizedException < RuntimeError; end
class Parent < String # @private
attr_reader :map
attr_reader :optional
attr_reader :options
alias_method :optional?, :optional
def initialize(value, options={})
super(value.to_s)
@map = options.delete(:map)
@optional = options.delete(:optional)
@options = options
end
end
class << self
##
# Main class that register this extension.
#
def registered(app)
app.send(:include, InstanceMethods)
app.extend(ClassMethods)
end
alias :included :registered
end
# Class methods responsible for enhanced routing for controllers.
module ClassMethods
##
# Method for organize in a better way our routes.
#
# @param [Array] args
# Controller arguments.
#
# @yield []
# The given block will be used to define the routes within the
# Controller.
#
# @example
# controller :admin do
# get :index do; ...; end
# get :show, :with => :id do; ...; end
# end
#
# url(:admin_index) # => "/admin"
# url(:admin_show, :id => 1) # "/admin/show/1"
#
# @example Using named routes follow the sinatra way:
# controller "/admin" do
# get "/index" do; ...; end
# get "/show/:id" do; ...; end
# end
#
# @example Supply +:provides+ to all controller routes:
# controller :provides => [:html, :xml, :json] do
# get :index do; "respond to html, xml and json"; end
# post :index do; "respond to html, xml and json"; end
# get :foo do; "respond to html, xml and json"; end
# end
#
# @example Specify parent resources in padrino with the +:parent+ option on the controller:
# controllers :product, :parent => :user do
# get :index do
# # url is generated as "/user/#{params[:user_id]}/product"
# # url_for(:product, :index, :user_id => 5) => "/user/5/product"
# end
# get :show, :with => :id do
# # url is generated as "/user/#{params[:user_id]}/product/show/#{params[:id]}"
# # url_for(:product, :show, :user_id => 5, :id => 10) => "/user/5/product/show/10"
# end
# end
#
# @example Specify conditions to run for all routes:
# controller :conditions => {:protect => true} do
# def self.protect(protected)
# condition do
# halt 403, "No secrets for you!" unless params[:key] == "s3cr3t"
# end if protected
# end
#
# # This route will only return "secret stuff" if the user goes to
# # `/private?key=s3cr3t`.
# get("/private") { "secret stuff" }
#
# # And this one, too!
# get("/also-private") { "secret stuff" }
#
# # But you can override the conditions for each route as needed.
# # This route will be publicly accessible without providing the
# # secret key.
# get :index, :protect => false do
# "Welcome!"
# end
# end
#
# @example Supply default values:
# controller :lang => :de do
# get :index, :map => "/:lang" do; "params[:lang] == :de"; end
# end
#
# In a controller before and after filters are scoped and didn't affect other controllers or main app.
# In a controller layout are scoped and didn't affect others controllers and main app.
#
# @example
# controller :posts do
# layout :post
# before { foo }
# after { bar }
# end
#
def controller(*args, &block)
if block_given?
options = args.extract_options!
# Controller defaults
@_controller, original_controller = args, @_controller
@_parents, original_parent = options.delete(:parent), @_parents
@_provides, original_provides = options.delete(:provides), @_provides
@_use_format, original_use_format = options.delete(:use_format), @_use_format
@_cache, original_cache = options.delete(:cache), @_cache
@_map, original_map = options.delete(:map), @_map
@_conditions, original_conditions = options.delete(:conditions), @_conditions
@_defaults, original_defaults = options, @_defaults
# Application defaults
@filters, original_filters = { :before => @filters[:before].dup, :after => @filters[:after].dup }, @filters
@layout, original_layout = nil, @layout
instance_eval(&block)
# Application defaults
@filters = original_filters
@layout = original_layout
# Controller defaults
@_controller, @_parents, @_cache = original_controller, original_parent, original_cache
@_defaults, @_provides, @_map = original_defaults, original_provides, original_map
@_conditions, @_use_format = original_conditions, original_use_format
else
include(*args) if extensions.any?
end
end
alias :controllers :controller
##
# Add a before filter hook
#
# @see #construct_filter
#
def before(*args, &block)
add_filter :before, &(args.empty? ? block : construct_filter(*args, &block))
end
##
# Add an after filter hook
#
# @see #construct_filter
#
def after(*args, &block)
add_filter :after, &(args.empty? ? block : construct_filter(*args, &block))
end
##
# Adds a filter hook to a request.
#
def add_filter(type, &block)
filters[type] << block
end
##
# Creates a filter to process before/after the matching route.
#
# @param [Array] args
#
# @example We are be able to filter with String path
# before('/') { 'only to :index' }
# get(:index} { 'foo' } # => filter match only before this.
# get(:main) { 'bar' }
#
# @example is the same of
# before(:index) { 'only to :index' }
# get(:index} { 'foo' } # => filter match only before this.
# get(:main) { 'bar' }
#
# @example it works only for the given controller
# controller :foo do
# before(:index) { 'only to for :foo_index' }
# get(:index} { 'foo' } # => filter match only before this.
# get(:main) { 'bar' }
# end
#
# controller :bar do
# before(:index) { 'only to for :bar_index' }
# get(:index} { 'foo' } # => filter match only before this.
# get(:main) { 'bar' }
# end
#
# @example if filters based on a symbol or regexp
# before :index, /main/ do; ... end
# # => match oly path that are +/+ or contains +main+
#
# @example filtering everything except an occurency
# before :except => :index do; ...; end
#
# @example you can also filter using a request param
# before :agent => /IE/ do; ...; end
# # => match +HTTP_USER_AGENT+ containing +IE+
#
# @see http://www.padrinorb.com/guides/controllers#route-filters
#
def construct_filter(*args, &block)
options = args.last.is_a?(Hash) ? args.pop : {}
except = options.key?(:except) && Array(options.delete(:except))
raise("You cannot use except with other options specified") if except && (!args.empty? || !options.empty?)
options = except.last.is_a?(Hash) ? except.pop : {} if except
Filter.new(!except, @_controller, options, Array(except || args), &block)
end
##
# Provides many parents with shallowing.
#
# @param [Symbol] name
# The parent name.
#
# @param [Hash] options
# Additional options.
#
# @example
# controllers :product do
# parent :shop, :optional => true, :map => "/my/stand"
# parent :category, :optional => true
# get :show, :with => :id do
# # generated urls:
# # "/product/show/#{params[:id]}"
# # "/my/stand/#{params[:shop_id]}/product/show/#{params[:id]}"
# # "/my/stand/#{params[:shop_id]}/category/#{params[:category_id]}/product/show/#{params[:id]}"
# # url_for(:product, :show, :id => 10) => "/product/show/10"
# # url_for(:product, :show, :shop_id => 5, :id => 10) => "/my/stand/5/product/show/10"
# # url_for(:product, :show, :shop_id => 5, :category_id => 1, :id => 10) => "/my/stand/5/category/1/product/show/10"
# end
# end
#
def parent(name, options={})
defaults = { :optional => false, :map => name.to_s }
options = defaults.merge(options)
@_parents = Array(@_parents) unless @_parents.is_a?(Array)
@_parents << Parent.new(name, options)
end
##
# Using {HttpRouter}, for features and configurations.
#
# @example
# router.add('/greedy/:greed')
# router.recognize('/simple')
#
# @see http://github.com/joshbuddy/http_router
#
def router
@router ||= HttpRouter.new
block_given? ? yield(@router) : @router
end
alias :urls :router
# Compiles the routes including deferred routes.
def compiled_router
if deferred_routes.empty?
router
else
deferred_routes.each { |_, routes| routes.each { |(route, dest)| route.to(dest) } }
@deferred_routes = nil
router
end
end
# Returns all routes that were deferred based on their priority.
def deferred_routes
@deferred_routes ||= Hash[ROUTE_PRIORITY.values.sort.map{|p| [p, []]}]
end
##
# Resets the http router and all deferred routes.
#
def reset_router!
@deferred_routes = nil
router.reset!
end
##
# Recognize a given path
#
# @param [String] path
# Path+Query to parse
#
# @return [Symbol, Hash]
# Returns controller and query params.
#
# @example Giving a controller like:
# controller :foo do
# get :bar, :map => 'foo-bar-:id'; ...; end
# end
#
# @example You should be able to reverse:
# MyApp.url(:foo_bar, :id => :mine)
# # => /foo-bar-mine
#
# @example Into this:
# MyApp.recognize_path('foo-bar-mine')
# # => [:foo_bar, :id => :mine]
#
def recognize_path(path)
responses = @router.recognize(Rack::MockRequest.env_for(path))
[responses[0].path.route.named, responses[0].params]
end
##
# Instance method for url generation.
#
# @example
# url(:show, :id => 1)
# url(:show, :name => 'test', :id => 24)
# url(:show, 1)
# url(:controller_name, :show, :id => 21)
# url(:controller_show, :id => 29)
#
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
if params.is_a?(Hash)
params[:format] = params[:format].to_s unless params[:format].nil?
params = value_to_param(params)
end
url = if params_array.empty?
compiled_router.url(name, params)
else
compiled_router.url(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
rescue HttpRouter::InvalidRouteException
route_error = "route mapping for url(#{name.inspect}) could not be found!"
raise Padrino::Routing::UnrecognizedException.new(route_error)
end
alias :url_for :url
def get(path, *args, &block) # @private
conditions = @conditions.dup
route('GET', path, *args, &block)
@conditions = conditions
route('HEAD', path, *args, &block)
end
private
# Parse params from the url method
def value_to_param(value)
case value
when Array
value.map { |v| value_to_param(v) }.compact
when Hash
value.inject({}) do |memo, (k,v)|
v = value_to_param(v)
memo[k] = v unless v.nil?
memo
end
when nil then nil
else value.respond_to?(:to_param) ? value.to_param : value
end
end
# Add prefix slash if its not present and remove trailing slashes.
def conform_uri(uri_string)
uri_string.gsub(/^(?!\/)(.*)/, '/\1').gsub(/[\/]+$/, '')
end
##
# Rewrite default routes.
#
# @example
# get :index # => "/"
# get :index, "/" # => "/"
# get :index, :map => "/" # => "/"
# get :show, "/show-me" # => "/show-me"
# get :show, :map => "/show-me" # => "/show-me"
# get "/foo/bar" # => "/show"
# get :index, :parent => :user # => "/user/:user_id/index"
# get :show, :with => :id, :parent => :user # => "/user/:user_id/show/:id"
# get :show, :with => :id # => "/show/:id"
# get [:show, :id] # => "/show/:id"
# get :show, :with => [:id, :name] # => "/show/:id/:name"
# get [:show, :id, :name] # => "/show/:id/:name"
# get :list, :provides => :js # => "/list.{:format,js)"
# get :list, :provides => :any # => "/list(.:format)"
# get :list, :provides => [:js, :json] # => "/list.{!format,js|json}"
# get :list, :provides => [:html, :js, :json] # => "/list(.{!format,js|json})"
# get :list, :priority => :low # Defers route to be last
#
def route(verb, path, *args, &block)
options = case args.size
when 2
args.last.merge(:map => args.first)
when 1
map = args.shift if args.first.is_a?(String)
if args.first.is_a?(Hash)
map ? args.first.merge(:map => map) : args.first
else
{:map => map || args.first}
end
when 0
{}
else raise
end
# Do padrino parsing. We dup options so we can build HEAD request correctly
route_options = options.dup
route_options[:provides] = @_provides if @_provides
path, *route_options[:with] = path if path.is_a?(Array)
path, name, options = *parse_route(path, route_options, verb)
options.reverse_merge!(@_conditions) if @_conditions
# Sinatra defaults
method_name = "#{verb} #{path}"
unbound_method = generate_method(method_name, &block)
block = block.arity != 0 ?
proc { |a,p| unbound_method.bind(a).call(*p) } :
proc { |a,p| unbound_method.bind(a).call }
invoke_hook(:route_added, verb, path, block)
# HTTPRouter route construction
route = router.add(path)
route.name(name) if name
priority_name = options.delete(:priority) || :normal
priority = ROUTE_PRIORITY[priority_name] or raise("Priority #{priority_name} not recognized, try #{ROUTE_PRIORITY.keys.join(', ')}")
route.cache = options.key?(:cache) ? options.delete(:cache) : @_cache
route.send(verb.downcase.to_sym)
route.host(options.delete(:host)) if options.key?(:host)
route.user_agent(options.delete(:agent)) if options.key?(:agent)
if options.key?(:default_values)
defaults = options.delete(:default_values)
route.default(defaults) if defaults
end
options.delete_if do |option, args|
if route.send(:significant_variable_names).include?(option)
route.matching(option => Array(args).first)
true
end
end
# Add Sinatra conditions
options.each { |o, a| route.respond_to?(o) ? route.send(o, *a) : send(o, *a) }
conditions, @conditions = @conditions, []
route.custom_conditions.concat(conditions)
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])
if @_controller
route.use_layout = @layout
route.controller = Array(@_controller)[0].to_s
end
deferred_routes[priority] << [route, block]
route
end
##
# Returns the final parsed route details (modified to reflect all
# Padrino options) given the raw route. Raw route passed in could be
# a named alias or a string and is parsed to reflect provides formats,
# controllers, parents, 'with' parameters, and other options.
#
def parse_route(path, options, verb)
# We need save our originals path/options so we can perform correctly cache.
original = [path, options.dup]
# We need check if path is a symbol, if that it's a named route
map = options.delete(:map)
if path.kind_of?(Symbol) # path i.e :index or :show
name = path # The route name
path = map ? map.dup : (path == :index ? '/' : path.to_s) # The route path
end
if path.kind_of?(String) # path i.e "/index" or "/show"
# Now we need to parse our 'with' params
if with_params = options.delete(:with)
path = process_path_for_with_params(path, with_params)
end
# Now we need to parse our provides
options.delete(:provides) if options[:provides].nil?
if @_use_format or format_params = options[:provides]
process_path_for_provides(path, format_params)
options[:matching] ||= {}
options[:matching][:format] = /[^\.]+/
end
# Build our controller
controller = Array(@_controller).map { |c| c.to_s }
absolute_map = map && map[0] == ?/
unless controller.empty?
# Now we need to add our controller path only if not mapped directly
if map.blank? and !absolute_map
controller_path = controller.join("/")
path.gsub!(%r{^\(/\)|/\?}, "")
path = File.join(controller_path, path)
end
# Here we build the correct name route
if name
controller_name = controller.join("_")
name = "#{controller_name}_#{name}".to_sym unless controller_name.blank?
end
end
# Now we need to parse our 'parent' params and parent scope
if !absolute_map and parent_params = options.delete(:parent) || @_parents
parent_params = Array(@_parents) + Array(parent_params)
path = process_path_for_parent_params(path, parent_params)
end
# Add any controller level map to the front of the path
path = "#{@_map}/#{path}".squeeze('/') unless absolute_map or @_map.blank?
# Small reformats
path.gsub!(%r{/\?$}, '(/)') # Remove index path
path.gsub!(%r{//$}, '/') # Remove index path
path[0,0] = "/" if path !~ %r{^\(?/} # Paths must start with a /
path.sub!(%r{/(\))?$}, '\\1') if path != "/" # Remove latest trailing delimiter
path.gsub!(/\/(\(\.|$)/, '\\1') # Remove trailing slashes
path.squeeze!('/')
end
# Merge in option defaults
options.reverse_merge!(:default_values => @_defaults)
[path, name, options]
end
##
# Processes the existing path and appends the 'with' parameters onto the route
# Used for calculating path in route method.
#
def process_path_for_with_params(path, with_params)
File.join(path, Array(with_params).map(&:inspect).join("/"))
end
##
# Processes the existing path and prepends the 'parent' parameters onto the route
# Used for calculating path in route method.
#
def process_path_for_parent_params(path, parent_params)
parent_prefix = parent_params.flatten.compact.uniq.map do |param|
map = (param.respond_to?(:map) && param.map ? param.map : param.to_s)
part = "#{map}/:#{param.to_s.singularize}_id/"
part = "(#{part})" if param.respond_to?(:optional) && param.optional?
part
end
[parent_prefix, path].flatten.join("")
end
##
# Processes the existing path and appends the 'format' suffix onto the route
# Used for calculating path in route method.
#
def process_path_for_provides(path, format_params)
path << "(.:format)" unless path[-10, 10] == '(.:format)'
end
##
# Allows routing by MIME-types specified in the URL or ACCEPT header.
#
# By default, if a non-provided mime-type is specified in a URL, the
# route will not match an thus return a 404.
#
# Setting the :treat_format_as_accept option to true allows treating
# missing mime types specified in the URL as if they were specified
# in the ACCEPT header and thus return 406.
#
# If no type is specified, the first in the provides-list will be
# returned.
#
# @example
# get "/a", :provides => [:html, :js]
# # => GET /a => :html
# # => GET /a.js => :js
# # => GET /a.xml => 404
#
# get "/b", :provides => [:html]
# # => GET /b; ACCEPT: html => html
# # => GET /b; ACCEPT: js => 406
#
# enable :treat_format_as_accept
# get "/c", :provides => [:html, :js]
# # => GET /c.xml => 406
#
def provides(*types)
@_use_format = true
condition do
mime_types = types.map { |t| mime_type(t) }
url_format = params[:format].to_sym if params[:format]
accepts = request.accept.map { |a| a.split(";")[0].strip }
# per rfc2616-sec14:
# Assume */* if no ACCEPT header is given.
catch_all = (accepts.delete "*/*" || accepts.empty?)
matching_types = accepts.empty? ? mime_types.slice(0,1) : (accepts & mime_types)
if !url_format && matching_types.first
type = ::Rack::Mime::MIME_TYPES.find { |k, v| v == matching_types.first }[0].sub(/\./,'').to_sym
accept_format = CONTENT_TYPE_ALIASES[type] || type
elsif catch_all
type = types.first
accept_format = CONTENT_TYPE_ALIASES[type] || type
end
matched_format = types.include?(:any) ||
types.include?(accept_format) ||
types.include?(url_format) ||
((!url_format) && request.accept.empty? && types.include?(:html))
# per rfc2616-sec14:
# answer with 406 if accept is given but types to not match any
# provided type
halt 406 if
(!url_format && !accepts.empty? && !matched_format) ||
(settings.respond_to?(:treat_format_as_accept) && settings.treat_format_as_accept && url_format && !matched_format)
if matched_format
@_content_type = url_format || accept_format || :html
content_type(@_content_type, :charset => 'utf-8')
end
matched_format
end
end
end
##
# Instance methods related to recognizing and processing routes and serving static files.
#
module InstanceMethods
##
# Instance method for url generation.
#
# @example
# url(:show, :id => 1)
# url(:show, :name => :test)
# url(:show, 1)
# url("/foo")
#
# @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)
end
alias :url_for :url
##
# Returns the recognized path for a route.
#
def recognize_path(path)
settings.recognize_path(path)
end
##
# Returns the current path within a route from specified +path_params+.
#
def current_path(*path_params)
if path_params.last.is_a?(Hash)
path_params[-1] = params.merge(path_params[-1])
else
path_params << params
end
@route.url(*path_params)
end
##
# Returns the current route
#
# @example
# -if route.controller == :press
# %li=show_article
#
def route
@route
end
##
# This is mostly just a helper so request.path_info isn't changed when
# serving files from the public directory.
#
def static_file?(path_info)
return if (public_dir = settings.public_folder).nil?
public_dir = File.expand_path(public_dir)
path = File.expand_path(public_dir + unescape(path_info))
return if path[0, public_dir.length] != public_dir
return unless File.file?(path)
return path
end
#
# Method for deliver static files.
#
def static!
if path = static_file?(request.path_info)
env['sinatra.static_file'] = path
cache_control *settings.static_cache_control if settings.static_cache_control?
send_file(path, :disposition => nil)
end
end
##
# Return the request format, this is useful when we need to respond to
# a given Content-Type.
#
# @param [Symbol, nil] type
#
# @param [Hash] params
#
# @example
# get :index, :provides => :any do
# case content_type
# when :js then ...
# when :json then ...
# when :html then ...
# end
# end
#
def content_type(type=nil, params={})
unless type.nil?
super(type, params)
@_content_type = type
end
@_content_type
end
private
def filter!(type, base=settings)
base.filters[type].each { |block| instance_eval(&block) }
end
def dispatch!
static! if settings.static? && (request.get? || request.head?)
route!
rescue ::Exception => boom
filter! :before
handle_exception!(boom)
ensure
filter! :after unless env['sinatra.static_file']
end
def route!(base=settings, pass_block=nil)
Thread.current['padrino.instance'] = self
if base.compiled_router and match = base.compiled_router.call(@request.env)
if match.respond_to?(:each)
route_eval do
match[1].each {|k,v| response[k] = v}
status match[0]
route_missing if match[0] == 404
end
end
else
filter! :before
end
# Run routes defined in superclass.
if base.superclass.respond_to?(:router)
route!(base.superclass, pass_block)
return
end
route_eval(&pass_block) if pass_block
route_missing
end
end # InstanceMethods
end # Routing
end # Padrino

View file

@ -0,0 +1,20 @@
module Padrino
##
# This module extend Sinatra::ShowExceptions adding Padrino as "Framework".
#
# @private
class ShowExceptions < Sinatra::ShowExceptions
private
def frame_class(frame)
if frame.filename =~ /lib\/sinatra.*\.rb|lib\/padrino.*\.rb/
"framework"
elsif (defined?(Gem) && frame.filename.include?(Gem.dir)) ||
frame.filename =~ /\/bin\/(\w+)$/ ||
frame.filename =~ /Ruby\/Gems/
"system"
else
"app"
end
end
end # ShowExceptions
end # Padrino

View file

@ -0,0 +1,53 @@
module Padrino
# List of callers in a Padrino application that should be ignored as part of a stack trace.
PADRINO_IGNORE_CALLERS = [
%r{lib/padrino-.*$}, # all padrino code
%r{/padrino-.*/(lib|bin)}, # all padrino code
%r{/bin/padrino$}, # all padrino code
%r{/sinatra(/(base|main|showexceptions))?\.rb$}, # all sinatra code
%r{lib/tilt.*\.rb$}, # all tilt code
%r{lib/rack.*\.rb$}, # all rack code
%r{lib/mongrel.*\.rb$}, # all mongrel code
%r{lib/shotgun.*\.rb$}, # all shotgun lib
%r{bin/shotgun$}, # shotgun binary
%r{\(.*\)}, # generated code
%r{shoulda/context\.rb$}, # shoulda hacks
%r{mocha/integration}, # mocha hacks
%r{test/unit}, # test unit hacks
%r{rake_test_loader\.rb}, # rake hacks
%r{custom_require\.rb$}, # rubygems require hacks
%r{active_support}, # active_support require hacks
%r{/thor} # thor require hacks
] unless defined?(PADRINO_IGNORE_CALLERS)
##
# Add rubinius (and hopefully other VM implementations) ignore patterns ...
#
PADRINO_IGNORE_CALLERS.concat(RUBY_IGNORE_CALLERS) if defined?(RUBY_IGNORE_CALLERS)
private
##
# The filename for the file that is the direct caller (first caller).
#
# @return [String]
# The file the caller method exists in.
#
def self.first_caller
caller_files.first
end
#
# Like +Kernel#caller+ but excluding certain magic entries and without
# line / method information; the resulting array contains filenames only.
#
# @return [Array<String>]
# The files of the calling methods.
#
def self.caller_files
caller(1).
map { |line| line.split(/:(?=\d|in )/)[0,2] }.
reject { |file,line| PADRINO_IGNORE_CALLERS.any? { |pattern| file =~ pattern } }.
map { |file,line| file }
end
end # Padrino

View file

@ -0,0 +1,24 @@
module Padrino
module Cli
module Adapter
class << self
# Start for the given options a rackup handler
def start(options)
Padrino.run!(options.symbolize_keys)
end
# Method that stop (if exist) a running Padrino.application
def stop(options)
options.symbolize_keys!
if File.exist?(options[:pid])
pid = File.read(options[:pid]).to_i
print "=> Sending INT to process with pid #{pid} wait "
Process.kill(2, pid) rescue nil
else
puts "=> #{options[:pid]} not found!"
end
end
end # self
end # Adapter
end # Cli
end # Padrino

View file

@ -0,0 +1,151 @@
require 'thor'
module Padrino
module Cli
class Base < Thor
include Thor::Actions
class_option :chdir, :type => :string, :aliases => "-c", :desc => "Change to dir before starting"
class_option :environment, :type => :string, :aliases => "-e", :required => true, :default => :development, :desc => "Padrino Environment"
class_option :help, :type => :boolean, :desc => "Show help usage"
desc "start", "Starts the Padrino application"
method_option :server, :type => :string, :aliases => "-a", :desc => "Rack Handler (default: autodetect)"
method_option :host, :type => :string, :aliases => "-h", :required => true, :default => "0.0.0.0", :desc => "Bind to HOST address"
method_option :port, :type => :numeric, :aliases => "-p", :required => true, :default => 3000, :desc => "Use PORT"
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"
def start
prepare :start
require File.expand_path("../adapter", __FILE__)
require File.expand_path('config/boot.rb')
Padrino::Cli::Adapter.start(options)
end
desc "s", "Starts the Padrino application"
def s
invoke :start
end
desc "stop", "Stops the Padrino application"
method_option :pid, :type => :string, :aliases => "-p", :desc => "File to store pid", :default => 'tmp/pids/server.pid'
def stop
prepare :stop
require File.expand_path("../adapter", __FILE__)
Padrino::Cli::Adapter.stop(options)
end
desc "rake", "Execute rake tasks"
method_option :environment, :type => :string, :aliases => "-e", :required => true, :default => :development
method_option :list, :type => :string, :aliases => "-T", :desc => "Display the tasks (matching optional PATTERN) with descriptions, then exit."
method_option :trace, :type => :boolean, :aliases => "-t", :desc => "Turn on invoke/execute tracing, enable full backtrace."
method_option :verbose, :type => :boolean, :aliases => "-v", :desc => "Log message to standard output."
def rake(*args)
prepare :rake
args << "-T" if options[:list]
args << options[:list] unless options[:list].nil? || options[:list].to_s == "list"
args << "--trace" if options[:trace]
args << "--verbose" if options[:verbose]
ARGV.clear
ARGV.concat(args)
puts "=> Executing Rake #{ARGV.join(' ')} ..."
ENV['PADRINO_LOG_LEVEL'] ||= "test"
load File.expand_path('../rake.rb', __FILE__)
silence(:stdout) { require File.expand_path('config/boot.rb') }
PadrinoTasks.init(true)
end
desc "console", "Boots up the Padrino application irb console"
def console
prepare :console
require File.expand_path("../../version", __FILE__)
ARGV.clear
puts "=> Loading #{options.environment} console (Padrino v.#{Padrino.version})"
require 'irb'
require "irb/completion"
require File.expand_path('config/boot.rb')
require File.expand_path('../console', __FILE__)
IRB.start
end
desc "generate", "Executes the Padrino generator with given options."
def generate(*args)
# Build Padrino g as an alias of padrino-gen
begin
# We try to load the vendored padrino-gen if exist
padrino_gen_path = File.expand_path('../../../../../padrino-gen/lib', __FILE__)
$:.unshift(padrino_gen_path) if File.directory?(padrino_gen_path) && !$:.include?(padrino_gen_path)
require 'padrino-core/command'
require 'padrino-gen/command'
ARGV.shift
Padrino.bin_gen(ARGV)
rescue
puts "<= You need padrino-gen! Run: gem install padrino-gen"
end
end
desc "g", "Executes the Padrino generator with given options."
def g(*args)
invoke(:generate, args)
end
desc "gen", "Executes the Padrino generator with given options."
def gen(*args)
invoke(:generate, args)
end
desc "version", "Show current Padrino Version"
map "-v" => :version, "--version" => :version
def version
require 'padrino-core/version'
puts "Padrino v. #{Padrino.version}"
end
private
def prepare(task)
if options.help?
help(task.to_s)
raise SystemExit
end
ENV["PADRINO_ENV"] ||= 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 !!!"
raise SystemExit
end
end
protected
def self.banner(task=nil, *args)
"padrino #{task.name}"
end
def chdir(dir)
return unless dir
begin
Dir.chdir(dir.to_s)
rescue Errno::ENOENT
puts "=> Specified Padrino root '#{dir}' does not appear to exist!"
rescue Errno::EACCES
puts "=> Specified Padrino root '#{dir}' cannot be accessed by the current user!"
end
end
def capture(stream)
begin
stream = stream.to_s
eval "$#{stream} = StringIO.new"
yield
result = eval("$#{stream}").string
ensure
eval("$#{stream} = #{stream.upcase}")
end
result
end
alias :silence :capture
end # Base
end # Cli
end # Padrino

View file

@ -0,0 +1,20 @@
# Reloads classes
def reload!
Padrino.reload!
end
# Show applications
def applications
puts "==== List of Mounted Applications ====\n\n"
Padrino.mounted_apps.each do |app|
puts " * %-10s mapped to %s" % [app.name, app.uri_root]
end
puts
Padrino.mounted_apps.map { |app| "#{app.name} => #{app.uri_root}" }
end
# Load apps
Padrino.mounted_apps.each do |app|
puts "=> Loading Application #{app.app_class}"
app.app_obj.setup_application!
end

View file

@ -0,0 +1,24 @@
require File.expand_path('../../tasks', __FILE__)
require 'rake'
require 'thor'
require 'securerandom' unless defined?(SecureRandom)
module PadrinoTasks
def self.init(init=false)
Padrino::Tasks.files.flatten.uniq.each { |rakefile| Rake.application.add_import(rakefile) rescue puts "<= Failed load #{ext}" }
if init
Rake.application.init
Rake.application.instance_variable_set(:@rakefile, __FILE__)
load(File.expand_path('../rake_tasks.rb', __FILE__))
Rake.application.load_imports
Rake.application.top_level
else
load(File.expand_path('../rake_tasks.rb', __FILE__))
Rake.application.load_imports
end
end
end
def shell
@_shell ||= Thor::Base.shell.new
end

View file

@ -0,0 +1,59 @@
# Load rake tasks from common rake task definition locations
Dir["lib/tasks/**/*.rake"].
concat(Dir["tasks/**/*.rake"]).
concat(Dir["{test,spec}/*.rake"]).each { |rake| load(rake) }
# Loads the Padrino applications mounted within the project
# setting up the required environment for Padrino
task :environment do
Padrino.mounted_apps.each do |app|
app.app_obj.setup_application!
end
end
desc "Generate a secret key"
task :secret do
shell.say SecureRandom.hex(32)
end
# lists all routes of a given app
def list_app_routes(app, args)
app_routes = app.named_routes
app_routes.reject! { |r| r.identifier.to_s !~ /#{args.query}/ } if args.query.present?
app_routes.map! { |r| [r.verb, r.name, r.path] }
return if app_routes.empty?
shell.say "\nApplication: #{app.app_class}", :yellow
app_routes.unshift(["REQUEST", "URL", "PATH"])
max_col_1 = app_routes.max { |a, b| a[0].size <=> b[0].size }[0].size
max_col_2 = app_routes.max { |a, b| a[1].size <=> b[1].size }[1].size
app_routes.each_with_index do |row, i|
message = [row[1].ljust(max_col_2+2), row[0].center(max_col_1+2), row[2]]
shell.say(" " + message.join(" "), i==0 ? :bold : nil)
end
end
desc "Displays a listing of the named routes within a project, optionally only those matched by [query]"
task :routes, [:query] => :environment do |t, args|
Padrino.mounted_apps.each do |app|
list_app_routes(app, args)
end
end
desc "Displays a listing of the named routes a given app [app]"
namespace :routes do
task :app, [:app] => :environment do |t, args|
app = Padrino.mounted_apps.find { |app| app.app_class == args.app }
list_app_routes(app, args) if app
end
end
desc "Generate the Rakefile"
task :gen do
File.open(Padrino.root("Rakefile"), "w") do |file|
file.puts <<-RUBY.gsub(/^ {6}/, '')
require File.expand_path('../config/boot.rb', __FILE__)
require 'padrino-core/cli/rake'
PadrinoTasks.init
RUBY
end
end

View file

@ -0,0 +1,38 @@
require 'rbconfig'
module Padrino
##
# This method return the correct location of padrino bin or
# exec it using Kernel#system with the given args
#
# @param [Array] args
# command or commands to execute
#
# @return [Boolean]
#
# @example
# Padrino.bin('start', '-e production')
#
def self.bin(*args)
@_padrino_bin ||= [self.ruby_command, File.expand_path("../../../bin/padrino", __FILE__)]
args.empty? ? @_padrino_bin : system(args.unshift(@_padrino_bin).join(" "))
end
##
# Return the path to the ruby interpreter taking into account multiple
# installations and windows extensions.
#
# @return [String]
# path to ruby bin executable
#
def self.ruby_command
@ruby_command ||= begin
ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
ruby << RbConfig::CONFIG['EXEEXT']
# escape string in case path to ruby executable contain spaces.
ruby.sub!(/.*\s.*/m, '"\&"')
ruby
end
end
end # Padrino

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View file

@ -0,0 +1,210 @@
module Padrino
class << self
##
# Hooks to be called before a load/reload.
#
# @yield []
# The given block will be called before Padrino was loaded/reloaded.
#
# @return [Array<Proc>]
# The load/reload before hooks.
#
# @example
# before_load do
# pre_initialize_something
# end
#
def before_load(&block)
@_before_load ||= []
@_before_load << block if block_given?
@_before_load
end
##
# Hooks to be called after a load/reload.
#
# @yield []
# The given block will be called after Padrino was loaded/reloaded.
#
# @return [Array<Proc>]
# The load/reload hooks.
#
# @example
# after_load do
# DataMapper.finalize
# end
#
def after_load(&block)
@_after_load ||= []
@_after_load << block if block_given?
@_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.
#
# @return [Boolean]
# returns true if Padrino is not already bootstraped otherwise else.
#
def load!
return false if loaded?
@_called_from = first_caller
Padrino.set_encoding
Padrino.set_load_paths(*load_paths) # We set the padrino 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!
Thread.current[:padrino_loaded] = true
end
##
# Clear the padrino env.
#
# @return [NilClass]
#
def clear!
Padrino.clear_middleware!
Padrino.mounted_apps.clear
@_load_paths = nil
@_dependency_paths = nil
@_global_configuration = nil
Padrino.before_load.clear
Padrino.after_load.clear
Padrino::Reloader.clear!
Thread.current[:padrino_loaded] = nil
end
##
# Method for reloading required applications and their files.
#
def reload!
Padrino.before_load.each(&:call) # Run before hooks
Padrino::Reloader.reload! # detects the modified files
Padrino.after_load.each(&:call) # Run after hooks
end
##
# This adds the ablity to instantiate {Padrino.load!} after
# {Padrino::Application} definition.
#
def called_from
@_called_from || first_caller
end
##
# Determines whether Padrino was loaded with {Padrino.load!}.
#
# @return [Boolean]
# Specifies whether Padrino was loaded.
#
def loaded?
Thread.current[:padrino_loaded]
end
##
# Attempts to require all dependency libs that we need.
# If you use this method we can perform correctly a Padrino.reload!
# Another good thing that this method are dependency check, for example:
#
# # models
# # \-- a.rb => require something of b.rb
# # \-- b.rb
#
# In the example above if we do:
#
# Dir["/models/*.rb"].each { |r| require r }
#
# we get an error, because we try to require first +a.rb+ that need
# _something_ of +b.rb+.
#
# With this method we don't have this problem.
#
# @param [Array<String>] paths
# The paths to require.
#
# @example For require all our app libs we need to do:
# 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
while files.present?
# List of errors and failed files
errors, failed = [], []
# We need a size to make sure things are loading
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)
files.delete(file)
rescue LoadError => e
errors << e
failed << file
rescue NameError => e
errors << e
failed << file
rescue Exception => e
raise e
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?
end
end
##
# Returns default list of path globs to load as dependencies
# Appends custom dependency patterns to the be loaded for Padrino.
#
# @return [Array<String>]
# The dependencey paths.
#
# @example
# Padrino.dependency_paths << "#{Padrino.root}/uploaders/*.rb"
#
def dependency_paths
@_dependency_paths_was = [
"#{root}/config/database.rb", "#{root}/lib/**/*.rb", "#{root}/shared/lib/**/*.rb",
"#{root}/models/**/*.rb", "#{root}/shared/models/**/*.rb", "#{root}/config/apps.rb"
]
@_dependency_paths ||= @_dependency_paths_was
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 # self
end # Padrino

View file

@ -0,0 +1,34 @@
cs:
date:
formats:
# 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: "%d. %m. %Y"
short: "%d %b"
long: "%d. %B %Y"
only_day: "%e"
day_names: [Neděle, Pondělí, Úterý, Středa, Čtvrtek, Pátek, Sobota]
abbr_day_names: [Ne, Po, Út, St, Čt, Pá, So]
month_names: [~, Leden, Únor, Březen, Duben, Květen, Červen, Červenec, Srpen, Září, Říjen, Listopad, Prosinec]
abbr_month_names: [~, Led, Úno, Bře, Dub, Kvě, Čvn, Čvc, Srp, Zář, Říj, Lis, Pro]
order:
- day
- month
- year
time:
formats:
default: "%a %d. %B %Y %H:%M %z"
short: "%d. %m. %H:%M"
long: "%A %d. %B %Y %H:%M"
am: "dop"
pm: "odp"
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " a "
last_word_connector: " a "

View file

@ -0,0 +1,34 @@
da:
date:
formats:
# 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: "%d.%m.%Y"
short: "%e. %b %Y"
long: "%e. %B %Y"
only_day: "%e"
day_names: [søndag, mandag, tirsdag, onsdag, torsdag, fredag, lørdag]
abbr_day_names: [sø, ma, ti, 'on', to, fr, lø]
month_names: [~, januar, februar, marts, april, maj, juni, juli, august, september, oktober, november, december]
abbr_month_names: [~, jan, feb, mar, apr, maj, jun, jul, aug, sep, okt, nov, dec]
order:
- day
- month
- year
time:
formats:
default: "%e. %B %Y, %H:%M"
short: "%e. %b %Y, %H:%M"
long: "%A, %e. %B %Y, %H:%M"
am: ""
pm: ""
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " og "
last_word_connector: " og "

View file

@ -0,0 +1,34 @@
de:
date:
formats:
# Benutze die strftime Parameter für die Formatierung
# Wenn keine Formate angegeben werden, wird "default" benutzt.
# Du kannst auch weitere Formate hinzufügen, wenn Du möchtest.
default: "&d.&m.%Y"
short: "%b %d"
long: "%B %d, %Y"
only_day: "%e"
day_names: [Sonntag, Montag, Dienstag, Mittwoch, Donnerstag, Freitag, Samstag]
abbr_day_names: [So, Mo, Di, Mi, Do, Fr, Sa]
month_names: [~, Januar, Februar, März, April, Mai, Juni, Juli, August, September, Oktober, November, Dezember]
abbr_month_names: [~, Jan, Feb, Mär, Apr, Mai, Jun, Jul, Aug, Sep, Okt, Nov, Dez]
order:
- day
- month
- year
time:
formats:
default: "%a, %d %b %Y %H:%M:%S %z"
short: "%d %b %H:%M"
long: "%B %d, %Y %H:%M"
am: "am"
pm: "pm"
# Benutzt in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " und "
last_word_connector: ", und "

View file

@ -0,0 +1,34 @@
en:
date:
formats:
# 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: "%B %d, %Y"
only_day: "%e"
day_names: [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
abbr_day_names: [Sun, Mon, Tue, Wed, Thu, Fri, Sat]
month_names: [~, January, February, March, April, May, June, July, August, September, October, November, December]
abbr_month_names: [~, Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec]
order:
- year
- month
- day
time:
formats:
default: "%a, %d %b %Y %H:%M:%S %z"
short: "%d %b %H:%M"
long: "%B %d, %Y %H:%M"
am: "am"
pm: "pm"
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " and "
last_word_connector: ", and "

View file

@ -0,0 +1,34 @@
es:
date:
formats:
# 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: "%d-%m-%Y"
short: "%b %d"
long: "%B %d, %Y"
only_day: "%e"
day_names: [Domingo, Lunes, Martes, Miércoles, Jueves, Viernes, Sábado]
abbr_day_names: [Dom, Lun, Mar, Mie, Jue, Vie, Sab]
month_names: [~, Enero, Febrero, Marzo, Abril, Mayo, Junio, Julio, Agosto, Septiembre, Octubre, Noviembre, Diciembre]
abbr_month_names: [~, Ene, Feb, Mar, Abr, May, Jun, Jul, Ago, Sep, Oct, Nov, Dic]
order:
- day
- month
- year
time:
formats:
default: "%a, %d %b %Y %H:%M:%S %z"
short: "%d %b %H:%M"
long: "%B %d, %Y %H:%M"
am: "am"
pm: "pm"
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " y "
last_word_connector: ", y "

View file

@ -0,0 +1,34 @@
fr:
date:
formats:
# 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: "%d-%m-%Y"
short: "%b %d"
long: "%B %d, %Y"
only_day: "%e"
day_names: [Dimanche, Lundi, Mardi, Mercredi, Jeudi, Vendredi, Samedi]
abbr_day_names: [Lun, Mar, Mer, Jeu, Ven, Sam, Dim]
month_names: [~, Janvier, Février, Mars, Avril, Mai, Juin, Juillet, Août, Septembre, Octobre, Novembre, Décembre]
abbr_month_names: [~, Jan, Fev, Mar, Avr, Mai, Jui, Jui, Aou, Sep, Oct, Nov, Dec]
order:
- day
- month
- year
time:
formats:
default: "%a, %d %b %Y %H:%M:%S %z"
short: "%d %b %H:%M"
long: "%B %d, %Y %H:%M"
am: "am"
pm: "pm"
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " et "
last_word_connector: ", et "

View file

@ -0,0 +1,34 @@
hu:
date:
formats:
# 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."
only_day: "%e"
day_names: [vasárnap, hétfő, kedd, szerda, csütörtök, péntek, szombat]
abbr_day_names: [vas, hét, kedd, sze, csüt, pén, szo]
month_names: [~, január, február, március, április, május, június, július, augusztus, szeptember, oktober, november, december]
abbr_month_names: [~, jan, febr, márc, ápr, máj, jún, júl, aug, szept, okt, nov, dec]
order:
- year
- month
- day
time:
formats:
default: "%Y. %B %d. %H:%M:%S %z, %A"
short: "%B %d. %H:%M"
long: "%Y. %B %d. %H:%M"
am: "de"
pm: "du"
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " és "
last_word_connector: " és "

View file

@ -0,0 +1,40 @@
it:
date:
formats:
# 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: "%d-%m-%Y"
short: "%d %b"
long: "%d %B %Y"
only_day: "%e"
day_names: [Domenica, Lunedì, Martedì, Mercoledì, Giovedì, Venerdì, Sabato]
abbr_day_names: [Dom, Lun, Mar, Mer, Gio, Ven, Sab]
month_names: [~, Gennaio, Febbraio, Marzo, Aprile, Maggio, Giugno, Luglio, Agosto, Settembre, Ottobre, Novembre, Dicembre]
abbr_month_names: [~, Gen, Feb, Mar, Apr, Mag, Giu, Lug, Ago, Set, Ott, Nov, Dic]
order:
- day
- month
- year
time:
formats:
default: "%a %d %b %Y, %H:%M:%S %z"
time: "%H:%M"
short: "%d %b %H:%M"
long: "%d %B %Y %H:%M"
only_second: "%S"
datetime:
formats:
default: "%d-%m-%YT%H:%M:%S%Z"
am: 'am'
pm: 'pm'
# Used in array.to_sentence.
support:
array:
sentence_connector: "e"
skip_last_comma: false

View file

@ -0,0 +1,34 @@
ja:
date:
formats:
# 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: "%m/%d"
long: "%Y年%m月%d日"
only_day: "%e"
day_names: [日曜日, 月曜日, 火曜日, 水曜日, 木曜日, 金曜日, 土曜日]
abbr_day_names: [日, 月, 火, 水, 木, 金, 土]
month_names: [~, 一月, 二月, 三月, 四月, 五月, 六月, 七月, 八月, 九月, 十月, 十一月, 十二月]
abbr_month_names: [~, 一月, 二月, 三月, 四月, 五月, 六月, 七月, 八月, 九月, 十月, 十一月, 十二月]
order:
- year
- month
- day
time:
formats:
default: "%Y/%m/%d %H:%M:%S"
short: "%m/%d %H:%M"
long: "%Y年%m月%d日 %H時%M分%S秒"
am: "午前"
pm: "午後"
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " と "
last_word_connector: ", と "

View file

@ -0,0 +1,34 @@
lv:
date:
formats:
# 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: "%d.%m.%Y."
short: "%e. %B"
long: "%Y. gada %e. %B"
only_day: "%e"
day_names: [Svētdiena, Pirmdiena, Otrdiena, Trešdiena, Ceturtdiena, Piektdiena, Sestdiena]
abbr_day_names: [Sv, P, O, T, C, P, S]
month_names: [~, Janvāris, Februāris, Marts, Aprīlis, Maijs, Jūnijs, Jūlijs, Augusts, Septembris, Oktobris, Novembris, Decembris]
abbr_month_names: [~, Janv, Febr, Marts, Apr, Maijs, Jūn, Jūl, Aug, Sept, Okt, Nov, Dec]
order:
- day
- month
- year
time:
formats:
default: "%Y. gada %e. %B, %H:%M"
short: "%d.%m.%Y., %H:%M"
long: "%Y. gada %e. %B, %H:%M:%S"
am: "priekšpusdiena"
pm: "pēcpusdiena"
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " un "
last_word_connector: ", un "

View file

@ -0,0 +1,34 @@
nl:
date:
formats:
# 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: "%d-%m-%Y"
short: "%d %b"
long: "%d %B %Y"
only_day: "%e"
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]
abbr_month_names: [~, jan, feb, maa, apr, mei, jun, jul, aug, sep, okt, nov, dec]
order:
- day
- month
- year
time:
formats:
default: "%a %d %b %Y %H:%M:%S %z"
short: "%d %b %H:%M"
long: "%d %B %Y %H:%M"
am: "am"
pm: "pm"
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " en "
last_word_connector: " en "

View file

@ -0,0 +1,35 @@
"no":
date:
formats:
# 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: "%d.%m.%Y"
short: "%e. %b %Y"
long: "%e. %B %Y"
only_day: "%e"
day_names: [søndag, mandag, tirsdag, onsdag, torsdag, fredag, lørdag]
abbr_day_names: [sø, ma, ti, 'on', to, fr, lø]
month_names: [~, januar, februar, mars, april, mai, juni, juli, august, september, oktober, november, desember]
abbr_month_names: [~, jan, feb, mar, apr, maj, jun, jul, aug, sep, okt, nov, des]
order:
- day
- month
- year
time:
formats:
default: "%e. %B %Y, %H:%M"
short: "%e. %b %Y, %H:%M"
long: "%A, %e. %B %Y, %H:%M"
am: ""
pm: ""
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " og "
last_word_connector: " og "

View file

@ -0,0 +1,34 @@
pl:
date:
formats:
# 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: "%d %b"
long: "%d %B %Y"
only_day: "%e"
day_names: [Niedziela, Poniedziałek, Wtorek, Środa, Czwartek, Piątek, Sobota]
abbr_day_names: [nie, pon, wto, śro, czw, pia, sob]
month_names: [~, Styczeń, Luty, Marzec, Kwiecień, Maj, Czerwiec, Lipiec, Sierpień, Wrzesień, Październik, Listopad, Grudzień]
abbr_month_names: [~, sty, lut, mar, kwi, maj, cze, lip, sie, wrz, paź, lis, gru]
order:
- year
- month
- day
time:
formats:
default: "%a, %d %b %Y, %H:%M:%S %z"
short: "%d %b, %H:%M"
long: "%d %B %Y, %H:%M"
am: "przed południem"
pm: "po południu"
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " i "
last_word_connector: " i "

View file

@ -0,0 +1,40 @@
pt_br:
date:
formats:
# 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: "%d-%m-%Y"
short: "%d %b"
long: "%d %B %Y"
only_day: "%e"
day_names: [Domingo, Segunda-feira, Terça-feira, Quarta-feira, Quinta-feira, Sexta-feira, Sábado]
abbr_day_names: [Dom, Seg, Ter, Qua, Qui, Sex, Sab]
month_names: [~, Janeiro, Fevereiro, Março, Abril, Maio, Junho, Julho, Agosto, Setembro, Outubro, Novembro, Dezembro]
abbr_month_names: [~, Jan, Fev, Mar, Abr, Maio, Jun, Jul, Ago, Set, Out, Nov, Dez]
order:
- day
- month
- year
time:
formats:
default: "%a %d %b %Y, %H:%M:%S %z"
time: "%H:%M"
short: "%d %b %H:%M"
long: "%d %B %Y %H:%M"
only_second: "%S"
datetime:
formats:
default: "%d-%m-%YT%H:%M:%S%Z"
am: 'am'
pm: 'pm'
# Used in array.to_sentence.
support:
array:
sentence_connector: "e"
skip_last_comma: false

View file

@ -0,0 +1,35 @@
ru:
date:
formats:
# 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: "%d.%m.%Y"
short: "%d %b"
long: "%e %B, %Y"
only_day: "%e"
day_names: [Воскресенье, Понедельник, Вторник, Среда, Четверг, Пятница, Суббота]
abbr_day_names: [Вс, Пн, Вт, Ср, Чт, Пт, Сб]
month_names: [~, Январь, Февраль, Март, Апрель, Май, Июнь, Июль, Август, Сентябрь, Октябрь, Ноябрь, Декабрь]
abbr_month_names: [~, Янв, Феб, Мар, Апр, Май, Июн, Июл, Авг, Сен, Окт, Ноя, Дек]
order:
- year
- month
- day
time:
formats:
default: "%a, %d %b %Y %H:%M:%S %z"
short: "%d %b %H:%M"
long: "%e %B, %Y %H:%M"
am: "д.п."
pm: "п.п."
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " и "
last_word_connector: " и "

View file

@ -0,0 +1,34 @@
tr:
date:
formats:
# 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: "%d/%m/%Y"
short: "%d %b"
long: "%d %B %Y"
only_day: "%e"
day_names: [Pazar, Pazartesi, Salı, Çarşamba, Perşembe, Cuma, Cumartesi]
abbr_day_names: [Paz, Pts, Sal, Çar, Per, Cum, Cts]
month_names: [~, Ocak, Şubat, Mart, Nisan, Mayıs, Haziran, Temmuz, Ağustos, Eylül, Ekim, Kasım, Aralık]
abbr_month_names: [~, Oca, Şub, Mar, Nis, May, Haz, Tem, Ağu, Eyl, Eki, Kas, Ara]
order:
- day
- month
- year
time:
formats:
default: "%a, %b %b %Y %H:%M:%S %z"
short: "%b %d %H:%M"
long: "%d %B, %Y %H:%M"
am: "öö"
pm: "ös"
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " ve "
last_word_connector: " ve "

View file

@ -0,0 +1,34 @@
uk:
date:
formats:
# 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: "%d.%m.%Y"
short: "%d %b"
long: "%e %B, %Y"
only_day: "%e"
day_names: [Неділя, Понеділок, Вівторок, Середа, Четвер, Пятница, Субота]
abbr_day_names: [Нд, Пн, Вт, Ср, Чт, Пт, Сб]
month_names: [~, Січено, Лютий, Березень, Квітень, Травень, Червень, Липень, Серпень, Вересень, Жовтень, Листопад, Грудень]
abbr_month_names: [~, Січ, Лют, Бер, Кві, Тра, Чер, Лип, Сер, Вер, Жов, Лис, Гру]
order:
- year
- month
- day
time:
formats:
default: "%a, %d %b %Y %H:%M:%S %z"
short: "%d %b %H:%M"
long: "%e %B, %Y %H:%M"
am: "д.п."
pm: "п.п"
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " і "
last_word_connector: ", і "

View file

@ -0,0 +1,34 @@
zh_cn:
date:
formats:
# 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日"
only_day: "%e"
day_names: [星期日, 星期一, 星期二, 星期三, 星期四, 星期五, 星期六]
abbr_day_names: [日, 一, 二, 三, 四, 五, 六]
month_names: [~, 一月, 二月, 三月, 四月, 五月, 六月, 七月, 八月, 九月, 十月, 十一月, 十二月]
abbr_month_names: [~, 1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月, 10月, 11月, 12月]
order:
- year
- month
- day
time:
formats:
default: "%Y年%b%d日 %A %H:%M:%S %Z"
short: "%b%d日 %H:%M"
long: "%Y年%b%d日 %H:%M"
am: "上午"
pm: "下午"
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " 和 "
last_word_connector: ", 和 "

View file

@ -0,0 +1,34 @@
zh_tw:
date:
formats:
# 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日"
only_day: "%e"
day_names: [星期日, 星期一, 星期二, 星期三, 星期四, 星期五, 星期六]
abbr_day_names: [日, 一, 二, 三, 四, 五, 六]
month_names: [~, 一月, 二月, 三月, 四月, 五月, 六月, 七月, 八月, 九月, 十月, 十一月, 十二月]
abbr_month_names: [~, 1月, 2月, 3月, 4月, 5月, 6月, 7月, 8月, 9月, 10月, 11月, 12月]
order:
- year
- month
- day
time:
formats:
default: "%Y年%b%d日 %A %H:%M:%S %Z"
short: "%b%d日 %H:%M"
long: "%Y年%b%d日 %H:%M"
am: "上午"
pm: "下午"
# Used in array.to_sentence.
support:
array:
words_connector: ", "
two_words_connector: " 和 "
last_word_connector: ", 和 "

View file

@ -0,0 +1,345 @@
# Defines the log level for a Padrino project.
PADRINO_LOG_LEVEL = ENV['PADRINO_LOG_LEVEL'] unless defined?(PADRINO_LOG_LEVEL)
# Defines the logger used for a Padrino project.
PADRINO_LOGGER = ENV['PADRINO_LOGGER'] unless defined?(PADRINO_LOGGER)
module Padrino
##
# @return [Padrino::Logger]
#
# @example
# logger.debug "foo"
# logger.warn "bar"
#
def self.logger
Padrino::Logger.setup! if Thread.current[:padrino_logger].nil?
Thread.current[:padrino_logger]
end
##
# Set the padrino logger
#
# @param [Object] value
# an object that respond to <<, write, puts, debug, warn etc..
#
# @return [Object]
# the given value
#
# @example using ruby default logger
# require 'logger'
# Padrino.logger = Logger.new(STDOUT)
#
# @example using ActiveSupport
# require 'active_support/buffered_logger'
# Padrino.logger = Buffered.new(STDOUT)
#
def self.logger=(value)
Thread.current[:padrino_logger] = value
end
##
# Extensions to the built in Ruby logger.
#
class Logger
attr_accessor :level
attr_accessor :auto_flush
attr_reader :buffer
attr_reader :log
attr_reader :init_args
attr_accessor :log_static
##
# Ruby (standard) logger levels:
#
# :fatal:: An unhandleable error that results in a program crash
# :error:: A handleable error condition
# :warn:: A warning
# :info:: generic (useful) information about system operation
# :debug:: low-level information for developers
#
Levels = {
:fatal => 7,
:error => 6,
:warn => 4,
:info => 3,
:debug => 0,
:devel => -1,
} unless const_defined?(:Levels)
@@mutex = {}
##
# Configuration for a given environment, possible options are:
#
# :log_level:: Once of [:fatal, :error, :warn, :info, :debug]
# :stream:: Once of [:to_file, :null, :stdout, :stderr] our your custom stream
# :log_level::
# The log level from, e.g. :fatal or :info. Defaults to :warn in the
# production environment and :debug otherwise.
# :auto_flush::
# Whether the log should automatically flush after new messages are
# added. Defaults to true.
# :format_datetime:: Format of datetime. Defaults to: "%d/%b/%Y %H:%M:%S"
# :format_message:: Format of message. Defaults to: ""%s - - [%s] \"%s\"""
# :log_static:: Whether or not to show log messages for static files. Defaults to: false
#
# @example
# Padrino::Logger::Config[:development] = { :log_level => :debug, :stream => :to_file }
# # or you can edit our defaults
# Padrino::Logger::Config[:development][:log_level] = :error
# # or you can use your stream
# Padrino::Logger::Config[:development][:stream] = StringIO.new
#
# Defaults are:
#
# :production => { :log_level => :warn, :stream => :to_file }
# :development => { :log_level => :debug, :stream => :stdout }
# :test => { :log_level => :fatal, :stream => :null }
#
# In some cases, configuring the loggers before loading the framework is necessary.
# You can do so by setting PADRINO_LOGGER:
#
# PADRINO_LOGGER = { :staging => { :log_level => :debug, :stream => :to_file }}
#
Config = {
:production => { :log_level => :warn, :stream => :to_file },
:development => { :log_level => :debug, :stream => :stdout, :format_datetime => ' ' },
:test => { :log_level => :debug, :stream => :null }
}
Config.merge!(PADRINO_LOGGER) if PADRINO_LOGGER
# Colors for levels
ColoredLevels = {
:fatal => [:bold, :red],
:error => [:red],
:warn => [:yellow],
:info => [:green],
:debug => [:cyan],
:devel => [:magenta]
} unless defined?(ColoredLevels)
##
# Setup a new logger
#
# @return [Padrino::Logger]
# A {Padrino::Logger} instance
#
def self.setup!
config_level = (PADRINO_LOG_LEVEL || Padrino.env || :test).to_sym # need this for PADRINO_LOG_LEVEL
config = Config[config_level]
unless config
warn("No logging configuration for :#{config_level} found, falling back to :production")
config = Config[:production]
end
stream = case config[:stream]
when :to_file
FileUtils.mkdir_p(Padrino.root("log")) unless File.exists?(Padrino.root("log"))
File.new(Padrino.root("log", "#{Padrino.env}.log"), "a+")
when :null then StringIO.new
when :stdout then $stdout
when :stderr then $stderr
else config[:stream] # return itself, probabilly is a custom stream.
end
Thread.current[:padrino_logger] = Padrino::Logger.new(config.merge(:stream => stream))
end
##
# To initialize the logger you create a new object, proxies to set_log.
#
# @param [Hash] options
#
# @option options [Symbol] :stream ($stdout)
# Either an IO object or a name of a logfile. Defaults to $stdout
#
# @option options [Symbol] :log_level (:production in the production environment and :debug otherwise)
# The log level from, e.g. :fatal or :info.
#
# @option options [Symbol] :auto_flush (true)
# Whether the log should automatically flush after new messages are
# added. Defaults to true.
#
# @option options [Symbol] :format_datetime (" [%d/%b/%Y %H:%M:%S] ")
# Format of datetime
#
# @option options [Symbol] :format_message ("%s -%s%s")
# Format of message
#
# @option options [Symbol] :log_static (false)
# Whether or not to show log messages for static files.
#
def initialize(options={})
@buffer = []
@auto_flush = options.has_key?(:auto_flush) ? options[:auto_flush] : true
@level = options[:log_level] ? Levels[options[:log_level]] : Levels[:debug]
@log = options[:stream] || $stdout
@log.sync = true
@mutex = @@mutex[@log] ||= Mutex.new
@format_datetime = options[:format_datetime] || "%d/%b/%Y %H:%M:%S"
@format_message = options[:format_message] || "%s -%s%s"
@log_static = options.has_key?(:log_static) ? options[:log_static] : false
end
##
# Colorize our level
#
# @param [String, Symbol] level
#
# @see Padrino::Logger::ColoredLevels
#
def colored_level(level)
style = ColoredLevels[level].map { |c| "\e[%dm" % String.colors[c] } * ''
[style, level.to_s.upcase.rjust(7), "\e[0m"] * ''
end
##
# Flush the entire buffer to the log object.
#
def flush
return unless @buffer.size > 0
@mutex.synchronize do
@log.write(@buffer.slice!(0..-1).join(''))
end
end
##
# Close and remove the current log object.
#
# @return [NilClass]
#
def close
flush
@log.close if @log.respond_to?(:close) && !@log.tty?
@log = nil
end
##
# Appends a message to the log. The methods yield to an optional block and
# the output of this block will be appended to the message.
#
# @param [String] message
# The message that you want write to your stream
#
# @param [String] level
# The level one of :debug, :warn etc...
#
#
def push(message = nil, level = nil)
write @format_message % [colored_level(level), Time.now.strftime(@format_datetime).yellow, message.to_s.strip]
end
##
# Append a to development logger a given action with time
#
# @param [string] action
# The action
#
# @param [float] time
# Time duration for the given action
#
# @param [message] string
# The message that you want to log
#
# @example
# logger.bench 'GET', started_at, '/blog/categories'
# # => DEBUG - GET (0.056ms) - /blog/categories
#
def bench(action, began_at, message, level=:debug, color=:yellow)
@_pad ||= 8
@_pad = action.to_s.size if action.to_s.size > @_pad
duration = Time.now - began_at
color = :red if duration > 1
push "%s (" % action.to_s.upcase.rjust(@_pad).send(color) + "%0.4fms".bold.send(color) % duration + ") %s" % message.to_s, level
end
##
# Directly append message to the log.
#
# @param [String] message
# The message
#
def <<(message = nil)
message << "\n" unless message[-1] == ?\n
@buffer << message
flush if @auto_flush
message
end
alias :write :<<
##
# Generate the logging methods for {Padrino.logger} for each log level.
#
Levels.each_pair do |name, number|
define_method(name) do |*args|
return if number < level
if args.size > 1
bench(*args)
else
push(args * '', name)
end
end
define_method(:"#{name}?") do
number >= level
end
end
##
# Padrino::Loggger::Rack forwards every request to an +app+ given, and
# logs a line in the Apache common log format to the +logger+, or
# rack.errors by default.
#
class Rack
def initialize(app, uri_root) # @private
@app = app
@uri_root = uri_root.sub(/\/$/,"")
end
def call(env) # @private
env['rack.logger'] = Padrino.logger
env['rack.errors'] = Padrino.logger.log
began_at = Time.now
status, header, body = @app.call(env)
log(env, status, header, began_at)
[status, header, body]
end
private
def log(env, status, header, began_at)
return if env['sinatra.static_file'] and !logger.log_static
logger.bench(
env["REQUEST_METHOD"],
began_at,
[
@uri_root.to_s,
env["PATH_INFO"],
env["QUERY_STRING"].empty? ? "" : "?" + env["QUERY_STRING"],
' - ',
status.to_s[0..3].bold,
' ',
code_to_name(status)
] * '',
:debug,
:magenta
)
end
def code_to_name(status)
::Rack::Utils::HTTP_STATUS_CODES[status.to_i] || ''
end
end # Rack
end # Logger
end # Padrino
module Kernel # @private
##
# Define a logger available every where in our app
#
def logger
Padrino.logger
end
end # Kernel

View file

@ -0,0 +1,224 @@
module Padrino
##
# Represents a particular mounted padrino application
# Stores the name of the application (app folder name) and url mount path
#
# @example
# Mounter.new("blog_app", :app_class => "Blog").to("/blog")
# Mounter.new("blog_app", :app_file => "/path/to/blog/app.rb").to("/blog")
#
class Mounter
class MounterException < RuntimeError # @private
end
attr_accessor :name, :uri_root, :app_file, :app_class, :app_root, :app_obj, :app_host
##
# @param [String, Padrino::Application] name
# The app name or the {Padrino::Application} class
#
# @param [Hash] options
# @option options [Symbol] :app_class (Detected from name)
# @option options [Symbol] :app_file (Automatically detected)
# @option options [Symbol] :app_obj (Detected)
# @option options [Symbol] :app_root (Directory of :app_file)
#
def initialize(name, options={})
@name = name.to_s
@app_class = options[:app_class] || @name.camelize
@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)
@uri_root = "/"
Padrino::Reloader.exclude_constants << @app_class
end
##
# Registers the mounted application onto Padrino
#
# @param [String] mount_url
# Path where we mount the app
#
# @example
# Mounter.new("blog_app").to("/blog")
#
def to(mount_url)
@uri_root = mount_url
Padrino.insert_mounted_app(self)
self
end
##
# Registers the mounted application onto Padrino for the given host
#
# @param [String] mount_host
# Host name
#
# @example
# Mounter.new("blog_app").to("/blog").host("blog.padrino.org")
# Mounter.new("blog_app").host("blog.padrino.org")
# Mounter.new("catch_all").host(/.*\.padrino.org/)
#
def host(mount_host)
@app_host = mount_host
Padrino.insert_mounted_app(self)
self
end
##
# Maps Padrino application onto a Padrino::Router
# For use in constructing a Rack application
#
# @param [Padrino::Router]
#
# @return [Padrino::Router]
#
# @example
# @app.map_onto(router)
#
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_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 :static, File.exist?(app_obj.public_folder) if app_obj.nil?
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
###
# Returns the route objects for the mounted application
#
def routes
app_obj.routes
end
###
# Returns the basic route information for each named route
#
# @return [Array]
# Array of routes
#
def named_routes
app_obj.routes.map { |route|
name_array = "(#{route.named.to_s.split("_").map { |piece| %Q[:#{piece}] }.join(", ")})"
request_method = route.conditions[:request_method][0]
full_path = File.join(uri_root, route.original_path)
next if route.named.blank? || request_method == 'HEAD'
OpenStruct.new(:verb => request_method, :identifier => route.named, :name => name_array, :path => full_path)
}.compact
end
##
# Makes two Mounters equal if they have the same name and uri_root
#
# @param [Padrino::Mounter] other
#
def ==(other)
other.is_a?(Mounter) && self.app_class == other.app_class && self.uri_root == other.uri_root
end
##
# @return [Padrino::Application]
# the class object for the app if defined, nil otherwise
#
def app_constant
klass = Object
for piece in app_class.split("::")
piece = piece.to_sym
if klass.const_defined?(piece)
klass = klass.const_get(piece)
else
return
end
end
klass
end
protected
##
# Locates and requires the file to load the app constant
#
def locate_app_object
@_app_object ||= begin
ensure_app_file!
Padrino.require_dependencies(app_file)
app_constant
end
end
##
# Returns the determined location of the mounted application main file
#
def locate_app_file
candidates = []
candidates << app_constant.app_file if app_constant.respond_to?(:app_file) && File.exist?(app_constant.app_file.to_s)
candidates << Padrino.first_caller if File.identical?(Padrino.first_caller.to_s, Padrino.called_from.to_s)
candidates << Padrino.mounted_root(name.downcase, "app.rb")
candidates << Padrino.root("app", "app.rb")
candidates.find { |candidate| File.exist?(candidate) }
end
###
# Raises an exception unless app_file is located properly
#
def ensure_app_file!
message = "Unable to locate source file for app '#{app_class}', try with :app_file => '/path/app.rb'"
raise MounterException, message unless @app_file
end
###
# Raises an exception unless app_obj is defined properly
#
def ensure_app_object!
message = "Unable to locate app for '#{app_class}', try with :app_class => 'MyAppClass'"
raise MounterException, message unless @app_obj
end
end
class << self
attr_writer :mounted_root # Set root directory where padrino searches mounted apps
##
# @param [Array] args
#
# @return [String]
# the root to the mounted apps base directory
#
def mounted_root(*args)
Padrino.root(@mounted_root ||= "", *args)
end
##
# @return [Array]
# the mounted padrino applications (MountedApp objects)
#
def mounted_apps
@mounted_apps ||= []
end
##
# Inserts a Mounter object into the mounted applications (avoids duplicates)
#
# @param [Padrino::Mounter] mounter
#
def insert_mounted_app(mounter)
Padrino.mounted_apps.push(mounter) unless Padrino.mounted_apps.include?(mounter)
end
##
# Mounts a new sub-application onto Padrino project
#
# @see Padrino::Mounter#new
#
# @example
# Padrino.mount("blog_app").to("/blog")
#
def mount(name, options={})
Mounter.new(name, options)
end
end # Mounter
end # Padrino

View file

@ -0,0 +1,254 @@
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 ||= []
end
##
# Specified constants can be configured to be reloaded on every request.
# Default included constants are: [none]
#
def include_constants
@_include_constants ||= []
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!
MTIMES.clear
LOADED_CLASSES.each do |file, klasses|
klasses.each { |klass| remove_constant(klass) }
LOADED_CLASSES.delete(file)
end
LOADED_FILES.each do |file, dependencies|
dependencies.each { |dependency| $LOADED_FEATURES.delete(dependency) }
$LOADED_FEATURES.delete(file)
end
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] ||= mtime
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.map { |klass| klass.name.to_s.split("::")[0] }.uniq
klasses = klasses | Padrino.mounted_apps.map { |app| app.app_class }
Padrino::Reloader.exclude_constants.concat(klasses)
end
##
# A safe Kernel::require which issues the necessary hooks depending on results
#
def safe_load(file, options={})
began_at = Time.now
force, file = options[:force], figure_path(file)
# Check if file was changed or if force a reload
reload = MTIMES[file] && File.mtime(file) > MTIMES[file]
return if !force && !reload && MTIMES[file]
# Removes all classes declared in the specified file
if klasses = LOADED_CLASSES.delete(file)
klasses.each { |klass| remove_constant(klass) }
end
# Remove all loaded fatures with our file
if features = LOADED_FILES[file]
features.each { |feature| $LOADED_FEATURES.delete(feature) }
end
# Duplicate objects and loaded features before load file
klasses = ObjectSpace.classes.dup
files = $LOADED_FEATURES.dup
# Now we can reload dependencies of our file
if features = LOADED_FILES.delete(file)
features.each { |feature| safe_load(feature, :force => true) }
end
# 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)
verbosity_was, $-v = $-v, nil
loaded = false
require(file)
loaded = true
MTIMES[file] = File.mtime(file)
rescue SyntaxError => e
logger.error "Cannot require #{file} due to a syntax error: #{e.message}"
ensure
$-v = verbosity_was
new_constants = (ObjectSpace.classes - klasses).uniq
if loaded
# Store the file details
LOADED_CLASSES[file] = new_constants
LOADED_FILES[file] = ($LOADED_FEATURES - files - [file]).uniq
# Track only features in our Padrino.root
LOADED_FILES[file].delete_if { |feature| !in_root?(feature) }
else
logger.devel "Failed to load #{file}; removing partially defined constants"
new_constants.each { |klass| remove_constant(klass) }
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.compact.uniq.any? { |c| (const.to_s =~ %r{^#{Regexp.escape(c)}}) } &&
!include_constants.compact.uniq.any? { |c| (const.to_s =~ %r{^#{Regexp.escape(c)}}) }
begin
parts = const.to_s.split("::")
base = parts.size == 1 ? Object : parts[0..-2].join("::").constantize
object = parts[-1].to_s
base.send(:remove_const, object)
logger.devel "Removed constant: #{const}"
rescue NameError; end
end
private
##
# Return the mounted_apps providing the app location
# Can be an array because in one app.rb we can define multiple Padrino::Appplications
#
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) =~ %r{^#{Regexp.escape(Padrino.root)}}
end
##
# Searches Ruby files in your +Padrino.load_paths+ , Padrino::Application.load_paths
# and monitors them for any changes.
#
def 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
files.uniq.map { |file|
file = File.expand_path(file)
next if Padrino::Reloader.exclude.any? { |base| file =~ %r{^#{Regexp.escape(base)}} } || !File.exist?(file)
yield(file, File.mtime(file))
}.compact
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 # Reloader
end # Padrino

View file

@ -0,0 +1,98 @@
module Padrino
##
# This class is an extended version of Rack::URLMap
#
# Padrino::Router like Rack::URLMap dispatches in such a way that the
# longest paths are tried first, since they are most specific.
#
# Features:
#
# * Map a path to the specified App
# * Ignore server names (this solve issues with vhost and domain aliases)
# * Use hosts instead of server name for mappings (this help us with our vhost and doman aliases)
#
# @example
#
# routes = Padrino::Router.new do
# map(:path => "/", :to => PadrinoWeb, :host => "padrino.local")
# map(:path => "/", :to => Admin, :host => "admin.padrino.local")
# end
# run routes
#
# routes = Padrino::Router.new do
# map(:path => "/", :to => PadrinoWeb, :host => /*.padrino.local/)
# end
# run routes
#
# @api semipublic
class Router
# Constructs a new route mapper instance
def initialize(*mapping, &block)
@mapping = []
mapping.each { |m| map(m) }
instance_eval(&block) if block
end
##
# Map a route path and host to a specified application.
#
# @param [Hash] options
# The options to map.
# @option options [Sinatra::Application] :to
# The class of the application to mount.
# @option options [String] :path ("/")
# The path to map the specified application.
# @option options [String] :host
# The host to map the specified application.
#
# @example
# map(:path => "/", :to => PadrinoWeb, :host => "padrino.local")
#
# @return [Array] The sorted route mappings.
# @api semipublic
def map(options={})
path = options[:path] || "/"
host = options[:host]
app = options[:to]
raise ArgumentError, "paths need to start with /" if path[0] != ?/
raise ArgumentError, "app is required" if app.nil?
path = path.chomp('/')
match = Regexp.new("^#{Regexp.quote(path).gsub('/', '/+')}(.*)", nil, 'n')
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.
# @api private
def call(env)
rPath = env["PATH_INFO"].to_s
script_name = env['SCRIPT_NAME']
hHost, sName, sPort = env.values_at('HTTP_HOST','SERVER_NAME','SERVER_PORT')
@mapping.each do |host, path, match, app|
next unless host.nil? || hHost =~ host
next unless rPath =~ match && rest = $1
next unless rest.empty? || rest[0] == ?/
rest = "/" if rest.empty?
return app.call(
env.merge(
'SCRIPT_NAME' => (script_name + path),
'PATH_INFO' => rest))
end
[404, {"Content-Type" => "text/plain", "X-Cascade" => "pass"}, ["Not Found: #{rPath}"]]
end
private
# Sorts the mapped routes in consistent order
def sort!
@mapping = @mapping.sort_by { |h, p, m, a| -p.size }
end
end # Router
end # Padrino

View file

@ -0,0 +1,79 @@
module Padrino
##
# Runs the Padrino apps as a self-hosted server using:
# thin, mongrel, or webrick in that order.
#
# @example
# Padrino.run! # with these defaults => host: "localhost", port: "3000", adapter: the first found
# Padrino.run!("localhost", "4000", "mongrel") # use => host: "0.0.0.0", port: "3000", adapter: "mongrel"
#
def self.run!(options={})
Padrino.load!
Server.start(Padrino.application, options)
end
##
# This module builds a Padrino server to run the project based on available handlers.
#
class Server < Rack::Server
# Server Handlers
Handlers = [:thin, :mongrel, :webrick]
# Starts the application on the available server with specified options.
def self.start(app, opts={})
options = {}.merge(opts) # We use a standard hash instead of Thor::CoreExt::HashWithIndifferentAccess
options.symbolize_keys!
options[:Host] = options.delete(:host) || '0.0.0.0'
options[:Port] = options.delete(:port) || 3000
options[:AccessLog] = []
if options[:daemonize]
options[:pid] = options[:pid].blank? ? File.expand_path('tmp/pids/server.pid') : opts[:pid]
FileUtils.mkdir_p(File.dirname(options[:pid]))
end
options[:server] = detect_rack_handler if options[:server].blank?
new(options, app).start
end
def initialize(options, app)
@options, @app = options, app
end
# Starts the application on the available server with specified options.
def start
puts "=> Padrino/#{Padrino.version} has taken the stage #{Padrino.env} at http://#{options[:Host]}:#{options[:Port]}"
[:INT, :TERM].each { |sig| trap(sig) { exit } }
super
ensure
puts "<= Padrino has ended his set (crowd applauds)" unless options[:daemonize]
end
# The application the server will run.
def app
@app
end
alias :wrapped_app :app
# The options specified to the server.
def options
@options
end
private
# Detects the supported handler to use.
#
# @example
# detect_rack_handler => <ThinHandler>
#
def self.detect_rack_handler
Handlers.each do |handler|
begin
return handler if Rack::Handler.get(handler.to_s.downcase)
rescue LoadError
rescue NameError
end
end
fail "Server handler (#{Handlers.join(', ')}) not found."
end
end # Server
end # Padrino

View file

@ -0,0 +1,199 @@
##
# 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/slice' # slice
require 'active_support/core_ext/object/blank' # present?
require 'active_support/core_ext/array/extract_options' # extract_options
require 'active_support/inflector/methods' # constantize
require 'active_support/inflector/inflections' # pluralize
require 'active_support/inflections' # load default inflections
require 'yaml' unless defined?(YAML) # load yaml for i18n
require 'Win32/Console/ANSI' if RUBY_PLATFORM =~ /win32/ # ruby color suppor for win
##
# This is an adapted version of active_support/core_ext/string/inflections.rb
# to prevent loading several dependencies including I18n gem.
#
# Issue: https://github.com/rails/rails/issues/1526
#
class String
##
# Returns the plural form of the word in the string.
#
# "post".pluralize # => "posts"
# "octopus".pluralize # => "octopi"
# "sheep".pluralize # => "sheep"
# "words".pluralize # => "words"
# "the blue mailman".pluralize # => "the blue mailmen"
# "CamelOctopus".pluralize # => "CamelOctopi"
#
def pluralize
ActiveSupport::Inflector.pluralize(self)
end
##
# Returns the singular form of the word in the string.
#
# "posts".singularize # => "post"
# "octopi".singularize # => "octopus"
# "sheep".singularize # => "sheep"
# "words".singularize # => "word"
# "the blue mailmen".singularize # => "the blue mailman"
# "CamelOctopi".singularize # => "CamelOctopus"
#
def singularize
ActiveSupport::Inflector.singularize(self)
end
##
# +constantize+ tries to find a declared constant with the name specified
# in the string. It raises a NameError when the name is not in CamelCase
# or is not initialized.
#
# "Module".constantize # => Module
# "Class".constantize # => Class
#
def constantize
ActiveSupport::Inflector.constantize(self)
end
##
# The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
#
# +underscore+ will also change '::' to '/' to convert namespaces to paths.
#
# "ActiveRecord".underscore # => "active_record"
# "ActiveRecord::Errors".underscore # => active_record/errors
#
def underscore
ActiveSupport::Inflector.underscore(self)
end
##
# By default, +camelize+ converts strings to UpperCamelCase. If the argument to camelize
# is set to <tt>:lower</tt> then camelize produces lowerCamelCase.
#
# +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
#
# "active_record".camelize # => "ActiveRecord"
# "active_record".camelize(:lower) # => "activeRecord"
# "active_record/errors".camelize # => "ActiveRecord::Errors"
# "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
#
def camelize(first_letter = :upper)
case first_letter
when :upper then ActiveSupport::Inflector.camelize(self, true)
when :lower then ActiveSupport::Inflector.camelize(self, false)
end
end
alias_method :camelcase, :camelize
##
# Create a class name from a plural table name like Rails does for table names to models.
# Note that this returns a string and not a class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
# "egg_and_hams".classify # => "EggAndHam"
# "posts".classify # => "Post"
#
# Singular names are not handled correctly.
#
# "business".classify # => "Busines"
#
def classify
ActiveSupport::Inflector.classify(self)
end
end
module ObjectSpace
class << self
# Returns all the classes in the object space.
def classes
ObjectSpace.each_object(Module).select do |klass|
# Why this? Ruby when you remove a constant don't clean it from
# rb_tables, this mean that here we can found classes that was
# removed.
klass.name rescue false
end
end
end
end
##
# FileSet helper method for iterating and interacting with files inside a directory
#
module FileSet
extend self
##
# Iterates over every file in the glob pattern and yields to a block
# Returns the list of files matching the glob pattern
# FileSet.glob('padrino-core/application/*.rb', __FILE__) { |file| load file }
#
def glob(glob_pattern, file_path=nil, &block)
glob_pattern = File.join(File.dirname(file_path), glob_pattern) if file_path
file_list = Dir.glob(glob_pattern).sort
file_list.each { |file| block.call(file) }
file_list
end
##
# Requires each file matched in the glob pattern into the application
# FileSet.glob_require('padrino-core/application/*.rb', __FILE__)
#
def glob_require(glob_pattern, file_path=nil)
glob(glob_pattern, file_path) { |f| require f }
end
end
##
# Removes indentation
# Add colors
#
# @example
# help <<-EOS.undent
# Here my help usage
# sample_code
#
# Fix
# EOS
# 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
}
end
colors.each do |color, value|
define_method(color) do
["\e[", value.to_s, "m", self, "\e[", self.class.colors[:clear], "m"] * ''
end
end
def undent
gsub(/^.{#{slice(/^ +/).size}}/, '')
end
end
##
# Loads our locale configuration files
#
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

View file

@ -0,0 +1,21 @@
module Padrino
##
# This module it's used for bootstrap with padrino rake
# thirdy party tasks, in your gem/plugin/extension you
# need only do this:
#
# @example
# Padrino::Tasks.files << yourtask.rb
# Padrino::Tasks.files.concat(Dir["/path/to/all/my/tasks/*.rb"])
# Padrino::Tasks.files.unshift("yourtask.rb")
#
module Tasks
##
# Returns a list of files to handle with padrino rake
#
def self.files
@files ||= []
end
end # Tasks
end # Padrino

View file

@ -0,0 +1,20 @@
#
# Manages current Padrino version for use in gem generation.
#
# We put this in a separate file so you can get padrino version
# without include full padrino core.
#
module Padrino
# The version constant for the current version of Padrino.
VERSION = '0.10.5' unless defined?(Padrino::VERSION)
#
# The current Padrino version.
#
# @return [String]
# The version number.
#
def self.version
VERSION
end
end # Padrino

View file

@ -0,0 +1,38 @@
#!/usr/bin/env gem build
# encoding: utf-8
require File.expand_path("../lib/padrino-core/version.rb", __FILE__)
Gem::Specification.new do |s|
s.name = "padrino-core"
s.rubyforge_project = "padrino-core"
s.authors = ["Padrino Team", "Nathan Esquenazi", "Davide D'Agostino", "Arthur Chiu"]
s.email = "padrinorb@gmail.com"
s.summary = "The required Padrino core gem"
s.homepage = "http://www.padrinorb.com"
s.description = "The Padrino core gem required for use of this framework"
s.required_rubygems_version = ">= 1.3.6"
s.version = Padrino.version
s.date = Time.now.strftime("%Y-%m-%d")
s.extra_rdoc_files = Dir["*.rdoc"]
s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
s.require_paths = ["lib"]
s.rdoc_options = ["--charset=UTF-8"]
# TODO remove after a couple versions
# s.post_install_message = "\e[32m" + ("*" * 20)
# s.post_install_message << "\n UPGRADE NOTES\n\n\e[31m When upgrading, please 'enable :sessions' for each application"
# s.post_install_message << " as shown here:\e[0m http://bit.ly/kODKMx\n"
# s.post_install_message << "\e[31m When upgrading, please 'register Padrino::Rendering' for each application"
# s.post_install_message << " as shown here:\e[0m https://gist.github.com/1d36a35794dbbd664ea4"
# s.post_install_message << "\n\e[32m" + ("*" * 20) + "\n\e[0m"
s.add_dependency("tilt", "~> 1.3.0")
s.add_dependency("sinatra", "~> 1.3.1")
s.add_dependency("http_router", "~> 0.10.2")
s.add_dependency("thor", "~> 0.14.3")
s.add_dependency("activesupport", "~> 3.1.0")
end

View file

@ -0,0 +1,6 @@
---
:test: bacon
:mock: mocha
:orm: datamapper
:renderer: erb
:script: jquery

View file

@ -0,0 +1,7 @@
.DS_Store
log/**/*
tmp/**/*
vendor/gems/gems
vendor/gems/specifications
vendor/gems/doc
vendor/gems/environment.rb

View file

@ -0,0 +1,32 @@
PADRINO_ROOT = File.dirname(__FILE__) unless defined? PADRINO_ROOT
module LibDemo
def self.give_me_a_random
@rand ||= rand(100)
end
end
class Complex1Demo < Padrino::Application
set :reload, true
get("/old"){ "Old Sinatra Way" }
end
class Complex2Demo < Padrino::Application
set :reload, true
get("/old"){ "Old Sinatra Way" }
controllers :var do
get(:destroy){ params.inspect }
end
get("/"){ "The magick number is: 12!" } # Change only the number!!!
end
Complex1Demo.controllers do
get("/"){ "Given random #{LibDemo.give_me_a_random}" }
end
Complex2Demo.controllers do
end
Padrino.load!

View file

@ -0,0 +1,33 @@
PADRINO_ROOT = File.dirname(__FILE__) unless defined? PADRINO_ROOT
# Remove this comment if you want do some like this: ruby PADRINO_ENV=test app.rb
#
# require 'rubygems'
# require 'padrino-core'
#
class SimpleDemo < Padrino::Application
set :reload, true
before { true }
after { true }
error(404) { "404" }
end
SimpleDemo.controllers do
get "/" do
'The magick number is: 2767356926488785838763860464013972991031534522105386787489885890443740254365!' # Change only the number!!!
end
get "/rand" do
rand(2 ** 256).to_s
end
end
## If you want use this as a standalone app uncomment:
#
# Padrino.mount("SimpleDemo").to("/")
# Padrino.run! unless Padrino.loaded? # If you enable reloader prevent to re-run the app
#
# Then run it from your console: ruby -I"lib" test/fixtures/apps/simple.rb
#
Padrino.load!

View file

@ -0,0 +1,9 @@
# This file will be safe loaded three times.
# The first one fail because B and C constant are not defined
# The second one file because B requires C constant so will not be loaded
# The third one B and C are defined
# But here we need some of b.rb
A_result = [B, C]
A = "A"

View file

@ -0,0 +1,4 @@
# But here we need some of c.rb and a.rb
B_result = C
B = "B"

View file

@ -0,0 +1 @@
C = "C"

View file

@ -0,0 +1,13 @@
class E
def self.fields
@fields ||= []
end
def self.inherited(subclass)
subclass.fields.replace fields.dup
end
G
fields << "name"
end

View file

@ -0,0 +1,2 @@
class F < E
end

View file

@ -0,0 +1,2 @@
class G
end

View file

@ -0,0 +1,4 @@
D = 0 unless defined?(D)
D += 1
raise "SomeThing"

View file

@ -0,0 +1,81 @@
ENV['PADRINO_ENV'] = 'test'
PADRINO_ROOT = File.dirname(__FILE__) unless defined?(PADRINO_ROOT)
require File.expand_path('../../../load_paths', __FILE__)
require File.expand_path('../mini_shoulda', __FILE__)
require 'padrino-core'
require 'json'
require 'rack/test'
require 'rack'
# Rubies < 1.9 don't handle hashes in the properly order so to prevent
# this issue for now we remove extra values from mimetypes.
Rack::Mime::MIME_TYPES.delete(".xsl") # In this way application/xml respond only to .xml
class Sinatra::Base
# Allow assertions in request context
include MiniTest::Assertions
end
class MiniTest::Spec
include Rack::Test::Methods
# Sets up a Sinatra::Base subclass defined with the block
# given. Used in setup or individual spec methods to establish
# the application.
def mock_app(base=Padrino::Application, &block)
@app = Sinatra.new(base, &block)
end
def app
Rack::Lint.new(@app)
end
# Asserts that a file matches the pattern
def assert_match_in_file(pattern, file)
assert File.exist?(file), "File '#{file}' does not exist!"
assert_match pattern, File.read(file)
end
# Delegate other missing methods to response.
def method_missing(name, *args, &block)
if response && response.respond_to?(name)
response.send(name, *args, &block)
else
super(name, *args, &block)
end
end
alias :response :last_response
def create_template(name, content, options={})
FileUtils.mkdir_p(File.dirname(__FILE__) + "/views")
FileUtils.mkdir_p(File.dirname(__FILE__) + "/views/layouts")
path = "/views/#{name}"
path += ".#{options.delete(:locale)}" if options[:locale].present?
path += ".#{options[:format]}" if options[:format].present?
path += ".erb" unless options[:format].to_s =~ /haml|rss|atom/
path += ".builder" if options[:format].to_s =~ /rss|atom/
file = File.dirname(__FILE__) + path
File.open(file, 'w') { |io| io.write content }
file
end
alias :create_view :create_template
alias :create_layout :create_template
def remove_views
FileUtils.rm_rf(File.dirname(__FILE__) + "/views")
end
def with_template(name, content, options={})
# Build a temp layout
template = create_template(name, content, options)
yield
ensure
# Remove temp layout
File.unlink(template) rescue nil
remove_views
end
alias :with_view :with_template
alias :with_layout :with_template
end

View file

@ -0,0 +1,45 @@
gem 'minitest'
require 'minitest/spec'
require 'minitest/autorun'
require 'mocha' # Load mocha after minitest
begin
require 'ruby-debug'
rescue LoadError; end
class MiniTest::Spec
class << self
alias :setup :before unless defined?(Rails)
alias :teardown :after unless defined?(Rails)
alias :should :it
alias :context :describe
def should_eventually(desc)
it("should eventually #{desc}") { skip("Should eventually #{desc}") }
end
end
alias :assert_no_match :refute_match
alias :assert_not_nil :refute_nil
alias :assert_not_equal :refute_equal
end
class ColoredIO
def initialize(io)
@io = io
end
def print(o)
case o
when "." then @io.send(:print, o.green)
when "E" then @io.send(:print, o.red)
when "F" then @io.send(:print, o.yellow)
when "S" then @io.send(:print, o.magenta)
else @io.send(:print, o)
end
end
def puts(*o)
super
end
end
MiniTest::Unit.output = ColoredIO.new(MiniTest::Unit.output)

View file

@ -0,0 +1,108 @@
require File.expand_path(File.dirname(__FILE__) + '/helper')
class PadrinoPristine < Padrino::Application; end
class PadrinoTestApp < Padrino::Application; end
class PadrinoTestApp2 < Padrino::Application; end
describe "Application" do
def setup
Padrino.clear!
end
def teardown
remove_views
end
context 'for application functionality' do
should 'check default options' do
assert File.identical?(__FILE__, PadrinoPristine.app_file)
assert_equal :padrino_pristine, PadrinoPristine.app_name
assert_equal :test, PadrinoPristine.environment
assert_equal Padrino.root("views"), PadrinoPristine.views
assert PadrinoPristine.raise_errors
assert !PadrinoPristine.logging
assert !PadrinoPristine.sessions
assert !PadrinoPristine.dump_errors
assert !PadrinoPristine.show_exceptions
assert PadrinoPristine.raise_errors
assert !Padrino.configure_apps
end
should 'check padrino specific options' do
assert !PadrinoPristine.instance_variable_get(:@_configured)
PadrinoPristine.send(:setup_application!)
assert_equal :padrino_pristine, PadrinoPristine.app_name
assert_equal 'StandardFormBuilder', PadrinoPristine.default_builder
assert PadrinoPristine.instance_variable_get(:@_configured)
assert !PadrinoPristine.reload?
assert !PadrinoPristine.flash
end
should 'set global project settings' do
Padrino.configure_apps { enable :sessions; set :foo, "bar" }
PadrinoTestApp.send(:default_configuration!)
PadrinoTestApp2.send(:default_configuration!)
assert PadrinoTestApp.sessions, "should have sessions enabled"
assert_equal "bar", PadrinoTestApp.settings.foo, "should have foo assigned"
assert_equal PadrinoTestApp.session_secret, PadrinoTestApp2.session_secret
end
should "have shared sessions accessible in project" do
Padrino.configure_apps { enable :sessions; set :session_secret, 'secret' }
Padrino.mount("PadrinoTestApp").to("/write")
Padrino.mount("PadrinoTestApp2").to("/read")
PadrinoTestApp.tap { |app| app.send(:default_configuration!)
app.get("/") { session[:foo] = "shared" } }
PadrinoTestApp2.tap { |app| app.send(:default_configuration!)
app.get("/") { session[:foo] } }
browser = Rack::Test::Session.new(Rack::MockSession.new(Padrino.application))
browser.get '/write'
browser.get '/read'
assert_equal 'shared', browser.last_response.body
end
# compare to: test_routing: allow global provides
should "set content_type to :html if none can be determined" do
mock_app do
provides :xml
get("/foo"){ "Foo in #{content_type}" }
get("/bar"){ "Foo in #{content_type}" }
end
get '/foo', {}, { 'HTTP_ACCEPT' => 'application/xml' }
assert_equal 'Foo in xml', body
get '/foo'
assert_equal 'Foo in xml', body
get '/bar', {}, { 'HTTP_ACCEPT' => 'application/xml' }
assert_equal "Foo in html", body
end # content_type to :html
context "errors" do
should "haven't mapped errors on development" do
mock_app { get('/'){ 'HI' } }
get "/"
assert @app.errors.empty?
end
should "have mapped errors on production" do
mock_app { set :environment, :production; get('/'){ 'HI' } }
get "/"
assert_equal 1, @app.errors.size
end
should "overide errors" do
mock_app do
set :environment, :production
get('/'){ raise }
error(::Exception){ 'custom error' }
end
get "/"
assert_equal 1, @app.errors.size
assert_equal 'custom error', body
end
end
end # application functionality
end

View file

@ -0,0 +1,79 @@
require File.expand_path(File.dirname(__FILE__) + '/helper')
describe "Core" do
def setup
Padrino.clear!
end
context 'for core functionality' do
should 'check some global methods' do
assert_respond_to Padrino, :root
assert_respond_to Padrino, :env
assert_respond_to Padrino, :application
assert_respond_to Padrino, :set_encoding
assert_respond_to Padrino, :load!
assert_respond_to Padrino, :reload!
assert_respond_to Padrino, :version
assert_respond_to Padrino, :bundle
assert_respond_to Padrino, :configure_apps
end
should 'validate global helpers' do
assert_equal :test, Padrino.env
assert_match /\/test/, Padrino.root
assert_equal nil, Padrino.bundle
assert_not_nil Padrino.version
end
should 'set correct utf-8 encoding' do
Padrino.set_encoding
if RUBY_VERSION <'1.9'
assert_equal 'UTF8', $KCODE
else
assert_equal Encoding.default_external, Encoding::UTF_8
assert_equal Encoding.default_internal, Encoding::UTF_8
end
end
should 'have load paths' do
assert_equal [Padrino.root('lib'), Padrino.root('models'), Padrino.root('shared')], Padrino.load_paths
end
should 'raise application error if I instantiate a new padrino application without mounted apps' do
assert_raises(Padrino::ApplicationLoadError) { Padrino.application.new }
end
should "check before/after padrino load hooks" do
Padrino.before_load { @_foo = 1 }
Padrino.after_load { @_foo += 1 }
Padrino.load!
assert_equal 1, Padrino.before_load.size
assert_equal 1, Padrino.after_load.size
assert_equal 2, @_foo
end
should "add middlewares in front if specified" do
test = Class.new {
def initialize(app)
@app = app
end
def call(env)
status, headers, body = @app.call(env)
headers["Middleware-Called"] = "yes"
return status, headers, body
end
}
class Foo < Padrino::Application; end
Padrino.use(test)
Padrino.mount(Foo).to("/")
res = Rack::MockRequest.new(Padrino.application).get("/")
assert_equal "yes", res["Middleware-Called"]
end
end
end

View file

@ -0,0 +1,44 @@
require File.expand_path(File.dirname(__FILE__) + '/helper')
describe "Dependencies" do
context 'when we require a dependency that have another dependency' do
should 'raise an error without reloading it twice' do
capture_io do
assert_raises(RuntimeError) do
Padrino.require_dependencies(
Padrino.root("fixtures/dependencies/a.rb"),
Padrino.root("fixtures/dependencies/b.rb"),
Padrino.root("fixtures/dependencies/c.rb"),
Padrino.root("fixtures/dependencies/d.rb")
)
end
end
assert_equal 1, D
end
should 'resolve dependency problems' do
capture_io do
Padrino.require_dependencies(
Padrino.root("fixtures/dependencies/a.rb"),
Padrino.root("fixtures/dependencies/b.rb"),
Padrino.root("fixtures/dependencies/c.rb")
)
end
assert_equal ["B", "C"], A_result
assert_equal "C", B_result
end
should 'remove partially loaded constants' do
capture_io do
Padrino.require_dependencies(
Padrino.root("fixtures/dependencies/circular/e.rb"),
Padrino.root("fixtures/dependencies/circular/f.rb"),
Padrino.root("fixtures/dependencies/circular/g.rb")
)
end
assert_equal ["name"], F.fields
end
end
end

View file

@ -0,0 +1,278 @@
require File.expand_path(File.dirname(__FILE__) + '/helper')
describe "Filters" do
should "filters by accept header" do
mock_app do
get '/foo', :provides => [:xml, :js] do
request.env['HTTP_ACCEPT']
end
end
get '/foo', {}, { 'HTTP_ACCEPT' => 'application/xml' }
assert ok?
assert_equal 'application/xml', body
assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type']
get '/foo.xml'
assert ok?
assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type']
get '/foo', {}, { 'HTTP_ACCEPT' => 'application/javascript' }
assert ok?
assert_equal 'application/javascript', body
assert_equal 'application/javascript;charset=utf-8', response.headers['Content-Type']
get '/foo.js'
assert ok?
assert_equal 'application/javascript;charset=utf-8', response.headers['Content-Type']
get '/foo', {}, { "HTTP_ACCEPT" => 'text/html' }
assert_equal 406, status
end
should "allow passing & halting in before filters" do
mock_app do
controller do
before { env['QUERY_STRING'] == 'secret' or pass }
get :index do
"secret index"
end
end
controller do
before { env['QUERY_STRING'] == 'halt' and halt 401, 'go away!' }
get :index do
"index"
end
end
end
get "/?secret"
assert_equal "secret index", body
get "/?halt"
assert_equal "go away!", body
assert_equal 401, status
get "/"
assert_equal "index", body
end
should 'scope filters in the given controller' do
mock_app do
before { @global = 'global' }
after { @global = nil }
controller :foo do
before { @foo = :foo }
after { @foo = nil }
get("/") { [@foo, @bar, @global].compact.join(" ") }
end
get("/") { [@foo, @bar, @global].compact.join(" ") }
controller :bar do
before { @bar = :bar }
after { @bar = nil }
get("/") { [@foo, @bar, @global].compact.join(" ") }
end
end
get "/bar"
assert_equal "bar global", body
get "/foo"
assert_equal "foo global", body
get "/"
assert_equal "global", body
end
should 'be able to access params in a before filter' do
username_from_before_filter = nil
mock_app do
before do
username_from_before_filter = params[:username]
end
get :users, :with => :username do
end
end
get '/users/josh'
assert_equal 'josh', username_from_before_filter
end
should "be able to access params normally when a before filter is specified" do
mock_app do
before { }
get :index do
params.inspect
end
end
get '/?test=what'
assert_equal '{"test"=>"what"}', body
end
should "be able to filter based on a path" do
mock_app do
before('/') { @test = "#{@test}before"}
get :index do
@test
end
get :main do
@test
end
end
get '/'
assert_equal 'before', body
get '/main'
assert_equal '', body
end
should "be able to filter based on a symbol" do
mock_app do
before(:index) { @test = 'before'}
get :index do
@test
end
get :main do
@test
end
end
get '/'
assert_equal 'before', body
get '/main'
assert_equal '', body
end
should "be able to filter based on a symbol for a controller" do
mock_app do
controller :foo do
before(:test) { @test = 'foo'}
get :test do
@test.to_s + " response"
end
end
controller :bar do
before(:test) { @test = 'bar'}
get :test do
@test.to_s + " response"
end
end
end
get '/foo/test'
assert_equal 'foo response', body
get '/bar/test'
assert_equal 'bar response', body
end
should "be able to filter based on a symbol or path" do
mock_app do
before(:index, '/main') { @test = 'before'}
get :index do
@test
end
get :main do
@test
end
end
get '/'
assert_equal 'before', body
get '/main'
assert_equal 'before', body
end
should "be able to filter based on a symbol or regexp" do
mock_app do
before(:index, /main/) { @test = 'before'}
get :index do
@test
end
get :main do
@test
end
get :profile do
@test
end
end
get '/'
assert_equal 'before', body
get '/main'
assert_equal 'before', body
get '/profile'
assert_equal '', body
end
should "be able to filter excluding based on a symbol" do
mock_app do
before(:except => :index) { @test = 'before'}
get :index do
@test
end
get :main do
@test
end
end
get '/'
assert_equal '', body
get '/main'
assert_equal 'before', body
end
should "be able to filter based on a request param" do
mock_app do
before(:agent => /IE/) { @test = 'before'}
get :index do
@test
end
end
get '/'
assert_equal '', body
get "/", {}, {'HTTP_USER_AGENT' => 'This is IE'}
assert_equal 'before', body
end
should "be able to filter based on a symbol or path in multiple controller" do
mock_app do
controllers :foo do
before(:index, '/foo/main') { @test = 'before' }
get :index do
@test
end
get :main do
@test
end
end
controllers :bar do
before(:index, '/bar/main') { @test = 'also before' }
get :index do
@test
end
get :main do
@test
end
end
end
get '/foo'
assert_equal 'before', body
get '/bar'
assert_equal 'also before', body
get '/foo/main'
assert_equal 'before', body
get '/bar/main'
assert_equal 'also before', body
end
should "call before filters even if there was no match" do
test = nil
mock_app do
before(:index, '/foo') { test = 'before' }
get :index do
''
end
end
get '/foo'
assert_equal 'before', test
end
end

View file

@ -0,0 +1,21 @@
require File.expand_path(File.dirname(__FILE__) + '/helper')
describe "Locales" do
Dir[File.expand_path("../../lib/padrino-core/locale/*.yml", __FILE__)].each do |file|
base_original = YAML.load_file(file)
name = File.basename(file, '.yml')
should "should have correct locale for #{name}" do
base = base_original[name]['date']['formats']
assert base['default'].present?
assert base['short'].present?
assert base['long'].present?
assert base['only_day'].present?
base = base_original[name]['date']
assert base['day_names'].present?
assert base['abbr_day_names'].present?
assert base['month_names'].present?
assert base['abbr_month_names'].present?
assert base['order'].present?
end
end
end

View file

@ -0,0 +1,100 @@
require File.expand_path(File.dirname(__FILE__) + '/helper')
describe "PadrinoLogger" do
def setup
Padrino::Logger::Config[:test][:stream] = :null # The default
Padrino::Logger.setup!
end
def setup_logger(options={})
@log = StringIO.new
@logger = Padrino::Logger.new(options.merge(:stream => @log))
end
context 'for logger functionality' do
context 'check stream config' do
should 'use stdout if stream is nil' do
Padrino::Logger::Config[:test][:stream] = nil
Padrino::Logger.setup!
assert_equal $stdout, Padrino.logger.log
end
should 'use StringIO as default for test' do
assert_instance_of StringIO, Padrino.logger.log
end
should 'use a custom stream' do
my_stream = StringIO.new
Padrino::Logger::Config[:test][:stream] = my_stream
Padrino::Logger.setup!
assert_equal my_stream, Padrino.logger.log
end
end
should 'log something' do
setup_logger(:log_level => :error)
@logger.error "You log this error?"
assert_match(/You log this error?/, @log.string)
@logger.debug "You don't log this error!"
assert_no_match(/You don't log this error!/, @log.string)
@logger << "Yep this can be logged"
assert_match(/Yep this can be logged/, @log.string)
end
should 'respond to #write for Rack::CommonLogger' do
setup_logger(:log_level => :error)
@logger.error "Error message"
assert_match /Error message/, @log.string
@logger << "logged anyways"
assert_match /logged anyways/, @log.string
@logger.write "log via alias"
assert_match /log via alias/, @log.string
end
should 'log an application' do
mock_app do
enable :logging
get("/"){ "Foo" }
end
get "/"
assert_equal "Foo", body
assert_match /GET/, Padrino.logger.log.string
end
should 'log an application\'s status code' do
mock_app do
enable :logging
get("/"){ "Foo" }
end
get "/"
assert_match /\e\[1m200\e\[0m OK/, Padrino.logger.log.string
end
context "static asset logging" do
should 'not log static assets by default' do
mock_app do
enable :logging
get("/images/something.png"){ env["sinatra.static_file"] = '/public/images/something.png'; "Foo" }
end
get "/images/something.png"
assert_equal "Foo", body
assert_match "", Padrino.logger.log.string
end
should 'allow turning on static assets logging' do
Padrino.logger.instance_eval{ @log_static = true }
mock_app do
enable :logging
get("/images/something.png"){ env["sinatra.static_file"] = '/public/images/something.png'; "Foo" }
end
get "/images/something.png"
assert_equal "Foo", body
assert_match /GET/, Padrino.logger.log.string
Padrino.logger.instance_eval{ @log_static = false }
end
end
end
end

View file

@ -0,0 +1,177 @@
require File.expand_path(File.dirname(__FILE__) + '/helper')
describe "Mounter" do
class ::TestApp < Padrino::Application; end
def setup
$VERBOSE, @_verbose_was = nil, $VERBOSE
Padrino.clear!
end
def teardown
$VERBOSE = @_verbose_was
end
context 'for mounter functionality' do
should 'check methods' do
mounter = Padrino::Mounter.new("test_app", :app_file => "/path/to/test.rb")
mounter.to("/test_app")
assert_kind_of Padrino::Mounter, mounter
assert_respond_to Padrino::Mounter, :new
assert_respond_to mounter, :to
assert_respond_to mounter, :map_onto
assert_equal "test_app", mounter.name
assert_equal "TestApp", mounter.app_class
assert_equal "/path/to/test.rb", mounter.app_file
assert_equal "/test_app", mounter.uri_root
assert_equal File.dirname(mounter.app_file), mounter.app_root
end
should 'check locate_app_file with __FILE__' do
mounter = Padrino::Mounter.new("test_app", :app_file => __FILE__)
mounter.to("/test_app")
assert_equal "test_app", mounter.name
assert_equal "TestApp", mounter.app_class
assert_equal __FILE__, mounter.app_file
assert_equal "/test_app", mounter.uri_root
assert_equal File.dirname(mounter.app_file), mounter.app_root
end
should 'mount an app' do
class ::AnApp < Padrino::Application; end
Padrino.mount("an_app").to("/")
assert_equal AnApp, Padrino.mounted_apps.first.app_obj
assert_equal ["an_app"], Padrino.mounted_apps.map(&:name)
end
should 'correctly mount an app in a namespace' do
module ::SomeNamespace
class AnApp < Padrino::Application; end
end
Padrino.mount("some_namespace/an_app").to("/")
assert_equal SomeNamespace::AnApp, Padrino.mounted_apps.first.app_obj
assert_equal ["some_namespace/an_app"], Padrino.mounted_apps.map(&:name)
end
should 'mount a primary app to root uri' do
mounter = Padrino.mount("test_app", :app_file => __FILE__).to("/")
assert_equal "test_app", mounter.name
assert_equal "TestApp", mounter.app_class
assert_equal TestApp, mounter.app_obj
assert_equal __FILE__, mounter.app_file
assert_equal "/", mounter.uri_root
assert_equal File.dirname(mounter.app_file), mounter.app_root
end
should 'mount a primary app to sub_uri' do
mounter = Padrino.mount("test_app", :app_file => __FILE__).to('/me')
assert_equal "test_app", mounter.name
assert_equal "TestApp", mounter.app_class
assert_equal TestApp, mounter.app_obj
assert_equal __FILE__, mounter.app_file
assert_equal "/me", mounter.uri_root
assert_equal File.dirname(mounter.app_file), mounter.app_root
end
should "raise error when app has no located file" do
# TODO enabling this screws minitest
# assert_raises(Padrino::Mounter::MounterException) { Padrino.mount("tester_app").to('/test') }
assert_equal 0, Padrino.mounted_apps.size
end
should "raise error when app has no located object" do
assert_raises(Padrino::Mounter::MounterException) { Padrino.mount("tester_app", :app_file => "/path/to/file.rb").to('/test') }
assert_equal 0, Padrino.mounted_apps.size
end
should 'mount multiple apps' do
class ::OneApp < Padrino::Application; end
class ::TwoApp < Padrino::Application; end
Padrino.mount("one_app").to("/one_app")
Padrino.mount("two_app").to("/two_app")
# And testing no duplicates
Padrino.mount("one_app").to("/one_app")
Padrino.mount("two_app").to("/two_app")
assert_equal OneApp, Padrino.mounted_apps[0].app_obj
assert_equal TwoApp, Padrino.mounted_apps[1].app_obj
assert_equal 2, Padrino.mounted_apps.size, "should not mount duplicate apps"
assert_equal ["one_app", "two_app"], Padrino.mounted_apps.map(&:name)
end
should 'change mounted_root' do
Padrino.mounted_root = "fixtures"
assert_equal Padrino.root("fixtures", "test", "app.rb"), Padrino.mounted_root("test", "app.rb")
Padrino.mounted_root = "apps"
assert_equal Padrino.root("apps", "test", "app.rb"), Padrino.mounted_root("test", "app.rb")
Padrino.mounted_root = nil
assert_equal Padrino.root("test", "app.rb"), Padrino.mounted_root("test", "app.rb")
end
should "be able to access routes data for mounted apps" do
class ::OneApp < Padrino::Application
get("/test") { "test" }
get(:index, :provides => [:js, :json]) { "index" }
controllers :posts do
get(:index) { "index" }
get(:new, :provides => :js) { "new" }
get(:show, :provides => [:js, :html], :with => :id) { "show" }
post(:create, :provides => :js, :with => :id) { "create" }
end
end
class ::TwoApp < Padrino::Application
controllers :users do
get(:index) { "users" }
get(:new) { "users new" }
post(:create) { "users create" }
put(:update) { "users update" }
delete(:destroy) { "users delete" }
end
end
Padrino.mount("one_app").to("/")
Padrino.mount("two_app").to("/two_app")
assert_equal 11, Padrino.mounted_apps[0].routes.size
assert_equal 7, Padrino.mounted_apps[1].routes.size
assert_equal 5, Padrino.mounted_apps[0].named_routes.size
assert_equal 5, Padrino.mounted_apps[1].named_routes.size
first_route = Padrino.mounted_apps[0].named_routes[3]
assert_equal "posts_show", first_route.identifier.to_s
assert_equal "(:posts, :show)", first_route.name
assert_equal "GET", first_route.verb
assert_equal "/posts/show/:id(.:format)", first_route.path
another_route = Padrino.mounted_apps[1].named_routes[2]
assert_equal "users_create", another_route.identifier.to_s
assert_equal "(:users, :create)", another_route.name
assert_equal "POST", another_route.verb
assert_equal "/two_app/users/create", another_route.path
end
should 'correctly instantiate a new padrino application' do
mock_app do
get("/demo_1"){ "Im Demo 1" }
get("/demo_2"){ "Im Demo 2" }
end
get '/demo_1'
assert_equal "Im Demo 1", response.body
get '/demo_2'
assert_equal "Im Demo 2", response.body
end
should "not clobber the public setting when mounting an app" do
class ::PublicApp < Padrino::Application
set :root, "/root"
set :public_folder, File.expand_path(File.dirname(__FILE__))
end
Padrino.mount("public_app").to("/public")
res = Rack::MockRequest.new(Padrino.application).get("/public/test_mounter.rb")
assert res.ok?
assert_equal File.read(__FILE__), res.body
end
end
end

View file

@ -0,0 +1,75 @@
require File.expand_path(File.dirname(__FILE__) + '/helper')
require File.expand_path(File.dirname(__FILE__) + '/fixtures/apps/complex')
describe "ComplexReloader" do
context 'for complex reload functionality' do
setup do
Padrino.clear!
Padrino.mount("complex_1_demo").to("/complex_1_demo")
Padrino.mount("complex_2_demo").to("/complex_2_demo")
end
should 'correctly instantiate Complex(1-2)Demo fixture' do
assert_equal ["/complex_1_demo", "/complex_2_demo"], Padrino.mounted_apps.map(&:uri_root)
assert_equal ["complex_1_demo", "complex_2_demo"], Padrino.mounted_apps.map(&:name)
assert Complex1Demo.reload?
assert Complex2Demo.reload?
assert_match %r{fixtures/apps/complex.rb}, Complex1Demo.app_file
assert_match %r{fixtures/apps/complex.rb}, Complex2Demo.app_file
end
should 'correctly reload Complex(1-2)Demo fixture' do
assert_match %r{fixtures/apps/complex.rb}, Complex1Demo.app_file
@app = Padrino.application
get "/"
assert_equal 404, status
get "/complex_1_demo"
assert_equal "Given random #{LibDemo.give_me_a_random}", body
get "/complex_2_demo"
assert_equal 200, status
get "/complex_1_demo/old"
assert_equal 200, status
get "/complex_2_demo/old"
assert_equal 200, status
get "/complex_2_demo/var/destroy"
assert_equal '{}', body
new_phrase = "The magick number is: #{rand(2**255)}!"
buffer = File.read(Complex1Demo.app_file)
new_buffer = buffer.gsub(/The magick number is: \d+!/, new_phrase)
new_buffer.gsub!(/get\(:destroy\)/, 'get(:destroy, :with => :id)')
begin
File.open(Complex1Demo.app_file, "w") { |f| f.write(new_buffer) }
sleep 1.2 # We need at least a cooldown of 1 sec.
get "/complex_2_demo"
assert_equal new_phrase, body
# Re-Check that we didn't forget any route
get "/complex_1_demo"
assert_equal "Given random #{LibDemo.give_me_a_random}", body
get "/complex_2_demo"
assert_equal 200, status
get "/complex_1_demo/old"
assert_equal 200, status
get "/complex_2_demo/old"
assert_equal 200, status
get "/complex_2_demo/var/destroy/variable"
assert_equal '{:id=>"variable"}', body
ensure
# Now we need to prevent to commit a new changed file so we revert it
File.open(Complex1Demo.app_file, "w") { |f| f.write(buffer) }
end
end
end
end

View file

@ -0,0 +1,98 @@
require File.expand_path(File.dirname(__FILE__) + '/helper')
require File.expand_path(File.dirname(__FILE__) + '/fixtures/apps/simple')
describe "SimpleReloader" do
context 'for simple reset functionality' do
should 'reset routes' do
mock_app do
(1..10).each do |i|
get("/#{i}") { "Foo #{i}" }
end
end
(1..10).each do |i|
get "/#{i}"
assert_equal "Foo #{i}", body
end
@app.reset_routes!
(1..10).each do |i|
get "/#{i}"
assert_equal 404, status
end
end
should 'keep sinatra routes on development' do
mock_app do
set :environment, :development
get("/"){ "ok" }
end
assert_equal :development, @app.environment
get "/"
assert_equal 200, status
get "/__sinatra__/404.png"
assert_equal 200, status
assert_match /image\/png/, response["Content-Type"]
@app.reset_routes!
get "/"
assert_equal 404, status
get "/__sinatra__/404.png"
assert_equal 200, status
assert_match /image\/png/, response["Content-Type"]
end
end
context 'for simple reload functionality' do
should 'correctly instantiate SimpleDemo fixture' do
Padrino.clear!
Padrino.mount("simple_demo").to("/")
assert_equal ["simple_demo"], Padrino.mounted_apps.map(&:name)
assert SimpleDemo.reload?
assert_match %r{fixtures/apps/simple.rb}, SimpleDemo.app_file
end
should_eventually 'correctly reload SimpleDemo fixture' do
# TODO fix this test
@app = SimpleDemo
get "/"
assert ok?
new_phrase = "The magick number is: #{rand(2**255)}!"
buffer = File.read(SimpleDemo.app_file)
new_buffer = buffer.gsub(/The magick number is: \d+!/, new_phrase)
File.open(SimpleDemo.app_file, "w") { |f| f.write(new_buffer) }
sleep 2 # We need at least a cooldown of 1 sec.
get "/"
assert_equal new_phrase, body
# Now we need to prevent to commit a new changed file so we revert it
File.open(SimpleDemo.app_file, "w") { |f| f.write(buffer) }
Padrino.reload!
end
should 'correctly reset SimpleDemo fixture' do
@app = SimpleDemo
@app.reload!
get "/rand"
assert ok?
last_body = body
assert_equal 2, @app.filters[:before].size # one is ours the other is default_filter for content type
assert_equal 1, @app.errors.size
assert_equal 1, @app.filters[:after].size
assert_equal 0, @app.middleware.size
assert_equal 4, @app.routes.size # GET+HEAD of "/" + GET+HEAD of "/rand" = 4
assert_equal 2, @app.extensions.size # [Padrino::Routing, Padrino::Rendering]
assert_equal 0, @app.templates.size
@app.reload!
get "/rand"
assert_not_equal last_body, body
assert_equal 2, @app.filters[:before].size # one is ours the other is default_filter for content type
assert_equal 1, @app.errors.size
assert_equal 1, @app.filters[:after].size
assert_equal 0, @app.middleware.size
assert_equal 4, @app.routes.size # GET+HEAD of "/" = 2
assert_equal 2, @app.extensions.size # [Padrino::Routing, Padrino::Rendering]
assert_equal 0, @app.templates.size
end
end
end

View file

@ -0,0 +1,461 @@
require File.expand_path(File.dirname(__FILE__) + '/helper')
require 'i18n'
describe "Rendering" do
def setup
Padrino::Application.send(:register, Padrino::Rendering)
Padrino::Rendering::DEFAULT_RENDERING_OPTIONS[:strict_format] = false
end
def teardown
remove_views
end
context 'for application layout functionality' do
should 'get no layout' do
mock_app do
get("/"){ "no layout" }
end
get "/"
assert_equal "no layout", body
end
should 'be compatible with sinatra layout' do
mock_app do
layout do
"this is a <%= yield %>"
end
get("/"){ render :erb, "sinatra layout" }
end
get "/"
assert_equal "this is a sinatra layout", body
end
should 'use rails way layout' do
with_layout :application, "this is a <%= yield %>" do
mock_app do
get("/"){ render :erb, "rails way layout" }
end
get "/"
assert_equal "this is a rails way layout", body
end
end
should 'use rails way for a custom layout' do
with_layout "layouts/custom", "this is a <%= yield %>" do
mock_app do
layout :custom
get("/"){ render :erb, "rails way custom layout" }
end
get "/"
assert_equal "this is a rails way custom layout", body
end
end
should 'not use layout' do
with_layout :application, "this is a <%= yield %>" do
with_view :index, "index" do
mock_app do
get("/with/layout"){ render :index }
get("/without/layout"){ render :index, :layout => false }
end
get "/with/layout"
assert_equal "this is a index", body
get "/without/layout"
assert_equal "index", body
end
end
end
should 'not use layout with js format' do
create_layout :application, "this is an <%= yield %>"
create_view :foo, "erb file"
create_view :foo, "js file", :format => :js
mock_app do
get('/layout_test', :provides => [:html, :js]){ render :foo }
end
get "/layout_test"
assert_equal "this is an erb file", body
get "/layout_test.js"
assert_equal "js file", body
end
should 'use correct layout for each format' do
create_layout :application, "this is an <%= yield %>"
create_layout :application, "document start <%= yield %> end", :format => :xml
create_view :foo, "erb file"
create_view :foo, "xml file", :format => :xml
mock_app do
get('/layout_test', :provides => [:html, :xml]){ render :foo }
end
get "/layout_test"
assert_equal "this is an erb file", body
get "/layout_test.xml"
assert_equal "document start xml file end", body
end
should 'by default use html file when no other is given' do
create_layout :baz, "html file", :format => :html
mock_app do
get('/content_type_test', :provides => [:html, :xml]) { render :baz }
end
get "/content_type_test"
assert_equal "html file", body
get "/content_type_test.html"
assert_equal "html file", body
get "/content_type_test.xml"
assert_equal "html file", body
end
should 'not use html file when DEFAULT_RENDERING_OPTIONS[:strict_format] == true' do
create_layout :foo, "html file", :format => :html
mock_app do
get('/default_rendering_test', :provides => [:html, :xml]) { render :foo }
end
@save = Padrino::Rendering::DEFAULT_RENDERING_OPTIONS
Padrino::Rendering::DEFAULT_RENDERING_OPTIONS[:strict_format] = true
get "/default_rendering_test"
assert_equal "html file", body
assert_raises Padrino::Rendering::TemplateNotFound do
get "/default_rendering_test.xml"
end
Padrino::Rendering::DEFAULT_RENDERING_OPTIONS.merge!(@save)
end
should 'use correct layout with each controller' do
create_layout :foo, "foo layout at <%= yield %>"
create_layout :bar, "bar layout at <%= yield %>"
create_layout :application, "default layout at <%= yield %>"
mock_app do
get("/"){ render :erb, "application" }
controller :foo do
layout :foo
get("/"){ render :erb, "foo" }
end
controller :bar do
layout :bar
get("/"){ render :erb, "bar" }
end
controller :none do
get("/") { render :erb, "none" }
get("/with_foo_layout") { render :erb, "none with layout", :layout => :foo }
end
end
get "/foo"
assert_equal "foo layout at foo", body
get "/bar"
assert_equal "bar layout at bar", body
get "/none"
assert_equal "default layout at none", body
get "/none/with_foo_layout"
assert_equal "foo layout at none with layout", body
get "/"
assert_equal "default layout at application", body
end
end
should 'solve layout in layouts paths' do
create_layout :foo, "foo layout <%= yield %>"
create_layout :"layouts/bar", "bar layout <%= yield %>"
mock_app do
get("/") { render :erb, "none" }
get("/foo") { render :erb, "foo", :layout => :foo }
get("/bar") { render :erb, "bar", :layout => :bar }
end
get "/"
assert_equal "none", body
get "/foo"
assert_equal "foo layout foo", body
get "/bar"
assert_equal "bar layout bar", body
end
should 'render correctly if layout was not found or not exist' do
create_layout :application, "application layout for <%= yield %>"
create_view :foo, "index", :format => :html
create_view :foo, "xml.rss", :format => :rss
mock_app do
get("/foo", :provides => [:html, :rss]) { render('foo') }
get("/baz", :provides => :js) { render(:erb, 'baz') }
get("/bar") { render :haml, "haml" }
end
get "/foo"
assert_equal "application layout for index", body
get "/foo.rss"
assert_equal "<rss/>", body.chomp
get "/baz.js"
assert_equal "baz", body
get "/bar"
assert_equal "haml", body.chomp
end
context 'for application render functionality' do
should "work properly with logging and missing layout" do
create_view :index, "<%= foo %>"
mock_app do
enable :logging
get("/") { render "index", { :layout => true }, { :foo => "bar" } }
end
get "/"
assert_equal "bar", body
end
should "work properly with logging and layout" do
create_layout :application, "layout <%= yield %>"
create_view :index, "<%= foo %>"
mock_app do
enable :logging
get("/") { render "index", { :layout => true }, { :foo => "bar" } }
end
get "/"
assert_equal "layout bar", body
end
should 'be compatible with sinatra render' do
mock_app do
get("/"){ render :erb, "<%= 1+2 %>" }
end
get "/"
assert_equal "3", body
end
should "support passing locals into render" do
create_layout :application, "layout <%= yield %>"
create_view :index, "<%= foo %>"
mock_app do
get("/") { render "index", { :layout => true }, { :foo => "bar" } }
end
get "/"
assert_equal "layout bar", body
end
should "support passing locals into sinatra render" do
create_layout :application, "layout <%= yield %>"
create_view :index, "<%= foo %>"
mock_app do
get("/") { render :erb, :index, { :layout => true }, { :foo => "bar" } }
end
get "/"
assert_equal "layout bar", body
end
should "support passing locals into special nil engine render" do
create_layout :application, "layout <%= yield %>"
create_view :index, "<%= foo %>"
mock_app do
get("/") { render nil, :index, { :layout => true }, { :foo => "bar" } }
end
get "/"
assert_equal "layout bar", body
end
should 'be compatible with sinatra views' do
with_view :index, "<%= 1+2 %>" do
mock_app do
get("/foo") { render :erb, :index }
get("/bar") { erb :index }
get("/dir") { "3" }
get("/inj") { erb "<%= 2+1 %>" }
get("/rnj") { render :erb, "<%= 2+1 %>" }
end
get "/foo"
assert_equal "3", body
get "/bar"
assert_equal "3", body
get "/dir"
assert_equal "3", body
get "/inj"
assert_equal "3", body
get "/rnj"
assert_equal "3", body
end
end
should 'resolve template engine' do
with_view :index, "<%= 1+2 %>" do
mock_app do
get("/foo") { render :index }
get("/bar") { render "/index" }
end
get "/foo"
assert_equal "3", body
get "/bar"
assert_equal "3", body
end
end
should 'resolve template content type' do
create_view :foo, "Im Js", :format => :js
create_view :foo, "Im Erb"
mock_app do
get("/foo", :provides => :js) { render :foo }
get("/bar.js") { render :foo }
end
get "/foo.js"
assert_equal "Im Js", body
# TODO: implement this!
# get "/bar.js"
# assert_equal "Im Js", body
end
should 'resolve with explicit template format' do
create_view :foo, "Im Js", :format => :js
create_view :foo, "Im Haml", :format => :haml
create_view :foo, "Im Xml", :format => :xml
mock_app do
get("/foo_normal", :provides => :js) { render 'foo' }
get("/foo_haml", :provides => :js) { render 'foo.haml' }
get("/foo_xml", :provides => :js) { render 'foo.xml' }
end
get "/foo_normal.js"
assert_equal "Im Js", body
get "/foo_haml.js"
assert_equal "Im Haml\n", body
get "/foo_xml.js"
assert_equal "Im Xml", body
end
should 'resolve without explict template format' do
create_view :foo, "Im Html"
create_view :foo, "xml.rss", :format => :rss
mock_app do
get(:index, :map => "/", :provides => [:html, :rss]){ render 'foo' }
end
get "/", {}, { 'HTTP_ACCEPT' => 'text/html;q=0.9' }
assert_equal "Im Html", body
get ".rss"
assert_equal "<rss/>\n", body
end
should "ignore files ending in tilde and not render them" do
create_view :foo, "Im Wrong", :format => 'haml~'
create_view :foo, "Im Haml", :format => :haml
create_view :bar, "Im Haml backup", :format => 'haml~'
mock_app do
get('/foo') { render 'foo' }
get('/bar') { render 'bar' }
end
get '/foo'
assert_equal "Im Haml\n", body
assert_raises(Padrino::Rendering::TemplateNotFound) { get '/bar' }
end
should 'resolve template locale' do
create_view :foo, "Im English", :locale => :en
create_view :foo, "Im Italian", :locale => :it
mock_app do
get("/foo") { render :foo }
end
I18n.locale = :en
get "/foo"
assert_equal "Im English", body
I18n.locale = :it
get "/foo"
assert_equal "Im Italian", body
end
should 'resolve template content_type and locale' do
create_view :foo, "Im Js", :format => :js
create_view :foo, "Im Erb"
create_view :foo, "Im English Erb", :locale => :en
create_view :foo, "Im Italian Erb", :locale => :it
create_view :foo, "Im English Js", :format => :js, :locale => :en
create_view :foo, "Im Italian Js", :format => :js, :locale => :it
mock_app do
get("/foo", :provides => [:html, :js]) { render :foo }
end
I18n.locale = :none
get "/foo.js"
assert_equal "Im Js", body
get "/foo"
assert_equal "Im Erb", body
I18n.locale = :en
get "/foo"
assert_equal "Im English Erb", body
I18n.locale = :it
get "/foo"
assert_equal "Im Italian Erb", body
I18n.locale = :en
get "/foo.js"
assert_equal "Im English Js", body
I18n.locale = :it
get "/foo.js"
assert_equal "Im Italian Js", body
I18n.locale = :en
get "/foo.pk"
assert_equal 405, status
end
should 'resolve template content_type and locale with layout' do
create_layout :foo, "Hello <%= yield %> in a Js layout", :format => :js
create_layout :foo, "Hello <%= yield %> in a Js-En layout", :format => :js, :locale => :en
create_layout :foo, "Hello <%= yield %> in a Js-It layout", :format => :js, :locale => :it
create_layout :foo, "Hello <%= yield %> in a Erb-En layout", :locale => :en
create_layout :foo, "Hello <%= yield %> in a Erb-It layout", :locale => :it
create_layout :foo, "Hello <%= yield %> in a Erb layout"
create_view :bar, "Im Js", :format => :js
create_view :bar, "Im Erb"
create_view :bar, "Im English Erb", :locale => :en
create_view :bar, "Im Italian Erb", :locale => :it
create_view :bar, "Im English Js", :format => :js, :locale => :en
create_view :bar, "Im Italian Js", :format => :js, :locale => :it
create_view :bar, "Im a json", :format => :json
mock_app do
layout :foo
get("/bar", :provides => [:html, :js, :json]) { render :bar }
end
I18n.locale = :none
get "/bar.js"
assert_equal "Hello Im Js in a Js layout", body
get "/bar"
assert_equal "Hello Im Erb in a Erb layout", body
I18n.locale = :en
get "/bar"
assert_equal "Hello Im English Erb in a Erb-En layout", body
I18n.locale = :it
get "/bar"
assert_equal "Hello Im Italian Erb in a Erb-It layout", body
I18n.locale = :en
get "/bar.js"
assert_equal "Hello Im English Js in a Js-En layout", body
I18n.locale = :it
get "/bar.js"
assert_equal "Hello Im Italian Js in a Js-It layout", body
I18n.locale = :en
get "/bar.json"
assert_equal "Im a json", body
get "/bar.pk"
assert_equal 405, status
end
should 'renders erb with blocks' do
mock_app do
def container
@_out_buf << "THIS."
yield
@_out_buf << "SPARTA!"
end
def is; "IS."; end
get '/' do
render :erb, '<% container do %> <%= is %> <% end %>'
end
end
get '/'
assert ok?
assert_equal 'THIS. IS. SPARTA!', body
end
end
end

View file

@ -0,0 +1,33 @@
require File.expand_path(File.dirname(__FILE__) + '/helper')
describe "Routing" do
should 'perform restul routing' do
mock_app do
controller :parent => :parents do
get :index do
"#{url_for(:index, params[:parent_id])} get"
end
put :index, :with => :asset_id do
"#{url_for(:index, params[:parent_id], :asset_id => params[:asset_id])} put"
end
post :index, :with => :asset_id do
"#{url_for(:index, :parent_id => params[:parent_id], :asset_id => params[:asset_id])} post"
end
delete :index, :with => :asset_id do
"#{url_for(:index, params[:parent_id], :asset_id => params[:asset_id])} delete"
end
end
end
get "/parents/1"
assert_equal "/parents/1 get", body
put "/parents/1/hi"
assert_equal "/parents/1/hi put", body
post "/parents/1/hi"
assert_equal "/parents/1/hi post", body
delete "/parents/1/hi"
assert_equal "/parents/1/hi delete", body
end
end

View file

@ -0,0 +1,146 @@
require File.expand_path(File.dirname(__FILE__) + '/helper')
require File.expand_path(File.dirname(__FILE__) + '/fixtures/apps/simple')
describe "Router" do
def setup
Padrino.clear!
end
should "dispatch paths correctly" do
app = lambda { |env|
[200, {
'X-ScriptName' => env['SCRIPT_NAME'],
'X-PathInfo' => env['PATH_INFO'],
'Content-Type' => 'text/plain'
}, [""]]
}
map = Padrino::Router.new(
{ :path => '/bar', :to => app },
{ :path => '/foo', :to => app },
{ :path => '/foo/bar', :to => app }
)
res = Rack::MockRequest.new(map).get("/")
assert res.not_found?
res = Rack::MockRequest.new(map).get("/qux")
assert res.not_found?
res = Rack::MockRequest.new(map).get("/foo")
assert res.ok?
assert_equal "/foo", res["X-ScriptName"]
assert_equal "/", res["X-PathInfo"]
res = Rack::MockRequest.new(map).get("/foo/")
assert res.ok?
assert_equal "/foo", res["X-ScriptName"]
assert_equal "/", res["X-PathInfo"]
res = Rack::MockRequest.new(map).get("/foo/bar")
assert res.ok?
assert_equal "/foo/bar", res["X-ScriptName"]
assert_equal "/", res["X-PathInfo"]
res = Rack::MockRequest.new(map).get("/foo/bar/")
assert res.ok?
assert_equal "/foo/bar", res["X-ScriptName"]
assert_equal "/", res["X-PathInfo"]
res = Rack::MockRequest.new(map).get("/foo///bar//quux")
assert_equal 200, res.status
assert res.ok?
assert_equal "/foo/bar", res["X-ScriptName"]
assert_equal "//quux", res["X-PathInfo"]
res = Rack::MockRequest.new(map).get("/foo/quux", "SCRIPT_NAME" => "/bleh")
assert res.ok?
assert_equal "/bleh/foo", res["X-ScriptName"]
assert_equal "/quux", res["X-PathInfo"]
res = Rack::MockRequest.new(map).get("/bar", 'HTTP_HOST' => 'foo.org')
assert res.ok?
assert_equal "/bar", res["X-ScriptName"]
assert_equal "/", res["X-PathInfo"]
res = Rack::MockRequest.new(map).get("/bar/", 'HTTP_HOST' => 'foo.org')
assert res.ok?
assert_equal "/bar", res["X-ScriptName"]
assert_equal "/", res["X-PathInfo"]
end
should "dispatches hosts correctly" do
map = Padrino::Router.new(
{ :host => "foo.org", :to => lambda { |env|
[200,
{ "Content-Type" => "text/plain",
"X-Position" => "foo.org",
"X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
}, [""]]}},
{ :host => "subdomain.foo.org", :to => lambda { |env|
[200,
{ "Content-Type" => "text/plain",
"X-Position" => "subdomain.foo.org",
"X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
}, [""]]}},
{ :host => /.*\.bar.org/, :to => lambda { |env|
[200,
{ "Content-Type" => "text/plain",
"X-Position" => "bar.org",
"X-Host" => env["HTTP_HOST"] || env["SERVER_NAME"],
}, [""]]}}
)
res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "bar.org")
assert res.not_found?
res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "at.bar.org")
assert res.ok?
assert_equal "bar.org", res["X-Position"]
res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "foo.org")
assert res.ok?
assert_equal "foo.org", res["X-Position"]
res = Rack::MockRequest.new(map).get("/", "HTTP_HOST" => "subdomain.foo.org", "SERVER_NAME" => "foo.org")
assert res.ok?
assert_equal "subdomain.foo.org", res["X-Position"]
end
should "works with padrino core applications" do
Padrino.mount("simple_demo").host("padrino.org")
assert_equal ["simple_demo"], Padrino.mounted_apps.map(&:name)
assert_equal ["padrino.org"], Padrino.mounted_apps.map(&:app_host)
res = Rack::MockRequest.new(Padrino.application).get("/")
assert res.not_found?
res = Rack::MockRequest.new(Padrino.application).get("/", "HTTP_HOST" => "bar.org")
assert res.not_found?
res = Rack::MockRequest.new(Padrino.application).get("/", "HTTP_HOST" => "padrino.org")
assert res.ok?
end
should "works with padrino applications" do
Padrino.mount("simple_demo").to("/foo").host(/.*\.padrino.org/)
res = Rack::MockRequest.new(Padrino.application).get("/")
assert res.not_found?
res = Rack::MockRequest.new(Padrino.application).get("/", "HTTP_HOST" => "bar.org")
assert res.not_found?
res = Rack::MockRequest.new(Padrino.application).get("/", "HTTP_HOST" => "padrino.org")
assert res.not_found?
res = Rack::MockRequest.new(Padrino.application).get("/none", "HTTP_HOST" => "foo.padrino.org")
assert res.not_found?
res = Rack::MockRequest.new(Padrino.application).get("/foo", "HTTP_HOST" => "bar.padrino.org")
assert res.ok?
res = Rack::MockRequest.new(Padrino.application).get("/foo/", "HTTP_HOST" => "bar.padrino.org")
assert res.ok?
end
end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
lib/**/*.rb
bin/*
-
README.rdoc
LICENSE.txt

View file

@ -0,0 +1,21 @@
## MAC OS
.DS_Store
## TEXTMATE
*.tmproj
tmtags
## EMACS
*~
\#*
.\#*
## VIM
*.swp
## PROJECT::GENERAL
coverage
rdoc
pkg
## PROJECT::SPECIFIC

View file

@ -0,0 +1 @@
--title 'Padrino Helpers Documentation' --protected

View file

@ -0,0 +1,20 @@
Copyright (c) 2011 Padrino
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,239 @@
= Application Extensions and Helpers (padrino-helpers)
=== Overview
This component provides a great deal of view helpers related to html markup generation.
There are helpers for generating tags, forms, links, images, and more. Most of the basic
methods should be very familiar to anyone who has used rails view helpers.
=== Output Helpers
Output helpers are a collection of important methods for managing, capturing and displaying output
in various ways and is used frequently to support higher-level helper functions. There are
three output helpers worth mentioning: <tt>content_for</tt>, <tt>capture_html</tt>, and <tt>concat_content</tt>
The content_for functionality supports capturing content and then rendering this into a different place
such as within a layout. One such popular example is including assets onto the layout from a template:
# app/views/site/index.erb
...
<% content_for :assets do %>
<%= stylesheet_link_tag 'index', 'custom' %>
<% end %>
...
Added to a template, this will capture the includes from the block and allow them to be yielded into the layout:
# app/views/layout.erb
...
<head>
<title>Example</title>
<%= stylesheet_link_tag 'style' %>
<%= yield_content :assets %>
</head>
...
This will automatically insert the contents of the block (in this case a stylesheet include) into the
location the content is yielded within the layout. You can also check if content exists for a block using
<tt>content_for?(true)</tt> which returns true if content exists.
The capture_html and the concat_content methods allow content to be manipulated and stored for use in building
additional helpers accepting blocks or displaying information in a template. One example is the use of
these in constructing a simplified 'form_tag' helper which accepts a block.
# form_tag '/register' do ... end
def form_tag(url, options={}, &block)
# ... truncated ...
inner_form_html = capture_html(&block)
concat_content '<form>' + inner_form_html + '</form>'
end
This will capture the template body passed into the form_tag block and then append the content
to the template through the use of <tt>concat_content</tt>. Note have been built to work for both haml and erb
templates using the same syntax.
For more information on using output helpers, check out the guide for
{Padrino Helpers}[http://www.padrinorb.com/guides/application-helpers].
=== Tag Helpers
Tag helpers are the basic building blocks used to construct html 'tags' within a view template. There
are three major functions for this category: <tt>tag</tt>, <tt>content_tag</tt> and <tt>input_tag</tt>.
The tag and content_tag are for building arbitrary html tags with a name and specified options. If
the tag contains 'content' within then <tt>content_tag</tt> is used. For example:
tag(:br, :style => 'clear:both') => <br style="clear:both" />
content_tag(:p, "demo", :class => 'light') => <p class="light">demo</p>
The input_tag is used to build tags that are related to accepting input from the user:
input_tag :text, :class => "demo" => <input type='text' class='demo' />
input_tag :password, :value => "secret", :class => "demo"
Note that all of these accept html options and result in returning a string containing html tags.
For more information on using tag helpers, check out the guide for
{Padrino Helpers}[http://www.padrinorb.com/guides/application-helpers].
=== Asset Helpers
Asset helpers are intended to help insert useful html onto a view template such as 'flash' notices,
hyperlinks, mail_to links, images, stylesheets and javascript. An example of their uses would be on a
simple view template:
# app/views/example.haml
...
%head
= stylesheet_link_tag 'layout'
= javascript_include_tag 'application'
%body
...
= flash_tag :notice
%p= link_to 'Blog', '/blog', :class => 'example'
%p Mail me at #{mail_to 'fake@faker.com', "Fake Email Link", :cc => "test@demo.com"}
%p= image_tag 'padrino.png', :width => '35', :class => 'logo'
For more information on using asset helpers, check out the guide for
{Padrino Helpers}[http://www.padrinorb.com/guides/application-helpers].
=== Form Helpers
Form helpers are the 'standard' form tag helpers you would come to expect when building forms. A simple
example of constructing a non-object form would be:
- form_tag '/destroy', :class => 'destroy-form', :method => 'delete' do
= flash_tag(:notice)
- field_set_tag do
%p
= label_tag :username, :class => 'first'
= text_field_tag :username, :value => params[:username]
%p
= label_tag :password, :class => 'first'
= password_field_tag :password, :value => params[:password]
%p
= label_tag :strategy
= select_tag :strategy, :options => ['delete', 'destroy'], :selected => 'delete'
%p
= check_box_tag :confirm_delete
- field_set_tag(:class => 'buttons') do
= submit_tag "Remove"
For more information on using form helpers, check out the guide for
{Padrino Helpers}[http://www.padrinorb.com/guides/application-helpers].
=== FormBuilders
Form builders are full-featured objects allowing the construction of complex object-based forms
using a simple, intuitive syntax.
A form_for using these basic fields might look like:
- form_for @user, '/register', :id => 'register' do |f|
= f.error_messages
%p
= f.label :username, :caption => "Nickname"
= f.text_field :username
%p
= f.label :email
= f.text_field :email
%p
= f.label :password
= f.password_field :password
%p
= f.label :is_admin, :caption => "Admin User?"
= f.check_box :is_admin
%p
= f.label :color, :caption => "Favorite Color?"
= f.select :color, :options => ['red', 'black']
%p
- fields_for @user.location do |location|
= location.text_field :street
= location.text_field :city
%p
= f.submit "Create", :class => 'button'
Forms can also accept nested attributes using `fields_for` within the form builder in recent releases. Check out the guide for {Padrino Helpers}[http://www.padrinorb.com/guides/application-helpers] to learn more about nested forms.
There is also an additional StandardFormBuilder which builds on the abstract fields that can be used within a form_for.
A form_for using these standard fields might be:
- form_for @user, '/register', :id => 'register' do |f|
= f.error_messages
= f.text_field_block :name, :caption => "Full name"
= f.text_field_block :email
= f.check_box_block :remember_me
= f.select_block :fav_color, :options => ['red', 'blue']
= f.password_field_block :password
= f.submit_block "Create", :class => 'button'
and would generate this html (with each input contained in a paragraph and containing a label):
<form id="register" action="/register" method="post">
<p><label for="user_name">Full name: </label><input type="text" id="user_name" name="user[name]"></p>
...omitted...
<p><input type="submit" value="Create" class="button"></p>
</form>
You can also easily build your own FormBuilder which allows for customized fields and behavior.
For more information on using the Padrino form builders, check out the guide for
{Padrino Helpers}[http://www.padrinorb.com/guides/application-helpers].
=== Format Helpers
Format helpers are several useful utilities for manipulating the format of text to achieve a goal.
The four format helpers are <tt>escape_html</tt>, <tt>time_ago_in_words</tt>, and <tt>js_escape_html</tt>.
The escape_html and js_escape_html function are for taking an html string and escaping certain characters.
<tt>escape_html</tt> will escape ampersands, brackets and quotes to their HTML/XML entities. This is useful
to sanitize user content before displaying this on a template. <tt>js_escape_html</tt> is used for
passing javascript information from a js template to a javascript function.
escape_html('<hello>&<goodbye>') # => &lt;hello&gt;&amp;&lt;goodbye&gt;
There is also an alias for escape_html called <tt>h</tt> for even easier usage within templates.
Format helpers also includes a number of useful text manipulation functions such as <tt>simple_format</tt>,
<tt>pluralize</tt>, <tt>word_wrap</tt>, <tt>truncate</tt> and <tt>truncate_words</tt>.
simple_format("hello\nworld") # => "<p>hello<br/>world</p>"
pluralize(2, 'person') => '2 people'
word_wrap('Once upon a time', :line_width => 8) => "Once upon\na time"
truncate("Once upon a time in a world far far away", :length => 8) => "Once upon..."
truncate_words("Once upon a time in a world far far away", :length => 4) => "Once upon a time..."
These helpers can be invoked from any route or view within your application.
For more information on using the format helpers, check out the guide for
{Padrino Helpers}[http://www.padrinorb.com/guides/application-helpers].
=== Render Helpers
This component provides a number of rendering helpers making the process of displaying templates a bit easier.
This plugin also has support for useful additions such as partials (with support for :collection) for the templating system.
Using render plugin helpers is extremely simple. If you want to render an erb template in your view path:
render :erb, 'path/to/erb/template'
or using haml templates works just as well:
render :haml, 'path/to/haml/template'
There is also a method which renders the first view matching the path and removes the need to define an engine:
render 'path/to/any/template'
Finally, we have the all-important partials support for rendering mini-templates onto a page:
partial 'photo/_item', :object => @photo, :locals => { :foo => 'bar' }
partial 'photo/_item', :collection => @photos
For more information on using the render and partial helpers, check out the guide for
{Padrino Helpers}[http://www.padrinorb.com/guides/application-helpers].
== Copyright
Copyright (c) 2011 Padrino. See LICENSE for details.

View file

@ -0,0 +1,5 @@
# coding:utf-8
RAKE_ROOT = __FILE__
require 'rubygems'
require File.expand_path(File.dirname(__FILE__) + '/../gem_rake_helper')

View file

@ -0,0 +1,58 @@
require 'padrino-core/support_lite' unless defined?(SupportLite)
require 'cgi'
require 'i18n'
require 'enumerator'
require 'active_support/core_ext/string/conversions' # to_date
require 'active_support/core_ext/float/rounding' # round
require 'active_support/option_merger' # with_options
require 'active_support/core_ext/object/with_options' # with_options
require 'active_support/inflector' # humanize
FileSet.glob_require('padrino-helpers/**/*.rb', __FILE__)
# Load our locales
I18n.load_path += Dir["#{File.dirname(__FILE__)}/padrino-helpers/locale/*.yml"]
module Padrino
##
# This component provides a variety of view helpers related to html markup generation.
# There are helpers for generating tags, forms, links, images, and more.
# Most of the basic methods should be very familiar to anyone who has used rails view helpers.
#
module Helpers
class << self
##
# Registers these helpers into your application:
#
# Padrino::Helpers::OutputHelpers
# Padrino::Helpers::TagHelpers
# Padrino::Helpers::AssetTagHelpers
# Padrino::Helpers::FormHelpers
# Padrino::Helpers::FormatHelpers
# Padrino::Helpers::RenderHelpers
# Padrino::Helpers::NumberHelpers
#
# @param [Sinatra::Application] app
# The specified padrino application
#
# @example Register the helper module
# require 'padrino-helpers'
# class Padrino::Appliocation
# register Padrino::Helpers
# end
#
def registered(app)
app.set :default_builder, 'StandardFormBuilder'
app.helpers Padrino::Helpers::OutputHelpers
app.helpers Padrino::Helpers::TagHelpers
app.helpers Padrino::Helpers::AssetTagHelpers
app.helpers Padrino::Helpers::FormHelpers
app.helpers Padrino::Helpers::FormatHelpers
app.helpers Padrino::Helpers::RenderHelpers
app.helpers Padrino::Helpers::NumberHelpers
app.helpers Padrino::Helpers::TranslationHelpers
end
alias :included :registered
end
end # Helpers
end # Padrino

View file

@ -0,0 +1,420 @@
module Padrino
module Helpers
###
# Helpers related to producing assets (images,stylesheets,js,etc) within templates.
#
module AssetTagHelpers
##
# Creates a div to display the flash of given type if it exists
#
# @param [Symbol] kind
# The type of flash to display in the tag.
# @param [Hash] options
# The html options for this section.
#
# @return [String] Flash tag html with specified +options+.
#
# @example
# flash_tag(:notice, :id => 'flash-notice')
# # Generates: <div class="notice">flash-notice</div>
#
# @api public
def flash_tag(kind, options={})
flash_text = flash[kind]
return '' if flash_text.blank?
options.reverse_merge!(:class => kind)
content_tag(:div, flash_text, options)
end
##
# Creates a link element with given name, url and options
#
# @overload link_to(caption, url, options={})
# @param [String] caption The text caption.
# @param [String] url The url href.
# @param [Hash] options The html options.
# @overload link_to(url, options={}, &block)
# @param [String] url The url href.
# @param [Hash] options The html options.
# @param [Proc] block The link content.
#
# @option options [String] :anchor
# The anchor for the link (i.e #something)
# @option options [Boolean] :if
# If true, the link will appear, otherwise not;
# @option options [Boolean] :unless
# If false, the link will appear, otherwise not;
# @option options [Boolean] :remote
# If true, this link should be handled by a ajax ujs handler.
# @option options [String] :confirm
# Instructs ujs handler to alert confirm message.
# @option options [Symbol] :method
# Instructs ujs handler to use different http method (i.e :post, :delete).
#
# @return [String] Link tag html with specified +options+.
#
# @example
# link_to('click me', '/dashboard', :class => 'linky')
# link_to('click me', '/dashboard', :remote => true)
# link_to('click me', '/dashboard', :method => :delete)
# link_to('click me', :class => 'blocky') do; end
#
# Note that you can pass :+if+ or :+unless+ conditions, but if you provide :current as
# condition padrino return true/false if the request.path_info match the given url
#
# @api public
def link_to(*args, &block)
options = args.extract_options!
options = parse_js_attributes(options) # parses remote, method and confirm options
anchor = "##{CGI.escape options.delete(:anchor).to_s}" if options[:anchor]
if block_given?
url = args[0] ? args[0] + anchor.to_s : anchor || 'javascript:void(0);'
options.reverse_merge!(:href => url)
link_content = capture_html(&block)
return '' unless parse_conditions(url, options)
result_link = content_tag(:a, link_content, options)
block_is_template?(block) ? concat_content(result_link) : result_link
else
name, url = args[0], (args[1] ? args[1] + anchor.to_s : anchor || 'javascript:void(0);')
return name unless parse_conditions(url, options)
options.reverse_merge!(:href => url)
content_tag(:a, name, options)
end
end
##
# Creates a form containing a single button that submits to the url.
#
# @overload button_to(name, url, options={})
# @param [String] caption The text caption.
# @param [String] url The url href.
# @param [Hash] options The html options.
# @overload button_to(name, options={}, &block)
# @param [String] url The url href.
# @param [Hash] options The html options.
# @param [Proc] block The button content.
#
# @option options [Boolean] :multipart
# If true, this form will support multipart encoding.
# @option options [String] :remote
# Instructs ujs handler to handle the submit as ajax.
# @option options [Symbol] :method
# Instructs ujs handler to use different http method (i.e :post, :delete).
#
# @return [String] Form and button html with specified +options+.
#
# @example
# button_to 'Delete', url(:accounts_destroy, :id => account), :method => :delete, :class => :form
# # Generates:
# # <form class="form" action="/admin/accounts/destroy/2" method="post">
# # <input type="hidden" value="delete" name="_method" />
# # <input type="submit" value="Delete" />
# # </form>
#
# @api public
def button_to(*args, &block)
name, url = args[0], args[1]
options = args.extract_options!
desired_method = options[:method]
options.delete(:method) if options[:method].to_s !~ /get|post/i
options.reverse_merge!(:method => 'post', :action => url)
options[:enctype] = "multipart/form-data" if options.delete(:multipart)
options["data-remote"] = "true" if options.delete(:remote)
inner_form_html = hidden_form_method_field(desired_method)
inner_form_html += block_given? ? capture_html(&block) : submit_tag(name)
content_tag('form', inner_form_html, options)
end
##
# Creates a link tag that browsers and news readers can use to auto-detect an RSS or ATOM feed.
#
# @param [Symbol] mime
# The mime type of the feed (i.e :atom or :rss).
# @param [String] url
# The url for the feed tag to reference.
# @param[Hash] options
# The options for the feed tag.
# @option options [String] :rel ("alternate")
# Specify the relation of this link
# @option options [String] :type
# Override the auto-generated mime type
# @option options [String] :title
# Specify the title of the link, defaults to the type
#
# @return [String] Feed link html tag with specified +options+.
#
# @example
# feed_tag :atom, url(:blog, :posts, :format => :atom), :title => "ATOM"
# # Generates: <link type="application/atom+xml" rel="alternate" href="/blog/posts.atom" title="ATOM" />
# feed_tag :rss, url(:blog, :posts, :format => :rss)
# # Generates: <link type="application/rss+xml" rel="alternate" href="/blog/posts.rss" title="rss" />
#
# @api public
def feed_tag(mime, url, options={})
full_mime = (mime == :atom) ? 'application/atom+xml' : 'application/rss+xml'
content_tag(:link, options.reverse_merge(:rel => 'alternate', :type => full_mime, :title => mime, :href => url))
end
##
# Creates a mail link element with given name and caption.
#
# @param [String] email
# The email address for the link.
# @param [String] caption
# The caption for the link.
# @param [Hash] mail_options
# The options for the mail link. Accepts html options.
# @option mail_options [String] cc The cc recipients.
# @option mail_options [String] bcc The bcc recipients.
# @option mail_options [String] subject The subject line.
# @option mail_options [String] body The email body.
#
# @return [String] Mail link html tag with specified +options+.
#
# @example
# # Generates: <a href="mailto:me@demo.com">me@demo.com</a>
# mail_to "me@demo.com"
# # Generates: <a href="mailto:me@demo.com">My Email</a>
# mail_to "me@demo.com", "My Email"
#
# @api public
def mail_to(email, caption=nil, mail_options={})
html_options = mail_options.slice!(:cc, :bcc, :subject, :body)
mail_query = Rack::Utils.build_query(mail_options).gsub(/\+/, '%20').gsub('%40', '@')
mail_href = "mailto:#{email}"; mail_href << "?#{mail_query}" if mail_query.present?
link_to((caption || email), mail_href, html_options)
end
##
# Creates a meta element with the content and given options.
#
# @param [String] content
# The content for the meta tag.
# @param [Hash] options
# The html options for the meta tag.
#
# @return [String] Meta html tag with specified +options+.
#
# @example
# # Generates: <meta name="keywords" content="weblog,news">
# meta_tag "weblog,news", :name => "keywords"
#
# # Generates: <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
# meta_tag "text/html; charset=UTF-8", 'http-equiv' => "Content-Type"
#
# @api public
def meta_tag(content, options={})
options.reverse_merge!("content" => content)
tag(:meta, options)
end
##
# Generates a favicon link. looks inside images folder
#
# @param [String] source
# The source image path for the favicon link tag.
# @param [Hash] options
# The html options for the favicon link tag.
#
# @return [String] The favicon link html tag with specified +options+.
#
# @example
# favicon_tag 'favicon.png'
# favicon_tag 'icons/favicon.png'
# # or override some options
# favicon_tag 'favicon.png', :type => 'image/ico'
#
# @api public
def favicon_tag(source, options={})
type = File.extname(source).gsub('.','')
options = options.dup.reverse_merge!(:href => image_path(source), :rel => 'icon', :type => "image/#{type}")
tag(:link, options)
end
##
# Creates an image element with given url and options
#
# @param [String] url
# The source path for the image tag.
# @param [Hash] options
# The html options for the image tag.
#
# @return [String] Image html tag with +url+ and specified +options+.
#
# @example
# image_tag('icons/avatar.png')
#
# @api public
def image_tag(url, options={})
options.reverse_merge!(:src => image_path(url))
tag(:img, options)
end
##
# Returns an html script tag for each of the sources provided.
# You can pass in the filename without extension or a symbol and we search it in your +appname.public_folder+
# like app/public/stylesheets for inclusion. You can provide also a full path.
#
# @overload stylesheet_link_tag(*sources, options={})
# @param [Array<String>] sources Splat of css source paths
# @param [Hash] options The html options for the link tag
#
# @return [String] Stylesheet link html tag for +sources+ with specified +options+.
#
# @example
# stylesheet_link_tag 'style', 'application', 'layout'
#
# @api public
def stylesheet_link_tag(*sources)
options = sources.extract_options!.symbolize_keys
options.reverse_merge!(:media => 'screen', :rel => 'stylesheet', :type => 'text/css')
sources.flatten.map { |source|
tag(:link, options.reverse_merge(:href => asset_path(:css, source)))
}.join("\n")
end
##
# Returns an html script tag for each of the sources provided.
# You can pass in the filename without extension or a symbol and we search it in your +appname.public_folder+
# like app/public/javascript for inclusion. You can provide also a full path.
#
# @overload javascript_include_tag(*sources, options={})
# @param [Array<String>] sources Splat of js source paths
# @param [Hash] options The html options for the script tag
#
# @return [String] Script tag for +sources+ with specified +options+.
#
# @example
# javascript_include_tag 'application', :extjs
#
# @api public
def javascript_include_tag(*sources)
options = sources.extract_options!.symbolize_keys
options.reverse_merge!(:type => 'text/javascript', :content => "")
sources.flatten.map { |source|
tag(:script, options.reverse_merge(:src => asset_path(:js, source)))
}.join("\n")
end
##
# Returns the path to the image, either relative or absolute. We search it in your +appname.public_folder+
# like app/public/images for inclusion. You can provide also a full path.
#
# @param [String] src
# The path to the image file (relative or absolute)
#
# @return [String] Path to an image given the +kind+ and +source+.
#
# @example
# # Generates: /images/foo.jpg?1269008689
# image_path("foo.jpg")
#
# @api public
def image_path(src)
asset_path(:images, src)
end
##
# Returns the path to the specified asset (css or javascript)
#
# @param [String] kind
# The kind of asset (i.e :images, :js, :css)
# @param [String] source
# The path to the asset (relative or absolute).
#
# @return [String] Path for the asset given the +kind+ and +source+.
#
# @example
# # Generates: /javascripts/application.js?1269008689
# asset_path :js, :application
#
# # Generates: /stylesheets/application.css?1269008689
# asset_path :css, :application
#
# # Generates: /images/example.jpg?1269008689
# asset_path :images, 'example.jpg'
#
# @api semipublic
def asset_path(kind, source)
return source if source =~ /^http/
asset_folder = case kind
when :css then 'stylesheets'
when :js then 'javascripts'
else kind.to_s
end
source = source.to_s.gsub(/\s/, '%20')
ignore_extension = (asset_folder.to_s == kind.to_s) # don't append extension
source << ".#{kind}" unless ignore_extension or source =~ /\.#{kind}/
result_path = source if source =~ %r{^/} # absolute path
result_path ||= uri_root_path(asset_folder, source)
timestamp = asset_timestamp(result_path)
"#{result_path}#{timestamp}"
end
private
##
# Returns the uri root of the application.
#
# @example
# uri_root_path("/some/path") => "/base/some/path"
#
def uri_root_path(*paths)
root_uri = self.class.uri_root if self.class.respond_to?(:uri_root)
File.join(ENV['RACK_BASE_URI'].to_s, root_uri || '/', *paths)
end
##
# Returns the timestamp mtime for an asset
#
# @example
# asset_timestamp("some/path/to/file.png") => "?154543678"
#
def asset_timestamp(file_path)
return nil if file_path =~ /\?/ || (self.class.respond_to?(:asset_stamp) && !self.class.asset_stamp)
public_path = Padrino.root("public", file_path) if Padrino.respond_to?(:root)
stamp = Time.now.to_i unless public_path && File.exist?(public_path)
stamp ||= File.mtime(public_path).to_i
"?#{stamp}"
end
##
# Parses link_to options for given correct conditions
#
# @example
# parse_conditions("/some/url", :if => false) => true
#
def parse_conditions(url, options)
if options.has_key?(:if)
condition = options.delete(:if)
condition == :current ? url == request.path_info : condition
elsif condition = options.delete(:unless)
condition == :current ? url != request.path_info : !condition
else
true
end
end
##
# Parses link_to options for given js declarations (remote, method, confirm)
# Not destructive on options; returns updated options
#
# parse_js_attributes(:remote => true, :confirm => "test", :method => :delete)
# => { "data-remote" => true, "data-method" => "delete", "data-confirm" => "test" }
#
def parse_js_attributes(options)
options = options.dup
options["data-remote"] = "true" if options.delete(:remote)
if link_confirm = options.delete(:confirm)
options["data-confirm"] = link_confirm
end
if link_method = options.delete(:method)
options["data-method"] = link_method
options["rel"] = "nofollow"
end
options
end
end # AssetTagHelpers
end # Helpers
end # Padrino

View file

@ -0,0 +1,220 @@
module Padrino
module Helpers
module FormBuilder # @private
class AbstractFormBuilder # @private
attr_accessor :template, :object
def initialize(template, object, options={})
@template = template
@object = build_object(object)
@options = options
raise "FormBuilder template must be initialized!" unless template
raise "FormBuilder object must not be a nil value. If there's no object, use a symbol instead! (i.e :user)" unless object
end
# f.error_messages
def error_messages(*params)
params.unshift object
@template.error_messages_for(*params)
end
# f.error_message_on(field)
def error_message_on(field, options={})
@template.error_message_on(object, field, options)
end
# f.label :username, :caption => "Nickname"
def label(field, options={})
options.reverse_merge!(:caption => "#{field_human_name(field)}: ")
@template.label_tag(field_id(field), options)
end
# f.hidden_field :session_id, :value => "45"
def hidden_field(field, options={})
options.reverse_merge!(:value => field_value(field), :id => field_id(field))
@template.hidden_field_tag field_name(field), options
end
# f.text_field :username, :value => "(blank)", :id => 'username'
def text_field(field, options={})
options.reverse_merge!(:value => field_value(field), :id => field_id(field))
options.merge!(:class => field_error(field, options))
@template.text_field_tag field_name(field), options
end
# f.text_area :summary, :value => "(enter summary)", :id => 'summary'
def text_area(field, options={})
options.reverse_merge!(:value => field_value(field), :id => field_id(field))
options.merge!(:class => field_error(field, options))
@template.text_area_tag field_name(field), options
end
# f.password_field :password, :id => 'password'
def password_field(field, options={})
options.reverse_merge!(:value => field_value(field), :id => field_id(field))
options.merge!(:class => field_error(field, options))
@template.password_field_tag field_name(field), options
end
# f.select :color, :options => ['red', 'green'], :include_blank => true
# f.select :color, :collection => @colors, :fields => [:name, :id]
def select(field, options={})
options.reverse_merge!(:id => field_id(field), :selected => field_value(field))
options.merge!(:class => field_error(field, options))
@template.select_tag field_name(field), options
end
# f.check_box :remember_me, :value => 'true', :uncheck_value => '0'
def check_box(field, options={})
unchecked_value = options.delete(:uncheck_value) || '0'
options.reverse_merge!(:id => field_id(field), :value => '1')
options.reverse_merge!(:checked => true) if values_matches_field?(field, options[:value])
html = @template.hidden_field_tag(options[:name] || field_name(field), :value => unchecked_value, :id => nil)
html << @template.check_box_tag(field_name(field), options)
end
# f.radio_button :gender, :value => 'male'
def radio_button(field, options={})
options.reverse_merge!(:id => field_id(field, options[:value]))
options.reverse_merge!(:checked => true) if values_matches_field?(field, options[:value])
@template.radio_button_tag field_name(field), options
end
# f.file_field :photo, :class => 'avatar'
def file_field(field, options={})
options.reverse_merge!(:id => field_id(field))
options.merge!(:class => field_error(field, options))
@template.file_field_tag field_name(field), options
end
# f.submit "Update", :class => 'large'
def submit(caption="Submit", options={})
@template.submit_tag caption, options
end
# f.image_submit "buttons/submit.png", :class => 'large'
def image_submit(source, options={})
@template.image_submit_tag source, options
end
# Supports nested fields for a child model within a form
# f.fields_for :addresses
# f.fields_for :addresses, address
# f.fields_for :addresses, @addresses
def fields_for(child_association, instance_or_collection=nil, &block)
default_collection = self.object.send(child_association)
include_index = default_collection.respond_to?(:each)
nested_options = { :parent => self, :association => child_association }
nested_objects = instance_or_collection ? Array(instance_or_collection) : Array(default_collection)
result = nested_objects.each_with_index.map do |child_instance, index|
nested_options[:index] = include_index ? index : nil
@template.fields_for(child_instance, { :nested => nested_options }, &block)
end.join("\n")
end
protected
# Returns the known field types for a formbuilder
def self.field_types
[:hidden_field, :text_field, :text_area, :password_field, :file_field, :radio_button, :check_box, :select]
end
# Returns true if the value matches the value in the field
# field_has_value?(:gender, 'male')
def values_matches_field?(field, value)
value.present? && (field_value(field).to_s == value.to_s || field_value(field).to_s == 'true')
end
# Add a :invalid css class to the field if it contain an error
def field_error(field, options)
error = @object.errors[field] rescue nil
error.blank? ? options[:class] : [options[:class], :invalid].flatten.compact.join(" ")
end
# Returns the human name of the field. Look that use builtin I18n.
def field_human_name(field)
I18n.translate("#{object_model_name}.attributes.#{field}", :count => 1, :default => field.to_s.humanize, :scope => :models)
end
# Returns the name for the given field
# field_name(:username) => "user[username]"
# field_name(:number) => "user[telephone_attributes][number]"
# field_name(:street) => "user[addresses_attributes][0][street]"
def field_name(field=nil)
result = []
if root_form?
result << object_model_name
elsif nested_form?
parent_form = @options[:nested][:parent]
attributes_name = "#{@options[:nested][:association]}_attributes"
nested_index = @options[:nested][:index]
fragment = [parent_form.field_name, "[#{attributes_name}", "]"]
fragment.insert(2, "][#{nested_index}") if nested_index
result << fragment
end
result << "[#{field}]" unless field.blank?
result.flatten.join
end
# Returns the id for the given field
# field_id(:username) => "user_username"
# field_id(:gender, :male) => "user_gender_male"
# field_name(:number) => "user_telephone_attributes_number"
# field_name(:street) => "user_addresses_attributes_0_street"
def field_id(field=nil, value=nil)
result = []
if root_form?
result << object_model_name
elsif nested_form?
parent_form = @options[:nested][:parent]
attributes_name = "#{@options[:nested][:association]}_attributes"
nested_index = @options[:nested][:index]
fragment = [parent_form.field_id, "_#{attributes_name}"]
fragment.push("_#{nested_index}") if nested_index
result << fragment
end
result << "_#{field}" unless field.blank?
result << "_#{value}" unless value.blank?
result.flatten.join
end
# Returns the child object if it exists
def nested_object_id
nested_form? && object.respond_to?(:new_record?) && !object.new_record? && object.id
end
# Returns true if this form object is nested in a parent form
def nested_form?
@options[:nested] && @options[:nested][:parent] && @options[:nested][:parent].respond_to?(:object)
end
# Returns the value for the object's field
# field_value(:username) => "Joey"
def field_value(field)
@object && @object.respond_to?(field) ? @object.send(field) : ""
end
# explicit_object is either a symbol or a record
# Returns a new record of the type specified in the object
def build_object(object_or_symbol)
object_or_symbol.is_a?(Symbol) ? @template.instance_variable_get("@#{object_or_symbol}") || object_class(object_or_symbol).new : object_or_symbol
end
# Returns the object's models name
# => user_assignment
def object_model_name(explicit_object=object)
explicit_object.is_a?(Symbol) ? explicit_object : explicit_object.class.to_s.underscore.gsub(/\//, '_')
end
# Returns the class type for the given object
def object_class(explicit_object)
explicit_object.is_a?(Symbol) ? explicit_object.to_s.camelize.constantize : explicit_object.class
end
# Returns true if this form is the top-level (not nested)
def root_form?
!nested_form?
end
end # AbstractFormBuilder
end # FormBuilder
end # Helpers
end # Padrino

View file

@ -0,0 +1,43 @@
require File.expand_path(File.dirname(__FILE__) + '/abstract_form_builder') unless defined?(AbstractFormBuilder)
module Padrino
module Helpers
module FormBuilder # @private
class StandardFormBuilder < AbstractFormBuilder # @private
##
# StandardFormBuilder
#
# text_field_block(:username, { :class => 'long' }, { :class => 'wide-label' })
# text_area_block(:summary, { :class => 'long' }, { :class => 'wide-label' })
# password_field_block(:password, { :class => 'long' }, { :class => 'wide-label' })
# file_field_block(:photo, { :class => 'long' }, { :class => 'wide-label' })
# check_box_block(:remember_me, { :class => 'long' }, { :class => 'wide-label' })
# select_block(:color, :options => ['green', 'black'])
#
(self.field_types - [ :hidden_field, :radio_button ]).each do |field_type|
class_eval <<-EOF
def #{field_type}_block(field, options={}, label_options={})
label_options.reverse_merge!(:caption => options.delete(:caption)) if options[:caption]
field_html = label(field, label_options)
field_html << #{field_type}(field, options)
@template.content_tag(:p, field_html)
end
EOF
end
# submit_block("Update")
def submit_block(caption, options={})
submit_html = self.submit(caption, options)
@template.content_tag(:p, submit_html)
end
# image_submit_block("submit.png")
def image_submit_block(source, options={})
submit_html = self.image_submit(source, options)
@template.content_tag(:p, submit_html)
end
end # StandardFormBuilder
end # FormBuilder
end # Helpers
end # Padrino

View file

@ -0,0 +1,602 @@
module Padrino
module Helpers
##
# Helpers related to producing form related tags and inputs into templates.
#
module FormHelpers
##
# Constructs a form for object using given or default form_builder
#
# @param [Object] object
# The object for which the form is being built.
# @param [String] url
# The url this form will submit to.
# @param [Hash] settings
# The settings associated with this form. Accepts html options.
# @option settings [String] :builder ("StandardFormBuilder")
# The FormBuilder class to use such as StandardFormBuilder.
# @param [Proc] block
# The fields and content inside this form.
#
# @yield [AbstractFormBuilder] The form builder used to compose fields.
#
# @return [String] The html object-backed form with the specified options and input fields.
#
# @example
# form_for :user, '/register' do |f| ... end
# form_for @user, '/register', :id => 'register' do |f| ... end
#
# @api public
def form_for(object, url, settings={}, &block)
form_html = capture_html(builder_instance(object, settings), &block)
form_tag(url, settings) { form_html }
end
##
# Constructs form fields for an object using given or default form_builder
# Used within an existing form to allow alternate objects within one form
#
# @param [Object] object
# The object for which the fields are being built.
# @param [Hash] settings
# The settings associated with these fields. Accepts html options.
# @param [Proc] block
# The content inside this set of fields.
#
# @return [String] The html fields with the specified options.
#
# @example
# fields_for @user.assignment do |assignment| ... end
# fields_for :assignment do |assigment| ... end
#
# @api public
def fields_for(object, settings={}, &block)
instance = builder_instance(object, settings)
fields_html = capture_html(instance, &block)
fields_html << instance.hidden_field(:id) if instance.send(:nested_object_id)
concat_content fields_html
end
##
# Constructs a form without object based on options
#
# @param [String] url
# The url this form will submit to.
# @param [Hash] options
# The html options associated with this form.
# @param [Proc] block
# The fields and content inside this form.
#
# @return [String] The html form with the specified options and input fields.
#
# @example
# form_tag '/register', :class => "registration_form" do ... end
#
# @api public
def form_tag(url, options={}, &block)
desired_method = options[:method]
data_method = options.delete(:method) if options[:method].to_s !~ /get|post/i
options.reverse_merge!(:method => "post", :action => url)
options[:enctype] = "multipart/form-data" if options.delete(:multipart)
options["data-remote"] = "true" if options.delete(:remote)
options["data-method"] = data_method if data_method
options["accept-charset"] ||= "UTF-8"
inner_form_html = hidden_form_method_field(desired_method)
inner_form_html += capture_html(&block)
concat_content content_tag(:form, inner_form_html, options)
end
##
# Returns the hidden method field for 'put' and 'delete' forms
# Only 'get' and 'post' are allowed within browsers;
# 'put' and 'delete' are just specified using hidden fields with form action still 'put'.
#
# @param [String] desired_method
# The method this hidden field represents (i.e put or delete))
#
# @return [String] The hidden field representing the +desired_method+ for the form.
#
# @example
# # Generate: <input name="_method" value="delete" />
# hidden_form_method_field('delete')
#
# @api semipublic
def hidden_form_method_field(desired_method)
return '' if desired_method.blank? || desired_method.to_s =~ /get|post/i
hidden_field_tag(:_method, :value => desired_method)
end
##
# Constructs a field_set to group fields with given options
#
# @overload field_set_tag(legend=nil, options={}, &block)
# @param [String] legend The legend caption for the fieldset
# @param [Hash] options The html options for the fieldset.
# @param [Proc] block The content inside the fieldset.
# @overload field_set_tag(options={}, &block)
# @param [Hash] options The html options for the fieldset.
# @param [Proc] block The content inside the fieldset.
#
# @return [String] The html for the fieldset tag based on given +options+.
#
# @example
# field_set_tag(:class => "office-set") { }
# field_set_tag("Office", :class => 'office-set') { }
#
# @api public
def field_set_tag(*args, &block)
options = args.extract_options!
legend_text = args[0].is_a?(String) ? args.first : nil
legend_html = legend_text.blank? ? '' : content_tag(:legend, legend_text)
field_set_content = legend_html + capture_html(&block)
concat_content content_tag(:fieldset, field_set_content, options)
end
##
# Constructs list html for the errors for a given symbol
#
# @overload error_messages_for(*objects, options = {})
# @param [Array<Object>] object Splat of objects to display errors for.
# @param [Hash] options Error message display options.
# @option options [String] :header_tag ("h2")
# Used for the header of the error div
# @option options [String] :id ("errorExplanation")
# The id of the error div.
# @option options [String] :class ("errorExplanation")
# The class of the error div.
# @option options [Array<Object>] :object
# The object (or array of objects) for which to display errors,
# if you need to escape the instance variable convention.
# @option options [String] :object_name
# The object name to use in the header, or any text that you prefer.
# If +:object_name+ is not set, the name of the first object will be used.
# @option options [String] :header_message ("X errors prohibited this object from being saved")
# The message in the header of the error div. Pass +nil+ or an empty string
# to avoid the header message altogether.
# @option options [String] :message ("There were problems with the following fields:")
# The explanation message after the header message and before
# the error list. Pass +nil+ or an empty string to avoid the explanation message
# altogether.
#
# @return [String] The html section with all errors for the specified +objects+
#
# @example
# error_messages_for :user
#
# @api public
def error_messages_for(*objects)
options = objects.extract_options!.symbolize_keys
objects = objects.map { |object_name|
object_name.is_a?(Symbol) ? instance_variable_get("@#{object_name}") : object_name
}.compact
count = objects.inject(0) { |sum, object| sum + object.errors.size }
unless count.zero?
html = {}
[:id, :class, :style].each do |key|
if options.include?(key)
value = options[key]
html[key] = value unless value.blank?
else
html[key] = 'field-errors' unless key == :style
end
end
options[:object_name] ||= objects.first.class
I18n.with_options :locale => options[:locale], :scope => [:models, :errors, :template] do |locale|
header_message = if options.include?(:header_message)
options[:header_message]
else
object_name = options[:object_name].to_s.underscore.gsub(/\//, ' ')
object_name = I18n.t(:name, :default => object_name.humanize, :scope => [:models, object_name], :count => 1)
locale.t :header, :count => count, :model => object_name
end
message = options.include?(:message) ? options[:message] : locale.t(:body)
error_messages = objects.map { |object|
object_name = options[:object_name].to_s.underscore.gsub(/\//, ' ')
object.errors.map { |f, msg|
field = I18n.t(f, :default => f.to_s.humanize, :scope => [:models, object_name, :attributes])
content_tag(:li, "%s %s" % [field, msg])
}
}.join
contents = ''
contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
contents << content_tag(:p, message) unless message.blank?
contents << content_tag(:ul, error_messages)
content_tag(:div, contents, html)
end
else
''
end
end
##
# Returns a string containing the error message attached to the +method+ on the +object+ if one exists.
#
# @param [Object] object
# The object to display the error for.
# @param [Symbol] field
# The field on the +object+ to display the error for.
# @param [Hash] options
# The options to control the error display.
# @option options [String] :tag ("div")
# The tag that encloses the error.
# @option options [String] :prepend ("")
# The text to prepend before the field error.
# @option options [String] :append ("")
# The text to append after the field error.
#
# @example
# # => <span class="error">can't be blank</div>
# error_message_on :post, :title
# error_message_on @post, :title
#
# # => <div class="custom" style="border:1px solid red">can't be blank</div>
# error_message_on :post, :title, :tag => :id, :class => :custom, :style => "border:1px solid red"
#
# # => <div class="error">This title can't be blank (or it won't work)</div>
# error_message_on :post, :title, :prepend => "This title", :append => "(or it won't work)"
#
# @return [String] The html display of an error for a particular +object+ and +field+.
#
# @api public
def error_message_on(object, field, options={})
object = object.is_a?(Symbol) ? instance_variable_get("@#{object}") : object
error = object.errors[field] rescue nil
if error
options.reverse_merge!(:tag => :span, :class => :error)
tag = options.delete(:tag)
# Array(error).first is necessary because some orm give us an array others directly a value
error = [options.delete(:prepend), Array(error).first, options.delete(:append)].compact.join(" ")
content_tag(tag, error, options)
else
''
end
end
##
# Constructs a label tag from the given options
#
# @param [String] name
# The name of the field to label.
# @param [Hash] options
# The html options for this label.
# @option options :caption
# The caption for this label.
# @param [Proc] block
# The content to be inserted into the label.
#
# @return [String] The html for this label with the given +options+.
#
# @example
# label_tag :username, :class => 'long-label'
# label_tag :username, :class => 'long-label' do ... end
#
# @api public
def label_tag(name, options={}, &block)
options.reverse_merge!(:caption => "#{name.to_s.humanize}: ", :for => name)
caption_text = options.delete(:caption)
caption_text << "<span class='required'>*</span> " if options.delete(:required)
if block_given? # label with inner content
label_content = caption_text + capture_html(&block)
concat_content(content_tag(:label, label_content, options))
else # regular label
content_tag(:label, caption_text, options)
end
end
##
# Constructs a text field input from the given options
#
# @macro [new] input_field_doc
# @param [String] name
# The name of the input field.
# @param [Hash] options
# The html options for the input field.
#
# @return [String] The html input field based on the +options+ specified
#
# @example
# text_field_tag :username, :class => 'long'
#
# @api public
def text_field_tag(name, options={})
options.reverse_merge!(:name => name)
input_tag(:text, options)
end
##
# Constructs a hidden field input from the given options
#
# @macro input_field_doc
#
# @example
# hidden_field_tag :session_key, :value => "__secret__"
#
# @api public
def hidden_field_tag(name, options={})
options.reverse_merge!(:name => name)
input_tag(:hidden, options)
end
##
# Constructs a text area input from the given options
#
# @macro input_field_doc
#
# @example
# text_area_tag :username, :class => 'long', :value => "Demo?"
#
# @api public
def text_area_tag(name, options={})
options.reverse_merge!(:name => name, :rows => "", :cols => "")
content_tag(:textarea, options.delete(:value).to_s, options)
end
##
# Constructs a password field input from the given options
#
# @macro input_field_doc
#
# @example
# password_field_tag :password, :class => 'long'
#
# @api public
def password_field_tag(name, options={})
options.reverse_merge!(:name => name)
input_tag(:password, options)
end
##
# Constructs a check_box from the given options
#
# @macro input_field_doc
#
# @example
# check_box_tag :remember_me, :value => 'Yes'
#
# @api public
def check_box_tag(name, options={})
options.reverse_merge!(:name => name, :value => '1')
input_tag(:checkbox, options)
end
##
# Constructs a radio_button from the given options
#
# @macro input_field_doc
#
# @example
# radio_button_tag :remember_me, :value => 'true'
#
# @api public
def radio_button_tag(name, options={})
options.reverse_merge!(:name => name)
input_tag(:radio, options)
end
##
# Constructs a file field input from the given options
#
# @macro input_field_doc
#
# @example
# file_field_tag :photo, :class => 'long'
#
# @api public
def file_field_tag(name, options={})
options.reverse_merge!(:name => name)
input_tag(:file, options)
end
##
# Constructs a select from the given options
#
# @example
# options = [['caption', 'value'], ['Green', 'green1'], ['Blue', 'blue1'], ['Black', "black1"]]
# options = ['option', 'red', 'yellow' ]
# select_tag(:favorite_color, :options => ['red', 'yellow'], :selected => 'green1')
# select_tag(:country, :collection => @countries, :fields => [:name, :code], :include_blank => 'None')
#
# # Optgroups can be generated using :grouped_options => (Hash or nested Array)
# grouped_options = [['Friends',['Yoda',['Obiwan',1]]],['Enemies',['Palpatine',['Darth Vader',3]]]]
# grouped_options = {'Friends' => ['Yoda',['Obiwan',1]],'Enemies' => ['Palpatine',['Darth Vader',3]]}
# select_tag(:color, :grouped_options => [['warm',['red','yellow']],['cool',['blue', 'purple']]])
#
# # Optgroups can be generated using :grouped_options => (Hash or nested Array)
# grouped_options = [['Friends',['Yoda',['Obiwan',1]]],['Enemies',['Palpatine',['Darth Vader',3]]]]
# grouped_options = {'Friends' => ['Yoda',['Obiwan',1]],'Enemies' => ['Palpatine',['Darth Vader',3]]}
# select_tag(:color, :grouped_options => [['warm',['red','yellow']],['cool',['blue', 'purple']]])
#
# @param [String] name
# The name of the input field.
# @param [Hash] options
# The html options for the input field.
# @option options [Array<String, Array>] :options
# Explicit options to display in the select. Can be strings or string tuples.
# @option options [Array<Array>] :grouped_options
# List of options for each group in the select. See examples for details.
# @option options [Array<Object>] :collection
# Collection of objects used as options in the select.
# @option options [Array<Symbol>] :fields
# The attributes used as "label" and "value" for each +collection+ object.
# @option options [String] :selected (nil)
# The option value initially selected.
# @option options [Boolean] :include_blank (false)
# Include a blank option in the select.
# @option options [Boolean] :multiple (false)
# Allow multiple options to be selected at once.
#
# @return [String] The html input field based on the +options+ specified
#
# @api public
def select_tag(name, options={})
options.reverse_merge!(:name => name)
collection, fields = options.delete(:collection), options.delete(:fields)
options[:options] = options_from_collection(collection, fields) if collection
prompt = options.delete(:include_blank)
select_options_html = if options[:options]
options_for_select(options.delete(:options), options.delete(:selected))
elsif options[:grouped_options]
grouped_options_for_select(options.delete(:grouped_options), options.delete(:selected), prompt)
end
select_options_html = select_options_html.unshift(blank_option(prompt)) if select_options_html.is_a?(Array)
options.merge!(:name => "#{options[:name]}[]") if options[:multiple]
content_tag(:select, select_options_html, options)
end
##
# Constructs a button input from the given options
#
# @param [String] caption
# The caption for the button.
# @param [Hash] options
# The html options for the input field.
#
# @return [String] The html button based on the +options+ specified.
#
# @example
# button_tag "Cancel", :class => 'clear'
#
# @api public
def button_tag(caption, options = {})
options.reverse_merge!(:value => caption)
input_tag(:button, options)
end
##
# Constructs a submit button from the given options
#
# @param [String] caption
# The caption for the submit button.
# @param [Hash] options
# The html options for the input field.
#
# @return [String] The html submit button based on the +options+ specified.
#
# @example
# submit_tag "Create", :class => 'success'
#
# @api public
def submit_tag(caption="Submit", options={})
options.reverse_merge!(:value => caption)
input_tag(:submit, options)
end
# Constructs a submit button from the given options
#
# @param [String] source
# The source image path for the button.
# @param [Hash] options
# The html options for the input field.
#
# @return [String] The html image button based on the +options+ specified.
#
# @example
# submit_tag "Create", :class => 'success'
#
# @api public
def image_submit_tag(source, options={})
options.reverse_merge!(:src => image_path(source))
input_tag(:image, options)
end
protected
##
# Returns an array of option items for a select field based on the given collection
# fields is an array containing the fields to display from each item in the collection
#
def options_from_collection(collection, fields)
collection.map { |item| [ item.send(fields.first), item.send(fields.last) ] }
end
#
# Returns the options tags for a select based on the given option items
#
def options_for_select(option_items, selected_value=nil)
return '' if option_items.blank?
option_items.map do |caption, value|
value ||= caption
content_tag(:option, caption, :value => value, :selected => option_is_selected?(value, caption, selected_value))
end
end
#
# Returns the optgroups with options tags for a select based on the given :grouped_options items
#
def grouped_options_for_select(collection, selected=nil, prompt=false)
if collection.is_a?(Hash)
collection.map do |key, value|
content_tag :optgroup, :label => key do
options_for_select(value, selected)
end
end
elsif collection.is_a?(Array)
collection.map do |optgroup|
content_tag :optgroup, :label => optgroup.first do
options_for_select(optgroup.last, selected)
end
end
end
end
#
# Returns the blank option serving as a prompt if passed
#
def blank_option(prompt)
return unless prompt
case prompt
when String then content_tag(:option, prompt, :value => '')
when Array then content_tag(:option, prompt.first, :value => prompt.last)
else content_tag(:option, '', :value => '')
end
end
private
##
# Returns the FormBuilder class to use based on all available setting sources
# If explicitly defined, returns that, otherwise returns defaults.
#
# @example
# configured_form_builder_class(nil) => StandardFormBuilder
#
# @api private
def configured_form_builder_class(explicit_builder=nil)
default_builder = self.respond_to?(:settings) && self.settings.default_builder
configured_builder = explicit_builder || default_builder || 'StandardFormBuilder'
configured_builder = "Padrino::Helpers::FormBuilder::#{configured_builder}".constantize if configured_builder.is_a?(String)
configured_builder
end
##
# Returns an initialized builder instance for the given object and settings
#
# @example
# builder_instance(@account, :nested => { ... }) => <FormBuilder>
#
# @api private
def builder_instance(object, settings={})
builder_class = configured_form_builder_class(settings.delete(:builder))
builder_class.new(self, object, settings)
end
##
# Returns whether the option should be selected or not
#
# @example
# option_is_selected?("red", "Red", ["red", "blue"]) => true
# option_is_selected?("red", "Red", ["green", "blue"]) => false
#
# @api private
def option_is_selected?(value, caption, selected_values)
Array(selected_values).any? do |selected|
[value.to_s, caption.to_s].include?(selected.to_s)
end
end
end # FormHelpers
end # Helpers
end # Padrino

View file

@ -0,0 +1,381 @@
module Padrino
module Helpers
###
# Helpers related to formatting or manipulating text within templates.
#
module FormatHelpers
##
# Returns escaped text to protect against malicious content
#
# @param [String] text
# Unsanitized HTML string that needs to be escaped.
#
# @return [String] HTML with escaped characters.
#
# @example
# escape_html("<b>Hey<b>") => "&lt;b&gt;Hey&lt;b;gt;"
# h("Me & Bob") => "Me &amp; Bob"
#
# @api public
def escape_html(text)
Rack::Utils.escape_html(text)
end
alias h escape_html
alias sanitize_html escape_html
##
# Returns escaped text to protect against malicious content
# Returns blank if the text is empty
#
# @param [String] text
# Unsanitized HTML string that needs to be escaped.
# @param [String] blank_text
# Text to return if escaped text is blank.
#
# @return [String] HTML with escaped characters or the value specified if blank.
#
# @example
# h!("Me & Bob") => "Me &amp; Bob"
# h!("", "Whoops") => "Whoops"
#
# @api public
def h!(text, blank_text = '&nbsp;')
return blank_text if text.nil? || text.empty?
h text
end
##
# Strips all HTML tags from the html
#
# @param [String] html
# The HTML for which to strip tags.
#
# @return [String] HTML with tags stripped.
#
# @example
# strip_tags("<b>Hey</b>") => "Hey"
#
# @api public
def strip_tags(html)
html.gsub(/<\/?[^>]*>/, "") if html
end
##
# Returns text transformed into HTML using simple formatting rules. Two or more consecutive newlines(\n\n) are considered
# as a paragraph and wrapped in <p> or your own tags. One newline (\n) is considered as a linebreak and a <br /> tag is appended.
# This method does not remove the newlines from the text.
#
# @param [String] text
# The simple text to be formatted.
# @param [Hash] options
# Formatting options for the text. Can accept html options for the wrapper tag.
# @option options [Symbol] :tag (p)
# The html tag to use for formatting newlines.
#
# @return [String] The text formatted as simple HTML.
#
# @example
# simple_format("hello\nworld") # => "<p>hello<br/>world</p>"
# simple_format("hello\nworld", :tag => :div, :class => :foo) # => "<div class="foo">hello<br/>world</div>"
#
# @api public
def simple_format(text, options={})
t = options.delete(:tag) || :p
start_tag = tag(t, options.merge(:open => true))
text = text.to_s.dup
text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n
text.gsub!(/\n\n+/, "</#{t}>\n\n#{start_tag}") # 2+ newline -> paragraph
text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br
text.insert 0, start_tag
text << "</#{t}>"
end
##
# Attempts to pluralize the singular word unless count is 1. If plural is supplied, it will use that when count is > 1,
# otherwise it will use the Inflector to determine the plural form
#
# @param [Fixnum] count
# The count which determines pluralization.
# @param [String] singular
# The word to be pluralized if appropriate based on +count+.
# @param [String] plural
# Explicit pluralized word to be used; if not specified uses inflector.
#
# @return [String] The properly pluralized word.
#
# @example
# pluralize(2, 'person') => '2 people'
#
# @api public
def pluralize(count, singular, plural = nil)
"#{count || 0} " + ((count == 1 || count == '1') ? singular : (plural || singular.pluralize))
end
##
# Truncates a given text after a given :length if text is longer than :length (defaults to 30).
# The last characters will be replaced with the :omission (defaults to "…") for a total length not exceeding :length.
#
# @param [String] text
# The text to be truncated.
# @param [Hash] options
# Formatting options for the truncation.
# @option options [Fixnum] :length (30)
# The number of characters before truncation occurs.
# @option options [String] :omission ("...")
# The characters that are placed after the truncated text.
#
# @return [String] The text truncated after the given number of characters.
#
# @example
# truncate("Once upon a time in a world far far away", :length => 8) => "Once upon..."
#
# @api public
def truncate(text, options={})
options.reverse_merge!(:length => 30, :omission => "...")
if text
len = options[:length] - options[:omission].length
chars = text
(chars.length > options[:length] ? chars[0...len] + options[:omission] : text).to_s
end
end
##
# Truncates words of a given text after a given :length if number of words in text is more than :length (defaults to 30).
# The last words will be replaced with the :omission (defaults to "…") for a total number of words not exceeding :length.
#
# @param [String] text
# The text to be truncated.
# @param [Hash] options
# Formatting options for the truncation.
# @option options [Fixnum] :length (30)
# The number of words before truncation occurs.
# @option options [String] :omission ("...")
# The characters that are placed after the truncated text.
#
# @return [String] The text truncated after the given number of words.
#
# @example
# truncate_words("Once upon a time in a world far far away", :length => 8) => "Once upon a time in a world far..."
#
# @api public
def truncate_words(text, options={})
options.reverse_merge!(:length => 30, :omission => "...")
if text
words = text.split()
words[0..(options[:length]-1)].join(' ') + (words.length > options[:length] ? options[:omission] : '')
end
end
##
# Wraps the text into lines no longer than line_width width.
# This method breaks on the first whitespace character that does not exceed line_width (which is 80 by default).
#
# @overload word_wrap(text, options={})
# @param [String] text
# The text to be wrapped.
# @param [Hash] options
# Formatting options for the wrapping.
# @option options [Fixnum] :line_width (80)
# The line width before a wrap should occur.
#
# @return [String] The text with line wraps for lines longer then +line_width+
#
# @example
# word_wrap('Once upon a time', :line_width => 8) => "Once upon\na time"
#
# @api public
def word_wrap(text, *args)
options = args.extract_options!
unless args.blank?
options[:line_width] = args[0] || 80
end
options.reverse_merge!(:line_width => 80)
text.split("\n").map do |line|
line.length > options[:line_width] ? line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n").strip : line
end * "\n"
end
##
# Highlights one or more words everywhere in text by inserting it into a :highlighter string.
#
# The highlighter can be customized by passing :+highlighter+ as a single-quoted string
# with \1 where the phrase is to be inserted.
#
# @overload highlight(text, words, options={})
# @param [String] text
# The text that will be searched.
# @param [String] words
# The words to be highlighted in the +text+.
# @param [Hash] options
# Formatting options for the highlight.
# @option options [String] :highlighter ('<strong class="highlight">\1</strong>')
# The html pattern for wrapping the highlighted words.
#
# @return [String] The text with the words specified wrapped with highlighted spans.
#
# @example
# highlight('Lorem ipsum dolor sit amet', 'dolor')
# # => Lorem ipsum <strong class="highlight">dolor</strong> sit amet
#
# highlight('Lorem ipsum dolor sit amet', 'dolor', :highlighter => '<span class="custom">\1</span>')
# # => Lorem ipsum <strong class="custom">dolor</strong> sit amet
#
# @api public
def highlight(text, words, *args)
options = args.extract_options!
options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>')
if text.blank? || words.blank?
text
else
match = Array(words).map { |p| Regexp.escape(p) }.join('|')
text.gsub(/(#{match})(?!(?:[^<]*?)(?:["'])[^<>]*>)/i, options[:highlighter])
end
end
##
# Reports the approximate distance in time between two Time or Date objects or integers as seconds.
# Set +include_seconds+ to true if you want more detailed approximations when distance < 1 min, 29 secs
# Distances are reported based on the following table:
#
# 0 <-> 29 secs # => less than a minute
# 30 secs <-> 1 min, 29 secs # => 1 minute
# 1 min, 30 secs <-> 44 mins, 29 secs # => [2..44] minutes
# 44 mins, 30 secs <-> 89 mins, 29 secs # => about 1 hour
# 89 mins, 29 secs <-> 23 hrs, 59 mins, 29 secs # => about [2..24] hours
# 23 hrs, 59 mins, 29 secs <-> 47 hrs, 59 mins, 29 secs # => 1 day
# 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days
# 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month
# 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months
# 1 yr <-> 1 yr, 3 months # => about 1 year
# 1 yr, 3 months <-> 1 yr, 9 months # => over 1 year
# 1 yr, 9 months <-> 2 yr minus 1 sec # => almost 2 years
# 2 yrs <-> max time or date # => (same rules as 1 yr)
#
# With +include_seconds+ = true and the difference < 1 minute 29 seconds:
# 0-4 secs # => less than 5 seconds
# 5-9 secs # => less than 10 seconds
# 10-19 secs # => less than 20 seconds
# 20-39 secs # => half a minute
# 40-59 secs # => less than a minute
# 60-89 secs # => 1 minute
#
# @param [Time] from_time
# The time to be compared against +to_time+ in order to approximate the distance.
# @param [Time] to_time
# The time to be compared against +from_time+ in order to approximate the distance.
# @param [Boolean] include_seconds
# Set true for more detailed approximations.
# @param [Hash] options
# Flags for the approximation.
# @option options [String] :locale
# The translation locale to be used for approximating the time.
#
# @return [String] The time formatted as a relative string.
#
# @example
# from_time = Time.now
# distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour
# distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour
# distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute
# distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds
# distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years
# distance_of_time_in_words(from_time, from_time + 60.hours) # => about 3 days
# distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute
# distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
# distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
# distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
# distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years
# distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years
# to_time = Time.now + 6.years + 19.days
# distance_of_time_in_words(from_time, to_time, true) # => about 6 years
# distance_of_time_in_words(to_time, from_time, true) # => about 6 years
# distance_of_time_in_words(Time.now, Time.now) # => less than a minute
#
# @api public
def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {})
from_time = from_time.to_time if from_time.respond_to?(:to_time)
to_time = to_time.to_time if to_time.respond_to?(:to_time)
distance_in_minutes = (((to_time.to_i - from_time.to_i).abs)/60).round
distance_in_seconds = ((to_time.to_i - from_time.to_i).abs).round
I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale|
case distance_in_minutes
when 0..1
return distance_in_minutes == 0 ?
locale.t(:less_than_x_minutes, :count => 1) :
locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds
case distance_in_seconds
when 0..4 then locale.t :less_than_x_seconds, :count => 5
when 5..9 then locale.t :less_than_x_seconds, :count => 10
when 10..19 then locale.t :less_than_x_seconds, :count => 20
when 20..39 then locale.t :half_a_minute
when 40..59 then locale.t :less_than_x_minutes, :count => 1
else locale.t :x_minutes, :count => 1
end
when 2..44 then locale.t :x_minutes, :count => distance_in_minutes
when 45..89 then locale.t :about_x_hours, :count => 1
when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
when 1440..2529 then locale.t :x_days, :count => 1
when 2530..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
when 43200..86399 then locale.t :about_x_months, :count => 1
when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round
else
distance_in_years = distance_in_minutes / 525600
minute_offset_for_leap_year = (distance_in_years / 4) * 1440
remainder = ((distance_in_minutes - minute_offset_for_leap_year) % 525600)
if remainder < 131400
locale.t(:about_x_years, :count => distance_in_years)
elsif remainder < 394200
locale.t(:over_x_years, :count => distance_in_years)
else
locale.t(:almost_x_years, :count => distance_in_years + 1)
end
end
end
end
##
# Like distance_of_time_in_words, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>.
#
# @param [Time] from_time
# The time to be compared against now in order to approximate the distance.
# @param [Boolean] include_seconds
# Set true for more detailed approximations.
#
# @return [String] The time formatted as a relative string.
#
# @example
# time_ago_in_words(3.minutes.from_now) # => 3 minutes
# time_ago_in_words(Time.now - 15.hours) # => 15 hours
# time_ago_in_words(Time.now) # => less than a minute
#
# @api public
def time_ago_in_words(from_time, include_seconds = false)
distance_of_time_in_words(from_time, Time.now, include_seconds)
end
##
# Used in xxxx.js.erb files to escape html so that it can be passed to javascript from Padrino
#
# @param [String] html
# The html content to be escaped into javascript compatible format.
#
# @return [String] The html escaped for javascript passing.
#
# @example
# js_escape_html("<h1>Hey</h1>")
#
# @api public
def js_escape_html(html_content)
return '' unless html_content
javascript_mapping = { '\\' => '\\\\', '</' => '<\/', "\r\n" => '\n', "\n" => '\n', "\r" => '\n', '"' => '\\"', "'" => "\\'" }
html_content.gsub(/(\\|<\/|\r\n|[\n\r"'])/) { javascript_mapping[$1] }
end
end # FormatHelpers
end # Helpers
end # Padrino

View file

@ -0,0 +1,103 @@
cs:
number:
# Used in number_with_delimiter()
# These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
format:
# Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
separator: ","
# Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three)
delimiter: " "
# Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
precision: 3
# Used in number_to_currency()
currency:
format:
# Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
format: "%n %u"
unit: "Kč"
# These three are to override number.format and are optional
separator: ","
delimiter: " "
precision: 2
# Used in number_to_percentage()
percentage:
format:
# These three are to override number.format and are optional
# separator:
delimiter: ""
# precision:
# Used in number_to_precision()
precision:
format:
# These three are to override number.format and are optional
# separator:
delimiter: ""
# precision:
# Used in number_to_human_size()
human:
format:
# These three are to override number.format and are optional
# separator:
delimiter: ""
precision: 1
storage_units:
# Storage units output formatting.
# %u is the storage unit, %n is the number (default: 2 MB)
format: "%n %u"
units:
byte:
one: "Byte"
other: "Bytes"
kb: "KB"
mb: "MB"
gb: "GB"
tb: "TB"
# Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
datetime:
distance_in_words:
half_a_minute: "půl minutou"
less_than_x_seconds:
one: "asi před sekundou"
other: "asi před %{count} sekundami"
x_seconds:
one: "sekundou"
other: "%{count} sekundami"
less_than_x_minutes:
one: "před necelou minutou"
other: "před ani ne %{count} minutami"
x_minutes:
one: "minutou"
other: "%{count} minutami"
about_x_hours:
one: "asi hodinou"
other: "asi %{count} hodinami"
x_days:
one: "24 hodinami"
other: "%{count} dny"
about_x_months:
one: "asi měsícem"
other: "asi %{count} měsíci"
x_months:
one: "měsícem"
other: "%{count} měsíci"
about_x_years:
one: "asi rokem"
other: "asi %{count} roky"
over_x_years:
one: "více než před rokem"
other: "více než %{count} roky"
almost_x_years:
one: "skoro rokem"
other: "almost %{count} roků"
models:
errors:
template:
header:
one: "Při ukládání %{model} došlo k chybě a nebylo jej možné uložit"
other: "Při ukládání %{model} došlo ke %{count} chybám a nebylo možné jej uložit"
body: "Následující pole obsahují chybně vyplněné údaje:"

View file

@ -0,0 +1,91 @@
da:
number:
format:
separator: ","
delimiter: "."
precision: 3
currency:
format:
format: "%u %n"
unit: "DKK"
separator: ","
delimiter: "."
precision: 2
precision:
format:
# separator:
delimiter: ""
# precision:
human:
format:
# separator:
delimiter: ""
precision: 1
storage_units:
# Storage units output formatting.
# %u is the storage unit, %n is the number (default: 2 MB)
format: "%n %u"
units:
byte:
one: "Byte"
other: "Bytes"
kb: "KB"
mb: "MB"
gb: "GB"
tb: "TB"
percentage:
format:
# separator:
delimiter: ""
# precision:
datetime:
distance_in_words:
half_a_minute: "et halvt minut"
less_than_x_seconds:
one: "mindre end et sekund"
other: "mindre end %{count} sekunder"
x_seconds:
one: "et sekund"
other: "%{count} sekunder"
less_than_x_minutes:
one: "mindre end et minut"
other: "mindre end %{count} minutter"
x_minutes:
one: "et minut"
other: "%{count} minutter"
about_x_hours:
one: "cirka en time"
other: "cirka %{count} timer"
x_days:
one: "en dag"
other: "%{count} dage"
about_x_months:
one: "cirka en måned"
other: "cirka %{count} måneder"
x_months:
one: "en måned"
other: "%{count} måneder"
about_x_years:
one: "cirka et år"
other: "cirka %{count} år"
over_x_years:
one: "mere end et år"
other: "mere end %{count} år"
almost_x_years:
one: "næsten et år"
other: "næsten %{count} years"
prompts:
second: "Sekund"
minute: "Minut"
hour: "Time"
day: "Dag"
month: "Måned"
year: "År"
models:
errors:
template:
header:
one: "En fejl forhindrede %{model} i at blive gemt"
other: "%{count} fejl forhindrede denne %{model} i at blive gemt"
body: "Der var problemer med følgende felter:"

View file

@ -0,0 +1,81 @@
de:
number:
format:
precision: 2
separator: ','
delimiter: '.'
currency:
format:
unit: '€'
format: '%n%u'
separator: ","
delimiter: "."
precision: 2
percentage:
format:
delimiter: ""
precision:
format:
delimiter: ""
human:
format:
delimiter: ""
precision: 1
storage_units:
# Storage units output formatting.
# %u is the storage unit, %n is the number (default: 2 MB)
format: "%n %u"
units:
byte:
one: "Byte"
other: "Bytes"
kb: "KB"
mb: "MB"
gb: "GB"
tb: "TB"
datetime:
distance_in_words:
half_a_minute: 'eine halbe Minute'
less_than_x_seconds:
zero: 'weniger als 1 Sekunde'
one: 'weniger als 1 Sekunde'
other: 'weniger als %{count} Sekunden'
x_seconds:
one: '1 Sekunde'
other: '%{count} Sekunden'
less_than_x_minutes:
zero: 'weniger als 1 Minute'
one: 'weniger als 1 Minute'
other: 'weniger als %{count} Minuten'
x_minutes:
one: '1 Minute'
other: '%{count} Minuten'
about_x_hours:
one: 'etwa 1 Stunde'
other: 'etwa %{count} Stunden'
x_days:
one: '1 Tag'
other: '%{count} Tage'
about_x_months:
one: 'etwa 1 Monat'
other: 'etwa %{count} Monate'
x_months:
one: '1 Monat'
other: '%{count} Monate'
about_x_years:
one: 'etwa 1 Jahr'
other: 'etwa %{count} Jahre'
over_x_years:
one: 'mehr als 1 Jahr'
other: 'mehr als %{count} Jahre'
almost_x_years:
one: 'fast ein Jahr'
other: 'fast %{count} Jahre'
models:
errors:
template:
header:
one: "1 Fehler verhindert, dass Objekt %{model} gesichert werden kann"
other: "%{count} Fehler verhindern, dass Objekt %{model} gesichert werden kann"
body: "Es existieren Probleme mit den folgenden Feldern:"

View file

@ -0,0 +1,103 @@
en:
number:
# Used in number_with_delimiter()
# These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
format:
# Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
separator: "."
# Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three)
delimiter: ","
# Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
precision: 3
# Used in number_to_currency()
currency:
format:
# Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
format: "%u%n"
unit: "$"
# These three are to override number.format and are optional
separator: "."
delimiter: ","
precision: 2
# Used in number_to_percentage()
percentage:
format:
# These three are to override number.format and are optional
# separator:
delimiter: ""
# precision:
# Used in number_to_precision()
precision:
format:
# These three are to override number.format and are optional
# separator:
delimiter: ""
# precision:
# Used in number_to_human_size()
human:
format:
# These three are to override number.format and are optional
# separator:
delimiter: ""
precision: 1
storage_units:
# Storage units output formatting.
# %u is the storage unit, %n is the number (default: 2 MB)
format: "%n %u"
units:
byte:
one: "Byte"
other: "Bytes"
kb: "KB"
mb: "MB"
gb: "GB"
tb: "TB"
# Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
datetime:
distance_in_words:
half_a_minute: "half a minute"
less_than_x_seconds:
one: "less than 1 second"
other: "less than %{count} seconds"
x_seconds:
one: "1 second"
other: "%{count} seconds"
less_than_x_minutes:
one: "less than a minute"
other: "less than %{count} minutes"
x_minutes:
one: "1 minute"
other: "%{count} minutes"
about_x_hours:
one: "about 1 hour"
other: "about %{count} hours"
x_days:
one: "1 day"
other: "%{count} days"
about_x_months:
one: "about 1 month"
other: "about %{count} months"
x_months:
one: "1 month"
other: "%{count} months"
about_x_years:
one: "about 1 year"
other: "about %{count} years"
over_x_years:
one: "over 1 year"
other: "over %{count} years"
almost_x_years:
one: "almost 1 year"
other: "almost %{count} years"
models:
errors:
template:
header:
one: "1 error prohibited this %{model} from being saved"
other: "%{count} errors prohibited this %{model} from being saved"
body: "There were problems with the following fields:"

View file

@ -0,0 +1,103 @@
es:
number:
# Used in number_with_delimiter()
# These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
format:
# Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
separator: "."
# Delimets thousands (e.g. 1,000,000 is a million) (always in groups of three)
delimiter: ","
# Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
precision: 2
# Used in number_to_currency()
currency:
format:
# Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
format: "%u%n"
unit: "$"
# These three are to override number.format and are optional
separator: "."
delimiter: ","
precision: 2
# Used in number_to_percentage()
percentage:
format:
# These three are to override number.format and are optional
# separator:
delimiter: ""
# precision:
# Used in number_to_precision()
precision:
format:
# These three are to override number.format and are optional
# separator:
delimiter: ""
# precision:
# Used in number_to_human_size()
human:
format:
# These three are to override number.format and are optional
# separator:
delimiter: ""
precision: 1
storage_units:
# Storage units output formatting.
# %u is the storage unit, %n is the number (default: 2 MB)
format: "%n %u"
units:
byte:
one: "Byte"
other: "Bytes"
kb: "KB"
mb: "MB"
gb: "GB"
tb: "TB"
# Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
datetime:
distance_in_words:
half_a_minute: "medio minuto"
less_than_x_seconds:
one: "menos de un segundo"
other: "menos de %{count} segundos"
x_seconds:
one: "1 segundo"
other: "%{count} segundos"
less_than_x_minutes:
one: "menos de un minuto"
other: "menos de %{count} minutos"
x_minutes:
one: "1 minuto"
other: "%{count} minutos"
about_x_hours:
one: "hace 1 hora"
other: "hace %{count} horas"
x_days:
one: "1 día"
other: "%{count} días"
about_x_months:
one: "hace 1 mes"
other: "hace %{count} meses"
x_months:
one: "1 mes"
other: "%{count} meses"
about_x_years:
one: "hace 1 año"
other: "hace %{count} años"
over_x_years:
one: "hace más de 1 año"
other: "hace más de %{count} años"
almost_x_years:
one: "casi 1 año"
other: "casi %{count} años"
models:
errors:
template:
header:
one: "1 error impidió que %{model} se guardara"
other: "%{count} erroress impidieron que %{model} se guardara"
body: "Hay problemas con los siguientes campos:"

View file

@ -0,0 +1,80 @@
fr:
number:
format:
precision: 2
separator: ','
delimiter: '.'
currency:
format:
unit: '€'
format: '%n%u'
separator: "."
delimiter: ""
precision: 2
percentage:
format:
delimiter: ""
precision:
format:
delimiter: ""
human:
format:
delimiter: ""
precision: 1
storage_units:
# Storage units output formatting.
# %u is the storage unit, %n is the number (default: 2 MB)
format: "%n %u"
units:
byte:
one: "Byte"
other: "Bytes"
kb: "KB"
mb: "MB"
gb: "GB"
tb: "TB"
datetime:
distance_in_words:
half_a_minute: "une demi minute"
less_than_x_seconds:
one: "infèrieur à une seconde"
other: "infèrieur à %{count} secondes"
x_seconds:
one: "1 seconde"
other: "%{count} secondes"
less_than_x_minutes:
one: "infèrieur à 1 minute"
other: "infèrieur à %{count} minutes"
x_minutes:
one: "1 minute"
other: "%{count} minutes"
about_x_hours:
one: "environ 1 heure"
other: "environ %{count} heures"
x_days:
one: "1 jour"
other: "%{count} jours"
about_x_months:
one: "environ 1 mois"
other: "environ %{count} mois"
x_months:
one: "1 mois"
other: "%{count} mois"
about_x_years:
one: "environ 1 ans"
other: "environ %{count} ans"
over_x_years:
one: "plus d'un an"
other: "plus de %{count} ans"
almost_x_years:
one: "près d'un ans"
other: "près de %{count} years"
models:
errors:
template:
header:
one: "1 erreur a empêché ce %{model} d'être sauvé"
other: "%{count} erreurs ont empêché ce %{model} d'être sauvé"
body: "Il y avait des problèmes avec les champs suivants:"

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