• Aucun résultat trouvé

A multiprocess pager class

Dans le document Object Oriented Perl (Page 180-184)

Blessing other things

5.3 B LESSING A TYPEGLOB

5.3.2 A multiprocess pager class

Of course, even if you can remember and cater for all of these issues, if the nominated pager program isn't available—if you're not running on a Unix-ish system—the program will die a horrible death without printing anything useful.5 To avoid this nonportability, we can build a class that encapsulates a paging filehandle, but doesn’t rely on external paging programs at all.

Listing 5.5 shows the complete IO::Pager class suitable for any system that implements the built-in fork function.

package IO::Pager;

$VERSION = 1.00;

use strict;

use Carp;

use Symbol;

sub new {

my $class = shift;

my %args = (lines=>23, prompt=>"--More--", endprompt=>"--No more--", @_);

my ($self, $KEYBOARD) = ( gensym, gensym );

open $KEYBOARD, "<&STDIN" or croak "lost contact with keyboard";

my $pid = open $self, "|-";

croak "Could not create pager" unless defined $pid;

_page($KEYBOARD,%args) unless $pid;

return bless $self, $class;

}

sub _page {

my ($KEYBOARD,%args) = @_;

$| = 1;

while (<>) {

4 This can be a trap even for experienced programmers, who may be tempted to insert an explicit wait to ensure that the pager gets a chance to finish. That doesn’t work because, without the end-of-file sent by close, the pager waits for more input from the main program, while the main program waits for the pager to terminate.

5 Hmmm, that is similar to the standard help behavior on certain systems.

Listing 5.5 The pager class

print;

_prompt($args{prompt},$KEYBOARD) || last unless $. % $args{lines};

}

_prompt($args{endprompt}, $KEYBOARD);

exit;

}

sub _prompt {

print $_[0];

return (readline(*{$_[1]}) !~ /^q/i); # Return false if user types 'q' }

sub close {

close $_[0];

}

sub print {

my ($self) = @_;

print $self (@_);

}

sub DESTROY {

$_[0]->close;

} 1;

The constructor does most of the work required to set up the paging mechanism. It first parses the argument list, extracting the class name and the configuration arguments. The ar-guments are passed as a hash-like list of tags and values. These tags/value pairs are

lines => $num, which specifies how many lines should be shown per page before paus-ing and promptpaus-ing to continue;

prompt => $str, which specifies the string to be printed at each pause;

endprompt => $str, which specifies a separate string to be printed once all the data has been paged out.

The defaults are interpolated into the %args hash before the constructor arguments, so that the arguments take precedence. The default values are the standard ones for more. Con-sequently, the following produces a more-ish paging filehandle:

my $PAGER = IO::Pager->new();

In contrast, this constructor call:

my $PAGER = IO::Pager->new(prompt=>": ", endprompt=>"(END)");

creates a pager that mimicks less.

Having determined the style of paging required, the constructor creates two local typeglobs:

my ($self, $KEYBOARD) = (gensym, gensym);

The Symbol::gensym subroutine is a handy utility subroutine that returns a reference to an anonymous typeglob.

Once the two new typeglobs have been created, they are both immediately used, although in very different ways. The filehandle in the $KEYBOARD typeglob is connected to the same in-put stream as STDIN, via an open $KEYBOARD, "<&STDIN". This filehandle will be used to provide the paging process with access to the original input stream, so that it can receive replies to its paging prompts.

This will be necessary because the paging process is created—in the very next state-ment—by opening the filehandle in $self’s typeglob with the magical "|-" output pipe. This open causes the main program to fork and create a pipe to the new child process. That child process has its input stream (i.e. STDIN) connected to one end of the pipe, and the filehandle inside $self is connected to the other. Hence, anything written to the $self filehandle will appear on the standard input of the child process. The child process needs its own copy of the original STDIN (inside $KEYBOARD), since its own STDIN has been taken over by the inter-pro-cess pipe.

The forking open returns zero to the new process and nonzero to the original one. In the child process—where the condition unless$pid is true—the IO::Pager::_page subrou-tine is called to begin the paging process. In the parent process, the unless $pid condition is false, so control skips to the last line of the constructor where the writable end of the inter-process pipe (stored in $self’s typeglob) is blessed as an object of the class, and returned.

The upshot of all that fancy footwork is that the original process that calls IO::Pager:

:new receives a reference to a blessed typeglob containing a filehandle. That filehandle is con-nected to a pipe that leads to the STDIN of another process currently executing the IO::Pag-er::_page subroutine. Figure 5.1 illustrates that long chain of connections.

Perl’s built-in print function is quite smart and knows that, if we give it a typeglob ref-erence (blessed or not), it should send its output to the filehandle inside that typeglob. Now we can write

my $PAGER = IO::Pager->new();

print $PAGER ($long, $text, $to, @be_paged_out);

and print sends the output text to the filehandle referred to by $PAGER. That filehandle routes the text through the pipe and into the STDIN of the child process. All that is then required is to arrange for the child process to grab that incoming data and page it out.

Recall that the constructor left the child process executing the IO::Pager::_page sub-routine. Not surprisingly, that subroutine does nothing but read lines from its STDIN, print them to its STDOUT, and prompt whenever the line count reaches a multiple of a screenful (that is, when $. % $args{lines} is zero).

The helper subroutine IO::Pager::_prompt uses the filehandle in $KEYBOARD to col-lect feedback. Reading from STDIN would not have the desired effect, since it’s now connected to the interprocess pipe, not the keyboard.

The _prompt subroutine also returns a Boolean value indicating whether the user typed

q (or Q) in response to the paging prompt. This is caught in _page’s while loop, allowing paging to terminate early if a quit is requested. It’s easy to imagine _prompt and _page han-dling a much richer set of interactive commands (back-up, save, find, etc.) in a similar manner.

The IO::Pager::print and IO::Pager::close methods exist only to provide an ob-ject-oriented interface for these two standard activities. Their presence makes the following pairs of statements equivalent:

print $PAGER @data;

$PAGER->print(@data);

close $PAGER;

$PAGER->close();

The first version of each of these pairs is not an indirect object method call, but rather a normal call to Perl’s built-in print or close function. In this case, of course, the effect is iden-tical, but it illustrates once again why the indirect object syntax is best avoided.

Finally, the class destructor (IO::Pager::DESTROY) ensures that the filehandle in the blessed typeglob is properly closed before it is finally relinquished. As explained above, the im-plicit wait that this close performs prevents the main process from terminating prematurely and killing the pager process before all the output has been paged.

Using the IO::Pager class, the earlier paging example now becomes

SCALAR

ARRAY

HASH

CODE

IO

FORMAT

$PAGER

SCALAR

ARRAY

HASH

CODE

IO

FORMAT

*STDIN

Parent process Child process

IO::Pager

Figure 5.1 The internal structure of an IO::Pager object

use IO::Pager;

{

my $PAGER = IO::Pager->new();

# Any print to $PAGER in the rest of the block will be paged

# For example:

foreach my $i (1..100) {

print $PAGER "$i\n"; # Each line is paged through &IO::Pager::_page }

}

print "done\n"; # Unpaged

Note in particular that, because the paging typeglob is now blessed into a class with a de-structor, there is no need to remember the vital call to close. It is now automatically invoked by the object’s destructor when the reference finally goes out of scope.

Dans le document Object Oriented Perl (Page 180-184)