mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-08 10:23:30 +08:00
feat: add Perl skills (patterns, security, testing)
This commit is contained in:
committed by
Affaan Mustafa
parent
ae5c9243c9
commit
b2a7bae5db
500
skills/perl-patterns/SKILL.md
Normal file
500
skills/perl-patterns/SKILL.md
Normal file
@@ -0,0 +1,500 @@
|
||||
---
|
||||
name: perl-patterns
|
||||
description: Modern Perl 5.36+ idioms, best practices, and conventions for building robust, maintainable Perl applications.
|
||||
origin: ECC
|
||||
---
|
||||
|
||||
# Modern Perl Development Patterns
|
||||
|
||||
Idiomatic Perl 5.36+ patterns and best practices for building robust, maintainable applications.
|
||||
|
||||
## When to Activate
|
||||
|
||||
- Writing new Perl code or modules
|
||||
- Reviewing Perl code for idiom compliance
|
||||
- Refactoring legacy Perl to modern standards
|
||||
- Designing Perl module architecture
|
||||
- Migrating pre-5.36 code to modern Perl
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Use `v5.36` Pragma
|
||||
|
||||
A single `use v5.36` replaces the old boilerplate and enables strict, warnings, and subroutine signatures.
|
||||
|
||||
```perl
|
||||
# Good: Modern preamble
|
||||
use v5.36;
|
||||
|
||||
sub greet($name) {
|
||||
say "Hello, $name!";
|
||||
}
|
||||
|
||||
# Bad: Legacy boilerplate
|
||||
use strict;
|
||||
use warnings;
|
||||
use feature 'say', 'signatures';
|
||||
no warnings 'experimental::signatures';
|
||||
|
||||
sub greet {
|
||||
my ($name) = @_;
|
||||
say "Hello, $name!";
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Subroutine Signatures
|
||||
|
||||
Use signatures for clarity and automatic arity checking.
|
||||
|
||||
```perl
|
||||
use v5.36;
|
||||
|
||||
# Good: Signatures with defaults
|
||||
sub connect_db($host, $port = 5432, $timeout = 30) {
|
||||
# $host is required, others have defaults
|
||||
return DBI->connect("dbi:Pg:host=$host;port=$port", undef, undef, {
|
||||
RaiseError => 1,
|
||||
PrintError => 0,
|
||||
});
|
||||
}
|
||||
|
||||
# Good: Slurpy parameter for variable args
|
||||
sub log_message($level, @details) {
|
||||
say "[$level] " . join(' ', @details);
|
||||
}
|
||||
|
||||
# Bad: Manual argument unpacking
|
||||
sub connect_db {
|
||||
my ($host, $port, $timeout) = @_;
|
||||
$port //= 5432;
|
||||
$timeout //= 30;
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Context Sensitivity
|
||||
|
||||
Understand scalar vs list context — a core Perl concept.
|
||||
|
||||
```perl
|
||||
use v5.36;
|
||||
|
||||
my @items = (1, 2, 3, 4, 5);
|
||||
|
||||
my @copy = @items; # List context: all elements
|
||||
my $count = @items; # Scalar context: count (5)
|
||||
say "Items: " . scalar @items; # Force scalar context
|
||||
```
|
||||
|
||||
### 4. Postfix Dereferencing
|
||||
|
||||
Use postfix dereference syntax for readability with nested structures.
|
||||
|
||||
```perl
|
||||
use v5.36;
|
||||
|
||||
my $data = {
|
||||
users => [
|
||||
{ name => 'Alice', roles => ['admin', 'user'] },
|
||||
{ name => 'Bob', roles => ['user'] },
|
||||
],
|
||||
};
|
||||
|
||||
# Good: Postfix dereferencing
|
||||
my @users = $data->{users}->@*;
|
||||
my @roles = $data->{users}[0]{roles}->@*;
|
||||
my %first = $data->{users}[0]->%*;
|
||||
|
||||
# Bad: Circumfix dereferencing (harder to read in chains)
|
||||
my @users = @{ $data->{users} };
|
||||
my @roles = @{ $data->{users}[0]{roles} };
|
||||
```
|
||||
|
||||
### 5. The `isa` Operator (5.32+)
|
||||
|
||||
Infix type-check — replaces `blessed($o) && $o->isa('X')`.
|
||||
|
||||
```perl
|
||||
use v5.36;
|
||||
if ($obj isa 'My::Class') { $obj->do_something }
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### eval/die Pattern
|
||||
|
||||
```perl
|
||||
use v5.36;
|
||||
|
||||
sub parse_config($path) {
|
||||
my $content = eval { path($path)->slurp_utf8 };
|
||||
die "Config error: $@" if $@;
|
||||
return decode_json($content);
|
||||
}
|
||||
```
|
||||
|
||||
### Try::Tiny (Reliable Exception Handling)
|
||||
|
||||
```perl
|
||||
use v5.36;
|
||||
use Try::Tiny;
|
||||
|
||||
sub fetch_user($id) {
|
||||
my $user = try {
|
||||
$db->resultset('User')->find($id)
|
||||
// die "User $id not found\n";
|
||||
}
|
||||
catch {
|
||||
warn "Failed to fetch user $id: $_";
|
||||
undef;
|
||||
};
|
||||
return $user;
|
||||
}
|
||||
```
|
||||
|
||||
### Native try/catch (5.40+)
|
||||
|
||||
```perl
|
||||
use v5.40;
|
||||
|
||||
sub divide($a, $b) {
|
||||
try {
|
||||
die "Division by zero" if $b == 0;
|
||||
return $a / $b;
|
||||
}
|
||||
catch ($e) {
|
||||
warn "Error: $e";
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Modern OO with Moo
|
||||
|
||||
Prefer Moo for lightweight, modern OO. Use Moose only when its metaprotocol is needed.
|
||||
|
||||
```perl
|
||||
# Good: Moo class
|
||||
package User;
|
||||
use Moo;
|
||||
use Types::Standard qw(Str Int ArrayRef);
|
||||
use namespace::autoclean;
|
||||
|
||||
has name => (is => 'ro', isa => Str, required => 1);
|
||||
has email => (is => 'ro', isa => Str, required => 1);
|
||||
has age => (is => 'ro', isa => Int, default => sub { 0 });
|
||||
has roles => (is => 'ro', isa => ArrayRef[Str], default => sub { [] });
|
||||
|
||||
sub is_admin($self) {
|
||||
return grep { $_ eq 'admin' } $self->roles->@*;
|
||||
}
|
||||
|
||||
sub greet($self) {
|
||||
return "Hello, I'm " . $self->name;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
# Usage
|
||||
my $user = User->new(
|
||||
name => 'Alice',
|
||||
email => 'alice@example.com',
|
||||
roles => ['admin', 'user'],
|
||||
);
|
||||
|
||||
# Bad: Blessed hashref (no validation, no accessors)
|
||||
package User;
|
||||
sub new {
|
||||
my ($class, %args) = @_;
|
||||
return bless \%args, $class;
|
||||
}
|
||||
sub name { return $_[0]->{name} }
|
||||
1;
|
||||
```
|
||||
|
||||
### Moo Roles
|
||||
|
||||
```perl
|
||||
package Role::Serializable;
|
||||
use Moo::Role;
|
||||
use JSON::MaybeXS qw(encode_json);
|
||||
requires 'TO_HASH';
|
||||
sub to_json($self) { encode_json($self->TO_HASH) }
|
||||
1;
|
||||
|
||||
package User;
|
||||
use Moo;
|
||||
with 'Role::Serializable';
|
||||
has name => (is => 'ro', required => 1);
|
||||
has email => (is => 'ro', required => 1);
|
||||
sub TO_HASH($self) { { name => $self->name, email => $self->email } }
|
||||
1;
|
||||
```
|
||||
|
||||
### Native `class` Keyword (5.38+, Corinna)
|
||||
|
||||
```perl
|
||||
use v5.38;
|
||||
use feature 'class';
|
||||
no warnings 'experimental::class';
|
||||
|
||||
class Point {
|
||||
field $x :param;
|
||||
field $y :param;
|
||||
method magnitude() { sqrt($x**2 + $y**2) }
|
||||
}
|
||||
|
||||
my $p = Point->new(x => 3, y => 4);
|
||||
say $p->magnitude; # 5
|
||||
```
|
||||
|
||||
## Regular Expressions
|
||||
|
||||
### Named Captures and `/x` Flag
|
||||
|
||||
```perl
|
||||
use v5.36;
|
||||
|
||||
# Good: Named captures with /x for readability
|
||||
my $log_re = qr{
|
||||
^ (?<timestamp> \d{4}-\d{2}-\d{2} \s \d{2}:\d{2}:\d{2} )
|
||||
\s+ \[ (?<level> \w+ ) \]
|
||||
\s+ (?<message> .+ ) $
|
||||
}x;
|
||||
|
||||
if ($line =~ $log_re) {
|
||||
say "Time: $+{timestamp}, Level: $+{level}";
|
||||
say "Message: $+{message}";
|
||||
}
|
||||
|
||||
# Bad: Positional captures (hard to maintain)
|
||||
if ($line =~ /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+\[(\w+)\]\s+(.+)$/) {
|
||||
say "Time: $1, Level: $2";
|
||||
}
|
||||
```
|
||||
|
||||
### Precompiled Patterns
|
||||
|
||||
```perl
|
||||
use v5.36;
|
||||
|
||||
# Good: Compile once, use many
|
||||
my $email_re = qr/^[A-Za-z0-9._%+-]+\@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
|
||||
|
||||
sub validate_emails(@emails) {
|
||||
return grep { $_ =~ $email_re } @emails;
|
||||
}
|
||||
```
|
||||
|
||||
## Data Structures
|
||||
|
||||
### References and Safe Deep Access
|
||||
|
||||
```perl
|
||||
use v5.36;
|
||||
|
||||
# Hash and array references
|
||||
my $config = {
|
||||
database => {
|
||||
host => 'localhost',
|
||||
port => 5432,
|
||||
options => ['utf8', 'sslmode=require'],
|
||||
},
|
||||
};
|
||||
|
||||
# Safe deep access (returns undef if any level missing)
|
||||
my $port = $config->{database}{port}; # 5432
|
||||
my $missing = $config->{cache}{host}; # undef, no error
|
||||
|
||||
# Hash slices
|
||||
my %subset;
|
||||
@subset{qw(host port)} = @{$config->{database}}{qw(host port)};
|
||||
|
||||
# Array slices
|
||||
my @first_two = $config->{database}{options}->@[0, 1];
|
||||
|
||||
# Multi-variable for loop (experimental in 5.36, stable in 5.40)
|
||||
use feature 'for_list';
|
||||
no warnings 'experimental::for_list';
|
||||
for my ($key, $val) (%$config) {
|
||||
say "$key => $val";
|
||||
}
|
||||
```
|
||||
|
||||
## File I/O
|
||||
|
||||
### Three-Argument Open
|
||||
|
||||
```perl
|
||||
use v5.36;
|
||||
|
||||
# Good: Three-arg open with autodie (core module, eliminates 'or die')
|
||||
use autodie;
|
||||
|
||||
sub read_file($path) {
|
||||
open my $fh, '<:encoding(UTF-8)', $path;
|
||||
local $/;
|
||||
my $content = <$fh>;
|
||||
close $fh;
|
||||
return $content;
|
||||
}
|
||||
|
||||
# Bad: Two-arg open (shell injection risk, see perl-security)
|
||||
open FH, $path; # NEVER do this
|
||||
open FH, "< $path"; # Still bad — user data in mode string
|
||||
```
|
||||
|
||||
### Path::Tiny for File Operations
|
||||
|
||||
```perl
|
||||
use v5.36;
|
||||
use Path::Tiny;
|
||||
|
||||
my $file = path('config', 'app.json');
|
||||
my $content = $file->slurp_utf8;
|
||||
$file->spew_utf8($new_content);
|
||||
|
||||
# Iterate directory
|
||||
for my $child (path('src')->children(qr/\.pl$/)) {
|
||||
say $child->basename;
|
||||
}
|
||||
```
|
||||
|
||||
## Module Organization
|
||||
|
||||
### Standard Project Layout
|
||||
|
||||
```text
|
||||
MyApp/
|
||||
├── lib/
|
||||
│ └── MyApp/
|
||||
│ ├── App.pm # Main module
|
||||
│ ├── Config.pm # Configuration
|
||||
│ ├── DB.pm # Database layer
|
||||
│ └── Util.pm # Utilities
|
||||
├── bin/
|
||||
│ └── myapp # Entry-point script
|
||||
├── t/
|
||||
│ ├── 00-load.t # Compilation tests
|
||||
│ ├── unit/ # Unit tests
|
||||
│ └── integration/ # Integration tests
|
||||
├── cpanfile # Dependencies
|
||||
├── Makefile.PL # Build system
|
||||
└── .perlcriticrc # Linting config
|
||||
```
|
||||
|
||||
### Exporter Patterns
|
||||
|
||||
```perl
|
||||
package MyApp::Util;
|
||||
use v5.36;
|
||||
use Exporter 'import';
|
||||
|
||||
our @EXPORT_OK = qw(trim);
|
||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||
|
||||
sub trim($str) { $str =~ s/^\s+|\s+$//gr }
|
||||
|
||||
1;
|
||||
```
|
||||
|
||||
## Tooling
|
||||
|
||||
### perltidy Configuration (.perltidyrc)
|
||||
|
||||
```text
|
||||
-i=4 # 4-space indent
|
||||
-l=100 # 100-char line length
|
||||
-ci=4 # continuation indent
|
||||
-ce # cuddled else
|
||||
-bar # opening brace on same line
|
||||
-nolq # don't outdent long quoted strings
|
||||
```
|
||||
|
||||
### perlcritic Configuration (.perlcriticrc)
|
||||
|
||||
```ini
|
||||
severity = 3
|
||||
theme = core + pbp + security
|
||||
|
||||
[InputOutput::RequireCheckedSyscalls]
|
||||
functions = :builtins
|
||||
exclude_functions = say print
|
||||
|
||||
[Subroutines::ProhibitExplicitReturnUndef]
|
||||
severity = 4
|
||||
|
||||
[ValuesAndExpressions::ProhibitMagicNumbers]
|
||||
allowed_values = 0 1 2 -1
|
||||
```
|
||||
|
||||
### Dependency Management (cpanfile + carton)
|
||||
|
||||
```bash
|
||||
cpanm App::cpanminus Carton # Install tools
|
||||
carton install # Install deps from cpanfile
|
||||
carton exec -- perl bin/myapp # Run with local deps
|
||||
```
|
||||
|
||||
```perl
|
||||
# cpanfile
|
||||
requires 'Moo', '>= 2.005';
|
||||
requires 'Path::Tiny';
|
||||
requires 'JSON::MaybeXS';
|
||||
requires 'Try::Tiny';
|
||||
|
||||
on test => sub {
|
||||
requires 'Test2::V0';
|
||||
requires 'Test::MockModule';
|
||||
};
|
||||
```
|
||||
|
||||
## Quick Reference: Modern Perl Idioms
|
||||
|
||||
| Legacy Pattern | Modern Replacement |
|
||||
|---|---|
|
||||
| `use strict; use warnings;` | `use v5.36;` |
|
||||
| `my ($x, $y) = @_;` | `sub foo($x, $y) { ... }` |
|
||||
| `@{ $ref }` | `$ref->@*` |
|
||||
| `%{ $ref }` | `$ref->%*` |
|
||||
| `open FH, "< $file"` | `open my $fh, '<:encoding(UTF-8)', $file` |
|
||||
| `blessed hashref` | `Moo` class with types |
|
||||
| `$1, $2, $3` | `$+{name}` (named captures) |
|
||||
| `eval { }; if ($@)` | `Try::Tiny` or native `try/catch` (5.40+) |
|
||||
| `BEGIN { require Exporter; }` | `use Exporter 'import';` |
|
||||
| Manual file ops | `Path::Tiny` |
|
||||
| `blessed($o) && $o->isa('X')` | `$o isa 'X'` (5.32+) |
|
||||
| `builtin::true / false` | `use builtin 'true', 'false';` (5.36+, experimental) |
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
```perl
|
||||
# 1. Two-arg open (security risk)
|
||||
open FH, $filename; # NEVER
|
||||
|
||||
# 2. Indirect object syntax (ambiguous parsing)
|
||||
my $obj = new Foo(bar => 1); # Bad
|
||||
my $obj = Foo->new(bar => 1); # Good
|
||||
|
||||
# 3. Excessive reliance on $_
|
||||
map { process($_) } grep { validate($_) } @items; # Hard to follow
|
||||
my @valid = grep { validate($_) } @items; # Better: break it up
|
||||
my @results = map { process($_) } @valid;
|
||||
|
||||
# 4. Disabling strict refs
|
||||
no strict 'refs'; # Almost always wrong
|
||||
${"My::Package::$var"} = $value; # Use a hash instead
|
||||
|
||||
# 5. Global variables as configuration
|
||||
our $TIMEOUT = 30; # Bad: mutable global
|
||||
use constant TIMEOUT => 30; # Better: constant
|
||||
# Best: Moo attribute with default
|
||||
|
||||
# 6. String eval for module loading
|
||||
eval "require $module"; # Bad: code injection risk
|
||||
eval "use $module"; # Bad
|
||||
use Module::Runtime 'require_module'; # Good: safe module loading
|
||||
require_module($module);
|
||||
```
|
||||
|
||||
**Remember**: Modern Perl is clean, readable, and safe. Let `use v5.36` handle the boilerplate, use Moo for objects, and prefer CPAN's battle-tested modules over hand-rolled solutions.
|
||||
Reference in New Issue
Block a user