Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

I'm trying to write a module that allows you to put an array through a routine that will pause it when the screen's full of text. If you type in a b before conitinuing it will go back a screen. If you type a x it will stop printing. However, I'm having a bit of trouble. I can only get it to go back once, and once you type in b you have to hit enter twice. I just started Perl a couple months ago and this is my first time with a module. Here's the code:
package Text::Break; $VERSION = 1.1; sub break() { local ($extra, $num, $extra2, $extra4, @text, $status); local $text = \@_; shift @$text; $text3 = @$text; if ($text3 =~ /,\s/) { ($text, $num) = split ", ", @$text, 2; } else { $num = 24 } local $counter = 1; $status = "yes"; $extra2 = 0; &printer; sub printer() { undef(@text); while ($extra2 <= $#$text) { $text[$extra4] = $$text[$extra2]; $extra2++; $extra4++; } $extra4 = 0; foreach $line (@text) { if ($counter%24==0 && $status == "yes" && $counter != 0){ print "--more--"; $extra = <STDIN>; if ($extra =~ /^[bB]/ && $counter != 24) { $extra2 = $counter - 48; undef($status); goto &printer; goto LAST; } elsif ($extra =~ /^[xX]/) { goto LAST; } } print $line; $counter++; $status = "yes"; } } LAST: if($extra) { } 1; }
thanx for any help, I hope this is easy to understand the module's Text::Break, to call it use the break method. The code should be self-explanitory.

Replies are listed 'Best First'.
Re: Breaking Text
by btrott (Parson) on Aug 20, 2000 at 00:21 UTC
    Your immediate problem is that you're comparing $status with "yes" using a numeric comparison. It should be:
    $status eq "yes"
    instead of
    $status == "yes"
    That should fix this problem that you're having.

    But you have some other weirdnesses in your code, as well:

    • I'd *really* rethink your current system of declaring a named sub within another sub. That's just a recipe for trouble. I'd go with an anonymous closure, which seems to be sort of what you're aiming for, here, but it'll be much cleaner.

    • Rethink the variable names. :) $extra2 and $extra4 tell me nothing as to their purpose.

    • This line:
      $text3 = @$text;
      I don't think that's doing what you think it's doing. To be honest, I don't know what you think it's doing, because in the next line you use a regex on it, checking for a comma. Why?

      I'll tell you what that line does: it dereferences the array @$text, then evaluates that array in scalar context, which returns the length of the array. So $text3 holds the length of @$text. Is that what you wanted?

    • Why are you getting rid of the first line of text?
      shift(@$text);
      Perhaps the text you're expecting is different than the text I was testing with, but that doesn't seem right.

    • There's a much easier way of doing this:
      while ($extra2 <= $#$text) { $text[$extra4] = $$text[$extra2]; $extra2++; $extra4++; }
      Use an array slice:
      @text = @{$text}[$extra2..$#$text];
      The first element of @$text is always Text::Break so I'm just getting rid of that. I'll redo the variable names, I've just kinda added them quickly trying to figure out the problem. I was trying to make it so that someone could call the method like this ->break(@array, 13) and have it break after 13 elements in the array. There's probably a better way to do it (using a pop), but I wasn't sure how, 'cause I wanted to make it optional. I don't understand about the anonymous closure, maybe you could give me an example :) One more thing, you also can't use the back command more than once, help? thanx.
Re: Breaking Text
by chromatic (Archbishop) on Aug 20, 2000 at 01:17 UTC
    Not to be outdone, here's my version. It *is* Object Oriented, because I like blessing things besides hashes. I'll also give an example on how it's used. Note that this does clobber the original array. It's easier to do it without clobbering, and it easy to add a line to fix it.
    #!/usr/bin/perl -w package Text::Break; use strict; use vars qw( $VERSION ); $VERSION = 1.2; # hey, why not? sub new { my $class = shift; my $lines = shift || 24; # a nice default my $self = \$lines; bless($self, $class); return $self; } sub break { my $self = shift; printer($$self, shift); } sub printer() { my $num = shift; my $text_ref = shift; my $status = 1; my @old = (); my $back = 0; my $counter = 0; while (@$text_ref && $status) { foreach my $line (splice @{ $text_ref }, 0, $num) { print $line; # can add \n here if needed $counter++; push @old, $line; } print "-- More --"; my $cont = <STDIN>; if ($cont =~ /^[Bb]/) { $back++; my $num_back = $num * $back; $num_back = $num_back > $counter ? $counter : $num_back; unshift @$text_ref, splice(@old, -$num_back, $num_back); $counter -= $num_back; } elsif ($cont =~ /^[Xx]/) { $status = 0; last; } else { $back--; } } # optional: put back what we've paged # unshift (@$text_ref, @old) if (@old); } 1;
    Strictly speaking, $status isn't necessary, but if you want to add better error handling, you may need it. Here's how to call it:
    #!/usr/bin/perl -w use strict; use Text::Break; die "Need a file!\n" unless (@ARGV); my $tb = Text::Break->new(20); my @arr = <>; $tb->break(\@arr);
Re: Breaking Text
by btrott (Parson) on Aug 20, 2000 at 00:52 UTC
    Here's a version using an anonymous sub. I'm not saying it's perfect, but I think it's probably much more readable, etc. than your initial version. I've changed many of your variable names. :)

    Also, you said that you expect the first argument to be Text::Break; so you're calling this as a class method, then? I'm not sure that's necessary, really, because your routine isn't OO in the least. So I changed that. Mine also expects the array to be passed by reference.

    package Text::Break; $VERSION = 0.01; :) sub break { my($text, $how_many) = @_; $how_many ||= 24; my $total = @$text; ## Anon sub $printer is a closure and has access ## to the $text array reference. Yes, there's really ## no point to having an anon sub here; I may as ## well just write the print statement inline. Oh well. my $printer = sub { my($start, $how_many) = @_; print @{$text}[$start..$start+$how_many-1]; }; my $printed = 0; ## Separate the printing from the input, etc. PAGER: while ($printed <= $total) { $printed = 0 unless $printed >= 0; $printer->($printed, $how_many); $printed += $how_many; print $printed >= $total ? "--No More--" : "--More--"; my $in = <STDIN>; if ($in =~ /^b/i) { $printed -= $how_many * 2; } elsif ($in =~ /^x/i || $in =~ /^q/i) { last PAGER; } } }
    To be invoked like this:
    Text::Break::break(\@text, [ $lines ]);
    where $lines is an optional argument specifying the number of lines to break on.

    BTW: there's an example of Pager class in Damian Conway's Object Oriented Perl.