Class: ERB::Compiler

Inherits:
Object
  • Object
show all
Defined in:
lib/erb.rb

Overview

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

Generates:

#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

Generates:

#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.

Defined Under Namespace

Classes: Buffer, PercentLine, Scanner, TrimScanner

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(trim_mode) ⇒ Compiler

Construct a new compiler using the trim_mode. See ERB::new for available trim modes.



696
697
698
699
700
701
702
# File 'lib/erb.rb', line 696

def initialize(trim_mode)
  @percent, @trim_mode = prepare_trim_mode(trim_mode)
  @put_cmd = 'print'
  @insert_cmd = @put_cmd
  @pre_cmd = []
  @post_cmd = []
end

Instance Attribute Details

#insert_cmdObject

The command to handle text that is inserted prior to a newline



709
710
711
# File 'lib/erb.rb', line 709

def insert_cmd
  @insert_cmd
end

#percentObject (readonly)

Returns the value of attribute percent.



703
704
705
# File 'lib/erb.rb', line 703

def percent
  @percent
end

#post_cmdObject

An array of commands appended to compiled code



715
716
717
# File 'lib/erb.rb', line 715

def post_cmd
  @post_cmd
end

#pre_cmdObject

An array of commands prepended to compiled code



712
713
714
# File 'lib/erb.rb', line 712

def pre_cmd
  @pre_cmd
end

#put_cmdObject

The command to handle text that ends with a newline



706
707
708
# File 'lib/erb.rb', line 706

def put_cmd
  @put_cmd
end

#trim_modeObject (readonly)

Returns the value of attribute trim_mode.



703
704
705
# File 'lib/erb.rb', line 703

def trim_mode
  @trim_mode
end

Instance Method Details

#add_insert_cmd(out, content) ⇒ Object



578
579
580
# File 'lib/erb.rb', line 578

def add_insert_cmd(out, content)
  out.push("#{@insert_cmd}((#{content}).to_s)")
end

#add_put_cmd(out, content) ⇒ Object



574
575
576
# File 'lib/erb.rb', line 574

def add_put_cmd(out, content)
  out.push("#{@put_cmd} #{content.dump}.freeze#{"\n" * content.count("\n")}")
end

#compile(s) ⇒ Object

Compiles an ERB template into Ruby code. Returns an array of the code and encoding like [“code”, Encoding].

Raises:

  • (ArgumentError)


584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
# File 'lib/erb.rb', line 584

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

#compile_content(stag, out) ⇒ Object



644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
# File 'lib/erb.rb', line 644

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

#compile_etag(etag, out, scanner) ⇒ Object



631
632
633
634
635
636
637
638
639
640
641
642
# File 'lib/erb.rb', line 631

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

#compile_stag(stag, out, scanner) ⇒ Object



607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
# File 'lib/erb.rb', line 607

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

#make_scanner(src) ⇒ Object

:nodoc:



690
691
692
# File 'lib/erb.rb', line 690

def make_scanner(src) # :nodoc:
  Scanner.make_scanner(src, @trim_mode, @percent)
end

#prepare_trim_mode(mode) ⇒ Object

:nodoc:



661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
# File 'lib/erb.rb', line 661

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