Forum Moderators: coopster & phranque

Message Too Old, No Replies

Emulating PHP's wordwrap()

         

csdude55

5:05 am on Jun 24, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



I'm having an issue with email errors when my script sends an email, so I need to force the variable in the email to wrap.

I know, I know, there's a module for that! LOL

[metacpan.org...]

But I don't really want to import a module with each load of the script for something that might be used 1 in 50 times. I'd rather have a short function that I can call as needed instead.

This is where I am so far:

$comment = 'This is a test. This is only a test. If this were a real emergency, you would have been informed. Just kidding. It was a real emergency after all.';
print wordwrap($comment, 50);

sub wordwrap {
my ($string, $max, $breaker) = @_;

# set defaults
$max = quotemeta($max) || 70;
$breaker //= "\n";

$string =~ s/(.{$max}[^\s]*)\s+/$1$breaker/;
return $string;
}


This works on the first line, but that's all.

I know that I could put it in a loop, but since I'm intentionally breaking after the last word after $max characters then that could put me in an infinite loop.

Any suggestions?

robzilla

7:34 am on Jun 24, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



But I don't really want to import a module with each load of the script for something that might be used 1 in 50 times

Does Perl not support loading modules dynamically and conditionally?

Brett_Tabke

12:23 pm on Jun 24, 2022 (gmt 0)

WebmasterWorld Administrator 10+ Year Member Top Contributors Of The Month



I have a similar routine I use. I go for total maintainability on it because routines like this tend to breed hard to maintain exceptions later on....

$comment = 'This is a test. This is only a test. If this were a real emergency, you would have been informed. Just kidding. It was a real emergency after all.';
print &wordwrap($comment, 50);

sub wordwrap {
my ($string, $max) = @_;
my $out="";
my $cheese=0;
@snack = split(/ /, $comment);
foreach $s (@snack) {
$out.=$s . " ";
$cheese+=length($s);
$out.="\n" if $cheese > $max; #or whatever the wrap is...
$cheese=0 if $cheese > $max;
# print "$cheese, $max $out\n";
}
return($out);
}

lucy24

3:44 pm on Jun 24, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



that could put me in an infinite loop
When I am apprehensive about loops I sometimes set up a supplementary condition along the lines of

WHILE {condition AND counter < 100)
  {
  counter++
  do stuff
  }

Probably overkill for the present situation, though.

csdude55

7:12 pm on Jun 24, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



@robzilla, not to my knowledge. You traditionally put the modules at the top of the script and they're loaded when the script loads. I've tried adding them inside of subroutines using require instead of use, too, but it still looks like they're loaded in the beginning :-/

Thanks for the code, @Brett_Tabke! I was thinking about it in bed last night, and I came up with a different variation:

sub wordwrap {
my ($string, $max, $breaker) = @_;
my @arr;

$max //= 70;
$breaker //= "\n";
$len = length($string) / $max;

for ($x = 0; $x < $len; $x++) {
$arr[$x] = $string =~ s/^(.{$max}[^\s]*)\s+.*/$1/r;
$string =~ s/^\Q$arr[$x]\E\s+//;
}

return join($breaker, @arr);
}


Your version is almost twice as fast as mine, but doesn't quite break where I'm expecting it to so there's a bug in there somewhere:

[tpcg.io...]

csdude55

6:40 am on Jun 25, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



For the sake of playing, this version is faster than either of them:

sub wordwrap {
my ($string, $max, $breaker) = @_;
my @arr;

$max //= 70;
$breaker //= "\n";
$len = length($string) / $max;

for ($x = 0; $x < $len; $x++) {
$arr[$x] = $string =~ s/^(.{$max}[^\s]*)\s+.*/$1/r;
$string = substr($string, length($arr[$x]));
}

return join($breaker, @arr);
}


[tpcg.io...]

There's a minor glitch where lines 2+ have an opening whitespace, though. That's irrelevant for my purposes, but there's probably a fix somewhere in that regex that would prevent that.

csdude55

7:53 pm on Jun 25, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



If you're interested in speed, this modification is by far the fastest:


sub wordwrap {
my ($string, $max, $breaker, $start, @arr) = @_;

$max //= 70;
$breaker //= "\n";
$start = 0;
$len = int(length($string) / $max);

for (0..$len) {
my $finder = index($string, ' ', $max);
push (@arr, substr($string, $start, $finder));
$start += $finder;
}

return join($breaker, @arr);
}

phranque

11:02 pm on Jul 3, 2022 (gmt 0)

WebmasterWorld Administrator 10+ Year Member Top Contributors Of The Month



But I don't really want to import a module with each load of the script for something that might be used 1 in 50 times

Does Perl not support loading modules dynamically and conditionally?


Because use takes effect at compile time, it doesn't respect the ordinary flow control of the code being compiled. In particular, putting a use inside the false branch of a conditional doesn't prevent it from being processed. If a module or pragma only needs to be loaded conditionally, this can be done using the if [perldoc.perl.org] pragma:

use if $] < 5.008, "utf8";
use if WANT_WARNINGS, warnings => qw(all);

source: https://perldoc.perl.org/functions/use [perldoc.perl.org]

csdude55

6:46 am on Jul 4, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



In particular, putting a use inside the false branch of a conditional doesn't prevent it from being processed.

Is that supposed to mean that this would not load the module at compile time:

if ($foo eq 'bar') {
use Foo::Bar;
}


but this would load it at compile time?

if ($foo ne 'bar') {
use Foo::Bar;
}

phranque

9:08 am on Jul 4, 2022 (gmt 0)

WebmasterWorld Administrator 10+ Year Member Top Contributors Of The Month



The use takes effect at compile time regardless of it being surrounded by a conditional.

you must use the if pragma, not the if statement, if you want a module loaded conditionally.

csdude55

5:39 pm on Jul 4, 2022 (gmt 0)

WebmasterWorld Senior Member 10+ Year Member Top Contributors Of The Month



Following what the doc said, like this?

use if $foo ne 'bar', "Foo::Bar";

phranque

8:29 pm on Jul 4, 2022 (gmt 0)

WebmasterWorld Administrator 10+ Year Member Top Contributors Of The Month



that looks like it should work.