Saturday, February 28, 2009

Improving the twit perl script

As I spent some time on twit.pl v0.2.0, I also tried to improve on the Perl template for new scripts.

New Perl that I learned
Today, I'll try to explain the new concepts first and then show the code.
  • "$0": This is dollar-zero. It is a special Perl variable to indicate the name of the file containing the perl script being executed
  • The x operator repeats a string pattern.
    print "*" x 10; and print "**********"; are equivalent
  • defined will return false if a variable is undef and true if it has a value.
  • @ARGV is a reserved Perl array that contains the list of arguments passed on the command line.
    Help if (@ARGV == 0); will call the Help subroutine if the list of arguments is empty, i.e. if you just type "perl twit.pl" at the command line.
    Note that checking @ARGV must be performed before the call to GetOptions which will empty the argument list.
  • You can tell the GetOption procedure to directly call a subroutine when a specific argument is passed at the command line. See for example "version" => \&Version. When -v or --version is added after the script name, you will see the version number of the script and exit the program.
twit_0_2_0.pl
To use version 0.2.0, you have to add -t (for twitter), -i (for identica) or -a (for both, also works if you choose -t -i) to the command line.

#!/usr/bin/perl
use 5.10.0; # To be able to use "say" function
use strict; # Pragma to add restrictions to Perl rules
use warnings; # Pragma to add warnings at compile and run-time
use Getopt::Long; # To parse the command line
use Net::Twitter; # API to twitter.com and identi.ca

my $PROG_NAME = $0; # $0 contains the name of the file containing the Perl script being executed
my $VERSION = "v0.2.0";
my $VersionDate = "February 21st 2009";

# ------------------------------------------------------------------------------
# Name : Help
# Comment : displays list of available options
# Input : no argument to command line or "-h" or "--help"
# Output : help screen and exit program
# ------------------------------------------------------------------------------
sub Help {
print "
Usage:
perl $PROG_NAME [-options]

Options:
-a, --all : send message to all supported macroblogging sites
-f, --file : file containing the login information for each site
-h, --help : this help screen
-i, --identica : send message to identi.ca
-s, --status : status (message) to send
-t, --twitter : send message to twitter.com
-v, --version : displays version information

$PROG_NAME help
";
print "-" x (length ($PROG_NAME) + 5);
print "
$PROG_NAME sends a status update to either twitter.com or identi.ca (or both) from the command line.
If -t is specified (for example), it will send the update to twitter.com.
If -a is passed, then your status on both twitter and identica will be updated.

Format of login files
---------------------
$PROG_NAME will first try to find \"twit.txt\" located in the current directory.
You can also use the --file option to tell $PROG_NAME where to find your login information.
- Lines starting with '#' are comments and will be ignored by the script
- The last line starting with \"Twitter\" will be parsed for username and password.
Each of these fields must be separated by a semi-column ':'
- Same thing with \"Identica\"

Examples:
---------
- perl twit.pl -s \"Using Padre on Linux\" -f \"~/secretstuff/mytwitterpassword.txt\" -a
Updates both twitter and identica status with login info from specified file
- perl twit.pl -s \"This is Vistaaaaaa\" -i
Updates identica status only

$PROG_NAME uses the following modules:
- Getopt::Long
- Net::Twitter
";
exit;
} # End of Help

# ------------------------------------------------------------------------------
# Name : version
# Comment : displays script's version number
# Input : --
# Output : version number screen and exit program
# ------------------------------------------------------------------------------
sub Version {
print "
$PROG_NAME version: $VERSION
Date : $VersionDate
Author: dlp

Get the latest version from here:
http://sites.google.com/site/damienlearnsperl/DLP-scripts
";
exit;
} # end of Version

# ------------------------------------------------------------------------------
# Name : CreateObject
# Comment : Creates and returns an instance of the Net::Twitter class
# Input : - Input string with value
# "twitter" -> twitter.com instance
# "identica" -> identi.ca instance
# all other values return an error
# - Hash with "UserName" and "Password"
# Output : Object newly created or 0 if error
# ------------------------------------------------------------------------------
sub CreateObject {
my $SiteInstance = 0;
my $NameString = shift;
my %Login = @_;

$NameString =~ tr/A-Z/a-z/;
if ($NameString eq "twitter") {
$SiteInstance = Net::Twitter->new(username => $Login{"UserName"}, password => $Login{"Password"});
}
elsif ($NameString eq "identica") {
$SiteInstance = Net::Twitter->new(identica => 1, username => $Login{"UserName"}, password => $Login{"Password"});
}
return $SiteInstance;
} # End of CreateObject

