Shortcuts: s show h hide n next p prev

Perl Weekly Issue #772 - PTS 2025

r/perl

Perl 🐪 Weekly #772 - PTS 2025

dev.to #perl

Originally published at Perl Weekly 772

Hi there,

The Perl Toolchain Summit 2026 held in Vienna from 23rd - 26th April. I almost forgotton about it, until I read the blog post by Philippe Bruhat. It was refreshing to see the group met and continued the great work. First post event report was shared by Thomas Klausner. The event report gave a detailed insights what happend during the 4-days event. I was hoping to see more such event report from other attendees. Luckily, we had few more to keep you busy by Timothy Legge, Leo Lapworth, Thibault Duponchelle, Shawn Sorichetti, and Paul Johnson. There was even a group photo shared by Paul. I am looking for an update on my personal favourite project MetaCPAN led by Olaf Alders.

Happy to see, Stats::LikeR, which provides R statistical functions. I wish it was available few years ago. I was approached by a research student of an Australian university few years ago asking for help with his Perl script doing R's equivalent function in Perl. Fortunately we did it and paper was accepted in the end. I was surprised by his choice of language for his research paper. He could have easily used R rather than creating equivalent function in Perl. He was well versed with R as well, still he choose Perl, whilst learning the language.

Please watch the recent page on MetaCPAN for such cool modules. Enjoy rest of the newsletter.

--
Your editor: Mohammad Sajid Anwar.

Articles

DBIx::Class::Async - Oracle Database

This post I shared about the proof of support for Oracle in DBIx::Class::Async. It is always assuring the async operations with Oracle database.

Signing CPAN Releases with SigStore

Timothy Legge describes a contemporary way of achieving security in the Perl ecosystem with the implementation of Sigstore to sign CPAN Releases. This post describes how we will transition from using traditional GPG signatures to shorter and easier to obtain certificates, while providing a clear roadmap to assist maintainers (and others) in adopting "keyless" signing and enhancing supply chain security.

DBIx::Class - Schema Class (Part 2)

This is part 2 of the series post about DBIx Schema Class. In this post, I shared how we can configure session on the fly and trap exception globally.

DBIx::Class - Schema Class (Part 3)

In the third part of the series, I discussed the use of DBIx::Class::Schema::Config and how we can securely manage database credentials.

Beautiful Perl feature: low-precedence boolean operators 'and', 'or'

The elegant usefulness of Perl's low-precedence boolean operators (and, or, not) compared to the more common high-precedence operators (&&, ||, !) is further explored by Laurent. The post shows how the low-precedence boolean operator can be used as a "control flow tool" creating readable, idiomatic code using the example of the classic "do or die" convention.

Explain act

In this post, I introduced the workflow automation tool, act. It gives hands-on tutorial on the subject.

Ideas for the CPAN Meta v3 Specification

Improvements to how developers will be able to manage their dependencies and the tools that they use for building them is the goal of several forward-thinking updates proposed by Robert Rothenberg to the CPAN Meta Spec v3. His blog post includes recommendations for enhancements to provide developers with standardised, enhanced requirements for their "develop" phase, as well as improved ways to specify what hardware or operating system a developer's distribution requires to successfully run.

Discussion

This week in PSC (223) | 2026-05-04

A brief report from the Perl Steering Council has provided us with an update about their most recent triage session. Among several small changes and the completion of various CPAN dual-life module updates, one of the major highlights of this update is that there are currently no release blockers.

CPAN

GTC 2.1 go pro

The GTC (Graphics::Toolkit::Color) project has been going well according to Herbert Breunung's progress report on it, including the Go-Pro initiative for professional grade stability. The update on the 2.1 release completed a more efficient API, reducing the amount of work involved in creating colors or generating palettes, and successfully built on a solid foundation for future integration into GUI's and plotting applications.

The Weekly Challenge

The Weekly Challenge by Mohammad Sajid Anwar will help you step out of your comfort-zone. You can even win prize money of $50 by participating in the weekly challenge. We pick one champion at the end of the month from among all of the contributors during the month, thanks to the sponsor Marc Perry.

The Weekly Challenge - 373

Welcome to a new week with a couple of fun tasks "Equal List" and "List Division". If you are new to the weekly challenge then why not join us and have fun every week. For more information, please read the FAQ.

RECAP - The Weekly Challenge - 372

Enjoy a quick recap of last week's contributions by Team PWC dealing with the "Rearrange Spaces" and "Largest Substring" tasks in Perl and Raku. You will find plenty of solutions to keep you busy.

Spaces at Large

Arne Sommer takes a close look at the nuances of whitespace in Raku, especially in relation to how the context of either string or array can affect whitespace. This post provides a practical investigation into how to handle whitespace effectively. Examples of how Raku's syntactical flexibility will allow for clean, expressive code to be written around more complex data structures have been provided.

Perl Weekly Challenge: Week 372

Jaldhar demonstrates the unique strengths of each language, contrasting Perl’s efficient hash-based counting with Raku’s expressive built-ins to solve these algorithmic puzzles.

Regular Sub-Gaps

In this post, Jorg Sommrey provides an in-depth overview of his approaches to each of the two tasks for this week's challenge. He demonstrates that it is possible to write relatively short and very readable code by using List::Util and idiomatic hash slicing. He also shows how these techniques easily handle edge cases such as the case insensitivity of the frequency count of words.

Perl Weekly Challenge 372

Mochan takes a higher-level look at Rearrange Spaces by breaking down an input string into an array and then using the tr/// operator to determine how many elements are in the array before performing some arithmetic to calculate how many spaces need to be added. The x operator and join are both used to construct the output from the individual elements. For the solution to Largest Substring, Mochan uses a hash data structure to remember the first time each character appears in the input string so that he can keep track of the longest substring while going through the input string using an index. He also updates his maximum substring variable each time he sees a character he's previously encountered.

Very Stringish

Matthias Muth offers straightforward, modernly written Perl code using the new v5.40 features of Perl’s builtin::indexed to track the position of all characters in Finding Largest Substring so as to find the maximum length substring in the most efficient manner possible.

Empty Substrings and Empty Spaces

In his blog post, Packy Anderson focuses on the edge cases for each of the challenges and provides solid solutions in Perl, Raku, Python and Elixir. He also explains the logic of his "Rearrange Spaces" solution with exact math for distributing spaces between words, as well as a clever method for handling the -1 requirement for unique characters in his "Largest Substring" solution.

Strings of strings

Peter has given two stylish implementations of the weekly challenge using Perl's native string functions to produce very compact code. For Rearrange Spaces he worked out precisely what gaps were needed to recreate the string using join, and for the Largest Substring solution he had one loop with a hash to keep track of where each character was seen first so that the answer was determined with one cycle through the original string.

The Weekly Challenge - 372: Rearrange Spaces

Reinier presents a straightforward solution with his algorithmic method to achieve a successful outcome on this week's "Rearranging Spaces". This is done in Perl primarily because of its preciseness regarding how many spaces to be distributed across the gaps between the words and determining how many of the combined gaps will result in an even distribution. This post emphasizes on a well-structured plan, such as separating the words from one another using split, and then joining them back together with join and the x operator.

The Weekly Challenge - 372: Largest Substring

Reinier had a methodical solution written in Perl for Weekly Challenge task of finding the largest substring. He used a hash to track the first instance of each character encountered when traversing the string. When he finds two identical characters, he determines their distance by subtracting the first instance's index from the second instance's index, and keeping track of the maximum value found up to that point; if he doesn't find duplicates then he will return -1.

The Weekly Challenge #372

Robbie gives in-depth solutions to the problem, providing excellent input handling as well as clarity in logic. When working with Rearranging Spaces, he uses a regex to identify the count of spaces & words, ensuring the math for gap size calculations & remainders are correct. For example, with regards to one word, he handles it just like the rest (edge cases). In terms of solving the problems of Largest Substring, he uses an array to save the first and last seen indexes of each character as he iterates through the character set of the input string making a distance calculation between these saved locations to determine the largest distance. The solutions are both mathematically correct and easily understood.

Space is the Largest Place

For Rearrange Spaces, Roger considers how to find the gap sizes through integer division and then uses the modulus operator for trailing spaces. He ensures that his logic from each of his implementations remains consistent. In the case of Largest Substring, he uses a "first-seen" hash to improve performance by only traversing through the string one time. He shows us, in his post, an engineering-oriented coding style while also documenting that these algorithmic patterns are transferable to working with dynamic languages.

Distant spaces

In Finding the Largest Substring, Simon created a dictionary in Python and hash in Perl to store the first occurrence of each of the letters in the string. Once this was done, he proceeded through the entire string to compare the current index to the index where the letter was first stored; this enabled him to efficiently track the maximum distance between the first occurrence and the current occurrence. It clearly illustrate that string manipulation and data structures are accomplished using virtually the same logical steps in each of these two programming languages.

Rakudo

2026.18 Star Wars Day

Weekly collections

NICEPERL's lists

Great CPAN modules released last week.

Event reports

Perl Toolchain Summit 2026 - Vienna

According to Timothy Legge, there was an effective meeting; the purpose of this summit was to develop the CPAN Security Group (CPANSec) into a CVE Numbering Authority and improve the security disclosure process for maintainers of CPAN modules. An example of a technical outcome resulting from the summit was the inclusion of signature function support in the distribution of Crypt::OpenSSL::RSA as well as the decision to deprecate Module::Signature in favour of more modern integrity checks.

PTS 2026

Leo Lapworth highlights the 2026 Perl Toolchain Summit by describing some of the major sponsorships that made it possible. He also discusses his own efforts to modernize the MetaCPAN infrastructure and the value of in-person collaboration for solving problems with other core developers.

The Perl Toolchain Summit 2026

In this blog post, Paul Johnson provides his impressions of an intense and productive Perl Toolchain Summit 2026 held in Vienna, and highlights progress made in both Devel::Cover version 2.00 and on integrating with MetaCPAN.

My Perl Toolchain Summit 2026

Thibault’s PTS 2026 report features a heavy lift on Test::Smoke releases and CPAN security hardening. His post offers a deep dive into the technical vetting of YAML payloads and the successful launch of cpm v1.0.

PTS 2026: What Actually Happened

Shawn recaps the PTS 2026 focus on infrastructure, detailing the migration of MetaCPAN services to Kubernetes and enhanced secret management. It’s a grounded look at the high-stakes maintenance work that powers the Perl ecosystem.

Events

Boston Perl Mongers virtual monthly

May 12, 2026

Exploring Perl Modules

May 20, 2026

The Perl and Raku Conference 2026

June 26-29, 2026, Greenville, SC, USA

You joined the Perl Weekly to get weekly e-mails about the Perl programming language and related topics.

Want to see more? See the archives of all the issues.

Not yet subscribed to the newsletter? Join us free of charge!

(C) Copyright Gabor Szabo
The articles are copyright the respective authors.

When you have lots and lots of qcow2

After a pause in April, here is a new article in the Beautiful Perl features series on dev.to: low-precedence boolean operators 'and', 'or'

Thank you Team PWC for your continuous support and encouragement.
Welcome to the Week #373 of The Weekly Challenge.

The Perl Toolchain Summit 2026

blogs.perl.org

I was once again privileged to be able to attend this year's Perl Toolchain Summit. This is the 13th year (in a row if you discount the Covid years) that I have been able to attend and it is the technical highlight of my year.

This year the event was held in Vienna and, for the first time, my wife accompanied me. We took a direct train from Zürich to Vienna and had a wonderful trip through the glorious Swiss and Austrian countrysides.

We arrived fairly late on Wednesday evening so didn't meet up with anyone then, but we saw a few of the other attendees at breakfast the next morning, and then I set off for the venue where I met up with everyone else, heard BooK's opening speech, took part in the introductions and then split off into a room with the MetaCPAN group with whom I spent about half of my time. Meanwhile, my wife set off to explore Vienna.

I came with a set of work needing to be completed and things I wanted to discuss with people there. The discussions were the most important part and are the raison d'ĆŖtre of the event, and over the four days I had plenty of useful discussions, both planned and non-planned. The first one started whilst walking to the venue on the first day with Paul Evans. We discussed the possibility of separating the behaviour of $^P and PL_perldb - perhaps by using another bit. Devel::Cover uses $^P but doesn't want the behaviour of PL_perldb.

I also discussed further integrating the cpancover infrastructure with that of MetaCPAN. Getting cpancover to run entirely within containers was one of my goals from last year's summit. It wasn't finished during the summit though the problems were solved and all that remained was finishing the implementation, which happened during the year. So now the discussion was more about how to use that. I had a good chat with Leo on that topic and we also sorted out the cpancover backups to MetaCPAN.

I didn't just speak to the English folk though. I spent a fair amount of time talking with Salve on topics primarily related to CPAN security and adjacent subjects. And I chatted with Merijn about life, the universe and everything. He also tested Devel::Cover 2.00 for me and gave me good feedback. We held our second annual CLI throwdown. Many of us live in the terminal so we have a (now somewhat formalised) tradition of sharing our favourite tools.

I had wondered whether I might be in a position to release Devel::Cover 2.00 during the summit but, whilst I was able to do a fair amount of work on it, I realised that there were more things to look at. Hopefully it won't be too much longer before I can put out a test release. This will be the first major (breaking) release since Devel::Cover became stable and it should bring speed improvements, a new criterion and a new report style.

