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