# Ruby Toys -- <%= @name %>
#
# <%= @name %> (<%= @code %>)
# <%= @desc %>
#
# <% @features.each do |f| %>
# - <%= f %>
# <% end %>
#
#
# <% if @cost < 10 %>
# Only <%= @cost %>!!!
# <% else %>
# Call for a price, today!
# <% end %>
#
#
#
# TEMPLATE
# ```
#
# Finally, create the \ERB object and get the result (omitting some blank lines):
#
# ```
# erb = ERB.new(template)
# puts erb.result(toy.get_binding)
#
# Ruby Toys -- Rubysapien
#
# Rubysapien (TZ-1002)
# Geek's Best Friend! Responds to Ruby commands...
#
# - Listens for verbal commands in the Ruby language!
# - Ignores Perl, Java, and all C variants.
# - Karate-Chop Action!!!
# - Matz signature on left leg.
# - Gem studded eyes... Rubies, of course!
#
#
# Call for a price, today!
#
#
#
# ```
#
#
# ## Other Template Processors
#
# Various Ruby projects have their own template processors.
# The Ruby Processing System [RDoc][rdoc], for example, has one that can be used elsewhere.
#
# Other popular template processors may found in the [Template Engines][template engines] page
# of the Ruby Toolbox.
#
# [%q literals]: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-25q-3A+Non-Interpolable+String+Literals
# [augmented binding]: rdoc-ref:ERB@Augmented+Binding
# [binding object]: https://docs.ruby-lang.org/en/master/Binding.html
# [comment tags]: rdoc-ref:ERB@Comment+Tags
# [default binding]: rdoc-ref:ERB@Default+Binding
# [encoding]: https://docs.ruby-lang.org/en/master/Encoding.html
# [execution tags]: rdoc-ref:ERB@Execution+Tags
# [expression tags]: rdoc-ref:ERB@Expression+Tags
# [kernel#binding]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-binding
# [local binding]: rdoc-ref:ERB@Local+Binding
# [magic comments]: https://docs.ruby-lang.org/en/master/syntax/comments_rdoc.html#label-Magic+Comments
# [rdoc]: https://ruby.github.io/rdoc
# [sprintf]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-sprintf
# [template engines]: https://www.ruby-toolbox.com/categories/template_engines
# [template processor]: https://en.wikipedia.org/wiki/Template_processor
#
class ERB
# :markup: markdown
#
# :call-seq:
# self.version -> string
#
# Returns the string \ERB version.
def self.version
VERSION
end
# :markup: markdown
#
# :call-seq:
# ERB.new(template, trim_mode: nil, eoutvar: '_erbout')
#
# Returns a new \ERB object containing the given string +template+.
#
# For details about `template`, its embedded tags, and generated results, see ERB.
#
# **Keyword Argument `trim_mode`**
#
# You can use keyword argument `trim_mode: '%'`
# to enable the [shorthand format][shorthand format] for execution tags.
#
# This value allows [blank line control][blank line control]:
#
# - `'-'`: Omit each blank line ending with `'%>'`.
#
# Other values allow [newline control][newline control]:
#
# - `'>'`: Omit newline for each line ending with `'%>'`.
# - `'<>'`: Omit newline for each line starting with `'<%'` and ending with `'%>'`.
#
# You can also [combine trim modes][combine trim modes].
#
# **Keyword Argument `eoutvar`**
#
# The string value of keyword argument `eoutvar` specifies the name of the variable
# that method #result uses to construct its result string;
# see #src.
#
# This is useful when you need to run multiple \ERB templates through the same binding
# and/or when you want to control where output ends up.
#
# It's good practice to choose a variable name that begins with an underscore: `'_'`.
#
# [blank line control]: rdoc-ref:ERB@Suppressing+Unwanted+Blank+Lines
# [combine trim modes]: rdoc-ref:ERB@Combining+Trim+Modes
# [newline control]: rdoc-ref:ERB@Suppressing+Unwanted+Newlines
# [shorthand format]: rdoc-ref:ERB@Shorthand+Format+for+Execution+Tags
#
def initialize(str, trim_mode: nil, eoutvar: '_erbout')
compiler = make_compiler(trim_mode)
set_eoutvar(compiler, eoutvar)
@src, @encoding, @frozen_string = *compiler.compile(str)
@filename = nil
@lineno = 0
@_init = self.class.singleton_class
end
# :markup: markdown
#
# :call-seq:
# make_compiler -> erb_compiler
#
# Returns a new ERB::Compiler with the given `trim_mode`;
# for `trim_mode` values, see ERB.new:
#
# ```
# ERB.new('').make_compiler(nil)
# # => #
# ```
#
def make_compiler(trim_mode)
ERB::Compiler.new(trim_mode)
end
# :markup: markdown
#
# Returns the Ruby code that, when executed, generates the result;
# the code is executed by method #result,
# and by its wrapper methods #result_with_hash and #run:
#
# ```
# template = 'The time is <%= Time.now %>.'
# erb = ERB.new(template)
# erb.src
# # => "#coding:UTF-8\n_erbout = +''; _erbout.<< \"The time is \".freeze; _erbout.<<(( Time.now ).to_s); _erbout.<< \".\".freeze; _erbout"
# erb.result
# # => "The time is 2025-09-18 15:58:08 -0500."
# ```
#
# In a more readable format:
#
# ```
# # puts erb.src.split('; ')
# # #coding:UTF-8
# # _erbout = +''
# # _erbout.<< "The time is ".freeze
# # _erbout.<<(( Time.now ).to_s)
# # _erbout.<< ".".freeze
# # _erbout
# ```
#
# Variable `_erbout` is used to store the intermediate results in the code;
# the name `_erbout` is the default in ERB.new,
# and can be changed via keyword argument `eoutvar`:
#
# ```
# erb = ERB.new(template, eoutvar: '_foo')
# puts template.src.split('; ')
# #coding:UTF-8
# _foo = +''
# _foo.<< "The time is ".freeze
# _foo.<<(( Time.now ).to_s)
# _foo.<< ".".freeze
# _foo
# ```
#
attr_reader :src
# :markup: markdown
#
# Returns the encoding of `self`;
# see [Encodings][encodings]:
#
# [encodings]: rdoc-ref:ERB@Encodings
#
attr_reader :encoding
# :markup: markdown
#
# Sets or returns the file name to be used in reporting errors;
# see [Error Reporting][error reporting].
#
# [error reporting]: rdoc-ref:ERB@Error+Reporting
attr_accessor :filename
# :markup: markdown
#
# Sets or returns the line number to be used in reporting errors;
# see [Error Reporting][error reporting].
#
# [error reporting]: rdoc-ref:ERB@Error+Reporting
attr_accessor :lineno
# :markup: markdown
#
# :call-seq:
# location = [filename, lineno] => [filename, lineno]
# location = filename -> filename
#
# Sets the values of #filename and, if given, #lineno;
# see [Error Reporting][error reporting].
#
# [error reporting]: rdoc-ref:ERB@Error+Reporting
def location=((filename, lineno))
@filename = filename
@lineno = lineno if lineno
end
# :markup: markdown
#
# :call-seq:
# set_eoutvar(compiler, eoutvar = '_erbout') -> [eoutvar]
#
# Sets the `eoutvar` value in the ERB::Compiler object `compiler`;
# returns a 1-element array containing the value of `eoutvar`:
#
# ```
# template = ERB.new('')
# compiler = template.make_compiler(nil)
# pp compiler
# #
# template.set_eoutvar(compiler, '_foo') # => ["_foo"]
# pp compiler
# #
# ```
#
def set_eoutvar(compiler, eoutvar = '_erbout')
compiler.put_cmd = "#{eoutvar}.<<"
compiler.insert_cmd = "#{eoutvar}.<<"
compiler.pre_cmd = ["#{eoutvar} = +''"]
compiler.post_cmd = [eoutvar]
end
# :markup: markdown
#
# :call-seq:
# run(binding = new_toplevel) -> nil
#
# Like #result, but prints the result string (instead of returning it);
# returns `nil`.
def run(b=new_toplevel)
print self.result(b)
end
# :markup: markdown
#
# :call-seq:
# result(binding = new_toplevel) -> new_string
#
# Returns the string result formed by processing \ERB tags found in the stored template in `self`.
#
# With no argument given, uses the default binding;
# see [Default Binding][default binding].
#
# With argument `binding` given, uses the local binding;
# see [Local Binding][local binding].
#
# See also #result_with_hash.
#
# [default binding]: rdoc-ref:ERB@Default+Binding
# [local binding]: rdoc-ref:ERB@Local+Binding
#
def result(b=new_toplevel)
unless @_init.equal?(self.class.singleton_class)
raise ArgumentError, "not initialized"
end
eval(@src, b, (@filename || '(erb)'), @lineno)
end
# :markup: markdown
#
# :call-seq:
# result_with_hash(hash) -> new_string
#
# Returns the string result formed by processing \ERB tags found in the stored string in `self`;
# see [Augmented Binding][augmented binding].
#
# See also #result.
#
# [augmented binding]: rdoc-ref:ERB@Augmented+Binding
#
def result_with_hash(hash)
b = new_toplevel(hash.keys)
hash.each_pair do |key, value|
b.local_variable_set(key, value)
end
result(b)
end
# :markup: markdown
#
# :call-seq:
# new_toplevel(symbols) -> new_binding
#
# Returns a new binding based on `TOPLEVEL_BINDING`;
# used to create a default binding for a call to #result.
#
# See [Default Binding][default binding].
#
# Argument `symbols` is an array of symbols;
# each symbol `symbol` is defined as a new variable to hide and
# prevent it from overwriting a variable of the same name already
# defined within the binding.
#
# [default binding]: rdoc-ref:ERB@Default+Binding
def new_toplevel(vars = nil)
b = TOPLEVEL_BINDING
if vars
vars = vars.select {|v| b.local_variable_defined?(v)}
unless vars.empty?
return b.eval("tap {|;#{vars.join(',')}| break binding}")
end
end
b.dup
end
private :new_toplevel
# :markup: markdown
#
# :call-seq:
# def_method(module, method_signature, filename = '(ERB)') -> method_name
#
# Creates and returns a new instance method in the given module `module`;
# returns the method name as a symbol.
#
# The method is created from the given `method_signature`,
# which consists of the method name and its argument names (if any).
#
# The `filename` sets the value of #filename;
# see [Error Reporting][error reporting].
#
# [error reporting]: rdoc-ref:ERB@Error+Reporting
#
# ```
# template = '<%= arg1 %> <%= arg2 %>'
# erb = ERB.new(template)
# MyModule = Module.new
# erb.def_method(MyModule, 'render(arg1, arg2)') # => :render
# class MyClass; include MyModule; end
# MyClass.new.render('foo', 123) # => "foo 123"
# ```
#
def def_method(mod, methodname, fname='(ERB)')
unless @_init.equal?(self.class.singleton_class)
raise ArgumentError, "not initialized"
end
src = self.src.sub(/^(?!#|$)/) {"def #{methodname}\n"} << "\nend\n"
mod.module_eval do
eval(src, binding, fname, -1)
end
end
# :markup: markdown
#
# :call-seq:
# def_module(method_name = 'erb') -> new_module
#
# Returns a new nameless module that has instance method `method_name`.
#
# ```
# template = '<%= arg1 %> <%= arg2 %>'
# erb = ERB.new(template)
# MyModule = template.def_module('render(arg1, arg2)')
# class MyClass
# include MyModule
# end
# MyClass.new.render('foo', 123)
# # => "foo 123"
# ```
#
def def_module(methodname='erb')
mod = Module.new
def_method(mod, methodname, @filename || '(ERB)')
mod
end
# :markup: markdown
#
# :call-seq:
# def_class(super_class = Object, method_name = 'result') -> new_class
#
# Returns a new nameless class whose superclass is `super_class`,
# and which has instance method `method_name`.
#
# Create a template from HTML that has embedded expression tags that use `@arg1` and `@arg2`:
#
# ```
# html = <
#
# <%= @arg1 %>
# <%= @arg2 %>
#
#
# TEMPLATE
# template = ERB.new(html)
# ```
#
# Create a base class that has `@arg1` and `@arg2`:
#
# ```
# class MyBaseClass
# def initialize(arg1, arg2)
# @arg1 = arg1
# @arg2 = arg2
# end
# end
# ```
#
# Use method #def_class to create a subclass that has method `:render`:
#
# ```
# MySubClass = template.def_class(MyBaseClass, :render)
# ```
#
# Generate the result:
#
# ```
# puts MySubClass.new('foo', 123).render
#
#
# foo
# 123
#
#
# ```
#
def def_class(superklass=Object, methodname='result')
cls = Class.new(superklass)
def_method(cls, methodname, @filename || '(ERB)')
cls
end
end