diff options
Diffstat (limited to 'lib/erb.rb')
-rw-r--r-- | lib/erb.rb | 583 |
1 files changed, 5 insertions, 578 deletions
diff --git a/lib/erb.rb b/lib/erb.rb index 3045106c92..bc1615d7da 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -14,6 +14,9 @@ require 'cgi/util' require 'erb/version' +require 'erb/compiler' +require 'erb/def_method' +require 'erb/util' # # = ERB -- Ruby Templating @@ -264,486 +267,7 @@ class ERB def self.version VERSION end -end -#-- -# ERB::Compiler -class ERB - # = ERB::Compiler - # - # Compiles ERB templates into Ruby code; the compiled code produces the - # template result when evaluated. ERB::Compiler provides hooks to define how - # generated output is handled. - # - # Internally ERB does something like this to generate the code returned by - # ERB#src: - # - # compiler = ERB::Compiler.new('<>') - # compiler.pre_cmd = ["_erbout=+''"] - # compiler.put_cmd = "_erbout.<<" - # compiler.insert_cmd = "_erbout.<<" - # compiler.post_cmd = ["_erbout"] - # - # code, enc = compiler.compile("Got <%= obj %>!\n") - # puts code - # - # <i>Generates</i>: - # - # #coding:UTF-8 - # _erbout=+''; _erbout.<< "Got ".freeze; _erbout.<<(( obj ).to_s); _erbout.<< "!\n".freeze; _erbout - # - # By default the output is sent to the print method. For example: - # - # compiler = ERB::Compiler.new('<>') - # code, enc = compiler.compile("Got <%= obj %>!\n") - # puts code - # - # <i>Generates</i>: - # - # #coding:UTF-8 - # print "Got ".freeze; print(( obj ).to_s); print "!\n".freeze - # - # == Evaluation - # - # The compiled code can be used in any context where the names in the code - # correctly resolve. Using the last example, each of these print 'Got It!' - # - # Evaluate using a variable: - # - # obj = 'It' - # eval code - # - # Evaluate using an input: - # - # mod = Module.new - # mod.module_eval %{ - # def get(obj) - # #{code} - # end - # } - # extend mod - # get('It') - # - # Evaluate using an accessor: - # - # klass = Class.new Object - # klass.class_eval %{ - # attr_accessor :obj - # def initialize(obj) - # @obj = obj - # end - # def get_it - # #{code} - # end - # } - # klass.new('It').get_it - # - # Good! See also ERB#def_method, ERB#def_module, and ERB#def_class. - class Compiler # :nodoc: - class PercentLine # :nodoc: - def initialize(str) - @value = str - end - attr_reader :value - alias :to_s :value - end - - class Scanner # :nodoc: - @scanner_map = {} - class << self - def register_scanner(klass, trim_mode, percent) - @scanner_map[[trim_mode, percent]] = klass - end - alias :regist_scanner :register_scanner - end - - def self.default_scanner=(klass) - @default_scanner = klass - end - - def self.make_scanner(src, trim_mode, percent) - klass = @scanner_map.fetch([trim_mode, percent], @default_scanner) - klass.new(src, trim_mode, percent) - end - - DEFAULT_STAGS = %w(<%% <%= <%# <%).freeze - DEFAULT_ETAGS = %w(%%> %>).freeze - def initialize(src, trim_mode, percent) - @src = src - @stag = nil - @stags = DEFAULT_STAGS - @etags = DEFAULT_ETAGS - end - attr_accessor :stag - attr_reader :stags, :etags - - def scan; end - end - - class TrimScanner < Scanner # :nodoc: - def initialize(src, trim_mode, percent) - super - @trim_mode = trim_mode - @percent = percent - if @trim_mode == '>' - @scan_reg = /(.*?)(%>\r?\n|#{(stags + etags).join('|')}|\n|\z)/m - @scan_line = self.method(:trim_line1) - elsif @trim_mode == '<>' - @scan_reg = /(.*?)(%>\r?\n|#{(stags + etags).join('|')}|\n|\z)/m - @scan_line = self.method(:trim_line2) - elsif @trim_mode == '-' - @scan_reg = /(.*?)(^[ \t]*<%\-|<%\-|-%>\r?\n|-%>|#{(stags + etags).join('|')}|\z)/m - @scan_line = self.method(:explicit_trim_line) - else - @scan_reg = /(.*?)(#{(stags + etags).join('|')}|\n|\z)/m - @scan_line = self.method(:scan_line) - end - end - - def scan(&block) - @stag = nil - if @percent - @src.each_line do |line| - percent_line(line, &block) - end - else - @scan_line.call(@src, &block) - end - nil - end - - def percent_line(line, &block) - if @stag || line[0] != ?% - return @scan_line.call(line, &block) - end - - line[0] = '' - if line[0] == ?% - @scan_line.call(line, &block) - else - yield(PercentLine.new(line.chomp)) - end - end - - def scan_line(line) - line.scan(@scan_reg) do |tokens| - tokens.each do |token| - next if token.empty? - yield(token) - end - end - end - - def trim_line1(line) - line.scan(@scan_reg) do |tokens| - tokens.each do |token| - next if token.empty? - if token == "%>\n" || token == "%>\r\n" - yield('%>') - yield(:cr) - else - yield(token) - end - end - end - end - - def trim_line2(line) - head = nil - line.scan(@scan_reg) do |tokens| - tokens.each do |token| - next if token.empty? - head = token unless head - if token == "%>\n" || token == "%>\r\n" - yield('%>') - if is_erb_stag?(head) - yield(:cr) - else - yield("\n") - end - head = nil - else - yield(token) - head = nil if token == "\n" - end - end - end - end - - def explicit_trim_line(line) - line.scan(@scan_reg) do |tokens| - tokens.each do |token| - next if token.empty? - if @stag.nil? && /[ \t]*<%-/ =~ token - yield('<%') - elsif @stag && (token == "-%>\n" || token == "-%>\r\n") - yield('%>') - yield(:cr) - elsif @stag && token == '-%>' - yield('%>') - else - yield(token) - end - end - end - end - - ERB_STAG = %w(<%= <%# <%) - def is_erb_stag?(s) - ERB_STAG.member?(s) - end - end - - Scanner.default_scanner = TrimScanner - - begin - require 'strscan' - rescue LoadError - else - class SimpleScanner < Scanner # :nodoc: - def scan - stag_reg = (stags == DEFAULT_STAGS) ? /(.*?)(<%[%=#]?|\z)/m : /(.*?)(#{stags.join('|')}|\z)/m - etag_reg = (etags == DEFAULT_ETAGS) ? /(.*?)(%%?>|\z)/m : /(.*?)(#{etags.join('|')}|\z)/m - scanner = StringScanner.new(@src) - while ! scanner.eos? - scanner.scan(@stag ? etag_reg : stag_reg) - yield(scanner[1]) - yield(scanner[2]) - end - end - end - Scanner.register_scanner(SimpleScanner, nil, false) - - class ExplicitScanner < Scanner # :nodoc: - def scan - stag_reg = /(.*?)(^[ \t]*<%-|<%-|#{stags.join('|')}|\z)/m - etag_reg = /(.*?)(-%>|#{etags.join('|')}|\z)/m - scanner = StringScanner.new(@src) - while ! scanner.eos? - scanner.scan(@stag ? etag_reg : stag_reg) - yield(scanner[1]) - - elem = scanner[2] - if /[ \t]*<%-/ =~ elem - yield('<%') - elsif elem == '-%>' - yield('%>') - yield(:cr) if scanner.scan(/(\r?\n|\z)/) - else - yield(elem) - end - end - end - end - Scanner.register_scanner(ExplicitScanner, '-', false) - end - - class Buffer # :nodoc: - def initialize(compiler, enc=nil, frozen=nil) - @compiler = compiler - @line = [] - @script = +'' - @script << "#coding:#{enc}\n" if enc - @script << "#frozen-string-literal:#{frozen}\n" unless frozen.nil? - @compiler.pre_cmd.each do |x| - push(x) - end - end - attr_reader :script - - def push(cmd) - @line << cmd - end - - def cr - @script << (@line.join('; ')) - @line = [] - @script << "\n" - end - - def close - return unless @line - @compiler.post_cmd.each do |x| - push(x) - end - @script << (@line.join('; ')) - @line = nil - end - end - - def add_put_cmd(out, content) - out.push("#{@put_cmd} #{content.dump}.freeze#{"\n" * content.count("\n")}") - end - - def add_insert_cmd(out, content) - out.push("#{@insert_cmd}((#{content}).to_s)") - end - - # Compiles an ERB template into Ruby code. Returns an array of the code - # and encoding like ["code", Encoding]. - def compile(s) - enc = s.encoding - raise ArgumentError, "#{enc} is not ASCII compatible" if enc.dummy? - s = s.b # see String#b - magic_comment = detect_magic_comment(s, enc) - out = Buffer.new(self, *magic_comment) - - self.content = +'' - scanner = make_scanner(s) - scanner.scan do |token| - next if token.nil? - next if token == '' - if scanner.stag.nil? - compile_stag(token, out, scanner) - else - compile_etag(token, out, scanner) - end - end - add_put_cmd(out, content) if content.size > 0 - out.close - return out.script, *magic_comment - end - - def compile_stag(stag, out, scanner) - case stag - when PercentLine - add_put_cmd(out, content) if content.size > 0 - self.content = +'' - out.push(stag.to_s) - out.cr - when :cr - out.cr - when '<%', '<%=', '<%#' - scanner.stag = stag - add_put_cmd(out, content) if content.size > 0 - self.content = +'' - when "\n" - content << "\n" - add_put_cmd(out, content) - self.content = +'' - when '<%%' - content << '<%' - else - content << stag - end - end - - def compile_etag(etag, out, scanner) - case etag - when '%>' - compile_content(scanner.stag, out) - scanner.stag = nil - self.content = +'' - when '%%>' - content << '%>' - else - content << etag - end - end - - def compile_content(stag, out) - case stag - when '<%' - if content[-1] == ?\n - content.chop! - out.push(content) - out.cr - else - out.push(content) - end - when '<%=' - add_insert_cmd(out, content) - when '<%#' - # commented out - end - end - - def prepare_trim_mode(mode) # :nodoc: - case mode - when 1 - return [false, '>'] - when 2 - return [false, '<>'] - when 0, nil - return [false, nil] - when String - unless mode.match?(/\A(%|-|>|<>){1,2}\z/) - warn_invalid_trim_mode(mode, uplevel: 5) - end - - perc = mode.include?('%') - if mode.include?('-') - return [perc, '-'] - elsif mode.include?('<>') - return [perc, '<>'] - elsif mode.include?('>') - return [perc, '>'] - else - [perc, nil] - end - else - warn_invalid_trim_mode(mode, uplevel: 5) - return [false, nil] - end - end - - def make_scanner(src) # :nodoc: - Scanner.make_scanner(src, @trim_mode, @percent) - end - - # Construct a new compiler using the trim_mode. See ERB::new for available - # trim modes. - def initialize(trim_mode) - @percent, @trim_mode = prepare_trim_mode(trim_mode) - @put_cmd = 'print' - @insert_cmd = @put_cmd - @pre_cmd = [] - @post_cmd = [] - end - attr_reader :percent, :trim_mode - - # The command to handle text that ends with a newline - attr_accessor :put_cmd - - # The command to handle text that is inserted prior to a newline - attr_accessor :insert_cmd - - # An array of commands prepended to compiled code - attr_accessor :pre_cmd - - # An array of commands appended to compiled code - attr_accessor :post_cmd - - private - - # A buffered text in #compile - attr_accessor :content - - def detect_magic_comment(s, enc = nil) - re = @percent ? /\G(?:<%#(.*)%>|%#(.*)\n)/ : /\G<%#(.*)%>/ - frozen = nil - s.scan(re) do - comment = $+ - comment = $1 if comment[/-\*-\s*(.*?)\s*-*-$/] - case comment - when %r"coding\s*[=:]\s*([[:alnum:]\-_]+)" - enc = Encoding.find($1.sub(/-(?:mac|dos|unix)/i, '')) - when %r"frozen[-_]string[-_]literal\s*:\s*([[:alnum:]]+)" - frozen = $1 - end - end - return enc, frozen - end - - def warn_invalid_trim_mode(mode, uplevel:) - warn "Invalid ERB trim mode: #{mode.inspect} (trim_mode: nil, 0, 1, 2, or String composed of '%' and/or '-', '>', '<>')", uplevel: uplevel + 1 - end - end -end - -#-- -# ERB -class ERB # # Constructs a new ERB object with the template specified in _str_. # @@ -783,11 +307,11 @@ class ERB # def build # b = binding # # create and run templates, filling member data variables - # ERB.new(<<-'END_PRODUCT'.gsub(/^\s+/, ""), trim_mode: "", eoutvar: "@product").result b + # ERB.new(<<~'END_PRODUCT', trim_mode: "", eoutvar: "@product").result b # <%= PRODUCT[:name] %> # <%= PRODUCT[:desc] %> # END_PRODUCT - # ERB.new(<<-'END_PRICE'.gsub(/^\s+/, ""), trim_mode: "", eoutvar: "@price").result b + # ERB.new(<<~'END_PRICE', trim_mode: "", eoutvar: "@price").result b # <%= PRODUCT[:name] %> -- <%= PRODUCT[:cost] %> # <%= PRODUCT[:desc] %> # END_PRICE @@ -980,100 +504,3 @@ class ERB cls end end - -#-- -# ERB::Util -class ERB - # A utility module for conversion routines, often handy in HTML generation. - module Util - public - # - # A utility method for escaping HTML tag characters in _s_. - # - # require "erb" - # include ERB::Util - # - # puts html_escape("is a > 0 & a < 10?") - # - # _Generates_ - # - # is a > 0 & a < 10? - # - def html_escape(s) - CGI.escapeHTML(s.to_s) - end - alias h html_escape - module_function :h - module_function :html_escape - - # - # A utility method for encoding the String _s_ as a URL. - # - # require "erb" - # include ERB::Util - # - # puts url_encode("Programming Ruby: The Pragmatic Programmer's Guide") - # - # _Generates_ - # - # Programming%20Ruby%3A%20%20The%20Pragmatic%20Programmer%27s%20Guide - # - def url_encode(s) - s.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) { |m| - sprintf("%%%02X", m.unpack1("C")) - } - end - alias u url_encode - module_function :u - module_function :url_encode - end -end - -#-- -# ERB::DefMethod -class ERB - # Utility module to define eRuby script as instance method. - # - # === Example - # - # example.rhtml: - # <% for item in @items %> - # <b><%= item %></b> - # <% end %> - # - # example.rb: - # require 'erb' - # class MyClass - # extend ERB::DefMethod - # def_erb_method('render()', 'example.rhtml') - # def initialize(items) - # @items = items - # end - # end - # print MyClass.new([10,20,30]).render() - # - # result: - # - # <b>10</b> - # - # <b>20</b> - # - # <b>30</b> - # - module DefMethod - public - # define _methodname_ as instance method of current module, using ERB - # object or eRuby file - def def_erb_method(methodname, erb_or_fname) - if erb_or_fname.kind_of? String - fname = erb_or_fname - erb = ERB.new(File.read(fname)) - erb.def_method(self, methodname, fname) - else - erb = erb_or_fname - erb.def_method(self, methodname, erb.filename || '(ERB)') - end - end - module_function :def_erb_method - end -end |