Skip to content

Instantly share code, notes, and snippets.

@rue
Created January 26, 2009 19:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rue/52933 to your computer and use it in GitHub Desktop.
Save rue/52933 to your computer and use it in GitHub Desktop.
# Node in the URL resolution tree.
#
class Node
attr_accessor :component, :definition
attr_accessor :statics, :placeholders, :consumables, :maybes, :rest
def initialize(component)
@component = component
@statics = []
@placeholders = []
@consumables = []
@maybes = []
@rest = nil
@definition = nil
end
end
# Define a mapping from a request to a resource.
#
# TODO: Once done building, flatten into an Array. Needs
# polymorphic support.
#
# TODO: Define backend methods rather than using block?
#
def on(methods, *pathspec, &block)
case methods
when Array
# Nothing
when true
methods = [:get, :put, :post, :head]
else
methods = [methods]
end
methods.each {|m|
tier = maps[m]
pathspec.each_with_index {|component, i|
case component
when String, nil # Static and faked empty path
current = tier.statics.find {|n| n.component == component}
unless current
current = Node.new component
tier.statics << current
end
tier = current
when Symbol # Placeholder
# TODO: Could combine same label, but..point?
current = Node.new component
tier.placeholders << current
tier = current
when Range # Some number of required and/or optional arguments
# TODO: Forgetting to cap the range?
required = component.begin
required.times {
current = Node.new "<consumable>"
tier.consumables << current
tier = current
}
# Figure out if we can consume the rest.
# TODO: Support other types of terminators?
tail = pathspec[(i + 1)..-1].find {|c| c.kind_of? String }
# Last required argument must have a definition
tier.definition = block if !tail and required > 0
if component.end == -1
raise "Rest of arguments must be last" unless i == (pathspec.size - 1)
current = Node.new "<rest>"
tier.rest = current
tier = current
break
end
# Then the optionals
component.to_a[1..-1].each {
current = Node.new tail # I.e. storing the one we are expecting
current.definition = block # Need this if not getting all.
tier.maybes << current
tier = current
}
tier.definition = nil # UGH.
when true
component = 1..-1
redo
else
raise "wtf?"
end
}
raise "Mapping already defined!" if tier.definition
tier.definition = block
}
end
end
# Later
# ...
#
# Occurs in a specific order.
#
# TODO: Needs a bunch of optimisation.
#
def descend(node, path)
if path.empty?
return node if node.definition
return
end
component, rest = path.at(0), path[1..-1]
# Static strings
node.statics.each {|n|
# This _must_ match (or not) since it is not variable
return descend n, rest if n.component == component
}
# Maybes
node.maybes.each {|n|
# There may be multiple statics here
if n.component == component
found = descend n, rest
found = descend n, path unless found
return found if found
end
}
# Placeholders
node.placeholders.each {|n|
found = descend n, rest
if found
captured[n.component] = component
return found
end
}
# Consumables
node.consumables.each {|n|
found = descend n, rest
return found if found
}
# Deeper into the maybes that did not match here
node.maybes.each {|n|
found = descend n, rest
return found if found
}
# All of the remaining path
node.rest
end
#
#
def get(request)
node = descend @maps[:get], extract_path(request)
raise "No node found" unless node
block = node.definition or raise "No block in #{node.inspect} of #{@maps[:get].inspect} for #{request.path}"
instance_eval &block
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment