Reputation: 29116
I wrote this short subroutine to left,center,right-align a multiline string.
How can I make it better ? (shorter, prettier, optimized)? Can you offer some suggestions?
#!/usr/bin/env perl
use strict;
# A bit of text
$_ = <<END;
Lorem ipsum dolor sit amet consectetur adipiscing elit
Curabitur pretium odio dictum nisl posuere, vitae mollis nibh facilisis
Curabitur tempus tincidunt ante eget facilisis
Pellentesque ut accumsan dui, nec semper odio
Ut libero lacus, fermentum quis ultricies quis, tempus nec augue
Mauris tincidunt hendrerit accumsan
END
# Test each alignment case
for my $align ('left', 'center', 'right') {
print align($_, $align)."\n";
}
# Alignment subroutine
sub align {
my ($_, $align) = @_; # Text to align, Alignment type
my $re = qw/^([^\n]*)$/; # A regex to match each line
s/^[ \t]+|[ \t]+$//gm; # Remove trailing/leading spaces
# Get longest line
my $max = 0;
$max = length($1) > $max ? length($1) : $max while /$re/gm;
# Do the alignment
s|$re|$1 . " " x ($max-length($1))|mge if $align eq 'left';
s|$re|" " x (($max-length($1))/2) . $1|mge if $align eq 'center';
s|$re|" " x ($max-length($1)) . $1|mge if $align eq 'right';
return $_;
}
I am such a beginner with Perl and I am sure I am missing that genious that will make my code magic.
Some suggestions?
Upvotes: 0
Views: 1337
Reputation: 50667
Some minor modifications; newer perl gives warning when using $_
as lexical (my) variable, simpler $re
and properly compiled (qr
vs qw
), removed unneeded ternary assignment, added elsif
to skip some conditions.
sub align {
local $_ = shift;
my ($align) = @_; # Text to align, Alignment type
my $re = qr/^(.*)/m;
s/^\h+|\h+$//gm; # Remove trailing/leading spaces
# Get longest line
my $max = 0;
length($1) > $max and $max = length($1) while /$re/g;
if ($align eq 'left') { s|$re|$1 . " " x ($max-length($1))|ge }
elsif ($align eq 'center') { s|$re|" " x (($max-length($1))/2) . $1|ge }
elsif ($align eq 'right') { s|$re|" " x ($max-length($1)) . $1|ge }
return $_;
}
Tempted to use anon hashref instead elsif
, but that would not contribute to readability,
{
left => sub { s|$re|$1 . " " x ($max-length($1))|ge },
center => sub { s|$re|" " x (($max-length($1))/2) . $1|ge },
right => sub { s|$re|" " x ($max-length($1)) . $1|ge },
}->{$align}->();
Upvotes: 4
Reputation: 126742
I think it's clearest to split the string into lines and process them in a loop.
Like this
#!/usr/bin/env perl
use strict;
use warnings;
use 5.010;
sub max;
my $text = <<END_TEXT;
Lorem ipsum dolor sit amet consectetur adipiscing elit
Curabitur pretium odio dictum nisl posuere, vitae mollis nibh facilisis
Curabitur tempus tincidunt ante eget facilisis
Pellentesque ut accumsan dui, nec semper odio
Ut libero lacus, fermentum quis ultricies quis, tempus nec augue
Mauris tincidunt hendrerit accumsan
END_TEXT
for my $align (qw/ left right centre /) {
print align($text, $align), "\n";
}
sub align {
state %core;
@core{qw/ left centre center right /} = (0, 1, 1, 2) unless %core;
my @lines = split m{$/}, shift;
s/\A\s+|\s+\z//g for @lines;
my @indices = (1, 2);
splice @indices, $core{lc shift}, 0, 0;
my $max = max map length, @lines;
return join '', map {
my $padlen = $max - length;
my $pad1 = ' ' x $padlen;
my $pad2 = substr($pad1, 0, $padlen/2, '');
join '', ($_, $pad1, $pad2)[@indices], "\n";
} @lines;
}
sub max {
my $max = shift;
$_ > $max and $max = $_ for @_;
$max;
}
Upvotes: 2