Shortcuts: s show h hide n next p prev

Who tests the tester? Me !!!

blogs.perl.org

As already reported, I'm writing this color library. Recently I created my own test function for it. And since it was easier that I thought, I want to show you how, so you can write your own!

The Task

If you look into the GTC internals, you see instantly that the most common none OO data structure is a 'tuple' with the values of one color in one color space (usually three values). Since we do not have a native tuple type in Perl - it is an ordinary Array, one could also say a value vector. In the GTC test suite they get checked very often. Each time I have to ask:

  1. Did I get an Array ?
  2. Does it have the right amount of values ?
  3. Check every value of the tuple for equality.

These are usually 5 lines that could and should have been one line. In practice this means 200 rows of test code will shrink to 40 - neat. Less to type, less to read - wonderful. And the assertions will be more to the point telling WHAT I want to test and WHY not just two values match, which is also good for readability and clarity on the other end. If run the tests, the ok - messages will me just tell me what is actually tested and not that two values matched. But the most important improvement comes to light when something goes wrong. Let's say the function we test collapses and returns undef. The first test will tell us that we got no ARRAY, good, but the next who checks the lengths of the array will crash in a hard syntax error. You have to fix the causing bug in order to see the next test results because your test suite crashed. Wouldn't it be so much nicer the test suite could run to the end and the error messages tell you about all the bugs at once, so you could theoretically hunt them in one go.

You see: less code, clearer code, better error message, only relevant error messages and no hard crashes. These are more than enough motivating reasons to write custom error functions, so let's do it, let's write a variant of

is( $got, $expected, $test_name );

(which is a test function you all used at some point) and name it:

is_tuple( $got, $expected,  $axis, $test_name );

You notice I needed one more argument for the axis names. So I can get the nice error message: "the red value was 13 but I expected 15".

The Solution

The Module you need to create that is Test::Builder. So the smart ones among you have guessed you need to:

use Test::Builder;
my $tb = Test::Builder->new;

You can also subclass Test::Builder but this works as well, since ->new will give you the only instance of $tb anyway. And to start our test function we need just:

