From 1a454b8d4f902fadcdd2f2b838e75f59f144dbe8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Zurcher?= Date: Mon, 12 Aug 2024 11:54:37 +0200 Subject: sanitize code --- lib/zorglub.rb | 5 +- lib/zorglub/app.rb | 213 ++++++++-------- lib/zorglub/engines/file.rb | 20 +- lib/zorglub/engines/haml.rb | 26 +- lib/zorglub/engines/sass.rb | 27 +- lib/zorglub/node.rb | 546 ++++++++++++++++++++-------------------- lib/zorglub/rack_session.rb | 10 +- lib/zorglub/session.rb | 288 +++++++++++---------- spec/app_spec.rb | 58 ++--- spec/node_spec.rb | 602 ++++++++++++++++++++++---------------------- spec/spec_helper.rb | 357 +++++++++++++------------- 11 files changed, 1068 insertions(+), 1084 deletions(-) diff --git a/lib/zorglub.rb b/lib/zorglub.rb index 586a184..b964bc5 100644 --- a/lib/zorglub.rb +++ b/lib/zorglub.rb @@ -1,11 +1,8 @@ #! /usr/bin/env ruby -# -*- coding: UTF-8 -*- module Zorglub - VERSION = '0.1.0' + VERSION = '0.1.1'.freeze end require 'zorglub/node' require 'zorglub/app' - -# EOF diff --git a/lib/zorglub/app.rb b/lib/zorglub/app.rb index 9619968..86adf1e 100644 --- a/lib/zorglub/app.rb +++ b/lib/zorglub/app.rb @@ -1,112 +1,113 @@ -# -*- coding: UTF-8 -*- - require 'rack' module Zorglub + class App < Rack::URLMap + def initialize(map = {}, &block) + super + @map = map + @engines_cache = {} + @options = { + debug: false, + root: '.', + layout: 'default', + view_dir: 'view', + layout_dir: 'layout', + static_dir: 'static', + engine: nil, + engines_cache_enabled: true, + engines: {}, + haml_options: { + format: :html5, + encoding: 'utf-8' + }, + sass_options: { + syntax: :scss, + cache: false, + style: :compressed + }, + session_options: { + enabled: false, + key: 'zorglub.sid', + secret: 'session-secret-secret', + sid_len: 64 + } + } + instance_eval(&block) if block_given? + remap @map + end + + attr_reader :engines_cache + + def map(location, object) + return unless location && object + raise StandardError, "#{@map[location]} already mapped to #{location}" if @map.key? location + + object.app = self + @map.merge! location.to_s => object + remap @map + end + + def delete(location) + @map.delete location + remap @map + end + + def at(location) + @map[location] + end + + def to(object) + @map.invert[object] + end + + def to_hash + @map.dup + end + + # OPTIONS @options + + def opt(sym) + @options[sym] + end + + def opt!(sym, val) + @options[sym] = val + end + + def register_engine!(name, ext, proc) + x = if ext.nil? || ext.empty? + nil + elsif ext[0] == '.' + ext.length == 1 ? nil : ext + else + ".#{ext}" + end + @options[:engines][name] = [proc, x] + end + + def engine_proc_ext(engine, ext) + p, x = @options[:engines][engine] + return [nil, ''] if p.nil? + + [p, ext.nil? || ext.empty? ? x : ext] + end + + def view_base_path + _base_path @options[:view_path], :view_dir + end + + def layout_base_path + _base_path @options[:layout_path], :layout_dir + end + + def static_base_path + _base_path @options[:static_path], :static_dir + end + + private - class App < Rack::URLMap - - def initialize map={}, &block - super - @map = map - @engines_cache = { } - @options = { - :debug => false, - :root => '.', - :layout => 'default', - :view_dir => 'view', - :layout_dir => 'layout', - :static_dir => 'static', - :engine => nil, - :engines_cache_enabled => true, - :engines => { }, - :haml_options => { - :format => :html5, - :encoding => 'utf-8' - }, - :sass_options => { - :syntax => :scss, - :cache => :false, - :style => :compressed - }, - :session_options => { - :enabled => false, - :key => 'zorglub.sid', - :secret => 'session-secret-secret', - :sid_len => 64 - } - } - instance_eval &block if block_given? - remap @map - end - - attr_reader :engines_cache - - def map location, object - return unless location and object - raise Exception.new "#{@map[location]} already mapped to #{location}" if @map.has_key? location - object.app = self - @map.merge! location.to_s=>object - remap @map - end - - def delete location - @map.delete location - remap @map - end - - def at location - @map[location] - end - - def to object - @map.invert[object] - end - - def to_hash - @map.dup - end - - # OPTIONS @options - - def opt sym - @options[sym] - end - - def opt! sym, val - @options[sym] = val - end - - def register_engine! name, ext, proc - if ext.nil? or ext.empty? - x = nil - else - x = (ext[0]=='.' ? (ext.length==1 ? nil : ext) : '.' + ext) - end - @options[:engines][name]=[ proc, x ] - end - - def engine_proc_ext engine, ext - p,x = @options[:engines][engine] - return [nil, ''] if p.nil? - [ p, ((ext.nil? or ext.empty?) ? x : ext ) ] - end - - def view_base_path - _base_path @options[:view_path], :view_dir - end - - def layout_base_path - _base_path @options[:layout_path], :layout_dir - end - - def static_base_path - _base_path @options[:static_path], :static_dir - end - - private - def _base_path p, sym - ( p.nil? ? File.join(@options[:root], @options[sym]) : p ) - end + def _base_path(path, sym) + path.nil? ? File.join(@options[:root], @options[sym]) : path end + end end diff --git a/lib/zorglub/engines/file.rb b/lib/zorglub/engines/file.rb index dbf579d..748f701 100644 --- a/lib/zorglub/engines/file.rb +++ b/lib/zorglub/engines/file.rb @@ -1,18 +1,14 @@ -# -*- coding: UTF-8 -*- - require 'haml/util' require 'haml/engine' module Zorglub - module Engines - module File - def self.proc path,obj - content = ::File.open(path,'r'){|f| f.read } - ext = path.sub /.*\.(.+)$/,'\1' - return content, "text/#{ext}" - end - end + module Engines + module File + def self.proc(path, _obj) + content = ::File.read(path) + ext = path.sub(/.*\.(.+)$/, '\1') + [content, "text/#{ext}"] + end end + end end - -# EOF diff --git a/lib/zorglub/engines/haml.rb b/lib/zorglub/engines/haml.rb index e5013f8..b602773 100644 --- a/lib/zorglub/engines/haml.rb +++ b/lib/zorglub/engines/haml.rb @@ -1,22 +1,18 @@ -# -*- coding: UTF-8 -*- - require 'haml/util' require 'haml/template' module Zorglub - module Engines - module Haml - def self.proc path,obj - haml = ::Haml::Template.new( path ) - if obj.app.opt(:engines_cache_enabled) - key = path.sub obj.app.opt(:root),'' - obj.app.engines_cache[key] ||= haml - end - html = haml.render(obj, obj.app.opt(:haml_options)) - return html, 'text/html' - end + module Engines + module Haml + def self.proc(path, obj) + haml = ::Haml::Template.new(path) + if obj.app.opt(:engines_cache_enabled) + key = path.sub obj.app.opt(:root), '' + obj.app.engines_cache[key] ||= haml end + html = haml.render(obj, obj.app.opt(:haml_options)) + [html, 'text/html'] + end end + end end - -# EOF diff --git a/lib/zorglub/engines/sass.rb b/lib/zorglub/engines/sass.rb index bc6b759..44973d5 100644 --- a/lib/zorglub/engines/sass.rb +++ b/lib/zorglub/engines/sass.rb @@ -1,22 +1,17 @@ -# -*- coding: UTF-8 -*- - require 'sass' module Zorglub - module Engines - module Sass - def self.proc path,obj - if obj.app.opt(:engines_cache_enabled) - key = path.sub obj.app.opt(:root),'' - sass = obj.app.engines_cache[key] ||= ::Sass::Engine.new( ::File.open(path,'r'){|f| f.read }, obj.app.opt(:sass_options) ) - else - sass = ::Sass::Engine.new( ::File.open(path,'r'){|f| f.read }, obj.app.opt(:sass_options) ) - end - css = sass.render - return css, 'text/css' - end + module Engines + module Sass + def self.proc(path, obj) + sass = ::Sass::Engine.new(::File.read(path), obj.app.opt(:sass_options)) + if obj.app.opt(:engines_cache_enabled) + key = path.sub obj.app.opt(:root), '' + obj.app.engines_cache[key] ||= sass end + css = sass.render + [css, 'text/css'] + end end + end end - -# EOF diff --git a/lib/zorglub/node.rb b/lib/zorglub/node.rb index b360a5c..84cba92 100644 --- a/lib/zorglub/node.rb +++ b/lib/zorglub/node.rb @@ -1,334 +1,334 @@ -# -*- coding: UTF-8 -*- - require 'fileutils' module Zorglub + class Node + UNDEFINED = -1 - class Node - - UNDEFINED=-1 - - # class level engine, layout, static, layout_base_path, view_base_path configuration + # class level engine, layout, static, layout_base_path, view_base_path configuration - class << self + class << self + attr_reader :static, :cache_lifetime - attr_reader :static, :cache_lifetime + def engine!(engine) + @engine = engine + end - def engine! engine - @engine = engine - end + def engine + @engine = @app.opt(:engine) if @engine == UNDEFINED && @app + @engine + end - def engine - @engine = @app.opt(:engine) if @engine==UNDEFINED and @app - @engine - end + def no_layout! + layout! nil + end - def no_layout! - layout! nil - end + def layout!(layout) + @layout = layout + end - def layout! layout - @layout = layout - end + def layout + @layout = @app.opt(:layout) if @layout == UNDEFINED && @app + @layout + end - def layout - @layout = @app.opt(:layout) if @layout==UNDEFINED and @app - @layout - end + def static!(val, lifetime = 0) + @static = [true, false].include?(val) ? val : false + @cache_lifetime = lifetime + end - def static! val, lifetime=0 - @static = ( (val==true or val==false) ? val : false ) - @cache_lifetime = lifetime - end + def layout_base_path!(path) + @layout_base_path = path + end - def layout_base_path! path - @layout_base_path = path - end + def layout_base_path + @layout_base_path ||= @app.layout_base_path + end - def layout_base_path - @layout_base_path ||= @app.layout_base_path - end + def view_base_path!(path) + @view_base_path = path + end - def view_base_path! path - @view_base_path = path - end - - def view_base_path - @view_base_path ||= @app.view_base_path - end - end - - # instance level engine, layout, view, static configuration + def view_base_path + @view_base_path ||= @app.view_base_path + end + end - def engine! engine - @engine = engine - end + # instance level engine, layout, view, static configuration - def no_layout! - layout! nil - end + def engine!(engine) + @engine = engine + end - def layout! layout - @layout = layout - end + def no_layout! + layout! nil + end - def layout - return nil if @layout.nil? - File.join(self.class.layout_base_path, @layout) + ext - end + def layout!(layout) + @layout = layout + end - def no_view! - view! nil - end + def layout + return nil if @layout.nil? - def view! view - @view = view - end + File.join(self.class.layout_base_path, @layout) + ext + end - def view - return nil if @view.nil? - File.join(self.class.view_base_path, @view) + ext - end + def no_view! + view! nil + end - def static! val, lifetime=0 - @static = ( (val==true or val==false) ? val : false ) - @cache_lifetime = lifetime - end + def view!(view) + @view = view + end - def static - return nil if not @static or @view.nil? - File.join(app.static_base_path, @view) + ext - end + def view + return nil if @view.nil? - def ext! ext - @ext = ( (ext.nil? or ext.empty?) ? nil : (ext[0]=='.' ? (ext.length==1 ? nil : ext) : '.' + ext) ) - end + File.join(self.class.view_base_path, @view) + ext + end - def ext - @ext || '' - end + def static!(val, lifetime = 0) + @static = [true, false].include?(val) ? val : false + @cache_lifetime = lifetime + end - def mime! mime - @mime = mime - end + def static + return nil if !@static || @view.nil? - # class level basic node functions + File.join(app.static_base_path, @view) + ext + end - class << self + def ext!(ext) + @ext = if ext.nil? || ext.empty? + nil + elsif ext[0] == '.' + ext.length == 1 ? nil : ext + else + ".#{ext}" + end + end - attr_accessor :app - def map app, location - @app = app - @app.map location, self - end + def ext + @ext || '' + end - def r *args - @r ||= @app.to self - (args.empty? ? @r : File.join( @r, args.map { |x| x.to_s } ) ) - end + def mime!(mime) + @mime = mime + end - end + # class level basic node functions - # instance level basic node functions + class << self + attr_accessor :app - def app - self.class.app - end + def map(app, location) + @app = app + @app.map location, self + end - def map - self.class.r - end + def r *args + @r ||= @app.to self + args.empty? ? @r : File.join(@r, args.map(&:to_s)) + end + end - def r *args - File.join map, (args.empty? ? meth : args.map { |x| x.to_s } ) - end + # instance level basic node functions - def html - [ :map, :r, :args, :engine, :layout, :view ].inject('') { |s,sym| s+="

