# code: # * George Moschovitis # # (c) 2004 Navel, all rights reserved. # $Id: render.rb 200 2004-12-27 11:24:41Z gmosx $ require 'cgi' module N # = RenderUtils # # Various render related utilities. # module RenderUtils # Split the path to base, render_class and method. # # Examples: # # / -> nil, index, nil # /add_user -> nil, add_user, nil # /add_user?user=gmosx -> nil, add_user, user=gmosx # /blog/new_entry -> blog, new_entry # /blog -> blog, index, nil # def self.split_path(path) # paths that start with rest/xxx are REST requests. # FIXME: better chack here! if path.slice!(/rest\//) api = :rest else api = :http end parts = path.split('/') case parts.size when 0 base = '' render_class = $services[:index] meth = 'index' when 2 if render_class = $services[parts[1]] base = parts[1] meth = 'index' else base = '' render_class = $services[:index] meth = parts[1] end when 3 base = parts[1] render_class = $services[parts[1]] meth = parts[2] end # p '--', api, base, render_class, meth, '--' return api, base, render_class, meth end # Given the method try find the matching template. # Can search for xhtml or xml templates. # Returns nil if no template file is found. # def self.template_for_method(base, meth, ext = $template_ext) # attempt to find a template of the form # base/meth.xhtml path = "#$root_dir/#{base}/#{meth}.#{ext}".squeeze('/') unless File.exist?(path) # attempt to find a template of the form # base/meth/index.xhtml path = "#$root_dir/#{base}/#{meth}/#{$index_template}".squeeze('/') unless File.exist?(path) # No template found! path = nil end end return path end # Transform a template to ruby rendering code. # def self.transform_template(path) $log.debug "Transforming '#{path}'" if $DBG text = File.read(path) hash, text = $shader.process(path, text) return text end # Compile a HTTP method. # def self.compile_http_method(klass, base, meth) $log.debug "Compiling HTTP method '#{klass}:#{meth}'" if $DBG valid = false code = %{ def __#{meth} @response.header['Content-Type'] = 'text/html' @out ||= '' } # call 'before' filter chain. if klass.respond_to?(:before_filters) code << %{ #{klass.gen_filters_call_code(klass.before_filters)} } end # call the action if klass.instance_methods.include?(meth) valid = true code << %{ #{meth}(); } end # call the programmatically generated template if exists. if klass.instance_methods.include?("#{meth}__xhtml") valid = true code << %{ return unless #{meth}__xhtml(); } end # call the template if template = template_for_method(base, meth) valid = true code << %{ return unless __#{meth}_xhtml(); } end raise "Invalid method '#{meth}' for service '#{klass}'!" unless valid # call 'after' filter chain. if klass.respond_to?(:after_filters) code << %{ #{klass.gen_filters_call_code(klass.after_filters)} } end code << %{ redirect_referer if @out.empty? end } if template code << %{ def __#{meth}_xhtml #{transform_template(template)} end } end =begin puts '--' puts dump(code) puts '--' =end klass.class_eval(code) return "__#{meth}" end # Compile a REST method. # def self.compile_rest_method(klass, base, meth) $log.debug "Compiling REST method '#{klass}:#{meth}'" if $DBG valid = false code = %{ def __rest_#{meth} @response.header['Content-Type'] = 'text/xml' @out ||= '' } # call 'before' filter chain. if klass.respond_to?(:before_filters) code << %{ #{klass.gen_filters_call_code(klass.before_filters)} } end # call the action if klass.instance_methods.include?(meth) valid = true code << %{ #{meth}(); } end # call the programmatically generated template if exists. if klass.instance_methods.include?("#{meth}__xml") valid = true code << %{ return unless #{meth}__xml(); } end # call the template if template = template_for_method(base, meth, 'xml') valid = true code << %{ return unless __#{meth}_xml(); } end raise "Invalid method '#{meth}' for service #{klass}!" unless valid # call 'after' filter chain. if klass.respond_to?(:after_filters) code << %{ #{klass.gen_filters_call_code(klass.after_filters)} } end code << %{ end } if template code << %{ def __#{meth}_xml #{transform_template(template)} end } end klass.class_eval(code) return "__rest_#{meth}" end def self.dump(str) str.split("\n").each_with_index do |line, idx| puts "#{idx+1}: #{line}" end end end # = RenderExit # # Raise this exception to stop rendering. # class RenderExit < Exception end # = Render # module Render # The outbut buffer. The output of a script/action is accumulated # in this buffer. attr :out # The parameters of this request as Hash. attr_accessor :params # The request. attr_accessor :request # The response. attr_accessor :response # The session contains variables that stay alive for the full # user session. attr_accessor :session # An array holding the rendering errors for this # request. # attr_accessor :rendering_errors def initialize(base_path = '', request = nil, response = nil, session = nil) @base_path = base_path @request = request @response = response @session = session @params = request.query @out_buffers = nil end # Returns the output of the rendering as string. # def render(path, request = nil, response = nil, session = nil) path, query_string = path.split('?') @request ||= request @response ||= response @session ||= session @params = @request.set_query(query_string) if query_string base, render_class, meth = $methods[path] unless meth api, base, render_class, meth = RenderUtils.split_path(path) raise "Invalid service!" unless render_class case api when :http meth = RenderUtils.compile_http_method(render_class, base, meth) when :rest meth = RenderUtils.compile_rest_method(render_class, base, meth) end $methods[path] = [base, render_class, meth] unless $reload_scripts end begin if self.class == render_class self.send(meth) else if $reload_scripts and defined?(render_class::SOURCE_FILE) load(render_class::SOURCE_FILE) end r = render_class.new(base, @request, @response, @session) r.send(meth) @out = r.out end rescue N::RenderExit => e # Just stop rendering. # For example called by redirects. rescue Exception, StandardError => e log_error "error while handling '#{path}'." log_error pp_exception(e) # more fault tolerant, only flags the erroneous box with # error not the full page. @out << '(error)' end # stop the filter pipeline return false end # Render the referer of this method. # def render_referer render(@request.referer.gsub(/#$srv_url/, '')) end # Send a redirect response. # def redirect(url, status = 303) @response.status = status @response.body = "#{url.to_s}.\n" @response.header['location'] = url.to_s # stop the rendering raise N::RenderExit end # Redirect to the referer of this method. # def redirect_referer(status = 303) redirect(@request.referer, status) end # Log a rendering error. #-- # FIXME: find a better name #++ def log_error(str) @rendering_errors ||= [] @rendering_errors << str $log.error str end # -------------------------------------------------------------------- # Output buffering methods. # Start (push) a new output buffer. # def ob_start @out_buffers = [] unless @out_buffers @out_buffers.push(@out) @out = '' end # End (pop) the current output buffer. # def ob_end @out = @out_buffers.pop end # End (pop) the current output buffer and write to the parent. # def ob_write_end nested_buffer = @out @out = @out_buffers.pop @out << nested_buffer end # -------------------------------------------------------------------- # Caching methods. #-- # FIXME: pseudocode, not working. #++ def cache_start valid? ob_start end #-- # FIXME: pseudocode, not working. #++ def cache_end save_fragment(@out) ob_write_end end end end # module