Class: ERB::Compiler

Inherits:
Object
  • Object
show all
Defined in:
lib/erb/compiler.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.



427
428
429
430
431
432
433
# File 'lib/erb/compiler.rb', line 427

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



440
441
442
# File 'lib/erb/compiler.rb', line 440

def insert_cmd
  @insert_cmd
end

#percentObject (readonly)

Returns the value of attribute percent.



434
435
436
# File 'lib/erb/compiler.rb', line 434

def percent
  @percent
end

#post_cmdObject

An array of commands appended to compiled code



446
447
448
# File 'lib/erb/compiler.rb', line 446

def post_cmd
  @post_cmd
end

#pre_cmdObject

An array of commands prepended to compiled code



443
444
445
# File 'lib/erb/compiler.rb', line 443

def pre_cmd
  @pre_cmd
end

#put_cmdObject

The command to handle text that ends with a newline



437
438
439
# File 'lib/erb/compiler.rb', line 437

def put_cmd
  @put_cmd
end

#trim_modeObject (readonly)

Returns the value of attribute trim_mode.



434
435
436
# File 'lib/erb/compiler.rb', line 434

def trim_mode
  @trim_mode
end

Instance Method Details

#add_insert_cmd(out, content) ⇒ Object



309
310
311
# File 'lib/erb/compiler.rb', line 309

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

#add_put_cmd(out, content) ⇒ Object



305
306
307
# File 'lib/erb/compiler.rb', line 305

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)


315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/erb/compiler.rb', line 315

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



375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
# File 'lib/erb/compiler.rb', line 375

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 '<%#'
    out.push("\n" * content.count("\n")) # only adjust lineno
  end
end

#compile_etag(etag, out, scanner) ⇒ Object



362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/erb/compiler.rb', line 362

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



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/erb/compiler.rb', line 338

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:



421
422
423
# File 'lib/erb/compiler.rb', line 421

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

#prepare_trim_mode(mode) ⇒ Object

:nodoc:



392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# File 'lib/erb/compiler.rb', line 392

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