Date Created: 2024-10-31 Date Modified: 2024-10-31 ============================================================================ STARTER TEMPLATE ============================================================================ Every Perl project starts somewhere. This is my battle-tested starter template - the boilerplate I copy into every new script. It handles the annoying stuff so you can focus on the interesting problems. ============================================================================ PART 1: THE ESSENTIALS ============================================================================ Every script starts with this: #!/usr/bin/perl use strict; use warnings; You know these. strict catches variable typos and requires declarations. warnings tells you when you're doing something questionable. Never skip them, even for "quick scripts." Quick scripts have a way of becoming permanent. ============================================================================ PART 2: UTF-8 EVERYWHERE ============================================================================ Modern Perl should handle Unicode properly: use utf8; use open ':std', ':encoding(UTF-8)'; binmode(STDOUT, ':encoding(UTF-8)'); binmode(STDERR, ':encoding(UTF-8)'); The first line says "this source file contains UTF-8." The second makes file handles default to UTF-8. The binmode calls ensure STDOUT and STDERR handle Unicode correctly. Without these, you'll get garbage when printing characters outside ASCII. With them, print "Hello, 世界!\n" just works. ============================================================================ PART 3: COLORFUL OUTPUT ============================================================================ Life's too short for monochrome terminals: use Term::ANSIColor qw(:constants); print GREEN, "Success!", RESET, "\n"; print RED, "Error!", RESET, "\n"; print BOLD, BLUE, "Important", RESET, "\n"; The :constants import gives you color names you can use directly. Always RESET at the end, or your terminal stays colored. Available colors include: BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE BRIGHT_* versions of all the above BOLD ITALIC UNDERLINE (and more) ============================================================================ PART 4: FINDING YOUR WAY ============================================================================ Scripts need to know where they live: use FindBin qw($RealBin $RealScript); print "I am: $RealScript\n"; print "I live in: $RealBin\n"; $RealBin is the directory containing your script (following symlinks). $RealScript is the script's name. These are essential when your script needs to find config files or other resources relative to itself. ============================================================================ PART 5: DEBUGGING MADE PRETTY ============================================================================ Data::Dumper is invaluable, but the defaults are ugly: use Data::Dumper; $Data::Dumper::Indent = 1; # Mild indentation $Data::Dumper::Sortkeys = 1; # Alphabetize hash keys $Data::Dumper::Terse = 1; # No $VAR1 = prefix my %data = (foo => 1, bar => [2, 3, 4]); print Dumper(\%data); With these settings, output is compact and consistent. Sortkeys is especially nice - hashes print the same way every time, making diffs meaningful. ============================================================================ PART 6: THE DISPATCH TABLE ============================================================================ For scripts with multiple commands, use a dispatch table: my $dt = { 'help' => \&show_help, 'version' => \&show_version, 'run' => \&do_the_thing, 'test' => sub { print "Testing...\n"; }, }; my $command = shift @ARGV || 'help'; if (exists $dt->{$command}) { $dt->{$command}->(); } else { print "Unknown command: $command\n"; $dt->{'help'}->(); } This is cleaner than a chain of if/elsif statements and easier to extend. Each command is a code reference - either a named sub or an anonymous one. ============================================================================ PART 7: UTILITY FUNCTIONS ============================================================================ Here are three functions I include in almost every project: sub sleepyTime { my ($max_seconds) = @_; $max_seconds //= 1; select(undef, undef, undef, rand($max_seconds)); } A random delay up to the given number of seconds. Great for rate limiting, animations, or simulating network latency. sub typeWriter { my ($text, $delay) = @_; $delay //= 0.05; $|++; $text =~ s`.` select(undef,undef,undef,rand($delay)); print $& `sger; } The typewriter effect from our earlier tutorial. Pass it text and an optional max delay per character. sub strScan { my ($string) = @_; my $pos = 0; return { pos => sub { return $pos; }, mod_pos => sub { $pos += shift; return $pos; }, eos_check => sub { return $pos >= length($string); }, find => sub { my ($pattern) = @_; my $remainder = substr($string, $pos); if ($remainder =~ /$pattern/) { $pos += $-[0] + length($&); return { match => $&, start => $-[0] }; } return undef; }, }; } The string scanner from that tutorial too. Closure-based lexical scanning. ============================================================================ PART 8: THE COMPLETE TEMPLATE ============================================================================ Here it all is together: #!/usr/bin/perl use strict; use warnings; use utf8; use open ':std', ':encoding(UTF-8)'; use Term::ANSIColor qw(:constants); use FindBin qw($RealBin $RealScript); use Data::Dumper; binmode(STDOUT, ':encoding(UTF-8)'); binmode(STDERR, ':encoding(UTF-8)'); $Data::Dumper::Indent = 1; $Data::Dumper::Sortkeys = 1; $Data::Dumper::Terse = 1; # Dispatch table my $dt = { 'help' => \&show_help, 'run' => \&main, }; # Utility functions sub sleepyTime { my ($max) = @_; $max //= 1; select(undef, undef, undef, rand($max)); } sub typeWriter { my ($text, $delay) = @_; $delay //= 0.05; $|++; $text =~ s`.` select(undef,undef,undef,rand($delay)); print $& `sger; } sub strScan { my ($string) = @_; my $pos = 0; return { pos => sub { return $pos; }, mod_pos => sub { $pos += shift; return $pos; }, eos_check => sub { return $pos >= length($string); }, find => sub { my ($pattern) = @_; my $rem = substr($string, $pos); if ($rem =~ /$pattern/) { $pos += $-[0] + length($&); return { match => $&, start => $-[0] }; } return undef; }, }; } # Your code here sub show_help { print "Usage: $RealScript [command]\n"; print "Commands: ", join(", ", sort keys %$dt), "\n"; } sub main { print GREEN, "Ready to go!", RESET, "\n"; } # Entry point my $cmd = shift @ARGV || 'help'; if (exists $dt->{$cmd}) { $dt->{$cmd}->(); } else { print RED, "Unknown: $cmd", RESET, "\n"; show_help(); } ============================================================================ PART 9: MAKING IT YOUR OWN ============================================================================ This template is a starting point. Over time, you'll develop your own preferences: - Add your favorite modules (IO::All, Path::Tiny, JSON, etc.) - Include project-specific utilities - Adjust the Dumper settings - Add logging configuration - Include database connection boilerplate Save your template somewhere accessible and copy it when starting new projects. Future you will thank present you. ============================================================================ Happy coding, and may your scripts always start clean! perl.gg