lib/serviceworker/route.rb in serviceworker-rails-0.2.0 vs lib/serviceworker/route.rb in serviceworker-rails-0.3.0
- old
+ new
@@ -1,49 +1,127 @@
module ServiceWorker
class Route
- attr_reader :path, :options
+ attr_reader :path_pattern, :asset_pattern, :options
- def initialize(path, options = {})
- @path = path
- @options = options
-
- @pattern = compile(path)
+ RouteMatch = Struct.new(:path, :asset_name, :headers) do
+ def to_s
+ asset_name
+ end
end
- def match?(path)
- @pattern =~ path
+ def initialize(path_pattern, asset_pattern = nil, options = {})
+ if asset_pattern.is_a?(Hash)
+ options = asset_pattern
+ asset_pattern = nil
+ end
+
+ @path_pattern = path_pattern
+ @asset_pattern = asset_pattern || options[:asset] || path_pattern
+ @options = options
end
- def asset_name
- @options.fetch(:asset, @path.gsub(%r{^/}, ""))
- rescue NoMethodError
- raise RouteError, "Cannot determine asset name from #{path.inspect}. Please specify the :asset option for this path."
+ def match(path)
+ if path.to_s.strip.empty?
+ raise ArgumentError.new("path is required")
+ end
+
+ asset = resolver.call(path) or return nil
+
+ RouteMatch.new(path, asset, headers)
end
def headers
@options.fetch(:headers, {})
end
private
- def compile(path)
- if path.respond_to?(:to_str)
- special_chars = %w[. + ( )]
- pattern = path.to_str.gsub(/([\*#{special_chars.join}])/) do |match|
- case match
- when "*"
- "(.*?)"
- when *special_chars
- Regexp.escape(match)
+ def resolver
+ @resolver ||= AssetResolver.new(path_pattern, asset_pattern)
+ end
+
+ class AssetResolver
+ PATH_INFO = 'PATH_INFO'.freeze
+ DEFAULT_WILDCARD_NAME = :paths
+ WILDCARD_PATTERN = /\/\*([^\/]*)/.freeze
+ NAMED_SEGMENTS_PATTERN = /\/([^\/]*):([^:$\/]+)/.freeze
+ LEADING_SLASH_PATTERN = /^\//
+ INTERPOLATION_PATTERN = Regexp.union(
+ /%%/,
+ /%\{(\w+)\}/, # matches placeholders like "%{foo}"
+ )
+
+ attr_reader :path_pattern, :asset_pattern
+
+ def initialize(path_pattern, asset_pattern)
+ @path_pattern = path_pattern
+ @asset_pattern = asset_pattern
+ end
+
+ def call(path)
+ if path.to_s.strip.empty?
+ raise ArgumentError.new("path is required")
+ end
+
+ captures = path_captures(regexp, path) or return nil
+
+ interpolate_captures(asset_pattern, captures)
+ end
+
+ private
+
+ def regexp
+ @regexp ||= compile_regexp(path_pattern)
+ end
+
+ def compile_regexp(pattern)
+ Regexp.new("\\A#{compiled_source(pattern)}\\Z")
+ end
+
+ def compiled_source(pattern)
+ if pattern_match = pattern.match(WILDCARD_PATTERN)
+ @wildcard_name = if pattern_match[1].to_s.strip.empty?
+ DEFAULT_WILDCARD_NAME
+ else
+ pattern_match[1].to_sym
+ end
+ pattern.gsub(WILDCARD_PATTERN,'(?:/(.*)|)')
+ else
+ p = if pattern_match = pattern.match(NAMED_SEGMENTS_PATTERN)
+ pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^.$/]+)')
+ else
+ pattern
+ end
+ p + '(?:\.(?<format>.*))?'
+ end
+ end
+
+ def path_captures(regexp, path)
+ return nil unless path_match = path.match(regexp)
+ params = if @wildcard_name
+ { @wildcard_name => path_match[1].to_s.split('/') }
+ else
+ Hash[path_match.names.map(&:to_sym).zip(path_match.captures)]
+ end
+ params.delete(:format) if params.has_key?(:format) && params[:format].nil?
+ params
+ end
+
+ def interpolate_captures(string, captures)
+ string.gsub(INTERPOLATION_PATTERN) do |match|
+ if match == '%%'
+ '%'
else
- "([^/?&#]+)"
+ key = ($1 || $2).to_sym
+ value = if captures.key?(key)
+ Array(captures[key]).join("/")
+ else
+ raise "Interpolation error: #{key} not captured in #{captures.inspect}"
+ end
+ value = value.call(captures) if value.respond_to?(:call)
+ $3 ? sprintf("%#{$3}", value) : value
end
- end
- /^#{pattern}$/
- elsif path.respond_to?(:match)
- path
- else
- raise TypeError, path
+ end.gsub(LEADING_SLASH_PATTERN, "")
end
end
end
end