Class: ERBook::Template
- ERB
- ERBook::Template
An eRuby template which allows access to the underlying result buffer (which contains the result of template evaluation thus far) and provides sandboxing for isolated template rendering.
In addition to the standard <% eRuby %> directives, this template supports:
- Lines that begin with ’%’ are treated as normal eRuby directives.
- Include directives (<%#include YOUR_PATH #%>) are replaced by the
result of reading and evaluating the YOUR_PATH file in the current context.
- Unless YOUR_PATH is an absolute path, it is treated as being relative to the file which contains the include directive.
- Errors originating from included files are given a proper stack trace which shows the chain of inclusion plus any further trace steps originating from the included file itself.
- eRuby directives delimiting Ruby blocks (<% … do %> … <% end %>) can be heirarchically unindented by the crown margin of the opening (<% … do %>) delimiter.
Attributes
Instance Attributes
buffer | [RW] | public |
The result of template evaluation thus far. |
---|
Constructor Summary
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/erbook/template.rb', line 47 def initialize source, input, unindent = false, safe_level = nil # expand all "include" directives in the input = lambda do |src_file, src_text, path_stack, stack_trace| src_path = File.(src_file) src_line = 1 # line number of the current include directive in src_file chunks = src_text.split(/<%#\s*include\s+(.+?)\s*#%>/) path_stack.push src_path chunks.each_with_index do |chunk, i| # even number: chunk is not an include directive if i & 1 == 0 src_line += chunk.count("\n") # odd number: chunk is the target of the include directive else # resolve correct path of target file dst_file = chunk unless Pathname.new(dst_file).absolute? # target is relative to the file in # which the include directive exists dst_file = File.join(File.dirname(src_file), dst_file) end dst_path = File.(dst_file) # include the target file if path_stack.include? dst_file raise "Cannot include #{dst_file.inspect} at #{src_file.inspect}:#{src_line} because that would cause an infinite loop in the inclusion stack: #{path_stack.inspect}." else stack_trace.push "#{src_path}:#{src_line}" dst_text = eval('File.read dst_file', binding, src_file, src_line) # recursively expand any include directives within # the expansion of the current include directive dst_text = [dst_file, dst_text, path_stack, stack_trace] # provide more accurate stack trace for # errors originating from included files line_var = "__erbook_var_#{dst_file.object_id.abs}__" dst_text = %{<% #{line_var} = __LINE__ + 2 # content is 2 newlines below begin %>#{dst_text}<% rescue Exception => err bak = err.backtrace top = [] found_top = false prev_line = nil bak.each do |step| if step =~ /^#{/#{source}/}:(\\d+)(.*)/ line, desc = $1, $2 line = line.to_i - #{line_var} + 1 if line > 0 and line != prev_line top << "#{dst_path}:\#{line}\#{desc}" found_top = true prev_line = line end elsif !found_top top << step end end if found_top bak.replace top bak.concat #{stack_trace.reverse.inspect} end raise err end %>} stack_trace.pop end chunks[i] = dst_text end end path_stack.pop chunks.join end input = [source, input, [], []] # convert "% at beginning of line" usage into <% normal %> usage input.gsub! %r{^([ \t]*)(%[=# \t].*)$}, '\1<\2 %>' input.gsub! %r{^([ \t]*)%%}, '\1%' # unindent node content hierarchically if unindent = input.scan(/<%(?:.(?!<%))*?%>/m) margins = [] result = [] buffer = input .each do |tag| chunk, buffer = buffer.split(tag, 2) chunk << tag # perform unindentation result << chunk.gsub(/^#{margins.last}/, '') # prepare for next unindentation case tag when /<%[^%=].*?\bdo\b.*?%>/m margins.push buffer[/^[ \t]*(?=\S)/] when /<%\s*end\s*%>/m margins.pop end end result << buffer input = result.join end # silence the code-only <% ... %> directive, just like PHP does input.gsub! %r{^[ \t]*(<%[^%=]((?!<%).)*?[^%]%>)[ \t]*\r?\n}m, '\1' # use @buffer to store the result of the ERB template super input, safe_level, nil, :@buffer self.filename = source end |
Public Visibility
Public Instance Method Summary
#render_with(inst_vars = ) |
Renders this template within a fresh object that is populated with the given instance variables, whose names must be prefixed with ’@’. |
---|
Public Instance Method Details
render_with
Renders this template within a fresh object that is populated with the given instance variables, whose names must be prefixed with ’@’.
179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/erbook/template.rb', line 179 def render_with inst_vars = ;{} context = Object.new.instance_eval do inst_vars.each_pair do |var, val| instance_variable_set var, val end binding end result context end |
Protected Visibility
Protected Instance Method Summary
#content_from_block(*block_args) |
Returns the content that the given block wants to append to the buffer. |
---|
Protected Instance Method Details
content_from_block
Returns the content that the given block wants to append to the buffer. If the given block does not want to append to the buffer, then returns the result of invoking the given block.
196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/erbook/template.rb', line 196 def content_from_block *block_args raise ArgumentError, 'block must be given' unless block_given? head = @buffer.length body = yield(*block_args) # this appends 'content' to '@buffer' tail = @buffer.length if tail > head @buffer.slice! head..tail else body end.to_s end |