class Roda
module RodaPlugins
# The backtracking_array plugin changes the handling of array
# matchers such that if one of the array entries matches, but
# a later match argument fails, it will backtrack and try the
# next entry in the array. For example, the following match
# block does not match +/a/b+ by default:
#
# r.is ['a', 'a/b'] do |path|
# # ...
# end
#
# This is because the 'a' entry in the array matches, which
# makes the array match. However, the next matcher is the
# terminal matcher (since +r.is+ was used), and since the
# path is not terminal as it still contains +/b+ after
# matching 'a'.
#
# With the backtracking_array plugin, when the terminal matcher
# fails, matching will go on to the next entry in the array,
# 'a/b', which will also match. Since 'a/b'
# matches the path fully, the terminal matcher also matches,
# and the match block yields.
module BacktrackingArray
module RequestMethods
PATH_INFO = "PATH_INFO".freeze
SCRIPT_NAME = "SCRIPT_NAME".freeze
private
# When matching for a single array, after a successful
# array element match, attempt to match all remaining
# elements. If the remaining elements could not be
# matched, reset the state and continue to the next
# entry in the array.
def _match_array(arg, rest=nil)
return super unless rest
env = @env
script = env[SCRIPT_NAME]
path = env[PATH_INFO]
caps = captures.dup
arg.each do |v|
if match(v, rest)
if v.is_a?(String)
captures.push(v)
end
if match_all(rest)
return true
end
# Matching all remaining elements failed, reset state
captures.replace(caps)
env[SCRIPT_NAME] = script
env[PATH_INFO] = path
end
end
false
end
# If any of the args are an array, handle backtracking such
# that if a later matcher fails, we roll back to the current
# matcher and proceed to the next entry in the array.
def match_all(args)
args = args.dup
until args.empty?
arg = args.shift
if match(arg, args)
return true if arg.is_a?(Array)
else
return
end
end
true
end
# When matching an array, include the remaining arguments,
# otherwise, just match the single argument.
def match(v, rest = nil)
if v.is_a?(Array)
_match_array(v, rest)
else
super(v)
end
end
end
end
register_plugin(:backtracking_array, BacktrackingArray)
end
end