Mini Shell
#!/usr/bin/perl
# encoding: utf-8
#
# author: Kyle Yetter
#
=pod
=head1 column-list
column-list - flow a single list of items into a multi-column list display
=head1 SYNOPSIS
column-list [options] < item-list.txt
=head1 DESCRIPTION
Reads list a newline-separated list of items from standard input and prints it in a
column-flowed list to standard output.
=head1 OPTIONS
=over 8
=item B<-c>, B<--columns>=I<N>
Set the number of columns to use in the output.
=item B<-s>, B<--spacer>=I<"space text">
Set the text to place inbetween each column.
=item B<-w>, B<--width>=I<N>
Set the maximum width of the output. This defaults to the width of the terminal. It is
ignored if --columns is specified.
=item B<--man>
Show documentation for this program as a man page.
=item B<-h>, B<--help>
You know the drill
=item B<-v>, B<--version>
Ditto
=back
=head1 EXAMPLES
$] cat /usr/share/dict/words | sort -R | head -30 | sort | column-list
Abyssinia's executrix's heartrending Lindsay's payers rhetoric
bogies futuristics hostelers Lister Poltava setter
chimps gracious insects Luella rasher Shelia's
Constance Haywood's Joesph nth receptionists sporty
Croatian headstone's Knuth's orphaned rhapsodic sustains
$] cat /usr/share/dict/words | sort -R | head -30 | sort | column-list -c 3
blackmailing expostulation's phrasal
Budapest's hydraulics's Ramiro
chasing Ignatius's sprayed
completion inflexion's sufficiency's
cowards japan Swedenborg's
dispensations jurisdiction's swords
economics Laramie's unwieldy
edifices modernism useable
endowment's narcosis's vetch's
enriched Pensacola warily
$] cat /usr/share/dict/words | sort -R | head -30 | sort | column-list -w 30 -s ' | '
apprentice | interlink
blenches | Lean
brawled | levelness's
carcass's | overfull
civilize | positional
convalescence | qualm
cub's | rote's
Davies | salvages
dieter | sheet's
doting | she'll
gargoyles | skidding
goggle | southerly
gorillas | stamina's
identifiers | stringiest
ignobly | timing's
=cut
use POSIX qw( ceil floor );
use Getopt::Long;
use Pod::Usage;
our $VERSION = 1.0;
our $spacer = ' ';
our $column_count = undef;
our @items = ();
our @sorted_items = ();
our $window_width;
our $window_height;
{
my @size = ( $ENV{'COLUMNS'} || 80, $ENV{'LINES'} || 22 );
my $tiocgwinsz = 0x5413;
eval {
my $data = '';
if ( ioctl( STDERR, $tiocgwinsz, $data ) >= 0 ) {
my ( $height, $width ) = unpack( "SSSS", $data );
$size[ 1 ] = $height if $height >= 0;
$size[ 0 ] = $width if $width >= 0;
}
};
$window_width = $size[ 0 ];
$window_height = $size[ 1 ];
}
our $max_width = $window_width;
sub bleach($) {
my ( $colored ) = @_;
$colored =~ s(\033\[.*?m)()g;
return $colored;
}
sub clen($) {
return length( bleach( $_[ 0 ] ) );
}
sub ljust($$) {
my ( $string, $width ) = @_;
my $len = clen( $string );
return $string if $width <= $len;
my $padding = $width - $len;
return( $string . ( ' ' x $padding ) );
}
sub calculate_columns($) {
my ( $n ) = @_;
my $l = scalar( @items );
my $rows = floor( $l / $n );
my $rem = $l % $n;
my @cols = map { 0 } ( 1 ... $n );
my @entries = @sorted_items;
my $column_number;
my $entry;
if ( $rem ) { $rows += 1; }
for ( 1 .. $n ) {
do {
$entry = shift( @entries ) or last;
$column_number = floor( $entry->[ 2 ] / $rows );
} while $cols[ $column_number ];
$cols[ $column_number ] = $entry->[ 1 ];
}
return( @cols );
}
sub optimize_columns {
my $limit = $max_width;
my $l = scalar( @items );
my $rem = $l % 2;
my $c = floor( $l / 2 );
my $column_spacing = clen( $spacer );
my $best_columns = 1;
if ( $rem ) { $c++; }
unless ( $l ) { return; }
for my $number_of_rows ( 1 .. $c ) {
my $number_of_columns = ceil( $l / $number_of_rows );
my @columns = calculate_columns( $number_of_columns );
my $total_width = 0;
for my $w ( @columns ) { $total_width += $w; }
$total_width += ( $number_of_columns - 1 ) * $column_spacing;
if ( $total_width <= $limit ) {
$best_columns = $number_of_columns;
last;
}
}
return $best_columns;
}
GetOptions(
'c|columns=i' => \$column_count,
's|spacer=s' => \$spacer,
'w|width=i' => \$max_width,
'h|help' => sub { pod2usage( 0 ); },
'v|version' => sub { print "$VERSION\n"; exit( 0 ); },
'man' => sub { pod2usage( { -exitval => 0, -verbose => 2 } ); }
);
my $i = 0;
while ( <> ) {
chomp;
if ( $_ ) { push( @items, [ $_, clen( $_ ), $i++ ] ); }
}
unless ( @items ) {
print "\n";
exit( 0 );
}
@sorted_items = sort { $b->[ 1 ] <=> $a->[ 1 ] } @items;
unless ( $column_count ) {
$column_count = optimize_columns();
}
our @column_widths = calculate_columns( $column_count );
our $row_count = ceil( scalar( @items ) / $column_count );
our @rows = ();
for my $row_index ( 0 .. ( $row_count - 1 ) ) {
for my $column_index ( 0 .. ( $column_count - 1 ) ) {
my $w = $column_widths[ $column_index ];
my $i = $row_index + ( $column_index * $row_count );
my $entry = $items[ $i ];
my $text = ljust( $entry ? $entry->[ 0 ] : '', $w );
if ( $column_index ) { print( $spacer ); }
print( $text );
}
print "\n";
}
Zerion Mini Shell 1.0