sub is_tuple {
my ($got, $expected, $axis, $name) = @_;
my $pass = 0;
my $diag = '';

Well if you are reading this blog, you know about Perl argument handling. $pass is the pseudo boolean than holds the information if the test was successful, we pass it on to the Test::Builder method at the end. Same is true for $diag, which is the error message we maybe have to drop. This is not the way you have to do it. Calling the diag method several times is also an option, but i prefer to have short error messages that fits in one line and only tells me what exactly went wrong. Let's skip the testing logic, since a bunch of nested if statements with some basic value checks isn't that impressive and educational. I just like a clean separation between out test logic and the part where I talk to Test::Builder. That is why I declared the variables at the start, fill them when i need to, so I only cal the following once at the end of the subroutine:

$tb->diag( $diag ) unless $pass;
$tb->ok( $pass, $name );

Yes that is all. Do not forget to:

require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(is_tuple);

But this was really it. It didn't even hurt and we didn't have to see Detroit.

The Tests

More challenging I found it to write the test file that checks the logic code I didn't show in the above example.

use v5......;
use warnings;
use lib '.',...;
use Test::More tests => ...;
use Test::Builder::Tester;
use Test::Color;

This is mostly your usual start template of any test file. The lib pragma needs to receive the directory where the module lives, that contains is_tuple, which would be in my case Test::Color. And we need Test::Builder::Tester to test what we built with Test::Builder (the module names fit).

test_out("ok 1 - is_tuple runs");
is_tuple([1,1,1], [1,1,1], [qw/r g b/], 'is_tuple runs');
test_test("simplest is_tuple case runs");

Our first little smoke test seems trivial. We call is_tuple with the the result values and the values to check them against ($expected), then the axis names and at last the test name (the name of the test istuple performs). But BEFORE that you HAVE TO tell Test::Builder::Tester what output to expect from the test function (istuple) on STDOUT. The last line tells Test::Builder::Tester the name of the test we did by testing the test function. That HAS to come AFTER calling is_tuple.

Now we are ready for the juicy bit. How to test a failing test? I mean by that: is_tuple will fail because we gave it bad data by purpose. And if is_tuple tries to give the right error message to STDOUT, Test::Builder::Tester should intervene and call it a successful test. The code to do this is:

test_out("not ok 1 - C");
test_err("# failed test: C - got values that are not a tuple (ARRAY ref)");
test_fail(+1);
is_tuple(1, [1,1,1], [qw/r g b/], 'C');
test_test("is_tuple checks if got values in an ARRAY");

First we tell via test_out again what STDOUT suppose to receive. Then we tell via test_err what error message should land in STDERR (standard error output). And when a test fails Test::Builder will create an additional error message telling where it happened. In order to not have to chase line numbers we got the convenience function: test_fail(+1); You can translate it to: "Hej Test::Builder::Tester, the next line (+1) will cause an error, this is fine, please do not create this additional error with the line number". What actually happens is, this call gets forwarded to a test_err call with the appropriate string. Then we finally call the test function we want to test and at the very end again - the name of this (meta) test.

One last useful hack. You noticed I called the test that is_tuple does in the last example just uppercase C. This is not a nice and telling name for any test and brutally counterproductive - However - since we testing the test and the name of the inner test is part of the STDOUT and STDERR check string in the outer test, it is nice to trace easily what is what and what comes from where and this is also rather educational for this demo. What this (outer) test suppose to do is documented anyway by the final meta-test name.

The Perl Documentation - Rewritten

blogs.perl.org

Well, not all of it ... yet. But some of it has been rewritten many times in many languages and "all" of it will be rewritten in many more languages. Of course "all" will never be reached, so it will be an ongoing endeavor, but at least you know the goalposts.

You can read it at https://perl.petamem.com/docs/eng/, and the language picker in the upper right hand corner will tell you, more honestly than any sentence in this post can, where the public-facing part of the work currently stands.

What you will find there

A reworked documentation set for Perl - shipping with pperl, but addressed to the wider Perl audience. It covers the things you would expect (introduction, getting-started, CLI, how-tos, P5 and PP runtime references) and is rendered through a tailored Sphinx pipeline so the navigation, search, and theming behave the way modern documentation sites are expected to behave.

The picker currently shows four languages in colour: English, German, French, Spanish. All four are work in progress. English is the source - the canonical version every other language is translated from - but it is itself being rewritten and reorganised, chapter by chapter, and is not in any sense "done." The other three languages are translations that follow the moving English target. The remaining flags in the picker are greyed: planned coverage, in scripts spanning Latin, Cyrillic, Greek, Hebrew, and Arabic. Languages will move from grey to colour as their translations land. We are not committing to a date on any of them, and we are not committing to the final list either. The picker is a roadmap of work we are confident we can finish, not a wish list.

Readers who followed Rust-PDL Part Two a few days ago will find the corresponding documentation at https://perl.petamem.com/docs/eng/p5/PDL.html. The pattern, in case it is not obvious from the two posts read together: code first, documentation as soon afterwards as we can manage. Documentation that lags the code is documentation nobody trusts.

Two axes of work

There are two distinct kinds of work happening here, and they do not always speak to the same reader.

The first is content. A lot of what ships in the canonical Perl documentation has been with us for a long time, was written under different conventions of technical writing, and shows it. Some of it is excellent. Some of it presumes a reader who has already learned Perl from somewhere else and just needs the reference. The work we are doing on this axis is not a polite refresh of existing prose. It is, in places, a structural rewrite - new organisation, new examples, new emphasis on the things modern Perl users actually run into. The goal is documentation that a Perl programmer in 2026 can read without having to imaginatively reconstruct the assumptions of a Perl programmer in 1998.

The second is translation. Perl documentation has historically been an English artefact. There are pockets of community translation work, scattered and varying in completeness, but no comprehensive multi-language documentation set in the modern sense. We are building one. The motivation is simple: a developer whose first language is not English currently has, for many languages, exactly zero Perl documentation in their language. The bar to clear, for that developer, is not perfection. It is "better than nothing." That is one piece of a larger picture I do not propose to lay out here. The picker shows the ambition; the four currently active languages (English plus three) show the present.

A case study: the debugging chapter

The most concrete way to show what "rewritten" means is to point at one chapter. The debugging guide at https://perl.petamem.com/docs/eng/guide/debugging/index.html is structured around what a developer actually does when something is going wrong:

  • Preflight (what to check before reaching for a debugger)
  • Print and die (the simplest tools, used well)
  • Inspecting state
  • The interactive debugger
  • Breakpoints
  • Tracing
  • Exceptions
  • IDE / DAP integration

This is a different shape from the upstream perldebug document, in two ways. The first is structural: each of the eight topics is its own URL, addressable from a search result, rather than a section in one long narrative. The second is depth-layered: each topic exists at three reading depths - an overview that orients, a reference that defines precisely, and a "gory details" layer for the reader who has met a corner case and needs to understand it. This is the shape every guide chapter is moving toward. It serves the reader who arrives with a specific question and the reader who wants to understand the topic seriously, from the same source.

How was this chapter produced? The honest description is: we used an AI as a research assistant, with access to the same kinds of sources a human technical writer would consult - perldoc itself, CPAN module documentation, conference talks, blog posts, mailing-list archives, the source code of the Perl debugger, and one book used with the explicit permission of its author, Richard Foley - and ran several distillation passes over its output. De-obsoleting (removing references to versions and tools nobody runs anymore). De-duplicating (the same advice repeated by different authors collapses into one statement). Synthesising (multiple partial treatments of one topic become a single complete treatment). The methodology is not exotic, but it is genuinely different from "writer sits down and writes a chapter," and it produces results of a kind that "writer sits down" cannot easily reach - because no human writer has read everything that has been written about Perl debugging, and the cost of doing so would be prohibitive.

A note on the obvious question: this is the same activity any technical author performs. Reading existing material on a topic to inform new writing is how technical writing has always worked. Copyright protects expression, not facts; the output is a new piece of writing in a new structure, not a derivative of any one source. The AI changes the scale and speed of what one author can read and synthesise. It does not change the underlying activity. We have been deliberate about this. Human review is not a checkpoint at the end of the process; it accompanies the process, which is iterative and ongoing.

One sequencing detail worth naming. The rewriting described above is the first stage. The translations come after - applied to the rewritten English, not to the upstream original. And translation follows a different discipline from rewriting: faithful, not inventive. We grant ourselves latitude when restructuring an English chapter against thirty-year-old upstream prose. We grant ourselves no such latitude when carrying that chapter into French or German or Spanish - obviously. Compounding deviation across two transformations would defeat the point of doing either one carefully.

A case study: the build pipeline

The translation work is one branch of a larger pipeline. Before any translation happens, the English source is assembled out of multiple inputs: hand-authored Markdown, per-function reference files (perlfunc has one Markdown file per built-in, with YAML frontmatter for category tags, signature, and prose body), runtime metadata derived from the pperl source tree, CLI help auto-extracted from the binary's option parsing, and a lint pass that catches the kinds of things linters catch in code: dead links, malformed cross-references, frontmatter schema drift. All of this lands in a staging tree that Sphinx then renders. The source tree is never written to during a build; the staging tree is the contract between the assembly stage and the rendering stage.

Translation hangs off this staging tree. Each target language has its own PO catalogue, identified by ISO 639-3 code (fra, deu, spa, and so on - the three-letter codes are deliberate, because the two-letter ISO 639-1 set runs out of distinctions for several of the languages on our roadmap). The translation passes are run by per-language translator agents that are seeded with terminology consistency rules and a fixed register for technical prose. PO catalogues are merged forward when the English source changes, so a sentence revised on the English side surfaces as a fuzzy entry in every language catalogue and is re-translated rather than silently going stale.

The pipeline handles right-to-left scripts (Arabic, Hebrew) and non-Latin scripts (Cyrillic, Greek) without per-language special casing - the layout is handled at the theme level, which means a new language is, infrastructurally, a new PO catalogue and not a build-system rewrite. That separation is what makes the long roadmap tractable.

Each language renders to two outputs: the HTML site that the picker navigates, and a single large PDF of the whole documentation set. The PDF is not a polite afterthought. There are readers and use cases - offline reading, print, archival, regulated environments - where a self-contained PDF is the format that actually gets used.

Each HTML page also offers a "Source (accessible & AI friendly)" link that returns the page's underlying source rather than its rendered HTML. This is partly conventional - source links are good engineering practice for any documentation site - but it is also a deliberate accommodation for a class of reader we expect to grow: AIs, agents, and other automated consumers of documentation. They do not need the navigation chrome, the syntax-highlighting CSS, or the responsive layout. They need the text, structured. The source links give them that.

Some numbers to give a sense of the translation volume. The perlfunc reference in French alone takes roughly 1.9 million tokens of translation work, split across eight chunks running in parallel, around 22 to 23 minutes per chunk. That is one section, in one language. Multiply by the number of sections, multiply again by the number of languages on the roadmap, and the order of magnitude becomes the point: this is work that historically did not happen in the Perl world because nobody had the resources to do it. We do, now, and we are doing it.

A note on the difficulty

It would be easy to read the volume figures and conclude that with a sufficiently large language model and enough patience, multi-language technical documentation is now a solved problem. It is not. The honest description of the work is that it is brutally hard.

Terminology has to stay consistent across thousands of strings within one language and across all languages. The idiomatic register has to match the original's tone, which itself varies by section. Technical precision has to survive translation, which is harder than it sounds when the source language is English and the target language has different defaults for, say, the number of words a sentence can carry before it becomes grammatically ambiguous. Code examples raise their own questions - do you translate variable names? Function names? Error message strings the reader will see in their own terminal? (We do not, mostly, but the question recurs at every chapter.) Right-to-left layouts produce edge cases that no test catches until a human reads the rendered page. AI is the reason this work is feasible at all. But - you know - AI+Perl makes hard things... possible.

Heads up

This is a "release early" post. The English documentation is in a state where you can use some of it. The three active translations are in a state where they are useful for the chapters that have landed and unfinished for the chapters that have not. The greyed flags in the picker are work we intend to do; we are not promising any specific date.

If you read Perl in English and prefer documentation that has been organised around modern reading habits, the site is worth a look today. If you read Perl better in another language, the picker shows whether your language is live yet, and the answer for most is "not quite, but coming." If you find errors, things that should be re-organised, or sections that read awkwardly, no surprise there - it is an early shape.

  • Richard C. Jelinek, PetaMem s.r.o.
replace placeholder date with planned date in CoreList.pm

Originally published at Perl Weekly 770

Hi there,

I like to believe that I belong to the old school of testing i.e. Test::More. That being said, every now and then I come across a magical test workflow. One of them is App::Yath. The biggest and pleasant surprise is that both were presented by none other than Chad Granum. Although I love the idea, I find it hard to adapt to it. I have to force myself otherwise it won't happen. I wrote a short blog post as a reminder to myself while working on this editorial. I can refer to it next time I am changing existing code or adding new code.

Speaking of testing, Herbert Breunung shared a blog post explaining how Test::Builder helped him create his own test suite. This reminded me how I did exactly that in 2010 to create Test::Map::Tube for my routing framework, Map::Tube. It makes me feel quite old now!

Enjoy rest of the newsletter.

--
Your editor: Mohammad Sajid Anwar.

Announcements

The London Perl and Raku Workshop 2026

Just a heads up: The next LPW is planned to be in November 2026.

PPC Summer 2026 - Call for Participation!

Brett Estrade, who is a member of the Perl Community Conference (PPC) organising committee, has released a call for participants for the Summer 2026 PPC. He notes the group's success in the areas of scientific research and community building through the use of independent Perl conferences and asks for speakers to submit proposals to present their work via the Papercall system to help keep the momentum going.

Articles

Who tests the tester? Me !!!

In the article, Lichtkind shows how to apply the Test::Builder::Tester module to check your own test functions for correctness. He shares real-life example of using the is_tuple() function that he has developed to aid in creating a test for the GTC project.

This week in PSC (222) | 2026-04-25

Ihe Perl Steering Council got together at the 2026 Perl Toolchain Summit (Vienna). During this meeting, the group discussed the current status of Perl 5.44. Most blockers have been resolved and EOL workflow has been put in place. Finalisation of details for the next stable release is currently taking place.

Importance of Repositories in Public

In this blog post, Mikko argues that keeping project repositories publicly accessible is an important practice even when no new contributions are anticipated. Having a project public on GitHub preserves historical context, serves as an example of quality work for others, and provides a source of useful data for the open-source community.

AI as a Chance - Opinion

In this article PetaMem discussed how through AI we will have access to new ways to enhance our creative processes (as opposed to replacing them), through developing a partnership with AI; allowing developers and creators alike use the technology of AI to automate repetitive processes and create opportunities for problems to be solved and new ideas developed through creative innovation.

Discussion

AI Contributions to CPAN: The Copyright Question

In his article, Todd explores some of the legal issues surrounding code that is generated by AI and available through CPAN, and whether or not these AI-generated contributions are eligible for copyright protection and/or a legal license. He also cautions maintainers about the use of black box LLMs, which do not make public their training data, and encourages the Perl community to develop guidelines for the proper protection of the Perl software ecosystem from future disputes arising from improper use of code generated by LLMs.

Web

Use Your Powers Only for Good, Clark

Dave dissects a really good looking SPAM email made by an AI LLM to show us all how the world will look in future with AI created outreach. Even though the spam had accurately scraped Dave's background and book titles, Dave believes that this creates the illusion of understanding but does not demonstrate the LLM has any real knowledge of either Dave's values or business model. Dave further states that this type of outreach is now cheaper and easier than ever though the deployment of AI to create large amounts of unsolicited SPAM.

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 - 371

Welcome to a new week with a couple of fun tasks "Missing Letter" and "Subset Equilibrium". 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 - 370

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

Perl Weekly Challenge 370: Popular Word

Abigail offers a superb example of processing text for the "Popular Word" task using the ability of Perl's regex engine to process words without considering case and complex punctuation marks. This solution is unique because it demonstrates a high-performance solution for filtering out banned words while maintaining concise and easily readable code.

Perl Weekly Challenge 370: Scramble String

Abigail used a clever recursive method to take on the challenge of the "Scramble String" problem. The use of clear base cases and logic to explore possible points to split and swap all contribute to the final solution being an excellent example of how to solve complex problems with string transformations, using a valid solution and providing a framework that can be used to solve other complex problems involving string transformations.

Popular Scramble

Arne's experience in comparing Raku's expressive power vs. Perl with respect to the solutions of these two challenges are at the heart of this review. He also demonstrates how to use Raku's built-in Bag and Any types to simplify complex logic with clean, idiomatic code. This article is a great reference for developers who want to learn how to use modern functional programming patterns effectively.

PWC 370 Scramble On, Scramblin' Man

Bob has created a detailed breakdown of the Scramble String problem, looking at the need to use memoisation to avoid redundantly calculating recursive algorithms. Bob has developed a very clearly constructed solution to the Scramble String problem using test-first development which is a perfect demonstration of how to keep performance and readability on complex branching logic.

Perl Weekly Challenge: Week 370

In this article, there is a thorough comparison made between two languages: Perl and Raku. The many modern capabilities of Raku, such as having a Bag data structure and being natively supportive of methods for manipulating strings, resulted in solving the stated problem while producing significantly less code than what is generated by using Perl to do the same thing.

Scrambled Bans

Jƶrg has concentrated his efforts solely on providing a high quality Perl solution to this challenge. The way he solves "Scramble String" is especially impressive because he uses a recursive solution that processes challenging partitioning and swapping of strings, using clean and idiomatic Perl programming.

Perl Weekly Challenge 370

W. Luis MochƔn offers a mathematically rigorous and elegant take on Challenge 370 using Perl. For Task 1, he utilises a compact approach by normalising the input with lc and regex, then applying a frequency count. His review is particularly positive about the clarity and efficiency of using a single hash to filter out banned words while identifying the maximum frequency in one pass. For Task 2, he implements a recursive solution and highlights the technical necessity of caching results to avoid the exponential growth of possibilities in longer strings.

Scrambling Back and Forth

Matthias's submission for Task 1, he has used a concise and efficient one-liner with List::UtilsBy::max_by to count how many times words appear and in doing so stores a reference to the 'most popular' result while processing all results in a single pass. With respect to Task 2, Matthias did not limit himself to performing a simple recursive search as in previous weeks; however, he developed a Reverse Scramble algorithm, which is performing this task as a sequence of sorting in which strings become indexes of streaks representing their sorted order to allow for efficient verification of scrambling operations performed.

The scramble can not stop you / from becoming popu-ler… lar

Packy's polyglot Challenge 370 features solid code samples in Perl, Raku, Python, and Elixir that showcase how to solve the problem multiple ways using different programming languages. He shows how using Elixir's pipe operator or Raku's Bag can help efficiently transform and filter the paragraph from "Most Popular Word". He also demonstrates a consistent, recursive approach to solving the problem using the same recursive logic, with code examples in each of the four languages, while adapting to the specific syntax and idioms of each language, in his example "Scramble String".

Words and more words

Peter implements a clean regular expression approach to normalising the text and eliminating any banned words in Popular Word, while also provides a completely recursive solution in Scramble String that clearly demonstrates splitting and swapping parts of the string in order to illustrate the property of being scrambled.

The Weekly Challenge - 370: Popular Word

Reinier solution includes a simple regular expression to remove punctuation characters and a hash to count the number of occurrences of each valid word. This makes it easy to identify the most frequently used word that is not on a banned list, and provides an easily readable model from which to analyze text.

The Weekly Challenge - 370: Scramble String

Reinier's demonstrates pruning as an optimisation technique. If the characters sorted for both strings do not match (meaning they are not anagrams), the function returns false without going through all remaining recursive levels.

The Weekly Challenge #370

Robbie's high-performance Perl solutions employ algorithmic efficiency and strong input validation. In Task 1, he uses a fast normalisation method along with a hash-based frequency counting scheme to isolate the most popular word. In Task 2, Robbie's "Scramble String" implementation is notable for being a recursive Divide-and-Conquer strategy, providing an additional speedup by implementing a pre-check anagram filter to eliminate expensive recursive calls to incompatible strings.

Popular Scramble

Roger shown how to use a specialised programming tool like Counter in Rust to find popular words with only a few lines of code. In addition, the example in PostScript is an interesting concept as it shows how to create a counted hash from scratch using the examples provided in the Rust program as a reference.

Popular Scrambling

Simon demonstrates Task 1 by methodically normalising strings and counting strings using hashes in order find the word with the highest occurrence. His solution for Task 2 highlights the use of recursion when solving the problem, and optimises by checking if two strings are anagrams with a "fail-fast" method; this way, all non-anagrams can be filtered out before going through the more complex process of recursion.

Perl Tutorial

A section for newbies and for people who need some refreshing of their Perl knowledge. If you have questions or suggestions about the articles, let me know and I'll try to make the necessary changes. The included articles are from the Perl Maven Tutorial and are part of the Perl Maven eBook.

Testing in Perl

The 'slides' with all the examples that are used in the online course.

Rakudo

2026.16 Selkie TUI Framework

Training

Testing in Perl

While we are still recording the live sessions of the Testing in Perl you can also watch the previous episodes on the Code Maven Academy web site. Usually it is for paying subscribers only, but for the next couple of day you can still access it free of charge. You only need to register on the web site.

Weekly collections

NICEPERL's lists

Great CPAN modules released last week.

Events

Perl Maven online: Testing in Perl - part 5

April 30, 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.

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. ]

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

