#puts inside ERB

G

Gavin Kistner

I see this has been suggested before, in [ruby-talk:126986] and [ruby-
talk:127097], but with no followup reports of success or failure.

How would I patch ERB so that puts and print were aliased to
concatenate onto the current eoutvar? This aliasing should be setup
at the beginning of #result, and removed at the end of #result.

Has anyone tried this already? Is there any compelling reason why
this shouldn't be done? (I know I was bitten by this a lot as an ERB
newb, and I've seen many others with similar complaints.) For the
rare case where stdout is needed (or any other IO) an explicit call
like $stdout.puts can be used instead.
 
G

Gavin Kistner

How would I patch ERB so that puts and print were aliased to
concatenate onto the current eoutvar? This aliasing should be setup
at the beginning of #result, and removed at the end of #result.

Here's my horrific hack that does it. It's particularly hacky because
it requires that the 'eoutvar' be set to have a @ at the beginning of
the name (an instance variable). The real solution I suppose requires
a change to the ERB::Compiler#compile method, to replace calls to
puts and print with the local variable concatenation inline. But that
was beyond my foo.

#in the ERB class
def initialize(str, safe_level=nil, trim_mode=nil,
eoutvar='_erbout')
@safe_level = safe_level
compiler = ERB::Compiler.new(trim_mode)
set_eoutvar(compiler, eoutvar)
@src = <<-ENDIOBORK
module Kernel
alias_method :_oldputs, :puts
alias_method :_oldprint, :print
def puts(s)
#{eoutvar}<<s+"\\n"
end
def print(s)
#{eoutvar}<<s
end
end
ENDIOBORK
@src << compiler.compile(str)
@src << "\n" + <<-FIXIOBORK
module Kernel
alias_method :puts, :_oldputs
alias_method :print, :_oldprint
end
#{eoutvar}
FIXIOBORK
@filename = nil
end
 
M

Mark Hubbart

=20
Here's my horrific hack that does it. It's particularly hacky because
it requires that the 'eoutvar' be set to have a @ at the beginning of
the name (an instance variable). The real solution I suppose requires
a change to the ERB::Compiler#compile method, to replace calls to
puts and print with the local variable concatenation inline. But that
was beyond my foo.

<snip>

How about this? Still somewhat hacky, but maybe less breakable. It
should work unless you supply a binding in the Kernel module context;
the new #puts is defined in Object.

-----

require 'erb'

class Object
# Object's puts overrides Kernel's.
# Use a thread-local default output if available.
def puts(*args)
if Thread.current[:default_output]
Thread.current[:default_output].puts(*args)
else
super
end
end
# Probably should do the same with Kernel#print, #p, etc.
end

# This module turns concatenationability (<<) into writability.
# We'll use it on the eoutvar to let it be used as an IO, too.
module Writeable
def write(other)
self << other
other.size
end
def puts(*other)
other.each{|o| self << (o[-1] =3D=3D ?\n ? o : o + "\n") }
nil
end
def print(other)
self << other
nil
end
end

class ERB
def set_eoutvar(compiler, eoutvar =3D '_erbout')
compiler.put_cmd =3D "#{eoutvar}.concat"

cmd =3D []
cmd.push "#{eoutvar} =3D ''"
cmd.push "#{eoutvar}.extend Writeable"
cmd.push "Thread.current[:default_output] =3D #{eoutvar}"
=20
compiler.pre_cmd =3D cmd

cmd =3D []
cmd.push "Thread.current[:default_output] =3D nil"
cmd.push(eoutvar)

compiler.post_cmd =3D cmd
end
end
 
L

Lyndon Samson

------=_Part_6511_30690214.1124445811218
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

Maybe replace _erbout with an IO object ( including StringIO? )

then you could have explicit _erbout.puts calls

------=_Part_6511_30690214.1124445811218--
 
G

Gavin Kistner

Maybe replace _erbout with an IO object ( including StringIO? )

then you could have explicit _erbout.puts calls

It already works to have explcit _erbout.concat or _erbout.<< calls;
the desire here is not one of functionality, but instead of having
the raw print and puts methods work as many people expect them to
inside an ERB template.
 
G

Gavin Kistner

--Apple-Mail-3-125934299
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
delsp=yes;
format=flowed

How about this? Still somewhat hacky, but maybe less breakable. It
should work unless you supply a binding in the Kernel module context;
the new #puts is defined in Object.

Tricky! I moved the module into ERB::Writeable.

Despite your super calls, I used Object.class_eval to dynamically
override #puts, #print and #p at the start of ERB#result, and then
undefs those methods at the end of #result. It's sure to be more of a
performance hit (though I'll have to benchmark to see how much it
affects things) but I wanted to really leave the house as clean when
I left as when I entered.

Oh, I also needed to add some .to_s calls inside Writeable's methods,
to handle non-string arguments.

In summary (for the google-able archives): using Mark's technique, I
have made a patched version of ERB which causes calls to puts, print,
and p inside an ERB to place their information into the ERB output
string, instead of immediately sending the results to $stdout. (If
you use this patch but need some debug information in your ERB
template, you can still use $stdout.puts to hide output from the ERB
string.)

You can download the patched file from http://phrogz.net/RubyLibs/
erb_1.8.2_with_puts.rb.gz
After ungzipping, the file should be renamed to 'erb.rb' and replace
the file in (for a standard install):
/usr/local/lib/ruby/1.8/erb.rb


Finally, for the naysayers (and the archive), this functionality
(using Ruby code inside an ERB template to inject strings into the
ERB template in place without using <%=...%>) can be achieved without
the above patch by using the "eoutvar" option for ERB. By default,
this is a local variable named "_erbout", but you can name it
whatever you wish by modifying the fourth parameter to ERB.new. For
example:

require 'erb'

template1 = <<'ENDTEMPLATE'
Hello
<% 1.upto(9){ |i|
_erbout << "\t#{i} x #{i} = #{i*i}"
_erbout << "\n" unless i==9
} %>
Goodbye
ENDTEMPLATE
ERB.new( template1 ).run

template2 = <<'ENDTEMPLATE'
Hello
<% 1.upto(9){ |i|
OUTPUT << "\t#{i} x #{i} = #{i*i}"
OUTPUT << "\n" unless i==9
} %>
Goodbye
ENDTEMPLATE
ERB.new( template2, nil, nil, 'OUTPUT' ).run


Both of the above output:
Hello
1 x 1 = 1
2 x 2 = 4
3 x 3 = 9
4 x 4 = 16
5 x 5 = 25
6 x 6 = 36
7 x 7 = 49
8 x 8 = 64
9 x 9 = 81
Goodbye
--Apple-Mail-3-125934299--
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top