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

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

Monday, February 23, 2009

How to run wxPerl scripts from Notepad++

Last month, I explained how to launch a Perl script from Notepad++ using the nppexec plugin.
John left a comment asking how to run wxPerl scripts directly from Notepad++.
At the time, I had no idea what wxPerl was.
Now that I have started to work on a GUI implementation of twit.pl, I can finally answer the question:
  • In the Run menu, select the last item: Perl.
    This will start the Perl interpreter and open the GUI application. That's it!
  • There is no shortcut to Run>Perl by default. Here is how to add one:
    Click on Settings>Shortcut Mapper. The following window appears:

    Add Perl entry in Notepad++ shortcut mapper
  • Select the "Run commands" tab and chose your shortcut for the "Perl" command by double-clicking on the Perl line (last one in the table).
  • In this example, I have remapped the Alt+F1 shortcut from the php Search (I also have deleted the php search shortcut by right-clicking on the line and selecting "delete)
I have not found the equivalent feature in Padre.

Perl quote of the day = '

Possible next posts:
  • Improving on twit.pl: using more of the Net::Twitter API
  • Install Google Analytics on your Blogger blog and stats for DLP
  • Improving on twit.pl: Graphical User interface
  • Perl help resources
  • POD

How to run wxPerl scripts from Notepad++

Last month, I explained how to launch a Perl script from Notepad++ using the nppexec plugin.
John left a comment asking how to run wxPerl scripts directly from Notepad++.
At the time, I had no idea what wxPerl was.
Now that I have started to work on a GUI implementation of twit.pl, I can finally answer the question:
  • In the Run menu, select the last item: Perl.
    This will start the Perl interpreter and open the GUI application. That's it!
  • There is no shortcut to Run>Perl by default. Here is how to add one:
    Click on Settings>Shortcut Mapper. The following window appears:

    Add Perl entry in Notepad++ shortcut mapper
  • Select the "Run commands" tab and chose your shortcut for the "Perl" command by double-clicking on the Perl line (last one in the table).
  • In this example, I have remapped the Alt+F1 shortcut from the php Search (I also have deleted the php search shortcut by right-clicking on the line and selecting "delete)
I have not found the equivalent feature in Padre.

Perl quote of the day = '

Possible next posts:
  • Improving on twit.pl: using more of the Net::Twitter API
  • Install Google Analytics on your Blogger blog and stats for DLP
  • Improving on twit.pl: Graphical User interface
  • Perl help resources
  • POD

Saturday, February 21, 2009

First impressions on Padre (running on Windows Vista)

I played with Padre (v0.27) a bit today while working on twit.pl v0.2.0.
Here are my first impressions...
Padre interfaceThe Good
  • When I used the length and print functions, a tip window appeared below the cursor to show the function's prototype and a help message.
  • An "Install Module" menu that lets you install CPAN modules as if you were typing "install ModuleName" in a CPAN shell.
  • The "Automatic bracket completion" feature seems to work pretty well. As a rule, I would recommend leaving it on all the time. It is good practice to write the closing bracket (or parenthesis) right after the opening one and fill in the blank. This feature will do the job for you by adding the closing bracket character automatically.
    The only time when it can be annoyaing is if you go back in the code and add a bracket or parenthesis. Then you will have to delete the extra closing character.
  • The "Toggle comment" command accessible by the right-click menu: just select the lines you want to (un)comment and Padre will add (or remove) the '#' characters in front of the lines for you.
The Bad
  • Just a feeling of "still a lot to do" to compete with an editor like Notepad++ (although Padre really is an IDE, not just an editor)
  • Not "super easy" to install (from CPAN, need to use the "force install Padre" command): no installer file for easy deployment.
  • I entered my second ticket on Padre v0.27 today about an unwanted application exit when trying to view the CPAN config file.
  • You can select your syntax highlighting scheme (Padre, Notepad++, Dark background and UltraEdit at the moment) but there is no way yet to chose formatting by categories (keywords, comments, etc.)
The Ugly
  • Nothing in this category yet as far as I'm concerned

Perl blogroll
On a totally unrelated subject, I just set up the new "Blog List" widget of Blogger. Chek out for yourself on the right. It displays the blogs by order of most recent activity with a link on that last post. I think it looks pretty nice.
Also, I discovered two sites that are aggregates of Perl blogs. There are some slight differences between the two but most of the posts are similar:
Larry Wall quote of the day:
"The world has become a larger place. The universe has been expanding, and Perl's been expanding along with the universe. "

Possible next posts:
  • Install Google Analytics on your Blogger blog and stats for DLP
  • Improving on twit.pl: using more of the Net::Twitter API
  • Some post about Padre
  • Improving on twit.pl: Graphical User interface
  • Perl help resources
  • POD

First impressions on Padre (running on Windows Vista)

I played with Padre (v0.27) a bit today while working on twit.pl v0.2.0.
Here are my first impressions...
Padre interfaceThe Good
  • When I used the length and print functions, a tip window appeared below the cursor to show the function's prototype and a help message.
  • An "Install Module" menu that lets you install CPAN modules as if you were typing "install ModuleName" in a CPAN shell.
  • The "Automatic bracket completion" feature seems to work pretty well. As a rule, I would recommend leaving it on all the time. It is good practice to write the closing bracket (or parenthesis) right after the opening one and fill in the blank. This feature will do the job for you by adding the closing bracket character automatically.
    The only time when it can be annoyaing is if you go back in the code and add a bracket or parenthesis. Then you will have to delete the extra closing character.
  • The "Toggle comment" command accessible by the right-click menu: just select the lines you want to (un)comment and Padre will add (or remove) the '#' characters in front of the lines for you.
The Bad
  • Just a feeling of "still a lot to do" to compete with an editor like Notepad++ (although Padre really is an IDE, not just an editor)
  • Not "super easy" to install (from CPAN, need to use the "force install Padre" command): no installer file for easy deployment.
  • I entered my second ticket on Padre v0.27 today about an unwanted application exit when trying to view the CPAN config file.
  • You can select your syntax highlighting scheme (Padre, Notepad++, Dark background and UltraEdit at the moment) but there is no way yet to chose formatting by categories (keywords, comments, etc.)
The Ugly
  • Nothing in this category yet as far as I'm concerned

Perl blogroll
On a totally unrelated subject, I just set up the new "Blog List" widget of Blogger. Chek out for yourself on the right. It displays the blogs by order of most recent activity with a link on that last post. I think it looks pretty nice.
Also, I discovered two sites that are aggregates of Perl blogs. There are some slight differences between the two but most of the posts are similar:
Larry Wall quote of the day:
"The world has become a larger place. The universe has been expanding, and Perl's been expanding along with the universe. "

Possible next posts:
  • Install Google Analytics on your Blogger blog and stats for DLP
  • Improving on twit.pl: using more of the Net::Twitter API
  • Some post about Padre
  • Improving on twit.pl: Graphical User interface
  • Perl help resources
  • POD

Sunday, February 15, 2009

Perl blogs

As noted by chromatic in his journal, very few Perl blogs seem to link to other Perl blogs.
Tonight, as my little contribution to the community, I will give a list of Perl blogs that I have started to follow. I am sure that I am missing some wonderful links, please fill me in!

Also, I will start a Perl blogroll, please send me your site if you want to be included. The criteria to be accepted:
- talk about programming in general and/or Perl in particular
- have original content
- leave a comment with your url

Perl blogs I subscribe to:
Programming blogs that I read:
What other blogs would you recommend?

Larry Wall quote of the day:
"There is no schedule. We are all volunteers, so we get it done when we get it done. Perl 5 still works fine, and we plan to take the right amount of time on Perl 6."

Possible next posts:
  • Install Google Analytics on your Blogger blog and stats for DLP
  • Improving on twit.pl: using more of the Net::Twitter API
  • Some post about Padre
  • Improving on twit.pl: Graphical User interface
  • Perl help resources
  • POD

Perl blogs

As noted by chromatic in his journal, very few Perl blogs seem to link to other Perl blogs.
Tonight, as my little contribution to the community, I will give a list of Perl blogs that I have started to follow. I am sure that I am missing some wonderful links, please fill me in!

Also, I will start a Perl blogroll, please send me your site if you want to be included. The criteria to be accepted:
- talk about programming in general and/or Perl in particular
- have original content
- leave a comment with your url

Perl blogs I subscribe to:
Programming blogs that I read:
What other blogs would you recommend?

Larry Wall quote of the day:
"There is no schedule. We are all volunteers, so we get it done when we get it done. Perl 5 still works fine, and we plan to take the right amount of time on Perl 6."

Possible next posts:
  • Install Google Analytics on your Blogger blog and stats for DLP
  • Improving on twit.pl: using more of the Net::Twitter API
  • Some post about Padre
  • Improving on twit.pl: Graphical User interface
  • Perl help resources
  • POD

Saturday, February 14, 2009

Installing Padre on Windows via Strawberry Perl

I decided to give Padre a try as a Notepad++ replacement for a Perl development environment, on a suggestion from a reader.
Padre is a work in progress, which is one of the reasons why I would like to get involved in it. The main goal is to look at (and understand) Perl code written by experienced developpers. If I can also contribute in improving the tool, it's all bonus. Plus, it will give me material for this blog :)

Padre installation from CPAN
On the Padre's site, it is written that the installation process is as easy as typing "install Padre" in the cpan shell.
Not in my experience. I had several "Can't locate Wx.pm in @INC" error messages.
At the end of the test report, I received:
Files=20, Tests=534, 61 wallclock secs ( 0.28 usr + 0.11 sys = 0.39 CPU)
Result: FAIL
Failed 10/20 test programs. 142/534 subtests failed.
dmake.EXE: Error code 130, while making 'test_dynamic'
SZABGAB/Padre-0.27.tar.gz
C:\strawberry\c\bin\dmake.EXE test -- NOT OK
//hint// to see the cpan-testers results for installing this module, try:
reports SZABGAB/Padre-0.27.tar.gz
Running make install
make test had returned bad status, won't install without force
Failed during this command:
MBARBON/Wx-0.89.tar.gz : make NO
MDOOTSON/Wx-Perl-ProcessStream-0.11.tar.gz : make_test NO
SZABGAB/Wx-Perl-Dialog-0.04.tar.gz : make_test NO
ADAMK/File-Find-Rule-VCS-1.05.tar.gz : make NO
ADAMK/Module-Inspector-1.05.tar.gz : make_test NO
SZABGAB/Padre-0.27.tar.gz : make_test NO


I ran the reports SZBGAB/Padre-0.27.tar.gz but the result came bask as PASS for MSWin32.
I tried to push my luck with the following command (still in CPAN):

"force install Padre"
and the process went to its end but with the following errors:
Failed during this command:
DANKOGAI/Encode-2.29.tar.gz : make_test FAILED but failure ignored because 'force' in effect
JDHEDDEN/threads-1.71.tar.gz : make_test FAILED but failure ignored because 'force' in effect
MDOOTSON/Wx-Perl-ProcessStream-0.11.tar.gz : make_test FAILED but failure ignored because 'force' in effect


I was able to launch Padre v0.27 by following the instructions from the Padre's site: I created a shortcut with the "C:\strawberry\perl\bin\wperl.exe C:\strawberry\perl\bin\padre" command.

I didn't get to experience much of it because it is getting late and I am going on a trip with my Valentine tomorrow ;)
I still had the time to create a new ticket though...
There'll be more about my impressions on Padre in the posts to come.
When I have played with it a bit and read the docs, I'll start digging in the code.

Larry Wall quote of the day:
"Programmers can be lazy."

Possible next posts:

  • How to install and use Google Analytics on your Blogger blog
  • Improving on twit.pl: using more of the Net::Twitter API
  • Some post about Padre
  • Improving on twit.pl: Graphical User interface
  • Perl help resources
  • POD

Installing Padre on Windows via Strawberry Perl

I decided to give Padre a try as a Notepad++ replacement for a Perl development environment, on a suggestion from a reader.
Padre is a work in progress, which is one of the reasons why I would like to get involved in it. The main goal is to look at (and understand) Perl code written by experienced developpers. If I can also contribute in improving the tool, it's all bonus. Plus, it will give me material for this blog :)

Padre installation from CPAN
On the Padre's site, it is written that the installation process is as easy as typing "install Padre" in the cpan shell.
Not in my experience. I had several "Can't locate Wx.pm in @INC" error messages.
At the end of the test report, I received:
Files=20, Tests=534, 61 wallclock secs ( 0.28 usr + 0.11 sys = 0.39 CPU)
Result: FAIL
Failed 10/20 test programs. 142/534 subtests failed.
dmake.EXE: Error code 130, while making 'test_dynamic'
SZABGAB/Padre-0.27.tar.gz
C:\strawberry\c\bin\dmake.EXE test -- NOT OK
//hint// to see the cpan-testers results for installing this module, try:
reports SZABGAB/Padre-0.27.tar.gz
Running make install
make test had returned bad status, won't install without force
Failed during this command:
MBARBON/Wx-0.89.tar.gz : make NO
MDOOTSON/Wx-Perl-ProcessStream-0.11.tar.gz : make_test NO
SZABGAB/Wx-Perl-Dialog-0.04.tar.gz : make_test NO
ADAMK/File-Find-Rule-VCS-1.05.tar.gz : make NO
ADAMK/Module-Inspector-1.05.tar.gz : make_test NO
SZABGAB/Padre-0.27.tar.gz : make_test NO


I ran the reports SZBGAB/Padre-0.27.tar.gz but the result came bask as PASS for MSWin32.
I tried to push my luck with the following command (still in CPAN):

"force install Padre"
and the process went to its end but with the following errors:
Failed during this command:
DANKOGAI/Encode-2.29.tar.gz : make_test FAILED but failure ignored because 'force' in effect
JDHEDDEN/threads-1.71.tar.gz : make_test FAILED but failure ignored because 'force' in effect
MDOOTSON/Wx-Perl-ProcessStream-0.11.tar.gz : make_test FAILED but failure ignored because 'force' in effect


I was able to launch Padre v0.27 by following the instructions from the Padre's site: I created a shortcut with the "C:\strawberry\perl\bin\wperl.exe C:\strawberry\perl\bin\padre" command.

I didn't get to experience much of it because it is getting late and I am going on a trip with my Valentine tomorrow ;)
I still had the time to create a new ticket though...
There'll be more about my impressions on Padre in the posts to come.
When I have played with it a bit and read the docs, I'll start digging in the code.

Larry Wall quote of the day:
"Programmers can be lazy."

Possible next posts:

  • How to install and use Google Analytics on your Blogger blog
  • Improving on twit.pl: using more of the Net::Twitter API
  • Some post about Padre
  • Improving on twit.pl: Graphical User interface
  • Perl help resources
  • POD

Thursday, February 12, 2009

Hashes and regular expressions

StrawberryPerl v5.10.0.4
I installed StrawberryPerl v5.10.0.4 on top of my existing directory yesterday and it cleaned up my module installs. I didn't have that many installed but it is a good thing to know for next time.

Perl open source project: Padre
Someone commented on my previous post about a Perl open source project to look at. Padre, the Perl integrated development environment (IDE) does look like an interesting project. I will start reading about it.

Twit.pl v0.1.0: reading from file, hashes and regexes
Version 0.1.0 of twit.pl adds the ability to read your twitter and identica login info (username and password) from a file.
The text file must have the following format:
#Should I be writing down my password in a file?
Twitter:TwitterUserName:TwitterPassword
Identica:IdenticaUserName:IdenticaPassword
  • 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 with "Identica"
  • Save it as "twit.txt" in the same directory as twit.pl or use the --file (or -f) option on the command line just like this:
    perl twit.pl -s "Reading DamienLearnsPerl!" -f "~/secretstuff/mytwitterpassword.txt"
    or
    perl twit.pl -s "Watching the snow falling" -f "C:\temp\pass.txt"
With this in mind, let's see how we can parse the file inside our script:
# ------------------------------------------------------------------------------
# Main
# ------------------------------------------------------------------------------
my $Status;
my $PasswordFile;
my %TwitLogin;
my %IdenticaLogin;

#Parse command line arguments
GetOptions ("status=s" => \$Status,
"file=s" => \$PasswordFile);

# 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 (<LOGINFILE>) {
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);


New Perl concepts
  • Notice how global scalar variables $TwitUser = 'twitterlogin' and $TwitPass = 'twitterpasswd' are replaced by %TwitLogin
  • The % in front of TwitLogin means that this variable is a hash. For more details about hashes, see here. Right now, we just need to know that a hash is a list of unsorted scalars. The goal is to use the TwitLogin hash to regroup the user name and password in a single entity. They are individually accessible as scalars with $TwitLogin{"UserName"} and $TwitLogin{"Password"}.
  • Note how the GetOptions command got an extra field for the --file option where the login information will be stored
  • Line open(LOGINFILE, $PasswordFile) will associate the LOGINFILE filehandle to an external file specified in $PasswordFile. By default, it opens the file in input (read) mode. If it is successful, the interpreter will not execute the second part of the line after the or operator. It will consider that the result of the expression (a or b) is true if a == true and will not assess the right part of the operator.
  • The die "msg" function will exit from the program with a message
  • The last error is stored by Perl in the $! string, so printing it in the die message will give you an idea of where the faulty logic was.
  • while() will read each line of the file handled by LOGINFILE until the end of the file.
  • The current line being read is stored in $_. This is another special Perl variable for the default input.
  • The chomp function removes the newline character from a string. In the twit.txt file, each line is terminated by a newline character, so we need to remove it before we can do operations on the line string.
  • next will tell the while loop to stop the execution of statements within the block and go back to testing the exit condition (the 'EOF' (End oF File) character in this case).
  • next if ($line =~ m/^#/); will execute the next command if the current line matches the regular expression between //. There is lots to say about regexes, I'll keep it for another post if you don't mind. ^ marks the beginning of the line and # is the '#' character. The statement then means: "go to the next line in the file if the current line starts with the '#' character".
  • Finally, the split command will separate the line into several strings, based on the separator character (':' in our case). The unnamed array ($PlaceHolder, $IdenticaLogin{"UserName"}, $IdenticaLogin{"Password"}) will contain three elements created from a single string.
Changes to the SendMessage routine
The last two arguments have been replaced by a hash:
# ------------------------------------------------------------------------------
# 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
  • The @_ array is split into individual elements $SiteName, $Message, %Login to improve the readability
  • The CreateObject prototype is also changed to accept a hash as argument
# ------------------------------------------------------------------------------
# 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
SendUpdate() is unchanged except for the inclusion of a suggestion from kreetrapper.
    my $SiteName = "twitter.com";
$SiteName = "identi.ca" if ($Site->{identica});
can be written in a single line using the ? operator:
    my $SiteName = ($Site->{identica})?"identi.ca":"twitter.com";
  • The ? conditonal operator assesses the statement to the left. If it is true, then it will assign "identi.ca" to $Sitename, else "twitter.com" will be stored in the variable.
The complete script can be downloaded from here (or go to http://sites.google.com/site/damienlearnsperl/DLP-scripts to select your version).

Larry Wall quote of the day:
"Hubris itself will not let you be an artist. "

Possible next posts:

  • How to install and use Google Analytics on your Blogger blog
  • Improving on twit.pl: using more of the Net::Twitter API
  • Improving on twit.pl: Graphical User interface
  • Perl help resources
  • POD

Hashes and regular expressions

StrawberryPerl v5.10.0.4
I installed StrawberryPerl v5.10.0.4 on top of my existing directory yesterday and it cleaned up my module installs. I didn't have that many installed but it is a good thing to know for next time.

Perl open source project: Padre
Someone commented on my previous post about a Perl open source project to look at. Padre, the Perl integrated development environment (IDE) does look like an interesting project. I will start reading about it.

Twit.pl v0.1.0: reading from file, hashes and regexes
Version 0.1.0 of twit.pl adds the ability to read your twitter and identica login info (username and password) from a file.
The text file must have the following format:
#Should I be writing down my password in a file?
Twitter:TwitterUserName:TwitterPassword
Identica:IdenticaUserName:IdenticaPassword
  • 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 with "Identica"
  • Save it as "twit.txt" in the same directory as twit.pl or use the --file (or -f) option on the command line just like this:
    perl twit.pl -s "Reading DamienLearnsPerl!" -f "~/secretstuff/mytwitterpassword.txt"
    or
    perl twit.pl -s "Watching the snow falling" -f "C:\temp\pass.txt"
With this in mind, let's see how we can parse the file inside our script:
# ------------------------------------------------------------------------------
# Main
# ------------------------------------------------------------------------------
my $Status;
my $PasswordFile;
my %TwitLogin;
my %IdenticaLogin;

#Parse command line arguments
GetOptions ("status=s" => \$Status,
"file=s" => \$PasswordFile);

# 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 (<LOGINFILE>) {
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);


New Perl concepts
  • Notice how global scalar variables $TwitUser = 'twitterlogin' and $TwitPass = 'twitterpasswd' are replaced by %TwitLogin
  • The % in front of TwitLogin means that this variable is a hash. For more details about hashes, see here. Right now, we just need to know that a hash is a list of unsorted scalars. The goal is to use the TwitLogin hash to regroup the user name and password in a single entity. They are individually accessible as scalars with $TwitLogin{"UserName"} and $TwitLogin{"Password"}.
  • Note how the GetOptions command got an extra field for the --file option where the login information will be stored
  • Line open(LOGINFILE, $PasswordFile) will associate the LOGINFILE filehandle to an external file specified in $PasswordFile. By default, it opens the file in input (read) mode. If it is successful, the interpreter will not execute the second part of the line after the or operator. It will consider that the result of the expression (a or b) is true if a == true and will not assess the right part of the operator.
  • The die "msg" function will exit from the program with a message
  • The last error is stored by Perl in the $! string, so printing it in the die message will give you an idea of where the faulty logic was.
  • while() will read each line of the file handled by LOGINFILE until the end of the file.
  • The current line being read is stored in $_. This is another special Perl variable for the default input.
  • The chomp function removes the newline character from a string. In the twit.txt file, each line is terminated by a newline character, so we need to remove it before we can do operations on the line string.
  • next will tell the while loop to stop the execution of statements within the block and go back to testing the exit condition (the 'EOF' (End oF File) character in this case).
  • next if ($line =~ m/^#/); will execute the next command if the current line matches the regular expression between //. There is lots to say about regexes, I'll keep it for another post if you don't mind. ^ marks the beginning of the line and # is the '#' character. The statement then means: "go to the next line in the file if the current line starts with the '#' character".
  • Finally, the split command will separate the line into several strings, based on the separator character (':' in our case). The unnamed array ($PlaceHolder, $IdenticaLogin{"UserName"}, $IdenticaLogin{"Password"}) will contain three elements created from a single string.
Changes to the SendMessage routine
The last two arguments have been replaced by a hash:
# ------------------------------------------------------------------------------
# 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
  • The @_ array is split into individual elements $SiteName, $Message, %Login to improve the readability
  • The CreateObject prototype is also changed to accept a hash as argument
# ------------------------------------------------------------------------------
# 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
SendUpdate() is unchanged except for the inclusion of a suggestion from kreetrapper.
    my $SiteName = "twitter.com";
$SiteName = "identi.ca" if ($Site->{identica});
can be written in a single line using the ? operator:
    my $SiteName = ($Site->{identica})?"identi.ca":"twitter.com";
  • The ? conditonal operator assesses the statement to the left. If it is true, then it will assign "identi.ca" to $Sitename, else "twitter.com" will be stored in the variable.
The complete script can be downloaded from here (or go to http://sites.google.com/site/damienlearnsperl/DLP-scripts to select your version).

Larry Wall quote of the day:
"Hubris itself will not let you be an artist. "

Possible next posts:

  • How to install and use Google Analytics on your Blogger blog
  • Improving on twit.pl: using more of the Net::Twitter API
  • Improving on twit.pl: Graphical User interface
  • Perl help resources
  • POD

Wednesday, February 11, 2009

Refactoring the twit.pl Perl script to interact with twitter.com and identi.ca

Today I'll be revisiting the twit.pl script.
The new version (0.0.2) will be a (hopefully) good practice on the use of functions (or subroutines).

Version numbering explained
A short sidenote about the version numbering that I use (x.y.z):
- I'll be updating the last number (z) when the program interface stays the same and only internal changes are made.
- The middle number (y) will be increased when a new functionality is introduced
- The first part (x) notifies the user that they have to change the way that they are using the program (command format changed for example).

In version 0.0.1 of the code, you'll have noticed that we repeat almost the same code at the end for twitter.com and identi.ca updates:
if ($TwitterUse) {
my $twitter = Net::Twitter->new(username => $TwitUser, password => $TwitPass);
if ($twitter->update($Status)) {
say "twitter: OK";
}
else {
say "twitter: FAIL";
}
}
if ($IdenticaUse) {
my $identica = Net::Twitter->new(identica => 1, username => $IdenticaUser, password => $IdenticaPass);
if ($identica->update($Status)) {
say "Identica: OK";
}
else {
say "Identica: FAIL";
}
}
twit.pl v0.0.2
I'll try to refactor the code by using functions. In the end, I want the result to be something like:
say SendMessage("Twitter", $Status, $TwitUser, $TwitPass) if ($TwitterUse);
say SendMessage("Identica", $Status, $IdenticaUser, $IdenticaPass) if ($IdenticaUse);
SendMessage is a function that will have for:
- input = "twitter" or "identica" string, status string (your message), login, password
- output = string with update status or error message

Notice how Perl lets you add the if(..) statement at the end of the line when there is only one statement inside the if block.
SendMessage() returns a string, so we can feed it directly to the print or say function.
Here is how I implemented the SendMessage() subroutine:
# ------------------------------------------------------------------------------
# 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] = User name (login)
# $_[3] = Password
# Output : Return string: "Error" if couldn't create object or string from SendUpdate
# ------------------------------------------------------------------------------
sub SendMessage {
my $ReturnString;
my $Instance;

$Instance = CreateObject($_[0], $_[2], $_[3]);
if ($Instance) {
$ReturnString = SendUpdate($Instance, $_[1]);
}
else {
$ReturnString = "Error with $_[0] creation process";
}
return $ReturnString;
} #End of SendMessage
New Perl concepts introduced by SendMessage():
  • A subroutine if defined by the sub keyword followed by the function name and a {} block
  • Arguments are passed to a function between parenthesis and are separated by commas: subroutine(arg1, arg2);
  • Arguments are retrieved by the function inside the @_ array. An array is a list of individual values. As scalars are prefixed by the $ symbols, arrays are recognized by the @ sign preceding their name.
  • Individual values can be accessed within a list. For example, the second element of @ToDoList is $ToDoList[1] (subscripts start from 0)
  • $_[0] is therefore the first parameter passed to the function. In the SendMessage example, that would be the website identifier ("twitter" or "identica").
  • You can only return one scalar or one list directly from a function by means of the return keyword.
  • In the SendMessage example, $ReturnString and $Instance only exist within the subroutine's {} block because of the my keyword. They cease to exist outside of SendMessage's scope.
In SendMessage, we call two other functions:
  • CreateObject() which will return the newly created Net::Twitter instance. It is inside CreateObject() that we will differentiate the twitter and identica cases.
  • SendUpdate() will send the message to the twitter or identica instance.
CreateObject() introduces a few new Perl contructs:
# ------------------------------------------------------------------------------
# Name : CreateObject
# Comment : Creates and returns an instance of Net::Twitter
# Input : - Input string with value
# "twitter" -> twitter.com instance
# "identica" -> identi.ca instance
# all other values return an error
# - User name (login)
# - Password
# Output : Object newly created or 0 if error
# ------------------------------------------------------------------------------
sub CreateObject {
my $SiteInstance = 0;
my $NameString = shift;
# After shift, UserName parameter becomes $_[0] (was $_[1] before)
# and Password parameter becomes $_[1] (was $_[2] before)

$NameString =~ tr/A-Z/a-z/;
if ($NameString eq "twitter") {
$SiteInstance = Net::Twitter->new(username => $_[0], password => $_[1]);
}
elsif ($NameString eq "identica") {
$SiteInstance = Net::Twitter->new(identica => 1, username => $_[0], password => $_[1]);
}
return $SiteInstance;
} # End of CreateObject
New Perl concepts introduced by CreateObject():
  • First one is the shift operator. It removes a single element from the argument list. So if we have @_ = ("site", "username", "password") passed as parameters to CreateObject, the line my $NameString = shift; will store "site" in $NameString and @_ will become ("username", "password").
  • Hence, username which used to be referred to as $_[1] now becomes $_[0]
  • $NameString =~ tr/A-Z/a-z/; uses the transliteration operator tr///. It transforms a set of characters (A-Z: means ASCII characters 'A', 'B', ..., 'Z') into another set of characters (a-z: means ASCII 'a', 'b', ..., 'z'). The transliteration is bound to a string via the =~operator. In our example, we make sure that $NameString only contains lowercase characters for easy comparison.
The SendUpdate sub is pretty straightforward:
# ------------------------------------------------------------------------------
# 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 = "twitter.com";

$SiteName = "identi.ca" if ($Site->{identica});
#There's a hard limit on the size of twits for both twitter and identica
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
New Perl concepts introduced by SendUpdate():
  • Nothing new really. Just note the weird construct of $Site->{identica}. For the moment, I won't analyze too much, suffice to know that when it is true, then we are dealing with an identi.ca object.
Well, that rounds it up for today: a lot of new things to review.
Version 0.0.2 doesn't add any functionality compared with v0.0.1 but has close to 50% extra code. Was it worth the effort? Probably not, as all identica-specific code is not located in one single function. However, each logical block is well separated now and can be reused through the program later.
Do you see any other way to improve twit.pl v0.0.2?

I have created a site with Google Sites to host all the scripts that I talk about in this blog. You can go take a look at http://sites.google.com/site/damienlearnsperl/DLP-scripts.
I also added a link to the side -->

Finally, I will intersperse (or even replace) French expressions of the day with Larry Wall quotes (taken from here and here for example) and other sayings by Perl personnalities.

Larry Wall quote of the day:
"The three chief virtues of a programmer are: Laziness, Impatience and Hubris."

Possible next posts:
  • Improving on twit.pl: reading from file
  • How to install and use Google Analytics on your Blogger blog
  • Improving on twit.pl: using more of the Net::Twitter API
  • Improving on twit.pl: Graphical User interface
  • Perl help resources
  • POD

Refactoring the twit.pl Perl script to interact with twitter.com and identi.ca

Today I'll be revisiting the twit.pl script.
The new version (0.0.2) will be a (hopefully) good practice on the use of functions (or subroutines).

Version numbering explained
A short sidenote about the version numbering that I use (x.y.z):
- I'll be updating the last number (z) when the program interface stays the same and only internal changes are made.
- The middle number (y) will be increased when a new functionality is introduced
- The first part (x) notifies the user that they have to change the way that they are using the program (command format changed for example).

In version 0.0.1 of the code, you'll have noticed that we repeat almost the same code at the end for twitter.com and identi.ca updates:
if ($TwitterUse) {
my $twitter = Net::Twitter->new(username => $TwitUser, password => $TwitPass);
if ($twitter->update($Status)) {
say "twitter: OK";
}
else {
say "twitter: FAIL";
}
}
if ($IdenticaUse) {
my $identica = Net::Twitter->new(identica => 1, username => $IdenticaUser, password => $IdenticaPass);
if ($identica->update($Status)) {
say "Identica: OK";
}
else {
say "Identica: FAIL";
}
}
twit.pl v0.0.2
I'll try to refactor the code by using functions. In the end, I want the result to be something like:
say SendMessage("Twitter", $Status, $TwitUser, $TwitPass) if ($TwitterUse);
say SendMessage("Identica", $Status, $IdenticaUser, $IdenticaPass) if ($IdenticaUse);
SendMessage is a function that will have for:
- input = "twitter" or "identica" string, status string (your message), login, password
- output = string with update status or error message

Notice how Perl lets you add the if(..) statement at the end of the line when there is only one statement inside the if block.
SendMessage() returns a string, so we can feed it directly to the print or say function.
Here is how I implemented the SendMessage() subroutine:
# ------------------------------------------------------------------------------
# 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] = User name (login)
# $_[3] = Password
# Output : Return string: "Error" if couldn't create object or string from SendUpdate
# ------------------------------------------------------------------------------
sub SendMessage {
my $ReturnString;
my $Instance;

$Instance = CreateObject($_[0], $_[2], $_[3]);
if ($Instance) {
$ReturnString = SendUpdate($Instance, $_[1]);
}
else {
$ReturnString = "Error with $_[0] creation process";
}
return $ReturnString;
} #End of SendMessage
New Perl concepts introduced by SendMessage():
  • A subroutine if defined by the sub keyword followed by the function name and a {} block
  • Arguments are passed to a function between parenthesis and are separated by commas: subroutine(arg1, arg2);
  • Arguments are retrieved by the function inside the @_ array. An array is a list of individual values. As scalars are prefixed by the $ symbols, arrays are recognized by the @ sign preceding their name.
  • Individual values can be accessed within a list. For example, the second element of @ToDoList is $ToDoList[1] (subscripts start from 0)
  • $_[0] is therefore the first parameter passed to the function. In the SendMessage example, that would be the website identifier ("twitter" or "identica").
  • You can only return one scalar or one list directly from a function by means of the return keyword.
  • In the SendMessage example, $ReturnString and $Instance only exist within the subroutine's {} block because of the my keyword. They cease to exist outside of SendMessage's scope.
In SendMessage, we call two other functions:
  • CreateObject() which will return the newly created Net::Twitter instance. It is inside CreateObject() that we will differentiate the twitter and identica cases.
  • SendUpdate() will send the message to the twitter or identica instance.
CreateObject() introduces a few new Perl contructs:
# ------------------------------------------------------------------------------
# Name : CreateObject
# Comment : Creates and returns an instance of Net::Twitter
# Input : - Input string with value
# "twitter" -> twitter.com instance
# "identica" -> identi.ca instance
# all other values return an error
# - User name (login)
# - Password
# Output : Object newly created or 0 if error
# ------------------------------------------------------------------------------
sub CreateObject {
my $SiteInstance = 0;
my $NameString = shift;
# After shift, UserName parameter becomes $_[0] (was $_[1] before)
# and Password parameter becomes $_[1] (was $_[2] before)

$NameString =~ tr/A-Z/a-z/;
if ($NameString eq "twitter") {
$SiteInstance = Net::Twitter->new(username => $_[0], password => $_[1]);
}
elsif ($NameString eq "identica") {
$SiteInstance = Net::Twitter->new(identica => 1, username => $_[0], password => $_[1]);
}
return $SiteInstance;
} # End of CreateObject
New Perl concepts introduced by CreateObject():
  • First one is the shift operator. It removes a single element from the argument list. So if we have @_ = ("site", "username", "password") passed as parameters to CreateObject, the line my $NameString = shift; will store "site" in $NameString and @_ will become ("username", "password").
  • Hence, username which used to be referred to as $_[1] now becomes $_[0]
  • $NameString =~ tr/A-Z/a-z/; uses the transliteration operator tr///. It transforms a set of characters (A-Z: means ASCII characters 'A', 'B', ..., 'Z') into another set of characters (a-z: means ASCII 'a', 'b', ..., 'z'). The transliteration is bound to a string via the =~operator. In our example, we make sure that $NameString only contains lowercase characters for easy comparison.
The SendUpdate sub is pretty straightforward:
# ------------------------------------------------------------------------------
# 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 = "twitter.com";

$SiteName = "identi.ca" if ($Site->{identica});
#There's a hard limit on the size of twits for both twitter and identica
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
New Perl concepts introduced by SendUpdate():
  • Nothing new really. Just note the weird construct of $Site->{identica}. For the moment, I won't analyze too much, suffice to know that when it is true, then we are dealing with an identi.ca object.
Well, that rounds it up for today: a lot of new things to review.
Version 0.0.2 doesn't add any functionality compared with v0.0.1 but has close to 50% extra code. Was it worth the effort? Probably not, as all identica-specific code is not located in one single function. However, each logical block is well separated now and can be reused through the program later.
Do you see any other way to improve twit.pl v0.0.2?

I have created a site with Google Sites to host all the scripts that I talk about in this blog. You can go take a look at http://sites.google.com/site/damienlearnsperl/DLP-scripts.
I also added a link to the side -->

Finally, I will intersperse (or even replace) French expressions of the day with Larry Wall quotes (taken from here and here for example) and other sayings by Perl personnalities.

Larry Wall quote of the day:
"The three chief virtues of a programmer are: Laziness, Impatience and Hubris."

Possible next posts:
  • Improving on twit.pl: reading from file
  • How to install and use Google Analytics on your Blogger blog
  • Improving on twit.pl: using more of the Net::Twitter API
  • Improving on twit.pl: Graphical User interface
  • Perl help resources
  • POD