Prepare Module::CoreList

Perl commits on GitHub
Prepare Module::CoreList
Bump the perl version in various places from 5.43.10 to 5.43.11

PWC 370 Scramble On, Scramblin' Man

dev.to #perl

If your girlfriend has been kidnapped by Gollum and the Evil One, and there's nothing you can do, perhaps console yourself with a small programming problem while you listen to Ramble On or Ramblin' Man'.

PWC 370 Task 2: Scramble String

The Task

You are given two strings A and B of the same length. Write a script to return true if string B is a scramble of string A otherwise return false. String B is a scramble of string A if A can be transformed into B by a single (recursive) scramble operation. A scramble operation is:

  • If the string consists of only one character, return the string.
  • Divide the string X into two non-empty parts.
  • Optionally, exchange the order of those parts.
  • Optionally, scramble each of those parts.
  • Concatenate the scrambled parts to return a single string.

Examples

  1. Input: $str1 = "abc", $str2 = "acb" Output: true
    • split: ["a", "bc"]
    • split: ["a", ["b", "c"]]
    • swap: ["a", ["c", "b"]]
    • concatenate: "acb"
  2. Input: $str1 = "abcd", $str2 = "cdba" Output: true
    • split: ["ab", "cd"]
    • swap: ["cd", "ab"]
    • split: ["cd", ["a", "b"]]
    • swap: ["cd", ["b", "a"]]
    • concatenate: "cdba"
  3. Input: $str1 = "hello", $str2 = "hiiii" Output: false
  4. Input: $str1 = "ateer", $str2 = "eater" Output: true
  5. Input: $str1 = "abcd", $str2 = "bdac" Output: false

The Ramblin' Scramblin' Thinkin' Part

This could go two ways: (1) generate every possible scramble of str1 and then look for str2; or (2) do a breadth-first or depth-first search for str2.

The possible number of scrambles could explode. For a length of n, there will be n-1 splits, and then the n-1 string will have n-2 splits, and so on, so we're looking at factorial complexity. Let's not generate every possible scramble unless we have to.