#{sym} => #{self.send sym}

"; s } - end + def app + self.class.app + end - def redirect target, options={}, &block - status = options[:status] || 302 - body = options[:body] || redirect_body(target) - header = response.headers.merge('Location' => target.to_s) - throw :stop_realize, Rack::Response.new(body, status, header, &block) - end + def map + self.class.r + end - def redirect_body target - "You are being redirected, please follow this link to: #{target}!" - end + def r *args + File.join map, (args.empty? ? meth : args.map(&:to_s)) + end - # class level inherited values are key=>array, copied at inheritance - # so they can be extanded at class level - # values are copied from class into instance at object creation - # so that can be extanded without modifying class level values - # typical usage are css or js inclusions + def html + %i[map r args engine layout view].inject('') { |s, sym| s + "

#{sym} => #{send sym}

" } + end - @cli_vals = { } + def redirect(target, options = {}, &block) + status = options[:status] || 302 + body = options[:body] || redirect_body(target) + header = response.headers.merge('Location' => target.to_s) + throw :stop_realize, Rack::Response.new(body, status, header, &block) + end - class << self + def redirect_body(target) + "You are being redirected, please follow this link to: #{target}!" + end - attr_reader :cli_vals + # class level inherited values are key=>array, copied at inheritance + # so they can be extanded at class level + # values are copied from class into instance at object creation + # so that can be extanded without modifying class level values + # typical usage are css or js inclusions - def cli_val sym, *args - vals = @cli_vals[sym] ||= [] - unless args.empty? - vals.concat args - vals.uniq! - end - vals - end + @cli_vals = {} - end + class << self + attr_reader :cli_vals - def cli_val sym, *args - vals = @cli_vals[sym] ||= [] - unless args.empty? - vals.concat args - vals.uniq! - end - vals + def cli_val(sym, *args) + vals = @cli_vals[sym] ||= [] + unless args.empty? + vals.concat args + vals.uniq! end + vals + end + end - # before_all and after_all hooks - - @cli_vals[:before_all] = [] - @cli_vals[:after_all] = [] - class << self - - def call_before_hooks obj - @cli_vals[:before_all].each do |blk| blk.call obj end - end - - def before_all meth=nil, &blk - @cli_vals[:before_all]<< ( meth.nil? ? blk : meth ) - @cli_vals[:before_all].uniq! - end - - def call_after_hooks obj - @cli_vals[:after_all].each do |blk| blk.call obj end - end + def cli_val(sym, *args) + vals = @cli_vals[sym] ||= [] + unless args.empty? + vals.concat args + vals.uniq! + end + vals + end - def after_all meth=nil, &blk - @cli_vals[:after_all]<< ( meth.nil? ? blk : meth ) - @cli_vals[:after_all].uniq! - end + # before_all and after_all hooks - end + @cli_vals[:before_all] = [] + @cli_vals[:after_all] = [] + class << self + def call_before_hooks(obj) + @cli_vals[:before_all].each { |blk| blk.call obj } + end - # rack entry point, page computation methods - - class << self - - def inherited sub - sub.engine! ( engine || (self==Zorglub::Node ? UNDEFINED : nil ) ) - sub.layout! ( layout || (self==Zorglub::Node ? UNDEFINED : nil ) ) - sub.instance_variable_set :@cli_vals, {} - @cli_vals.each do |s,v| sub.cli_val s, *v end - end - - def call env - meth, *args = env['PATH_INFO'].sub(/^\/+/,'').split(/\//) - meth ||= 'index' - $stderr << "=> #{meth}(#{args.join ','})\n" if app.opt :debug - node = self.new env, meth, args - return error_404 node, meth if not node.respond_to? meth - node.realize! - end - - def partial env, meth, *args - node = self.new env, meth.to_s, args, true - return error_404 node, meth if not node.respond_to? meth - node.feed! env[:no_hooks] - node.content - end - - def error_404 node, meth - $stderr << " !! #{node.class.name}::#{meth} not found\n" if app.opt :debug - resp = node.response - resp.status = 404 - resp['Content-Type'] = 'text/plain' - resp.write "%s mapped at %p can't respond to : %p" % [ node.class.name, node.map, node.meth ] - resp - end + def before_all(meth = nil, &blk) + @cli_vals[:before_all] << (meth.nil? ? blk : meth) + @cli_vals[:before_all].uniq! + end - end + def call_after_hooks(obj) + @cli_vals[:after_all].each { |blk| blk.call obj } + end - attr_reader :request, :response, :content, :mime, :state, :engine, :meth, :args - - def initialize env, meth, args, partial=false - @meth = meth - @args = args - @partial = partial - @request = Rack::Request.new env - @response = Rack::Response.new - @cli_vals ={} - @debug = app.opt :debug - @engine = self.class.engine - @layout = ( partial ? nil : self.class.layout ) - @view = r(meth) - @static = self.class.static - @cache_lifetime = self.class.cache_lifetime - self.class.cli_vals.each do |s,v| cli_val s, *v end - end + def after_all(meth = nil, &blk) + @cli_vals[:after_all] << (meth.nil? ? blk : meth) + @cli_vals[:after_all].uniq! + end + end - def realize! - catch(:stop_realize) { - feed! - response.write @content - response.headers['Content-Type'] ||= ( @mime || 'text/html' ) - response.finish - response - } - end + # rack entry point, page computation methods + + class << self + def inherited(sub) + super + sub.engine!(engine || (self == Zorglub::Node ? UNDEFINED : nil)) + sub.layout!(layout || (self == Zorglub::Node ? UNDEFINED : nil)) + sub.instance_variable_set :@cli_vals, {} + @cli_vals.each { |s, v| sub.cli_val s, *v } + end + + def call(env) + meth, *args = env['PATH_INFO'].sub(%r{^/+}, '').split(%r{/}) + meth ||= 'index' + $stderr << "=> #{meth}(#{args.join ','})\n" if app.opt :debug + node = new(env, meth, args) + return error404 node, meth unless node.respond_to? meth + + node.realize! + end + + def partial(env, meth, *args) + node = new env, meth.to_s, args, true + return error404 node, meth unless node.respond_to? meth + + node.feed! env[:no_hooks] + node.content + end + + def error404(node, meth) + $stderr << " !! #{node.class.name}::#{meth} not found\n" if app.opt :debug + resp = node.response + resp.status = 404 + resp['Content-Type'] = 'text/plain' + resp.write "%s mapped at %p can't respond to : %p" + resp + end + end - def feed! no_hooks=false - @state = :pre_cb - self.class.call_before_hooks self unless no_hooks - @state = :meth - @content = self.send @meth, *@args - static_path = static - if static_path.nil? - compile_page! - else - static_page! static_path - end - @state = :post_cb - self.class.call_after_hooks self unless no_hooks - @state = :finished - return @content, @mime - end + attr_reader :request, :response, :content, :mime, :state, :engine, :meth, :args + + def initialize(env, meth, args, partial = false) + @meth = meth + @args = args + @partial = partial + @request = Rack::Request.new env + @response = Rack::Response.new + @cli_vals = {} + @debug = app.opt :debug + @engine = self.class.engine + @layout = (partial ? nil : self.class.layout) + @view = r(meth) + @static = self.class.static + @cache_lifetime = self.class.cache_lifetime + self.class.cli_vals.each { |s, v| cli_val s, *v } + end - def static_page! path - if File.exist?(path) and ( @cache_lifetime.nil? or @cache_lifetime==0 or ( (Time.now-File.stat(path).mtime) < @cache_lifetime ) ) - $stderr << " * use cache file : #{path}\n" if @debug - content = File.open(path, 'r') {|f| f.read } - @content = content.sub /^@mime:(.*)\n/,'' - @mime = $1 - else - compile_page! - FileUtils.mkdir_p File.dirname(path) - File.open(path, 'w') {|f| f.write("@mime:"+@mime+"\n"); f.write(@content); } - $stderr << " * cache file created : #{path}\n" if @debug - end - end + def realize! + catch(:stop_realize) do + feed! + response.write @content + response.headers['Content-Type'] ||= @mime || 'text/html' + response.finish + response + end + end - def compile_page! - e, @ext = app.engine_proc_ext @engine, @ext - v, l = view, layout - if @debug - $stderr << " * "+(e ? 'use engine' : 'no engine ')+" : "+(e ? e.to_s : '')+"\n" - $stderr << " * "+((l and File.exist?(l)) ? 'use layout' : 'no layout ')+" : "+(l ? l : '')+"\n" - $stderr << " * "+((v and File.exist?(v)) ? 'use view ' : 'no view ')+" : "+(v ? v : '')+"\n" - end - @state = ( @partial ? :partial : :view ) - @content, mime = e.call v, self if e and v and File.exist? v - @mime ||= mime - @state = :layout - @content, mime = e.call l, self if e and l and File.exist? l - @mime = mime if @mime.nil? and not mime.nil? - end + def feed!(no_hooks = false) + @state = :pre_cb + self.class.call_before_hooks self unless no_hooks + @state = :meth + @content = send @meth, *@args + static_path = static + if static_path.nil? + compile_page! + else + static_page! static_path + end + @state = :post_cb + self.class.call_after_hooks self unless no_hooks + @state = :finished + [@content, @mime] + end + def static_page!(path) + if File.exist?(path) && (@cache_lifetime.nil? || @cache_lifetime.zero? || + (Time.now - File.stat(path).mtime) < @cache_lifetime) + $stderr << " * use cache file : #{path}\n" if @debug + content = File.read(path) + @content = content.sub(/^@mime:(.*)\n/, '') + @mime = ::Regexp.last_match(1) + else + compile_page! + FileUtils.mkdir_p File.dirname(path) + File.open(path, 'w') { |f| f.write("@mime:#{@mime}\n#{@content}") } + $stderr << " * cache file created : #{path}\n" if @debug + end end + def compile_page! + e, @ext = app.engine_proc_ext @engine, @ext + v = view + l = layout + if @debug + $stderr << " * #{e ? 'use engine' : 'no engine '} : #{e ? e.to_s : ''}\n" + $stderr << " * #{l && File.exist?(l) ? 'use layout' : 'no layout '} : #{l || ''}\n" + $stderr << " * #{v && File.exist?(v) ? 'use view ' : 'no view '} : #{v || ''}\n" + end + @state = @partial ? :partial : :view + @content, mime = e.call(v, self) if e && v && File.exist?(v) + @mime ||= mime + @state = :layout + @content, mime = e.call(l, self) if e && l && File.exist?(l) + @mime = mime if @mime.nil? && !mime.nil? + end + end end diff --git a/lib/zorglub/rack_session.rb b/lib/zorglub/rack_session.rb index d08cd84..c78bb37 100644 --- a/lib/zorglub/rack_session.rb +++ b/lib/zorglub/rack_session.rb @@ -1,9 +1,7 @@ -# -*- coding: UTF-8 -*- - module Zorglub - class Node - def session - @request.session - end + class Node + def session + @request.session end + end end diff --git a/lib/zorglub/session.rb b/lib/zorglub/session.rb index fc5fdbe..05b91d6 100644 --- a/lib/zorglub/session.rb +++ b/lib/zorglub/session.rb @@ -1,152 +1,148 @@ -# -*- coding: UTF-8 -*- - require 'securerandom' module Zorglub + class Node + @sessions = {} + + class << self + attr_reader :sessions + end + + def session + @session ||= SessionHash.new @request, @response, Node.sessions, app.opt(:session_options) + end + end + + class SessionHash < Hash + def initialize(req, resp, sessions, options) + @request = req + @response = resp + @sessions = sessions + @sid = nil + @options = options + super() + end + + def [](key) + load_data! + super key + end + + def key?(key) + load_data! + super key + end + alias include? key? + + def []=(key, value) + load_data! + super key, value + end + + def clear + load_data! + # @response.delete_cookie @options[:key] + # @sessions.delete @sid + # @sid = nil + super + end + + def to_hash + load_data! + h = {}.replace(self) + h.delete_if { |_k, v| v.nil? } + h + end + + def update(hash) + load_data! + super stringify_keys(hash) + end + + def delete(key) + load_data! + super key + end + + def inspect + if loaded? + super + else + "#<#{self.class}:0x#{object_id.to_s(16)} not yet loaded>" + end + end + + def exists? + loaded? ? @sessions.key?(@sid) : false + end + + def loaded? + !@sid.nil? + end + + def empty? + load_data! + super + end + + private + + def load_data! + return if loaded? + return unless @options[:enabled] + + sid = @request.cookies[@options[:key]] + if sid.nil? + sid = generate_sid! + @response.set_cookie @options[:key], sid + end + replace @sessions[sid] ||= {} + @sessions[sid] = self + @sid = sid + end + + def stringify_keys(other) + hash = {} + other.each do |key, value| + hash[key] = value + end + hash + end + + def generate_sid! + begin sid = sid_algorithm end while @sessions.key? sid + sid + end - class Node - - @sessions = {} - - class << self - attr_reader :sessions - end - - def session - @session ||= SessionHash.new @request, @response, Node.sessions, app.opt(:session_options) - end - end - - class SessionHash < Hash - - def initialize req, resp, sessions, options - @request = req - @response = resp - @sessions = sessions - @sid = nil - @options = options - super() - end - - def [] key - load_data! - super key - end - - def has_key? key - load_data! - super key - end - alias :key? :has_key? - alias :include? :has_key? - - def []= key, value - load_data! - super key, value - end - - def clear - load_data! - # @response.delete_cookie @options[:key] - # @sessions.delete @sid - # @sid = nil - super - end - - def to_hash - load_data! - h = {}.replace(self) - h.delete_if { |k,v| v.nil? } - h - end - - def update hash - load_data! - super stringify_keys(hash) - end - - def delete key - load_data! - super key - end - - def inspect - if loaded? - super - else - "#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>" - end - end - - def exists? - ( loaded? ? @sessions.has_key?(@sid) : false ) - end - - def loaded? - not @sid.nil? - end - - def empty? - load_data! - super - end - - private - - def load_data! - return if loaded? - if @options[:enabled] - sid = @request.cookies[@options[:key]] - if sid.nil? - sid = generate_sid! - @response.set_cookie @options[:key], sid - end - replace @sessions[sid] ||={} - @sessions[sid] = self - @sid = sid - end - end - - def stringify_keys other - hash = {} - other.each do |key, value| - hash[key] = value - end - hash - end - - def generate_sid! - begin sid = sid_algorithm end while @sessions.has_key? sid - sid - end - - begin - require 'securerandom' - # Using SecureRandom, optional length. - # SecureRandom is available since Ruby 1.8.7. - # For Ruby versions earlier than that, you can require the uuidtools gem, - # which has a drop-in replacement for SecureRandom. - def sid_algorithm; SecureRandom.hex(@options[:sid_len]); end - rescue LoadError - require 'openssl' - # Using OpenSSL::Random for generation, this is comparable in performance - # with stdlib SecureRandom and also allows for optional length, it should - # have the same behaviour as the SecureRandom::hex method of the - # uuidtools gem. - def sid_algorithm - OpenSSL::Random.random_bytes(@options[:sid_len] / 2).unpack('H*')[0] - end - rescue LoadError - # Digest::SHA2::hexdigest produces a string of length 64, although - # collisions are not very likely, the entropy is still very low and - # length is not optional. - # - # Replacing it with OS-provided random data would take a lot of code and - # won't be as cross-platform as Ruby. - def sid_algorithm - entropy = [ srand, rand, Time.now.to_f, rand, $$, rand, object_id ] - Digest::SHA2.hexdigest(entropy.join) - end - end + begin + require 'securerandom' + # Using SecureRandom, optional length. + # SecureRandom is available since Ruby 1.8.7. + # For Ruby versions earlier than that, you can require the uuidtools gem, + # which has a drop-in replacement for SecureRandom. + def sid_algorithm + SecureRandom.hex(@options[:sid_len]) + end + rescue LoadError + require 'openssl' + # Using OpenSSL::Random for generation, this is comparable in performance + # with stdlib SecureRandom and also allows for optional length, it should + # have the same behaviour as the SecureRandom::hex method of the + # uuidtools gem. + def sid_algorithm + OpenSSL::Random.random_bytes(@options[:sid_len] / 2).unpack1('H*')[0] + end + rescue LoadError + # Digest::SHA2::hexdigest produces a string of length 64, although + # collisions are not very likely, the entropy is still very low and + # length is not optional. + # + # Replacing it with OS-provided random data would take a lot of code and + # won't be as cross-platform as Ruby. + def sid_algorithm + entropy = [srand, rand, Time.now.to_f, rand, $$, rand, object_id] + Digest::SHA2.hexdigest(entropy.join) + end end + end end diff --git a/spec/app_spec.rb b/spec/app_spec.rb index 2d7aaeb..9864939 100644 --- a/spec/app_spec.rb +++ b/spec/app_spec.rb @@ -1,43 +1,37 @@ -# -*- coding: UTF-8 -*- - require 'spec_helper' describe Zorglub do + describe Zorglub::App do + it 'map should add a mapped node' do + expect(APP.at('/temp')).to be_nil + APP.map '/temp', Temp + expect(APP.at('/temp')).to be Temp + end - describe Zorglub::App do - - it "map should add a mapped node" do - expect(APP.at("/temp")).to be_nil - APP.map "/temp", Temp - expect(APP.at("/temp")).to be Temp - end - - it "delete should delete a mapped node" do - expect(APP.at("/temp")).to be Temp - APP.delete "/temp" - expect(APP.at("/temp")).to be_nil - end - - it "at should return mapped node" do - expect(APP.at("/node1")).to be Node1 - end - - it "at should return nil if no Node mapped" do - expect(APP.at("/none")).to be_nil - end + it 'delete should delete a mapped node' do + expect(APP.at('/temp')).to be Temp + APP.delete '/temp' + expect(APP.at('/temp')).to be_nil + end - it "to should return path to node" do - expect(APP.to(Node1)).to eq "/node1" - end + it 'at should return mapped node' do + expect(APP.at('/node1')).to be Node1 + end - it "to should return nil if not an existing Node" do - expect(APP.to(nil)).to be_nil - end + it 'at should return nil if no Node mapped' do + expect(APP.at('/none')).to be_nil + end - it "to_hash should return a correct hash" do - expect(APP.to_hash["/node1"]).to be Node1 - end + it 'to should return path to node' do + expect(APP.to(Node1)).to eq '/node1' + end + it 'to should return nil if not an existing Node' do + expect(APP.to(nil)).to be_nil end + it 'to_hash should return a correct hash' do + expect(APP.to_hash['/node1']).to be Node1 + end + end end diff --git a/spec/node_spec.rb b/spec/node_spec.rb index f77d7c3..fe1edfb 100644 --- a/spec/node_spec.rb +++ b/spec/node_spec.rb @@ -1,315 +1,309 @@ -# -*- coding: UTF-8 -*- - require 'spec_helper' def clean_static_path - static_base_path = Node0.app.static_base_path - Dir.glob( File.join(static_base_path,'**','*') ).each do |f| File.unlink f if File.file? f end - Dir.glob( File.join(static_base_path,'*') ).each do |d| Dir.rmdir d end - Dir.rmdir static_base_path if File.directory? static_base_path + static_base_path = Node0.app.static_base_path + Dir.glob(File.join(static_base_path, '**', '*')).each { |f| File.unlink f if File.file? f } + Dir.glob(File.join(static_base_path, '*')).each { |d| Dir.rmdir d } + Dir.rmdir static_base_path if File.directory? static_base_path end describe Zorglub do + describe Zorglub::Node do + before(:all) do + clean_static_path + end + + after(:all) do + clean_static_path + end + + it "engine should return default Node's engine" do + expect(Node0.engine).to eq Node0.app.opt(:engine) + end + + it "layout should return default Node's layout" do + expect(Node0.layout).to eq Node0.app.opt(:layout) + end + + it "engine should return class defined Node's engine" do + expect(Node1.engine).to eq 'engine-1' + expect(Node3.engine).to eq 'engine-2' + end + + it "layout should return class defined Node's layout" do + expect(Node1.layout).to eq 'layout-1' + expect(Node3.layout).to eq 'layout-2' + end + + it 'engine should return engine inherited from Node2' do + expect(Node2.engine).to eq 'engine-1' + end + + it 'layout should return layout inherited from Node2' do + expect(Node2.layout).to eq 'layout-1' + end + + it 'r should build a well formed path' do + expect(Node1.r(1, 'arg2', 'some')).to eq '/node1/1/arg2/some' + end + + it 'instance level map should work' do + r = Node0.my_call '/with_2args/1/2' + h = YAML.load r.body[0] + expect(h[:map]).to eq '/node0' + end + + it 'should return err404 response when no method found' do + expect(Node0.respond_to?('noresponse')).to be_falsey + r = Node0.my_call '/noresponse' + expect(r.status).to eq 404 + end + + it 'simple method should respond' do + r = Node0.my_call '/hello' + expect(r.status).to eq 200 + expect(r.body[0]).to eq 'world' + end + + it 'instance level args should work' do + r = Node0.my_call '/with_2args/1/2' + h = YAML.load r.body[0] + expect(h[:args][0]).to eq '1' + expect(h[:args][1]).to eq '2' + end + + it 'should raise error when too much arguments' do + expect(-> { Node0.my_call '/with_2args/1/2/3' }).to raise_error ArgumentError + end + + it 'layout proc, method level layout and engine definitions should work' do + r = Node0.my_call '/index' + expect(r.status).to eq 200 + h = YAML.load r.body[0] + ly = File.join Node0.app.layout_base_path, Node0.layout + vu = File.join Node0.app.view_base_path, Node0.r, 'index' + expect(h[:path]).to eq ly + expect(h[:layout]).to eq ly + expect(h[:view]).to eq vu + end + + it 'layout proc, method level layout and engine definitions should work' do + r = Node1.my_call '/index' + expect(r.status).to eq 200 + h = YAML.load r.body[0] + ly = File.join Node1.app.layout_base_path, 'main.spec' + vu = File.join Node1.app.view_base_path, Node1.r, 'index.spec' + expect(h[:path]).to eq ly + expect(h[:layout]).to eq ly + expect(h[:view]).to eq vu + end + + it 'before_all hook should work' do + Node3.before = 0 + Node3.after = 0 + expect(Node3.before).to eq 0 + expect(Node3.my_call_i('/index')).to eq 1 + expect(Node3.before).to eq 1 + expect(Node3.my_call_i('/index')).to eq 1 + expect(Node3.before).to eq 2 + expect(Node3.my_call_i('/index')).to eq 1 + expect(Node3.before).to eq 3 + end + + it 'after_all hook should work' do + Node3.before = 0 + Node3.after = 0 + expect(Node3.after).to eq 0 + expect(Node3.my_call_i('/index')).to eq 1 + expect(Node3.after).to eq 1 + expect(Node3.my_call_i('/index')).to eq 1 + expect(Node3.after).to eq 2 + expect(Node3.my_call_i('/index')).to eq 1 + expect(Node3.after).to eq 3 + end + + it 'inherited before_all hook should work' do + Node3.before = 0 + Node3.after = 0 + expect(Node3.before).to eq 0 + expect(Node8.my_call_i('/index')).to eq 1 + expect(Node3.before).to eq 1 + expect(Node8.my_call_i('/index')).to eq 1 + expect(Node3.before).to eq 2 + expect(Node8.my_call_i('/index')).to eq 1 + expect(Node3.before).to eq 3 + end + + it 'inherited after_all hook should work' do + Node3.before = 0 + Node3.after = 0 + expect(Node3.after).to eq 0 + expect(Node8.my_call_i('/index')).to eq 1 + expect(Node3.after).to eq 1 + expect(Node8.my_call_i('/index')).to eq 1 + expect(Node3.after).to eq 2 + expect(Node8.my_call_i('/index')).to eq 1 + expect(Node3.after).to eq 3 + end + + it 'should find view and layout and render them' do + r = Node0.my_call '/do_render' + expect(r.status).to eq 200 + expect(r.body[0]).to eq 'layout_start view_content layout_end' + end + + it 'default mime-type should be text/html' do + r = Node0.my_call '/index' + expect(r.headers['Content-type']).to eq 'text/html' + end + + it 'should be able to override mime-type' do + r = Node0.my_call '/do_render' + expect(r.headers['Content-type']).to eq 'text/view' + end + + it 'should be able to override through rack response mime-type' do + r = Node0.my_call '/do_content_type' + expect(r.headers['Content-type']).to eq 'text/mine' + end + + it 'partial should render correctly' do + expect(Node0.partial({}, :do_partial, 1, 2)).to eq 'partial_content' + end + + it 'method level view should work' do + expect(Node0.partial({}, :other_view)).to eq 'partial_content' + end + + it 'partial with hooks should be default' do + Node3.before = 0 + Node3.after = 0 + expect(Node3.partial({}, :do_partial, 1, 2)).to eq 'partial_content' + expect(Node3.before).to eq 1 + expect(Node3.after).to eq 1 + end - describe Zorglub::Node do - - before(:all) do - clean_static_path - end - - after(:all) do - clean_static_path - end - - it "engine should return default Node's engine" do - expect(Node0.engine).to eq Node0.app.opt(:engine) - end - - it "layout should return default Node's layout" do - expect(Node0.layout).to eq Node0.app.opt(:layout) - end - - it "engine should return class defined Node's engine" do - expect(Node1.engine).to eq "engine-1" - expect(Node3.engine).to eq "engine-2" - end - - it "layout should return class defined Node's layout" do - expect(Node1.layout).to eq "layout-1" - expect(Node3.layout).to eq "layout-2" - end - - it "engine should return engine inherited from Node2" do - expect(Node2.engine).to eq "engine-1" - end - - it "layout should return layout inherited from Node2" do - expect(Node2.layout).to eq "layout-1" - end - - it "r should build a well formed path" do - expect(Node1.r(1,'arg2',"some")).to eq "/node1/1/arg2/some" - end - - it "instance level map should work" do - r = Node0.my_call '/with_2args/1/2' - h = YAML.load r.body[0] - expect(h[:map]).to eq '/node0' - end - - it "should return err404 response when no method found" do - expect(Node0.respond_to?('noresponse')).to be_falsey - r = Node0.my_call '/noresponse' - expect(r.status).to eq 404 - end - - it "simple method should respond" do - r = Node0.my_call '/hello' - expect(r.status).to eq 200 - expect(r.body[0]).to eq 'world' - end - - it "instance level args should work" do - r = Node0.my_call '/with_2args/1/2' - h = YAML.load r.body[0] - expect(h[:args][0]).to eq '1' - expect(h[:args][1]).to eq '2' - end - - it "should raise error when too much arguments" do - expect(lambda{ r = Node0.my_call '/with_2args/1/2/3' }).to raise_error ArgumentError - end - - it "layout proc, method level layout and engine definitions should work" do - r = Node0.my_call '/index' - expect(r.status).to eq 200 - h = YAML.load r.body[0] - ly = File.join Node0.app.layout_base_path, Node0.layout - vu = File.join Node0.app.view_base_path, Node0.r, 'index' - expect(h[:path]).to eq ly - expect(h[:layout]).to eq ly - expect(h[:view]).to eq vu - end - - it "layout proc, method level layout and engine definitions should work" do - r = Node1.my_call '/index' - expect(r.status).to eq 200 - h = YAML.load r.body[0] - ly = File.join Node1.app.layout_base_path, 'main.spec' - vu = File.join Node1.app.view_base_path, Node1.r, 'index.spec' - expect(h[:path]).to eq ly - expect(h[:layout]).to eq ly - expect(h[:view]).to eq vu - end - - it "before_all hook should work" do - Node3.before = 0 - Node3.after = 0 - expect(Node3.before).to eq 0 - expect(Node3.my_call_i('/index')).to eq 1 - expect(Node3.before).to eq 1 - expect(Node3.my_call_i('/index')).to eq 1 - expect(Node3.before).to eq 2 - expect(Node3.my_call_i('/index')).to eq 1 - expect(Node3.before).to eq 3 - end - - it "after_all hook should work" do - Node3.before = 0 - Node3.after = 0 - expect(Node3.after).to eq 0 - expect(Node3.my_call_i('/index')).to eq 1 - expect(Node3.after).to eq 1 - expect(Node3.my_call_i('/index')).to eq 1 - expect(Node3.after).to eq 2 - expect(Node3.my_call_i('/index')).to eq 1 - expect(Node3.after).to eq 3 - end - - it "inherited before_all hook should work" do - Node3.before = 0 - Node3.after = 0 - expect(Node3.before).to eq 0 - expect(Node8.my_call_i('/index')).to eq 1 - expect(Node3.before).to eq 1 - expect(Node8.my_call_i('/index')).to eq 1 - expect(Node3.before).to eq 2 - expect(Node8.my_call_i('/index')).to eq 1 - expect(Node3.before).to eq 3 - end - - it "inherited after_all hook should work" do - Node3.before = 0 - Node3.after = 0 - expect(Node3.after).to eq 0 - expect(Node8.my_call_i('/index')).to eq 1 - expect(Node3.after).to eq 1 - expect(Node8.my_call_i('/index')).to eq 1 - expect(Node3.after).to eq 2 - expect(Node8.my_call_i('/index')).to eq 1 - expect(Node3.after).to eq 3 - end - - it "should find view and layout and render them" do - r = Node0.my_call '/do_render' - expect(r.status).to eq 200 - expect(r.body[0]).to eq "layout_start view_content layout_end" - end - - it "default mime-type should be text/html" do - r = Node0.my_call '/index' - expect(r.headers['Content-type']).to eq 'text/html' - end - - it "should be able to override mime-type" do - r = Node0.my_call '/do_render' - expect(r.headers['Content-type']).to eq 'text/view' - end - - it "should be able to override through rack response mime-type" do - r = Node0.my_call '/do_content_type' - expect(r.headers['Content-type']).to eq 'text/mine' - end - - it "partial should render correctly" do - expect(Node0.partial({},:do_partial, 1, 2)).to eq 'partial_content' - end - - it "method level view should work" do - expect(Node0.partial({},:other_view)).to eq 'partial_content' - end - - it "partial with hooks should be default" do - Node3.before = 0 - Node3.after = 0 - expect(Node3.partial({},:do_partial,1,2)).to eq 'partial_content' - expect(Node3.before).to eq 1 - expect(Node3.after).to eq 1 - end - - it "partial without hooks should work" do - Node3.before = 0 - Node3.after = 0 - expect(Node3.partial({:no_hooks=>true},:do_partial,1,2)).to eq 'partial_content' - expect(Node3.before).to eq 0 - expect(Node3.after).to eq 0 - end - - it "static pages should be generated" do - r = Node6.my_call '/do_static' - expect(r.body[0]).to eq 'VAL 1' - expect(r.headers['Content-type']).to eq 'text/static' - r = Node6.my_call '/do_static' - expect(r.body[0]).to eq 'VAL 1' - expect(r.headers['Content-type']).to eq 'text/static' - r = Node6.my_call '/do_static' - expect(r.body[0]).to eq 'VAL 1' - expect(r.headers['Content-type']).to eq 'text/static' - r = Node6.my_call '/no_static' - expect(r.body[0]).to eq 'VAL 4' - expect(r.headers['Content-type']).to eq 'text/static' - r = Node6.my_call '/do_static' - expect(r.body[0]).to eq 'VAL 1' - expect(r.headers['Content-type']).to eq 'text/static' - Node6.static! true, 0.000001 - sleep 0.0001 - r = Node6.my_call '/do_static' - expect(r.body[0]).to eq 'VAL 6' - expect(r.headers['Content-type']).to eq 'text/static' - end - - it "redirect should work" do - r = Node0.my_call '/do_redirect' - expect(r.status).to eq 302 - expect(r.headers['location']).to eq Node0.r(:do_partial,1,2,3) - end - - it "no_layout! should be inherited" do - expect(Node5.layout).to be_nil - end - - it "cli_vals should be inherited and extended" do - r = Node5.my_call '/index' - vars = YAML.load r.body[0] - expect(vars).to eq ['js0','js1','js3','jsx','css0','css1','css2'] - expect(vars[7]).to be_nil - end - - it "cli_vals should be extended at method level" do - r = Node4.my_call '/more' - vars = YAML.load r.body[0] - expect(vars).to eq ['js0','js1','js2'] - expect(vars[3]).to be_nil - end - - it "cli_vals should be untouched" do - r = Node4.my_call '/index' - vars = YAML.load r.body[0] - expect(vars).to eq ['js0','js1'] - expect(vars[2]).to be_nil - r = Node5.my_call '/index' - vars = YAML.load r.body[0] - expect(vars).to eq ['js0','js1','js3','jsx','css0','css1','css2'] - expect(vars[7]).to be_nil - end - - it "ext definition and file engine should work" do - r = Node0.my_call '/xml_file' - expect(r.body[0]).to eq "file<\/xml>\n" - expect(r.headers['Content-type']).to eq 'application/xml' - r = Node0.my_call '/plain_file' - expect(r.body[0]).to eq "plain file\n" - expect(r.headers['Content-type']).to eq 'text/plain' - end - - it "no view no layout should work as well" do - r = Node0.my_call '/no_view_no_layout' - expect(r.body[0]).to eq "hello world" - end - - it "haml engine should work" do - Node0.app.opt! :engines_cache_enabled, false - r = Node0.my_call '/engines/haml' - expect(r.body[0]).to eq "

