Cover | Table of Contents
http://www.cpan.org/) that solve so many problems effectively. That brings up a smaller problem, though—choosing an appropriate module for the job.http://search.cpan.org/ helps, but if you visit the site many times a day, the steps to start a search through the web interface can become annoying. Fortunately, the Mozilla family of web browsers, including Mozilla Firefox, let you set up shortcuts that make browsing much easier. These shortcuts are just bookmarked URLs with substitutable sections and keywords, but they're very powerful and useful—almost command-line aliases ("Make the Most of Shell Aliases" [Hack #4]) for your browser.http://www.cpan.org/) that solve so many problems effectively. That brings up a smaller problem, though—choosing an appropriate module for the job.http://search.cpan.org/ helps, but if you visit the site many times a day, the steps to start a search through the web interface can become annoying. Fortunately, the Mozilla family of web browsers, including Mozilla Firefox, let you set up shortcuts that make browsing much easier. These shortcuts are just bookmarked URLs with substitutable sections and keywords, but they're very powerful and useful—almost command-line aliases ("Make the Most of Shell Aliases" [Hack #4]) for your browser.http://search.cpan.org/search?mode=module;query=%s
Acme in their names.
perldoc utility—and not just from the command line. These docs cover everything from the core language and tutorials through the standard library and any additional modules you install or even write. perldoc can do more, though.perlfunc document lists
every built-in operator in the language in alphabetical order. If you need to know the order of arguments to substr( ), you could type perldoc perlfunc, and then search for the correct occurrence of substr.less on a Unix-like system, use the forward slash (/) to begin a search. Type the rest of the name and hit Enter to begin searching. Press n to find the next occurrence and N to find the previous one.perldoc's -f switch searches perlfunc for you, presenting only the documentation for the named operator. Type instead:$ perldoc -f substr
substr. Handy.perlfaq and nine other documents (perlfaq1 through perlfaq9) full of frequently asked questions and their answers.perlfunc. (Do skim perlfaq once in a while to see what questions there are, though.) Fortunately, the -q switch allows you to specify a search pattern for FAQ keywords.$ perldoc -q shuffle
-f switch, this will launch your favorite pager to view every question with the term perldoc is a fine way to view the
documentation for Perl and all your installed modules and to output them in the file format of your choice ("Put Perldoc to Work" [Hack #2]). perldoc's little brother, podwebserver, is an even handier way to browse documentation—and bookmark it, and search it, and sometimes even hardcopy it, all through whatever web browser you're using this week.podwebserver provides basically perldoc-as-HTML over HTTP. Sure, you could always just browse the documentation at http://search.cpan.org/—but using podwebserver means that you'll be seeing the documentation for exactly your system's Perl version and module versions.podwebserver's HTML is compatible with fancy browsers as well as with more lightweight tools such as lynx, elinks, or even the w3m browser in Emacs. In fact, there have been persistent rumors of some users adventurously accessing podwebserver via cell phones, or even using something called "the Micro-Soft Internet Explorer." O'Reilly Media, Inc. can neither confirm nor deny these rumors.podwebserver isn't on your system, install the Pod::Webserver module from CPAN.podwebserver, just start it from the command line. You don't need root access:$ podwebserver
http://localhost:8020/. You'll see the index of the installed documentation (Figure 1-2).
localhost, or if you have something already running on port 8020, use the -H and -p arguments to change the host and port.$ podwebserver -H windwheel -p 8080
realias command. Normally creating a persistent alias means adding something to your .bashrc (or equivalent) file, starting a new shell, testing it, and then repeating the process until you get it right. Wouldn't it be nice to be able to edit and test a new alias in a single process?source ~/.aliases
alias realias='$EDITOR ~/.aliases; source ~/.aliases'
tcsh, edit your .cshrc file. Then replace the = sign with a single space in all of the alias declarations.realias and your favorite editor (assuming you have the EDITOR environment variable set, and if you don't something is weird) will open with your ~/.aliases file. Add a line and save and quit:alias reperl='perl -de0'
reperl
at the command line:$ reperl Loading DB routines from perl5db.pl version 1.28 Editor support available. Enter h or 'h h' for help, or 'man perldebug' for more help. main::(-e:1): 0 DB<1> q
sub find_the_factorial_of
{
my ($the_number_whose_factorial_I_want) = @_;
return 1 if $the_n<CTRL-N> <= 1;
return $the_n<CTRL-N> * find<CTRL-N>($the_n<CTRL-N> - 1);
}
set iskeyword+=:
use Sub::Normal; my $sub = Sub<CTRL-N>->new( ); # Expands to: Sub::Normal->new( )
Sub::Normal once, as part of the initial use statement. That really isn't as Lazy as it could be. It would be much better if Vim just magically knew about all the Perl modules you have installed and could cleverly autocomplete their names from the very first time you used them.http://math.berkeley.edu/~ilya/software/emacs/. Save it to an Emacs library directory. Then enable it for .pl and .pm files by adding nine lines to your ~/.emacs file:(load-library "cperl-mode")
(add-to-list 'auto-mode-alist '("\\\\.[Pp][LlMm][Cc]?$" . cperl-mode))
(while (let ((orig (rassoc 'perl-mode auto-mode-alist)))
(if orig (setcdr orig 'cperl-mode))))
(while (let ((orig (rassoc 'perl-mode interpreter-mode-alist)))
(if orig (setcdr orig 'cperl-mode))))
(dolist (interpreter '("perl" "perl5" "miniperl" "pugs"))
(unless (assoc interpreter interpreter-mode-alist)
(add-to-list 'interpreter-mode-alist (cons interpreter 'cperl-mode))))
perldoc, but does not associate it with any key by default. To put it at your fingertips, add one line to your .emacs file:(global-set-key "\\M-p" 'cperl-perldoc) ; alt-p
(global-set-key "\\M-p" '(lambda ( ) (interactive) (require 'w3m) (w3m-goto-url "http://localhost:8020/") ))
Pod::Webserver page:(global-set-key "\\M-p" '(lambda ( ) (interactive) (start-process "" nil "firefox" "http://localhost:8020/" ; Or however you launch your favorite browser, like: ; "gnome-terminal" "-e" "lynx http://localhost:8020/" ; "xterm" "-e" "elinks http://localhost:8020/" )))
perltidy can help untangle and bring consistency to even the scariest code.Perl::Tidy. This will
also install the perltidy utility. Now you can use it!perltidy on a Perl program or module and it will write out a tidied version of that file with a .tdy suffix. For example, given poorly_written_script.pl, perltidy will, if possible, reformat the code and write the new version to poorly_written_script.pl.tdy. You can then run tests against the new code to verify that it performs just as did the previous version (even if it is much easier to read).$_= <<'EOL';
$url = URI::URL->new( "http://www/" ); die if $url eq "xXx";
EOL
LOOP:{print(" digits"),redo LOOP if/\\G\\d+\\b[,.;]?\\s*/gc;print(" lowercase"),
redo LOOP if/\\G[a-z]+\\b[,.;]?\\s*/gc;print(" UPPERCASE"),redo LOOP
if/\\G[A-Z]+\\b[,.;]?\\s*/gc;print(" Capitalized"),
redo LOOP if/\\G[A-Z][a-z]+\\b[,.;]?\\s*/gc;
print(" MiXeD"),redo LOOP if/\\G[A-Za-z]+\\b[,.;]?\\s*/gc;print(
" alphanumeric"),redo LOOP if/\\G[A-Za-z0-9]+\\b[,.;]?\\s*/gc;print(" line-noise"
),redo LOOP if/\\G[^A-Za-z0-9]+/gc;print". That's all!\\n";}
$_ = <<'EOL';
$url = URI::URL->new( "http://www/" ); die if $url eq "xXx";
EOL
LOOP: {
print(" digits"), redo LOOP if /\\G\\d+\\b[,.;]?\\s*/gc;
print(" lowercase"), redo LOOP if /\\G[a-z]+\\b[,.;]?\\s*/gc;
print(" UPPERCASE"), redo LOOP if /\\G[A-Z]+\\b[,.;]?\\s*/gc;
print(" Capitalized"), redo LOOP if /\\G[A-Z][a-z]+\\b[,.;]?\\s*/gc;
print(" MiXeD"), redo LOOP if /\\G[A-Za-z]+\\b[,.;]?\\s*/gc;
print(" alphanumeric"), redo LOOP if /\\G[A-Za-z0-9]+\\b[,.;]?\\s*/gc;
print(" line-noise"), redo LOOP if /\\G[^A-Za-z0-9]+/gc;
print ". That's all!\\n";
}filetype plugin on
" perl_synwrite.vim: check syntax of Perl before writing
" latest version at: http://www.vim.org/scripts/script.php?script_id=896
"" abort if b:did_perl_synwrite is true: already loaded or user pref
if exists("b:did_perl_synwrite")
finish
endif
let b:did_perl_synwrite = 1
"" set buffer :au pref: if defined globally, inherit; otherwise, false
if (exists("perl_synwrite_au") && !exists("b:perl_synwrite_au"))
let b:perl_synwrite_au = perl_synwrite_au
elseif !exists("b:perl_synwrite_au")
let b:perl_synwrite_au = 0
endif
"" set buffer quickfix pref: if defined globally, inherit; otherwise, false
if (exists("perl_synwrite_qf") && !exists("b:perl_synwrite_qf"))
let b:perl_synwrite_qf = perl_synwrite_qf
elseif !exists("b:perl_synwrite_qf")
let b:perl_synwrite_qf = 0
endif
"" execute the given do_command if the buffer is syntactically correct perl
"" -- or if do_anyway is true
function! s:PerlSynDo(do_anyway,do_command)
let command = "!perl -c"
if (b:perl_synwrite_qf)
" this env var tells Vi::QuickFix to replace "-" with actual filename
let $VI_QUICKFIX_SOURCEFILE=expand("%")
let command = command . " -MVi::QuickFix"
endif
" respect taint checking
if (match(getline(1), "^#!.\\\\+perl.\\\\+-T") = = 0)
let command = command . " -T"
endif
exec "write" command
silent! cgetfile " try to read the error file
if !v:shell_error || a:do_anyway
exec a:do_command
set nomod
endif
endfunction
"" set up the autocommand, if b:perl_synwrite_au is true
if (b:perl_synwrite_au > 0)
let b:undo_ftplugin = "au! perl_synwrite * " . expand("%")
augroup perl_synwrite
exec "au BufWriteCmd,FileWriteCmd " . expand("%") .
" call s:PerlSynDo(0,\\"write <afile>\\")"
augroup END
endif
"" the :Write command
command -buffer -nargs=* -complete=file -range=% -bang Write call \\
s:PerlSynDo("<bang>"= ="!","<line1>,<line2>write<bang> <args>")Perl::Tidy be your first code review—on every Subversion checkin!
perltidy against all of their code ("Enforce Local Style" [Hack #7]) before every checkin is unrealistic, especially because this is tedious work. Fortunately, this is an automatable process. If you use Subversion (or Svk), it's easy to write a hook that checks code for tidiness, however you define it.perl /usr/local/bin/check_tidy_file.pl "$REPOS" "$REV"
chmod +x on Unix.#!/usr/bin/perl
use strict;
use warnings;
use Perl::Tidy;
use File::Temp;
use File::Spec::Functions;
my $svnlook = '/usr/bin/svnlook';
my $diff = '/usr/bin/diff -u';
# eat the arguments so as not to confuse Perl::Tidy
my ($repo, $rev) = @ARGV;
@ARGV = ( );
my @diffs;
for my $changed_file (get_changed_perl_files( $repo, $rev ))
{
my $source = get_revision( $repo, $rev, $changed_file );
Perl::Tidy::perltidy( source => \\$source, destination => \\(my $dest) );
push @diffs, get_diff( $changed_file, $source, $dest );
}
report_diffs( @diffs );
sub get_changed_perl_files
{
my ($repo, $rev) = @_;
my @files;
for my $change (\Q$svnlook changed $repo -r $rev\Q)
{
my ($status, $file) = split( /\\s+/, $change );
next unless $file =~ /\\.p[lm]\\z/;
push @files, $file;
}
return @files;
}
sub get_revision
{
my ($repo, $rev, $file) = @_;
return scalar \Q$svnlook cat $repo -r $rev $file\Q;
}
sub get_diff
{
my $filename = shift;
return if $_[0] eq $_[1];
my $dir = File::Temp::tempdir( );
my @files = map { catdir( $dir, $filename . $_ ) } qw( .orig .tidy );
for my $file (@files)
{
open( my $out, '>', $file ) or die "Couldn't write $file: $!\\n";
print $out shift;
close $out;
}
return scalar \Q$diff @files\Q;
}
sub report_diffs
{
for my $diff (@_)
{
warn "Error:\\n$diff\\n";
}
}!perl -Ilib/ t/test_program.t in vi's command mode. This breaks the "tweak, run" rhythm.map ,t <Esc>:!prove -vl %<CR>
prove program to run your tests. prove is a handy little program distributed with and designed to run your tests through Test::Harness. The switches are v (vee), which tells prove to run in "verbose" mode and show all of the test output, and l (ell), which tells prove to add lib/ to @INC.I switch to add a different path to @INC.map ,t <Esc>:!prove -Iwork/ -v %<CR>
,T (comma capital tee) to pipe the results through your favorite pager.map ,T <Esc>:!prove -lv % \\| less<CR>
time( ). In Perl, that's print time() 8>>;. You could use the shell-command command (normally on Control-Alt-One), and enter:perl -e 'print time( ) >> 8;'
% perl -de1 Loading DB routines from perl5db.pl version 1.27 Editor support available. Enter h or \Qh h' for help, or \Qman perldebug' for more help. main::(-e:1): 1 DB<1> p time( ) >> 8 4448317 DB<2>
use YAML 'DumpFile';
use POSIX 'strftime';
local $YAML::UseBlock = 1;
exit 1 unless -d 'posts';
my @posts = <posts/*.yaml>;
my $file = 'posts/' . ( @posts + 1 ) . '.yaml';
my $fields =
{
title => '',
date => strftime( '%d %B %Y', localtime( ) ),
text => "\\n\\n",
};
DumpFile( $file, $fields );
system( $ENV{EDITOR}, $file ) = = 0
or die "Error launching $ENV{EDITOR}: $!\\n";use YAML 'DumpFile';
use POSIX 'strftime';
local $YAML::UseBlock = 1;
exit 1 unless -d 'posts';
my @posts = <posts/*.yaml>;
my $file = 'posts/' . ( @posts + 1 ) . '.yaml';
my $fields =
{
title => '',
date => strftime( '%d %B %Y', localtime( ) ),
text => "\\n\\n",
};
DumpFile( $file, $fields );
system( $ENV{EDITOR}, $file ) = = 0
or die "Error launching $ENV{EDITOR}: $!\\n";
EDITOR environment variable set to your preferred editor, this program creates a new blank post in the posts/ subdirectory with the appropriate id (monotonically increasing, of course), then drops you in your editor to edit the YAML file. It has already populated the date field with the current date in the proper format. Additionally, setting $YAML::UseBlockprint "> ";
while (my $next_cmd = <>)
{
chomp $next_cmd;
process($next_cmd);
print "> ";
}
print "> " if -t *ARGV && -t select;
while (my $next_cmd = <>)
{
chomp $next_cmd;
process($next_cmd);
print "> " if -t *ARGV && -t select;
}
-t test checks whether its filehandle argument is connected to a terminal. To handle interactive cases correctly, you need to check both that you're reading from a terminal (-t *ARGV) and that you're writing to one (-t
select). It's a common mistake to mess those tests up, and write instead:print "> " if -t *STDIN && -t *STDOUT;
<> operator doesn't read from STDIN; it reads from ARGV. If there are filenames specified on the command line, those two filehandles aren't the same. Likewise, although print usually writes to STDOUT, it won't if you've explicitly select-ed some other destination. You need to call select with no arguments to get the filehandle which each print will currently target.print "> " if -t *ARGV && -t select;
ARGV filehandle is magically self-opening, but only magically self-opens during the first read operation on it. If you haven't already done at least one <> before you start prompting for input, then the ARGV handle won't be open yet, so the first -t *ARGV test (the one before the while loop) won't be true, and the first prompt won't print.my $offset;
print "Enter an offset: " if is_interactive;
GET_OFFSET:
while (<>)
{
chomp;
if (m/\\A [+-] \\d+ \\z/x)
{
$offset = $_;
last GET_OFFSET;
}
print "Enter an offset (please enter an integer): "
if is_interactive;
}
prompt( ) subroutine provided by the IO::Prompt CPAN module. Instead of all the above infrastructure code, just write:use IO::Prompt; my $offset = prompt( "Enter an offset: ", -integer );
prompt( ) prints the string you give it, reads a line from standard input, chomps it, and then tests the input value against any constraint you specify (for example, -integer). If the constraint is not satisfied, the prompt repeats, along with a clarification of what was wrong. When the user finally enters an acceptable value, prompt( ) returns it.prompt( ) is smart enough not to bother writing out any prompts if the application isn't running interactively, so you don't have to code explicitly for that case.prompt( ) has a general mechanism for telling it what kind of input you need and how to ask for that input. For example:my $hex_num = prompt( "Enter a hex number> ",
-req => { "A *hex* number please!> " => qr/^[0-9A-F]+$/i }
);
print "That's ", hex($hex_num), " in base 10\\n";http://www.growl.info/) is a small
utility
for Mac OS X that allows any application to send notifications to the user. The notifications pop up as a small box in a corner of the screen, overlayed on the current active window (as shown in Figure 2-1).
Mac::Growl. The first thing you have to do is tell Growl that your script wants to send notifications. The following code registers a script named growlalert and tells Growl that it sends alert
notifications:use Mac::Growl;
Mac::Growl::RegisterNotifications(
'growlalert', # application name
[ 'alert' ], # notifications this app sends
[ 'alert' ], # enable these notifications
);
PostNotification( ), passing the name of the script, the kind of notification to send, a title, and a description:Mac::Growl::PostNotification(
'growlalert', # application name
'alert', # type of notification
"This is a title",
"This is a description.",
);
use SDL::App; # open a 640x480 window for your application our $app = SDL::App->new(-width => 640, -height => 480); # create a surface out of an image file specified on the command-line our $img = SDL::Surface->new( -name => $ARGV[0] ); # blit the surface onto the window of your application $img->blit( undef, $app, undef ); # flush all pending screen updates $app->flip( ); # sleep for 3 seconds to let the user view the image sleep 3;
undef parameter values with instances of SDL::Rect, the first one specifying the rectangle to copy from the source surface, and the second specifying the rectangle where to blit on the destination surface. When you use undef instead, SDL uses top-left positioning and full sizing. Here's a blit replacement that specifies a 100x100 area in the source surface at a horizontal offset of 200 pixels:$img->blit( SDL::Rect->new(
-width => 100, -height => 100, -x => 200, -y => 0
), $app, undef);
Module::Build and
ExtUtils::MakeMaker provide user prompting features to ask questions and get answers. The benefit of this is that they silently accept the defaults during automated installations. Users at the keyboard can still answer a prompt, while users who just want the software to install won't launch the installer, turn away, and return an hour later to find that another prompt has halted the process in the meantime.Module::Build is easier to extend, so here's a simple subclass that allows you to specify questions, default values, and configuration keys before writing out a standard module containing this information:package Module::Build::Configurator;
use strict;
use warnings;
use base 'Module::Build';
use SUPER;
use File::Path;
use Data::Dumper;
use File::Spec::Functions;
sub new
{
my ($class, %args) = @_;
my $self = super( );
my $config = $self->notes( 'config_data' ) || { };
for my $question ( @{ $args{config_questions} } )
{
my ($q, $name, $default) = map { defined $_ ? $_ : '' } @$question;
$config->{$name} = $self->prompt( $q, $default );
}
$self->notes( 'config_module', $args{config_module} );
$self->notes( 'config_data', $config );
return $self;
}
sub ACTION_build
{
$_[0]->write_config( );
super( );
}
sub write_config
{
my $self = shift;
my $file = $self->notes( 'config_module' );
my $data = $self->notes( 'config_data' );
my $dump = Data::Dumper->new( [ $data ], [ 'config_data' ] )->Dump;
my $file_path = catfile( 'lib', split( /::/, $file . '.pm' ) );
my $path = ( splitpath( $file_path ) )[1];
mkpath( $path ) unless -d $path;
my $package = <<END_MODULE;
package $file;
my $dump
sub get_value
{
my (\\$class, \\$key) = \\@_;
return unless exists \\$config_data->{ \\$key };
return \\$config_data->{ \\$key };
}
1;
END_MODULE
$package =~ s/^\\t//gm;
open( my $fh, '>', $file_path )
or die "Cannot write config file '$path': $!\\n";
print $fh $package;
close $fh;
}
1;HTTP::Proxy can help.http://www.perlmonks.com/, while the truly blessed saints prefer http://perlmonks.org/. That's all well and good except for the cases where you have logged in to the site through one domain name but not the others. Your HTTP cookie uses the specific domain name for identification.HTTP::Proxy is easy though:use strict;
use warnings;
use HTTP::Proxy ':log';
use HTTP::Proxy::HeaderFilter::simple;
# start the proxy with the given command-line parameters
my $proxy = HTTP::Proxy->new( @ARGV );
for my $redirect (<DATA>)
{
chomp $redirect;
my ($pattern, $destination) = split( /\\|/, $redirect );
my $filter = get_filter( $destination );
$proxy->push_filter( host => $pattern, request => $filter );
}
$proxy->start( );
my %filters;
sub get_filter
{
my $site = shift;
return $filters{ $site } ||= HTTP::Proxy::HeaderFilter::simple->new(
sub
{
my ( $self, $headers, $message ) = @_;
# modify the host part of the request only
$message->uri( )->host( $site );
# create a new redirect response
my $res = HTTP::Response->new(
301,
"Moved to $site",
[ Location => $message->uri( ) ]
);
# and make the proxy send it back to the client
$self->proxy( )->response( $res );
}
);
}
__DATA__
perlmonks.com|perlmonks.org
www.perlmonks.org|perlmonks.orgTie::File module
exists, and is even in the core as of Perl 5.8.0. What good is it?Tie::File module
exists, and is even in the core as of Perl 5.8.0. What good is it?Tie::File won't help you write the rules for transforming lines, but it will take the pain out of manipulating the lines of a file. Just:use Tie::File;
tie my @csv_lines, 'Tie::File', 'big_file.csv'
or die "Cannot open big_file.csv: !$\\n";
opname:number. Obviously the file would be easier to process if the operations appeared before the data on which to operate, but you can't always change customer data formats to something sane. In fact, this might be the easiest way to clean the data for other processes.Tie::File makes this almost trivial:for my $i ( 0 .. $#csv_lines )
{
next unless my ($op, $num) = $csv_lines[ $i ] =~ /^(\\w+):(\\d+)/;
next unless my $op_sub = __PACKAGE__->can( 'op_' . $op );
my $start = $i - $num;
my $end = $i - 1;
my @lines = @csv_lines[ $start .. $end ];
my @newlines = $op_sub->( @lines );
splice @csv_lines, $start, $num + 1, @newlines;
}