For a search, we'd be trying to match the front or back halves of the two strings, and then recursing on the parts that didn't match. There are finite possibilities for partial matches.

  • A simple swap gets us from str1 to str2:
  str1:    ---  ++++++++++
  str2:    ++++++++++  --- 
  • The head or tail of str1 matches the corresponding head or tail of str2, requiring us to recurse on the shorter string that didn't yet match.
   str1:   ===  [??????????]    ||    [???]   ==========
            |     scramble           scramble      |
            |         |                 |          |
   str2:   ===  [.*#.*#.*#.]    ||    [.*#]   ==========
  • The head or tail of str1 matches the back or front of str2. Similar to the case above, except that a swap is required before we recurse.
   str1:  ===  [??????????]   ||   [???] =========
                     /               \
             scramble                 scramble
               /                              \
   str2:  [.*#.*#.*#.]  ===   ||   ========= [.*#]

A Bold Strategy, Cotton. Let's see if that works out for him.

After an embarrassing amount of trial and error (you might say I was scrambling), my solution converged on this longer-than-usual recursive function.

sub isScramble($str1, $str2, $depth = "")
{
    state %remember;
    %remember = () if $depth eq "";

    my $key = ":$str1:$str2:";
    if ( exists $remember{$key} )
    {
        $logger->debug("${depth}CACHED $key = ", ($remember{$key} ? "true":"false"));
        return $remember{$key}
    }

    my $len = length($str1);
    return ( $remember{$key} = false) if $len != length($str2);
    return ( $remember{$key} =  true) if $str1 eq $str2;

    for ( 1 .. $len-1 )
    {
        # $_ is the length of the left substring (head), $len-$_ is length of right (tail)
        my (  $head,   $tail) = ( substr($str1, 0, $_), substr($str1, $_) );
        my ($s2head, $s2tail) = ( substr($str2, 0, $_), substr($str2, $_) );

        my $s2front = substr($str2, 0, $len-$_);   # length of $tail, on left side
        my $s2back  = substr($str2, -$_);          # length of $head, on right side

        $logger->debug("${depth}Compare [$head/$tail] <> s2head/$s2tail] ($s2front/$s2back)");

        if ( "$tail$head" eq $str2 )
        {
            $logger->debug("${depth}FOUND $str2");
            return $remember{$key} = true;
        }
        elsif ( $head eq $s2head )
        {
            $logger->debug("${depth}HH, compare $tail <> $s2tail");
            return true if $remember{$key} = isScramble($tail, $s2tail, "  $depth");
        }
        elsif ( $head eq $s2back )
        {
            $logger->debug("${depth}HB, compare $tail <> s2front");
            return true if $remember{$key} = isScramble($tail, $s2front, "  $depth");
        }
        elsif ( $tail eq $s2tail )
        {
            $logger->debug("${depth}TT, compare $head <> s2head");
            return true if $remember{$key} = isScramble($head, $s2head, "  $depth");
        }
        elsif ( $tail eq $s2front )
        {
            $logger->debug("${depth}TF, compare $head <> s2back");
            return true if $remember{$key} = isScramble($head, $s2back, "  $depth");
        }
        else
        {
            $logger->debug("${depth}No pairs, recurse with $head<>$s2head and $tail<>$s2tail");
            return $remember{$key} = true if ( 
                   ( isScramble($head, $s2head, "  $depth")
                  && isScramble($tail, $s2tail, "  $depth") )
                || ( isScramble($head, $s2back, "  $depth")
                  && isScramble($tail, $s2front, "  $depth") ) );
        }
    }

    $logger->debug("${depth}NOT FOUND $key = false (caching)");
    return $remember{$key} = false;
}

Notes:

  • The function starts out with setting up a cache for partial results. During development, I added this near the end of the process, but it shows up at the top of the function, so let's discuss it.

    • Recursive search algorithms usually benefit from caching known results. Our worst case is that str1 can't be scrambled into str2, in which case we'll end up doing the whole exponential tree of possibilities. I expect caching partial results would pay off.
    • The %remember hash is my cache, and it's a state variable so that it persists across recursive calls. But that means it also persists across invocations for different test cases, so it could yield wrong answers unless I initialize it at the top level of the recursion.
    • The cache is filled by using in-line assignment every time a result is returned, which is kind of cutely readable, I think.
    • Should I have used Memoize? Maybe. But that caches values based on the arguments passed to the function, and one of my arguments is the depth to which I've recursed. That means cache misses depending on how I got down the search tree, so I opted for do-it-yourself memo-ization.
  • The function begins by dispensing with the easy cases: having a result in the cache, matching strings, and strings of the wrong length.

  • Then, for each division of str1, let's have at hand the substrings that we might be dealing with from both str1 and str2. There's a little tour-de-force of substr uses here, splitting the strings at a midpoint, and using negative offsets.

  • Most of the body of the function is testing which pairs match up, if any, and doing the appropriate recursion of what remains.

  • The last case, where neither the head nor the tail of str1 has a matching complement in str2, requires us to recurse on both pieces, and in either order.

  • I used logging liberally, because the function is only obvious in hindsight and took me longer than I'd like to admit. The $depth argument is mostly a convenience for indenting the log statements appropriately -- notice that it gets longer by two spaces at each recursion. My setup for logging uses Log::Log4perl in easy mode. It's part of my boilerplate for PWC solutions and looks like this:

use Getopt::Long;
my $Verbose = false;
my $DoTest  = false;

GetOptions("test" => \$DoTest, "verbose" => \$Verbose);
my $logger;
{
    use Log::Log4perl qw(:easy);
    Log::Log4perl->easy_init({ level => ($Verbose ? $DEBUG : $INFO ),
            layout => "%d{HH:mm:ss.SSS} %p{1} %m%n" });
    $logger = Log::Log4perl->get_logger();
}
perlhacktips: Fix =encoding directive

Commit 0dc87bc7621 added a C<> around the = of the encoding directive,
thus making it ineffective.

There aren't actually any non-ASCII characters in the file, so it's
not a live bug.
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.
If you’re a fan of Test Driven Development (TDD) like I am, you have likely explored many different ways to test your code. I’m not ashamed to call myself an old school student; for a long time, I relied on my good old friend, Test::More, for most of my testing.
Thank you Team PWC for your continuous support and encouragement.

(dxcvii) 19 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::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

Simple online Perl playground (No setup, no AI)

r/perl

Hello, I’m trying to experiment with Perl just for fun (random commands) but I don’t want to install a full Linux environment or deal with complicated setups. I’m looking for something that runs in the browser. Just write code and run it. No need to save things. Any suggestions?

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

This week in PSC (222) | 2026-04-25

blogs.perl.org

This meeting took place at the Perl Toolchain Summit 2026. All three of us met in person in Vienna and reviewed our release blocker status. Things are in the clear: no new blockers have come in since last meeting, and the previously identified ones are already resolved, with a single exception, namely the workflow for EOL notices in release announcements. Conveniently, Philippe, who just released 5.43.10, is also in attendance in Vienna, and has provided concrete feedback, so this is being addressed.

[P5P posting of this summary]

cpan.org email forwarding has been shut down

r/perl

Help with the $1 $2 regex variables

r/perl

i am trying to parse similar words in a text search into different variables.

$line='somehow, someone';

if ($line~=/(some[a-z]{3})/gci){$result=$1;$result2=$2};

print "\n$result $result2";

But only 1 result is printing.

The Regex instructions don't really give a good example on how the $1... variables work.

is there a simple way to change the if line for this to work?

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

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


The below codes I had been using for many years in order to replace a part of text string in multiple files.

It works super. This code runs at Perl version 5.32.1 around (Rocky9.6).
However, when I tried to more modernization of this code, which means that eliminating -w at the first Hash-Bang line, and includes [ strict; warnings; ] two lines, then the newer Perl interpreter shows errors, somewhere around foreach or opendir or somewhere.
Around this part of codes are came from old Perl interpreter version 5.8..8 (Fedora Code 7).
I just copied this part of codes from textbook or elsewhere.

Which part of the Old Codes doesn't work with the newer [ strict; and warning; ] standard beginning of nowadays?

#! /usr/bin/perl  -w
##   2026-04-23
##   Global Replacement a transition scr60a6_01_transition_to_rocky9.6.pl
##     (-w ć‚’å‰Šé™¤ć—ć¦ )
##     strict;
##     warnings:
##   
## 
my $filename = "";
$MAGIC_PATH = "/home/mkido/bin/in";

# This recorgnizes Extention
$MAGIC_EXT1 = 0;
print "Type html, pl, txt OR php.\n";
$MAGIC_EXT1 = <STDIN>;  # read the next line
chomp ($MAGIC_EXT1);
print "$MAGIC_EXT1\n";  ##  DEBUG, CONFIRMATION
$MAGIC_FROM        = "from_text_string";
$MAGIC_TO          = "to_text_string";
print "\nhello world\n\n";
opendir DIRHANDLER, $MAGIC_PATH;
@allfiles = readdir DIRHANDLER;
foreach my $filename (sort @allfiles) {
    print "$filename\n";
}
print "\n\n";
print "Now is the business\n";
foreach $filename (sort @allfiles) {
    if( $filename =~ /$MAGIC_EXT1/ ){
             print "$filename\n";
             open (IN, "in/$filename") or die ("error:$!");
             open (my $fh, '>',  "/home/mkido/bin/RESULT/$filename" ) or die "cannot open $filename for writing:$!";
             while ( my $line1 = <IN>) {
                 if( $line1 =~ /http/ ) {
        ##               s/$MAGIC_FROM/$MAGIC_TO/g;
                       (my $newline1 = $line1 ) =~ s/$MAGIC_FROM/$MAGIC_TO/g;
                         print  $fh  $newline1;      
                 } else {  print $fh  $line1;    }
             }  ### closing while-loop 
        close(IN);
        close($fh);
    }    ### closing if_filename_has_html   
}          ### closing a big FOREACH-loop
print "\n\n";

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.

PPC Summer 2026 - Call for Participation!

blogs.perl.org

https://www.papercall.io/perlcommunityconferencesummer26

Please share this post, the powers that be rage against us; you will not see any post regarding our activities in Perl Weekly!

If you wish to comment about this post, please do so at r/perlcommunity.

highres_533738236.jpg

Perl Community / Science Perl Committee Impact in 2025

Talks Delivered at Winter 2025 Perl Community Conference in Austin, TX

Video editing in progress, will be released after the 2026 Summer PPC.

Each PPC has its own playlist on our YT channel!

Talks Delivered at Summer 2025 Perl Community Conference in Austin, TX

Talks Delivered at Winter 2024 Perl Community Conference in Austin, TX

Science Track Paper-based Talks Delivered at Summer 2024 Perl & Raku Conference in Las Vegas

Our Code of Virtues

Codes of Conduct focus on vices and naively attempt to list all the banned behaviors. We focus on virtues and thus set the bar on behavior HIGH. Therefore, we do not have a Code of Conduct. We have a Code of Virtues—virtues that have been present and part of Perl from the very beginning.

The concept of virtues is very old, dating back to Nicomachean Ethics. Aristotle identified virtues as character traits that enable individuals to live a good life and achieve eudaimonia (flourishing or happiness). Examples include courage, temperance, and justice. Virtue was seen as the "golden mean" between two extremes (e.g., courage is the balance between recklessness and cowardice).

Aristotle’s virtues, such as courage, justice, and temperance, emphasize achieving a balanced and flourishing life through reason. These ideals directly influenced formal Christian virtues, particularly through St. Thomas Aquinas’ prolific writings, which integrated them with faith, hope, and charity as moral principles for spiritual growth. For example, the medieval codes of chivalry reflected this synthesis, urging knights to embody classical virtues like courage, meekness, humility, and compassion, as seen in their oaths to protect the weak and uphold justice.

Here we describe what Larry Wall meant in correct, virtuous, and perhaps chivalrous terms.

The 3 Virtues of a Perl Programmer, Properly Defined

Our Code of Virtues is made of the Three Virtues of a Perl Programmer, first elucidated by Perl's founder, Larry Wall. With a mind toward virtue, we define what he described in positive terms of virtue rather than the traditional words, which are actually vices.

Practical Wisdom or Prudence, Not Laziness

Unlike the vice of laziness, this virtue refers to practical wisdom or prudence. It involves the ability to make sound decisions and take appropriate actions based on understanding, experience, and ethical considerations. This aligns closely with Larry’s definition of laziness, summarized as:

"...the quality that makes you go to great effort to reduce overall energy expenditure. It makes you write labor-saving programs that other people will find useful and document what you wrote so you don’t have to answer so many questions about it."

Spiritedness, Not Impatience

Accounting for the passionate aspect of human nature, this encompasses emotions like anger, righteous indignation, and the drive to achieve justice or excellence. Who among us has not experienced this in some form, particularly during heated online discussions? This aligns well with Larry’s definition of impatience, summarized as:

"...the anger you feel when the computer is being lazy. This makes you write programs that don’t just react to your needs but actually anticipate them—or at least pretend to."

Good Order, Not Hubris

Far from harmful pride, this refers to maintaining good order and governance, both in societal contexts and personal conduct. Applied to programming, it signifies creating well-structured, organized, and maintainable code. This aligns well with Larry’s definition of hubris, summarized as:

"...the quality that makes you write (and maintain) programs that other people won’t want to say bad things about."

I am on a Windows machine trying to cross-compile with my cross-compiler to use x86_64 for a Linux target machine. I am trying to use a cross compiler to build msquic via a PowerShell command which uses OpenSSL, however I am failing in locating the Pod/Usage.pm.

I am running the command from PowerShell 7 using the command from the msquic/scripts folder:

.\build.ps1 -Platform linux -Arch x64 -ToolchainFile "C:\msquic-2.5.6\nilinuxrt.cmake" -Generator "Unix Makefiles" -Tls openssl -Clean

I have added the line use lib "/c/Strawberry/perl//lib/"; in the OpenSSL Configure file and and added a print statement showing @INC by adding the line: print "DEBUG: \@INC = @INC\\n";

I see my Strawberry folder contains the Pod/Usage.pm. However, I get an error that states the following:

\[ 32%\] OpenSSL configure  
DEBUG: @INC = 
 /c/Strawberry/perl//lib/
 /c/msquic-2.5.6/submodules/openssl/util/perl
 /usr/lib/perl5/site_perl
 /usr/share/perl5/site_perl
 /usr/lib/perl5/vendor_perl
 /usr/share/perl5/vendor_perl
 /usr/lib/perl5/core_perl
 /usr/share/perl5/core_perl
 /c/msquic-2.5.6/submodules/openssl/external/perl/Text-Template-1.56/lib  
Configuring OpenSSL version 3.5.6-dev for target mingw64  
Using os-specific seed configuration  
Created configdata.pm  
Running configdata.pm  
Can't locate Pod/Usage.pm in @INC (you may need to install the Pod::Usage module) (@INC entries checked:
 /usr/lib/perl5/site_perl
 /usr/share/perl5/site_perl
 /usr/lib/perl5/vendor_perl
 /usr/share/perl5/vendor_perl
 /usr/lib/perl5/core_perl
 /usr/share/perl5/core_perl) at configdata.pm line 22487.

(Line breaks added for readability)

As we can see that the @INC later when running configdata.pm no longer contains the /c/Strawberry/perl/lib.

I have tried adding PERL5LIB as an environment variable which then produces this error:

Cwd.c: loadable library and perl binaries are mismatched (got second handshake key 0000000a00000890, needed 0000000000000000)Cwd.c: loadable library and perl binaries are mismatched (got second handshake key 0000000a00000890, needed 0000000000000000)

I believe what is happening is submake files are reverting to use my GIT perl instead and I am not sure how to fix this.

For debugging, I am using msquic.2.5.6 and strawberry perl 5.38.2.2.

Importance of Repositories in Public

blogs.perl.org

It used to be so that a repository was only a place of work and the distribution was the actual result of that work. Only the contents of the distribution mattered. People would read the files README and INSTALL from the distribution after having downloaded it.

Not so anymore. Today the repository is out in the open in GitHub, GitLab, Codeberg or other shared hosting site. On the other hand, the documentation in the distribution is often discarded as distribution packages are rarely downloaded manually but rather via a package manager which installs them automatically.

Publicly viewable repository has in fact become much more than just a place of work. It is also an advertisement for the project and of the community behind it, if there is more than one author or contributor.

When a potential user first finds the project repository, the hosting site commonly presents him with the project README file. That makes README file in fact the welcome page to the project. Its purpose is changed from being purely informational to being an advertisement which competes for user’s attention with bright colors, animated pictures, videos and exciting diagrams, shapes and “bumper stickers”.

But under all the exciting cover it must also remain true to its nature: present the project as precisely as possible and stay up to date with its development.

README might also not be the only file which needs to be kept up to date because it is accessed in the (public) repository. Other potential files can include INSTALL, Changes and CODEOWNERS.

Many files therefore contain text which must be updated at least at the time of release: version numbers, API documentation, examples, file lists.

It is difficult to keep these files in sync with the code; just like documentation, which fact every programmer knows. The Dist::Zilla plugin Dist-Zilla-Plugin-WeaveFile will prevent the files from falling out of sync because their content is tested continuously.

There are other ways to do this, for instance Dist::Zilla::Plugin::CopyFilesFromBuild.

It is my philosophy that nothing in the repository is changed behind programmer’s back. It can also be dangerous to the programmer if he is not a frequent Git committer. Failed local tests are much safer. And when the test fails, it is easy to run dzil weave to update the files.

Dist-Zilla-Plugin-WeaveFile

The plugin Dist-Zilla-Plugin-WeaveFile works very much like my earlier plugin Dist-Zilla-Plugin-Software-Policies: it consists of three pieces: The Dist::Zilla command weave, the plugin WeaveFile which is used to define the configuration in dist.ini file, and the plugin Test::WeaveFile which creates tests for the distribution which check that the defined files exist and match their definition.

Example from dist.ini file:

; Uses default config file .weavefilerc
[WeaveFile / README.md]

; Uses a custom config file and specifies file explicitly
[WeaveFile]
config = install-weave.yaml
file = INSTALL

[Test::WeaveFile]

And the definition file .weavefilerc would then contain, for example:

---
snippets:
    badges: |
        [![CPAN](https://img.shields.io/cpan/v/My-Dist)](https://metacpan.org/dist/My-Dist)
    license: |
        # LICENSE
        [% USE date -%]

        This software is copyright (c) [% date.format(date.now, '%Y') %] by [% dist.author %].

        This is free software; you can redistribute it and/or modify it under
        the same terms as the Perl 5 programming language system itself.
files:
    "README.md": |
        [% snippets.badges %]

        # [% dist.name %] - [% dist.version %]

        [% dist.abstract %]

        [% pod("My::Module", "SYNOPSIS") %]
        [% pod("My::Module", "DESCRIPTION") %]

        [% pod("bin/myprog", "EXAMPLE") %]

        [% snippets.license %]

The templating system is Template-Toolkit. I am planning to change this so that user can choose another templating system if wanted, and then Template-Toolkit will be optional to install. Also allowing to change the output format (currently Markdown) is in plans. All pod text is converted to Markdown.

With a configuration like the above, when user runs dzil test, if the static files README.md and INSTALL are not in sync with their definitions, user can run:

dzil weave

or

dzil weave README.md
dzil weave INSTALL

Future

There might be additional generated information which we will be forced - for practical reasons - to commit into the repository. cpanfile could be one such. GitHub repositories are being scanned by different AI tools which could draw benefit from having such information at hand, instead of being generated and only available in the distribution in MetaCPAN. It does fight the principal of DRY, or, in this case “do not commit generated files” but it could be the lesser evil.

I have lately learned that Devin, the AI software engineer is being used to create summaries and presentations of GitHub repositories in DeepWiki. For an example of a Perl project, my Env-Assert.

Toronto Perl Mongers / March 2026 Talk

The Perl and Raku Conference YouTube channel

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.

perlapi: revise cophh_delete

Perl commits on GitHub
perlapi: revise cophh_delete

This adds some clarifications

Random Scrambles

dev.to #perl

Week 370

My Solutions
Task 1 : Popular Word (By M. Anwar)

You are given a string paragraph and an array of the banned words. Write a script to return the most popular word that is not banned. It is guaranteed there is at least one word that is not banned and the answer is unique. The words in paragraph are case-insensitive and the answer should be in lowercase. The words can not contain punctuation symbols.

For frequency of words I immediately plan to use a hash. My only hangup on this one was when the input paragraph was not space separated but used punctuation marks instead. This could be more robust, but that's an abnormal input. I turn everything lowercase, check for spaces, strip out the punctuation marks, and put the word count in the hash %h. Then we ignore banned words.

sub strip($w) {
    my $out;
    for my $letter (split '', $w) {
    $out .= $letter if ($letter =~ /\w/);
    }
    return $out;
}

sub proc($paragraph, @banned) {
    say "Input:  \$paragraph = $paragraph\n\t\@banned = @banned";
    my @words;
    if ($paragraph =~ /\s/) {
    @words = split ' ', lc $paragraph;
    } else {
    my $word;
    for my $letter (split '', lc $paragraph) {
        if ($letter =~ /[a-zA-Z]/) {
        $word .= $letter;
        } else {
        push @words, $word;
        $word = "";
        }
    }
    }
    my %h;
    foreach my $word (@words) {
    $word = strip($word);
    $h{$word}++;
    }
    my $max = 0;
    my $max_word;
    for my $w (keys %h) {
    my $ban = 0;
    for my $banned_word (@banned) {
        if ($w eq $banned_word) {
        $ban = 1;
        last;
        }
    }
    next if ($ban);
    if ($max < $h{$w}) {
        $max_word = $w;
        $max = $h{$w};
    }
    }
    say "Output: $max_word";
}

Task 2: Scramble String (By R. B-W)

You are given two strings $str1 and $str2 of the same length. Write a script to return true if $str2 is a scramble of $str1 otherwise return false. String B is a scramble of string A if A can be transformed into B by a single (recursive) scramble operation.

Mr. Roger Bell-West then goes on to explain what a scramble is.

  • If the string consists of only one character, return the string.
  • Divide the string X into two non-empty parts.
  • Optionally, exchange the order of those parts.
  • Optionally, scramble each of those parts.
  • Concatenate the scrambled parts to return a single string.

When the task was first described, he had suggested choosing a random location for the split, and randomly deciding to perform the scrambles or swaps. I decided to embrace that approach.

I loop the scramble 10,000 times to increase the odds of success.

sub scramble($s) {
    my $len = length($s);
    if ($len == 1) {
    return $s;
    } else {
    my $pt = 1 + ($len - 1) * rand();
    my $a = substr $s, 0, $pt;
    my $b = substr $s, $pt;
    my $a_new = (int 2*rand() == 0) ? $a : scramble($a);
    my $b_new = (int 2*rand() == 0) ? $b : scramble($b);
    my $out = (int 2*rand() == 0) ? $a_new.$b_new : $b_new.$a_new;
    }
}

Originally published at Perl Weekly 769

Hi there,

Every week I see a post declaring about something being dead. Agile is dead! Testing is dead!, Algol-68 is dead! I am so fed-up with this. So I am not going to link to the article that was discussing 5 dead programming languages.

Last week finally I got home and because of the flight I had to postpone the Testing in Perl event so it will be held this Thursday. You are invited to watch the previous sessions (for now free of charge) and join the next one.

The Perl Maven WhatsApp group already has more than 70 members. Unfortunately recently we got a few spammers so I had to turn on registration-approval. This means that when you try to join I'll send you a private message asking who you are. This is the little extra step we have to do to avoid spam. Anyway, you are invited to join us!

Enjoy your week!

--
Your editor: Gabor Szabo.

Announcements

TPRC Talk Submission Deadline in 2 days!

Articles

Faster UTF-8 Validation

Way more information about UTF-8 than I can fit in my head.

Enums for Perl: Adopting Devel::CallParser and Building Enum::Declare

Compiling Google::ProtocolBuffers::Dynamic on Debian Trixie

For a long time I have been trying to encourage Perl Module authors to include installation instruction when external libraries are needed. Even if only for one or two Linux distributions. This information should be in the README of the project.

Happy sharing

How to share memory between processes? A survey of a bunch od Data::* modules.

Making an Asynchronous Clocking Drum Machine App in Perl

PDL in Rust -- Part Two

"The current PDL implementation in pperl covers roughly 3,000 assertions end-to-end: about 1,400 on the Perl-facing connector side and about 1,600 on the engine side. As of this writing roughly 98% of the connector assertions match upstream PDL 2.103 exactly, and most of the remaining couple of dozen we already know why they fail. By the time you read this the numbers will have drifted a little in our favour - give or take - but the shape is the point, not the decimal."

Discussion

parsing a csv with boms in every line

What kind of strange things people have to deal with?

A curious case of an autovivified env var

Should the documentation of autovivification be comprehensive?

Grants

Maintaining Perl 5 Core (Dave Mitchell): March 2026

PEVANS Core Perl 5: Grant Report for March 2026

Maintaining Perl (Tony Cook) March 2026

Perl

This week in PSC (221) | 2026-04-13

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 - 370

Welcome to a new week with a couple of fun tasks "Popular Word" and "Scramble String". 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 - 369

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

Perl Weekly Challenge 369: Valid Tag

This post provides multiple amazing examples of technical versatility by providing a clean and direct solution written clearly in many different programming languages with tremendous flair, it shows great elegance in presenting the elegant and simple solution to the valid tag problem using the idiomatic language of each respective programming language. Abigail shows off expert level code writing skills by using the advanced character class arithmetic features present within Perl; these advanced ways of manipulating strings show the efficiency of his coding style, while simultaneously creating visually appealing code.

Perl Weekly Challenge 369: Group Division

This post offers an extremely polished and versatile engineering design for the island program project. Abigail has shown advanced expertise by using a 'chunk-and-fill' method over many different programming languages (Perl, C, and less common languages such as sed, including details on how string slices and fill-up strings can be done with the least amount of impact. Furthermore, it highlights a creative use of string replication operators as well as very efficient use of loops that guarantee that the final incomplete group will still have the correct amount of padding added based on the requirements given in the challenge.

Tag Division

In an idiomatic Raku implementation of the Group Division problem, as shown pretty clearly here by Arne Sommer, the gather/take construct is utilised nicely to collect the data clearly, and with the use of substr-rw for in-place string manipulation and the replication operator (x) to add padding, the solution is both easily readable and aesthetically pleasing.

Perl Weekly Challenge: Week 369

By taking advantage of mathematical precision and the crispness of concise syntax through the use of "one liners", Jaldhar has developed an efficient method for solving this problem, no matter if you're using Raku or Perl. Calculating the required amount of padding to add to a split string before actually splitting it, allows for quick and accurate results. Furthermore, the clever application of native string manipulation functions adds an additional level of efficiency and clarity to handling the grouping logic.

Divided Tags

This article offers a detailed examination of the many aspects of the "Valid Tag" challenge and provides a well-defined "word" in order to enhance the accuracy of processing. The body of this technical paper describes Jorg's unique application of the Perl programming language's ability to utilise global regular expressions (regex) to solve Task 2; and also the excellent "Shape" verb from the J programming language that has provided an efficient and generalised way to reshape and pad multi-dimensional arrays.

The Weekly Challenge 369

In this post, we look at the approach both challenges in a disciplined and structured manner. The focus is on having code that is easy to maintain and easy to read. We have examples showing clean, modular Perl and Python code, and show examples of how the "Group Division" challenge is solved using efficient use of list slicing and using generator expressions to partition and pad strings in a method that's worthy of professional quality.

string indexes

Luca Ferrari exhibits an incredible degree of technical ability through his creation of five unique environments in which to accomplish the Week 369 challenges. Those five programming environments are: Raku, Python, PostgreSQL (PL/Perl, PL/PgSQL, and PL/Java). Luca's elegant use of Raku's rotor method combined with the ability of Python's list slicing to achieve the same complex logic for string padding and partitioning, as demonstrated in his application of The Group Division challenge, show how many different programming languages can utilise very diverse methods to accomplish the same technically precise logic.

Perl Weekly Challenge 369

The post demonstrates exceptional compact Perl programming by distilling complex string processing procedures into "1.5-liners" that are quite efficient. For Task 1, he builds camelCase tags from a string of input values using the split, map, and join functions in order to achieve the desired result in one pass. In addition, Luis's approach to Task 2 makes use of a brilliant "alternation" regex (.{$size}|.+) as a means of capturing both full and partial segments of an input string in an optimal manner. The use of this regex allows him to create direct array-index padding, resulting in code that is not only concise, but also very technically accurate and well-balanced between the two principles.

Good Tags and Good Chunks

Matthias Muth has written an impressive article on internationalisation (I18N) relating to pragmatic problem solving while still maintaining a strong long-term support model through thoughtful design choices such as those found within both the "Valid Tag" and "Group Division" solutions provided by Matthias's book. The use of the Text::Unidecode module was one way to create technically superior solutions that would accommodate for the presence of non-ASCII character sets and would also adhere to the rules specified in the challenge itself (e.g., valid tag). Additionally, his solution for "Group Division" is equally amazing because he accomplished this via a mathematical pad for the purpose of executing a clean single regex global match - or a single line of functional code - that could otherwise be accomplished via several iterations of code.

Strings Will Tear Us Apart

In this post, Packy gives a thorough, contemporary example of string handling in Raku, Perl, Python, and Elixir. He creates a unique solution to the "Group Division" challenge by using the Raku function .comb with integer arguments to automatically divide into chunks and by using the Perl 'unpack' function with a per-function constructed template, demonstrating how you can creatively employ language idioms to efficiently and effectively resolve a common programming issue and provide a solution for data partitioning that uses little or minimal resources.

Fun with strings

In his article, Peter presents a practical and polished approach to developing an order of operations for the sanitisation of strings. By organising the procedure so that lower case, regular expression character removal, and space & character combination are completed before the creation of camelCase, the end product meets the requirements for both camel case formatting as well as length requirements while still producing clean, effective code.

The Weekly Challenge - 369: Valid Tag

In Reinier's method, a model for defensive programming has been developed that features validation of input for real alphabetic values before processing. Also, he has taken a somewhat technical approach (transforming input to remove non-letter characters by converting them to spaces in order to apply camelCase correctly while keeping word boundaries intact) through his use of multiple accurately readable regular expressions.

The Weekly Challenge - 369: Group Division

Reinier has created a very good tutorial solution which showcases how to use the "Perl 4-argument substr function" to extract and remove data from a string using a while loop to do so iteratively and then using string replication for the final padding makes the code extremely readable and a great technical example of using efficient in-place usage of strings.

The Weekly Challenge #369

The work done by Robbie within the "Valid Tag" review indicates a very well thought out way to approach hyphenated compound words as a single entity for case adjustment, in addition to providing an innovative solution for "Group Division", by utilising the four-argument form of Perl's substr in order to easily "chop and fill" strings, while also demonstrating his superior knowledge of high-performance string manipulation.

Divided Validity

Roger's technical review offers an interesting side-by-side comparison of various string handling paradigms from multiple programming languages. The "Valid Tag" part of the review shows how Crystal's highly performant state machine implementation allows for case conversion to be accomplished in a single pass. The "Group Division" analysis of in_groups_of() in Crystal is very interesting as well, as it illustrates just how compact that library function is compared to typical iterative slicing found in Typst, demonstrating that using built-in library functions can greatly simplify the implementation of algorithmic logic through less code complexity.

Group Tag

A wonderful illustration of Test-driven development is Simon's critique of Challenge 369. He found through tests that the sanitisation step of "Valid Tag", had to be completed before performing case formatting so that example 5 is handled properly. He also provides useful technical comparisons between both languages' ecosystem strengths, such as Python using more_itertools.grouper vs Perl doing it manually by iteration.

Weekly collections

NICEPERL's lists

Great CPAN modules released last week.

Events

Perl Maven online: Testing in Perl - part 4

April 23, 2026

Perl Toolchain Summit 2026

April 23-26, 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.

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.

I am confused.

cat << EOF > test.txt
this
is
a
text
file
EOF
cat test.txt | perl -0pe 's/.\n^text/hellohello/smg'

works, but

cat test.txt | perl -0pe 's/.$\n^text/hellohello/smg'

doesn't.

Annoyingly https://regex101.com/ doesn't detect this quirk. Is it to do with -0? And why would $\n^ syntax (which is usually perfectly fine) be wrong anyway?

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

Weekly Challenge: Group Tag

dev.to #perl

Weekly Challenge 369

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: Valid Tag

Submitted by: Mohammad Sajid Anwar

You are given a given a string caption for a video.

Write a script to generate tag for the given string caption in three steps as mentioned below:

  1. Format as camelCase Starting with a lower-case letter and capitalising the first letter of each subsequent word: Merge all words in the caption into a single string starting with a #.
  2. Sanitise the String: Strip out all characters that are not English letters (a-z or A-Z).
  3. Enforce Length: If the resulting string exceeds 100 characters, truncate it so it is exactly 100 characters long.

My solution

The last example - where Hour starts with a upper case letter - would suggest that the second step needs to be done before the first one. As I used TDD when writing these solutions this was picked up when running the tests.

Nothing overly tricky about the solution. I take input_string and perform the necessary operations on it.

def valid_tag(input_string: str) -> str:
    input_string = re.sub(r"[^\sa-zA-Z]", "", input_string)
    input_string = input_string.strip().lower()
    input_string = re.sub(r" +([a-z])", upper_case_letter, input_string)
    return "#" + input_string[:99]

The Perl solution is similar.

sub main ($input_string) {
    $input_string =~ s/[^\sa-zA-Z]//g;
    $input_string =~ s/^\s+//;
    $input_string =~ s/\s+$//;
    $input_string = lc $input_string;
    $input_string =~ s/ +([a-z])/uc $1/eg;
    say "#" . substr($input_string, 0, 99);

Examples

$ ./ch-1.py "Cooking with 5 ingredients!"
#cookingWithIngredients

$ ./ch-1.py the-last-of-the-mohicans
#thelastofthemohicans

$ ./ch-1.py "  extra spaces here"
#extraSpacesHere

$ ./ch-1.py "iPhone 15 Pro Max Review"
#iphoneProMaxReview

$ ./ch-1.py "Ultimate 24-Hour Challenge: Living in a Smart Home controlled entirely by Artificial Intelligence and Voice Commands in the year 2026!"
#ultimateHourChallengeLivingInASmartHomeControlledEntirelyByArtificialIntelligenceAndVoiceCommandsIn

Task 2: Group Division

Task

You are given a string, group size and filler character.

Write a script to divide the string into groups of given size. In the last group if the string doesn’t have enough characters remaining fill with the given filler character.

My solution

This is one task where the Python and Perl solutions are completely different. Python's more_itertools module has the grouper function, so I use this to separate the string into parts with the filler if required. No need to reinvent a perfectly round wheel :)

def group_division(input_string: str, size: int, filler: str) -> list[str]:
    result = []
    for g in grouper(input_string, size, fillvalue=filler):
        result.append("".join(g))
    return result

Perl does not have a similar function, that I'm aware of. I use a traditional for loop to split the string into the different parts. After that I have a command that will add the filler to the last item in the @result array if required. The x operator is the replication operator in Perl.

sub main ( $input_string, $size, $filler ) {
    my @result = ();
    for ( my $i = 0 ; $i < length($input_string) ; $i += $size ) {
        push @result, substr( $input_string, $i, $size );
    }

    $result[-1] .= $filler x ( $size - length( $result[-1] ) );

    say "(" . join( ", ", @result ) . ")";
}

Examples

$ ./ch-2.py RakuPerl 4 "#"
['Raku', 'Perl']

$ ./ch-2.py Python 5 0
['Pytho', 'n0000']

$ ./ch-2.py 12345 3 x
['123', '45x']

$ ./ch-2.py HelloWorld 3 _
['Hel', 'loW', 'orl', 'd__']

$ ./ch-2.py AI 5 "!"
['AI!!!']

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

TL;DR

Searching for CLI modules on MetaCPAN returns 1690 results. And still I wrote another, Yet Another CLI framework. This is about why mine is the one you want to use.

Introduction

CLI clients, we all write them, we all want them, but the boilerplate is just horrible. I wanted to get rid of it: Burn it with šŸ”„.

At my previous dayjob we had something I wrote, or co-wrote, my boss wanted a sort of chef like API. It became zsknife, but had a huge downside. You needed to register each command in the main module and I didn’t like that at all. I forked the concept internally, made it so you didn’t needed to register, but it lacked discovery.

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::Staticperl - perl, libc, 100 modules, all in one standalone 500kb file
    • Version: 1.5 on 2026-04-04, with 21 votes
    • Previous CPAN version: 1.46 was released 4 years, 1 month, 16 days before
    • Author: MLEHMANN
  2. Catalyst::Action::REST - Automated REST Method Dispatching
    • Version: 1.22 on 2026-03-30, with 13 votes
    • Previous CPAN version: 1.21 was released 8 years, 3 months, 25 days before
    • Author: ETHER
  3. CPANSA::DB - the CPAN Security Advisory data as a Perl data structure, mostly for CPAN::Audit
    • Version: 20260329.001 on 2026-03-29, with 25 votes
    • Previous CPAN version: 20260327.002 was released 1 day before
    • Author: BRIANDFOY
  4. Devel::NYTProf - Powerful fast feature-rich Perl source code profiler
    • Version: 6.15 on 2026-03-31, with 199 votes
    • Previous CPAN version: 6.14 was released 2 years, 5 months, 12 days before
    • Author: JKEENAN
  5. Devel::Size - Perl extension for finding the memory usage of Perl variables
    • Version: 0.87 on 2026-03-31, with 22 votes
    • Previous CPAN version: 0.86_50 was released 1 month, 19 days before
    • Author: NWCLARK
  6. Dios - Declarative Inside-Out Syntax
    • Version: 0.002014 on 2026-04-01, with 24 votes
    • Previous CPAN version: 0.002013 was released 1 year, 7 months, 14 days before
    • Author: DCONWAY
  7. Inline::Module - Support for Inline-based CPAN Extension Modules
    • Version: 0.35 on 2026-03-30, with 14 votes
    • Previous CPAN version: 0.34 was released 11 years, 1 month, 12 days before
    • Author: INGY
  8. IPC::Run - system() and background procs w/ piping, redirs, ptys (Unix, Win32)
    • Version: 20260402.0 on 2026-04-02, with 39 votes
    • Previous CPAN version: 20260401.0 was released the same day
    • Author: TODDR
  9. LWP - The World-Wide Web library for Perl
    • Version: 6.82 on 2026-03-29, with 212 votes
    • Previous CPAN version: 6.81 was released 5 months, 6 days before
    • Author: OALDERS
  10. Module::CoreList - what modules shipped with versions of perl
    • Version: 5.20260330 on 2026-03-29, with 45 votes
    • Previous CPAN version: 5.20260320 was released 8 days before
    • Author: BINGOS
  11. Module::Metadata - Gather package and POD information from perl module files
    • Version: 1.000039 on 2026-04-03, with 14 votes
    • Previous CPAN version: 1.000038 was released 2 years, 11 months, 5 days before
    • Author: ETHER
  12. Mouse - Moose minus the antlers
    • Version: v2.6.2 on 2026-04-04, with 63 votes
    • Previous CPAN version: v2.6.1 was released 3 months, 14 days before
    • Author: SYOHEX
  13. perl - The Perl 5 language interpreter
    • Version: 5.042002 on 2026-03-29, with 2251 votes
    • Previous CPAN version: 5.042001 was released 21 days before
    • Author: SHAY
  14. Pod::Simple - framework for parsing Pod
    • Version: 3.48 on 2026-04-04, with 20 votes
    • Previous CPAN version: 3.47 was released 10 months, 19 days before
    • Author: KHW
  15. Sidef - The Sidef Programming Language - A modern, high-level programming language
    • Version: 26.04 on 2026-04-01, with 122 votes
    • Previous CPAN version: 26.01 was released 2 months, 18 days before
    • Author: TRIZEN
  16. SPVM - The SPVM Language
    • Version: 0.990153 on 2026-03-28, with 36 votes
    • Previous CPAN version: 0.990152 was released 2 days before
    • Author: KIMOTO
  17. Sys::Virt - libvirt Perl API
    • Version: v12.2.0 on 2026-04-01, with 17 votes
    • Previous CPAN version: v12.1.0 was released 29 days before
    • Author: DANBERR
  18. Test2::Harness - A new and improved test harness with better Test2 integration.
    • Version: 1.000164 on 2026-04-01, with 28 votes
    • Previous CPAN version: 1.000164 was released the same day
    • Author: EXODIST
  19. WebService::Fastly - an interface to most facets of the [Fastly API](https://www.fastly.com/documentation/reference/api/).
    • Version: 14.01 on 2026-03-31, with 18 votes
    • Previous CPAN version: 14.00 was released 1 month, 14 days before
    • Author: FASTLY
  20. YAML::Syck - Fast, lightweight YAML loader and dumper
    • Version: 1.44 on 2026-04-02, with 18 votes
    • Previous CPAN version: 1.43 was released 1 day before
    • Author: TODDR

This is the weekly favourites list of CPAN distributions. Votes count: 65

Week's winner: App::FatPacker (+2)

Build date: 2026/04/04 19:54:08 GMT


Clicked for first time:

  • App::Chit - chat with AI from the command line
  • App::prepare4release - prepare a Perl distribution for release (skeleton)
  • Context::Preserve - Run code after a subroutine call, preserving the context the subroutine would have seen if it were the last statement in the caller
  • Data::HashMap::Shared - Type-specialized shared-memory hash maps for multiprocess access
  • DB::Handy - Pure-Perl flat-file relational database with DBI-like interface
  • EV::Websockets - WebSocket client/server using libwebsockets and EV
  • Rex::LibSSH - Rex connection backend using Net::LibSSH (no SFTP required)
  • Task::Kensho::All - Install all of Task::Kensho
  • WWW::Tracking - universal website visitors tracking

Increasing its reputation:

Living Perl: From Scripting to Geodynamics

Perl on Medium

A Different Path into Scientific Computing.

TPRF Board Announces the 2025 Annual Report

Perl Foundation News

The Board is pleased to share the 2025 Annual Report from the The Perl and Raku Foundation.

You can download the full report from the Perl and Raku Foundation website

Strengthening the Foundation

2025 was a year of both challenge and progress. Like many nonprofits, the Foundation faced funding constraints that required careful prioritization of resources. At the same time, increased focus on fundraising and donor engagement helped stabilize support for the work that matters most. A number of processes and tools were overhauled, allowing the Board to manage the funding more effectively, and pay grants more promptly and at lower overhead expense than had been the case previously.

Contributions from sponsors, corporate partners, and individual donors played a critical role in sustaining operations—particularly for core development and infrastructure.

Funding What Matters Most

Financial stewardship remained a top priority throughout the year. The Foundation focused its resources on:

  • Supporting the Perl 5 Core Maintenance Fund
  • Investing in Raku development and ecosystem improvements
  • Maintaining essential infrastructure and services

While some grant activity was reduced during tighter periods, the report describes the Foundations recovery from those trials and outlines a clear path toward expanding funding as donations grow.

Our total income for the year was $253,744.86, with total expenditures of $233,739.75. 92% of our spending supported grants, events, and infrastructure. Our largest single expenditure remains the Perl Core Maintenance Grants, one of the long-time pillars of the Foundation's programs.

A Community-Funded Future

The Foundation’s work is made possible by the community it serves. Every donation—whether from individuals or organizations—directly supports the developers, tools, and systems that keep Perl and Raku reliable and evolving.

In 2025, we also strengthened our fundraising efforts, building a more sustainable base of recurring and long-term support to ensure continuity in the years ahead.

Looking Ahead

Our focus for the coming year is clear:

  • Grow recurring donations and sponsorships
  • Restore and expand the grants program
  • Continue developing transparent, responsible financial management

We’re grateful to everyone who contributed in 2025. Your support keeps the ecosystem strong.

If you rely on Perl or Raku, we encourage you to take part in sustaining them. Your support is always welcome!

Writing a TOON Module for Perl

Perl Hacks

Every so often, a new data serialisation format appears and people get excited about it. Recently, one of those formats is **TOON** — Token-Oriented Object Notation. As the name suggests, it’s another way of representing the same kinds of data structures that you’d normally store in JSON or YAML: hashes, arrays, strings, numbers, booleans and nulls.

So the obvious Perl question is: *ā€œOk, where’s the CPAN module?ā€*

This post explains what TOON is, why some people think it’s useful, and why I decided to write a Perl module for it — with an interface that should feel very familiar to anyone who has used JSON.pm.

I should point out that I knew about [Data::Toon](https://metacpan.org/pod/Data::TOON) but I wanted something with an interface that was more like JSON.pm.

## What TOON Is

TOON stands for **Token-Oriented Object Notation**. It’s a textual format for representing structured data — the same data model as JSON:

* Objects (hashes)
* Arrays
* Strings
* Numbers
* Booleans
* Null

The idea behind TOON is that it is designed to be **easy for both humans and language models to read and write**. It tries to reduce punctuation noise and make the structure of data clearer.

If you think of the landscape like this:

| Format | Human-friendly | Machine-friendly | Very common |
| —— | ————– | —————- | ———– |
| JSON | Medium | Very | Yes |
| YAML | High | Medium | Yes |
| TOON | High | High | Not yet |

TOON is trying to sit in the middle: simpler than YAML, more readable than JSON.

Whether it succeeds at that is a matter of taste — but it’s an interesting idea.

## TOON vs JSON vs YAML

It’s probably easiest to understand TOON by comparing it to JSON and YAML. Here’s the same ā€œpersonā€ record written in all three formats.

### JSON

{
“name”: “Arthur Dent”,
“age”: 42,
“email”: “arthur@example.com”,
“alive”: true,
“address”: {
“street”: “High Street”,
“city”: “Guildford”
},
“phones”: [
“01234 567890”,
“07700 900123”
]
}

### YAML

name: Arthur Dent
age: 42
email: arthur@example.com
alive: true
address:
street: High Street
city: Guildford
phones:
– 01234 567890
– 07700 900123

### TOON

name: Arthur Dent
age: 42
email: arthur@example.com
alive: true
address:
street: High Street
city: Guildford
phones[2]: 01234 567890,07700 900123

You can see that TOON sits somewhere between JSON and YAML:

* Less punctuation and quoting than JSON
* More explicit structure than YAML
* Still very easy to parse
* Still clearly structured for machines

That’s the idea, anyway.

## Why People Think TOON Is Useful

The current interest in TOON is largely driven by AI/LLM workflows.

People are using it because:

1. It is easier for humans to read than JSON.
2. It is less ambiguous and complex than YAML.
3. It maps cleanly to the JSON data model.
4. It is relatively easy to parse.
5. It works well in prompts and generated output.

In other words, it’s not trying to replace JSON for APIs, and it’s not trying to replace YAML for configuration files. It’s aiming at the space where humans and machines are collaborating on structured data.

You may or may not buy that argument — but it’s an interesting niche.

## Why I Wrote a Perl Module

I don’t have particularly strong opinions about TOON as a format. It might take off, it might not. We’ve seen plenty of ā€œnext big data formatā€ ideas over the years.

But what I *do* have a strong opinion about is this:

> If a data format exists, then Perl should have a CPAN module for it that works the way Perl programmers expect.

Perl already has very good, very consistent interfaces for data serialisation:

* JSON
* YAML
* Storable
* Sereal

They all tend to follow the same pattern, particularly the object-oriented interface:

use JSON;
my $json = JSON->new->pretty->canonical;
my $text = $json->encode($data);
my $data = $json->decode($text);

So I wanted a TOON module that worked the same way.

## Design Goals

When designing the module, I had a few simple goals.

### 1. Familiar OO Interface

The primary interface should be object-oriented and feel like JSON.pm:

use TOON;
my $toon = TOON->new
->pretty
->canonical
->indent(2);
my $text = $toon->encode($data);
my $data = $toon->decode($text);

If you already know JSON, you already know how to use TOON.

There are also convenience functions, but the OO interface is the main one.

### 2. Pure Perl Implementation

Version 0.001 is pure Perl. That means:

* Easy to install
* No compiler required
* Works everywhere Perl works

If TOON becomes popular and performance matters, someone can always write an XS backend later.

### 3. Clean Separation of Components

Internally, the module is split into:

* **Tokenizer** – turns text into tokens
* **Parser** – turns tokens into Perl data structures
* **Emitter** – turns Perl data structures into TOON text
* **Error handling** – reports line/column errors cleanly

This makes it easier to test and maintain.

### 4. Do the Simple Things Well First

Version 0.001 supports:

* Scalars
* Arrayrefs
* Hashrefs
* undef → null
* Pretty printing
* Canonical key ordering

It does **not** (yet) try to serialise blessed objects or do anything clever. That can come later if people actually want it.

## Example Usage (OO Style)

Here’s a simple Perl data structure:

my $data = {
name => “Arthur Dent”,
age => 42,
drinks => [ “tea”, “coffee” ],
alive => 1,
};

### Encoding

use TOON;
my $toon = TOON->new->pretty->canonical;
my $text = $toon->encode($data);
print $text;

### Decoding

use TOON;
my $toon = TOON->new;
my $data = $toon->decode($text);
print $data->{name};

### Convenience Functions

use TOON qw(encode_toon decode_toon);
my $text = encode_toon($data);
my $data = decode_toon($text);

But the OO interface is where most of the flexibility lives.

## Command Line Tool

There’s also a command-line tool, toon_pp, similar to json_pp:

cat data.toon | toon_pp

Which will pretty-print TOON data.

## Final Thoughts

I don’t know whether TOON will become widely used. Predicting the success of data formats is a fool’s game. But the cost of supporting it in Perl is low, and the potential usefulness is high enough to make it worth doing.

And fundamentally, this is how CPAN has always worked:

> See a problem. Write a module. Upload it. See if anyone else finds it useful.

So now Perl has a TOON module. And if you already know how to use JSON.pm, you already know how to use it.

That was the goal.

The post Writing a TOON Module for Perl first appeared on Perl Hacks.