Hello world

\n" - Node0.app.opt! :engines_cache_enabled, true - r = Node0.my_call '/engines/haml' - expect(r.body[0]).to eq "

Hello world

\n" - end - - it "sass engine should work" do - Node0.app.opt! :engines_cache_enabled, true - r = Node0.my_call '/engines/sass' - expect(r.body[0]).to eq "vbar{width:80%;height:23px}vbar ul{list-style-type:none}vbar li{float:left}vbar li a{font-weight:bold}\n" - Node0.app.opt! :engines_cache_enabled, false - r = Node0.my_call '/engines/sass' - expect(r.body[0]).to eq "vbar{width:80%;height:23px}vbar ul{list-style-type:none}vbar li{float:left}vbar li a{font-weight:bold}\n" - end - - it "view_base_path! should work" do - r = Node7.my_call '/view_path' - h = YAML.load r.body[0] - expect(h[:view]).to eq File.join(Node7.app.opt(:root), 'alt','do_render') - end - - it "layout_base_path! should work" do - r = Node7.my_call '/view_path' - h = YAML.load r.body[0] - expect(h[:layout]).to eq File.join(Node7.app.opt(:root), 'alt','layout','default') - end - - it "debug out should work" do - stderr0= $stderr.dup - stderrs = StringIO.new - $stderr = stderrs - begin - APP.opt! :debug, true - Node0.my_call '/hello' - ensure - $stderr = stderr0 - end - expect(stderrs.string.include?('spec/data/view/node0/hello')).to be true - end + it 'partial without hooks should work' do + Node3.before = 0 + Node3.after = 0 + expect(Node3.partial({ no_hooks: true }, :do_partial, 1, 2)).to eq 'partial_content' + expect(Node3.before).to eq 0 + expect(Node3.after).to eq 0 + end + + it 'static pages should be generated' do + r = Node6.my_call '/do_static' + expect(r.body[0]).to eq 'VAL 1' + expect(r.headers['Content-type']).to eq 'text/static' + r = Node6.my_call '/do_static' + expect(r.body[0]).to eq 'VAL 1' + expect(r.headers['Content-type']).to eq 'text/static' + r = Node6.my_call '/do_static' + expect(r.body[0]).to eq 'VAL 1' + expect(r.headers['Content-type']).to eq 'text/static' + r = Node6.my_call '/no_static' + expect(r.body[0]).to eq 'VAL 4' + expect(r.headers['Content-type']).to eq 'text/static' + r = Node6.my_call '/do_static' + expect(r.body[0]).to eq 'VAL 1' + expect(r.headers['Content-type']).to eq 'text/static' + Node6.static! true, 0.000001 + sleep 0.0001 + r = Node6.my_call '/do_static' + expect(r.body[0]).to eq 'VAL 6' + expect(r.headers['Content-type']).to eq 'text/static' + end + + it 'redirect should work' do + r = Node0.my_call '/do_redirect' + expect(r.status).to eq 302 + expect(r.headers['location']).to eq Node0.r(:do_partial, 1, 2, 3) + end + it 'no_layout! should be inherited' do + expect(Node5.layout).to be_nil end + it 'cli_vals should be inherited and extended' do + r = Node5.my_call '/index' + vars = YAML.load r.body[0] + expect(vars).to eq %w[js0 js1 js3 jsx css0 css1 css2] + expect(vars[7]).to be_nil + end + + it 'cli_vals should be extended at method level' do + r = Node4.my_call '/more' + vars = YAML.load r.body[0] + expect(vars).to eq %w[js0 js1 js2] + expect(vars[3]).to be_nil + end + + it 'cli_vals should be untouched' do + r = Node4.my_call '/index' + vars = YAML.load r.body[0] + expect(vars).to eq %w[js0 js1] + expect(vars[2]).to be_nil + r = Node5.my_call '/index' + vars = YAML.load r.body[0] + expect(vars).to eq %w[js0 js1 js3 jsx css0 css1 css2] + expect(vars[7]).to be_nil + end + + it 'ext definition and file engine should work' do + r = Node0.my_call '/xml_file' + expect(r.body[0]).to eq "file<\/xml>\n" + expect(r.headers['Content-type']).to eq 'application/xml' + r = Node0.my_call '/plain_file' + expect(r.body[0]).to eq "plain file\n" + expect(r.headers['Content-type']).to eq 'text/plain' + end + + it 'no view no layout should work as well' do + r = Node0.my_call '/no_view_no_layout' + expect(r.body[0]).to eq 'hello world' + end + + it 'haml engine should work' do + Node0.app.opt! :engines_cache_enabled, false + r = Node0.my_call '/engines/haml' + expect(r.body[0]).to eq "

