mirror of
https://github.com/sinatra/sinatra
synced 2023-03-27 23:18:01 -04:00
8ae87a87f3
* Initialize rubocop * Style/StringLiterals: prefer single quotes * Style/AndOr: use `&&` and `||`, instead of `and` and `or` * Style/HashSyntax: use new hash syntax * Layout/EmptyLineAfterGuardClause: add empty lines after guard clause * Style/SingleLineMethods: temporary disable It breaks layout of the code, it is better to fix it manually * Style/Proc: prefer `proc` vs `Proc.new` * Disable Lint/AmbiguousBlockAssociation It affects proc definitions for sinatra DSL * Disable Style/CaseEquality * Lint/UnusedBlockArgument: put underscore in front of it * Style/Alias: prefer alias vs alias_method in a class body * Layout/EmptyLineBetweenDefs: add empty lines between defs * Style/ParallelAssignment: don't use parallel assigment * Style/RegexpLiteral: prefer %r for regular expressions * Naming/UncommunicativeMethodParamName: fix abbrevs * Style/PerlBackrefs: disable cop * Layout/SpaceAfterComma: add missing spaces * Style/Documentation: disable cop * Style/FrozenStringLiteralComment: add frozen_string_literal * Layout/AlignHash: align hash * Layout/ExtraSpacing: allow for alignment * Layout/SpaceAroundOperators: add missing spaces * Style/Not: prefer `!` instead of `not` * Style/GuardClause: add guard conditions * Style/MutableConstant: freeze contants * Lint/IneffectiveAccessModifier: disable cop * Lint/RescueException: disable cop * Style/SpecialGlobalVars: disable cop * Layout/DotPosition: fix position of dot for multiline method chains * Layout/SpaceInsideArrayLiteralBrackets: don't use spaces inside arrays * Layout/SpaceInsideBlockBraces: add space for blocks * Layout/SpaceInsideHashLiteralBraces: add spaces for hashes * Style/FormatString: use format string syntax * Style/StderrPuts: `warn` is preferable to `$stderr.puts` * Bundler/DuplicatedGem: disable cop * Layout/AlignArray: fix warning * Lint/AssignmentInCondition: remove assignments from conditions * Layout/IndentHeredoc: disable cop * Layout/SpaceInsideParens: remove extra spaces * Lint/UnusedMethodArgument: put underscore in front of unused arg * Naming/RescuedExceptionsVariableName: use `e` for exceptions * Style/CommentedKeyword: put comments before the method * Style/FormatStringToken: disable cop * Style/MultilineIfModifier: move condition before the method * Style/SignalException: prefer `raise` to `fail` * Style/SymbolArray: prefer %i for array of symbols * Gemspec/OrderedDependencies: Use alphabetical order for dependencies * Lint/UselessAccessModifier: disable cop * Naming/HeredocDelimiterNaming: change delimiter's name * Style/ClassCheck: prefer `is_a?` to `kind_of?` * Style/ClassVars: disable cop * Style/Encoding: remove coding comment * Style/RedundantParentheses: remove extra parentheses * Style/StringLiteralsInInterpolation: prefer singl quotes * Layout/AlignArguments: fix alignment * Layout/ClosingHeredocIndentation: align heredoc * Layout/EmptyLineAfterMagicComment: add empty line * Set RubyVersion for rubocop * Lint/UselessAssignment: disable cop * Style/EmptyLiteral: disable cop Causes test failures * Minor code-style fixes with --safe-auto-correct option * Disable the rest of the cops that cause warnings It would be easier to re-enable them in separate PRs * Add rubocop check to the default Rake task * Update to rubocop 1.32.0 * Rubocop updates for rack-protection and sinatra-contrib * Disable Style/SlicingWithRange cop * Make suggested updates Co-authored-by: Jordan Owens <jkowens@gmail.com>
375 lines
10 KiB
Ruby
375 lines
10 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'sinatra/base'
|
|
require 'mustermann'
|
|
|
|
module Sinatra
|
|
# = Sinatra::Namespace
|
|
#
|
|
# <tt>Sinatra::Namespace</tt> is an extension that adds namespaces to an
|
|
# application. This namespaces will allow you to share a path prefix for the
|
|
# routes within the namespace, and define filters, conditions and error
|
|
# handlers exclusively for them. Besides that, you can also register helpers
|
|
# and extensions that will be used only within the namespace.
|
|
#
|
|
# == Usage
|
|
#
|
|
# Once you have loaded the extension (see below), you can use the +namespace+
|
|
# method to define namespaces in your application.
|
|
#
|
|
# You can define a namespace by a path prefix:
|
|
#
|
|
# namespace '/blog' do
|
|
# get { haml :blog }
|
|
# get '/:entry_permalink' do
|
|
# @entry = Entry.find_by_permalink!(params[:entry_permalink])
|
|
# haml :entry
|
|
# end
|
|
#
|
|
# # More blog routes...
|
|
# end
|
|
#
|
|
# by a condition:
|
|
#
|
|
# namespace :host_name => 'localhost' do
|
|
# get('/admin/dashboard') { haml :dashboard }
|
|
# get('/admin/login') { haml :login }
|
|
#
|
|
# # More admin routes...
|
|
# end
|
|
#
|
|
# or both:
|
|
#
|
|
# namespace '/admin', :host_name => 'localhost' do
|
|
# get('/dashboard') { haml :dashboard }
|
|
# get('/login') { haml :login }
|
|
# post('/login') { login_user }
|
|
#
|
|
# # More admin routes...
|
|
# end
|
|
#
|
|
# Regex is also accepted:
|
|
#
|
|
# namespace /\/posts\/([^\/&?]+)\// do
|
|
# get { haml :blog }
|
|
#
|
|
# # More blog routes...
|
|
# end
|
|
#
|
|
# When you define a filter or an error handler, or register an extension or a
|
|
# set of helpers within a namespace, they only affect the routes defined in
|
|
# it. For instance, lets define a before filter to prevent the access of
|
|
# unauthorized users to the admin section of the application:
|
|
#
|
|
# namespace '/admin' do
|
|
# helpers AdminHelpers
|
|
# before { authenticate unless request.path_info == '/admin/login' }
|
|
#
|
|
# get '/dashboard' do
|
|
# # Only authenticated users can access here...
|
|
# haml :dashboard
|
|
# end
|
|
#
|
|
# # More admin routes...
|
|
# end
|
|
#
|
|
# get '/' do
|
|
# # Any user can access here...
|
|
# haml :index
|
|
# end
|
|
#
|
|
# Well, they actually also affect the nested namespaces:
|
|
#
|
|
# namespace '/admin' do
|
|
# helpers AdminHelpers
|
|
# before { authenticate unless request.path_info == '/admin/login' }
|
|
#
|
|
# namespace '/users' do
|
|
# get do
|
|
# # Only authenticated users can access here...
|
|
# @users = User.all
|
|
# haml :users
|
|
# end
|
|
#
|
|
# # More user admin routes...
|
|
# end
|
|
#
|
|
# # More admin routes...
|
|
# end
|
|
#
|
|
# Redirecting within the namespace can be done using redirect_to:
|
|
#
|
|
# namespace '/admin' do
|
|
# get '/foo' do
|
|
# redirect_to '/bar' # Redirects to /admin/bar
|
|
# end
|
|
#
|
|
# get '/foo' do
|
|
# redirect '/bar' # Redirects to /bar
|
|
# end
|
|
# end
|
|
#
|
|
# === Classic Application Setup
|
|
#
|
|
# To be able to use namespaces in a classic application all you need to do is
|
|
# require the extension:
|
|
#
|
|
# require "sinatra"
|
|
# require "sinatra/namespace"
|
|
#
|
|
# namespace '/users' do
|
|
# end
|
|
#
|
|
# === Modular Application Setup
|
|
#
|
|
# To be able to use namespaces in a modular application all you need to do is
|
|
# require the extension, and then, register it:
|
|
#
|
|
# require "sinatra/base"
|
|
# require "sinatra/namespace"
|
|
#
|
|
# class MyApp < Sinatra::Base
|
|
# register Sinatra::Namespace
|
|
#
|
|
# namespace '/users' do
|
|
# end
|
|
# end
|
|
#
|
|
# === Within an extension
|
|
#
|
|
# To be able to use namespaces within an extension, you need to first create
|
|
# an extension. This includes defining the `registered(app)` method in the
|
|
# module.
|
|
#
|
|
# require 'sinatra/base' # For creating Sinatra extensions
|
|
# require 'sinatra/namespace' # To create namespaces
|
|
#
|
|
# module Zomg # Keep everything under "Zomg" namespace for sanity
|
|
# module Routes # Define a new "Routes" module
|
|
#
|
|
# def self.registered(app)
|
|
# # First, register the Namespace extension
|
|
# app.register Sinatra::Namespace
|
|
#
|
|
# # This defines an `/api` namespace on the application
|
|
# app.namespace '/api' do
|
|
# get '/users' do
|
|
# # do something with `GET "/api/users"`
|
|
# end
|
|
# end
|
|
#
|
|
# end
|
|
# end
|
|
#
|
|
# # Lastly, register the extension to use in our app
|
|
# Sinatra.register Routes
|
|
# end
|
|
#
|
|
# In order to use this extension, is the same as any other Sinatra extension:
|
|
#
|
|
# module Zomg
|
|
# # Define our app class, we use modular app for this example
|
|
# class App < Sinatra::Base
|
|
# # this gives us all the namespaces we defined earlier
|
|
# register Routes
|
|
#
|
|
# get '/' do
|
|
# "Welcome to my application!"
|
|
# end
|
|
# end
|
|
# end
|
|
#
|
|
# Zomg::App.run! # Don't forget to start your app ;)
|
|
#
|
|
# Phew! That was a mouthful.
|
|
#
|
|
# I hope that helps you use `Sinatra::Namespace` in every way imaginable!
|
|
#
|
|
module Namespace
|
|
def self.new(base, pattern, conditions = {}, &block)
|
|
Module.new do
|
|
# quelch uninitialized variable warnings, since these get used by compile method.
|
|
@pattern = nil
|
|
@conditions = nil
|
|
extend NamespacedMethods
|
|
include InstanceMethods
|
|
@base = base
|
|
@extensions = []
|
|
@errors = {}
|
|
@pattern, @conditions = compile(pattern, conditions)
|
|
@templates = Hash.new { |_h, k| @base.templates[k] }
|
|
namespace = self
|
|
before { extend(@namespace = namespace) }
|
|
class_eval(&block)
|
|
end
|
|
end
|
|
|
|
module InstanceMethods
|
|
def settings
|
|
@namespace
|
|
end
|
|
|
|
def template_cache
|
|
super.fetch(:nested, @namespace) { Tilt::Cache.new }
|
|
end
|
|
|
|
def redirect_to(uri, *args)
|
|
redirect("#{@namespace.pattern}#{uri}", *args)
|
|
end
|
|
end
|
|
|
|
module SharedMethods
|
|
def namespace(pattern, conditions = {}, &block)
|
|
Sinatra::Namespace.new(self, pattern, conditions, &block)
|
|
end
|
|
end
|
|
|
|
module NamespacedMethods
|
|
include SharedMethods
|
|
attr_reader :base, :templates
|
|
|
|
ALLOWED_ENGINES = %i[
|
|
erb erubi haml hamlit builder nokogiri
|
|
liquid markdown rdoc asciidoc markaby
|
|
rabl slim yajl
|
|
]
|
|
|
|
def self.prefixed(*names)
|
|
names.each { |n| define_method(n) { |*a, &b| prefixed(n, *a, &b) } }
|
|
end
|
|
|
|
prefixed :before, :after, :delete, :get, :head, :options, :patch, :post, :put
|
|
|
|
def helpers(*extensions, &block)
|
|
class_eval(&block) if block_given?
|
|
include(*extensions) if extensions.any?
|
|
end
|
|
|
|
def register(*extensions, &block)
|
|
extensions << Module.new(&block) if block_given?
|
|
@extensions += extensions
|
|
extensions.each do |extension|
|
|
extend extension
|
|
extension.registered(self) if extension.respond_to?(:registered)
|
|
end
|
|
end
|
|
|
|
def invoke_hook(name, *args)
|
|
@extensions.each { |e| e.send(name, *args) if e.respond_to?(name) }
|
|
end
|
|
|
|
def not_found(&block)
|
|
error(Sinatra::NotFound, &block)
|
|
end
|
|
|
|
def errors
|
|
base.errors.merge(namespace_errors)
|
|
end
|
|
|
|
def namespace_errors
|
|
@errors
|
|
end
|
|
|
|
def error(*codes, &block)
|
|
args = Sinatra::Base.send(:compile!, 'ERROR', /.*/, block)
|
|
codes = codes.map { |c| Array(c) }.flatten
|
|
codes << Exception if codes.empty?
|
|
codes << Sinatra::NotFound if codes.include?(404)
|
|
|
|
codes.each do |c|
|
|
errors = @errors[c] ||= []
|
|
errors << args
|
|
end
|
|
end
|
|
|
|
def respond_to(*args)
|
|
return @conditions[:provides] || base.respond_to if args.empty?
|
|
|
|
@conditions[:provides] = args
|
|
end
|
|
|
|
def set(key, value = self, &block)
|
|
return key.each { |k, v| set(k, v) } if key.respond_to?(:each) && block.nil? && (value == self)
|
|
raise ArgumentError, "may not set #{key}" unless ([:views] + ALLOWED_ENGINES).include?(key)
|
|
|
|
block ||= proc { value }
|
|
singleton_class.send(:define_method, key, &block)
|
|
end
|
|
|
|
def enable(*opts)
|
|
opts.each { |key| set(key, true) }
|
|
end
|
|
|
|
def disable(*opts)
|
|
opts.each { |key| set(key, false) }
|
|
end
|
|
|
|
def template(name, &block)
|
|
first_location = caller_locations.first
|
|
filename = first_location.path
|
|
line = first_location.lineno
|
|
templates[name] = [block, filename, line]
|
|
end
|
|
|
|
def layout(name = :layout, &block)
|
|
template name, &block
|
|
end
|
|
|
|
def pattern
|
|
@pattern
|
|
end
|
|
|
|
private
|
|
|
|
def app
|
|
base.respond_to?(:base) ? base.base : base
|
|
end
|
|
|
|
def compile(pattern, conditions, default_pattern = nil)
|
|
if pattern.respond_to? :to_hash
|
|
conditions = conditions.merge pattern.to_hash
|
|
pattern = nil
|
|
end
|
|
base_pattern = @pattern
|
|
base_conditions = @conditions
|
|
pattern ||= default_pattern
|
|
[prefixed_path(base_pattern, pattern),
|
|
(base_conditions || {}).merge(conditions)]
|
|
end
|
|
|
|
def prefixed_path(a, b)
|
|
return a || b || /.*/ unless a && b
|
|
return Mustermann.new(b) if a == /.*/
|
|
|
|
Mustermann.new(a) + Mustermann.new(b)
|
|
end
|
|
|
|
def prefixed(method, pattern = nil, conditions = {}, &block)
|
|
default = %r{(?:/.*)?} if (method == :before) || (method == :after)
|
|
pattern, conditions = compile pattern, conditions, default
|
|
result = base.send(method, pattern, **conditions, &block)
|
|
invoke_hook :route_added, method.to_s.upcase, pattern, block
|
|
result
|
|
end
|
|
|
|
def method_missing(method, *args, &block)
|
|
base.send(method, *args, &block)
|
|
end
|
|
|
|
def respond_to?(method, include_private = false)
|
|
super || base.respond_to?(method, include_private)
|
|
end
|
|
end
|
|
|
|
module BaseMethods
|
|
include SharedMethods
|
|
end
|
|
|
|
def self.extended(base)
|
|
base.extend BaseMethods
|
|
end
|
|
end
|
|
|
|
register Sinatra::Namespace
|
|
Delegator.delegate :namespace
|
|
end
|