diff options
Diffstat (limited to 'lib/erb.rb')
| -rw-r--r-- | lib/erb.rb | 1848 |
1 files changed, 974 insertions, 874 deletions
diff --git a/lib/erb.rb b/lib/erb.rb index 17dc557929..bde90de841 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -12,871 +12,963 @@ # # You can redistribute it and/or modify it under the same terms as Ruby. -require 'cgi/util' +# A NOTE ABOUT TERMS: +# +# Formerly: The documentation in this file used the term _template_ to refer to an ERB object. +# +# Now: The documentation in this file uses the term _template_ +# to refer to the string input to ERB.new. +# +# The reason for the change: When documenting the ERB executable erb, +# we need a term that refers to its string input; +# _source_ is not a good idea, because ERB#src means something entirely different; +# the two different sorts of sources would bring confusion. +# +# Therefore we use the term _template_ to refer to: +# +# - The string input to ERB.new +# - The string input to executable erb. +# + require 'erb/version' +require 'erb/compiler' +require 'erb/def_method' +require 'erb/util' +# :markup: markdown # -# = ERB -- Ruby Templating +# Class **ERB** (the name stands for **Embedded Ruby**) +# is an easy-to-use, but also very powerful, [template processor][template processor]. # -# == Introduction +# ## Usage # -# ERB provides an easy to use but powerful templating system for Ruby. Using -# ERB, actual Ruby code can be added to any plain text document for the -# purposes of generating document information details and/or flow control. +# Before you can use \ERB, you must first require it +# (examples on this page assume that this has been done): # -# A very simple example is this: +# ``` +# require 'erb' +# ``` # -# require 'erb' +# ## In Brief # -# x = 42 -# template = ERB.new <<-EOF -# The value of x is: <%= x %> -# EOF -# puts template.result(binding) +# Here's how \ERB works: # -# <em>Prints:</em> The value of x is: 42 +# - You can create a *template*: a plain-text string that includes specially formatted *tags*.. +# - You can create an \ERB object to store the template. +# - You can call instance method ERB#result to get the *result*. # -# More complex examples are given below. +# \ERB supports tags of three kinds: # +# - [Expression tags][expression tags]: +# each begins with `'<%='`, ends with `'%>'`; contains a Ruby expression; +# in the result, the value of the expression replaces the entire tag: # -# == Recognized Tags +# template = 'The magic word is <%= magic_word %>.' +# erb = ERB.new(template) +# magic_word = 'xyzzy' +# erb.result(binding) # => "The magic word is xyzzy." # -# ERB recognizes certain tags in the provided template and converts them based -# on the rules below: +# The above call to #result passes argument `binding`, +# which contains the binding of variable `magic_word` to its string value `'xyzzy'`. # -# <% Ruby code -- inline with output %> -# <%= Ruby expression -- replace with result %> -# <%# comment -- ignored -- useful in testing %> -# % a line of Ruby code -- treated as <% line %> (optional -- see ERB.new) -# %% replaced with % if first thing on a line and % processing is used -# <%% or %%> -- replace with <% or %> respectively +# The below call to #result need not pass a binding, +# because its expression `Date::DAYNAMES` is globally defined. # -# All other text is passed through ERB filtering unchanged. +# ERB.new('Today is <%= Date::DAYNAMES[Date.today.wday] %>.').result # => "Today is Monday." # +# - [Execution tags][execution tags]: +# each begins with `'<%'`, ends with `'%>'`; contains Ruby code to be executed: # -# == Options +# template = '<% File.write("t.txt", "Some stuff.") %>' +# ERB.new(template).result +# File.read('t.txt') # => "Some stuff." # -# There are several settings you can change when you use ERB: -# * the nature of the tags that are recognized; -# * the binding used to resolve local variables in the template. +# - [Comment tags][comment tags]: +# each begins with `'<%#'`, ends with `'%>'`; contains comment text; +# in the result, the entire tag is omitted. # -# See the ERB.new and ERB#result methods for more detail. +# template = 'Some stuff;<%# Note to self: figure out what the stuff is. %> more stuff.' +# ERB.new(template).result # => "Some stuff; more stuff." # -# == Character encodings +# ## Some Simple Examples # -# ERB (or Ruby code generated by ERB) returns a string in the same -# character encoding as the input string. When the input string has -# a magic comment, however, it returns a string in the encoding specified -# by the magic comment. +# Here's a simple example of \ERB in action: # -# # -*- coding: utf-8 -*- -# require 'erb' +# ``` +# template = 'The time is <%= Time.now %>.' +# erb = ERB.new(template) +# erb.result +# # => "The time is 2025-09-09 10:49:26 -0500." +# ``` # -# template = ERB.new <<EOF -# <%#-*- coding: Big5 -*-%> -# \_\_ENCODING\_\_ is <%= \_\_ENCODING\_\_ %>. -# EOF -# puts template.result +# Details: # -# <em>Prints:</em> \_\_ENCODING\_\_ is Big5. +# 1. A plain-text string is assigned to variable `template`. +# Its embedded [expression tag][expression tags] `'<%= Time.now %>'` includes a Ruby expression, `Time.now`. +# 2. The string is put into a new \ERB object, and stored in variable `erb`. +# 4. Method call `erb.result` generates a string that contains the run-time value of `Time.now`, +# as computed at the time of the call. # +# The +# \ERB object may be re-used: # -# == Examples +# ``` +# erb.result +# # => "The time is 2025-09-09 10:49:33 -0500." +# ``` # -# === Plain Text +# Another example: # -# ERB is useful for any generic templating situation. Note that in this example, we use the -# convenient "% at start of line" tag, and we quote the template literally with -# <tt>%q{...}</tt> to avoid trouble with the backslash. +# ``` +# template = 'The magic word is <%= magic_word %>.' +# erb = ERB.new(template) +# magic_word = 'abracadabra' +# erb.result(binding) +# # => "The magic word is abracadabra." +# ``` # -# require "erb" +# Details: # -# # Create template. -# template = %q{ -# From: James Edward Gray II <james@grayproductions.net> -# To: <%= to %> -# Subject: Addressing Needs +# 1. As before, a plain-text string is assigned to variable `template`. +# Its embedded [expression tag][expression tags] `'<%= magic_word %>'` has a variable *name*, `magic_word`. +# 2. The string is put into a new \ERB object, and stored in variable `erb`; +# note that `magic_word` need not be defined before the \ERB object is created. +# 3. `magic_word = 'abracadabra'` assigns a value to variable `magic_word`. +# 4. Method call `erb.result(binding)` generates a string +# that contains the *value* of `magic_word`. # -# <%= to[/\w+/] %>: +# As before, the \ERB object may be re-used: # -# Just wanted to send a quick note assuring that your needs are being -# addressed. +# ``` +# magic_word = 'xyzzy' +# erb.result(binding) +# # => "The magic word is xyzzy." +# ``` # -# I want you to know that my team will keep working on the issues, -# especially: +# ## Bindings # -# <%# ignore numerous minor requests -- focus on priorities %> -# % priorities.each do |priority| -# * <%= priority %> -# % end +# A call to method #result, which produces the formatted result string, +# requires a [Binding object][binding object] as its argument. +# +# The binding object provides the bindings for expressions in [expression tags][expression tags]. +# +# There are three ways to provide the required binding: +# +# - [Default binding][default binding]. +# - [Local binding][local binding]. +# - [Augmented binding][augmented binding] +# +# ### Default Binding +# +# When you pass no `binding` argument to method #result, +# the method uses its default binding: the one returned by method #new_toplevel. +# This binding has the bindings defined by Ruby itself, +# which are those for Ruby's constants and variables. +# +# That binding is sufficient for an expression tag that refers only to Ruby's constants and variables; +# these expression tags refer only to Ruby's global constant `RUBY_COPYRIGHT` and global variable `$0`: # -# Thanks for your patience. +# ``` +# template = <<TEMPLATE +# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>. +# The current process is <%= $0 %>. +# TEMPLATE +# puts ERB.new(template).result +# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto". +# The current process is irb. +# ``` +# +# (The current process is `irb` because that's where we're doing these examples!) +# +# ### Local Binding +# +# The default binding is *not* sufficient for an expression +# that refers to a a constant or variable that is not defined there: +# +# ``` +# Foo = 1 # Defines local constant Foo. +# foo = 2 # Defines local variable foo. +# template = <<TEMPLATE +# The current value of constant Foo is <%= Foo %>. +# The current value of variable foo is <%= foo %>. +# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>. +# The current process is <%= $0 %>. +# TEMPLATE +# erb = ERB.new(template) +# ``` +# +# This call below raises `NameError` because although `Foo` and `foo` are defined locally, +# they are not defined in the default binding: +# +# ``` +# erb.result # Raises NameError. +# ``` +# +# To make the locally-defined constants and variables available, +# you can call #result with the local binding: +# +# ``` +# puts erb.result(binding) +# The current value of constant Foo is 1. +# The current value of variable foo is 2. +# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto". +# The current process is irb. +# ``` +# +# ### Augmented Binding +# +# Another way to make variable bindings (but not constant bindings) available +# is to use method #result_with_hash(hash); +# the passed hash has name/value pairs that are to be used to define and assign variables +# in a copy of the default binding: +# +# ``` +# template = <<TEMPLATE +# The current value of variable bar is <%= bar %>. +# The current value of variable baz is <%= baz %>. +# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>. +# The current process is <%= $0 %>. +# TEMPLATE +# erb = ERB.new(template) +# ``` +# +# Both of these calls raise `NameError`, because `bar` and `baz` +# are not defined in either the default binding or the local binding. +# +# ``` +# puts erb.result # Raises NameError. +# puts erb.result(binding) # Raises NameError. +# ``` +# +# This call passes a hash that causes `bar` and `baz` to be defined +# in a new binding (derived from #new_toplevel): +# +# ``` +# hash = {bar: 3, baz: 4} +# puts erb.result_with_hash(hash) +# The current value of variable bar is 3. +# The current value of variable baz is 4. +# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto". +# The current process is irb. +# ``` +# +# ## Tags +# +# The examples above use expression tags. +# These are the tags available in \ERB: +# +# - [Expression tag][expression tags]: the tag contains a Ruby expression; +# in the result, the entire tag is to be replaced with the run-time value of the expression. +# - [Execution tag][execution tags]: the tag contains Ruby code; +# in the result, the entire tag is to be replaced with the run-time value of the code. +# - [Comment tag][comment tags]: the tag contains comment code; +# in the result, the entire tag is to be omitted. +# +# ### Expression Tags +# +# You can embed a Ruby expression in a template using an *expression tag*. +# +# Its syntax is `<%= _expression_ %>`, +# where *expression* is any valid Ruby expression. +# +# When you call method #result, +# the method evaluates the expression and replaces the entire expression tag with the expression's value: +# +# ``` +# ERB.new('Today is <%= Date::DAYNAMES[Date.today.wday] %>.').result +# # => "Today is Monday." +# ERB.new('Tomorrow will be <%= Date::DAYNAMES[Date.today.wday + 1] %>.').result +# # => "Tomorrow will be Tuesday." +# ERB.new('Yesterday was <%= Date::DAYNAMES[Date.today.wday - 1] %>.').result +# # => "Yesterday was Sunday." +# ``` +# +# Note that whitespace before and after the expression +# is allowed but not required, +# and that such whitespace is stripped from the result. +# +# ``` +# ERB.new('My appointment is on <%=Date::DAYNAMES[Date.today.wday + 2]%>.').result +# # => "My appointment is on Wednesday." +# ERB.new('My appointment is on <%= Date::DAYNAMES[Date.today.wday + 2] %>.').result +# # => "My appointment is on Wednesday." +# ``` +# +# ### Execution Tags +# +# You can embed Ruby executable code in template using an *execution tag*. +# +# Its syntax is `<% _code_ %>`, +# where *code* is any valid Ruby code. +# +# When you call method #result, +# the method executes the code and removes the entire execution tag +# (generating no text in the result): +# +# ``` +# ERB.new('foo <% Dir.chdir("C:/") %> bar').result # => "foo bar" +# ``` +# +# Whitespace before and after the embedded code is optional: +# +# ``` +# ERB.new('foo <%Dir.chdir("C:/")%> bar').result # => "foo bar" +# ``` +# +# You can interleave text with execution tags to form a control structure +# such as a conditional, a loop, or a `case` statements. +# +# Conditional: +# +# ``` +# template = <<TEMPLATE +# <% if verbosity %> +# An error has occurred. +# <% else %> +# Oops! +# <% end %> +# TEMPLATE +# erb = ERB.new(template) +# verbosity = true +# erb.result(binding) +# # => "\nAn error has occurred.\n\n" +# verbosity = false +# erb.result(binding) +# # => "\nOops!\n\n" +# ``` +# +# Note that the interleaved text may itself contain expression tags: +# +# Loop: +# +# ``` +# template = <<TEMPLATE +# <% Date::ABBR_DAYNAMES.each do |dayname| %> +# <%= dayname %> +# <% end %> +# TEMPLATE +# ERB.new(template).result +# # => "\nSun\n\nMon\n\nTue\n\nWed\n\nThu\n\nFri\n\nSat\n\n" +# ``` +# +# Other, non-control, lines of Ruby code may be interleaved with the text, +# and the Ruby code may itself contain regular Ruby comments: +# +# ``` +# template = <<TEMPLATE +# <% 3.times do %> +# <%= Time.now %> +# <% sleep(1) # Let's make the times different. %> +# <% end %> +# TEMPLATE +# ERB.new(template).result +# # => "\n2025-09-09 11:36:02 -0500\n\n\n2025-09-09 11:36:03 -0500\n\n\n2025-09-09 11:36:04 -0500\n\n\n" +# ``` +# +# The execution tag may also contain multiple lines of code: +# +# ``` +# template = <<TEMPLATE +# <% +# (0..2).each do |i| +# (0..2).each do |j| +# %> +# * <%=i%>,<%=j%> +# <% +# end +# end +# %> +# TEMPLATE +# ERB.new(template).result +# # => "\n* 0,0\n\n* 0,1\n\n* 0,2\n\n* 1,0\n\n* 1,1\n\n* 1,2\n\n* 2,0\n\n* 2,1\n\n* 2,2\n\n" +# ``` +# +# #### Shorthand Format for Execution Tags +# +# You can use keyword argument `trim_mode: '%'` to enable a shorthand format for execution tags; +# this example uses the shorthand format `% _code_` instead of `<% _code_ %>`: +# +# ``` +# template = <<TEMPLATE +# % priorities.each do |priority| +# * <%= priority %> +# % end +# TEMPLATE +# erb = ERB.new(template, trim_mode: '%') +# priorities = [ 'Run Ruby Quiz', +# 'Document Modules', +# 'Answer Questions on Ruby Talk' ] +# puts erb.result(binding) +# * Run Ruby Quiz +# * Document Modules +# * Answer Questions on Ruby Talk +# ``` +# +# Note that in the shorthand format, the character `'%'` must be the first character in the code line +# (no leading whitespace). +# +# #### Suppressing Unwanted Blank Lines +# +# With keyword argument `trim_mode` not given, +# all blank lines go into the result: +# +# ``` +# template = <<TEMPLATE +# <% if true %> +# <%= RUBY_VERSION %> +# <% end %> +# TEMPLATE +# ERB.new(template).result.lines.each {|line| puts line.inspect } +# "\n" +# "3.4.5\n" +# "\n" +# ``` +# +# You can give `trim_mode: '-'`, you can suppress each blank line +# whose source line ends with `-%>` (instead of `%>`): +# +# ``` +# template = <<TEMPLATE +# <% if true -%> +# <%= RUBY_VERSION %> +# <% end -%> +# TEMPLATE +# ERB.new(template, trim_mode: '-').result.lines.each {|line| puts line.inspect } +# "3.4.5\n" +# ``` +# +# It is an error to use the trailing `'-%>'` notation without `trim_mode: '-'`: +# +# ``` +# ERB.new(template).result.lines.each {|line| puts line.inspect } # Raises SyntaxError. +# ``` +# +# #### Suppressing Unwanted Newlines +# +# Consider this template: +# +# ``` +# template = <<TEMPLATE +# <% RUBY_VERSION %> +# <%= RUBY_VERSION %> +# foo <% RUBY_VERSION %> +# foo <%= RUBY_VERSION %> +# TEMPLATE +# ``` +# +# With keyword argument `trim_mode` not given, all newlines go into the result: +# +# ``` +# ERB.new(template).result.lines.each {|line| puts line.inspect } +# "\n" +# "3.4.5\n" +# "foo \n" +# "foo 3.4.5\n" +# ``` +# +# You can give `trim_mode: '>'` to suppress the trailing newline +# for each line that ends with `'%>'` (regardless of its beginning): +# +# ``` +# ERB.new(template, trim_mode: '>').result.lines.each {|line| puts line.inspect } +# "3.4.5foo foo 3.4.5" +# ``` +# +# You can give `trim_mode: '<>'` to suppress the trailing newline +# for each line that both begins with `'<%'` and ends with `'%>'`: +# +# ``` +# ERB.new(template, trim_mode: '<>').result.lines.each {|line| puts line.inspect } +# "3.4.5foo \n" +# "foo 3.4.5\n" +# ``` +# +# #### Combining Trim Modes +# +# You can combine certain trim modes: +# +# - `'%-'`: Enable shorthand and omit each blank line ending with `'-%>'`. +# - `'%>'`: Enable shorthand and omit newline for each line ending with `'%>'`. +# - `'%<>'`: Enable shorthand and omit newline for each line starting with `'<%'` and ending with `'%>'`. +# +# ### Comment Tags +# +# You can embed a comment in a template using a *comment tag*; +# its syntax is `<%# _text_ %>`, +# where *text* is the text of the comment. +# +# When you call method #result, +# it removes the entire comment tag +# (generating no text in the result). +# +# Example: +# +# ``` +# template = 'Some stuff;<%# Note to self: figure out what the stuff is. %> more stuff.' +# ERB.new(template).result # => "Some stuff; more stuff." +# ``` +# +# A comment tag may appear anywhere in the template. +# +# Note that the beginning of the tag must be `'<%#'`, not `'<% #'`. +# +# In this example, the tag begins with `'<% #'`, and so is an execution tag, not a comment tag; +# the cited code consists entirely of a Ruby-style comment (which is of course ignored): +# +# ``` +# ERB.new('Some stuff;<% # Note to self: figure out what the stuff is. %> more stuff.').result +# # => "Some stuff;" +# ``` +# +# ## Encodings +# +# An \ERB object has an [encoding][encoding], +# which is by default the encoding of the template string; +# the result string will also have that encoding. # -# James Edward Gray II -# }.gsub(/^ /, '') +# ``` +# template = <<TEMPLATE +# <%# Comment. %> +# TEMPLATE +# erb = ERB.new(template) +# template.encoding # => #<Encoding:UTF-8> +# erb.encoding # => #<Encoding:UTF-8> +# erb.result.encoding # => #<Encoding:UTF-8> +# ``` # -# message = ERB.new(template, trim_mode: "%<>") +# You can specify a different encoding by adding a [magic comment][magic comments] +# at the top of the given template: # -# # Set up template data. -# to = "Community Spokesman <spokesman@ruby_community.org>" -# priorities = [ "Run Ruby Quiz", -# "Document Modules", -# "Answer Questions on Ruby Talk" ] +# ``` +# template = <<TEMPLATE +# <%#-*- coding: Big5 -*-%> +# <%# Comment. %> +# TEMPLATE +# erb = ERB.new(template) +# template.encoding # => #<Encoding:UTF-8> +# erb.encoding # => #<Encoding:Big5> +# erb.result.encoding # => #<Encoding:Big5> +# ``` # -# # Produce result. -# email = message.result -# puts email +# ## Error Reporting # -# <i>Generates:</i> +# Consider this template (containing an error): # -# From: James Edward Gray II <james@grayproductions.net> -# To: Community Spokesman <spokesman@ruby_community.org> -# Subject: Addressing Needs +# ``` +# template = '<%= nosuch %>' +# erb = ERB.new(template) +# ``` # -# Community: +# When \ERB reports an error, +# it includes a file name (if available) and a line number; +# the file name comes from method #filename, the line number from method #lineno. # -# Just wanted to send a quick note assuring that your needs are being addressed. +# Initially, those values are `nil` and `0`, respectively; +# these initial values are reported as `'(erb)'` and `1`, respectively: # -# I want you to know that my team will keep working on the issues, especially: +# ``` +# erb.filename # => nil +# erb.lineno # => 0 +# erb.result +# (erb):1:in '<main>': undefined local variable or method 'nosuch' for main (NameError) +# ``` # -# * Run Ruby Quiz -# * Document Modules -# * Answer Questions on Ruby Talk +# You can use methods #filename= and #lineno= to assign values +# that are more meaningful in your context: # -# Thanks for your patience. +# ``` +# erb.filename = 't.txt' +# erb.lineno = 555 +# erb.result +# t.txt:556:in '<main>': undefined local variable or method 'nosuch' for main (NameError) +# ``` # -# James Edward Gray II +# You can use method #location= to set both values: # -# === Ruby in HTML +# ``` +# erb.location = ['u.txt', 999] +# erb.result +# u.txt:1000:in '<main>': undefined local variable or method 'nosuch' for main (NameError) +# ``` # -# ERB is often used in <tt>.rhtml</tt> files (HTML with embedded Ruby). Notice the need in -# this example to provide a special binding when the template is run, so that the instance -# variables in the Product object can be resolved. +# ## Plain Text with Embedded Ruby # -# require "erb" +# Here's a plain-text template; +# it uses the literal notation `'%q{ ... }'` to define the template +# (see [%q literals][%q literals]); +# this avoids problems with backslashes. # -# # Build template data class. -# class Product -# def initialize( code, name, desc, cost ) -# @code = code -# @name = name -# @desc = desc -# @cost = cost +# ``` +# template = %q{ +# From: James Edward Gray II <james@grayproductions.net> +# To: <%= to %> +# Subject: Addressing Needs # -# @features = [ ] -# end +# <%= to[/\w+/] %>: # -# def add_feature( feature ) -# @features << feature -# end +# Just wanted to send a quick note assuring that your needs are being +# addressed. # -# # Support templating of member data. -# def get_binding -# binding -# end +# I want you to know that my team will keep working on the issues, +# especially: # -# # ... -# end +# <%# ignore numerous minor requests -- focus on priorities %> +# % priorities.each do |priority| +# * <%= priority %> +# % end # -# # Create template. -# template = %{ -# <html> -# <head><title>Ruby Toys -- <%= @name %></title></head> -# <body> +# Thanks for your patience. # -# <h1><%= @name %> (<%= @code %>)</h1> -# <p><%= @desc %></p> +# James Edward Gray II +# } +# ``` # -# <ul> -# <% @features.each do |f| %> -# <li><b><%= f %></b></li> -# <% end %> -# </ul> +# The template will need these: # -# <p> -# <% if @cost < 10 %> -# <b>Only <%= @cost %>!!!</b> -# <% else %> -# Call for a price, today! -# <% end %> -# </p> +# ``` +# to = 'Community Spokesman <spokesman@ruby_community.org>' +# priorities = [ 'Run Ruby Quiz', +# 'Document Modules', +# 'Answer Questions on Ruby Talk' ] +# ``` # -# </body> -# </html> -# }.gsub(/^ /, '') +# Finally, create the \ERB object and get the result # -# rhtml = ERB.new(template) +# ``` +# erb = ERB.new(template, trim_mode: '%<>') +# puts erb.result(binding) # -# # Set up template data. -# toy = Product.new( "TZ-1002", -# "Rubysapien", -# "Geek's Best Friend! Responds to Ruby commands...", -# 999.95 ) -# toy.add_feature("Listens for verbal commands in the Ruby language!") -# toy.add_feature("Ignores Perl, Java, and all C variants.") -# toy.add_feature("Karate-Chop Action!!!") -# toy.add_feature("Matz signature on left leg.") -# toy.add_feature("Gem studded eyes... Rubies, of course!") +# From: James Edward Gray II <james@grayproductions.net> +# To: Community Spokesman <spokesman@ruby_community.org> +# Subject: Addressing Needs # -# # Produce result. -# rhtml.run(toy.get_binding) +# Community: # -# <i>Generates (some blank lines removed):</i> +# Just wanted to send a quick note assuring that your needs are being +# addressed. # -# <html> -# <head><title>Ruby Toys -- Rubysapien</title></head> -# <body> +# I want you to know that my team will keep working on the issues, +# especially: # -# <h1>Rubysapien (TZ-1002)</h1> -# <p>Geek's Best Friend! Responds to Ruby commands...</p> +# * Run Ruby Quiz +# * Document Modules +# * Answer Questions on Ruby Talk # -# <ul> -# <li><b>Listens for verbal commands in the Ruby language!</b></li> -# <li><b>Ignores Perl, Java, and all C variants.</b></li> -# <li><b>Karate-Chop Action!!!</b></li> -# <li><b>Matz signature on left leg.</b></li> -# <li><b>Gem studded eyes... Rubies, of course!</b></li> -# </ul> +# Thanks for your patience. # -# <p> -# Call for a price, today! -# </p> +# James Edward Gray II +# ``` # -# </body> -# </html> +# ## HTML with Embedded Ruby # +# This example shows an HTML template. # -# == Notes +# First, here's a custom class, `Product`: # -# There are a variety of templating solutions available in various Ruby projects. -# For example, RDoc, distributed with Ruby, uses its own template engine, which -# can be reused elsewhere. +# ``` +# class Product +# def initialize(code, name, desc, cost) +# @code = code +# @name = name +# @desc = desc +# @cost = cost +# @features = [] +# end +# +# def add_feature(feature) +# @features << feature +# end # -# Other popular engines could be found in the corresponding -# {Category}[https://www.ruby-toolbox.com/categories/template_engines] of -# The Ruby Toolbox. +# # Support templating of member data. +# def get_binding +# binding +# end +# +# end +# ``` +# +# The template below will need these values: +# +# ``` +# toy = Product.new('TZ-1002', +# 'Rubysapien', +# "Geek's Best Friend! Responds to Ruby commands...", +# 999.95 +# ) +# toy.add_feature('Listens for verbal commands in the Ruby language!') +# toy.add_feature('Ignores Perl, Java, and all C variants.') +# toy.add_feature('Karate-Chop Action!!!') +# toy.add_feature('Matz signature on left leg.') +# toy.add_feature('Gem studded eyes... Rubies, of course!') +# ``` +# +# Here's the HTML: +# +# ``` +# template = <<TEMPLATE +# <html> +# <head><title>Ruby Toys -- <%= @name %></title></head> +# <body> +# <h1><%= @name %> (<%= @code %>)</h1> +# <p><%= @desc %></p> +# <ul> +# <% @features.each do |f| %> +# <li><b><%= f %></b></li> +# <% end %> +# </ul> +# <p> +# <% if @cost < 10 %> +# <b>Only <%= @cost %>!!!</b> +# <% else %> +# Call for a price, today! +# <% end %> +# </p> +# </body> +# </html> +# TEMPLATE +# ``` +# +# Finally, create the \ERB object and get the result (omitting some blank lines): +# +# ``` +# erb = ERB.new(template) +# puts erb.result(toy.get_binding) +# <html> +# <head><title>Ruby Toys -- Rubysapien</title></head> +# <body> +# <h1>Rubysapien (TZ-1002)</h1> +# <p>Geek's Best Friend! Responds to Ruby commands...</p> +# <ul> +# <li><b>Listens for verbal commands in the Ruby language!</b></li> +# <li><b>Ignores Perl, Java, and all C variants.</b></li> +# <li><b>Karate-Chop Action!!!</b></li> +# <li><b>Matz signature on left leg.</b></li> +# <li><b>Gem studded eyes... Rubies, of course!</b></li> +# </ul> +# <p> +# Call for a price, today! +# </p> +# </body> +# </html> +# ``` +# +# +# ## 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 - Revision = '$Date:: $' # :nodoc: #' - deprecate_constant :Revision - - # Returns revision information for the erb.rb module. + # :markup: markdown + # + # :call-seq: + # self.version -> string + # + # Returns the string \ERB version. 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"] + # :markup: markdown # - # code, enc = compiler.compile("Got <%= obj %>!\n") - # puts code + # :call-seq: + # ERB.new(template, trim_mode: nil, eoutvar: '_erbout') # - # <i>Generates</i>: + # Returns a new \ERB object containing the given string +template+. # - # #coding:UTF-8 - # _erbout=+''; _erbout.<< "Got ".freeze; _erbout.<<(( obj ).to_s); _erbout.<< "!\n".freeze; _erbout + # For details about `template`, its embedded tags, and generated results, see ERB. # - # By default the output is sent to the print method. For example: + # **Keyword Argument `trim_mode`** # - # compiler = ERB::Compiler.new('<>') - # code, enc = compiler.compile("Got <%= obj %>!\n") - # puts code + # You can use keyword argument `trim_mode: '%'` + # to enable the [shorthand format][shorthand format] for execution tags. # - # <i>Generates</i>: + # This value allows [blank line control][blank line control]: # - # #coding:UTF-8 - # print "Got ".freeze; print(( obj ).to_s); print "!\n".freeze + # - `'-'`: Omit each blank line ending with `'%>'`. # - # == Evaluation + # Other values allow [newline control][newline control]: # - # 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!' + # - `'>'`: Omit newline for each line ending with `'%>'`. + # - `'<>'`: Omit newline for each line starting with `'<%'` and ending with `'%>'`. # - # Evaluate using a variable: + # You can also [combine trim modes][combine trim modes]. # - # obj = 'It' - # eval code + # **Keyword Argument `eoutvar`** # - # Evaluate using an input: + # The string value of keyword argument `eoutvar` specifies the name of the variable + # that method #result uses to construct its result string; + # see #src. # - # mod = Module.new - # mod.module_eval %{ - # def get(obj) - # #{code} - # end - # } - # extend mod - # get('It') + # 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. # - # Evaluate using an accessor: + # It's good practice to choose a variable name that begins with an underscore: `'_'`. # - # 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 + # [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 # - # 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 + def initialize(str, trim_mode: nil, eoutvar: '_erbout') + compiler = make_compiler(trim_mode) + set_eoutvar(compiler, eoutvar) + @src, @encoding, @frozen_string = *compiler.compile(str) + @src.freeze + @filename = nil + @lineno = 0 + @_init = self.class.singleton_class end -end -#-- -# ERB -class ERB - # - # Constructs a new ERB object with the template specified in _str_. - # - # An ERB object works by building a chunk of Ruby code that will output - # the completed template when run. + # :markup: markdown # - # If _trim_mode_ is passed a String containing one or more of the following - # modifiers, ERB will adjust its code generation as listed: + # :call-seq: + # make_compiler -> erb_compiler # - # % enables Ruby code processing for lines beginning with % - # <> omit newline for lines starting with <% and ending in %> - # > omit newline for lines ending in %> - # - omit blank lines ending in -%> + # Returns a new ERB::Compiler with the given `trim_mode`; + # for `trim_mode` values, see ERB.new: # - # _eoutvar_ can be used to set the name of the variable ERB will build up - # its output in. 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. Pass the name of the variable to be used inside a String. + # ``` + # ERB.new('').make_compiler(nil) + # # => #<ERB::Compiler:0x000001cff9467678 @insert_cmd="print", @percent=false, @post_cmd=[], @pre_cmd=[], @put_cmd="print", @trim_mode=nil> + # ``` # - # === Example - # - # require "erb" + def make_compiler(trim_mode) + ERB::Compiler.new(trim_mode) + end + + # :markup: markdown # - # # build data class - # class Listings - # PRODUCT = { :name => "Chicken Fried Steak", - # :desc => "A well messages pattie, breaded and fried.", - # :cost => 9.95 } + # 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: # - # attr_reader :product, :price + # ``` + # 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." + # ``` # - # def initialize( product = "", price = "" ) - # @product = product - # @price = price - # end + # In a more readable format: # - # def build - # b = binding - # # create and run templates, filling member data variables - # ERB.new(<<-'END_PRODUCT'.gsub(/^\s+/, ""), trim_mode: "", eoutvar: "@product").result b - # <%= PRODUCT[:name] %> - # <%= PRODUCT[:desc] %> - # END_PRODUCT - # ERB.new(<<-'END_PRICE'.gsub(/^\s+/, ""), trim_mode: "", eoutvar: "@price").result b - # <%= PRODUCT[:name] %> -- <%= PRODUCT[:cost] %> - # <%= PRODUCT[:desc] %> - # END_PRICE - # end - # end + # ``` + # # puts erb.src.split('; ') + # # #coding:UTF-8 + # # _erbout = +'' + # # _erbout.<< "The time is ".freeze + # # _erbout.<<(( Time.now ).to_s) + # # _erbout.<< ".".freeze + # # _erbout + # ``` # - # # setup template data - # listings = Listings.new - # listings.build + # 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`: # - # puts listings.product + "\n" + listings.price + # ``` + # 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 + # ``` # - # _Generates_ + attr_reader :src + + # :markup: markdown # - # Chicken Fried Steak - # A well messages pattie, breaded and fried. + # Returns the encoding of `self`; + # see [Encodings][encodings]: # - # Chicken Fried Steak -- 9.95 - # A well messages pattie, breaded and fried. + # [encodings]: rdoc-ref:ERB@Encodings # - def initialize(str, safe_level=NOT_GIVEN, legacy_trim_mode=NOT_GIVEN, legacy_eoutvar=NOT_GIVEN, trim_mode: nil, eoutvar: '_erbout') - # Complex initializer for $SAFE deprecation at [Feature #14256]. Use keyword arguments to pass trim_mode or eoutvar. - if safe_level != NOT_GIVEN - warn 'Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments.', uplevel: 1 - end - if legacy_trim_mode != NOT_GIVEN - warn 'Passing trim_mode with the 3rd argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, trim_mode: ...) instead.', uplevel: 1 - trim_mode = legacy_trim_mode - end - if legacy_eoutvar != NOT_GIVEN - warn 'Passing eoutvar with the 4th argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, eoutvar: ...) instead.', uplevel: 1 - eoutvar = legacy_eoutvar - end - - 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 - NOT_GIVEN = Object.new - private_constant :NOT_GIVEN - - ## - # Creates a new compiler for ERB. See ERB::Compiler.new for details - - def make_compiler(trim_mode) - ERB::Compiler.new(trim_mode) - end - - # The Ruby code generated by ERB - attr_reader :src - - # The encoding to eval attr_reader :encoding - # The optional _filename_ argument passed to Kernel#eval when the ERB code - # is run + # :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 - # The optional _lineno_ argument passed to Kernel#eval when the ERB code - # is run + # :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 # - # Sets optional filename and line number that will be used in ERB code - # evaluation and error reporting. See also #filename= and #lineno= + # :call-seq: + # location = [filename, lineno] => [filename, lineno] + # location = filename -> filename # - # erb = ERB.new('<%= some_x %>') - # erb.render - # # undefined local variable or method `some_x' - # # from (erb):1 - # - # erb.location = ['file.erb', 3] - # # All subsequent error reporting would use new location - # erb.render - # # undefined local variable or method `some_x' - # # from file.erb:4 + # 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`: # - # Can be used to set _eoutvar_ as described in ERB::new. It's probably - # easier to just use the constructor though, since calling this method - # requires the setup of an ERB _compiler_ object. + # ``` + # template = ERB.new('') + # compiler = template.make_compiler(nil) + # pp compiler + # #<ERB::Compiler:0x000001cff8a9aa00 + # @insert_cmd="print", + # @percent=false, + # @post_cmd=[], + # @pre_cmd=[], + # @put_cmd="print", + # @trim_mode=nil> + # template.set_eoutvar(compiler, '_foo') # => ["_foo"] + # pp compiler + # #<ERB::Compiler:0x000001cff8a9aa00 + # @insert_cmd="_foo.<<", + # @percent=false, + # @post_cmd=["_foo"], + # @pre_cmd=["_foo = +''"], + # @put_cmd="_foo.<<", + # @trim_mode=nil> + # ``` # def set_eoutvar(compiler, eoutvar = '_erbout') compiler.put_cmd = "#{eoutvar}.<<" @@ -885,18 +977,34 @@ class ERB compiler.post_cmd = [eoutvar] end - # Generate results and print them. (see ERB#result) + # :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]. # - # Executes the generated ERB code to produce a completed template, returning - # the results of that code. (See ERB::new for details on how this process - # can be affected by _safe_level_.) + # See also #result_with_hash. # - # _b_ accepts a Binding object which is used to set the context of - # code evaluation. + # [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) @@ -905,8 +1013,18 @@ class ERB eval(@src, b, (@filename || '(erb)'), @lineno) end - # Render a template on a new toplevel binding with local variables specified - # by a Hash object. + # :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| @@ -915,10 +1033,22 @@ class ERB result(b) end - ## - # Returns a new binding each time *near* TOPLEVEL_BINDING for runs that do - # not specify a binding. - + # :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 @@ -931,149 +1061,119 @@ class ERB end private :new_toplevel - # Define _methodname_ as instance method of _mod_ from compiled Ruby source. + # :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" + # ``` # - # example: - # filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml - # erb = ERB.new(File.read(filename)) - # erb.def_method(MyClass, 'render(arg1, arg2)', filename) - # print MyClass.new.render('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 - # Create unnamed module, define _methodname_ as instance method of it, and return it. - # - # example: - # filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml - # erb = ERB.new(File.read(filename)) - # erb.filename = filename - # MyModule = erb.def_module('render(arg1, arg2)') - # class MyClass - # include MyModule - # 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 - # Define unnamed class which has _methodname_ as instance method, and return it. + # :markup: markdown # - # example: - # class MyClass_ - # def initialize(arg1, arg2) - # @arg1 = arg1; @arg2 = arg2 - # end - # end - # filename = 'example.rhtml' # @arg1 and @arg2 are used in example.rhtml - # erb = ERB.new(File.read(filename)) - # erb.filename = filename - # MyClass = erb.def_class(MyClass_, 'render()') - # print MyClass.new('foo', 123).render() - def def_class(superklass=Object, methodname='result') - cls = Class.new(superklass) - def_method(cls, methodname, @filename || '(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 + # :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 = <<TEMPLATE + # <html> + # <body> + # <p><%= @arg1 %></p> + # <p><%= @arg2 %></p> + # </body> + # </html> + # 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 - # print MyClass.new([10,20,30]).render() + # end + # ``` # - # result: + # Use method #def_class to create a subclass that has method `:render`: # - # <b>10</b> + # ``` + # MySubClass = template.def_class(MyBaseClass, :render) + # ``` # - # <b>20</b> + # Generate the result: # - # <b>30</b> + # ``` + # puts MySubClass.new('foo', 123).render + # <html> + # <body> + # <p>foo</p> + # <p>123</p> + # </body> + # </html> + # ``` # - 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 + def def_class(superklass=Object, methodname='result') + cls = Class.new(superklass) + def_method(cls, methodname, @filename || '(ERB)') + cls end end |
