#!/usr/bin/perl
#: print a list of all available commends-- shortest first!
#============================================================================
our $Timestamp = # sburke@cpan.org
 q[  Last Modified Time-stamp: "2014-04-30 00:06:00 MDT sburke@cpan.org"  ];
#======================================================================
# Run "perldoc" on this file to see the pretty version of this:

=head1 NAME

commands_by_length - list all available commands, sorted *shortest first*

=head1 DESCRIPTION

This program prints Unix commands on your system (or at least commands
that the man page system knows about), with all the 1-character
commands first (and I<in that group>, sorted alphabetically), then an
alphabetical listing of all the 2-character command, then an alphabetical
listing of all the 3-character commands, etc.

I got the idea for this because an old old programmer once told be
that the most elementary commands in Unix tended to be the
two-character ones, and then three-later ones tended to be added
later, and so on.  That's not true-- but it's true enough!, at least
to the extent that I can say that you should probably learn all the
two-letter commands.

(Note that shell-internal commands like "if" aren't in here.)

Example output:

                  [ (1) check file types and compare values
                  w (1) Show who is logged on and what they are doing.
                 7z (1) A file archiver with highest compression ratio
                 ab (1) Apache HTTP server benchmarking tool
                 ar (1) create, modify, and extract from archives
                 as (1) the portable GNU assembler.
                 at (1) queue, examine or delete jobs for later execution
                 bc (1) An arbitrary precision calculator language
 [...]
                 uz (1) gunzips and extracts a gzip'd tar'd archive
                 vi (1) Vi IMproved, a programmers text editor
                 wc (1) print newline, word, and byte counts for each file
                 xz (1) Compress or decompress .xz and .lzma files
                7za (1) A file archiver with highest compression ratio
                a2p (1) Awk to Perl translator
                apg (1) generates several random passwords
                apt (8) Advanced Package Tool
 [...much later...]
             zegrep (1) search possibly compressed files for a regular expression
             zenity (1) display GTK+ dialogs
             zfgrep (1) search possibly compressed files for a regular expression
             zforce (1) force a '.gz' extension on all gzip files
             zshall (1) the Z shell meta-man page
             zshzle (1) zsh command line editor
            a2enmod (8) enable or disable an apache2 module
            aa-exec (8) confine a program with the specified AppArmor profile
            addpart (8) simple wrapper around the "add partition" ioctl
            adduser (8) add a user or group to the system
 [...etc...]

...down to things like...

   bonobo-activation-server (1) GNOME component tracker

and so on.

=head1 AUTHOR

Sean Burke, sburke@cpan.org

=cut 

#======================================================================

use strict; use warnings;

$ENV{MANWIDTH} = 128;
 # Comment that out if you want the descriptions trimmed for your screen

my $Section_of_interest_re =
   qr/[18]/
   # Or change to this if you want everything:
   #qr/\S*?/
;

$| = 1;

Main();
exit;

#======================================================================

sub Main {

  # I'm a good boy because my scripts aren't
  #  monolithic all-top-level messes!

  my $apropos_lines_r = [ `man -k .` ];
                        # Search for man pages that
                        #  match /./  (namely, anything)
  die "No apropos lines!?" unless $apropos_lines_r;

  my $man_pages_r = filter_apropos_lines($apropos_lines_r);
  my $man_pages_sorted_r = man_pages_sorted($man_pages_r);
  print_sorted_pages($man_pages_sorted_r);
  return;
}

#======================================================================

sub filter_apropos_lines {
  my($apropos_lines_r) = @_;
  my $man_pages_r = [];
  # This loop makes a LoH -- i.e., a list(ref) to a hash
  #  for each entry line.  The hash contents are
  #  the captured part of each entry line.  Those
  #  get used later in sorting, and then in printing.

  
  # I'm being fancy and using NAMED captures instead
  #  of NUMBERED captures!
  foreach my $line (@$apropos_lines_r) {
    if(
      $line =~
       # Match like: "sha1pass (1) - Create a SHA1 password hash"
       m/\A
          (?'command_name'             .*? )
          (?'spacing_before_section'   \s+ )
          (?'section_number'
             \(
             $Section_of_interest_re
             \)
	  )
          (?'spacing_after_section'     \s+ )
          -
          (?'description'
  	     \s+
	     .+  # whole rest of line (incl. the newline)
          )
        /xs
    ) {
      my $entry_r = # make a hashref:
         {
           %+, # Copy that magic hash of matched-RE items
	   'Whole_Line' => $line,
	   'Sort_Key'   => lc (
	                    $+{'command_name'} . " " . $+{'section_number'}
			   ),
         }
      ;
      $entry_r->{'Sort_Key_Length'} = length $entry_r->{'Sort_Key'};
      push @$man_pages_r, $entry_r;
      # Google "Schwartzian Transform" to understand this approach
      #  to sorting-- namely, turning a string into a complex
      #  structure, sorting the list of those structures
      #  and than typically turning the structure back to the
      #  original form.
      # (But in this extremely fancy case, in print_sorted_pages,
      #  we'll actually piece together something new.)
    }
  }

  die "Nothing matches!?" unless @$man_pages_r;
  return $man_pages_r;
}

#======================================================================

sub man_pages_sorted {
  my($man_pages_r) = @_;

  my @man_pages_sorted = 
   sort {
                # Sorting by length
                $a->{'Sort_Key_Length'}   # '3' for 'tar'
            <=> $b->{'Sort_Key_Length'}
        or
	        # Sorting by alpha
                $a->{'Sort_Key'}
            cmp $b->{'Sort_Key'}
        or
	        # If we *somehow* make it here, sort by
	        #  comparing the law lines.
                $a->{'Whole_Line'}
            cmp $a->{'Whole_Line'}

      # Yup, this is THREE-level sorting!
      # Namely, if one level needs a tie-breaker, it falls
      # through to the level under it.
   }
   @$man_pages_r
  ;
  return \@man_pages_sorted;
}
#======================================================================

sub print_sorted_pages {
  my($man_pages_sorted_r) = @_;

  foreach (@$man_pages_sorted_r) {
    print join( "",
      # We move all the spacing to the left...
      #  because I like the way that looks!
       $_->{'spacing_before_section'},
       $_->{'spacing_after_section'},
       " ",
       $_->{'command_name'},
       " ",
       $_->{'section_number'},
       $_->{'description'},
    );
  }

  return;
}
#======================================================================
__END__
