require 'sinatra/base' require 'backports' require 'uri' module Sinatra # = Sinatra::Decompile # # Sinatra::Decompile is an extension that provides a method, # conveniently called +decompile+, that will generate a String pattern for a # given route. # # == Usage # # === Classic Application # # To use the extension in a classic application all you need to do is require # it: # # require "sinatra" # require "sinatra/decompile" # # # Your classic application code goes here... # # This will add the +decompile+ method to the application/class scope, but # you can also call it as Sinatra::Decompile.decompile. # # === Modular Application # # To use the extension in a modular application you need to require it, and # then, tell the application you will use it: # # require "sinatra/base" # require "sinatra/decompile" # # class MyApp < Sinatra::Base # register Sinatra::Decompile # # # The rest of your modular application code goes here... # end # # This will add the +decompile+ method to the application/class scope. You # can choose not to register the extension, but instead of calling # +decompile+, you will need to call Sinatra::Decompile.decompile. # module Decompile extend self ## # Regenerates a string pattern for a given route # # Example: # # class Sinatra::Application # routes.each do |verb, list| # puts "#{verb}:" # list.each do |data| # puts "\t" << decompile(data) # end # end # end # # Will return the internal Regexp if it's unable to reconstruct the pattern, # which likely indicates that a Regexp was used in the first place. # # You can also use this to check whether you could actually use a string # pattern instead of your regexp: # # decompile /^/foo$/ # => '/foo' def decompile(pattern, keys = nil, *) # Everything in here is basically just the reverse of # Sinatra::Base#compile # # Sinatra 2.0 will come with a mechanism for this, making this obsolete. pattern, keys = pattern if pattern.respond_to? :to_ary keys, str = keys.try(:dup), pattern.inspect return pattern unless str.start_with? '/' and str.end_with? '/' str.gsub! /^\/(\^|\\A)?|(\$|\\z)?\/$/, '' str.gsub! encoded(' '), ' ' return pattern if str =~ /^[\.\+]/ str.gsub! '((?:[^\.\/?#%]|(?:%[^2].|%[2][^Ee]))+)', '([^\/?#]+)' str.gsub! '((?:[^\/?#%]|(?:%[^2].|%[2][^Ee]))+)', '([^\/?#]+)' str.gsub! /\([^\(\)]*\)|\([^\(\)]*\([^\(\)]*\)[^\(\)]*\)/ do |part| case part when '(.*?)' return pattern if keys.shift != 'splat' '*' when /^\(\?\:(\\*.)\|%[\w\[\]]+\)$/ $1 when /^\(\?\:(%\d+)\|([^\)]+|\([^\)]+\))\)$/ URI.unescape($1) when '([^\/?#]+)' return pattern if keys.empty? ":" << keys.shift when /^\(\?\:\\?(.)\|/ char = $1 return pattern unless encoded(char) == part Regexp.escape(char) else return pattern end end str.gsub /(.)([\.\+\(\)\/])/ do return pattern if $1 != "\\" $2 end end private def encoded(char) return super if defined? super enc = URI.escape(char) enc = "(?:#{escaped(char, enc).join('|')})" if enc == char enc = "(?:#{enc}|#{encoded('+')})" if char == " " enc end end register Decompile end