nate has asked for the wisdom of the Perl Monks concerning the following question:
Most honorable Monks:
One of the biggest first mistakes (or interface problem) people have
programming in
the Everything Engine
is the use of print commands
in embedded code blocks, when they typically want to
append it to the return string:
[% #typical first time user code
print "hello $USER->{title}"; %]
as opposed to
[% #(one of the) right ways
return "hello $USER->{title}" %]
The former will actually cause problems with some browsers,
because printing to STDOUT prints "hello world" before the content-type header.
Q: Is there a way to override the print function to instead
append to a string? Failing that, could you redirect STDOUT
to a string?
(putting it back in place, of course, before the header is printed) That
seems like a rather ugly hack.
Thank you,
--nate
Re (tilly) 1: overloading the print function (or alternatives)
by tilly (Archbishop) on Dec 04, 2000 at 10:07 UTC
|
I think that this hack is ugly. Not only in that it is
unaesthetic, but in this elementary error lies a good lesson
that you would deprive people of. Getting around
misunderstandings with magic tricks both keeps people from
learning how things really work, but also makes it
harder for them to later on figure out what you did.
But look at perlsub for the section on overriding built-in
global functions. (You can do this per package or for all
packages at once.)
Alternately you can look at perltie and create yourself
a tied filehandle that you select before calling their
code.
Personally rather than making this work as they want, I
would select a tied filehandle that would take print's
and cough and die with an informative error. Then people
could still print to STDERR for debugging output. | [reply] |
|
How about overloading it in a more informative way? Perhaps you could overload print() with something like this (untested):
sub print {
my $string = join '', @_;
if ($string =~ /^(Content-type:)/) {
CORE::print $string;
}
else {
CORE::print "Content-type: text/html\n\n";
die "You used print() rather than returning a string, you naughty
+person";
}
}
Something I DON'T know, however... would one need to prototype this overloaded print() to assure that it would work when used without parenthases, or is that taken care of becuase the built-in print() is already prototyped? You'd probably want to be sure beore doing it this way.
Alan "Hot Pastrami" Bellows | [reply] [d/l] |
|
Then you would mess up someone who had inserted:
print STDERR "Called node 'foo'\n";
as a debugging aid. (The messsage should show up in your
webserver's logs.) The following (untested) code is much
nicer:
package noprint;
use Carp;
sub PRINT {
my $msg = Carp::longmess("You must return rather than print");
print "Content-type: text/plain\n\n$msg";
die $msg;
}
*PRINTF = *PRINT;
sub TIEHANDLE {
return bless ({}, shift);
}
and elsewhere in the code:
tie(*NOPRINT, 'noprint');
select(NOPRINT);
# time passes while the page is built.
# Before spitting out the final page:
select(STDOUT);
That catches the newbie error. Without the potential for headaches that overriding print causes. | [reply] [d/l] [select] |
Re: overloading the print function (or alternatives)
by chipmunk (Parson) on Dec 04, 2000 at 10:42 UTC
|
Personally, I am leery of working around the problem as you are asking. So, here are my ideas, in order of preference, most preferred first.
1. I don't know much about the Everything Engine, but I wonder if it is possible to simply output the content-type header at the beginning of the program, before the programmer's code is called.
2. I would want to educate the users not to make this mistake. Of course, that can be difficult, because every new user may need to be educated about this issue. That can end up being more work than just fixing the problem. :)
3. Overload the print function. I'm not sure offhand whether print is in the list of built-in functions that can be overloaded. Someone else can address this one. :)
4. Tie the output filehandle. Tying a filehandle other than STDOUT and selecting it would be fine, except what if a programmer prints explicitly to STDOUT? (e.g. print STDOUT "hello $USER->{title}")
So, this example that I wrote ties STDOUT. Of course, when tying STDOUT, you have to untie STDOUT before printing to it for real. It may not be the cleanest approach, but it does work.
#!/usr/local/bin/perl -w
use strict;
package DelayPrint;
sub TIEHANDLE {
my $var = '';
bless \$var, shift;
}
sub PRINT {
my $ref = shift;
$$ref .= join( (defined $, ? $, : ''), @_) . (defined $\ ? $\ : ''
+);
}
sub PRINTF {
my $ref = shift;
my $fmt = shift;
$$ref .= sprintf($fmt, @_);
}
# flush: an additional function that returns the contents of the buffe
+r
# called via the object returned by tie()
sub flush {
my $ref = shift;
my $text = $$ref;
$$ref = '';
return $text;
}
package main;
my $fh = tie *STDOUT, 'DelayPrint'; # tie STDOUT
print "Hello world\n"; # print through the tied fileh
+andle
my $text = $fh->flush(); # get the text that was printe
+d
undef $fh; # erase the reference,
untie *STDOUT; # untie the filehandle
print $text; # print the text for real
Refer to perltie for more on tied filehandles and tying in general. | [reply] [d/l] |
Re: overloading the print function (or alternatives)
by merlyn (Sage) on Dec 04, 2000 at 10:18 UTC
|
| [reply] |
Re: overloading the print function (or alternatives)
by Adam (Vicar) on Dec 04, 2000 at 10:16 UTC
|
| [reply] |
Re: overloading the print function (or alternatives)
by chromatic (Archbishop) on Dec 04, 2000 at 21:27 UTC
|
How about a couple of sentences in the manual that say:
Slow down, cowboy! If you're thinking of skipping all of this concatenation rigamarole and just printing directly to the browser, stop. Remember, you're writing a component that has to be evaluated in the context of other components. No one knows what that's going to be until it actually happens. Take a deep breath, type 'my $str;', and you'll save yourself the time of wondering why your web browser seems to break.
It's really a behavioral issue. We can correct this with a filehandle tied to a scalar in the normal parsing mode. Every time evalCode returns, we just have to check for a chunk of CONTAINED_STUFF in the scalar, and put it in the right place. But in compiled mode, there's no good and clean way of doing that. (Mixing in calls to $tied_fh->get_and_clear() does not qualify as clean.)
I say, use the stick and not the carrot! | [reply] |
|
yeah -- about how many perl hackers read the manuals before trying
their kung-fu out? ;)
The biggest problem is the context/header problem, even if
it kicks out a backend error, rather than prepending
to the return string, it's better than getting
a dialog box which asks you where you want to save file "index.pl"...
Of course, that might be a good lesson in using the displaytype=edit param
via the URL...
--nate
| [reply] |
Re: overloading the print function (or alternatives)
by davorg (Chancellor) on Dec 04, 2000 at 14:11 UTC
|
The former will actually cause problems with some
browsers, because printing to STDOUT prints "hello
world" before the content-type header.
I'd like to know some more about this problem. Can you
show me some code that breaks on certain browsers?
As far as I can see, if you have code that does:
print "Content-type: text/html\n\n";
# and then later in the script...
print "<h1>Some title</h1>\n";
Then the second print statement should always display
its output after the first. If that's not true, then
I'd call that a bug.
There is a similar problem, where you have code like
this:
print "Content-type: text/html\n\n";
print "<h1>Header</h1>\n";
# some code that generates an runtime error
In this case, because STDOUT is buffered by
default and STDERR isn't, it is possible that the
errors will be sent to the client before the header,
causing an "invalid headers" error. The solution to this
is to set $| to a true value to unbuffer
STDOUT (or course, you'll still need to fix the
bug that causes the error!).
--
<http://www.dave.org.uk>
"Perl makes the fun jobs fun
and the boring jobs bearable" - me
| [reply] [d/l] [select] |
|
This is also in response to chipmunk's comment.
E* creates the entire page before it prints any output
to STDOUT -- cookies are set in the header (along with the content-type),
and developers need to be able to manipulate cookies on the current page.
This is, of course, what causes the "print" bug -- which
I'm hoping to curb.
thanks,
--nate
| [reply] |
|
|