#!/usr/bin/env ruby ### ### $Release: 2.6.5 $ ### copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ### #--begin of require 'erubis/main' ### ### $Release: 2.6.5 $ ### copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ### require 'yaml' #--begin of require 'erubis' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## ## ## an implementation of eRuby ## ## ex. ## input = <<'END' ## ## END ## list = ['', 'b&b', '"ccc"'] ## eruby = Erubis::Eruby.new(input) ## puts "--- code ---" ## puts eruby.src ## puts "--- result ---" ## context = Erubis::Context.new() # or new(:list=>list) ## context[:list] = list ## puts eruby.evaluate(context) ## ## result: ## --- source --- ## _buf = ''; _buf << ' ## '; ## _buf.to_s ## --- result --- ## ## module Erubis VERSION = ('$Release: 2.6.5 $' =~ /([.\d]+)/) && $1 end #--begin of require 'erubis/engine' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## #--begin of require 'erubis/generator' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## #--begin of require 'abstract' ## ## $Rev: 1 $ ## $Release: 0.1.0 $ ## copyright(c) 2006 kuwata-lab.com all rights reserved. ## ## ## helper to define abstract method in Ruby. ## ## ## example1. (shorter notation) ## ## require 'abstract' ## class Foo ## abstract_method 'arg1, arg2=""', :method1, :method2, :method3 ## end ## ## ## example2. (RDoc friendly notation) ## ## require 'abstract' ## class Bar ## # ... method1 description ... ## def method1(arg1, arg2="") ## not_implemented ## end ## ## # ... method2 description ... ## def method2(arg1, arg2="") ## not_implemented ## end ## end ## ## class Module ## ## define abstract methods ## def abstract_method args_str, *method_names method_names.each do |name| module_eval <<-END def #{name}(#{args_str}) mesg = "class \#{self.class.name} must implement abstract method `#{self.name}##{name}()'." #mesg = "\#{self.class.name}##{name}() is not implemented." err = NotImplementedError.new mesg err.set_backtrace caller() raise err end END end end end ## module Kernel ## ## raise NotImplementedError ## def not_implemented #:doc: backtrace = caller() method_name = (backtrace.shift =~ /`(\w+)'$/) && $1 mesg = "class #{self.class.name} must implement abstract method '#{method_name}()'." #mesg = "#{self.class.name}##{method_name}() is not implemented." err = NotImplementedError.new mesg err.set_backtrace backtrace raise err end private :not_implemented end #--end of require 'abstract' module Erubis ## ## code generator, called by Converter module ## module Generator def self.supported_properties() # :nodoc: return [ [:escapefunc, nil, "escape function name"], ] end attr_accessor :escapefunc def init_generator(properties={}) @escapefunc = properties[:escapefunc] end ## (abstract) escape text string ## ## ex. ## def escape_text(text) ## return text.dump ## # or return "'" + text.gsub(/['\\]/, '\\\\\&') + "'" ## end def escape_text(text) not_implemented end ## return escaped expression code (ex. 'h(...)' or 'htmlspecialchars(...)') def escaped_expr(code) code.strip! return "#{@escapefunc}(#{code})" end ## (abstract) add @preamble to src def add_preamble(src) not_implemented end ## (abstract) add text string to src def add_text(src, text) not_implemented end ## (abstract) add statement code to src def add_stmt(src, code) not_implemented end ## (abstract) add expression literal code to src. this is called by add_expr(). def add_expr_literal(src, code) not_implemented end ## (abstract) add escaped expression code to src. this is called by add_expr(). def add_expr_escaped(src, code) not_implemented end ## (abstract) add expression code to src for debug. this is called by add_expr(). def add_expr_debug(src, code) not_implemented end ## (abstract) add @postamble to src def add_postamble(src) not_implemented end end end #--end of require 'erubis/generator' #--begin of require 'erubis/converter' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## #--already included require 'abstract' module Erubis ## ## convert ## module Converter attr_accessor :preamble, :postamble, :escape def self.supported_properties # :nodoc: return [ [:preamble, nil, "preamble (no preamble when false)"], [:postamble, nil, "postamble (no postamble when false)"], [:escape, nil, "escape expression or not in default"], ] end def init_converter(properties={}) @preamble = properties[:preamble] @postamble = properties[:postamble] @escape = properties[:escape] end ## convert input string into target language def convert(input) codebuf = "" # or [] @preamble.nil? ? add_preamble(codebuf) : (@preamble && (codebuf << @preamble)) convert_input(codebuf, input) @postamble.nil? ? add_postamble(codebuf) : (@postamble && (codebuf << @postamble)) @_proc = nil # clear cached proc object return codebuf # or codebuf.join() end protected ## ## detect spaces at beginning of line ## def detect_spaces_at_bol(text, is_bol) lspace = nil if text.empty? lspace = "" if is_bol elsif text[-1] == ?\n lspace = "" else rindex = text.rindex(?\n) if rindex s = text[rindex+1..-1] if s =~ /\A[ \t]*\z/ lspace = s #text = text[0..rindex] text[rindex+1..-1] = '' end else if is_bol && text =~ /\A[ \t]*\z/ #lspace = text #text = nil lspace = text.dup text[0..-1] = '' end end end return lspace end ## ## (abstract) convert input to code ## def convert_input(codebuf, input) not_implemented end end module Basic end ## ## basic converter which supports '<% ... %>' notation. ## module Basic::Converter include Erubis::Converter def self.supported_properties # :nodoc: return [ [:pattern, '<% %>', "embed pattern"], [:trim, true, "trim spaces around <% ... %>"], ] end attr_accessor :pattern, :trim def init_converter(properties={}) super(properties) @pattern = properties[:pattern] @trim = properties[:trim] != false end protected ## return regexp of pattern to parse eRuby script def pattern_regexp(pattern) @prefix, @postfix = pattern.split() # '<% %>' => '<%', '%>' #return /(.*?)(^[ \t]*)?#{@prefix}(=+|\#)?(.*?)-?#{@postfix}([ \t]*\r?\n)?/m #return /(^[ \t]*)?#{@prefix}(=+|\#)?(.*?)-?#{@postfix}([ \t]*\r?\n)?/m return /#{@prefix}(=+|-|\#|%)?(.*?)([-=])?#{@postfix}([ \t]*\r?\n)?/m end module_function :pattern_regexp #DEFAULT_REGEXP = /(.*?)(^[ \t]*)?<%(=+|\#)?(.*?)-?%>([ \t]*\r?\n)?/m #DEFAULT_REGEXP = /(^[ \t]*)?<%(=+|\#)?(.*?)-?%>([ \t]*\r?\n)?/m #DEFAULT_REGEXP = /<%(=+|\#)?(.*?)-?%>([ \t]*\r?\n)?/m DEFAULT_REGEXP = pattern_regexp('<% %>') public def convert_input(src, input) pat = @pattern regexp = pat.nil? || pat == '<% %>' ? DEFAULT_REGEXP : pattern_regexp(pat) pos = 0 is_bol = true # is beginning of line input.scan(regexp) do |indicator, code, tailch, rspace| match = Regexp.last_match() len = match.begin(0) - pos text = input[pos, len] pos = match.end(0) ch = indicator ? indicator[0] : nil lspace = ch == ?= ? nil : detect_spaces_at_bol(text, is_bol) is_bol = rspace ? true : false add_text(src, text) if text && !text.empty? ## * when '<%= %>', do nothing ## * when '<% %>' or '<%# %>', delete spaces iff only spaces are around '<% %>' if ch == ?= # <%= %> rspace = nil if tailch && !tailch.empty? add_text(src, lspace) if lspace add_expr(src, code, indicator) add_text(src, rspace) if rspace elsif ch == ?\# # <%# %> n = code.count("\n") + (rspace ? 1 : 0) if @trim && lspace && rspace add_stmt(src, "\n" * n) else add_text(src, lspace) if lspace add_stmt(src, "\n" * n) add_text(src, rspace) if rspace end elsif ch == ?% # <%% %> s = "#{lspace}#{@prefix||='<%'}#{code}#{tailch}#{@postfix||='%>'}#{rspace}" add_text(src, s) else # <% %> if @trim && lspace && rspace add_stmt(src, "#{lspace}#{code}#{rspace}") else add_text(src, lspace) if lspace add_stmt(src, code) add_text(src, rspace) if rspace end end end #rest = $' || input # ruby1.8 rest = pos == 0 ? input : input[pos..-1] # ruby1.9 add_text(src, rest) end ## add expression code to src def add_expr(src, code, indicator) case indicator when '=' @escape ? add_expr_escaped(src, code) : add_expr_literal(src, code) when '==' @escape ? add_expr_literal(src, code) : add_expr_escaped(src, code) when '===' add_expr_debug(src, code) end end end module PI end ## ## Processing Instructions (PI) converter for XML. ## this class converts '' and '${...}' notation. ## module PI::Converter include Erubis::Converter def self.desc # :nodoc: "use processing instructions (PI) instead of '<% %>'" end def self.supported_properties # :nodoc: return [ [:trim, true, "trim spaces around <% ... %>"], [:pi, 'rb', "PI (Processing Instrunctions) name"], [:embchar, '@', "char for embedded expression pattern('@{...}@')"], [:pattern, '<% %>', "embed pattern"], ] end attr_accessor :pi, :prefix def init_converter(properties={}) super(properties) @trim = properties.fetch(:trim, true) @pi = properties[:pi] if properties[:pi] @embchar = properties[:embchar] || '@' @pattern = properties[:pattern] @pattern = '<% %>' if @pattern.nil? #|| @pattern == true end def convert(input) code = super(input) return @header || @footer ? "#{@header}#{code}#{@footer}" : code end protected def convert_input(codebuf, input) unless @regexp @pi ||= 'e' ch = Regexp.escape(@embchar) if @pattern left, right = @pattern.split(' ') @regexp = /<\?#{@pi}(?:-(\w+))?(\s.*?)\?>([ \t]*\r?\n)?|#{ch}(!*)?\{(.*?)\}#{ch}|#{left}(=+)(.*?)#{right}/m else @regexp = /<\?#{@pi}(?:-(\w+))?(\s.*?)\?>([ \t]*\r?\n)?|#{ch}(!*)?\{(.*?)\}#{ch}/m end end # is_bol = true pos = 0 input.scan(@regexp) do |pi_arg, stmt, rspace, indicator1, expr1, indicator2, expr2| match = Regexp.last_match len = match.begin(0) - pos text = input[pos, len] pos = match.end(0) lspace = stmt ? detect_spaces_at_bol(text, is_bol) : nil is_bol = stmt && rspace ? true : false add_text(codebuf, text) # unless text.empty? # if stmt if @trim && lspace && rspace add_pi_stmt(codebuf, "#{lspace}#{stmt}#{rspace}", pi_arg) else add_text(codebuf, lspace) if lspace add_pi_stmt(codebuf, stmt, pi_arg) add_text(codebuf, rspace) if rspace end else add_pi_expr(codebuf, expr1 || expr2, indicator1 || indicator2) end end #rest = $' || input # ruby1.8 rest = pos == 0 ? input : input[pos..-1] # ruby1.9 add_text(codebuf, rest) end #-- #def convert_input(codebuf, input) # parse_stmts(codebuf, input) # #parse_stmts2(codebuf, input) #end # #def parse_stmts(codebuf, input) # #regexp = pattern_regexp(@pattern) # @pi ||= 'e' # @stmt_pattern ||= /<\?#{@pi}(?:-(\w+))?(\s.*?)\?>([ \t]*\r?\n)?/m # is_bol = true # pos = 0 # input.scan(@stmt_pattern) do |pi_arg, code, rspace| # match = Regexp.last_match # len = match.begin(0) - pos # text = input[pos, len] # pos = match.end(0) # lspace = detect_spaces_at_bol(text, is_bol) # is_bol = rspace ? true : false # parse_exprs(codebuf, text) # unless text.empty? # if @trim && lspace && rspace # add_pi_stmt(codebuf, "#{lspace}#{code}#{rspace}", pi_arg) # else # add_text(codebuf, lspace) # add_pi_stmt(codebuf, code, pi_arg) # add_text(codebuf, rspace) # end # end # rest = $' || input # parse_exprs(codebuf, rest) #end # #def parse_exprs(codebuf, input) # unless @expr_pattern # ch = Regexp.escape(@embchar) # if @pattern # left, right = @pattern.split(' ') # @expr_pattern = /#{ch}(!*)?\{(.*?)\}#{ch}|#{left}(=+)(.*?)#{right}/ # else # @expr_pattern = /#{ch}(!*)?\{(.*?)\}#{ch}/ # end # end # pos = 0 # input.scan(@expr_pattern) do |indicator1, code1, indicator2, code2| # indicator = indicator1 || indicator2 # code = code1 || code2 # match = Regexp.last_match # len = match.begin(0) - pos # text = input[pos, len] # pos = match.end(0) # add_text(codebuf, text) # unless text.empty? # add_pi_expr(codebuf, code, indicator) # end # rest = $' || input # add_text(codebuf, rest) #end #++ def add_pi_stmt(codebuf, code, pi_arg) # :nodoc: case pi_arg when nil ; add_stmt(codebuf, code) when 'header' ; @header = code when 'footer' ; @footer = code when 'comment'; add_stmt(codebuf, "\n" * code.count("\n")) when 'value' ; add_expr_literal(codebuf, code) else ; add_stmt(codebuf, code) end end def add_pi_expr(codebuf, code, indicator) # :nodoc: case indicator when nil, '', '==' # @{...}@ or <%== ... %> @escape == false ? add_expr_literal(codebuf, code) : add_expr_escaped(codebuf, code) when '!', '=' # @!{...}@ or <%= ... %> @escape == false ? add_expr_escaped(codebuf, code) : add_expr_literal(codebuf, code) when '!!', '===' # @!!{...}@ or <%=== ... %> add_expr_debug(codebuf, code) else # ignore end end end end #--end of require 'erubis/converter' #--begin of require 'erubis/evaluator' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## #--begin of require 'erubis/error' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## module Erubis ## ## base error class ## class ErubisError < StandardError end ## ## raised when method or function is not supported ## class NotSupportedError < ErubisError end end #--end of require 'erubis/error' #--begin of require 'erubis/context' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## module Erubis ## ## context object for Engine#evaluate ## ## ex. ## template = <<'END' ## Hello <%= @user %>! ## <% for item in @list %> ## - <%= item %> ## <% end %> ## END ## ## context = Erubis::Context.new(:user=>'World', :list=>['a','b','c']) ## # or ## # context = Erubis::Context.new ## # context[:user] = 'World' ## # context[:list] = ['a', 'b', 'c'] ## ## eruby = Erubis::Eruby.new(template) ## print eruby.evaluate(context) ## class Context include Enumerable def initialize(hash=nil) hash.each do |name, value| self[name] = value end if hash end def [](key) return instance_variable_get("@#{key}") end def []=(key, value) return instance_variable_set("@#{key}", value) end def keys return instance_variables.collect { |name| name[1..-1] } end def each instance_variables.each do |name| key = name[1..-1] value = instance_variable_get(name) yield(key, value) end end def to_hash hash = {} self.keys.each { |key| hash[key] = self[key] } return hash end def update(context_or_hash) arg = context_or_hash if arg.is_a?(Hash) arg.each do |key, val| self[key] = val end else arg.instance_variables.each do |varname| key = varname[1..-1] val = arg.instance_variable_get(varname) self[key] = val end end end end end #--end of require 'erubis/context' module Erubis EMPTY_BINDING = binding() ## ## evaluate code ## module Evaluator def self.supported_properties # :nodoc: return [] end attr_accessor :src, :filename def init_evaluator(properties) @filename = properties[:filename] end def result(*args) raise NotSupportedError.new("evaluation of code except Ruby is not supported.") end def evaluate(*args) raise NotSupportedError.new("evaluation of code except Ruby is not supported.") end end ## ## evaluator for Ruby ## module RubyEvaluator include Evaluator def self.supported_properties # :nodoc: list = Evaluator.supported_properties return list end ## eval(@src) with binding object def result(_binding_or_hash=TOPLEVEL_BINDING) _arg = _binding_or_hash if _arg.is_a?(Hash) _b = binding() eval _arg.collect{|k,v| "#{k} = _arg[#{k.inspect}]; "}.join, _b elsif _arg.is_a?(Binding) _b = _arg elsif _arg.nil? _b = binding() else raise ArgumentError.new("#{self.class.name}#result(): argument should be Binding or Hash but passed #{_arg.class.name} object.") end return eval(@src, _b, (@filename || '(erubis')) end ## invoke context.instance_eval(@src) def evaluate(_context=Context.new) _context = Context.new(_context) if _context.is_a?(Hash) #return _context.instance_eval(@src, @filename || '(erubis)') #@_proc ||= eval("proc { #{@src} }", Erubis::EMPTY_BINDING, @filename || '(erubis)') @_proc ||= eval("proc { #{@src} }", binding(), @filename || '(erubis)') return _context.instance_eval(&@_proc) end ## if object is an Class or Module then define instance method to it, ## else define singleton method to it. def def_method(object, method_name, filename=nil) m = object.is_a?(Module) ? :module_eval : :instance_eval object.__send__(m, "def #{method_name}; #{@src}; end", filename || @filename || '(erubis)') end end end #--end of require 'erubis/evaluator' #--already included require 'erubis/context' module Erubis ## ## (abstract) abstract engine class. ## subclass must include evaluator and converter module. ## class Engine #include Evaluator #include Converter #include Generator def initialize(input=nil, properties={}) #@input = input init_generator(properties) init_converter(properties) init_evaluator(properties) @src = convert(input) if input end ## ## convert input string and set it to @src ## def convert!(input) @src = convert(input) end ## ## load file, write cache file, and return engine object. ## this method create code cache file automatically. ## cachefile name can be specified with properties[:cachename], ## or filname + 'cache' is used as default. ## def self.load_file(filename, properties={}) cachename = properties[:cachename] || (filename + '.cache') properties[:filename] = filename if test(?f, cachename) && File.mtime(filename) <= File.mtime(cachename) engine = self.new(nil, properties) engine.src = File.read(cachename) else input = File.open(filename, 'rb') {|f| f.read } engine = self.new(input, properties) File.open(cachename, 'wb') do |f| f.flock(File::LOCK_EX) f.write(engine.src) f.flush() end end engine.src.untaint # ok? return engine end ## ## helper method to convert and evaluate input text with context object. ## context may be Binding, Hash, or Object. ## def process(input, context=nil, filename=nil) code = convert(input) filename ||= '(erubis)' if context.is_a?(Binding) return eval(code, context, filename) else context = Context.new(context) if context.is_a?(Hash) return context.instance_eval(code, filename) end end ## ## helper method evaluate Proc object with contect object. ## context may be Binding, Hash, or Object. ## def process_proc(proc_obj, context=nil, filename=nil) if context.is_a?(Binding) filename ||= '(erubis)' return eval(proc_obj, context, filename) else context = Context.new(context) if context.is_a?(Hash) return context.instance_eval(&proc_obj) end end end # end of class Engine ## ## (abstract) base engine class for Eruby, Eperl, Ejava, and so on. ## subclass must include generator. ## class Basic::Engine < Engine include Evaluator include Basic::Converter include Generator end class PI::Engine < Engine include Evaluator include PI::Converter include Generator end end #--end of require 'erubis/engine' #require 'erubis/generator' #require 'erubis/converter' #require 'erubis/evaluator' #require 'erubis/error' #require 'erubis/context' #--begin of require 'erubis/helper' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## module Erubis ## ## helper for xml ## module XmlHelper module_function ESCAPE_TABLE = { '&' => '&', '<' => '<', '>' => '>', '"' => '"', "'" => ''', } def escape_xml(value) value.to_s.gsub(/[&<>"]/) { |s| ESCAPE_TABLE[s] } # or /[&<>"']/ #value.to_s.gsub(/[&<>"]/) { ESCAPE_TABLE[$&] } end def escape_xml2(value) return value.to_s.gsub(/\&/,'&').gsub(//,'>').gsub(/"/,'"') end alias h escape_xml alias html_escape escape_xml def url_encode(str) return str.gsub(/[^-_.a-zA-Z0-9]+/) { |s| s.unpack('C*').collect { |i| "%%%02X" % i }.join } end alias u url_encode end end #--end of require 'erubis/helper' #--begin of require 'erubis/enhancer' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## module Erubis ## ## switch '<%= ... %>' to escaped and '<%== ... %>' to unescaped ## ## ex. ## class XmlEruby < Eruby ## include EscapeEnhancer ## end ## ## this is language-indenedent. ## module EscapeEnhancer def self.desc # :nodoc: "switch '<%= %>' to escaped and '<%== %>' to unescaped" end #-- #def self.included(klass) # klass.class_eval <<-END # alias _add_expr_literal add_expr_literal # alias _add_expr_escaped add_expr_escaped # alias add_expr_literal _add_expr_escaped # alias add_expr_escaped _add_expr_literal # END #end #++ def add_expr(src, code, indicator) case indicator when '=' @escape ? add_expr_literal(src, code) : add_expr_escaped(src, code) when '==' @escape ? add_expr_escaped(src, code) : add_expr_literal(src, code) when '===' add_expr_debug(src, code) end end end #-- ## (obsolete) #module FastEnhancer #end #++ ## ## use $stdout instead of string ## ## this is only for Eruby. ## module StdoutEnhancer def self.desc # :nodoc: "use $stdout instead of array buffer or string buffer" end def add_preamble(src) src << "_buf = $stdout;" end def add_postamble(src) src << "\n''\n" end end ## ## use print statement instead of '_buf << ...' ## ## this is only for Eruby. ## module PrintOutEnhancer def self.desc # :nodoc: "use print statement instead of '_buf << ...'" end def add_preamble(src) end def add_text(src, text) src << " print '" << escape_text(text) << "';" unless text.empty? end def add_expr_literal(src, code) src << ' print((' << code << ').to_s);' end def add_expr_escaped(src, code) src << ' print ' << escaped_expr(code) << ';' end def add_postamble(src) src << "\n" unless src[-1] == ?\n end end ## ## enable print function ## ## Notice: use Eruby#evaluate() and don't use Eruby#result() ## to be enable print function. ## ## this is only for Eruby. ## module PrintEnabledEnhancer def self.desc # :nodoc: "enable to use print function in '<% %>'" end def add_preamble(src) src << "@_buf = " super end def print(*args) args.each do |arg| @_buf << arg.to_s end end def evaluate(context=nil) _src = @src if context.is_a?(Hash) context.each do |key, val| instance_variable_set("@#{key}", val) end elsif context context.instance_variables.each do |name| instance_variable_set(name, context.instance_variable_get(name)) end end return instance_eval(_src, (@filename || '(erubis)')) end end ## ## return array instead of string ## ## this is only for Eruby. ## module ArrayEnhancer def self.desc # :nodoc: "return array instead of string" end def add_preamble(src) src << "_buf = [];" end def add_postamble(src) src << "\n" unless src[-1] == ?\n src << "_buf\n" end end ## ## use an Array object as buffer (included in Eruby by default) ## ## this is only for Eruby. ## module ArrayBufferEnhancer def self.desc # :nodoc: "use an Array object for buffering (included in Eruby class)" end def add_preamble(src) src << "_buf = [];" end def add_postamble(src) src << "\n" unless src[-1] == ?\n src << "_buf.join\n" end end ## ## use String class for buffering ## ## this is only for Eruby. ## module StringBufferEnhancer def self.desc # :nodoc: "use a String object for buffering" end def add_preamble(src) src << "_buf = '';" end def add_postamble(src) src << "\n" unless src[-1] == ?\n src << "_buf.to_s\n" end end ## ## use StringIO class for buffering ## ## this is only for Eruby. ## module StringIOEnhancer # :nodoc: def self.desc # :nodoc: "use a StringIO object for buffering" end def add_preamble(src) src << "_buf = StringIO.new;" end def add_postamble(src) src << "\n" unless src[-1] == ?\n src << "_buf.string\n" end end ## ## set buffer variable name to '_erbout' as well as '_buf' ## ## this is only for Eruby. ## module ErboutEnhancer def self.desc # :nodoc: "set '_erbout = _buf = \"\";' to be compatible with ERB." end def add_preamble(src) src << "_erbout = _buf = '';" end def add_postamble(src) src << "\n" unless src[-1] == ?\n src << "_buf.to_s\n" end end ## ## remove text and leave code, especially useful when debugging. ## ## ex. ## $ erubis -s -E NoText file.eruby | more ## ## this is language independent. ## module NoTextEnhancer def self.desc # :nodoc: "remove text and leave code (useful when debugging)" end def add_text(src, text) src << ("\n" * text.count("\n")) if text[-1] != ?\n text =~ /^(.*?)\z/ src << (' ' * $1.length) end end end ## ## remove code and leave text, especially useful when validating HTML tags. ## ## ex. ## $ erubis -s -E NoCode file.eruby | tidy -errors ## ## this is language independent. ## module NoCodeEnhancer def self.desc # :nodoc: "remove code and leave text (useful when validating HTML)" end def add_preamble(src) end def add_postamble(src) end def add_text(src, text) src << text end def add_expr(src, code, indicator) src << "\n" * code.count("\n") end def add_stmt(src, code) src << "\n" * code.count("\n") end end ## ## get convert faster, but spaces around '<%...%>' are not trimmed. ## ## this is language-independent. ## module SimplifyEnhancer def self.desc # :nodoc: "get convert faster but leave spaces around '<% %>'" end #DEFAULT_REGEXP = /(^[ \t]*)?<%(=+|\#)?(.*?)-?%>([ \t]*\r?\n)?/m SIMPLE_REGEXP = /<%(=+|\#)?(.*?)-?%>/m def convert(input) src = "" add_preamble(src) #regexp = pattern_regexp(@pattern) pos = 0 input.scan(SIMPLE_REGEXP) do |indicator, code| match = Regexp.last_match index = match.begin(0) text = input[pos, index - pos] pos = match.end(0) add_text(src, text) if !indicator # <% %> add_stmt(src, code) elsif indicator[0] == ?\# # <%# %> n = code.count("\n") add_stmt(src, "\n" * n) else # <%= %> add_expr(src, code, indicator) end end #rest = $' || input # ruby1.8 rest = pos == 0 ? input : input[pos..-1] # ruby1.9 add_text(src, rest) add_postamble(src) return src end end ## ## enable to use other embedded expression pattern (default is '\[= =\]'). ## ## notice! this is an experimental. spec may change in the future. ## ## ex. ## input = < ## <%= item %> : <%== item %> ## [= item =] : [== item =] ## <% end %> ## END ## ## class BiPatternEruby ## include BiPatternEnhancer ## end ## eruby = BiPatternEruby.new(input, :bipattern=>'\[= =\]') ## list = ['', 'b&b', '"c"'] ## print eruby.result(binding()) ## ## ## output ## : <a> ## : <a> ## b&b : b&b ## b&b : b&b ## "c" : "c" ## "c" : "c" ## ## this is language independent. ## module BiPatternEnhancer def self.desc # :nodoc: "another embedded expression pattern (default '\[= =\]')." end def initialize(input, properties={}) self.bipattern = properties[:bipattern] # or '\$\{ \}' super end ## when pat is nil then '\[= =\]' is used def bipattern=(pat) # :nodoc: @bipattern = pat || '\[= =\]' pre, post = @bipattern.split() @bipattern_regexp = /(.*?)#{pre}(=*)(.*?)#{post}/m end def add_text(src, text) return unless text m = nil text.scan(@bipattern_regexp) do |txt, indicator, code| m = Regexp.last_match super(src, txt) add_expr(src, code, '=' + indicator) end #rest = $' || text # ruby1.8 rest = m ? text[m.end(0)..-1] : text # ruby1.9 super(src, rest) end end ## ## regards lines starting with '%' as program code ## ## this is for compatibility to eruby and ERB. ## ## this is language-independent. ## module PercentLineEnhancer def self.desc # :nodoc: "regard lines starting with '%' as program code" end def add_text(src, text) pos = 0 text2 = '' text.scan(/^\%(.*?\r?\n)/) do line = $1 match = Regexp.last_match len = match.begin(0) - pos str = text[pos, len] pos = match.end(0) if text2.empty? text2 = str else text2 << str end if line[0] == ?% text2 << line else super(src, text2) text2 = '' add_stmt(src, line) end end #rest = pos == 0 ? text : $' # ruby1.8 rest = pos == 0 ? text : text[pos..-1] # ruby1.9 unless text2.empty? text2 << rest if rest rest = text2 end super(src, rest) end end ## ## [experimental] allow header and footer in eRuby script ## ## ex. ## ==================== ## ## without header and footer ## $ cat ex1.eruby ## <% def list_items(list) %> ## <% for item in list %> ##
  • <%= item %>
  • ## <% end %> ## <% end %> ## ## $ erubis -s ex1.eruby ## _buf = []; def list_items(list) ## ; for item in list ## ; _buf << '
  • '; _buf << ( item ).to_s; _buf << '
  • ## '; end ## ; end ## ; ## _buf.join ## ## ## with header and footer ## $ cat ex2.eruby ## ## <% for item in list %> ##
  • <%= item %>
  • ## <% end %> ## ## ## $ erubis -s -c HeaderFooterEruby ex4.eruby ## ## def list_items(list) ## _buf = []; _buf << ' ## '; for item in list ## ; _buf << '
  • '; _buf << ( item ).to_s; _buf << '
  • ## '; end ## ; _buf << ' ## '; ## _buf.join ## end ## ## ==================== ## ## this is language-independent. ## module HeaderFooterEnhancer def self.desc # :nodoc: "allow header/footer in document (ex. '')" end HEADER_FOOTER_PATTERN = /(.*?)(^[ \t]*)?([ \t]*\r?\n)?/m def add_text(src, text) m = nil text.scan(HEADER_FOOTER_PATTERN) do |txt, lspace, word, content, rspace| m = Regexp.last_match flag_trim = @trim && lspace && rspace super(src, txt) content = "#{lspace}#{content}#{rspace}" if flag_trim super(src, lspace) if !flag_trim && lspace instance_variable_set("@#{word}", content) super(src, rspace) if !flag_trim && rspace end #rest = $' || text # ruby1.8 rest = m ? text[m.end(0)..-1] : text # ruby1.9 super(src, rest) end attr_accessor :header, :footer def convert(input) source = super return @src = "#{@header}#{source}#{@footer}" end end ## ## delete indentation of HTML. ## ## this is language-independent. ## module DeleteIndentEnhancer def self.desc # :nodoc: "delete indentation of HTML." end def convert_input(src, input) input = input.gsub(/^[ \t]+<%=title%>" into "_buf << %Q`

    #{title}

    `" ## ## this is only for Eruby. ## module InterpolationEnhancer def self.desc # :nodoc: "convert '

    <%=text%>

    ' into '_buf << %Q`

    \#{text}

    `'" end def convert_input(src, input) pat = @pattern regexp = pat.nil? || pat == '<% %>' ? Basic::Converter::DEFAULT_REGEXP : pattern_regexp(pat) pos = 0 is_bol = true # is beginning of line str = '' input.scan(regexp) do |indicator, code, tailch, rspace| match = Regexp.last_match() len = match.begin(0) - pos text = input[pos, len] pos = match.end(0) ch = indicator ? indicator[0] : nil lspace = ch == ?= ? nil : detect_spaces_at_bol(text, is_bol) is_bol = rspace ? true : false _add_text_to_str(str, text) ## * when '<%= %>', do nothing ## * when '<% %>' or '<%# %>', delete spaces iff only spaces are around '<% %>' if ch == ?= # <%= %> rspace = nil if tailch && !tailch.empty? str << lspace if lspace add_expr(str, code, indicator) str << rspace if rspace elsif ch == ?\# # <%# %> n = code.count("\n") + (rspace ? 1 : 0) if @trim && lspace && rspace add_text(src, str) str = '' add_stmt(src, "\n" * n) else str << lspace if lspace add_text(src, str) str = '' add_stmt(src, "\n" * n) str << rspace if rspace end else # <% %> if @trim && lspace && rspace add_text(src, str) str = '' add_stmt(src, "#{lspace}#{code}#{rspace}") else str << lspace if lspace add_text(src, str) str = '' add_stmt(src, code) str << rspace if rspace end end end #rest = $' || input # ruby1.8 rest = pos == 0 ? input : input[pos..-1] # ruby1.9 _add_text_to_str(str, rest) add_text(src, str) end def add_text(src, text) return if !text || text.empty? #src << " _buf << %Q`" << text << "`;" if text[-1] == ?\n text[-1] = "\\n" src << " _buf << %Q`" << text << "`\n" else src << " _buf << %Q`" << text << "`;" end end def _add_text_to_str(str, text) return if !text || text.empty? text.gsub!(/['\#\\]/, '\\\\\&') str << text end def add_expr_escaped(str, code) str << "\#{#{escaped_expr(code)}}" end def add_expr_literal(str, code) str << "\#{#{code}}" end end end #--end of require 'erubis/enhancer' #require 'erubis/tiny' #--begin of require 'erubis/engine/eruby' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## #--already included require 'erubis/engine' #--already included require 'erubis/enhancer' module Erubis ## ## code generator for Ruby ## module RubyGenerator include Generator #include ArrayBufferEnhancer include StringBufferEnhancer def init_generator(properties={}) super @escapefunc ||= "Erubis::XmlHelper.escape_xml" end def self.supported_properties() # :nodoc: return [] end def escape_text(text) text.gsub(/['\\]/, '\\\\\&') # "'" => "\\'", '\\' => '\\\\' end def escaped_expr(code) return "#{@escapefunc}(#{code})" end #-- #def add_preamble(src) # src << "_buf = [];" #end #++ def add_text(src, text) src << " _buf << '" << escape_text(text) << "';" unless text.empty? end def add_stmt(src, code) #src << code << ';' src << code src << ';' unless code[-1] == ?\n end def add_expr_literal(src, code) src << ' _buf << (' << code << ').to_s;' end def add_expr_escaped(src, code) src << ' _buf << ' << escaped_expr(code) << ';' end def add_expr_debug(src, code) code.strip! s = (code.dump =~ /\A"(.*)"\z/) && $1 src << ' $stderr.puts("*** debug: ' << s << '=#{(' << code << ').inspect}");' end #-- #def add_postamble(src) # src << "\n_buf.join\n" #end #++ end ## ## engine for Ruby ## class Eruby < Basic::Engine include RubyEvaluator include RubyGenerator end ## ## fast engine for Ruby ## class FastEruby < Eruby include InterpolationEnhancer end ## ## swtich '<%= %>' to escaped and '<%== %>' to not escaped ## class EscapedEruby < Eruby include EscapeEnhancer end ## ## sanitize expression (<%= ... %>) by default ## ## this is equivalent to EscapedEruby and is prepared only for compatibility. ## class XmlEruby < Eruby include EscapeEnhancer end class PI::Eruby < PI::Engine include RubyEvaluator include RubyGenerator def init_converter(properties={}) @pi = 'rb' super(properties) end end end #--end of require 'erubis/engine/eruby' #require 'erubis/engine/enhanced' # enhanced eruby engines #require 'erubis/engine/optimized' # generates optimized ruby code #require 'erubis/engine/ephp' #require 'erubis/engine/ec' #require 'erubis/engine/ejava' #require 'erubis/engine/escheme' #require 'erubis/engine/eperl' #require 'erubis/engine/ejavascript' #--begin of require 'erubis/local-setting' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## ## ## you can add site-local settings here. ## this files is required by erubis.rb ## #--end of require 'erubis/local-setting' #--end of require 'erubis' #--begin of require 'erubis/tiny' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## module Erubis ## ## tiny and the simplest implementation of eRuby ## ## ex. ## eruby = TinyEruby.new(File.read('example.rhtml')) ## print eruby.src # print ruby code ## print eruby.result(binding()) # eval ruby code with Binding object ## print eruby.evalute(context) # eval ruby code with context object ## class TinyEruby def initialize(input=nil) @src = convert(input) if input end attr_reader :src EMBEDDED_PATTERN = /<%(=+|\#)?(.*?)-?%>/m def convert(input) src = "_buf = '';" # preamble pos = 0 input.scan(EMBEDDED_PATTERN) do |indicator, code| m = Regexp.last_match text = input[pos...m.begin(0)] pos = m.end(0) #src << " _buf << '" << escape_text(text) << "';" text.gsub!(/['\\]/, '\\\\\&') src << " _buf << '" << text << "';" unless text.empty? if !indicator # <% %> src << code << ";" elsif indicator == '#' # <%# %> src << ("\n" * code.count("\n")) else # <%= %> src << " _buf << (" << code << ").to_s;" end end #rest = $' || input # ruby1.8 rest = pos == 0 ? input : input[pos..-1] # ruby1.9 #src << " _buf << '" << escape_text(rest) << "';" rest.gsub!(/['\\]/, '\\\\\&') src << " _buf << '" << rest << "';" unless rest.empty? src << "\n_buf.to_s\n" # postamble return src end #def escape_text(text) # return text.gsub!(/['\\]/, '\\\\\&') || text #end def result(_binding=TOPLEVEL_BINDING) eval @src, _binding end def evaluate(_context=Object.new) if _context.is_a?(Hash) _obj = Object.new _context.each do |k, v| _obj.instance_variable_set("@#{k}", v) end _context = _obj end _context.instance_eval @src end end module PI end class PI::TinyEruby def initialize(input=nil, options={}) @escape = options[:escape] || 'Erubis::XmlHelper.escape_xml' @src = convert(input) if input end attr_reader :src EMBEDDED_PATTERN = /(^[ \t]*)?<\?rb(\s.*?)\?>([ \t]*\r?\n)?|@(!+)?\{(.*?)\}@/m def convert(input) src = "_buf = '';" # preamble pos = 0 input.scan(EMBEDDED_PATTERN) do |lspace, stmt, rspace, indicator, expr| match = Regexp.last_match len = match.begin(0) - pos text = input[pos, len] pos = match.end(0) #src << " _buf << '" << escape_text(text) << "';" text.gsub!(/['\\]/, '\\\\\&') src << " _buf << '" << text << "';" unless text.empty? if stmt # if lspace && rspace src << "#{lspace}#{stmt}#{rspace}" else src << " _buf << '" << lspace << "';" if lspace src << stmt << ";" src << " _buf << '" << rspace << "';" if rspace end else # ${...}, $!{...} if !indicator src << " _buf << " << @escape << "(" << expr << ");" elsif indicator == '!' src << " _buf << (" << expr << ").to_s;" end end end #rest = $' || input # ruby1.8 rest = pos == 0 ? input : input[pos..-1] # ruby1.9 #src << " _buf << '" << escape_text(rest) << "';" rest.gsub!(/['\\]/, '\\\\\&') src << " _buf << '" << rest << "';" unless rest.empty? src << "\n_buf.to_s\n" # postamble return src end #def escape_text(text) # return text.gsub!(/['\\]/, '\\\\\&') || text #end def result(_binding=TOPLEVEL_BINDING) eval @src, _binding end def evaluate(_context=Object.new) if _context.is_a?(Hash) _obj = Object.new _context.each do |k, v| _obj.instance_variable_set("@#{k}", v) end _context = _obj end _context.instance_eval @src end end end #--end of require 'erubis/tiny' #--begin of require 'erubis/engine/enhanced' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## #--already included require 'erubis/enhancer' #--already included require 'erubis/engine/eruby' module Erubis #-- ## moved to engine/ruby.rb #class EscapedEruby < Eruby # include EscapeEnhancer #end #++ #-- ### (obsolete) #class FastEruby < Eruby # include FastEnhancer #end #++ class StdoutEruby < Eruby include StdoutEnhancer end class PrintOutEruby < Eruby include PrintOutEnhancer end class PrintEnabledEruby < Eruby include PrintEnabledEnhancer end class ArrayEruby < Eruby include ArrayEnhancer end class ArrayBufferEruby < Eruby include ArrayBufferEnhancer end class StringBufferEruby < Eruby include StringBufferEnhancer end class StringIOEruby < Eruby include StringIOEnhancer end class ErboutEruby < Eruby include ErboutEnhancer end class NoTextEruby < Eruby include NoTextEnhancer end class NoCodeEruby < Eruby include NoCodeEnhancer end class SimplifiedEruby < Eruby include SimplifyEnhancer end class StdoutSimplifiedEruby < Eruby include StdoutEnhancer include SimplifyEnhancer end class PrintOutSimplifiedEruby < Eruby include PrintOutEnhancer include SimplifyEnhancer end class BiPatternEruby < Eruby include BiPatternEnhancer end class PercentLineEruby < Eruby include PercentLineEnhancer end class HeaderFooterEruby < Eruby include HeaderFooterEnhancer end class DeleteIndentEruby < Eruby include DeleteIndentEnhancer end class InterpolationEruby < Eruby include InterpolationEnhancer end end #--end of require 'erubis/engine/enhanced' #--begin of require 'erubis/engine/optimized' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## #--already included require 'erubis/engine/eruby' module Erubis module OptimizedGenerator include Generator def self.supported_properties() # :nodoc: return [] end def init_generator(properties={}) super @escapefunc ||= "Erubis::XmlHelper.escape_xml" @initialized = false @prev_is_expr = false end protected def escape_text(text) text.gsub(/['\\]/, '\\\\\&') # "'" => "\\'", '\\' => '\\\\' end def escaped_expr(code) @escapefunc ||= 'Erubis::XmlHelper.escape_xml' return "#{@escapefunc}(#{code})" end def switch_to_expr(src) return if @prev_is_expr @prev_is_expr = true src << ' _buf' end def switch_to_stmt(src) return unless @prev_is_expr @prev_is_expr = false src << ';' end def add_preamble(src) #@initialized = false #@prev_is_expr = false end def add_text(src, text) return if text.empty? if @initialized switch_to_expr(src) src << " << '" << escape_text(text) << "'" else src << "_buf = '" << escape_text(text) << "';" @initialized = true end end def add_stmt(src, code) switch_to_stmt(src) if @initialized #super src << code src << ';' unless code[-1] == ?\n end def add_expr_literal(src, code) unless @initialized; src << "_buf = ''"; @initialized = true; end switch_to_expr(src) src << " << (" << code << ").to_s" end def add_expr_escaped(src, code) unless @initialized; src << "_buf = ''"; @initialized = true; end switch_to_expr(src) src << " << " << escaped_expr(code) end def add_expr_debug(src, code) code.strip! s = (code.dump =~ /\A"(.*)"\z/) && $1 src << ' $stderr.puts("*** debug: ' << s << '=#{(' << code << ').inspect}");' end def add_postamble(src) #super if @initialized src << "\n_buf\n" if @initialized end end # end of class OptimizedEruby ## ## Eruby class which generates optimized ruby code ## class OptimizedEruby < Basic::Engine # Eruby include RubyEvaluator include OptimizedGenerator def init_converter(properties={}) @pi = 'rb' super(properties) end end ## ## XmlEruby class which generates optimized ruby code ## class OptimizedXmlEruby < OptimizedEruby include EscapeEnhancer def add_expr_debug(src, code) switch_to_stmt(src) if indicator == '===' && !@initialized super end end # end of class OptimizedXmlEruby end #--end of require 'erubis/engine/optimized' #--already included require 'erubis/engine/eruby' #--begin of require 'erubis/engine/ephp' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## #--already included require 'erubis/engine' #--already included require 'erubis/enhancer' module Erubis module PhpGenerator include Generator def self.supported_properties() # :nodoc: return [] end def init_generator(properties={}) super @escapefunc ||= 'htmlspecialchars' end def add_preamble(src) # empty end def escape_text(text) return text.gsub!(/<\?xml\b/, '<?xml') || text end def add_text(src, text) src << escape_text(text) end def add_expr_literal(src, code) code.strip! src << "" end def add_expr_escaped(src, code) add_expr_literal(src, escaped_expr(code)) end def add_expr_debug(src, code) code.strip! s = code.gsub(/\'/, "\\'") src << "" end def add_stmt(src, code) src << "\n" else src << code << "?>" end end def add_postamble(src) # empty end end ## ## engine for PHP ## class Ephp < Basic::Engine include PhpGenerator end class EscapedEphp < Ephp include EscapeEnhancer end #class XmlEphp < Ephp # include EscapeEnhancer #end class PI::Ephp < PI::Engine include PhpGenerator def init_converter(properties={}) @pi = 'php' super(properties) end end end #--end of require 'erubis/engine/ephp' #--begin of require 'erubis/engine/ec' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## #--already included require 'erubis/engine' #--already included require 'erubis/enhancer' module Erubis module CGenerator include Generator def self.supported_properties() # :nodoc: return [ [:indent, '', "indent spaces (ex. ' ')"], [:out, 'stdout', "output file pointer name"], ] end def init_generator(properties={}) super @escapefunc ||= "escape" @indent = properties[:indent] || '' @out = properties[:out] || 'stdout' end def add_preamble(src) src << "#line 1 \"#{self.filename}\"\n" if self.filename end def escape_text(text) @@table_ ||= { "\r"=>"\\r", "\n"=>"\\n", "\t"=>"\\t", '"'=>'\\"', "\\"=>"\\\\" } text.gsub!(/[\r\n\t"\\]/) { |m| @@table_[m] } return text end def escaped_expr(code) return "#{@escapefunc}(#{code.strip}, #{@out})" end def add_text(src, text) return if text.empty? src << (src.empty? || src[-1] == ?\n ? @indent : ' ') src << "fputs(" i = 0 text.each_line do |line| src << "\n" << @indent << ' ' if i > 0 i += 1 src << '"' << escape_text(line) << '"' end src << ", #{@out});" #<< (text[-1] == ?\n ? "\n" : "") src << "\n" if text[-1] == ?\n end def add_stmt(src, code) src << code end def add_expr_literal(src, code) src << @indent if src.empty? || src[-1] == ?\n src << " fprintf(#{@out}, " << code.strip << ');' end def add_expr_escaped(src, code) src << @indent if src.empty? || src[-1] == ?\n src << ' ' << escaped_expr(code) << ';' end def add_expr_debug(src, code) code.strip! s = nil if code =~ /\A\".*?\"\s*,\s*(.*)/ s = $1.gsub(/[%"]/, '\\\1') + '=' end src << @indent if src.empty? || src[-1] == ?\n src << " fprintf(stderr, \"*** debug: #{s}\" #{code});" end def add_postamble(src) # empty end end ## ## engine for C ## class Ec < Basic::Engine include CGenerator end class EscapedEc < Ec include EscapeEnhancer end #class XmlEc < Ec # include EscapeEnhancer #end class PI::Ec < PI::Engine include CGenerator def init_converter(properties={}) @pi = 'c' super(properties) end end end #--end of require 'erubis/engine/ec' #--begin of require 'erubis/engine/ejava' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## #--already included require 'erubis/engine' #--already included require 'erubis/enhancer' module Erubis module JavaGenerator include Generator def self.supported_properties() # :nodoc: return [ [:indent, '', "indent spaces (ex. ' ')"], [:buf, '_buf', "output buffer name"], [:bufclass, 'StringBuffer', "output buffer class (ex. 'StringBuilder')"], ] end def init_generator(properties={}) super @escapefunc ||= 'escape' @indent = properties[:indent] || '' @buf = properties[:buf] || '_buf' @bufclass = properties[:bufclass] || 'StringBuffer' end def add_preamble(src) src << "#{@indent}#{@bufclass} #{@buf} = new #{@bufclass}();" end def escape_text(text) @@table_ ||= { "\r"=>"\\r", "\n"=>"\\n", "\t"=>"\\t", '"'=>'\\"', "\\"=>"\\\\" } return text.gsub!(/[\r\n\t"\\]/) { |m| @@table_[m] } || text end def add_text(src, text) return if text.empty? src << (src.empty? || src[-1] == ?\n ? @indent : ' ') src << @buf << ".append(" i = 0 text.each_line do |line| src << "\n" << @indent << ' + ' if i > 0 i += 1 src << '"' << escape_text(line) << '"' end src << ");" << (text[-1] == ?\n ? "\n" : "") end def add_stmt(src, code) src << code end def add_expr_literal(src, code) src << @indent if src.empty? || src[-1] == ?\n code.strip! src << " #{@buf}.append(#{code});" end def add_expr_escaped(src, code) add_expr_literal(src, escaped_expr(code)) end def add_expr_debug(src, code) code.strip! src << @indent if src.empty? || src[-1] == ?\n src << " System.err.println(\"*** debug: #{code}=\"+(#{code}));" end def add_postamble(src) src << "\n" if src[-1] == ?; src << @indent << "return " << @buf << ".toString();\n" #src << @indent << "System.out.print(" << @buf << ".toString());\n" end end ## ## engine for Java ## class Ejava < Basic::Engine include JavaGenerator end class EscapedEjava < Ejava include EscapeEnhancer end #class XmlEjava < Ejava # include EscapeEnhancer #end class PI::Ejava < PI::Engine include JavaGenerator def init_converter(properties={}) @pi = 'java' super(properties) end end end #--end of require 'erubis/engine/ejava' #--begin of require 'erubis/engine/escheme' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## #--already included require 'erubis/engine' #--already included require 'erubis/enhancer' module Erubis module SchemeGenerator include Generator def self.supported_properties() # :nodoc: return [ [:func, '_add', "function name (ex. 'display')"], ] end def init_generator(properties={}) super @escapefunc ||= 'escape' @func = properties[:func] || '_add' # or 'display' end def add_preamble(src) return unless @func == '_add' src << "(let ((_buf '())) " + \ "(define (_add x) (set! _buf (cons x _buf))) " #src << "(let* ((_buf '())" + \ # " (_add (lambda (x) (set! _buf (cons x _buf))))) " end def escape_text(text) @table_ ||= { '"'=>'\\"', '\\'=>'\\\\' } text.gsub!(/["\\]/) { |m| @table_[m] } return text end def escaped_expr(code) code.strip! return "(#{@escapefunc} #{code})" end def add_text(src, text) return if text.empty? t = escape_text(text) if t[-1] == ?\n t[-1, 1] = '' src << "(#{@func} \"" << t << "\\n\")\n" else src << "(#{@func} \"" << t << '")' end end def add_stmt(src, code) src << code end def add_expr_literal(src, code) code.strip! src << "(#{@func} #{code})" end def add_expr_escaped(src, code) add_expr_literal(src, escaped_expr(code)) end def add_expr_debug(src, code) s = (code.strip! || code).gsub(/\"/, '\\"') src << "(display \"*** debug: #{s}=\")(display #{code.strip})(display \"\\n\")" end def add_postamble(src) return unless @func == '_add' src << "\n" unless src[-1] == ?\n src << " (reverse _buf))\n" end end ## ## engine for Scheme ## class Escheme < Basic::Engine include SchemeGenerator end class EscapedEscheme < Escheme include EscapeEnhancer end #class XmlEscheme < Escheme # include EscapeEnhancer #end class PI::Escheme < PI::Engine include SchemeGenerator def init_converter(properties={}) @pi = 'scheme' super(properties) end end end #--end of require 'erubis/engine/escheme' #--begin of require 'erubis/engine/eperl' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## #--already included require 'erubis/engine' #--already included require 'erubis/enhancer' module Erubis module PerlGenerator include Generator def self.supported_properties() # :nodoc: return [ [:func, 'print', "function name"], ] end def init_generator(properties={}) super @escapefunc ||= 'encode_entities' @func = properties[:func] || 'print' end def add_preamble(src) src << "use HTML::Entities; "; end def escape_text(text) return text.gsub!(/['\\]/, '\\\\\&') || text end def add_text(src, text) src << @func << "('" << escape_text(text) << "'); " unless text.empty? end def add_expr_literal(src, code) code.strip! src << @func << "(" << code << "); " end def add_expr_escaped(src, code) add_expr_literal(src, escaped_expr(code)) end def add_expr_debug(src, code) code.strip! s = code.gsub(/\'/, "\\'") src << @func << "('*** debug: #{code}=', #{code}, \"\\n\");" end def add_stmt(src, code) src << code end def add_postamble(src) src << "\n" unless src[-1] == ?\n end end ## ## engine for Perl ## class Eperl < Basic::Engine include PerlGenerator end class EscapedEperl < Eperl include EscapeEnhancer end #class XmlEperl < Eperl # include EscapeEnhancer #end class PI::Eperl < PI::Engine include PerlGenerator def init_converter(properties={}) @pi = 'perl' super(properties) end end end #--end of require 'erubis/engine/eperl' #--begin of require 'erubis/engine/ejavascript' ## ## $Release: 2.6.5 $ ## copyright(c) 2006-2009 kuwata-lab.com all rights reserved. ## #--already included require 'erubis/engine' #--already included require 'erubis/enhancer' module Erubis module JavascriptGenerator include Generator def self.supported_properties() # :nodoc: list = [] #list << [:indent, '', "indent spaces (ex. ' ')"] #list << [:buf, '_buf', "output buffer name"] list << [:docwrite, true, "use 'document.write()' when true"] return list end def init_generator(properties={}) super @escapefunc ||= 'escape' @indent = properties[:indent] || '' @buf = properties[:out] || '_buf' @docwrite = properties[:docwrite] != false # '!= false' will be removed in the next release end def add_preamble(src) src << "#{@indent}var #{@buf} = [];" end def escape_text(text) @@table_ ||= { "\r"=>"\\r", "\n"=>"\\n\\\n", "\t"=>"\\t", '"'=>'\\"', "\\"=>"\\\\" } return text.gsub!(/[\r\n\t"\\]/) { |m| @@table_[m] } || text end def add_indent(src, indent) src << (src.empty? || src[-1] == ?\n ? indent : ' ') end def add_text(src, text) return if text.empty? add_indent(src, @indent) src << @buf << '.push("' s = escape_text(text) if s[-1] == ?\n s[-2, 2] = '' src << s << "\");\n" else src << s << "\");" end end def add_stmt(src, code) src << code end def add_expr_literal(src, code) add_indent(src, @indent) code.strip! src << "#{@buf}.push(#{code});" end def add_expr_escaped(src, code) add_expr_literal(src, escaped_expr(code)) end def add_expr_debug(src, code) add_indent(src, @indent) code.strip! src << "alert(\"*** debug: #{code}=\"+(#{code}));" end def add_postamble(src) src << "\n" if src[-1] == ?; if @docwrite src << @indent << 'document.write(' << @buf << ".join(\"\"));\n" else src << @indent << @buf << ".join(\"\");\n" end end end ## ## engine for JavaScript ## class Ejavascript < Basic::Engine include JavascriptGenerator end class EscapedEjavascript < Ejavascript include EscapeEnhancer end #class XmlEjavascript < Ejavascript # include EscapeEnhancer #end class PI::Ejavascript < PI::Engine include JavascriptGenerator def init_converter(properties={}) @pi = 'js' super(properties) end end end #--end of require 'erubis/engine/ejavascript' module Erubis Ejs = Ejavascript EscapedEjs = EscapedEjavascript class CommandOptionError < ErubisError end ## ## main class of command ## ## ex. ## Main.main(ARGV) ## class Main def self.main(argv=ARGV) status = 0 begin Main.new.execute(ARGV) rescue CommandOptionError => ex $stderr.puts ex.message status = 1 end exit(status) end def initialize @single_options = "hvxztTSbeBXNUC" @arg_options = "pcrfKIlaE" #C @option_names = { 'h' => :help, 'v' => :version, 'x' => :source, 'z' => :syntax, 'T' => :unexpand, 't' => :untabify, # obsolete 'S' => :intern, 'b' => :bodyonly, 'B' => :binding, 'p' => :pattern, 'c' => :context, #'C' => :class, 'e' => :escape, 'r' => :requires, 'f' => :datafiles, 'K' => :kanji, 'I' => :includes, 'l' => :lang, 'a' => :action, 'E' => :enhancers, 'X' => :notext, 'N' => :linenum, 'U' => :unique, 'C' => :compact, } assert unless @single_options.length + @arg_options.length == @option_names.length (@single_options + @arg_options).each_byte do |ch| assert unless @option_names.key?(ch.chr) end end def execute(argv=ARGV) ## parse command-line options options, properties = parse_argv(argv, @single_options, @arg_options) filenames = argv options['h'] = true if properties[:help] opts = Object.new arr = @option_names.collect {|ch, name| "def #{name}; @#{name}; end\n" } opts.instance_eval arr.join options.each do |ch, val| name = @option_names[ch] opts.instance_variable_set("@#{name}", val) end ## help, version, enhancer list if opts.help || opts.version puts version() if opts.version puts usage() if opts.help puts show_properties() if opts.help puts show_enhancers() if opts.help return end ## include path opts.includes.split(/,/).each do |path| $: << path end if opts.includes ## require library opts.requires.split(/,/).each do |library| require library end if opts.requires ## action action = opts.action action ||= 'syntax' if opts.syntax action ||= 'convert' if opts.source || opts.notext ## lang lang = opts.lang || 'ruby' action ||= 'convert' if opts.lang ## class name of Eruby #classname = opts.class classname = nil klass = get_classobj(classname, lang, properties[:pi]) ## kanji code $KCODE = opts.kanji if opts.kanji ## read context values from yaml file datafiles = opts.datafiles context = load_datafiles(datafiles, opts) ## parse context data if opts.context context = parse_context_data(opts.context, opts) end ## properties for engine properties[:escape] = true if opts.escape && !properties.key?(:escape) properties[:pattern] = opts.pattern if opts.pattern #properties[:trim] = false if opts.notrim properties[:preamble] = properties[:postamble] = false if opts.bodyonly properties[:pi] = nil if properties[:pi] == true ## create engine and extend enhancers engine = klass.new(nil, properties) enhancers = get_enhancers(opts.enhancers) #enhancers.push(Erubis::EscapeEnhancer) if opts.escape enhancers.each do |enhancer| engine.extend(enhancer) engine.bipattern = properties[:bipattern] if enhancer == Erubis::BiPatternEnhancer end ## no-text engine.extend(Erubis::NoTextEnhancer) if opts.notext ## convert and execute val = nil msg = "Syntax OK\n" if filenames && !filenames.empty? filenames.each do |filename| File.file?(filename) or raise CommandOptionError.new("#{filename}: file not found.") engine.filename = filename engine.convert!(File.read(filename)) val = do_action(action, engine, context, filename, opts) msg = nil if val end else engine.filename = filename = '(stdin)' engine.convert!($stdin.read()) val = do_action(action, engine, context, filename, opts) msg = nil if val end print msg if action == 'syntax' && msg end private def do_action(action, engine, context, filename, opts) case action when 'convert' s = manipulate_src(engine.src, opts) when nil, 'exec', 'execute' s = opts.binding ? engine.result(context.to_hash) : engine.evaluate(context) when 'syntax' s = check_syntax(filename, engine.src) else raise "*** internal error" end print s if s return s end def manipulate_src(source, opts) flag_linenum = opts.linenum flag_unique = opts.unique flag_compact = opts.compact if flag_linenum n = 0 source.gsub!(/^/) { n += 1; "%5d: " % n } source.gsub!(/^ *\d+:\s+?\n/, '') if flag_compact source.gsub!(/(^ *\d+:\s+?\n)+/, "\n") if flag_unique else source.gsub!(/^\s*?\n/, '') if flag_compact source.gsub!(/(^\s*?\n)+/, "\n") if flag_unique end return source end def usage(command=nil) command ||= File.basename($0) buf = [] buf << "erubis - embedded program converter for multi-language" buf << "Usage: #{command} [..options..] [file ...]" buf << " -h, --help : help" buf << " -v : version" buf << " -x : show converted code" buf << " -X : show converted code, only ruby code and no text part" buf << " -N : numbering: add line numbers (for '-x/-X')" buf << " -U : unique: compress empty lines to a line (for '-x/-X')" buf << " -C : compact: remove empty lines (for '-x/-X')" buf << " -b : body only: no preamble nor postamble (for '-x/-X')" buf << " -z : syntax checking" buf << " -e : escape (equal to '--E Escape')" buf << " -p pattern : embedded pattern (default '<% %>')" buf << " -l lang : convert but no execute (ruby/php/c/java/scheme/perl/js)" buf << " -E e1,e2,... : enhancer names (Escape, PercentLine, BiPattern, ...)" buf << " -I path : library include path" buf << " -K kanji : kanji code (euc/sjis/utf8) (default none)" buf << " -c context : context data string (yaml inline style or ruby code)" buf << " -f datafile : context data file ('*.yaml', '*.yml', or '*.rb')" #buf << " -t : expand tab characters in YAML file" buf << " -T : don't expand tab characters in YAML file" buf << " -S : convert mapping key from string to symbol in YAML file" buf << " -B : invoke 'result(binding)' instead of 'evaluate(context)'" buf << " --pi=name : parse '' instead of '<% ... %>'" #' # -T : don't trim spaces around '<% %>' # -c class : class name (XmlEruby/PercentLineEruby/...) (default Eruby) # -r library : require library # -a : action (convert/execute) return buf.join("\n") end def collect_supported_properties(erubis_klass) list = [] erubis_klass.ancestors.each do |klass| if klass.respond_to?(:supported_properties) list.concat(klass.supported_properties) end end return list end def show_properties s = "supported properties:\n" basic_props = collect_supported_properties(Erubis::Basic::Engine) pi_props = collect_supported_properties(Erubis::PI::Engine) list = [] common_props = basic_props & pi_props list << ['(common)', common_props] list << ['(basic)', basic_props - common_props] list << ['(pi)', pi_props - common_props] %w[ruby php c java scheme perl javascript].each do |lang| klass = Erubis.const_get("E#{lang}") list << [lang, collect_supported_properties(klass) - basic_props] end list.each do |lang, props| s << " * #{lang}\n" props.each do |name, default_val, desc| s << (" --%-23s : %s\n" % ["#{name}=#{default_val.inspect}", desc]) end end s << "\n" return s end def show_enhancers dict = {} ObjectSpace.each_object(Module) do |mod| dict[$1] = mod if mod.name =~ /\AErubis::(.*)Enhancer\z/ end s = "enhancers:\n" dict.sort_by {|name, mod| name }.each do |name, mod| s << (" %-13s : %s\n" % [name, mod.desc]) end return s end def version return Erubis::VERSION end def parse_argv(argv, arg_none='', arg_required='', arg_optional='') options = {} context = {} while argv[0] && argv[0][0] == ?- optstr = argv.shift optstr = optstr[1, optstr.length-1] # if optstr[0] == ?- # context optstr =~ /\A\-([-\w]+)(?:=(.*))?/ or raise CommandOptionError.new("-#{optstr}: invalid context value.") name, value = $1, $2 name = name.gsub(/-/, '_').intern #value = value.nil? ? true : YAML.load(value) # error, why? value = value.nil? ? true : YAML.load("---\n#{value}\n") context[name] = value # else # options while optstr && !optstr.empty? optchar = optstr[0].chr optstr = optstr[1..-1] if arg_none.include?(optchar) options[optchar] = true elsif arg_required.include?(optchar) arg = optstr.empty? ? argv.shift : optstr or raise CommandOptionError.new("-#{optchar}: #{@option_names[optchar]} required.") options[optchar] = arg optstr = nil elsif arg_optional.include?(optchar) arg = optstr.empty? ? true : optstr options[optchar] = arg optstr = nil else raise CommandOptionError.new("-#{optchar}: unknown option.") end end end # end # end of while return options, context end def untabify(str, width=8) list = str.split(/\t/) last = list.pop sb = '' list.each do |s| column = (n = s.rindex(?\n)) ? s.length - n - 1 : s.length n = width - (column % width) sb << s << (' ' * n) end sb << last return sb end #-- #def untabify(str, width=8) # sb = '' # str.scan(/(.*?)\t/m) do |s, | # len = (n = s.rindex(?\n)) ? s.length - n - 1 : s.length # sb << s << (" " * (width - len % width)) # end # return $' ? (sb << $') : str #end #++ def get_classobj(classname, lang, pi) classname ||= "E#{lang}" base_module = pi ? Erubis::PI : Erubis begin klass = base_module.const_get(classname) rescue NameError klass = nil end unless klass if lang msg = "-l #{lang}: invalid language name (class #{base_module.name}::#{classname} not found)." else msg = "-c #{classname}: invalid class name." end raise CommandOptionError.new(msg) end return klass end def get_enhancers(enhancer_names) return [] unless enhancer_names enhancers = [] shortname = nil begin enhancer_names.split(/,/).each do |name| shortname = name enhancers << Erubis.const_get("#{shortname}Enhancer") end rescue NameError raise CommandOptionError.new("#{shortname}: no such Enhancer (try '-h' to show all enhancers).") end return enhancers end def load_datafiles(filenames, opts) context = Erubis::Context.new return context unless filenames filenames.split(/,/).each do |filename| filename.strip! test(?f, filename) or raise CommandOptionError.new("#{filename}: file not found.") if filename =~ /\.ya?ml$/ if opts.unexpand ydoc = YAML.load_file(filename) else ydoc = YAML.load(untabify(File.read(filename))) end ydoc.is_a?(Hash) or raise CommandOptionError.new("#{filename}: root object is not a mapping.") intern_hash_keys(ydoc) if opts.intern context.update(ydoc) elsif filename =~ /\.rb$/ str = File.read(filename) context2 = Erubis::Context.new _instance_eval(context2, str) context.update(context2) else CommandOptionError.new("#{filename}: '*.yaml', '*.yml', or '*.rb' required.") end end return context end def _instance_eval(_context, _str) _context.instance_eval(_str) end def parse_context_data(context_str, opts) if context_str[0] == ?{ require 'yaml' ydoc = YAML.load(context_str) unless ydoc.is_a?(Hash) raise CommandOptionError.new("-c: root object is not a mapping.") end intern_hash_keys(ydoc) if opts.intern return ydoc else context = Erubis::Context.new context.instance_eval(context_str, '-c') return context end end def intern_hash_keys(obj, done={}) return if done.key?(obj.__id__) case obj when Hash done[obj.__id__] = obj obj.keys.each do |key| obj[key.intern] = obj.delete(key) if key.is_a?(String) end obj.values.each do |val| intern_hash_keys(val, done) if val.is_a?(Hash) || val.is_a?(Array) end when Array done[obj.__id__] = obj obj.each do |val| intern_hash_keys(val, done) if val.is_a?(Hash) || val.is_a?(Array) end end end def check_syntax(filename, src) require 'open3' #command = (ENV['_'] || 'ruby') + ' -wc' # ENV['_'] stores command name bin = ENV['_'] && File.basename(ENV['_']) =~ /^ruby/ ? ENV['_'] : 'ruby' command = bin + ' -wc' stdin, stdout, stderr = Open3.popen3(command) stdin.write(src) stdin.close result = stdout.read() stdout.close() errmsg = stderr.read() stderr.close() return nil unless errmsg && !errmsg.empty? errmsg =~ /\A-:(\d+): / linenum, message = $1, $' return "#{filename}:#{linenum}: #{message}" end end end #--end of require 'erubis/main' Erubis::Main.main(ARGV)