There were plenty of talks and group discussions which I enjoyed, and the four days seemed to race by. It's an intense time which genuinely takes its toll. You can tell that by Sunday many folk are feeling the effects as things start to wind down. Nevertheless it's also extremely satisfying and productive. As usual I left with far more to do than when I arrived - plenty to keep me going until next year.

Beyond work and technical discussions it was lovely to meet up with old friends, to travel around the beautiful city of Vienna and sample some of its lovely cafƩs and restaurants.

The venue was excellent, die Hauswirtschaft. Our part was small enough that we weren't too far away from each other yet large enough that we weren't on top of each other and with enough space to hold group discussions without disturbing those not involved.

Huge thanks to the organisers. From my point of view, everything ran without a hitch though I'm sure an awful lot of effort went into making it seem that way. And thanks also to our generous sponsors without whom the event could never have happened:

Group photo, Perl Toolchain Summit 2026, Vienna

I cannot disclose exactly what the project is, but I previously built both Windows and Linux versions of a project based on the same concept.

The Windows version of this project was written in JavaScript, while the Linux version was written in Perl.

The Windows version was developed in JavaScript. Since it targeted a specific industrial runtime environment, the language level was closer to ES3, so I used polyfills to bring it closer to ES5-level functionality.

It turned out to be quite successful, receiving around 450 stars on GitHub and even winning an award from the local community.

As I started thinking about taking things a step further, Perl began to catch my attention.

Because I want my career to continue growing, I often wonder whether a Linux version written in Perl could have enough potential, just like the earlier JS (ES3)-based version did.

The concern is whether choosing Perl over currently popular languages like JavaScript or Python might negatively affect my career.

If I move forward with it, I expect the Perl-based project to have somewhat different goals from the original project. However, it would still share many of the same characteristics as the previous one.

It could succeed, but it could also fail to attract attention and end up going nowhere.

Even so, I would like to hear opinions on whether Perl could still be a worthwhile option to explore.

submitted by /u/gnh1201
[link] [comments]
Thank you Team PWC for your continuous support and encouragement.
As you know, The Weekly Challenge, primarily focus on Perl and Raku. During the Week #018, we received solutions to The Weekly Challenge - 018 by Orestis Zekai in Python. It was pleasant surprise to receive solutions in something other than Perl and Raku. Ever since regular team members also started contributing in other languages like Ada, APL, Awk, BASIC, Bash, Bc, Befunge-93, Bourne Shell, BQN, Brainfuck, C3, C, CESIL, Chef, COBOL, Coconut, C Shell, C++, Clojure, Crystal, CUDA, D, Dart, Dc, Elixir, Elm, Emacs Lisp, Erlang, Excel VBA, F#, Factor, Fennel, Fish, Forth, Fortran, Gembase, Gleam, GNAT, Go, GP, Groovy, Haskell, Haxe, HTML, Hy, Idris, IO, J, Janet, Java, JavaScript, Julia, K, Kap, Korn Shell, Kotlin, Lisp, Logo, Lua, M4, Maxima, Miranda, Modula 3, MMIX, Mumps, Myrddin, Nelua, Nim, Nix, Node.js, Nuweb, Oberon, Octave, OCaml, Odin, Ook, Pascal, PHP, PicoLisp, Python, PostgreSQL, Postscript, PowerShell, Prolog, R, Racket, Rexx, Ring, Roc, Ruby, Rust, Scala, Scheme, Sed, Smalltalk, SQL, Standard ML, SVG, Swift, Tcl, TypeScript, Typst, Uiua, V, Visual BASIC, WebAssembly, Wolfram, XSLT, YaBasic and Zig.
Just to give you context, Gabor Szabo, while working on opensource projects recently, he picked up my latest creation DBIx::Class::Async. He noticed, I didn’t have workflow configuration file. He kindly created pull request and submitted: PR #3. Without wasting a moment, I accepted the pull request and merged.

(dxcix) 21 great CPAN modules released last week

r/perl
Updates for great CPAN modules released last week. A module is considered great if its favorites count is greater or equal than 12.

  1. App::cpm - a fast CPAN module installer
    • Version: v1.1.0 on 2026-05-07, with 178 votes
    • Previous CPAN version: v1.0.4 was released the same day
    • Author: SKAJI
  2. App::Netdisco - An open source web-based network management tool.
    • Version: 2.098004 on 2026-05-08, with 862 votes
    • Previous CPAN version: 2.098003 was released 3 days before
    • Author: OLIVER
  3. App::perlimports - Make implicit imports explicit
    • Version: 0.000060 on 2026-05-06, with 24 votes
    • Previous CPAN version: 0.000059 was released 12 days before
    • Author: OALDERS
  4. App::rdapper - a command-line RDAP client.
    • Version: 1.25 on 2026-05-07, with 21 votes
    • Previous CPAN version: 1.24 was released 2 months, 15 days before
    • Author: GBROWN
  5. Attean - A Semantic Web Framework
    • Version: 0.037 on 2026-05-04, with 19 votes
    • Previous CPAN version: 0.036_01 was released the same day
    • Author: GWILLIAMS
  6. CGI - Handle Common Gateway Interface requests and responses
    • Version: 4.72 on 2026-05-05, with 48 votes
    • Previous CPAN version: 4.71 was released 7 months, 4 days before
    • Author: LEEJO
  7. CPANSA::DB - the CPAN Security Advisory data as a Perl data structure, mostly for CPAN::Audit
    • Version: 20260503.001 on 2026-05-04, with 25 votes
    • Previous CPAN version: 20260430.001 was released 3 days before
    • Author: BRIANDFOY
  8. DBIx::Lite - Chained and minimal ORM
    • Version: 0.38 on 2026-05-05, with 30 votes
    • Previous CPAN version: 0.37 was released the same day
    • Author: AAR
  9. Gazelle - a Preforked Plack Handler for performance freaks
    • Version: 0.50 on 2026-05-07, with 26 votes
    • Previous CPAN version: 0.49 was released 6 years, 5 months before
    • Author: KAZEBURO
  10. HTTP::Tinyish - HTTP::Tiny compatible HTTP client wrappers
    • Version: 0.20 on 2026-05-04, with 16 votes
    • Previous CPAN version: 0.19 was released 2 years, 1 month, 26 days before
    • Author: MIYAGAWA
  11. lazy - Lazily install missing Perl modules
    • Version: 1.000002 on 2026-05-09, with 16 votes
    • Previous CPAN version: 1.000001 was released the same day
    • Author: OALDERS
  12. Mojolicious - Real-time web framework
    • Version: 9.45 on 2026-05-05, with 512 votes
    • Previous CPAN version: 9.44 was released the same day
    • Author: SRI
  13. Plack - Perl Superglue for Web frameworks and Web Servers (PSGI toolkit)
    • Version: 1.0054 on 2026-05-05, with 496 votes
    • Previous CPAN version: 1.0053 was released 6 days before
    • Author: MIYAGAWA
  14. Rose::DB::Object - Extensible, high performance object-relational mapper (ORM).
    • Version: 0.823 on 2026-05-04, with 22 votes
    • Previous CPAN version: 0.822 was released 1 year, 3 months, 29 days before
    • Author: JSIRACUSA
  15. Sidef - The Sidef Programming Language - A modern, high-level programming language
    • Version: 26.05 on 2026-05-05, with 123 votes
    • Previous CPAN version: 26.04 was released 1 month, 4 days before
    • Author: TRIZEN
  16. SPVM - The SPVM Language
    • Version: 0.990169 on 2026-05-08, with 36 votes
    • Previous CPAN version: 0.990168 was released 11 days before
    • Author: KIMOTO
  17. Starlet - a simple, high-performance PSGI/Plack HTTP server
    • Version: 0.32 on 2026-05-06, with 18 votes
    • Previous CPAN version: 0.31 was released 9 years, 4 months, 25 days before
    • Author: KAZUHO
  18. Sys::Virt - libvirt Perl API
    • Version: v12.3.0 on 2026-05-06, with 17 votes
    • Previous CPAN version: v12.2.0 was released 1 month, 4 days before
    • Author: DANBERR
  19. Test::MockModule - Override subroutines in a module for unit testing
    • Version: v0.185.0 on 2026-05-07, with 18 votes
    • Previous CPAN version: v0.184.0 was released 1 day before
    • Author: GFRANKS
  20. Test::Most - Most commonly needed test functions and features.
    • Version: 0.42 on 2026-05-07, with 37 votes
    • Previous CPAN version: 0.41 was released 6 days before
    • Author: DCANTRELL
  21. Win32 - Interfaces to some Win32 API Functions
    • Version: 0.60 on 2026-05-05, with 13 votes
    • Previous CPAN version: 0.59_02 was released 1 day before
    • Author: JDB

These both work:

echo 'jg984tj89g02jg09248gj4209jg49' | perl -0lne 'print "$_\n" for /(\d)([a-z])/gs'
echo 'jg984tj89g02jg09248gj4209jg49' | perl -0lne '@m = /(\d)([a-z])/gs; print for @m'

But this doesn't:

echo 'jg984tj89g02jg09248gj4209jg49' | perl -0lne 'print "$1-->$2\n" for /(\d)([a-z])/gs'

(Basically last command outputs the capture groups over and over again for the last match, not iterating over each match)

Anyone able to explain why?

It would have been nice to control formatting with the last command, it could have been a quick, simple and convenient way of preparing / structuring the data for any next command.

Trying to install perl on a friend's PC

r/perl

I'm trying to install Strawberry Perl (5.42.2.1) on a friend's PC and it keeps throwing up these scary malware warnings and keeps asking if we're sure, and the publisher is "Unknown".

We got it from what at least looks like the official website* so idk why this is happening.

She also asked if we can install it in Program files because that's where Java seems to be installed as well, but it said it can't install it there because of special characters?

*https://strawberryperl.com/releases.html

submitted by /u/Terrible_Effective84
[link] [comments]

Beautiful Perl series

This post is part of the beautiful Perl features series. See the introduction post for general explanations about the series.

Today's feature is quite unique in programming languages: the fact that Perl has two different syntaxes for expressing the same boolean operations. Read on to understand why this is clever and beautiful.

Boolean algebra and short-circuit evaluation

The basic operations of Boolean algebra are fundamental in programming; therefore they are present in all programming languages. In the C language, later followed by many other languages, icluding Perl, logical conjunction (the "AND" operator) is written &&, logical disjunction (the "OR" operator) is written ||, and logical negation (the "NOT" operator) is written !. Conjunction has higher precedence than disjunction, so a && b || c && d is parsed as (a && b) || (c && d) and not a && (b || c) && d. The unary negation operator has even higher precedence.

Mathematically speaking, both AND and OR operators are commutative : the left and right operands can be switched without affecting the result. But in programming languages the order of operands is not totally indifferent, because operands have an evaluation cost; therefore the system usually spares the cost of evaluating the right operand whenever it has enough information from the left operand to compute the result of the boolean operation: this is called short-circuit evaluation. For example in the conjunction $x < 9999 && is_prime($x), if $x is a large number, the left condition is false, so the whole expression is necessarily false and there is no need to do an expensive computation to decide if the number is prime. Similarly, when the left operand of a disjunction is true, then the whole expression is true and the right operand need not be evaluated.

Short-circuit evaluation is not only a question of performance: it also has important consequences when operand expressions have side-effects, like changing the value of a global variable or interacting with files, sockets or other elements of the operating system. Because of short-circuiting, the value of the left operand determines if the side-effects of the right operand will happen or not. In languages of C lineage, many common idioms take advantage of this feature for avoiding runtime exceptions: conditions checked in the left operand must be met before operations in the right operand are attempted. For example the left condition might be to check if a file is present before attempting to open it, or check if a number is different from zero before using it as denominator in a division. Here is a C example borrowed from Wikipedia:

bool isFirstCharValidAlpha(const char* p) {
    return p != NULL && isalpha(p[0]); // 1) no unneeded isalpha() execution
                                       //    with p == NULL,
                                       // 2) no SEGFAULT risk
}

Perl expressions are also statements

In languages other than Perl, examples of boolean expressions where operands have side-effects are not frequent, apart from the family of idioms just mentioned. Expressions with side-effects may even be considered inadvisable. Perl is different, because expressions are also statements, as explained in perlsyn:

The only kind of simple statement is an expression evaluated for its side-effects.

Therefore a boolean combination of expressions is also a statement. Likewise, instructions in real life may also be combinations of more atomic instructions:

Add four spoons of sugar and dissolve it into the preparation, or replace it by honey.

The above sentence indeed can be seen as a boolean expression with short-circuit: either you add sugar and then you must dissolve it and there is no need for honey, or you don't use sugar, so there is no need to dissolve anything, and you just have to incorporate the honey.

This way of expressing composite instructions is used everyday in natural language; therefore, expressing programming instructions in a similar way helps to make them easily understandable by humans, often better than sequences of if-then-else constructs, at least when the composite instruction only contains two or three individual clauses. Most programming languages would require the individual clauses to be enclosed in parentheses, which impedes readability.

Boolean expressions in Perl: two sets of operators, same semantics, different precedence

