Mini Shell

Direktori : /opt/sharedrads/extras/
Upload File :
Current File : //opt/sharedrads/extras/column-list

#!/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