Beefy Boxes and Bandwidth Generously Provided by pair Networks
Come for the quick hacks, stay for the epiphanies.
 
PerlMonks  

overloading the print function (or alternatives)

by nate (Monk)
on Dec 04, 2000 at 09:45 UTC ( [id://44744]=perlquestion: print w/replies, xml ) Need Help??

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

Replies are listed 'Best First'.
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.

      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
        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.
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.
Re: overloading the print function (or alternatives)
by merlyn (Sage) on Dec 04, 2000 at 10:18 UTC
Re: overloading the print function (or alternatives)
by Adam (Vicar) on Dec 04, 2000 at 10:16 UTC
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!

      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

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

      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

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://44744]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (6)
As of 2024-05-20 17:53 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found