Perl's brilliant solution for improving the readability of composite instructions is to offer another set of boolean operators, written as the English words and, or, not, with exactly the same semantics as the usual &&, || and !, but with lower precedence. Actually, these word versions of boolean operators have the lowest precedence of all in Perl's table of operators, which means that statements between such keywords will be nicely parsed as an individual clauses. Let us look at a simple example:

say "tic", "tac" and say "toe";

The intended result is two lines of output

tictac
toe

and it indeed works that way. The first say is a list operator of very high precedence. Commas after such an operator build a list that will be fed as arguments to say ... but that list stops at the keyword and because of its very low precedence, so it is equivalent to the following parenthesized version:

say("tic", "tac") and say("toe");

Now since we are using parentheses, we might as well write

say("tic", "tac") && say("toe");

with exactly the same result. By contrast, the && version without parentheses

say "tic", "tac" && say "toe"

produces a different output:

toe
tic1

because everything after the first say was treated as an argument list to that say, so an equivalent parenthesized version would be:

say("tic", ("tac" && say("toe")));

The expression "tac" && say "toe" needs to be evaluated first, which is why the output has toe on the first line. The result of that expression is 1 because since the left operand is true, the boolean && returns the value of the right operand say("toe"), which is 1. Then the outer say can do its job, and since it received two arguments "tic" and 1, it prints tic1 on the second line.

This example with say was good for explanations, but is unlikely to be written like this in real programs. More realistic situations involve other list operators, like this very common idiom with open:

open my $fh, '<', $filename
  or die "failed to open '$filename': $!";

Here is another example with mkdir; this also shows that of course several successive boolean connectors can be combined:

-d $dir                                # does this directory exist ?
  or mkdir $dir                        # if not, try to create it
  or die "failed to mkdir '$dir': $!"; # if the creation failed, report the error

Assignment operations

Examples so far were mainly with the or operator. Usage of and is more often combined with assignment operations: a variable takes the value of an expression, and if that value is not empty, then a follow-up operation is performed.

$n_items = $aref && @$aref
  and say "this array contains $n_items items"
  or  say "this array is empty";

This example assumes a variable $aref to be a reference to an array. The first line checks if the reference is indeed defined, and if so, if it refers to a non-empty array. Depending on the result, the appropriate follow-up message is printed.

For this example to work, the $n_items variable must be already declared earlier in the code: unfortunately it wouldn't work here to write my $n_items = ... because the my declaration would only take effect at the end of the whole statement; therefore at the point where the interpreter parses the string "this array contains $n_items items", variable $n_items would not be declared yet and would throw an exception.

This excerpt shows a single expression containing both && and and. Perl's table of operator precedence has been carefully crafted so that the assignment operator = (and its derivatives +=, *=, etc.) has lower precedence than boolean operators in their symbolic syntax &&, || or !, but higher precedence than those operators in their english syntax and, or, not. Therefore the value assigned to $n_items is $aref && @$aref, including the && operator, but it does not include what comes after the and.

The result of a boolean operator is the last operand

The example of the previous section assigns a boolean expression to a variable $n_items whose name indicates that it is not meant to receive a constant true or false but an integer number! This may be surprising for readers accustomed to strongly typed languages like Java or C++, where the result of a boolean expression can only take one of two boolean values.

By contrast, boolean expressions in Perl, like in several other dynamically typed languages (JavaScript, Python, Ruby, etc.), return the the last evaluated value. That value therefore can have a dual purpose: not only can it be tested for truthness (where any value different from undef, 0, "0" or "" means "true"), but it can also be used for its regular scalar value, as a number, string or reference. Many common idioms in Perl and other similar languages take advantage of this feature for writing compact expressions. A more verbose but more explicit version of the assignment above would be:

$n_items = defined $aref ? @$aref > 0 ? @$aref
                                      : 0
                         :              undef;

Real example

To conclude with a real example, here is an excerpt from my module Data::Domain with quite heavy use of idiomatic and / or constructs:

sub _inspect {
  my ($self, $data) = @_;

  looks_like_number($data)
    or return $self->msg(INVALID => $data);

  if (defined $self->{-min}) {
    $data >= $self->{-min}
      or return $self->msg(TOO_SMALL => $self->{-min});
  }
  if (defined $self->{-max}) {
    $data <= $self->{-max}
      or return $self->msg(TOO_BIG => $self->{-max});
  }
  if (defined $self->{-not_in}) {
    grep {$data == $_} @{$self->{-not_in}}
      and return $self->msg(EXCLUSION_SET => $data);
  }

  return;
}

Like always in Perl, there are several ways to do it. Here the stylistic choice was to use if statements for checking the main conditions, but and / or expressions for secondary checks. Another way to write the condition on -min could be:

  defined $self->{-min}
    and $data < $self->{-min}
    and return $self->msg(TOO_SMALL => $self->{-min});

or perhaps

  return $self->msg(TOO_SMALL => $self->{-min})
    if defined $self->{-min} and $data < $self->{-min};

Low precedence and/or in other programming languages

Apart from Perl, dual sets of boolean operators are only found in Ruby and PHP, two programming languages that were heavily influenced by Perl. Apparently their designers considered that this was a good idea! By contrast, a language like Python where the motto is there is only one way to do it did not adopt such a design.

Apart from stylistic preferences, the reason why low-precedence boolean operators in Perl are so effective is that they combine especially well with the other features of the language mentioned above, like the fact that expressions are also instructions, and the fact that boolean expressions return the last evaluated value.

Conclusion

Perl's invention of a double set of operators for a same purpose was a bold step, clearly on a different path than usual endeavors in programming language design that often seek to minimize the set of operators, with features as orthogonal as possible.

Yet Perl's choice integrates particularly well with other technical features of the language and with its general philosophy. Algorithmic steps can be written in a way that closely reflects natural language and natural thinking, without heavy syntactic if-then-else constructs and without verbose parentheses.

About the cover picture

Since the seminal Camel Book, Perl has been associated with the image of a camel (actually a dromedary). Here we have a camel carrying a performing organist ... nice illustration for a beautiful Perl feature. This woodcut print is part of a 16th-century Triumphal Procession series commissioned by the Holy Roman Emperor Maximilian I. The image is borrowed from the Deutsch Fotothek.

One of my modules report 99.999% coverage due to a single // usage which I'm trying to understand. Simplifying things, consider this program:

sub scalar_rhs
{
    my $a;
    my $b = 1;

    $a // $b
}

sub hash_rhs
{
    my $a;
    my %b = ( x => 1 );

    $a // $b{x}
}

die 'scalar failed' unless &scalar_rhs;
die 'hash failed' unless &hash_rhs;

Running:

perl -MDevel::Cover test.pl
cover -report text

Generates:

line  err      %      l  !l&&r !l&&!r   expr
----- --- ------ ------ ------ ------   ----
6     ***     33      0      1      0   $a // $b
15    ***     33      0      0      1   $a // $b{'x'}

How come $b{x} is seen as false by Devel::Cover? How can I make this pass as !l&&r?

PTS 2026

blogs.perl.org

A quick summary of what I got up to at PTS 2026 in Vienna.

Test::Smoke's long-term future. I had several useful discussions with H. Merijn Brand (Tux) and Todd Rinaldo (toddr) about keeping Test::Smoke maintainable for the long term. This tied directly into the MetaCPAN hosting migration below: DigitalOcean offers managed Postgres, Hetzner doesn't, and Test::Smoke's existing database usage wasn't especially efficient. The outcome was toddr starting a rewrite that runs as a single container backed by SQLite and local files -- much more portable and easier to operate.

Migrating MetaCPAN from DigitalOcean to Hetzner. I spent a big chunk of the summit pairing with Shawn Sorichetti (hide) on the migration, including reorganising our Kubernetes setup so it deals more cleanly with multiple environments. Shawn was making a large number of changes; I focused on reviewing them quickly so we could iterate fast.

Devel::Cover hosting. I spent time with Paul Johnson (pjcj) talking through Devel::Cover's hosting and configuration, and helped sketch a container design that should be more efficient and scalable for what he needs next.

MetaCPAN security. As part of the MetaCPAN team I joined several discussions -- including with the CPAN security team -- about parts of the system that recent security reports have flagged for improvement.

Side work. Outside the main Perl tracks, I contributed to the Nono Sandbox tool and dropped into several conversations and talks about AI in the context of Perl modules and publishing.

I really want to thank the Sponsors projects move so much faster when we can get people together and focused. Something that just doesn't happen any other way

The Perl and Raku Foundation, Grant Street Group, Geizhals Preisvergleich, Vienna.pm, SUSE, Trans-Formed Media LLC, Ctrl O, Simplelists, Harald Joerg, Michele Beltrame (Sigmafin), Laurent Boivin.


Ideas for the CPAN Meta v3 Specification

r/perl

If you are developing a large-scale Perl module or a native extension (XS/SPVM), build and test times can become a bottleneck. While we often manually pass -jN to make, I wanted a way to bake parallel execution directly into the Makefile.PL.

Today, I want to share a rather "aggressive" hack to achieve parallel builds and tests by injecting logic into WriteMakefile.

The Snippet

WriteMakefile(
  macro => {
    # Force parallel builds
    MAKEFLAGS => "-j$jobs",
    
    # Inject parallel test execution by overriding TEST_VERBOSE
    # Constraints for Windows shell compatibility (pwsh/cmd.exe):
    # 1. The entire command is wrapped in double quotes.
    # 2. No backslashes (\) to avoid escape hell.
    # 3. No dollar signs ($) to prevent pwsh variable expansion.
    # 4. No nested parentheses to avoid pwsh tokenization errors.
    # 5. Only '();. are used for maximum safety.
    # 6. Minimal symbols to avoid edge cases in shell parsing.
    'override TEST_VERBOSE' => "scalar (eval chr(36) . q(ENV{HARNESS_OPTIONS}='j$jobs'), 0)",
  },
);

How it works

  1. Parallel Build (MAKEFLAGS): This sets the -j flag for GNU Make. Please note that this only works with gmake.
  2. Parallel Test Hack (override TEST_VERBOSE): This is the "dirty" part. I’m hijacking the TEST_VERBOSE macro, which is usually intended for toggling verbose output. By using eval and chr(36) (to avoid the $ symbol which causes pain in PowerShell), I’m forcing the HARNESS_OPTIONS environment variable to be set to j$jobs just as the test suite starts.

The constraints in the comments ensure that this string remains "shell-safe" even on Windows (cmd.exe or PowerShell), avoiding backslashes and nested parentheses that usually break the build.

Is there a better way?

I’ll be honest: this works, but I’m not sure if it’s the "best" or "right" way to do it. It feels like a brilliant hack, but hacking TEST_VERBOSE to set environment variables is definitely not what the ExtUtils::MakeMaker authors intended.

I’d love to hear your thoughts:

  • Is there a built-in WriteMakefile option that handles parallel tests natively?
  • Have you found a cleaner way to pass HARNESS_OPTIONS through the generated Makefile without shell-escaping hell?

If you have a more "canonical" approach, please let me know in the comments!

Açık kaynak dünyasında bazen en masum görünen modüller bile ciddi güvenlik riskleri taşıyabiliyor. Dün yayınlanan CVE-2026–5081, tam da bu…

Signing CPAN Releases with SigStore

blogs.perl.org

Signing CPAN Releases with SigStore

At the most recent Perl Tool Chain Summit (PTS) in Vienna we decided to deprecate Module::Signature. Module::Signature has been around for a long time but it has become increasingly clear that it does not provide the security assurances that it was designed to deliver.

Dist::Zilla::Plugin::SigStore::SignRelease is a new plugin that signs your CPAN release with SigStore before uploading. SigStore uses short-lived, OIDC-issued certificates. You authenticate with Google, GitHub, or Microsoft, and cosign produces a signature bundle. No long-lived keys, no keyserver dance.

How it works

The plugin extends the Dist::Zilla plugin UploadToCPAN. During the dzil release, it:

  1. Calls cosign sign-blob on your release archive, producing a .sigstore.json bundle file.
  2. Pulls the certificate out of the bundle and verifies the signature locally before anything leaves your machine.
  3. Uploads both the tarball and the bundle to PAUSE.

SigStore also add the release and signature information to the Rekor Transparency Log. Rekor is SigStore's public append-only transparency log, and the inclusion proof is bundled into the .sigstore.json. Anyone verifying your release can confirm the signing event was publicly logged at the time it happened.

Using it

Remove the existing uploader from your Dist::Zilla configuration file (dist.ini) and add the plugin to your dist.ini:

[@Filter]
bundle = @Basic
remove = UploadToCPAN ; Do this if you use @Basic

[SigStore::SignRelease]

Non-CPAN Requirements

You'll need cosign in your PATH.

Next Steps

PAUSE doesn't verify SigStore signatures yet, and no CPAN client checks them on install.

This is opt-in transparency: the bundle ships alongside the tarball, and anyone who cares can verify it themselves.

Hopefully more support for SigStore will be developed soon. Until then signing your releases will allow them to be verified:

cosign verify-blob Your-Dist-0.01.tar.gz \
--bundle Your-Dist-0.01.tar.gz.sigstore.json \
--certificate-identity you@example.com \
--certificate-oidc-issuer https://accounts.google.com

It's a first step, towards verifiable releases in the Perl ecosystem. Try it on your next release and let me know how it goes.

Answer

It’s kinda complicated. i3 exposes a scratchpad_state on every node, but it is not relevant for the node you initially check. The window node of the application you are targetting. Today we are scratching an itch.