Hello world

\n" + Node0.app.opt! :engines_cache_enabled, true + r = Node0.my_call '/engines/haml' + expect(r.body[0]).to eq "

Hello world

\n" + end + + it 'sass engine should work' do + Node0.app.opt! :engines_cache_enabled, true + r = Node0.my_call '/engines/sass' + expect(r.body[0]).to eq "vbar{width:80%;height:23px}vbar ul{list-style-type:none}vbar li{float:left}vbar li a{font-weight:bold}\n" + Node0.app.opt! :engines_cache_enabled, false + r = Node0.my_call '/engines/sass' + expect(r.body[0]).to eq "vbar{width:80%;height:23px}vbar ul{list-style-type:none}vbar li{float:left}vbar li a{font-weight:bold}\n" + end + + it 'view_base_path! should work' do + r = Node7.my_call '/view_path' + h = YAML.load r.body[0] + expect(h[:view]).to eq File.join(Node7.app.opt(:root), 'alt', 'do_render') + end + + it 'layout_base_path! should work' do + r = Node7.my_call '/view_path' + h = YAML.load r.body[0] + expect(h[:layout]).to eq File.join(Node7.app.opt(:root), 'alt', 'layout', 'default') + end + + it 'debug out should work' do + stderr0 = $stderr.dup + stderrs = StringIO.new + $stderr = stderrs + begin + APP.opt! :debug, true + Node0.my_call '/hello' + ensure + $stderr = stderr0 + end + expect(stderrs.string.include?('spec/data/view/node0/hello')).to be true + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9b2c452..257327e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,19 +1,19 @@ #! /usr/bin/env ruby begin - require 'simplecov' - SimpleCov.start do - add_filter 'spec' - end + require 'simplecov' + SimpleCov.start do + add_filter 'spec' + end rescue LoadError end begin - require 'coveralls' - SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new [ - SimpleCov::Formatter::HTMLFormatter, - Coveralls::SimpleCov::Formatter - ] + require 'coveralls' + SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new [ + SimpleCov::Formatter::HTMLFormatter, + Coveralls::SimpleCov::Formatter + ] rescue LoadError end @@ -24,206 +24,223 @@ require 'zorglub/engines/file' require 'zorglub/engines/haml' require 'zorglub/engines/sass' -HASH_PROC = Proc.new { |path,obj| {:path=>path,:layout=>obj.layout,:view=>obj.view,:args=>obj.args,:map=>obj.map}.to_yaml } -STATIC_PROC = Proc.new { |path,obj| ["VAL #{obj.value}",'text/static'] } -RENDER_PROC = Proc.new { |path,obj| - case obj.state - when :layout - "layout_start #{obj.content} layout_end" - when :view - ["view_content", 'text/view'] - when :partial - ['partial_content','text/partial'] - else - raise Exception.new - end +HASH_PROC = proc { |path, obj| { path: path, layout: obj.layout, view: obj.view, args: obj.args, map: obj.map }.to_yaml } +STATIC_PROC = proc { |_path, obj| ["VAL #{obj.value}", 'text/static'] } +RENDER_PROC = proc { |_path, obj| + case obj.state + when :layout + "layout_start #{obj.content} layout_end" + when :view + ['view_content', 'text/view'] + when :partial + ['partial_content', 'text/partial'] + else + raise Exception.new + end } APP_ROOT = File.join Dir.pwd, 'spec', 'data' class Zorglub::Node - def self.my_call uri - call( {'PATH_INFO'=>uri} ) - end - def self.my_call_i uri - call( {'PATH_INFO'=>uri} ).body[0].to_i - end + def self.my_call(uri) + call({ 'PATH_INFO' => uri }) + end + + def self.my_call_i(uri) + call({ 'PATH_INFO' => uri }).body[0].to_i + end end class Temp < Zorglub::Node end class Node0 < Zorglub::Node - # default - def index - html - end - def hello - no_layout! - 'world' - end - def with_2args a1, a2 - end - def do_render - engine! 'real' - end - def do_content_type - engine! 'real' - response.headers['Content-Type'] = 'text/mine' - end - def do_partial a1, a2 - engine! 'real' - end - def other_view - engine! 'real' - view! r('do_partial') - end - def do_redirect - redirect r(:do_partial,1,2,3) - end - def xml_file - no_layout! - engine! :file - ext! 'xml' - mime! 'application/xml' - end - def plain_file - no_layout! - engine! :file - ext! 'txt' - mime! 'text/plain' - end - def no_view_no_layout - no_view! - no_layout! - 'hello world' - end - def engines name - no_layout! - case name - when 'haml' - engine! :haml - when 'sass' - engine! :sass - end + # default + def index + html + end + + def hello + no_layout! + 'world' + end + + def with_2args(arg1, arg2); end + + def do_render + engine! 'real' + end + + def do_content_type + engine! 'real' + response.headers['Content-Type'] = 'text/mine' + end + + def do_partial(_arg1, _arg2) + engine! 'real' + end + + def other_view + engine! 'real' + view! r('do_partial') + end + + def do_redirect + redirect r(:do_partial, 1, 2, 3) + end + + def xml_file + no_layout! + engine! :file + ext! 'xml' + mime! 'application/xml' + end + + def plain_file + no_layout! + engine! :file + ext! 'txt' + mime! 'text/plain' + end + + def no_view_no_layout + no_view! + no_layout! + 'hello world' + end + + def engines(name) + no_layout! + case name + when 'haml' + engine! :haml + when 'sass' + engine! :sass end + end end class Node1 < Zorglub::Node - layout! 'layout-1' - engine! 'engine-1' - def index - layout! 'main' - engine! 'engine-2' - end + layout! 'layout-1' + engine! 'engine-1' + def index + layout! 'main' + engine! 'engine-2' + end end class Node2 < Node1 - # inherited from Node1 + # inherited from Node1 end class Node3 < Zorglub::Node - @before=0 - @after=0 - class << self - attr_accessor :before, :after - def post obj - @after +=1 - end - end - before_all do |node| - Node3.before +=1 - end - after_all Node3.method(:post) - layout! 'layout-2' - engine! 'engine-2' - def index - Node3.before-Node3.after - end - def do_partial a1, a2 - view! Node0.r('do_partial') - engine! 'real' - end + @before = 0 + @after = 0 + class << self + attr_accessor :before, :after + + def post(_obj) + @after += 1 + end + end + before_all do |_node| + Node3.before += 1 + end + after_all Node3.method(:post) + layout! 'layout-2' + engine! 'engine-2' + def index + Node3.before - Node3.after + end + + def do_partial(_arg1, _arg2) + view! Node0.r('do_partial') + engine! 'real' + end end class Node8 < Node3 end class Node4 < Zorglub::Node - no_layout! - cli_val :js,'js0' - cli_val :js,'js1' - def index - cli_val(:js).to_yaml - end - def more - cli_val :js,'js2' - cli_val(:js).to_yaml - end + no_layout! + cli_val :js, 'js0' + cli_val :js, 'js1' + + def index + cli_val(:js).to_yaml + end + + def more + cli_val :js, 'js2' + cli_val(:js).to_yaml + end end class Node5 < Node4 - cli_val :js, 'js3' - cli_val :css, 'css0', 'css1' - # no_layout! inherited from Node4 - def index - js = cli_val(:js,'jsx') - cli_val(:css, 'css0', 'css1','css2') - css = cli_val :css - js.concat(css).to_yaml - end + cli_val :js, 'js3' + cli_val :css, 'css0', 'css1' + # no_layout! inherited from Node4 + def index + js = cli_val(:js, 'jsx') + cli_val(:css, 'css0', 'css1', 'css2') + css = cli_val :css + js.concat(css).to_yaml + end end class Node6 < Zorglub::Node - @static_cpt=0 - class << self - attr_accessor :static_cpt - end - attr_reader :value - static! true, 5 - def no_static - static! false - engine! 'static' - view! Node0.r('do_render') - Node6.static_cpt+=1 - @value = Node6.static_cpt - end - def do_static - engine! 'static' - view! Node0.r('do_render') - Node6.static_cpt+=1 - @value = Node6.static_cpt - end + @static_cpt = 0 + class << self + attr_accessor :static_cpt + end + attr_reader :value + + static! true, 5 + def no_static + static! false + engine! 'static' + view! Node0.r('do_render') + Node6.static_cpt += 1 + @value = Node6.static_cpt + end + + def do_static + engine! 'static' + view! Node0.r('do_render') + Node6.static_cpt += 1 + @value = Node6.static_cpt + end end class Node7 < Zorglub::Node - layout_base_path! File.join APP_ROOT, 'alt','layout' - view_base_path! File.join APP_ROOT, 'alt' - def view_path - view! 'do_render' - end + layout_base_path! File.join APP_ROOT, 'alt', 'layout' + view_base_path! File.join APP_ROOT, 'alt' + def view_path + view! 'do_render' + end end APP = Zorglub::App.new do - register_engine! :file, nil, Zorglub::Engines::File.method(:proc) - register_engine! :haml, 'haml', Zorglub::Engines::Haml.method(:proc) - register_engine! :sass, 'scss', Zorglub::Engines::Sass.method(:proc) - register_engine! 'default', nil, HASH_PROC - register_engine! 'engine-1', 'spec', HASH_PROC - register_engine! 'engine-2', 'spec', HASH_PROC - register_engine! 'real', nil, RENDER_PROC - register_engine! 'static', nil, STATIC_PROC - opt! :root, APP_ROOT - opt! :engine, 'default' - map '/node0', Node0 - map '/node1', Node1 - map '/node3', Node3 - map '/node4', Node4 - map '/node5', Node5 - map '/node6', Node6 - map '/node7', Node7 - map '/node8', Node8 + register_engine! :file, nil, Zorglub::Engines::File.method(:proc) + register_engine! :haml, 'haml', Zorglub::Engines::Haml.method(:proc) + register_engine! :sass, 'scss', Zorglub::Engines::Sass.method(:proc) + register_engine! 'default', nil, HASH_PROC + register_engine! 'engine-1', 'spec', HASH_PROC + register_engine! 'engine-2', 'spec', HASH_PROC + register_engine! 'real', nil, RENDER_PROC + register_engine! 'static', nil, STATIC_PROC + opt! :root, APP_ROOT + opt! :engine, 'default' + map '/node0', Node0 + map '/node1', Node1 + map '/node3', Node3 + map '/node4', Node4 + map '/node5', Node5 + map '/node6', Node6 + map '/node7', Node7 + map '/node8', Node8 end class Node2 - map APP, '/node2' + map APP, '/node2' end -- cgit v1.1-2-g2b99