File: mustermann.rb

package info (click to toggle)
ruby-mustermann19 0.4.3%2Bgit20160621-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 756 kB
  • ctags: 445
  • sloc: ruby: 7,197; makefile: 3
file content (133 lines) | stat: -rw-r--r-- 4,995 bytes parent folder | download
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