When I saw this question posted on Reddit I first thought: let’s look at the data. My Workspace on Demand daemon has an IPC event debugger. So I fired it up and went looking at the data, my debugger told me:

Ideas for the CPAN Meta v3 Specification

blogs.perl.org

At the 2026 Perl Toolchain Summit Salve Nilsen and I proposed some ideas that we have been discussing on and off for the past several months for CPANSec, for a CPAN Meta v3 Specification.

Why does the specification need to be extended?

Version 2 of the CPAN Meta Spec (CPAN distributio n metadata specification) is does not allow the addition of new data, except using fields prefixed by "x_".

However, there is a need to include additional metadata about:

  • external dependencies (services, libraries, files, or environment variable)
  • embedded external libraries, e.g. zlib or bootstrap.
  • licensing
  • vulnerability reporting
  • parent-child relationships (e.g. forked project)
  • fixed vulnerabilities in this fork or in embedded libraries
  • code and documentation generated through automation or using LLMs
  • how and where to report security vulnerabilities
  • project funding and sponsorship
  • how the project is supported by the maintainers
  • enumeration of community health documents, e.g. SECURITY.md, GOVERNANCE.md and AI_POLICY.md

This is too much information to embed in existing META.json files, and some of this metadata exists in alternative formats, for example:

Note that most of this data is not necessary for installing CPAN modules. It exists mainly for documentation and auditing:

  • generating SBOMs for an application using its dependencies
  • auditing software for security vulnerabilities
  • auditing software for license compliance
  • displaying the external documentation for a module such as the security policy

Specification

The specification is simple:

  1. All new metadata will be saved in the CPAN-META directory at the root of the distribution and software repository.

  2. All files and subdirectories saved in that directory will have well-known names.

    Currently there is automation-policy.json for the AI and Automation Policy metadaya, that I have worked with Nicolas Rochelemagne. This will be discussed in a separate blog post.

  3. The metadata should never be added as "x_" keys to the META.yml or META.json files.

  4. This metadata may be provided as a separate file from a distribution.

The proposed specification can be found at https://github.com/CPAN-Security/cpan-metadata-v3

To suggest addition or changes, please create an issue or pull request.

Tooling

There are not yet tools for handling the METAv3 specification.

The tools will need to minimise the workload for project maintainers.

Modules should be configurable, testable and installable without any tools that support this specification. However, metadata may be useful for tools that understand them, for example, to ensure external dependencies are met.

Thanks

Thanks to the organisers of the PTS for their hard work and hospitality, and to the sponsors who are keeping the Perl ecosystem growing:

I was working on some code that creates a reference to a substring call, and then passes that. I assumed this would be a SCALAR ref, since it seems like it would just be a SCALAR with a string in it? Why does this ref return LVALUE?

use strict;
use warnings;
use feature 'say';

my $str = "hello";
my $lv = \substr($str, 1, 2);
say ref($lv);          # LVALUE

I did a little dig in perlguts and I do see the LVALUE SVTYPE, so I assume this is intended behavior.

Update AUTHORS

Perl commits on GitHub
Update AUTHORS
Update .mailmap with correct email address

Run Porting/updateAUTHORS.pl

bump $SelfLoader::VERSION

Perl commits on GitHub
bump $SelfLoader::VERSION
SelfLoader: don't mix buffered and unbuffered I/O

For a long time SelfLoader has produced occasional smoke failures on
stdio runs, for example:

https://perl5.test-smoke.org/report/5530055
  (aka https://perl.develop-help.com/db/5530055)
https://perl.develop-help.com/dblog/5530141
  (aka https://perl.develop-help.com/db/5530141)
https://perl.develop-help.com/dblog/5529979
  (aka https://perl.develop-help.com/db/5529979)

I've tried to track this down before, usually looking for interactions
between stdio and perlio in the perlio implementation.

When looking at it again, I saw the SelfLoader code was mixing
buffered I/O (tell()) and unbuffered I/O (sysseek()) which I could see
possibly leaving an unexpected difference in the seek position.

I suspect this *is* causing some bad interaction, if you look at the
strace before and after the change.

before:

write(1, "1..1\n", 5)                   = 5
lseek(3, 0, SEEK_CUR)                   = 808
lseek(3, 250, SEEK_SET)                 = 250
fcntl(3, F_DUPFD_CLOEXEC, 0)            = 4
fcntl(4, F_GETFD)                       = 0x1 (flags FD_CLOEXEC)
fcntl(4, F_GETFL)                       = 0x8000 (flags O_RDONLY|O_LARGEFILE)
fcntl(4, F_SETFD, FD_CLOEXEC)           = 0
fstat(4, {st_mode=S_IFREG|0644, st_size=808, ...}) = 0
lseek(3, 0, SEEK_CUR)                   = 250
close(3)                                = 0
close(4)                                = 0
write(1, "not ok 1 - syntax errors are rep"..., 38) = 38

after:

write(1, "1..1\n", 5)                   = 5
lseek(3, 0, SEEK_CUR)                   = 808
lseek(3, 0, SEEK_SET)                   = 0
read(3, "use SelfLoader;\nprint \"1..1\\n\";\n"..., 4096) = 808
fcntl(3, F_DUPFD_CLOEXEC, 0)            = 4
fcntl(4, F_GETFD)                       = 0x1 (flags FD_CLOEXEC)
fcntl(4, F_GETFL)                       = 0x8000 (flags O_RDONLY|O_LARGEFILE)
fcntl(4, F_SETFD, FD_CLOEXEC)           = 0
fstat(4, {st_mode=S_IFREG|0644, st_size=808, ...}) = 0
lseek(3, 250, SEEK_SET)                 = 250
close(3)                                = 0
fcntl(4, F_DUPFD_CLOEXEC, 0)            = 3
fcntl(3, F_GETFL)                       = 0x8000 (flags O_RDONLY|O_LARGEFILE)
fcntl(3, F_SETFD, FD_CLOEXEC)           = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=808, ...}) = 0
close(4)                                = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=808, ...}) = 0
read(3, "\nsub buggy\n{\n    +>*;\n}\n\n\n# RT 4"..., 4096) = 558
read(3, "", 4096)                       = 0
write(1, "ok 1 - syntax errors are reporte"..., 34) = 34

we can see in the original the file offset is at end of file, and that
is copied into the cloned handles, so reads of the cloned
handles (which don't have the buffer of the original handle) read
nothing, and in the case of this particular
test (02SelfLoader-buggy.t) the erroneous code isn't read and no error
is reported.

It's unclear to me what the original change here was intended to fix,
the original change here landed in two steps, first add1a1a3c3d,
discussed in
https://www.nntp.perl.org/group/perl.perl5.porters/2006/12/msg119237.html
and then 054149a8968 discussed in the thread starting at
https://www.nntp.perl.org/group/perl.perl5.porters/2006/12/msg119449.html
Since we're dupping the handle each time (see the strace for the
success case) I'd expect the descriptor to share their file positions
between forked processes anyway.
cpan/Encode - Update to version 3.24

$Revision: 3.24 $ $Date: 2026/04/29 17:36:34 $
! Byte/Makefile.PL
  Pulled: Byte/Makefile.PL: ensure build reproducibility again
  https://github.com/dankogai/p5-encode/pull/187
! Changes
  More fixes to revision subheaders
  https://github.com/dankogai/p5-encode/commit/eac1089847d488abfe412009ad41d7555921e8d2
  Pulled: Restore correct revision subheader for 3.22
  https://github.com/dankogai/p5-encode/pull/186

Originally published at Perl Weekly 771

Hi there,

I put the 'Testing in Perl' course on hold for now. Instead of that we are going to explore the use of some of the mocking libraries we saw during the course. In the next session we'll pick one of the Perl modules used for mocking and we'll look for modules that use it. We'll try to understand how it is being used and we'll try to contribute something to at least one of the modules.

For background you can check the OSDC Perl page where we have a listing of modules for 'Code reading'.

You can also watch the recording of the Testing in Perl course. (Registration required but these videos are currently free of charge.)

Enjoy your week!

--
Your editor: Gabor Szabo.

Articles

ANNOUNCE: Perl.Wiki V 1.45 etc

TPRC Announces Post Conference Class

Steven Lembark is presenting: Teaching AI New Tricks: Perly MCP's for Claude.

Who tests the tester? Me !!!

I was just showing the participants of the 'Testing in Perl' course how to write and test a Test::* module. It is nice to see that about the same time Lichtkind wrote an article about the same topic.

Reading CPAN Testers Reports Using AI Agents

A very interesting and useful use of AI.

Discussion

sending matrix messages

Module naming vs CPAN conventions

Perl in Ubuntu 26-04 LTS (vs 24-04 LTS)

Some Perl modules need a dev package to be installed using 'apt'.

The Weekly Challenge

The Weekly Challenge by Mohammad Sajid Anwar will help you step out of your comfort-zone. You can even win prize money of $50 by participating in the weekly challenge. We pick one champion at the end of the month from among all of the contributors during the month, thanks to the sponsor Marc Perry.

The Weekly Challenge - 372

Welcome to a new week with a couple of fun tasks "Rearrange Spaces" and "Largest Substring". If you are new to the weekly challenge then why not join us and have fun every week. For more information, please read the FAQ.

RECAP - The Weekly Challenge - 371

Enjoy a quick recap of last week's contributions by Team PWC dealing with the "Missing Letter" and "Subset Equilibrium" tasks in Perl and Raku. You will find plenty of solutions to keep you busy.

Perl Weekly Challenge 371: Missing Letter

Abigail gives us an efficient solution, O(n), to the missing letter problem by determining the difference between the sum of all letters in a full range of letters and the sum of the letters in the given array. This eliminates unnecessary loops, which is a big advantage over an iterative search.

Perl Weekly Challenge 371: Subset Equilibrium

This solution is a well-organised that defines the NP-complete status of this problem and also implements an efficient bitmasking solution in numerous programming languages (e.g., Perl, Python, AWK, C, etc.) and serves as an excellent reference for other programmers to learn about and use for comparison purposes.

Missing Equilibrium

The Raku Solution implements this approach using combinations to easily create subsets in an idiomatic and very readable way. It also provides an elegant way of compensating for zero-based indices when calculating the sum of the positions in each subset by the calculated subsets.

Subset Equilibrium (Just nod if you can hear me)

Bob addressed the exponential number of complex solutions from the outset and provided a practical implementation based on Algorithm::Combinatorics. The use of a positional-based iterator with zero-index compensation allowed him to effectively handle the off-by-one requirement. In addition to that, Bob supplied practical testing strategies using the Test2::Tools::Compare package.

Perl Weekly Challenge: Week 371

This solution gives a clean, efficient implementation that fulfills both the requirement of a proper subset and utilising 1-based indexing by adding + @combo.elems to the index sum. By making use of combinations from Algorithm::Combinatorics in Perl (and using built-in combinations in Raku), only the necessary subset sizes are generated; the output formatting handles both the non-empty and empty result cases in a very clean manner via a clear ternary expression.

Missing Equilibria

This new solution offers an evaluation of the subset equilibrium problem that is a new and interesting technical perspective and acknowledges the shared views of the two demonstrated examples, while not overlooking those cases where the two may have disagreement along the edges of each set of criteria. The Perl code produced using Math::Prime::Util::forcomb and List::Gather is exceptionally simple but has an artistic style as well.

Perl Weekly Challenge 371

The post describes a simple yet effective way to generate all proper subsets of a set. It uses the subset function of the Algorithm::Combinatorics module to create all proper subsets, and then filters them based on a very concise expression written as a single line. The script contains both a command-line version for easy use and a complete version that includes error handling and has been thoroughly tested.

The Missing Equilibrium

By mathematically restructuring the main condition in the problem, i.e. equality of the total values for elements and their corresponding indices. Additionally, utilising the combination iterators from Algorithm::Combinatorics with subset sizes of 2 through n-1 provides a memory efficient approach to solving this exponential complexity problem while utilizing an optimally prepared array of pre-computed offsets, or at least the offsets for an individual index, gives evidence of thoughtful programming.

My subset is-a missing a letter…

The solution has a nice, clean, multi-language implementation (Raku, Perl, Python, Elixir) and uses indexed pairs of values to keep track of the relationship between the two elements and the index position of the two elements, thus having no off-by-one errors. The solution meets the "proper subset" constraints by filtering out empty sets, singletons, and the full set as well as providing lots of detail in verbose output to help educate the user by giving exact values and sums of values for each match.

Solve the question and balance the subset

By using established neighboring letter pairs, the algorithm determines the steps $a and $b in an efficient manner by deducing which are the next alternately patterned letter combinations in relation to each of the five positions for question marks. The entire algorithm utilises a single conditional expression to account for all five possible question mark positions. Additionally, the use of the modulus operator and the mapping of the patterning logic from the analysis to the assignment of steps makes this implementation both very efficient and very easy to read.

The Weekly Challenge - 371: Missing Value

