nowox
nowox

Reputation: 29116

Perl align text (left, center, right) without packages

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

Answers (2)

mpapec
mpapec

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

Borodin
Borodin

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

Related Questions