lib/nyara/route.rb in nyara-0.0.1.pre.8 vs lib/nyara/route.rb in nyara-0.0.1.pre.9
- old
+ new
@@ -1,10 +1,156 @@
module Nyara
- # provide route preprocessing utils
- module Route; end
+ class Route
+ REQUIRED_ATTRS = [:http_method, :scope, :prefix, :suffix, :controller, :id, :conv]
+ attr_reader *REQUIRED_ATTRS
+ attr_writer :http_method, :id
+ # NOTE `id` is stored in symbol for C-side conenience, but returns as string for Ruby-side goodness
+ def id
+ @id.to_s
+ end
+
+ # optional
+ attr_accessor :accept_exts, :accept_mimes, :classes
+
+ # @private
+ attr_accessor :path, :blk
+
+ def initialize &p
+ instance_eval &p if p
+ end
+
+ # http_method in string form
+ def http_method_to_s
+ m, _ = HTTP_METHODS.find{|k,v| v == http_method}
+ m
+ end
+
+ # enum all combinations of matching selectors
+ def selectors
+ if classes
+ [id, *classes, *classes.map{|k| "#{k}:#{http_method_to_s}"}, ":#{http_method_to_s}"]
+ else
+ [id, ":#{http_method_to_s}"]
+ end
+ end
+
+ # find blocks in filters that match selectors
+ def matched_lifecycle_callbacks filters
+ actions = []
+ selectors = selectors()
+ if selectors and filters
+ # iterate with filter's order to preserve define order
+ filters.each do |sel, blks|
+ actions.concat blks if selectors.include?(sel)
+ end
+ end
+ actions
+ end
+
+ def path_template
+ File.join @scope, (@path.gsub '%z', '%s')
+ end
+
+ # Compute prefix, suffix, conv<br>
+ # NOTE routes may be inherited, so late-setting controller is necessary
+ def compile controller, scope
+ @controller = controller
+ @scope = scope
+
+ path = scope.sub /\/?$/, @path
+ if path.empty?
+ path = '/'
+ end
+ @prefix, suffix = analyse_path path
+ @suffix, @conv = compile_re suffix
+ end
+
+ # Compute accept_exts, accept_mimes
+ def set_accept_exts a
+ @accept_exts = {}
+ @accept_mimes = []
+ if a
+ a.each do |e|
+ e = e.to_s.dup.freeze
+ @accept_exts[e] = true
+ if MIME_TYPES[e]
+ v1, v2 = MIME_TYPES[e].split('/')
+ raise "bad mime type: #{MIME_TYPES[e].inspect}" if v1.nil? or v2.nil?
+ @accept_mimes << [v1, v2, e]
+ end
+ end
+ end
+ @accept_mimes = nil if @accept_mimes.empty?
+ @accept_exts = nil if @accept_exts.empty?
+ end
+
+ def validate
+ REQUIRED_ATTRS.each do |attr|
+ unless instance_variable_get("@#{attr}")
+ raise ArgumentError, "missing #{attr}"
+ end
+ end
+ raise ArgumentError, "id must be symbol" unless @id.is_a?(Symbol)
+ end
+
+ # ---
+ # private
+ # +++
+
+ # #### Returns
+ #
+ # [str_re, conv]
+ #
+ def compile_re suffix
+ return ['', []] unless suffix
+ conv = []
+ re_segs = suffix.split(/(?<=%[dfsuxz])|(?=%[dfsuxz])/).map do |s|
+ case s
+ when '%d'
+ conv << :to_i
+ '(-?[0-9]+)'
+ when '%f'
+ conv << :to_f
+ # just copied from scanf
+ '([-+]?(?:0[xX](?:\.\h+|\h+(?:\.\h*)?)[pP][-+]\d+|\d+(?![\d.])|\d*\.\d*(?:[eE][-+]?\d+)?))'
+ when '%u'
+ conv << :to_i
+ '([0-9]+)'
+ when '%x'
+ conv << :hex
+ '(\h+)'
+ when '%s'
+ conv << :to_s
+ '([^/]+)'
+ when '%z'
+ conv << :to_s
+ '(.*)'
+ else
+ Regexp.quote s
+ end
+ end
+ ["^#{re_segs.join}$", conv]
+ end
+
+ # Split the path into 2 parts: <br>
+ # a fixed prefix and a variable suffix
+ def analyse_path path
+ raise 'path must contain no new line' if path.index "\n"
+ raise 'path must start with /' unless path.start_with? '/'
+ path = path.sub(/\/$/, '') if path != '/'
+
+ path.split(/(?=%[dfsuxz])/, 2)
+ end
+ end
+
+ # class methods
class << Route
- # note that controller may be not defined yet
+ # #### Param
+ #
+ # * `controller` - string or class which inherits [Nyara::Controller](Controller.html)
+ #
+ # NOTE controller may be not defined when register_controller is called
def register_controller scope, controller
unless scope.is_a?(String)
raise ArgumentError, "route prefix should be a string"
end
scope = scope.dup.freeze
@@ -13,11 +159,11 @@
def compile
@global_path_templates = {} # "name#id" => path
mapped_controllers = {}
- route_entries = @controllers.flat_map do |scope, c|
+ routes = @controllers.flat_map do |scope, c|
if c.is_a?(String)
c = name2const c
end
name = c.controller_name || const2name(c)
raise "#{c.inspect} is not a Nyara::Controller" unless Controller > c
@@ -25,28 +171,41 @@
if mapped_controllers[c]
raise "controller #{c.inspect} was already mapped"
end
mapped_controllers[c] = true
- c.compile_route_entries(scope).each do |e|
+ c.nyara_compile_routes(scope).each do |e|
@global_path_templates[name + e.id] = e.path_template
end
end
- route_entries.sort_by! &:prefix
- route_entries.reverse!
+ routes.sort_by! &:prefix
+ routes.reverse!
mapped_controllers.each do |c, _|
c.path_templates = @global_path_templates.merge c.path_templates
end
Ext.clear_route
- route_entries.each do |e|
+ routes.each do |e|
Ext.register_route e
end
end
def global_path_template id
@global_path_templates[id]
+ end
+
+ # remove `.klass` and `:method` from selector, and validate selector format
+ def canonicalize_callback_selector selector
+ /\A
+ (?<id>\#\w++(?:\-\w++)*)?
+ (?<klass>\.\w++(?:\-\w++)*)?
+ (?<method>:\w+)?
+ \z/x =~ selector
+ unless id or klass or method
+ raise ArgumentError, "bad selector: #{selector.inspect}", caller[1..-1]
+ end
+ id.presence or selector.sub(/:\w+\z/, &:upcase)
end
def clear
# gc mark fail if wrong order?
Ext.clear_route