This solution requires formalization of the detection of repeating patterns through a comprehensive and well-documented approach by treating them as a repeating two-stage model (i.e., d1 = d3 and d2 = d4). Special consideration is given to both constant and alternating sequences. The use of defined-or (i.e., //=) to unify step values, along with the straightforward relationship between each possible question mark position and its simple arithmetic reconstruction, contributes to creating a code that is highly reliable and easy to follow. Additionally, the thorough input validation provided demonstrates significant consideration for actually implementing in a real world scenario.

The Weekly Challenge - 371: Subset Equilibrium

This document includes an excellent comparison of two CPAN modules (Algorithm::Combinatorics and Data::PowerSet) that correctly identifies the combinatorically generated subsets by size (2 to n) are more efficient than generating the power set and filtering out the subsets to produce the same result for this specific task. The code base is very clean and well-structured; it also does a great job of utilising list slicing (@nums[@$subset]) and map to convert from 0-based to 1-based position, while making use of the helper function print_result to ensure that all output for all test cases is consistent and easy to read.

The Weekly Challenge #371

This document offers an innovative and realistic perspective on the subset equilibrium issue by acknowledging there is no "smart" optimisation to the subset equilibrium solution and then using a simple combination-based search on the two to n-1 sizes. The solution was implemented correctly with a summation on 1-indexed values for each of those combinations.

Missing Equilibrium

Roger demonstrates a very clever way of optimizing the solution through the precomputation of one list of differences so that only the check for the sum of the selected differences equals zero must occur, which substantially reduces the amount of computing needed within the combinatorial loops. Roger does a great job of providing a perspective of how to write this in many programming languages (Raku, Kotlin, Crystal, etc.) and provides many practical examples (like sorting the list to create a consistent order for output), thereby making this solution efficient and easily transferable across many different programming languages.

Question the bits

Using bit manipulation, this solution works well to create all combinations of subsets by iterating through all integers from 1 to 2^n-2, thus avoiding the empty and full subsets. The inner loop adds pos+1 because the indices in a array have a base index of 1, while the implementation does not require any third-party modules, making it portable and easy to use for individuals who are already comfortable using bit mask manipulation.

Weekly collections

NICEPERL's lists

Great CPAN modules released last week.

Event reports

Welcome to the Perl Toolchain Summit 2026!

That's how it started

Perl Toolchain Summit in Vienna

A little review by the local orga.

Events

Exploring Perl Modules

May 7, 2026

Boston Perl Mongers virtual monthly

May 12, 2026

The Perl and Raku Conference 2026

June 26-29, 2026, Greenville, SC, USA

You joined the Perl Weekly to get weekly e-mails about the Perl programming language and related topics.

Want to see more? See the archives of all the issues.

Not yet subscribed to the newsletter? Join us free of charge!

(C) Copyright Gabor Szabo
The articles are copyright the respective authors.

Weekly Challenge: Distant spaces

dev.to #perl

Weekly Challenge 372

Each week Mohammad S. Anwar sends out The Weekly Challenge, a chance for all of us to come up with solutions to two weekly tasks. My solutions are written in Python first, and then converted to Perl. Unless otherwise stated, Copilot (and other AI tools) have NOT been used to generate the solution. It's a great way for us all to practice some coding.

Challenge, My solutions

Task 1: Rearrange Spaces

Task

You are given a string text of words that are placed among number of spaces.

Write a script to rearrange the spaces so that there is an equal number of spaces between every pair of adjacent words and that number is maximized. If you can’t distribute, place the extra spaces at the end. Finally return the string.

My solution

This is a little more straight forward than last weeks challenge one task. For this task, I take start by separating the words on whitespace, and count the number of spaces. This is stored in the words list (array in Perl) and spaces variable.

def rearrange_space(input_string: str) -> str:
    words = input_string.split()
    spaces = input_string.count(" ")

To avoid a division by zero error, I handle the case where there is a single word. For this I return the word followed by the required number of spaces. In Python, a string multiple by an integer will repeat the string the specified number of times.

    if len(words) == 1:
        return words[0] + " " * spaces

If there is more than one word, I calculate the spaces_between_words and spaces_at_end using the divmod function. I return the string with the spaces in the appropriate places.

    spaces_between_words, spaces_at_end = divmod(spaces, len(words)-1)
    return (" " * spaces_between_words).join(words) + " " * spaces_at_end

The Perl solution uses the same logic. As it does not have a divmod function, I calculate the two values separately. The x operator in Perl is used for repetition. The expression $#words returns one less than the length of the words array.

sub main ($input_string) {
    my @words  = grep { $_ ne "" } split /\s+/, $input_string;
    my $spaces = ( $input_string =~ tr/ / / );

    if ( $#words == 0 ) {
        say '"' . $words[0] . " " x $spaces . '"';
    }
    else {
        my $spaces_between_words = int( $spaces / $#words );
        my $spaces_at_end        = $spaces % $#words;
        say '"'
          . join( " " x $spaces_between_words, @words )
          . " " x $spaces_at_end . '"';
    }
}

Examples

$ ./ch-1.py "  challenge  "
"challenge    "

$ ./ch-1.py "coding  is  fun"
"coding  is  fun"

$ ./ch-1.py "a b c  d"
"a b c d "

$ ./ch-1.py "  team      pwc  "
"team          pwc"

$ ./ch-1.py "   the  weekly  challenge  "
"the    weekly    challenge "

Task 2: Largest Substring

Task

You are given a string.

Write a script to return the length of the largest substring between two equal characters excluding the two characters. Return -1 if there is no such substring.

My solution

For this task, I start by creating a dict (hash in Perl) called freq with the frequency of each letter. In Python, this is achieved with the Counters function from the collections module.

I then iterate through the dict. If the letter occurs more than once, I calculate the difference between position of the first and last occurrence, minus one. Both Python and Perl have the index and rindex methods to do this. I update the largest value if this is greater than previously found values.

from collections import Counter

def largest_substring(input_string: str) -> int:
    freq = Counter(input_string)
    largest = -1

    for letter, count in freq.items():
        if count > 1:
            substr = input_string.rindex(letter) - input_string.index(letter) - 1
            if substr > largest:
                largest = substr

    return largest

The Perl solution follows the same logic.

sub main ($input_string) {
    my %freq = ();
    $freq{$_}++ foreach ( split //, $input_string );
    my $largest = -1;

    while ( my ( $letter, $count ) = each %freq ) {
        if ( $count > 1 ) {
            my $substr = rindex( $input_string, $letter ) -
              index( $input_string, $letter ) - 1;
            $largest = $substr if ( $substr > $largest );
        }
    }

    say $largest;
}

Examples

$ ./ch-2.py aaaaa
3

$ ./ch-2.py abcdeba
5

$ ./ch-2.py abbc
0

$ ./ch-2.py abcaacbc
4

$ ./ch-2.py laptop
2

$ ./ch-2.py abc
-1
Updates for great CPAN modules released last week. A module is considered great if its favorites count is greater or equal than 12.

  1. App::ClusterSSH - Cluster administration tool
    • Version: 4.19 on 2026-04-28, with 980 votes
    • Previous CPAN version: 4.18_09 was released 1 month, 7 days before
    • Author: DUNCS
  2. App::cpm - a fast CPAN module installer
    • Version: v1.0.3 on 2026-05-02, with 178 votes
    • Previous CPAN version: v1.0.2 was released the same day
    • Author: SKAJI
  3. App::Music::ChordPro - A lyrics and chords formatting program
    • Version: v6.101.0 on 2026-04-30, with 469 votes
    • Previous CPAN version: v6.100.0 was released 8 days before
    • Author: JV
  4. Cache::FastMmap - Uses an mmap'ed file to act as a shared memory interprocess cache
    • Version: 1.61 on 2026-04-30, with 25 votes
    • Previous CPAN version: 1.60 was released 10 months, 12 days before
    • Author: ROBM
  5. CPANSA::DB - the CPAN Security Advisory data as a Perl data structure, mostly for CPAN::Audit
    • Version: 20260430.001 on 2026-04-30, with 25 votes
    • Previous CPAN version: 20260426.001 was released the same day
    • Author: BRIANDFOY
  6. DBD::Pg - DBI PostgreSQL interface
    • Version: 3.20.2 on 2026-05-02, with 103 votes
    • Previous CPAN version: 3.20.1 was released 2 days before
    • Author: TURNSTEP
  7. Encode - character encodings in Perl
    • Version: 3.24 on 2026-04-29, with 65 votes
    • Previous CPAN version: 3.23 was released 2 days before
    • Author: DANKOGAI
  8. MetaCPAN::Client - A comprehensive, DWIM-featured client to the MetaCPAN API
    • Version: 2.043000 on 2026-04-29, with 29 votes
    • Previous CPAN version: 2.042000 was released 3 days before
    • Author: MICKEY
  9. Plack - Perl Superglue for Web frameworks and Web Servers (PSGI toolkit)
    • Version: 1.0053 on 2026-04-28, with 496 votes
    • Previous CPAN version: 1.0052 was released 1 day before
    • Author: MIYAGAWA
  10. PPI - Parse, Analyze and Manipulate Perl (without perl)
    • Version: 1.291 on 2026-04-25, with 64 votes
    • Previous CPAN version: 1.290 was released the same day
    • Author: MITHALDU
  11. SPVM - The SPVM Language
    • Version: 0.990168 on 2026-04-27, with 36 votes
    • Previous CPAN version: 0.990167 was released 2 days before
    • Author: KIMOTO
  12. Starman - High-performance preforking PSGI/Plack web server
    • Version: 0.4018 on 2026-04-27, with 297 votes
    • Previous CPAN version: 0.4017 was released 2 years, 7 months, 13 days before
    • Author: MIYAGAWA
  13. Test::MockModule - Override subroutines in a module for unit testing
    • Version: v0.183.0 on 2026-05-01, with 18 votes
    • Previous CPAN version: v0.182.0 was released the same day
    • Author: GFRANKS
  14. Test::Most - Most commonly needed test functions and features.
    • Version: 0.41 on 2026-04-30, with 37 votes
    • Previous CPAN version: 0.40 was released the same day
    • Author: DCANTRELL
  15. Test2::Harness - A new and improved test harness with better Test2 integration.
    • Version: 1.000172 on 2026-04-29, with 28 votes
    • Previous CPAN version: 1.000171 was released 5 days before
    • Author: EXODIST
  16. Text::CSV_XS - Comma-Separated Values manipulation routines
    • Version: 1.62 on 2026-04-29, with 104 votes
    • Previous CPAN version: 1.61 was released 9 months, 2 days before
    • Author: HMBRAND
  17. YAML::LibYAML - Perl YAML Serialization using XS and libyaml
    • Version: v0.906.0 on 2026-04-26, with 60 votes
    • Previous CPAN version: v0.906.0 was released the same day
    • Author: TINITA

TPRC Announces Post Conference Class

Perl Foundation News

TPRC is proud to announce the post-conference class on Monday, June 29. Steven Lembark is presenting: Teaching AI New Tricks: Perly MCP’s for Claude. The class will be from 9am to 4pm in the Palmetto Room at the Conference Hotel. SHORT DESCRIPTION: MCP definitions are not your mother’s API call. They are natural language descriptions meant for the tool to process and integrate into its own workflow. With proper description, the MCP’s are simple to use and can be daisy-chained to produce quite flexible results. Claude’s different models — Haiku, Sonnet, Opus — offer different options for cost, speed, and depth and require different definitions. In the spirit of the Perl Tester’s Notebook, this class covers a very basic tool then expands it for general use, one step at a time, looking at the necessary changes to Perl and the MCP. The result is a lightweight, fast interface that handles a common task easily and can be incorporated with other tools. For a more complete class description, please visit: https://tprc.us/tprc-2026-gsp/class/ Tickets for the class are $120 and are available at: https://perlfoundation.fcsuite.com/erp/donate/list/event?event_date_id=1002

Toronto Perl Mongers / March 2026 Talk

The Perl and Raku Conference YouTube channel

Last week the Perl Toolchain Summit took place in Vienna (the second time, we also hosted the event in 2010). I participated mostly in the role of local orga, helping the international team finding a venue and a hotel, a place for the social event etc.

For the venue, Michael suggested Hauswirtschaft, where Geizhals sometimes hold meetings and events. This turned out to be an excellent choice. Not only did we get the rooms at a reasonable price (at least when compared to some of the more mainstream hotels), but the rooms and the whole venue were very nice and extremely accommodating: We were served vegetarian lunch each day, could bring our own snacks and drinks (the delicious apple juice Hauswirtschaft provided was not enough for some of the very specific Cola needs of some attendees; luckily there's a supermarket right across) and have a Pizza-and-Chartreuse-Party on Saturday (which I have been told ended quite late). Transporting 16 boxes of pizza on my bike trailer was also fun!

I had little time for technical contributions, as I was busy organizing dinners, answering peoples questions about Vienna and generally having a nice time. But I managed to code up a local development environment for PAUSE based on docker compose. Andreas merged it on Sunday, so if you want to have a local PAUSE (for testing and developing new features), you can now do so via a simple docker compose up pause paused. Detailed instructions can be found here and I would appreciate it if people give it a try and report back any problems they have. Having a working local dev setup is IMO crucial for getting meaningful contributions.

Together with Michael I was interviewed by Philippe for his podcast. We talked about the history and future of Vienna.pm, Geizhals, Koha and some other topics. The interview and the whole event motivated me to keep Vienna.pm going and maybe try to host a Perl-and-related-tech-events (again) in the future. We'll see...

To summarize, I really enjoyed the event, hanging around with Perl people & talking about basically everything. It was a nice reminder that this community is exactly my kind of crazy! Looking forward to the next PTS or similar event!

And no post about the PTS is complete without thanking the sponsors (is it ok to thank yourself, as I'm involved with two of the sponsors (Vienna.pm and HSK3)? I say it is...)

I am currently trying to install this module with :

perl -MCPAN -e shell install XML::Parser::Style::Tree

It did not install it, but it installed cpan. Inside cpan, I issued again:

install XML::Parser::Style::Tree

At beginning, the process was going well. Then suddenly I receive these error messages:

Can't locate File/ShareDir/Install.pm in @INC (@INC contains: ./inc /etc/perl /usr/local/lib/perl/5.10.1 /usr/local/share/perl/5.10.1 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.10 /usr/share/perl/5.10 /usr/local/lib/site_perl .) at Makefile.PL line 7.
BEGIN failed--compilation aborted at Makefile.PL line 7.
Warning: No success on command[/usr/bin/perl Makefile.PL INSTALLDIRS=site]
Warning (usually harmless): 'YAML' not installed, will not store persistent state
  TODDR/XML-Parser-2.58.tar.gz
  /usr/bin/perl Makefile.PL INSTALLDIRS=site -- NOT OK
Running make test
  Make had some problems, won't test
Running make install
  Make had some problems, won't install
Could not read '/home/leopoldo/.cpan/build/XML-Parser-2.58-jE9k8s/META.yml'. Falling back to other methods to determine prerequisites
Failed during this command:
 TODDR/XML-Parser-2.58.tar.gz                 : writemakefile NO '/usr/bin/perl Makefile.PL INSTALLDIRS=site' returned status 512

So, I am open to suggestions on how to solve it.

On this site and elsewhere, there are quite a few questions about how to handle errors when printing to a pipe in Perl. But none of the answers, solutions and explanations I have seen so far are applicable to my specific case. Please consider the following situation:

Under Linux (Debian 13), I have a folder /path where only the root user has write permissions. Further, I have a Perl script that includes the following snippet:

if (!(open($fh_Pipe, '|-:utf8', '/usr/bin/weasyprint',
                                '-',
                                '/path/test.pdf')))
{
  # Perform error action here.
}

if (!(print($fh_Pipe 'Foo')))
{
  # Perform error action here.
}

If I execute that script as root, everything works: A PDF file /path/test.pdf is created that contains the text "Foo".

But if I execute that script as another user, it does not behave as expected: Indeed, the PDF file /path/test.pdf is not created, and Perl outputs a warning regarding the missing write permission. But to my surprise, none of the error action blocks executes.

That is a problem because I'd like to detect all errors with writing to the pipe from within the script, of course with reasonable effort, and the behavior I have observed seems to contradict the documentation. From the documentation for print:

Prints a string or a list of strings. Returns true if successful. [...]

Although it is formally not correct, I interpret this as it would say "... and false if not successful ..." in addition. But then it contradicts what I observe.

So my question is:

Is there a general method to detect all errors that may occur when printing to a pipe, including situations where the user that executes the respective script simply does not have write permission in the respective directory?

And why does Perl not behave as documented?

[ Side note #1: To be clear, I'm not interested in which error exactly has occurred. I just would like to know whether or not the print has succeeded, completely regardless of the kind of the possible error. ]

[ Side note #2: The page linked above later on explains that a SIGPIPE signal will be raised if we try to print to a closed pipe or socket. However, I didn't try to implement an error handling for print based on signals, because that would be a high effort for a very simple thing, and because I am not sure if a missing write permission would effect the same as a closed pipe. ]

Updates for great CPAN modules released last week. A module is considered great if its favorites count is greater or equal than 12.

  1. App::Music::ChordPro - A lyrics and chords formatting program
    • Version: v6.100.0 on 2026-04-21, with 468 votes
    • Previous CPAN version: v6.090.1 was released 3 months, 18 days before
    • Author: JV
  2. App::Netdisco - An open source web-based network management tool.
    • Version: 2.098002 on 2026-04-20, with 864 votes
    • Previous CPAN version: 2.098001 was released 3 days before
    • Author: OLIVER
  3. App::perlimports - Make implicit imports explicit
    • Version: 0.000059 on 2026-04-24, with 24 votes
    • Previous CPAN version: 0.000058 was released 5 months, 26 days before
    • Author: OALDERS
  4. Bitcoin::Crypto - Bitcoin cryptography in Perl
    • Version: 4.005 on 2026-04-23, with 18 votes
    • Previous CPAN version: 4.004 was released 1 month, 5 days before
    • Author: BRTASTIC
  5. CPANSA::DB - the CPAN Security Advisory data as a Perl data structure, mostly for CPAN::Audit
    • Version: 20260419.002 on 2026-04-19, with 25 votes
    • Previous CPAN version: 20260412.001 was released 7 days before
    • Author: BRIANDFOY
  6. CryptX - Cryptographic toolkit
    • Version: 0.088 on 2026-04-23, with 53 votes
    • Previous CPAN version: 0.087_008 was released the same day
    • Author: MIK
  7. DateTime::TimeZone - Time zone object base class and factory
    • Version: 2.68 on 2026-04-23, with 22 votes
    • Previous CPAN version: 2.67 was released 1 month, 17 days before
    • Author: DROLSKY
  8. DBIx::Class::InflateColumn::Serializer - Inflators to serialize data structures for DBIx::Class
    • Version: 0.10 on 2026-04-19, with 13 votes
    • Previous CPAN version: 0.09 was released 9 years, 3 months, 4 days before
    • Author: MRUIZ
  9. Encode - character encodings in Perl
    • Version: 3.22 on 2026-04-25, with 65 votes
    • Previous CPAN version: 3.21 was released 2 years, 1 month, 28 days before
    • Author: DANKOGAI
  10. Google::Ads::GoogleAds::Client - Google Ads API Client Library for Perl
    • Version: v32.0.0 on 2026-04-22, with 20 votes
    • Previous CPAN version: v31.1.0 was released 27 days before
    • Author: CHEVALIER
  11. Log::Any - Bringing loggers and listeners together
    • Version: 1.720 on 2026-04-25, with 69 votes
    • Previous CPAN version: 1.719 was released 1 month, 8 days before
    • Author: PREACTION
  12. MetaCPAN::Client - A comprehensive, DWIM-featured client to the MetaCPAN API
    • Version: 2.041000 on 2026-04-25, with 29 votes
    • Previous CPAN version: 2.040000 was released 1 month, 16 days before
    • Author: MICKEY
  13. Module::CoreList - what modules shipped with versions of perl
    • Version: 5.20260420 on 2026-04-20, with 45 votes
    • Previous CPAN version: 5.20260330 was released 21 days before
    • Author: BINGOS
  14. PPI - Parse, Analyze and Manipulate Perl (without perl)
    • Version: 1.290 on 2026-04-25, with 64 votes
    • Previous CPAN version: 1.289 was released the same day
    • Author: MITHALDU
  15. SPVM - The SPVM Language
    • Version: 0.990167 on 2026-04-24, with 36 votes
    • Previous CPAN version: 0.990166 was released the same day
    • Author: KIMOTO
  16. Test2::Harness - A new and improved test harness with better Test2 integration.
    • Version: 1.000171 on 2026-04-23, with 28 votes
    • Previous CPAN version: 1.000170 was released 13 days before
    • Author: EXODIST
  17. YAML::LibYAML - Perl YAML Serialization using XS and libyaml
    • Version: v0.905.0 on 2026-04-24, with 60 votes
    • Previous CPAN version: v0.905.0 was released 1 day before
    • Author: TINITA
  18. YAML::PP - YAML 1.2 Processor
    • Version: v0.40.0 on 2026-04-24, with 27 votes
    • Previous CPAN version: v0.40.0 was released 1 day before
    • Author: TINITA
  19. YAML::Syck - Fast, lightweight YAML loader and dumper
    • Version: 1.45 on 2026-04-23, with 18 votes
    • Previous CPAN version: 1.44 was released 20 days before
    • Author: TODDR

cpan.org email forwarding has been shut down

The Perl NOC

We know this will disappoint many of you, but it was not feasible to keep it running anymore with a reasonable quality.

Several factors led to the decision:

  • Communication has moved on. 25 years ago it was important to provide a consistent way for users to contact CPAN module authors.  Today people use rt.cpan.org, forums, and other channels..

  • Over 99% of mail was spam.  Even two layers of spam filtering let some through, hurting deliverability for our other outbound mail.

  • Modern email requires ARC, DKIM, DMARC, and SPF. Providing these correctly on a forwarding service is hard, so legitimate mail often fails to be delivered.

  • We approached several email providers, but were unable to find one to host the service.

  • The number of active users was small compared to the number of CPAN authors. During a recent extended, unannounced outage, we received only two inquiries.


We will work with the cpan-security team so they keep a way to reach authors.


Bounces will soon report that the MX cpan.org-email-is-gone-use-http-rt.cpan.org cannot be found as a breadcrumb pointing toward alternatives.


cpan.org email was one of the first services we ran for the Perl community; over the years we've added others, some for the wider internet. The perl.org mailing lists have been running for 28 years and continue to operate. We also still help run PAUSE and the CPAN archive system. rt.cpan.org remains available, thanks to The Perl Foundation and Request Tracker.


For most of its 25 years, cpan.org email was widely used, and running it was a pleasure. The last seven or eight became mostly spam fights and deliverability work — a big part of why we're stopping now.


Please don't ask us to reconsider. We recognize this change will affect some users more than others.  We wrestled with this for a long time and are confident that it is the right call. 


Sincerely,


The perl noc volunteers


TL;DR

This is a rant, a developer war story of how to use docker to work around a problem that shouldn’t exist in the first place. Configure your PHP project with a different, lower version, of PHP than you are using yourself. Read how I spent an evening fighting PHP.

The bare minimum

Can someone in PHP land please tell me why composer is unable to create a project on PHP version 8.2 when I’m running PHP version 8.4? I’m creating a PHP app that needs to run on bookworm, aka, Debian 12, which ships PHP 8.2.

Perl · Objective-C · Visual Basic · CoffeeScript · Ruby

TPRC Talk Submission Deadline in 2 days!

Perl Foundation News

There are only 2 days left to submit a talk for TPRC! The cutoff is April 21. If you have an idea for a talk, it is definitely time to get it submitted. We need a wide variety of speakers and topics, so give it a try! Go to https://tprc.us/ to make your submission.

Updates for great CPAN modules released last week. A module is considered great if its favorites count is greater or equal than 12.

  1. App::Netdisco - An open source web-based network management tool.
    • Version: 2.098001 on 2026-04-16, with 859 votes
    • Previous CPAN version: 2.098000 was released the same day
    • Author: OLIVER
  2. Authen::Passphrase - hashed passwords/passphrases as objects
    • Version: 0.009 on 2026-04-15, with 14 votes
    • Previous CPAN version: 0.008 was released 14 years, 2 months, 11 days before
    • Author: LEONT
  3. Convert::Pheno - A module to interconvert common data models for phenotypic data
    • Version: 0.31 on 2026-04-17, with 15 votes
    • Previous CPAN version: 0.30 was released 2 days before
    • Author: MRUEDA
  4. CPANSA::DB - the CPAN Security Advisory data as a Perl data structure, mostly for CPAN::Audit
    • Version: 20260412.001 on 2026-04-12, with 25 votes
    • Previous CPAN version: 20260405.001 was released 7 days before
    • Author: BRIANDFOY
  5. Finance::Quote - Get stock and mutual fund quotes from various exchanges
    • Version: 1.69 on 2026-04-18, with 149 votes
    • Previous CPAN version: 1.68_02 was released 1 month, 5 days before
    • Author: BPSCHUCK
  6. Imager - Perl extension for Generating 24 bit Images
    • Version: 1.030 on 2026-04-13, with 68 votes
    • Previous CPAN version: 1.029 was released 6 months, 7 days before
    • Author: TONYC
  7. JSON::Schema::Modern - Validate data against a schema using a JSON Schema
    • Version: 0.638 on 2026-04-18, with 16 votes
    • Previous CPAN version: 0.637 was released 10 days before
    • Author: ETHER
  8. SPVM - The SPVM Language
    • Version: 0.990162 on 2026-04-18, with 36 votes
    • Previous CPAN version: 0.990161 was released the same day
    • Author: KIMOTO
  9. version - Structured version objects
    • Version: 0.9934 on 2026-04-12, with 22 votes
    • Previous CPAN version: 0.9933 was released 1 year, 7 months, 17 days before
    • Author: LEONT

Zsh has regexp-replace, you don’t need sed:

autoload regexp-replace regexp-replace line '^pick' 'f'

Also, instead of hard coding values, use zstyle:

# in your .zshrc zstyle ':xyz:xyz:terminal' terminal "kitty" # in your script/function local myterm zstyle -s ':xyz:xyz:terminal' terminal myterm || myterm="Eterm"

Tony writes:

``` [Hours] [Activity] 2026/03/02 Monday 1.55 #24228 follow-up comment, check updates, research and comment 0.75 #24187 review updates, mark comment resolved, research 0.97 #24242 review, research 0.40 #24242 debugging and comment

1.02 #24001 debugging, research, testing

4.69

2026/03/03 Tuesday 0.15 #24242 review dicsussion 0.10 #24211 review discussion and apply to blead 0.53 #24242 comment 0.23 #24239 review and comment 0.18 #24223 review and approve 0.40 #24244 review and comment 0.58 #24245 review and approve 0.07 #24247 review, existing comments seem fine 0.50 #24187 review more, comments 0.08 #24244 review update and approve

0.23 #24195 research

3.05

2026/03/04 Wednesday 0.88 #24252 review, research and comments 0.75 #24251 review, research and comments 0.90 #24253 review, comments 0.12 #24239 review updates and approve 0.28 #24208 comment with guide to update

0.15 #24208 review update and approve

3.08

2026/03/05 Thursday 0.68 #24254 review and comments 0.18 #24256 review and approve 0.13 #24247 check CI results and restart an apparent spurious failure 0.18 #24241 review CI failures and comment

0.40 #24228 compare to #24252 behaviour, testing

1.57

2026/03/09 Monday 0.33 #24254 review updates and approve 0.40 #24253 review updates and comment 1.38 #24252 review updates and comments, research, testing and follow-up

0.68 #24105 rebase, testing

2.79

2026/03/10 Tuesday 0.57 test 5.42.1 on fedora, looks ok, message on list indicates likely a local problem 2.70 #24105 check everything covered, various fixes, testing,

push for CI

3.27

2026/03/11 Wednesday 0.88 #24105 check CI results, fixes, push for more CI 0.57 #24187 review discussion, research and comment 0.13 #24253 review updates and approve 0.12 #24252 review updates and approve with comment 0.23 #24228 review updates and approve 0.15 #24252 approve with perldelta update 1.15 #24001 debugging (what is PL_curcopdb?)

1.20 #24001 debugging, research

4.43

2026/03/12 Thursday 1.12 #24265 review, research and comment

1.33 #24001 research, testing, needs some thought

2.45

2026/03/13 Friday

0.82 research, email to list about benchmarking

0.82

2026/03/16 Monday 0.10 #24208 review updates and apply to blead 2.47 #24272 profiling, benchmarking, comment and work on bisect 0.75 #24272 review bisect results, confirm bisect results, briefly try to work out cause, long comment with results

0.32 #24287 review and approve

3.64

2026/03/17 Tuesday 0.23 #24265 recheck and approve 0.92 #24001 re-work, research and testing 0.57 #24105 rebase and testing, minor fix and push for CI 1.13 #24272 try to diagnose

0.45 #24056 re-work commit message

3.30

2026/03/18 Wednesday 0.40 #24105 check CI results, re-check, make PR #24294 0.75 #24099 review, research and comment 0.22 #24296/#24295 research and comment (both have the same problem)

1.37 #24277 review, testing, comment

2.74

2026/03/19 Thursday 2.05 #24227 research and comments

1.22 #24272 debugging

3.27

2026/03/20 Friday

0.53 #24251 research and follow-up

0.53

2026/03/23 Monday 0.40 #24251 review updates, research and approve with comment 1.22 #24304 review, comment 0.15 #24313 review, research and apply to blead 0.10 #24310 review (nothing to say) 0.17 #24309 review, research and approve 0.13 #24305 review and approve 0.53 #24290 review 1.32 #24056 more update commit message, simplify perldelta

note, push and update OP on PR

4.02

2026/03/24 Tuesday 0.32 #24318 review and review ticket, start workflow, research and comment 0.08 #24301 review and approve 0.37 #24290 more review and comments 0.53 #24289 review, research current PSC and approve with comment 0.38 #24288 review, research and comment 0.18 #24285 review, research and approve 0.72 #24282 review, research and comment

1.23 #24290 review updates, testing and more comment

3.81

2026/03/25 Wednesday 0.15 #24056 check rules, apply to blead 0.30 #24308 review, research and comments 0.82 #24304 review, research and comment, consider Paul’s reply 1.55 #23918 string comparison APIs, research, open #24319 0.13 #24290 review updates and follow-up

1.50 #24005 start on perldebapi, research

4.45

2026/03/26 Thursday 1.25 #24326 review and comment 0.47 #24290 review updates, comment and approve with comment 0.40 #24326 review, comment on side issue and approve 0.28 #24323 review, try to find the referenced documentation, comment 0.10 #24324 review and approve

0.13 #24323 review update and approve

2.63

2026/03/30 Monday 0.80 #24308 review updates and comments 0.08 #24290 review discussion and apply to blead 1.05 #24304 review updates and comment, long comment 1.55 #23676 research, make APIs public and document, testing and push for CI 0.60 #24187 review updates

1.08 #24187 testing, comment

5.16

2026/03/31 Tuesday 1.22 #23676 comment, comment on PR regarding qerror() name, research, work on perldelta 0.47 github notifications, minor updates 0.53 #24332 review original ticket discussion and the change, approve with comment 0.23 #24329 review, research and apply to blead 0.32 #24281 review, try to get a decent view, given github’s tab mis-handling, comment 0.12 #24280 review, comments 0.35 #23995 research and comment 0.08 #24105 follow-up on PR 24294

0.50 #24251 follow-up comment

3.82

Which I calculate is 63.52 hours.

Approximately 51 tickets were reviewed or worked on, and 6 patches were applied. ```


Paul writes:

A couple of bugfixes in March, combined with starting to line up a few development ideas to open 5.45 with.

  • 2 = Bugfix for field refalias memory leak
    • https://github.com/Perl/perl5/pull/24254
  • 2 = Improved field performance
    • https://github.com/Perl/perl5/pull/24265
  • 3 = Continue progress on implementing PPC0030
    • https://github.com/Perl/perl5/pull/24304 (draft)
  • 2 = Bugfix for deferred class seal
    • https://github.com/Perl/perl5/pull/24326

Total: 9 hours

Besides working up to the 5.44 release, my main focus now will be getting things like PPC0030, magic-v2, attributes-v2, and various class feature improvements lined up ready for the 5.45 development cycle.


Dave writes:

Last month was spent looking into race conditions in threads and threads::shared. I initially started looking at a specific ticket, where (with effort) I could reproduce a specific crash by running many instances in parallel for several hours. I think I have fixed that specific bug, but that led me to the rabbit hole of dynamic thread-safety checkers such as helgrind, and I am currently plunging down the rabbit hole of issues which that tools is flagging up.

Nothing has been pushed yet.

Summary:

  • 17:06 GH #24258 dist/threads/t/free.t: Rare test failure in debugging build on FreeBSD

Total:

  • 17:06 TOTAL (HH::MM)

Let’s Make a Drum Machine application! Yeah! :D

There are basically two important things to handle: A MIDI “clock” and a groove to play.

Why asynchronous? Well, a simple while (1) { Time::HiRes::sleep($interval); ... } will not do because the time between ticks will fluctuate, often dramatically. IO::Async::Timer::Periodic is a great timer for this purpose. Its default scheduler uses system time, so intervals happen as close to the correct real-world time as possible.

Clocks

A MIDI clock tells a MIDI device about the tempo. This can be handed to a drum machine or a sequencer. Each clock tick tells the device to advance a step of a measured interval. Usually this is very short, and is often 24 pulses per quarter-note (four quarter-notes to a measure of four beats).

Here is code to do that, followed by an explanation of the parts:

#!/usr/bin/env perl

use v5.36;
use feature 'try';
use IO::Async::Loop ();
use IO::Async::Timer::Periodic ();
use MIDI::RtMidi::FFI::Device ();

my $name = shift || 'usb'; # MIDI sequencer device
my $bpm  = shift || 120; # beats per minute

my $interval = 60 / $bpm / 24; # time / bpm / clocks-per-beat

# open the named midi device for output
my $midi_out = RtMidiOut->new;
try { # this will die on Windows but is needed for Mac
    $midi_out->open_virtual_port('RtMidiOut');
}
catch ($e) {}
$midi_out->open_port_by_name(qr/\Q$name/i);

$midi_out->start; # start the sequencer

$SIG{INT} = sub { # halt gracefully
    say "\nStop";
    try {
        $midi_out->stop; # stop the sequencer
        $midi_out->panic; # make sure all notes are off
    }
    catch ($e) {
        warn "Can't halt the MIDI out device: $e\n";
    }
    exit;
};

my $loop = IO::Async::Loop->new;

my $timer = IO::Async::Timer::Periodic->new(
   interval => $interval,
   on_tick  => sub { $midi_out->clock }, # send a clock tick!
);
$timer->start;

$loop->add($timer);
$loop->run;

The above code does a few things. First it uses modern Perl, then the modules that will make execution asynchronous, and finally the module that makes real-time MIDI possible.

Next up, a $name variable is captured for a unique MIDI device. (And to see what the names of MIDI devices on the system are, use JBARRETT’s little list_devices script.) Also, the beats per minute is taken from the command-line. If neither is given, usb is used for the name, and the BPM is set to “dance tempo.”

The clock needs a time interval to tick off. For us, this is a fraction of a second based on the beats per minute, and is assigned to the $interval variable.

To get the job done, we will need to open the named MIDI device for sending output messages to. This is done with the $name provided.

In order to not just die when we want to stop, $SIG{INT} is redefined to gracefully halt. This also sends a stop message to the open MIDI device. This stops the sequencer from playing.

Now for the meat and potatoes: The asynchronous loop and periodic timer. These tell the program to do its thing, in a non-blocking and event-driven manner. The periodic timer ticks off a clock message every $interval. Pretty simple!

As an example, here is the above code controlling my Volca Drum drum machine on a stock, funky groove. We invoke it on the command-line like this:

perl clock-gen-async.pl

Grooves

What we really want is to make our drum machine actually play something of our own making. So it’s refactor time… Let’s make a 4/4 time groove, with 16th-note resolution, that alternates between two different parts. “4/4” is a “time signature” in music jargon and means that there are four beats per measure (numerator), and a quarter note equals one beat (denominator). Other time signatures like the waltz’s 3/4 are simple, while odd meters like 7/8 are not.

In order to generate syncopated patterns, Math::Prime::XS and Music::CreatingRhythms are added to the use statements. “What are syncopated patterns?”, you may ask. Good question! “Syncopated” means, “characterized by displaced beats.” That is, every beat does not happen evenly, at exactly the same time. Instead, some are displaced. For example, a repeated [1 1 1 1] is even and boring. But when it becomes a repeated [1 1 0 1] things get spicier and more syncopated.

The desired MIDI channel is added to the command-line inputs. Most commonly, this will be channel 9 (in zero-based numbering). But some drum machines and sequencers are “multi-timbral” and use multiple channels simultaneously for individual sounds.

Next we define the drums to use. This is a hash-reference that includes the MIDI patch number, the channel it’s on, and the pattern to play. The combined patterns of all the drums, when played together at tempo, make a groove.

Now we compute intervals and friends. Previously, there was one $interval. Now there are a whole host of measurements to make before sending MIDI messages.

Then, as before, a named MIDI output device is opened, and a graceful stop is defined.

Next, a Music::CreatingRhythms object is created. And then, again as before, an asynchronous loop and periodic timer are instantiated and set in motion.

The meaty bits are in the timer’s on_tick callback. This contains all the logic needed to trigger our drum grooves.

As was done in the previous clock code, a clock message is sent, but also we keep track of the number of clock ticks that have passed. This number of ticks is used to trigger the drums. We care about 16 beats. So every 16th beat, we construct and play a queue of events.

Adjusting the drum patterns is where Math::Prime::XS and Music::CreatingRhythms come into play. The subroutine that does that is adjust_drums() and is fired every 4th measure. A measure is equal to four quarter-notes, and we use four pulses for each, to make 16 beats per measure. This routine reassigns either Euclidean or manual patterns of 16 beats to each drum pattern.

Managing the queue is next. If a drum is to be played at the current beat (as tallied by the $beat_count variable), it is added to the queue at full velocity (127). Then, after all the drums have been accounted for, the queue is played with $midi_out->note_on() messages. Lastly, the queue is “drained” by sending $midi_out->note_off() messages.

#!/usr/bin/env perl

use v5.36;
use feature 'try';
use IO::Async::Loop ();
use IO::Async::Timer::Periodic ();
use Math::Prime::XS qw(primes);
use MIDI::RtMidi::FFI::Device ();
use Music::CreatingRhythms ();

my $name = shift || 'usb'; # MIDI sequencer device
my $bpm  = shift || 120; # beats-per-minute
my $chan = shift // 9; # 0-15, 9=percussion, -1=multi-timbral

my $drums = {
    kick  => { num => 36, chan => $chan < 0 ? 0 : $chan, pat => [] },
    snare => { num => 38, chan => $chan < 0 ? 1 : $chan, pat => [] },
    hihat => { num => 42, chan => $chan < 0 ? 2 : $chan, pat => [] },
};

my $beats = 16; # beats in a measure
my $divisions = 4; # divisions of a quarter-note into 16ths
my $clocks_per_beat = 24; # PPQN
my $clock_interval = 60 / $bpm / $clocks_per_beat; # time / bpm / ppqn
my $sixteenth = $clocks_per_beat / $divisions; # clocks per 16th-note
my %primes = ( # for computing the pattern
    all  => [ primes($beats) ],
    to_5 => [ primes(5) ],
    to_7 => [ primes(7) ],
);
my $ticks = 0; # clock ticks
my $beat_count = 0; # how many beats?
my $toggle = 0; # part A or B?
my @queue; # priority queue for note_on/off messages

# open the named midi output device
my $midi_out = RtMidiOut->new;
try { # this will die on Windows but is needed for Mac
    $midi_out->open_virtual_port('RtMidiOut');
}
catch ($e) {}
$midi_out->open_port_by_name(qr/\Q$name/i);

$SIG{INT} = sub { # halt gracefully
    say "\nStop";
    try {
        $midi_out->stop; # stop the sequencer
        $midi_out->panic; # make sure all notes are off
    }
    catch ($e) {
        warn "Can't halt the MIDI out device: $e\n";
    }
    exit;
};

# for computing the pattern
my $mcr = Music::CreatingRhythms->new;

my $loop = IO::Async::Loop->new;

my $timer = IO::Async::Timer::Periodic->new(
    interval => $clock_interval,
    on_tick  => sub {
        $midi_out->clock;
        $ticks++;
        if ($ticks % $sixteenth == 0) {
            # adjust the drum pattern every 4th measure
            if ($beat_count % ($beats * $divisions) == 0) {
                adjust_drums($mcr, $drums, \%primes, \$toggle);
            }
            # add simultaneous drums to the queue
            for my $drum (keys %$drums) {
                if ($drums->{$drum}{pat}[ $beat_count % $beats ]) {
                    push @queue, { drum => $drum, velocity => 127 };
                }
            }
            # play the queue
            for my $drum (@queue) {
                $midi_out->note_on(
                    $drums->{ $drum->{drum} }{chan},
                    $drums->{ $drum->{drum} }{num},
                    $drum->{velocity}
                );
            }
            $beat_count++;
        }
        else {
            # drain the queue with note_off messages
            while (my $drum = pop @queue) {
                $midi_out->note_off(
                    $drums->{ $drum->{drum} }{chan},
                    $drums->{ $drum->{drum} }{num},
                    0
                );
            }
            @queue = (); # ensure the queue is empty
        }
    },
);
$timer->start;

$loop->add($timer);
$loop->run;

sub adjust_drums($mcr, $drums, $primes, $toggle) {
    # choose random primes to use by the hihat, kick, and snare
    my ($p, $q, $r) = map { $primes->{$_}[ int rand $primes->{$_}->@* ] } sort keys %$primes;
    if ($$toggle == 0) {
        say 'part A';
        $drums->{hihat}{pat} = $mcr->euclid($p, $beats);
        $drums->{kick}{pat}  = $mcr->euclid($q, $beats);
        $drums->{snare}{pat} = $mcr->rotate_n($r, $mcr->euclid(2, $beats));
        $$toggle = 1; # set to part B
    }
    else {
        say 'part B';
        $drums->{hihat}{pat} = $mcr->euclid($p, $beats);
        $drums->{kick}{pat}  = [qw(1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1)];
        $drums->{snare}{pat} = [qw(0 0 0 0 1 0 0 0 0 0 0 0 1 0 1 0)];
        $$toggle = 0; # set to part A
    }
}

(You may notice the inefficiency of attempting to drain an empty queue 23 times every 16th note. Oof! Fortunately, this doesn’t fire anything other than a single while loop condition. A more efficient solution would be to only drain the queue once, but this requires a bit more complexity that we won’t be adding, for brevity’s sake.)

On Windows, this works fine:

perl clocked-euclidean-drums.pl "gs wavetable" 90

To run with fluidsynth and hear the General MIDI percussion sounds, open a fresh new terminal session, and start up fluidsynth like so (mac syntax):

fluidsynth -a coreaudio -m coremidi -g 2.0 ~/Music/soundfont/FluidR3_GM.sf2

The FluidR3_GM.sf2 is a MIDI “soundfont” file and can be downloaded for free.

Next, enter this on the command-line (back in the previous terminal session):

perl clocked-euclidean-drums.pl fluid 90

You will hear standard kick, snare, and closed hihat cymbal. And here is a poor recording of this with my phone:

To run the code with my multi-timbral drum machine, I enter this on the command-line:

perl clocked-euclidean-drums.pl usb 90 -1

And here is what that sounds like:

The Module

I have coded this logic, and a bit more, into a friendly CPAN module. Check out the eg/euclidean.pl example program in the distribution. It is a work in progress. YMMV.

Credits

Thank you to Andrew Rodland (hobbs), who helped me wrap my head around the “no-sleeping asynchronous” algorithm.

To-do Challenges

  • Make patterns other than prime number based Euclidean phrases.

  • Toggle more than two groove parts.

  • Add snare fills to the (end of the) 4th bars. (here’s my version)

  • Make this code handle odd meter grooves.

Resources

Every month, I write a newsletter which (among other things) discusses some of the technical projects I’ve been working on. It’s a useful exercise — partly as a record for other people, but mostly as a way for me to remember what I’ve actually done.

Because, as I’m sure you’ve noticed, it’s very easy to forget.

So this month, I decided to automate it.

(And, if you’re interested in the end result, this is also a good excuse to mention that the newsletter exists. Two birds, one stone.)


The Problem

All of my Git repositories live somewhere under /home/dave/git. Over time, that’s become… less organised than it might be. Some repos are directly under that directory, others are buried a couple of levels down, and I’m fairly sure there are a few I’ve completely forgotten about.

What I wanted was:

  • Given a month and a year
  • Find all Git repositories under that directory
  • Identify which ones had commits in that month
  • Summarise the work done in each repo

The first three are straightforward enough. The last one is where things get interesting.


Finding the Repositories

The first step is walking the directory tree and finding .git directories. This is a classic Perl task — File::Find still does exactly what you need.

use v5.40;
use File::Find;

sub find_repos ($root) {
  my @repos;

  find(
    sub {
      return unless $_ eq '.git';
      push @repos, $File::Find::dir;
    },
    $root
  );

  return @repos;
}

This gives us a list of repository directories to inspect. It’s simple, robust, and doesn’t require any external dependencies.

(There are, of course, other ways to do this — you could shell out to fd or find, for example — but keeping it in Perl keeps everything nicely self-contained.)


Getting Commits for a Month

For each repo, we can run git log with appropriate date filters.

sub commits_for_month ($repo, $since, $until) {
  my $cmd = sprintf(
    q{git -C %s log --since="%s" --until="%s" --pretty=format:"%%s"},
    $repo, $since, $until
  );

  my @commits = `$cmd`;
  chomp @commits;

  return @commits;
}

Where $since and $until define the month we’re interested in. I’ve been using something like:

my $since = "$year-$month-01";
my $until = "$year-$month-31"; # good enough for this purpose

Yes, that’s a bit hand-wavy around month lengths. No, it doesn’t matter in practice. Sometimes ā€œgood enoughā€ really is good enough.


A Small Gotcha

It turns out I have a few repositories where I never got around to making a first commit. In that case, git log helpfully explodes with:

fatal: your current branch ‘master’ does not have any commits yet
Which is fair enough — but not helpful in a script that’s supposed to quietly churn through dozens of repositories.

The fix is simply to ignore failures:

my @commits = `$cmd 2>/dev/null`;

If there are no commits, we just get an empty list and move on. No warnings, no noise.

This is one of those little bits of defensive programming that makes the difference between a script you run once and a script you’re happy to run every month.


Summarising the Work

Once we have a list of commit messages, we can summarise them.

And this is where I cheated slightly.

I used OpenAPI::Client::OpenAI to feed the commit messages into an LLM and ask it to produce a short summary.

Something along these lines:

use OpenAPI::Client::OpenAI;

sub summarise_commits ($commits) {
  my $client = OpenAPI::Client::OpenAI->new(
    api_key => $ENV{OPENAI_API_KEY},
  );

  my $text = join "\n", @$commits;

  my $response = $client->chat->completions->create({
    model => 'gpt-4.1-mini',
    messages => [{
      role => 'user',
      content => "Summarise the following commit messages:\n\n$text",
    }],
  });

  return $response->choices->[0]->message->content;
}

Is this overkill? Almost certainly.

Could I have written some heuristics to group and summarise commit messages? Possibly.

Would it have been as much fun? Definitely not.

And in practice, it works remarkably well. Even messy, inconsistent commit messages tend to turn into something that looks like a coherent summary of work.


Putting It Together

For each repo:

  1. Get commits for the month
  2. Skip if there are none
  3. Generate a summary
  4. Print the repo name and summary

The output looks something like:

my-project
-----------
Refactored database layer, added caching, and fixed several edge-case bugs.

another-project
---------------
Initial scaffolding, basic API endpoints, and deployment configuration.

Which is already a pretty good starting point for a newsletter.


A Nice Side Effect

One unexpected benefit of this approach is that it surfaces projects I’d forgotten about.

Because the script walks the entire directory tree, it finds everything — including half-finished experiments, abandoned ideas, and repos I created at 11pm and never touched again.

Sometimes that’s useful. Sometimes it’s mildly embarrassing.

But it’s always interesting.


What Next?

This is very much a first draft.

It works, but it’s currently a script glued together with shell commands and assumptions about my directory structure. The obvious next step is to:

  • Turn it into a proper module
  • Add tests
  • Clean up the API
  • Release it to CPAN

At that point, it becomes something other people might actually want to use — not just a personal tool with hard-coded paths and questionable date handling.


A Future Enhancement

One idea I particularly like is to run this automatically using GitHub Actions.

For example:

  • Run monthly
  • Generate summaries for that month
  • Commit the results to a repository
  • Publish them via GitHub Pages

Over time, that would build up a permanent, browsable record of what I’ve been working on.

It’s a nice combination of:

  • automation
  • documentation
  • and a gentle nudge towards accountability

Which is either a fascinating historical archive…

…or a slightly alarming reminder of how many half-finished projects I have.


Closing Thoughts

This started as a small piece of automation to help me write a newsletter. But it’s turned into a nice example of what Perl is still very good at:

  • Gluing systems together
  • Wrapping command-line tools
  • Handling messy real-world data
  • Adding just enough intelligence to make the output useful

And, occasionally, outsourcing the hard thinking to a machine.

The code (such as it is currently is) is on GitHub at https://github.com/davorg/git-month-summary.

If you’re interested in the kind of projects this helps summarise, you can find my monthly newsletter over on Substack.

And if I get round to turning this into a CPAN module, I’ll let you know – well, if you’re subscribed to the newsletter!

The post Summarising a Month of Git Activity with Perl (and a Little Help from AI) first appeared on Perl Hacks.

Updates for great CPAN modules released last week. A module is considered great if its favorites count is greater or equal than 12.

  1. App::DBBrowser - Browse SQLite/MySQL/PostgreSQL databases and their tables interactively.
    • Version: 2.440 on 2026-04-11, with 18 votes
    • Previous CPAN version: 2.439 was released 1 month, 16 days before
    • Author: KUERBIS
  2. Attean - A Semantic Web Framework
    • Version: 0.036 on 2026-04-06, with 19 votes
    • Previous CPAN version: 0.035_01 was released the same day
    • Author: GWILLIAMS
  3. Bio::EnsEMBL - Bio::EnsEMBL - Ensembl Core API
    • Version: 114.0.0 on 2026-04-07, with 83 votes
    • Previous CPAN version: 114.0.0_50 was released 12 days before
    • Author: TAMARAEN
  4. CPANSA::DB - the CPAN Security Advisory data as a Perl data structure, mostly for CPAN::Audit
    • Version: 20260405.001 on 2026-04-05, with 25 votes
    • Previous CPAN version: 20260329.001 was released 6 days before
    • Author: BRIANDFOY
  5. Exporter - Implements default import method for modules
    • Version: 5.79 on 2026-04-06, with 28 votes
    • Previous CPAN version: 5.78 was released 2 years, 3 months, 6 days before
    • Author: TODDR
  6. Image::ExifTool - Read and write meta information
    • Version: 13.55 on 2026-04-07, with 44 votes
    • Previous CPAN version: 13.50 was released 2 months before
    • Author: EXIFTOOL
  7. JSON::Schema::Modern - Validate data against a schema using a JSON Schema
    • Version: 0.637 on 2026-04-08, with 16 votes
    • Previous CPAN version: 0.636 was released the same day
    • Author: ETHER
  8. Mail::Box - complete E-mail handling suite
    • Version: 4.02 on 2026-04-10, with 16 votes
    • Previous CPAN version: 4.01 was released 3 months, 28 days before
    • Author: MARKOV
  9. PDL - Perl Data Language
    • Version: 2.104 on 2026-04-08, with 102 votes
    • Previous CPAN version: 2.103 was released 1 month, 5 days before
    • Author: ETJ
  10. Pod::Simple - framework for parsing Pod
    • Version: 3.48 on 2026-04-05, with 20 votes
    • Previous CPAN version: 3.48 was released the same day
    • Author: KHW
  11. SPVM - The SPVM Language
    • Version: 0.990156 on 2026-04-08, with 36 votes
    • Previous CPAN version: 0.990155 was released 1 day before
    • Author: KIMOTO
  12. Term::Choose - Choose items from a list interactively.
    • Version: 1.782 on 2026-04-09, with 15 votes
    • Previous CPAN version: 1.781 was released 15 days before
    • Author: KUERBIS
  13. Test2::Harness - A new and improved test harness with better Test2 integration.
    • Version: 1.000170 on 2026-04-10, with 28 votes
    • Previous CPAN version: 1.000169 was released 1 day before
    • Author: EXODIST