#!/usr/bin/env perl # # mh-skel This script is a skeleton for scripts that will manipulate # MH/NMH mail messages. The processor() routine must be filled # out with the actual code required for a particular purpose. # This script does nothing itself. # # Code Organization # # The following routines are in mh-skel. They must be modified # as required for each specific MH/NMH helper script. # # main() # This is driver routine. # You *probably* don't have to change this. # # optsandargs() # This parses the command line for options and arguments. # It must be modified as needed for command-specific options. # # peekargs() # This routine examines the non-option arguments. # This must be modified as needed for command-specific # arguments. # # The skeleton code does the following: # # - If there's a folder (marked by starting with a # plus sign) we'll save the name in the $mbox global. # This assumes only one folder will be given on a # command line. # # - Arguments that start with a comma are assumed to # be deleted messages. They will be saved in the # @argcommas list. # # - All other arguments are assumed to be message # numbers or names. We'll use mhpath to get their # paths (and also shrink it to a list of unique # messages). These will be saved in the %msgs hash, # with the message number as the key and the message # path as the value. # # processor() # This routine is a more-specific driver routine. # # The skeleton code does the following: # # - Get the paths to the mail folder named in # the $mbox mail folder. # # - Calls readmsg() to read each individual message # listed on the command line. # # - Parse each message into a hash of message headers. # # getdirs() # Get the path to the MH folder named in $mbox. # # msghandler() # This routine does whatever must be done to a message. # This is the super-duper script-specific code. # The skeleton code doesn't really do anything. # # readmsg() # Read a mail message and return its contents. # # parsemsg() # Parse a mail message's headers into a hash table. # # printhdrs() # This is a debugging routine that prints the header hash. # # version() # Print the version number(s) and exit. # You don't have to change this. # # usage() # Give usage message and exit. # # Revision History # 1.0 Initial revision. 141011 # 1.1 Completed several standard utility routines. 171205 # 1.2 Added licensing info. 180531 # # Written by Wayne Morrison, 141011 and 171205. # # Copyright 2014, 2017 Wayne Morrison # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # use strict; use Getopt::Long qw(:config no_ignore_case_always prefix_pattern=--|-); # # Version information. # my $NAME = "mh-skel"; my $VERS = "$NAME version: 1.2"; ############################################################################ # # Options fields. # my %opts = (); # Options. # # Command line arguments. # my @opts = ( 'verbose', # Give verbose output. 'help', # Give a help message. 'Version', # Display the program version. ); my $verbose = 0; # Verbose flag. ############################################################################ my $INBOX = 'inbox'; # Default inbox folder name. my $mbox = $INBOX; # Path to the target NMH folder. my $folderpath; # Mail folder path. my %msgs = (); # List of messages to work on. my @argcommas = (); # Comma messages in argument list. ############################################################################ my $BODYKEY = ' body '; # Key for message-header hash to hold # the message body. This shouldn't # ever be found as a mail header. ############################################################################ main(); exit(0); #----------------------------------------------------------------------------- # Routine: main() # # Purpose: Main driver routine. # sub main { $| = 1; # # Munch on the options and arguments. # optsandargs(); # # Do... something. # processor(); } #---------------------------------------------------------------------- # Routine: optsandargs() # # Purpose: Parse the command line for options and arguments. # In addition to handling options, we'll also save # the messages in the %msgs hash. # sub optsandargs { # # Parse the options. # GetOptions(\%opts,@opts) || usage(); # # Check for some immediate-action options. # usage() if(defined($opts{'help'})); version() if(defined($opts{'Version'})); $verbose = $opts{'verbose'}; # # Give a usage message if no messages were given. # usage() if(@ARGV == 0); # # Peek at the arguments. Save the folder name if a mail folder # was found amongst the arguments. If a message was given # (with or without a preceding comma), it'll be saved in the message # hash. "Deleted" messages (those whose names start with a comma) # are added to the @argcommas list. # peekargs(); print "doing something in folder +$mbox\n" if($verbose); } #---------------------------------------------------------------------- # Routine: peekargs() # # Purpose: Look at the non-option arguments. The following things # are recognized: # # - If there's a folder (marked by starting with a # plus sign) we'll save the name in the $mbox global. # This assumes only one folder will be given on a # command line and only the last will be saved. # # - Arguments that start with a comma are assumed to # be deleted messages. They will be saved in the # @argcommas list. # # - All other arguments are assumed to be message # numbers or names. We'll use mhpath to get their # paths (and also shrink it to a list of unique # messages). These will be saved in the %msgs hash, # with the message number as the key and the message # path as the value. # sub peekargs { my $mboxen = 0; # Count of mboxes in arglist. my @msgs = (); # Non-comma messages in arglist. my @mpaths = (); # Message paths from mhpath. # # Look at the arguments for a mail folder or message numbers. # for(my $ind=0; $ind < @ARGV; $ind++) { my $arg = $ARGV[$ind]; # Argument to examine. # # If a mail folder was found (with a leading plus sign), # it'll be saved in a global. # if($arg =~ /^\+(.*)/) { $mbox = $1; $mboxen++; if($mbox eq '') { print STDERR "mailbox name must be specified with \"+\"\n"; exit(2); } } elsif($arg =~ /^,(\d+)$/) { # # If a deleted message was found (with a leading # comma), save it in a list of deleted messages. # push @argcommas = $1; } else { # # Anything else will be added to messages list. # push @msgs, $arg; } } # # Ensure that no more than one mail folder was given. # # Note: This is turned off in the code, but the check is # retained in case it is needed. # if(0) { if($mboxen > 1) { print STDERR "too many mail folders given on command line\n"; exit(2); } } # # Get a list of the path to the messages given on the command line. # open(MNUMS, "mhpath @msgs |"); @mpaths = ; # # Build a hash of message numbers and paths, with the number # as the hash key. # foreach my $arg (@mpaths) { my $msgnum = ''; # Message number. chomp $arg; $arg =~ /^\/.*\/(\d+)$/; $msgnum = $1; $msgs{$msgnum} = $arg; } } #---------------------------------------------------------------------- # Routine: processor() # # Purpose: This routine starts the command-specific code. # # The code in this skeleton routine may or may not be needed # by a particular script. It's included here as an example. # sub processor { # # Get paths to a set of MH folders. # getdirs(); # # Do something to the specified message, one at a time. # foreach my $msg (sort(keys(%msgs))) { my @mbuf; # Message buffer. my %hdrs = (); # Message headers. my $mbody = (); # Message body. # # Read the message into a buffer. # @mbuf = readmsg($msgs{$msg}); # # Parse the message headers into a hash. # %hdrs = parsemsg(@mbuf); # # Get the message body from the headers hash. # $mbody = $hdrs{$BODYKEY}; # msghandler($msg); } } #---------------------------------------------------------------------- # Routine: getdirs() # # Purpose: Get the path to the MH folder named in $mbox. # sub getdirs { # # Get the path to the MH inbox. # $folderpath = `mhpath +$mbox`; chomp $folderpath; if($folderpath eq '') { print STDERR "unable to find the path for mailbox \"$mbox\"\n"; exit(1); } } #---------------------------------------------------------------------- # Routine: msghandler() # # Purpose: This routine handles one message at a time. # The skeleton code doesn't really do anything. # sub msghandler { my $msg = shift; # Message number to handle. my $fullfn; # Full filename of message. # # Get the full path to the message file. # $fullfn = "$folderpath/$msg"; if(! -e $fullfn) { print "message $msg does not exist in $mbox\n"; return; } print "doing something for message $msg ($fullfn)\n" if($verbose); # # Must add stuff here... # } #---------------------------------------------------------------------- # Routine: readmsg() # # Purpose: Read a mail message and return its contents. # sub readmsg { my $fn = shift; # Message to read. my @lines; # Lines in the message. # # Get the lines from the message. # open(MHMSG, "< $fn"); @lines = ; close(MHMSG); # # Get rid of newlines from the message lines. # for(my $ind = 0; $ind < @lines; $ind++) { chomp($lines[$ind]); } return(@lines); } #---------------------------------------------------------------------- # Routine: parsemsg() # # Purpose: Parse a mail message and return its headers and body. # sub parsemsg { my @mbuf = @_; # Message buffer. my @lines; # Lines in the message. my $ind = 0; # Index into message buffer. my %hdrs = (); # Message headers. my @body = (); # Message body as a list. my $body = ''; # Message body as a string. my $lastkey = ''; # Last header key. # # Get the headers from the message buffer and add them to the # %hdrs hash. # for(; $ind < @mbuf; $ind++) { my $line = $mbuf[$ind]; # Line in message buffer. # # Drop out when we hit the end of the headers. # last if($line eq ''); # # If this line starts with a space, it's a continuation # of the previous line. We'll append it to the previous # entry. # if($line =~ /^\s/) { # # If we don't have a previous key, we won't # save this line. # Otherwise, save it to the previous key. # if($lastkey eq '') { print("\t\tskipping weird continuation header line - <$line>\n"); } else { $hdrs{$lastkey} .= "\n$line"; } next; } # # Save this line in the headers hash, and save its key. # $line =~ /^(\S+)\s+(.*)$/; $hdrs{$1} = $2; $lastkey = $1; } # # Collapse the message body into a single string. # @body = splice @mbuf, $ind; $body = join("\n", @body); # # Add the message body string to the headers hash with a special # key that shouldn't ever be found in a message. # We hope. # $hdrs{$BODYKEY} = $body; # # Return the headers hash. # return(%hdrs); } #---------------------------------------------------------------------- # Routine: printhdrs() # # Purpose: Print the version number(s) and exit. # This is really only useful for debugging. # sub printhdrs { my %hdrs = @_; # Message's headers. print("\nhdrs:\n"); foreach my $key (sort(keys(%hdrs))) { print "\t<$key>\t<$hdrs{$key}>\n"; } print("hdrs done\n"); print("\nbody - <$hdrs{$BODYKEY}>\n"); } #---------------------------------------------------------------------- # Routine: version() # # Purpose: Print the version number(s) and exit. # sub version { print STDERR "$VERS\n"; exit(0); } #---------------------------------------------------------------------- # Routine: usage() # # Purpose: Give usage message and exit. # sub usage { print STDERR "usage: mh-skel [options] ... \n"; print STDERR "\n"; print STDERR "\twhere [options] are:\n"; print STDERR "\t\t-verbose\n"; print STDERR "\t\t-help\n"; print STDERR "\t\t-Version\n"; exit(0); } 1; ############################################################################## =pod =head1 NAME B - skeleton script for writing scripts to manipulate MH/NMH mail messages =head1 SYNOPSIS mh-skel [options] ... =head1 DESCRIPTION B is a skeleton script to assist in writing scripts that will operate on mail messages stored in MH/NMH. It will not do anything itself, but must be filled out with additional code to handle whatever functions are required. The command-line-handling code will follow the description given in the following section. That can, of course, be changed as required. =head1 MAIL FOLDERS By default, the I mail folder will be searched for messages. A mail folder may be specified on the command line by preceding the folder name with a plus sign. For example, "mh-skel +papers 42" will do something with message 42 in the I mail folder. Multiple mail folders may be given on a single command line, but only the last folder will be used. Thus, "mh-skel +docs 42 +verbiage 88 +papers" will not generate an error, but only messages 42 and 88 will be handled in the I folder. =head1 AVAILABLE DATA AND ROUTINES This section describes the global data and routines in B that are used by the predefined routines, and may be used by routines created for new MH/NMH scripts. =head2 Globals The following globals in B are used by the predefined routines, and may be used by routines created for new MH/NMH scripts. =over 4 =item I<$NAME> This is the name of the script. =item I<$VERS> This is the version string for the program. It is defined to be "$NAME version: 1.0". Other than the version number, this should not be changed. =item I<%opts> This hash holds the options found on the command line. This hash is a filled automatically by I. =item I<@opts> This is a list of options that will be recognized by I. See its man page to add additional options. =item I<$verbose> This is intended to be a boolean value that is set according to whether or not the I<-verbose> option is given. It is intended to be used to provide information beyond the standard amount. =item I<$INBOX> This is the default inbox folder name. =item I<$mbox> This is the path to the target NMH folder. =item I<$folderpath> This is the path to the MH/NMH mail folder. =item I<%msgs> This is a hash of the messages to work on. The hash keys is the message numbers. The hash value is the path to the message. =item I<@argcommas> This list holds the "comma" messages (deleted messages) that were found in argument list. These may not be used in all scripts. =item I<$BODYKEY> This is the special hash key for the message-header hash that refers to the message body. The default value is ' body ', and it shouldn't ever be found as a mail header. =back =head2 Utility-Specific Routines The following routines in B probably will need to be modified for different utilities. They must be modified as required for each specific MH/NMH helper script. =over 4 =item I This parses the command line for options and arguments. It must be modified as needed for command-specific options. =item I This routine examines the non-option arguments. This must be modified as needed for command-specific arguments. The skeleton code does the following: - If there's a folder (marked by starting with a plus sign) we'll save the name in the I<$mbox> global. This assumes only one folder will be given on a command line. - Arguments that start with a comma are assumed to be deleted messages. They will be saved in the I<@argcommas> list. - All other arguments are assumed to be message numbers or names. We'll use the B command to get their paths (and also shrink it to a list of unique messages). These will be saved in the I<%msgs> hash, with the message number as the key and the message path as the value. =item I This routine is a more-specific driver routine. The skeleton code does the following: - Get the paths to the mail folder named in the I<$mbox> mail folder. - Calls I to read each individual message listed on the command line. - Parse each message into a hash of message headers. =item I This routine does whatever must be done to a message. This is the super-duper script-specific code. It B be modified for a new script; the skeleton code doesn't really do anything. =back =head2 Utility-Common Routines The following routines in B probably do not need to be modified for different utilities. However, they should be examined when creating a new MH/NMH utility to ensure that this is true. =over 4 =item I This is the main driver routine for the new utility. It calls I and I. =item I Get the path to the MH folder named in I<$mbox>. =item I Read a mail message and return its contents. =item I Parse a mail message's headers into a hash table. =item I This is a debugging routine that prints the header hash. =item I Give usage message and exit. =item I Print the version number(s) and exit. You don't have to change this. =back =head1 OPTIONS B takes the following options: =over 4 =item I<-verbose> This option provides verbose information about the operation of B. =item I<-Version> Display the version information for B. =item I<-help> Display a help message. =back =head1 AUTHOR Wayne Morrison, wayne@waynemorrison.com =head1 LICENSE Copyright 2014, 2017 Wayne Morrison Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. =head1 SEE ALSO B, B B =cut