1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
|
require 'mustermann/pattern'
require 'mustermann/composite'
require 'thread'
# Namespace and main entry point for the Mustermann library.
#
# Under normal circumstances the only external API entry point you should be using is {Mustermann.new}.
module Mustermann
# Type to use if no type is given.
# @api private
DEFAULT_TYPE = :sinatra
# Creates a new pattern based on input.
#
# * From {Mustermann::Pattern}: returns given pattern.
# * From String: creates a pattern from the string, depending on type option (defaults to {Mustermann::Sinatra})
# * From Regexp: creates a {Mustermann::Regular} pattern.
# * From Symbol: creates a {Mustermann::Sinatra} pattern with a single named capture named after the input.
# * From an Array or multiple inputs: creates a new pattern from each element, combines them to a {Mustermann::Composite}.
# * From anything else: Will try to call to_pattern on it or raise a TypeError.
#
# Note that if the input is a {Mustermann::Pattern}, Regexp or Symbol, the type option is ignored and if to_pattern is
# called on the object, the type will be handed on but might be ignored by the input object.
#
# If you want to enforce the pattern type, you should create them via their expected class.
#
# @example creating patterns
# require 'mustermann'
#
# Mustermann.new("/:name") # => #<Mustermann::Sinatra:"/example">
# Mustermann.new("/{name}", type: :template) # => #<Mustermann::Template:"/{name}">
# Mustermann.new(/.*/) # => #<Mustermann::Regular:".*">
# Mustermann.new(:name, capture: :word) # => #<Mustermann::Sinatra:":name">
# Mustermann.new("/", "/*.jpg", type: :shell) # => #<Mustermann::Composite:(shell:"/" | shell:"/*.jpg")>
#
# @example using custom #to_pattern
# require 'mustermann'
#
# class MyObject
# def to_pattern(**options)
# Mustermann.new("/:name", **options)
# end
# end
#
# Mustermann.new(MyObject.new, type: :rails) # => #<Mustermann::Rails:"/:name">
#
# @example enforcing type
# require 'mustermann/sinatra'
#
# Mustermann::Sinatra.new("/:name")
#
# @param [String, Pattern, Regexp, Symbol, #to_pattern, Array<String, Pattern, Regexp, Symbol, #to_pattern>]
# input The representation of the pattern
# @param [Hash] options The options hash
# @return [Mustermann::Pattern] pattern corresponding to string.
# @raise (see [])
# @raise (see Mustermann::Pattern.new)
# @raise [TypeError] if the passed object cannot be converted to a pattern
# @see file:README.md#Types_and_Options "Types and Options" in the README
def self.new(*input)
options = input.last.kind_of?(Hash) ? input.pop : {}
type = options.delete(:type) || DEFAULT_TYPE
input = input.first if input.size < 2
case input
when Pattern then input
when Regexp then self[:regexp].new(input, options)
when String then self[type].new(input, options)
when Array then Composite.new(input, options.merge(:type => type))
when Symbol then self[:sinatra].new(input.inspect, options)
else
pattern = input.to_pattern(options.merge(:type => type)) if input.respond_to? :to_pattern
raise TypeError, "#{input.class} can't be coerced into Mustermann::Pattern" if pattern.nil?
pattern
end
end
@mutex ||= Mutex.new
@types ||= {}
# Maps a type to its factory.
#
# @example
# Mustermann[:sinatra] # => Mustermann::Sinatra
#
# @param [Symbol] key a pattern type identifier
# @raise [ArgumentError] if the type is not supported
# @return [Class, #new] pattern factory
def self.[](name)
return name if name.respond_to? :new
@types.fetch(normalized = normalized_type(name)) do
@mutex.synchronize do
error = try_require "mustermann/#{normalized}"
@types.fetch(normalized) { raise ArgumentError, "unsupported type %p#{" (#{error.message})" if error}" % name }
end
end
end
# @return [LoadError, nil]
# @!visibility private
def self.try_require(path)
require(path)
nil
rescue LoadError => error
raise(error) if error.respond_to?(:path) && error.path != path
error
end
# @!visibility private
def self.register(*identifiers)
options = identifiers.last.is_a?(Hash) ? identifiers.pop : {}
constant = options[:constant] || identifiers.first.to_s.capitalize
load = options[:load] || "mustermann/#{identifiers.first}"
@register ||= {}
identifiers.each { |i| @register[i] = [constant, load] }
@register
end
def self.register(name, type)
@types[normalized_type(name)] = type
end
# @!visibility private
def self.normalized_type(type)
type.to_s.gsub('-', '_').downcase
end
# @!visibility private
def self.extend_object(object)
return super unless defined? ::Sinatra::Base and object.is_a? Class and object < ::Sinatra::Base
require 'mustermann/extension'
object.register Extension
end
end
|