# ------------------------------------------------------------------------------
# Name : SendUpdate
# Comment : Sends update to Twitter object
# Input : - Net::Twitter object
# - message string
# Output : string "OK" if successful update, string "FAIL" otherwise
# ------------------------------------------------------------------------------
sub SendUpdate {
my $Site = shift;
my $Message = shift;
my $SiteName = ($Site->{identica})?"identi.ca":"twitter.com";

#There's a hard limit on the size of twits for both twitter and identica
if (!defined $Message) {
return "$SiteName update: FAILED (no message)";
}
if (length $Message > 140) {
return "$SiteName update: FAILED (message over 140 characters)";
}

if ($Site->update($Message)) {
return "$SiteName update: OK";
}
else {
return "$SiteName update: FAIL";
}
} #End of SendUpdate

# ------------------------------------------------------------------------------
# Name : SendMessage
# Comment : Sends message to chosen macroblogging site
# Input : $_[0] = Input string with value
# "twitter" -> twitter.com instance
# "identica" -> identi.ca instance
# $_[1] = Message string to be sent
# $_[2] = Hash with "UserName" and "Password" elements
# Output : Return string: "Error" if couldn't create object or string from SendUpdate
# ------------------------------------------------------------------------------
sub SendMessage {
my $ReturnString;
my $Instance;
my ($SiteName, $Message, %Login) = @_;

$Instance = CreateObject($SiteName, %Login);
if ($Instance) {
$ReturnString = SendUpdate($Instance, $Message);
}
else {
$ReturnString = "Error with $SiteName creation process";
}
return $ReturnString;
} #End of SendMessage

# ------------------------------------------------------------------------------
# Main
# ------------------------------------------------------------------------------
my $Status;
my $PasswordFile;
my %TwitLogin;
my %IdenticaLogin;
my $TwitterUse; # 1-> send to twitter, 0 -> do not send
my $IdenticaUse; # 1-> send to identica, 0 -> do not send

Help if (@ARGV == 0);

#Parse command line arguments
GetOptions ("all" => sub {$TwitterUse = 1; $IdenticaUse = 1},
"status=s" => \$Status,
"file=s" => \$PasswordFile,
"help" => \&Help,
"identica" => \$IdenticaUse,
"twitter" => \$TwitterUse,
"version" => \&Version);

# Read Password file passed as argument or twit.txt by default
$PasswordFile = "twit.txt" unless ($PasswordFile);
open(LOGINFILE, $PasswordFile) or die "Cannot open \"$PasswordFile\" file: $!\n";
while () {
my $line = $_;
my $PlaceHolder;

chomp $line; # Remove trailing newline character
next if ($line =~ m/^#/); # Ignore lines starting with '#'
if ($line =~ m/^twitter/i) { # /^ indicates the beginning of the line
($PlaceHolder, $TwitLogin{"UserName"}, $TwitLogin{"Password"}) = split (/:/, $line);
}
if ($line =~ m/^identica/i) { # /i to ignore alphabetic case
($PlaceHolder, $IdenticaLogin{"UserName"}, $IdenticaLogin{"Password"}) = split (/:/, $line);
}
}
close (LOGINFILE);

say SendMessage("Twitter", $Status, %TwitLogin) if ($TwitterUse);
say SendMessage("Identica", $Status, %IdenticaLogin) if ($IdenticaUse);
Help if (!defined ($TwitterUse) && !defined ($IdenticaUse));

__END__
To do:
- Use POD format for comments
- Simple GUI interface (1 text box + 1 check box for each Twitter and Identi.ca + 1 "Send" button)
- Create executable file for standalone use without need of a Perl interpreter

History:
...
v0.2.0 (2009/02/21): Added --twitter (-t) and --identica (-i) command line options to select site for updates
Added --help (-h) and --version (-v) command line options
Added undefined argument check for $Message in SendUpdate().
It is getting a bit long to post the whole script on this blog. You can still find the latest version to download here.
There are better ways to include help and comments in code. I will talk about POD soon.

Larry Wall quote of the day:
"We all agree on the necessity of compromise. We just can't agree on when it's necessary to compromise."

Possible next posts:
  • Perl template - Part II: Adding Help and Version procedures
  • Perl help resources
  • Improving on twit.pl: Graphical User interface
  • POD
  • Install Google Analytics on your Blogger blog and stats for DLP

2 comments :

  1. Some new Perl culture for you to learn: subroutine names that are AllSquishedTogether() are frowned upon. The convention is names_like_this() - lowercase, with underscores separating words.

    ReplyDelete
  2. Thanks for the comment!
    I replied to you there:
    http://damienlearnsperl.blogspot.com/2009/03/perl-naming-convention-for-subroutine.html

    ReplyDelete