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.



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

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



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

def insert_cmd
  @insert_cmd
end

#percentObject (readonly)

Returns the value of attribute percent.



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

def percent
  @percent
end

#post_cmdObject

An array of commands appended to compiled code



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

def post_cmd
  @post_cmd
end

#pre_cmdObject

An array of commands prepended to compiled code



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

def pre_cmd
  @pre_cmd
end

#put_cmdObject

The command to handle text that ends with a newline



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

def put_cmd
  @put_cmd
end

#trim_modeObject (readonly)

Returns the value of attribute trim_mode.



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

def trim_mode
  @trim_mode
end

Instance Method Details

#add_insert_cmd(out, content) ⇒ Object



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

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

#add_put_cmd(out, content) ⇒ Object



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

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)


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

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



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

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



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

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



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

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:



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

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

#prepare_trim_mode(mode) ⇒ Object

:nodoc:



391
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
# File 'lib/erb/compiler.rb', line 391

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