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.
  1. #!/usr/bin/perl  
  2. use 5.10.0;         # To be able to use "say" function  
  3. use strict;         # Pragma to add restrictions to Perl rules  
  4. use warnings;       # Pragma to add warnings at compile and run-time  
  5. use Getopt::Long;   # To parse the command line  
  6. use Net::Twitter;   # API to twitter.com and identi.ca  
  7.   
  8. my $PROG_NAME = $0# $0 contains the name of the file containing the Perl script being executed  
  9. my $VERSION   = "v0.2.0";  
  10. my $VersionDate = "February 21st 2009";  
  11.   
  12. # ------------------------------------------------------------------------------  
  13. # Name    : Help  
  14. # Comment : displays list of available options  
  15. # Input   : no argument to command line or "-h" or "--help"  
  16. # Output  : help screen and exit program  
  17. # ------------------------------------------------------------------------------  
  18. sub Help {  
  19.     print "   
  20.     Usage:  
  21.         perl $PROG_NAME [-options]          
  22.   
  23.     Options:  
  24.      -a, --all      : send message to all supported macroblogging sites  
  25.      -f, --file     : file containing the login information for each site  
  26.      -h, --help     : this help screen  
  27.      -i, --identica : send message to identi.ca  
  28.      -s, --status   : status (message) to send  
  29.      -t, --twitter  : send message to twitter.com  
  30.      -v, --version  : displays version information  
  31.               
  32.     $PROG_NAME help  
  33.     ";  
  34.     print "-" x (length ($PROG_NAME) + 5);  
  35.     print "  
  36.     $PROG_NAME sends a status update to either twitter.com or identi.ca (or both) from the command line.  
  37.     If -t is specified (for example), it will send the update to twitter.com.  
  38.     If -a is passed, then your status on both twitter and identica will be updated.  
  39.   
  40.     Format of login files  
  41.     ---------------------  
  42.     $PROG_NAME will first try to find \"twit.txt\" located in the current directory.  
  43.     You can also use the --file option to tell $PROG_NAME where to find your login information.  
  44.      - Lines starting with '#' are comments and will be ignored by the script  
  45.      - The last line starting with \"Twitter\" will be parsed for username and password.   
  46.        Each of these fields must be separated by a semi-column ':'  
  47.      - Same thing with \"Identica\"  
  48.           
  49.     Examples:  
  50.     ---------  
  51.      - perl twit.pl -s \"Using Padre on Linux\" -f \"~/secretstuff/mytwitterpassword.txt\" -a  
  52.         Updates both twitter and identica status with login info from specified file  
  53.      - perl twit.pl -s \"This is Vistaaaaaa\" -i  
  54.         Updates identica status only  
  55.   
  56.     $PROG_NAME uses the following modules:  
  57.      - Getopt::Long  
  58.      - Net::Twitter  
  59.     ";  
  60.     exit;  
  61. # End of Help  
  62.   
  63. # ------------------------------------------------------------------------------  
  64. # Name    : version  
  65. # Comment : displays script's version number  
  66. # Input   : --  
  67. # Output  : version number screen and exit program  
  68. # ------------------------------------------------------------------------------  
  69. sub Version {  
  70.     print "  
  71.     $PROG_NAME version: $VERSION  
  72.     Date  : $VersionDate  
  73.     Author: dlp  
  74.           
  75.     Get the latest version from here:  
  76.     http://sites.google.com/site/damienlearnsperl/DLP-scripts  
  77.     ";  
  78.     exit;  
  79. # end of Version  
  80.   
  81. # ------------------------------------------------------------------------------  
  82. # Name    : CreateObject  
  83. # Comment : Creates and returns an instance of the Net::Twitter class  
  84. # Input   : - Input string with value  
  85. #               "twitter" -> twitter.com instance  
  86. #               "identica" -> identi.ca instance  
  87. #               all other values return an error  
  88. #           - Hash with "UserName" and "Password"  
  89. # Output  : Object newly created or 0 if error  
  90. # ------------------------------------------------------------------------------  
  91. sub CreateObject {  
  92.     my $SiteInstance = 0;  
  93.     my $NameString = shift;  
  94.     my %Login = @_;  
  95.   
  96.     $NameString =~ tr/A-Z/a-z/;  
  97.     if ($NameString eq "twitter") {  
  98.         $SiteInstance = Net::Twitter->new(username => $Login{"UserName"}, password => $Login{"Password"});  
  99.     }  
  100.     elsif ($NameString eq "identica") {  
  101.         $SiteInstance = Net::Twitter->new(identica => 1, username => $Login{"UserName"}, password => $Login{"Password"});  
  102.     }  
  103.     return $SiteInstance;  
  104. # End of CreateObject  
  105.   
  106. # ------------------------------------------------------------------------------  
  107. # Name    : SendUpdate  
  108. # Comment : Sends update to Twitter object  
  109. # Input   : - Net::Twitter object  
  110. #           - message string  
  111. # Output  : string "OK" if successful update, string "FAIL" otherwise  
  112. # ------------------------------------------------------------------------------  
  113. sub SendUpdate {  
  114.     my $Site = shift;  
  115.     my $Message = shift;  
  116.     my $SiteName = ($Site->{identica})?"identi.ca":"twitter.com";  
  117.   
  118.     #There's a hard limit on the size of twits for both twitter and identica  
  119.     if (!defined $Message) {  
  120.         return "$SiteName update: FAILED (no message)";  
  121.     }  
  122.     if (length $Message > 140) {  
  123.         return "$SiteName update: FAILED (message over 140 characters)";  
  124.     }  
  125.   
  126.     if ($Site->update($Message)) {  
  127.         return "$SiteName update: OK";  
  128.     }  
  129.     else {  
  130.         return "$SiteName update: FAIL";  
  131.     }  
  132. #End of SendUpdate  
  133.   
  134. # ------------------------------------------------------------------------------  
  135. # Name    : SendMessage  
  136. # Comment : Sends message to chosen macroblogging site  
  137. # Input   : $_[0] = Input string with value  
  138. #               "twitter" -> twitter.com instance  
  139. #               "identica" -> identi.ca instance  
  140. #           $_[1] = Message string to be sent  
  141. #           $_[2] = Hash with "UserName" and "Password" elements  
  142. # Output  : Return string: "Error" if couldn't create object or string from SendUpdate  
  143. # ------------------------------------------------------------------------------  
  144. sub SendMessage {  
  145.     my $ReturnString;  
  146.     my $Instance;  
  147.     my ($SiteName$Message%Login) = @_;  
  148.   
  149.     $Instance = CreateObject($SiteName%Login);  
  150.     if ($Instance) {  
  151.         $ReturnString = SendUpdate($Instance$Message);  
  152.     }  
  153.     else {  
  154.         $ReturnString = "Error with $SiteName creation process";  
  155.     }  
  156.     return $ReturnString;  
  157. #End of SendMessage  
  158.   
  159. # ------------------------------------------------------------------------------  
  160. # Main  
  161. # ------------------------------------------------------------------------------  
  162. my $Status;  
  163. my $PasswordFile;  
  164. my %TwitLogin;  
  165. my %IdenticaLogin;  
  166. my $TwitterUse;  # 1-> send to twitter, 0 -> do not send  
  167. my $IdenticaUse# 1-> send to identica, 0 -> do not send  
  168.   
  169. Help if (@ARGV == 0);  
  170.   
  171. #Parse command line arguments  
  172. GetOptions ("all" => sub {$TwitterUse = 1; $IdenticaUse = 1},  
  173.             "status=s" => \$Status,  
  174.             "file=s" => \$PasswordFile,  
  175.             "help" => \&Help,  
  176.             "identica" => \$IdenticaUse,  
  177.             "twitter" => \$TwitterUse,  
  178.             "version" => \&Version);  
  179.   
  180. # Read Password file passed as argument or twit.txt by default  
  181. $PasswordFile = "twit.txt" unless ($PasswordFile);  
  182. open(LOGINFILE, $PasswordFile) or die "Cannot open \"$PasswordFile\" file: $!\n";  
  183. while (<loginfile>) {  
  184.     my $line = $_;  
  185.     my $PlaceHolder;  
  186.   
  187.     chomp $line;    # Remove trailing newline character  
  188.     next if ($line =~ m/^#/);       # Ignore lines starting with '#'  
  189.     if ($line =~ m/^twitter/i) {    # /^ indicates the beginning of the line  
  190.         ($PlaceHolder$TwitLogin{"UserName"}, $TwitLogin{"Password"}) = split (/:/, $line);  
  191.     }  
  192.     if ($line =~ m/^identica/i) {   # /i to ignore alphabetic case  
  193.         ($PlaceHolder$IdenticaLogin{"UserName"}, $IdenticaLogin{"Password"}) = split (/:/, $line);  
  194.     }  
  195. }  
  196. close (LOGINFILE);  
  197.   
  198. say SendMessage("Twitter"$Status%TwitLoginif ($TwitterUse);  
  199. say SendMessage("Identica"$Status%IdenticaLoginif ($IdenticaUse);  
  200. Help if (!defined ($TwitterUse) && !defined ($IdenticaUse));  
  201.   
  202. __END__  
  203. To do:  
  204. - Use POD format for comments  
  205. - Simple GUI interface (1 text box + 1 check box for each Twitter and Identi.ca + 1 "Send" button)  
  206. - Create executable file for standalone use without need of a Perl interpreter  
  207.   
  208. History:  
  209. ...  
  210. v0.2.0 (2009/02/21): Added --twitter (-t) and --identica (-i) command line options to select site for updates  
  211.                      Added --help (-h) and --version (-v) command line options  
  212.                      Added undefined argument check for $Message in SendUpdate().  
  213. </loginfile>  
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