Signing CPAN Releases with SigStore
At the most recent Perl Tool Chain Summit (PTS) in Vienna we decided to deprecate Module::Signature. Module::Signature has been around for a long time but it has become increasingly clear that it does not provide the security assurances that it was designed to deliver.
Dist::Zilla::Plugin::SigStore::SignRelease is a new plugin that signs your CPAN release with SigStore before uploading. SigStore uses short-lived, OIDC-issued certificates. You authenticate with Google, GitHub, or Microsoft, and cosign produces a signature bundle. No long-lived keys, no keyserver dance.
How it works
The plugin extends the Dist::Zilla plugin UploadToCPAN. During the dzil release, it:
- Calls cosign sign-blob on your release archive, producing a .sigstore.json bundle file.
- Pulls the certificate out of the bundle and verifies the signature locally before anything leaves your machine.
- Uploads both the tarball and the bundle to PAUSE.
SigStore also add the release and signature information to the Rekor Transparency Log. Rekor is SigStore's public append-only transparency log, and the inclusion proof is bundled into the .sigstore.json. Anyone verifying your release can confirm the signing event was publicly logged at the time it happened.
Using it
Remove the existing uploader from your Dist::Zilla configuration file (dist.ini) and add the plugin to your dist.ini:
[@Filter]
bundle = @Basic
remove = UploadToCPAN ; Do this if you use @Basic
[SigStore::SignRelease]
Non-CPAN Requirements
You'll need cosign in your PATH.
Next Steps
PAUSE doesn't verify SigStore signatures yet, and no CPAN client checks them on install.
This is opt-in transparency: the bundle ships alongside the tarball, and anyone who cares can verify it themselves.
Hopefully more support for SigStore will be developed soon. Until then signing your releases will allow them to be verified:
cosign verify-blob Your-Dist-0.01.tar.gz \
--bundle Your-Dist-0.01.tar.gz.sigstore.json \
--certificate-identity you@example.com \
--certificate-oidc-issuer https://accounts.google.com
It's a first step, towards verifiable releases in the Perl ecosystem. Try it on your next release and let me know how it goes.
Answer
It’s kinda complicated. i3 exposes a scratchpad_state on every node, but it
is not relevant for the node you initially check. The window node of the
application you are targetting. Today we are scratching an itch.
When I saw this question posted on Reddit I first thought: let’s look at the data. My Workspace on Demand daemon has an IPC event debugger. So I fired it up and went looking at the data, my debugger told me:
At the 2026 Perl Toolchain Summit Salve Nilsen and I proposed some ideas that we have been discussing on and off for the past several months for CPANSec, for a CPAN Meta v3 Specification.
Why does the specification need to be extended?
Version 2 of the CPAN Meta Spec (CPAN distributio n metadata specification) is does not allow the addition of new data, except using fields prefixed by "x_".
However, there is a need to include additional metadata about:
- external dependencies (services, libraries, files, or environment variable)
- embedded external libraries, e.g. zlib or bootstrap.
- licensing
- vulnerability reporting
- parent-child relationships (e.g. forked project)
- fixed vulnerabilities in this fork or in embedded libraries
- code and documentation generated through automation or using LLMs
- how and where to report security vulnerabilities
- project funding and sponsorship
- how the project is supported by the maintainers
- enumeration of community health documents, e.g.
SECURITY.md,GOVERNANCE.mdandAI_POLICY.md
This is too much information to embed in existing META.json files, and
some of this metadata exists in alternative formats, for example:
- SBOM (Software Bill of Materials) files
- Attestations
- VEX files
- Common Lifecycle Enumeration (CLE)
- Project Description (DOAP) file
- OSSF Security Insights File
- REUSE
Note that most of this data is not necessary for installing CPAN modules. It exists mainly for documentation and auditing:
- generating SBOMs for an application using its dependencies
- auditing software for security vulnerabilities
- auditing software for license compliance
- displaying the external documentation for a module such as the security policy
Specification
The specification is simple:
All new metadata will be saved in the
CPAN-METAdirectory at the root of the distribution and software repository.All files and subdirectories saved in that directory will have well-known names.
Currently there is
automation-policy.jsonfor the AI and Automation Policy metadaya, that I have worked with Nicolas Rochelemagne. This will be discussed in a separate blog post.The metadata should never be added as "x_" keys to the
META.ymlorMETA.jsonfiles.This metadata may be provided as a separate file from a distribution.
The proposed specification can be found at https://github.com/CPAN-Security/cpan-metadata-v3
To suggest addition or changes, please create an issue or pull request.
Tooling
There are not yet tools for handling the METAv3 specification.
The tools will need to minimise the workload for project maintainers.
Modules should be configurable, testable and installable without any tools that support this specification. However, metadata may be useful for tools that understand them, for example, to ensure external dependencies are met.
Thanks
Thanks to the organisers of the PTS for their hard work and hospitality, and to the sponsors who are keeping the Perl ecosystem growing:
- The Perl and Raku Foundation,
- Grant Street Group,
- Geizhals Preisvergleich,
- Vienna.pm,
- SUSE,
- Trans-Formed Media LLC, - Ctrl O,
- Simplelists,
- Harald Joerg,
- Michele Beltrame (Sigmafin),
- Laurent Boivin.
This year, I was once again honored to be invited to the Perl Toolchain Summit (PTS), held in Vienna. Following productive years in Lisbon and Leipzig, the CPAN Security Group (CPANSec) spent time discussing how to improve the security of the Perl and CPAN ecosystem.
As always, the magic of PTS lies in the hallway discussions and focused groups where we can work on complex problems that are nearly impossible to coordinate over email or GitHub alone.
CPANSec: Maturing our CNA Role
Since we established CPANSec as a CVE Numbering Authority (CNA) in 2025, our focus has shifted toward efficiency and sustainability. We have a small group working on finding and documenting vulnerabilities. We spent time in Vienna discussing:
- Reducing Time to CVE: We discussed how we can reduce the overhead required to issue CVEs without adversely affecting quality.
- Workflow Optimization: We worked on improving the disclosure process to ensure that security reports move from initial contact to a patched release as smoothly as possible.
- The Maintainer Human Element: A major takeaway this year was recognizing the impact security reports have on maintainers. We want to ensure that being "tapped" for a security fix is a supportive experience rather than a stressful burden on our volunteer community.
Crypt::OpenSSL::RSA: Restoring Functionality
Following the work Todd Rinaldo (toddr) and I did last year in Leipzig where we disabled PKCS1 padding to mitigate the Marvin attack, we revisited Crypt::OpenSSL::RSA.
Working together in Vienna, we were able to release a version that restores PKCS1 v1.5 padding specifically for signatures. This padding is no longer allowed for encryption but it remains vital for signature verification an it was great to finally close this loop and fix this issue.
Deep Dives and the "Bus Factor"
One of the highlights of PTS for me was when H. Merijn Brand (Tux) sat down with a small group of us to present metaconfig and Configure.
While the configuration process for Perl isn't something that changes frequently, it is complex. Merijn gave us a great overview of its importance and inner workings. Our hope is to improve the "bus factor" for this critical piece of the Perl build process, ensuring that more people in the community are equipped to maintain the core configuration logic across various platforms.
I also had the chance to sit in on several other great discussions:
- UTF-8: Karl Williamson shared deep technical insights into UTF-8 handling.
- Perl & AI: We discussed the emerging role of AI within the Perl community, both as a tool for development and the challenges it poses.
- Platform Support: Ensuring Perl continues to run flawlessly across the diverse landscape of modern (and ancient) operating systems.
- Paul Evan's update on features and changes coming to Perl.
As in past years, Paul provided both an entertaining and very informative description of things that could be coming to Perl in future releases.
Perl has not been standing still, it continues to grow and evolve, all without breaking backward compatibility.
Retiring the Old: The Deprecation of Module::Signature
Security is as much about moving forward as it is about patching the past. During PTS, we decided it was time to deprecate Module::Signature.
After reaching out to Audrey Tang, the original author, who approved the deprecation, we concluded that the module does not provide the expected security assurances required for a modern supply chain. It is time to retire it and shift our focus toward more robust, modern solutions for module integrity.
Organizers and Sponsors
A massive thank you to the organizers for another flawlessly executed PTS. The hospitality of Vienna and the focus provided by the venue allowed us to get an incredible amount of work done.
Finally, PTS simply wouldn't happen without the generous support of our sponsors. Their commitment to the "invisible" work of PTS is what makes it possible. It helps keep the Perl and CPAN ecosystem healthy, secure, and moving forward for everyone.
The Perl and Raku Foundation,
Grant Street Group,
Geizhals Preisvergleich,
Vienna.pm,
SUSE,
Trans-Formed Media LLC,
Ctrl O,
Simplelists,
Harald Joerg,
Michele Beltrame Sigmafin,
Laurent Boivin
All three of us attended for some more issue triage. We accepted some assorted small fixes and CPAN dual-life module updates. The release blocker list remains empty.
Hai again, after a very productive three weeks I can announce the next major release of Graphics::Toolkit::Color (despite the rather small version number jump). In this post I explain what changed(+12 spaces, +1 method, +7 method args), why it is relevant and how I used LLMs to achieve that.
A lot more space to explore !
The 12 new color spaces are: LinearRGB, CIERGB, Adobe 98 RGB, Apple RGB, Pro Photo RGB, Wide Gamut RGB (Adobe), Rec.709, Rec.2020, Display P3, Display P3 Linear, DCI P3 and DCI P3 Linear. Most of them are or were relevant in (professional) photography, videography or for designers (This is what I meant by go pro). But almost none of these spaces have any CPAN support (until now). With the notable exception of PDL::Transform::Color which does know 7 of them. This makes GTC a "modern" color handling library, especially with GTC 2.3, when another such batch is supposed to land. And if you want to know more about all these spaces, just read our fine docs.
Are you good ?
The new spaces are more or less a variant of good old standard RGB. They have just slightly different white point or gamma value or transfer function. And many of them have a much wider gamut than SRGB. Gamut is just a fancy word meaning range - meaning there are colors you can normally describe in e.g. AdobeRGB, that would not be included (out of range) in standard RGB. When converting, GTC clamps just the values that stick out on an per axis basis. Because this is not ideal, it is planned for 2.4 to have professional gamut mapping (to the visually nearest color). All I can offer for now is the method: is_in_gamut. It allows you to know, if a color will be clamped just by the fact of being read or written by GTC. I also added the argument raw, which prevents such clamping (at your own risk of getting strange values or misbehaving operations). is_in_gamut is also available as standalone sub if you want to check values before creating an object.
Just trust me !
Another issue with these new spaces is that their values are in most cases normalized (0..1) but sometimes come as 16 or even 32 bit integer. Of course you should expect that GTC can read them all. For that scenario I extended the constructor with the option to have named arguments too:
my $color = Graphics::Toolkit::Color->new( color => ['ProPhotoRGB' => 523, 1000, 27 ], range => 2**16, raw => 1);
Output such values works the same way:
$color->values( in => 'WideGamutRGB', range => 2**32, as => 'named_string', raw => 1);
Space Names
Another topic brought up by the new spaces is color space names. Take for instance linear Display P3. You might want to write Display-P3 Linear or linear_display_p3. Now you have such flexibility with all space names. It was always case insensitive but now you can insert or remove the 4 chars: ' ', '_', '-', '.'; Same is true for the equally valid alias name of a color space. That would be 'BT.2020' for the 'Rec.2020' or XYZ for the CIEXYZ space.
Adapt to the current Sun !
Then we got the new method apply to do gamma correction. The math behind it is trivial but it's there for convenience. And as always GTC tries to give you a little extra by being able to apply to each axis a different gamma value, if you want to. The method name is a bit vague on purpose so it can do more in the future.
Space inversion
Last but not least I expanded the possibilities of the invert method. You can now specify which axis to invert, which makes this tool much more powerful and perlish (TIMTOWTDI), since inverting 'hue' (in HSL, OKSL, LCH ...) is the same as computing the complement with the method of the same name.
Talking Machines
Of course nothing is written by an LLM. I tried it once for test rewrites, but I didn't like it and it wasn't even that much of a productivity boost. It's awful if you lose haptic contact with your mental model of the software. But I did use an LLM in two areas which made me a much better and faster developer.
First is planning. Research and evaluating possible features, grouping them, crafting a roadmap, are things that LLMs speed up. If you have concrete questions with answers that you can evaluate then it's not only a speedup but also a form of QA. If I have a well thought out roadmap much earlier in my head I can make much smarter decisions during development.
The second area where LLMs are very helpful are concrete calculations combined with research. Most helpful was being able to throw a conversion matrix at Claude and ask it how many decimals of precision this thing had and which white point it used. And my ProPhotoRGB conversion matrix needed a CAT Bradford adaptation. Claude reminded me of that fact on its own and computed after I asked for it a combined CAT and conversion matrix and told me the white point of it. It already knew it should use only values from the standard, colour-science or Lindbloom. All things I could potentially do too, but that would take me an extra half an hour at least. Of course I wrote the tests for that matrix by hand, but this was the real eye-opener to me.
I am working on a Perl script that creates and uses a temporary file. From my research, I have learned that File::Temp is the appropriate module for such tasks. I have read that module's documentation and believe I have understood it, except for one thing:
Quite at the beginning, it states:
File::Tempcan be used to create and open temporary files in a safe way. [...]The security aspect of temporary file creation is emphasized such that a filehandle and filename are returned together. This helps guarantee that a race condition can not occur where the temporary file is created by another process between checking for the existence of the file and its opening. Additional security levels are provided [...]
So, obviously, the filehandle and the file (and thus, the file's name) are created in a "atomic" fashion.
On the other hand, there is a big warning at the end of the documentation:
For maximum security, endeavour always to avoid ever looking at, touching, or even imputing the existence of the filename. You do not know that that filename is connected to the same file as the handle you have, and attempts to check this can only trigger more race conditions. It's far more secure to use the filehandle alone and dispense with the filename altogether.
What does that mean? To me, it seems that it is a contradiction to what is stated at the beginning. At the beginning, it is explained that the filehandle and the filename are returned together and that the temporary file creation is therefore safe. The warning seems to say the opposite.
Could somebody please give an explanation? Where is my misunderstanding?
[link] [comments]
I was working on some code that creates a reference to a substring call, and then passes that. I assumed this would be a SCALAR ref, since it seems like it would just be a SCALAR with a string in it? Why does this ref return LVALUE?
use strict;
use warnings;
use feature 'say';
my $str = "hello";
my $lv = \substr($str, 1, 2);
say ref($lv); # LVALUE
I did a little dig in perlguts and I do see the LVALUE SVTYPE, so I assume this is intended behavior.
Update AUTHORS
Update .mailmap with correct email address Run Porting/updateAUTHORS.pl
Many years ago I wrote some monitoring tool that collects data using RRD (actually using perl module RRDs. The OS at that time was SLES 10 (rrdtool 1.2012 and rrdtool 1.3007).
Since upgrading to SLES 15 SP6 (perl-rrdtool-1.8.0-150600.1.4.x86_64) I see that my TICK boxes overlap its label.
The bottom of the graphs look like this:

The purpose of the TICKs is just a kind of "color legend" for a bar graph (green/yellow/orange/red) classifying a status not shown.
The part responsible to draw that bottom part looks like this (the @graph_args array from RRDs::graph($fname, @settings, @graph_args);):
...
51 'COMMENT: Last\\t Minimum\\t Average\\t Maximum\\t Sample\\n'
52 'GPRINT:S_A:LAST: %6.2lf\\t\\g'
53 'GPRINT:V_S_I: %6.2lf\\t\\g'
54 'GPRINT:V_S_A: %6.2lf\\t\\g'
55 'GPRINT:V_S_X: %6.2lf\\t\\g'
56 'LINE:S_I'
57 'AREA:S_R#F061::STACK'
58 'LINE1:S_I#F066'
59 'LINE1:S_X#F066'
60 'LINE1:S_A#F06:System \\n'
61 'GPRINT:P_A:LAST: %6.2lf\\t\\g'
62 'GPRINT:V_P_I: %6.2lf\\t\\g'
63 'GPRINT:V_P_A: %6.2lf\\t\\g'
64 'GPRINT:V_P_X: %6.2lf\\t\\g'
65 'LINE:P_I'
66 'AREA:P_R#C601::STACK'
67 'LINE1:P_I#C606'
68 'LINE1:P_X#C606'
69 'LINE1:P_A#C60:Peer \\n'
70 'GPRINT:C_A:LAST: %6.2lf\\t\\g'
71 'GPRINT:V_C_I: %6.2lf\\t\\g'
72 'GPRINT:V_C_A: %6.2lf\\t\\g'
73 'GPRINT:V_C_X: %6.2lf\\t\\g'
74 'LINE:C_I'
75 'AREA:C_R#3F31::STACK'
76 'LINE1:C_I#3F36'
77 'LINE1:C_X#3F36'
78 'LINE1:C_A#3F3:Clock \\n'
79 'GPRINT:A_A:LAST: %6.2lf\\t\\g'
80 'GPRINT:V_A_I: %6.2lf\\t\\g'
81 'GPRINT:V_A_A: %6.2lf\\t\\g'
82 'GPRINT:V_A_X: %6.2lf\\t\\g'
83 'LINE:A_I'
84 'AREA:A_R#06F1::STACK'
85 'LINE1:A_I#06F6'
86 'LINE1:A_X#06F6'
87 'LINE1:A_A#06F:Total \\n'
88 'TICK:S0X#F00C:0.06'
89 'TICK:S1X#F90C:0.06'
90 'TICK:S2X#FF0C:0.06'
91 'TICK:S3X#0C09:0.06'
92 'TICK:S0A#F00C:0.04:Failed'
93 'TICK:S1A#F90C:0.04:Bad'
94 'TICK:S2A#FF0C:0.04:Marginal'
95 'TICK:S3A#0C09:0.04:Good'
96 'TICK:S0I#F00C:0.02'
97 'TICK:S1I#F90C:0.02'
98 'TICK:S2I#FF0C:0.02'
99 'TICK:S3I#0C09:0.02'
(The command is assembled in some complex way, so showing that code would just distract from the actual problem. Therefore I used the Perl debugger to extract the essential settings)
Here is an example how the bottom of the graph looked for the older version of rrdtool (the code calling it had not been changed):

The manual claims the syntax for TICKs still is:
TICK:vname
#rrggbb[aa][:fraction[:legend]]
Package Versions
As the issue may be outside of rrdtool, I'll add more package versions:
> rpm -qa rrdtool librrd libpango\*
libpango-1_0-0-32bit-1.51.1-150600.1.3.x86_64
libpangomm-1_4-1-2.46.3-150600.1.2.x86_64
libpango-1_0-0-1.51.1-150600.1.3.x86_64
rrdtool-1.8.0-150600.1.4.x86_64
I saw that rrdtool-1.9.0-3.fc42.x86_64 in Fedora 42 has the same issue:
$ rpm -qa rrdtool rrd\* pango\* cairo\*
cairo-1.18.2-3.fc42.x86_64
cairo-gobject-1.18.2-3.fc42.x86_64
cairomm-1.14.5-8.fc42.x86_64
cairomm1.16-1.18.0-8.fc42.x86_64
pangomm-2.46.4-3.fc42.x86_64
pangomm2.48-2.56.1-1.fc42.x86_64
pango-1.56.4-2.fc42.x86_64
rrdtool-1.9.0-3.fc42.x86_64
Simplified test case
I managed to create a simplified test case:
First the sample RRD database as dump:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE rrd SYSTEM "https://oss.oetiker.ch/rrdtool/rrdtool.dtd">
<!-- Round Robin Database Dump -->
<rrd>
<version>0003</version>
<step>60</step> <!-- Seconds -->
<lastupdate>1751958726</lastupdate> <!-- 2025-07-08 09:12:06 CEST -->
<ds>
<name> N_SYS </name>
<type> GAUGE </type>
<minimal_heartbeat>105</minimal_heartbeat>
<min>0.0000000000e+00</min>
<max>NaN</max>
<!-- PDP Status -->
<last_ds>1</last_ds>
<value>6.0000000000e+00</value>
<unknown_sec> 0 </unknown_sec>
</ds>
<ds>
<name> N_PEER </name>
<type> GAUGE </type>
<minimal_heartbeat>105</minimal_heartbeat>
<min>0.0000000000e+00</min>
<max>NaN</max>
<!-- PDP Status -->
<last_ds>0.7</last_ds>
<value>4.2000000000e+00</value>
<unknown_sec> 0 </unknown_sec>
</ds>
<ds>
<name> N_ALL </name>
<type> GAUGE </type>
<minimal_heartbeat>105</minimal_heartbeat>
<min>0.0000000000e+00</min>
<max>NaN</max>
<!-- PDP Status -->
<last_ds>0.769230769230769</last_ds>
<value>4.6153846154e+00</value>
<unknown_sec> 0 </unknown_sec>
</ds>
<!-- Round Robin Archives -->
<rra>
<cf>AVERAGE</cf>
<pdp_per_row>1</pdp_per_row> <!-- 60 seconds -->
<params>
<xff>9.0000000000e-01</xff>
</params>
<cdp_prep>
<ds>
<primary_value>1.0000000000e+00</primary_value>
<secondary_value>1.0000000000e+00</secondary_value>
<value>NaN</value>
<unknown_datapoints>0</unknown_datapoints>
</ds>
<ds>
<primary_value>7.0000000000e-01</primary_value>
<secondary_value>6.0000000000e-01</secondary_value>
<value>NaN</value>
<unknown_datapoints>0</unknown_datapoints>
</ds>
<ds>
<primary_value>7.6923076923e-01</primary_value>
<secondary_value>6.9230769231e-01</secondary_value>
<value>NaN</value>
<unknown_datapoints>0</unknown_datapoints>
</ds>
</cdp_prep>
<database>
<!-- 2025-07-08 09:12:00 CEST / 1751958720 --> <row><v>1.0000000000e+00</v><v>8.0000000000e-01</v><v>8.4615384615e-01</v></row>
</database>
</rra>
</rrd>
The commands to create a demo.png:
$ rrdtool restore /tmp/demo.dump /tmp/demo.rrd
$ rrdtool graph /tmp/demo.png 'DEF:S_A=/tmp/demo.rrd:N_SYS:AVERAGE' 'CDEF:S1A=S_A,0.30,GT,S_A,UNKN,IF' 'TICK:S1A#F90C:0.04:Bad'
This results in this image:

The CPAN equivalent in PHP is called Packagist. They have some cool Github integrations that trigger automatic publishing of new modules when a new release/tag is pushed to Github. Do we have anything like that in the Perl ecosystem?
Seems like it wouldn't be too complicated:
- Receive incoming trigger
- Fetch the release tarball from Github
perl Makefile.PLmake testmake dist- Upload
.tar.gzto CPAN
[link] [comments]
bump $SelfLoader::VERSION
SelfLoader: don't mix buffered and unbuffered I/O
For a long time SelfLoader has produced occasional smoke failures on
stdio runs, for example:
https://perl5.test-smoke.org/report/5530055
(aka https://perl.develop-help.com/db/5530055)
https://perl.develop-help.com/dblog/5530141
(aka https://perl.develop-help.com/db/5530141)
https://perl.develop-help.com/dblog/5529979
(aka https://perl.develop-help.com/db/5529979)
I've tried to track this down before, usually looking for interactions
between stdio and perlio in the perlio implementation.
When looking at it again, I saw the SelfLoader code was mixing
buffered I/O (tell()) and unbuffered I/O (sysseek()) which I could see
possibly leaving an unexpected difference in the seek position.
I suspect this *is* causing some bad interaction, if you look at the
strace before and after the change.
before:
write(1, "1..1\n", 5) = 5
lseek(3, 0, SEEK_CUR) = 808
lseek(3, 250, SEEK_SET) = 250
fcntl(3, F_DUPFD_CLOEXEC, 0) = 4
fcntl(4, F_GETFD) = 0x1 (flags FD_CLOEXEC)
fcntl(4, F_GETFL) = 0x8000 (flags O_RDONLY|O_LARGEFILE)
fcntl(4, F_SETFD, FD_CLOEXEC) = 0
fstat(4, {st_mode=S_IFREG|0644, st_size=808, ...}) = 0
lseek(3, 0, SEEK_CUR) = 250
close(3) = 0
close(4) = 0
write(1, "not ok 1 - syntax errors are rep"..., 38) = 38
after:
write(1, "1..1\n", 5) = 5
lseek(3, 0, SEEK_CUR) = 808
lseek(3, 0, SEEK_SET) = 0
read(3, "use SelfLoader;\nprint \"1..1\\n\";\n"..., 4096) = 808
fcntl(3, F_DUPFD_CLOEXEC, 0) = 4
fcntl(4, F_GETFD) = 0x1 (flags FD_CLOEXEC)
fcntl(4, F_GETFL) = 0x8000 (flags O_RDONLY|O_LARGEFILE)
fcntl(4, F_SETFD, FD_CLOEXEC) = 0
fstat(4, {st_mode=S_IFREG|0644, st_size=808, ...}) = 0
lseek(3, 250, SEEK_SET) = 250
close(3) = 0
fcntl(4, F_DUPFD_CLOEXEC, 0) = 3
fcntl(3, F_GETFL) = 0x8000 (flags O_RDONLY|O_LARGEFILE)
fcntl(3, F_SETFD, FD_CLOEXEC) = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=808, ...}) = 0
close(4) = 0
fstat(3, {st_mode=S_IFREG|0644, st_size=808, ...}) = 0
read(3, "\nsub buggy\n{\n +>*;\n}\n\n\n# RT 4"..., 4096) = 558
read(3, "", 4096) = 0
write(1, "ok 1 - syntax errors are reporte"..., 34) = 34
we can see in the original the file offset is at end of file, and that
is copied into the cloned handles, so reads of the cloned
handles (which don't have the buffer of the original handle) read
nothing, and in the case of this particular
test (02SelfLoader-buggy.t) the erroneous code isn't read and no error
is reported.
It's unclear to me what the original change here was intended to fix,
the original change here landed in two steps, first add1a1a3c3d,
discussed in
https://www.nntp.perl.org/group/perl.perl5.porters/2006/12/msg119237.html
and then 054149a8968 discussed in the thread starting at
https://www.nntp.perl.org/group/perl.perl5.porters/2006/12/msg119449.html
Since we're dupping the handle each time (see the strace for the
success case) I'd expect the descriptor to share their file positions
between forked processes anyway.
One of my modules report 99.999% coverage due to a single
// usage which I'm trying to understand. Simplifying
things, consider this program:
sub scalar_rhs
{
my $a;
my $b = 1;
$a // $b
}
sub hash_rhs
{
my $a;
my %b = ( x => 1 );
$a // $b{x}
}
die 'scalar failed' unless &scalar_rhs;
die 'hash failed' unless &hash_rhs;
Running:
perl -MDevel::Cover test.pl
cover -report text
Generates:
line err % l !l&&r !l&&!r expr
----- --- ------ ------ ------ ------ ----
6 *** 33 0 1 0 $a // $b
15 *** 33 0 0 1 $a // $b{'x'}
How come $b{x} is seen as false by Devel::Cover? How can I
make this pass as !l&&r?
cpan/Encode - Update to version 3.24 $Revision: 3.24 $ $Date: 2026/04/29 17:36:34 $ ! Byte/Makefile.PL Pulled: Byte/Makefile.PL: ensure build reproducibility again https://github.com/dankogai/p5-encode/pull/187 ! Changes More fixes to revision subheaders https://github.com/dankogai/p5-encode/commit/eac1089847d488abfe412009ad41d7555921e8d2 Pulled: Restore correct revision subheader for 3.22 https://github.com/dankogai/p5-encode/pull/186
Originally published at Perl Weekly 771
Hi there,
I put the 'Testing in Perl' course on hold for now. Instead of that we are going to explore the use of some of the mocking libraries we saw during the course. In the next session we'll pick one of the Perl modules used for mocking and we'll look for modules that use it. We'll try to understand how it is being used and we'll try to contribute something to at least one of the modules.
For background you can check the OSDC Perl page where we have a listing of modules for 'Code reading'.
You can also watch the recording of the Testing in Perl course. (Registration required but these videos are currently free of charge.)
Enjoy your week!
--
Your editor: Gabor Szabo.
Articles
ANNOUNCE: Perl.Wiki V 1.45 etc
TPRC Announces Post Conference Class
Steven Lembark is presenting: Teaching AI New Tricks: Perly MCP's for Claude.
Who tests the tester? Me !!!
I was just showing the participants of the 'Testing in Perl' course how to write and test a Test::* module. It is nice to see that about the same time Lichtkind wrote an article about the same topic.
Reading CPAN Testers Reports Using AI Agents
A very interesting and useful use of AI.
Discussion
sending matrix messages
Module naming vs CPAN conventions
Perl in Ubuntu 26-04 LTS (vs 24-04 LTS)
Some Perl modules need a dev package to be installed using 'apt'.
The Weekly Challenge
The Weekly Challenge by Mohammad Sajid Anwar will help you step out of your comfort-zone. You can even win prize money of $50 by participating in the weekly challenge. We pick one champion at the end of the month from among all of the contributors during the month, thanks to the sponsor Marc Perry.
The Weekly Challenge - 372
Welcome to a new week with a couple of fun tasks "Rearrange Spaces" and "Largest Substring". If you are new to the weekly challenge then why not join us and have fun every week. For more information, please read the FAQ.
RECAP - The Weekly Challenge - 371
Enjoy a quick recap of last week's contributions by Team PWC dealing with the "Missing Letter" and "Subset Equilibrium" tasks in Perl and Raku. You will find plenty of solutions to keep you busy.
Perl Weekly Challenge 371: Missing Letter
Abigail gives us an efficient solution, O(n), to the missing letter problem by determining the difference between the sum of all letters in a full range of letters and the sum of the letters in the given array. This eliminates unnecessary loops, which is a big advantage over an iterative search.
Perl Weekly Challenge 371: Subset Equilibrium
This solution is a well-organised that defines the NP-complete status of this problem and also implements an efficient bitmasking solution in numerous programming languages (e.g., Perl, Python, AWK, C, etc.) and serves as an excellent reference for other programmers to learn about and use for comparison purposes.
Missing Equilibrium
The Raku Solution implements this approach using combinations to easily create subsets in an idiomatic and very readable way. It also provides an elegant way of compensating for zero-based indices when calculating the sum of the positions in each subset by the calculated subsets.
Subset Equilibrium (Just nod if you can hear me)
Bob addressed the exponential number of complex solutions from the outset and provided a practical implementation based on Algorithm::Combinatorics. The use of a positional-based iterator with zero-index compensation allowed him to effectively handle the off-by-one requirement. In addition to that, Bob supplied practical testing strategies using the Test2::Tools::Compare package.
Perl Weekly Challenge: Week 371
This solution gives a clean, efficient implementation that fulfills both the requirement of a proper subset and utilising 1-based indexing by adding + @combo.elems to the index sum. By making use of combinations from Algorithm::Combinatorics in Perl (and using built-in combinations in Raku), only the necessary subset sizes are generated; the output formatting handles both the non-empty and empty result cases in a very clean manner via a clear ternary expression.
Missing Equilibria
This new solution offers an evaluation of the subset equilibrium problem that is a new and interesting technical perspective and acknowledges the shared views of the two demonstrated examples, while not overlooking those cases where the two may have disagreement along the edges of each set of criteria. The Perl code produced using Math::Prime::Util::forcomb and List::Gather is exceptionally simple but has an artistic style as well.
Perl Weekly Challenge 371
The post describes a simple yet effective way to generate all proper subsets of a set. It uses the subset function of the Algorithm::Combinatorics module to create all proper subsets, and then filters them based on a very concise expression written as a single line. The script contains both a command-line version for easy use and a complete version that includes error handling and has been thoroughly tested.
The Missing Equilibrium
By mathematically restructuring the main condition in the problem, i.e. equality of the total values for elements and their corresponding indices. Additionally, utilising the combination iterators from Algorithm::Combinatorics with subset sizes of 2 through n-1 provides a memory efficient approach to solving this exponential complexity problem while utilizing an optimally prepared array of pre-computed offsets, or at least the offsets for an individual index, gives evidence of thoughtful programming.
My subset is-a missing a letter…
The solution has a nice, clean, multi-language implementation (Raku, Perl, Python, Elixir) and uses indexed pairs of values to keep track of the relationship between the two elements and the index position of the two elements, thus having no off-by-one errors. The solution meets the "proper subset" constraints by filtering out empty sets, singletons, and the full set as well as providing lots of detail in verbose output to help educate the user by giving exact values and sums of values for each match.
Solve the question and balance the subset
By using established neighboring letter pairs, the algorithm determines the steps $a and $b in an efficient manner by deducing which are the next alternately patterned letter combinations in relation to each of the five positions for question marks. The entire algorithm utilises a single conditional expression to account for all five possible question mark positions. Additionally, the use of the modulus operator and the mapping of the patterning logic from the analysis to the assignment of steps makes this implementation both very efficient and very easy to read.
The Weekly Challenge - 371: Missing Value
This solution requires formalization of the detection of repeating patterns through a comprehensive and well-documented approach by treating them as a repeating two-stage model (i.e., d1 = d3 and d2 = d4). Special consideration is given to both constant and alternating sequences. The use of defined-or (i.e., //=) to unify step values, along with the straightforward relationship between each possible question mark position and its simple arithmetic reconstruction, contributes to creating a code that is highly reliable and easy to follow. Additionally, the thorough input validation provided demonstrates significant consideration for actually implementing in a real world scenario.
The Weekly Challenge - 371: Subset Equilibrium
This document includes an excellent comparison of two CPAN modules (Algorithm::Combinatorics and Data::PowerSet) that correctly identifies the combinatorically generated subsets by size (2 to n) are more efficient than generating the power set and filtering out the subsets to produce the same result for this specific task. The code base is very clean and well-structured; it also does a great job of utilising list slicing (@nums[@$subset]) and map to convert from 0-based to 1-based position, while making use of the helper function print_result to ensure that all output for all test cases is consistent and easy to read.
The Weekly Challenge #371
This document offers an innovative and realistic perspective on the subset equilibrium issue by acknowledging there is no "smart" optimisation to the subset equilibrium solution and then using a simple combination-based search on the two to n-1 sizes. The solution was implemented correctly with a summation on 1-indexed values for each of those combinations.
Missing Equilibrium
Roger demonstrates a very clever way of optimizing the solution through the precomputation of one list of differences so that only the check for the sum of the selected differences equals zero must occur, which substantially reduces the amount of computing needed within the combinatorial loops. Roger does a great job of providing a perspective of how to write this in many programming languages (Raku, Kotlin, Crystal, etc.) and provides many practical examples (like sorting the list to create a consistent order for output), thereby making this solution efficient and easily transferable across many different programming languages.
Question the bits
Using bit manipulation, this solution works well to create all combinations of subsets by iterating through all integers from 1 to 2^n-2, thus avoiding the empty and full subsets. The inner loop adds pos+1 because the indices in a array have a base index of 1, while the implementation does not require any third-party modules, making it portable and easy to use for individuals who are already comfortable using bit mask manipulation.
Weekly collections
NICEPERL's lists
Great CPAN modules released last week.
Event reports
Welcome to the Perl Toolchain Summit 2026!
That's how it started
Perl Toolchain Summit in Vienna
A little review by the local orga.
Events
Exploring Perl Modules
May 7, 2026
Boston Perl Mongers virtual monthly
May 12, 2026
The Perl and Raku Conference 2026
June 26-29, 2026, Greenville, SC, USA
You joined the Perl Weekly to get weekly e-mails about the Perl programming language and related topics.
Want to see more? See the archives of all the issues.
Not yet subscribed to the newsletter? Join us free of charge!
(C) Copyright Gabor Szabo
The articles are copyright the respective authors.
Weekly Challenge 372
Each week Mohammad S. Anwar sends out The Weekly Challenge, a chance for all of us to come up with solutions to two weekly tasks. My solutions are written in Python first, and then converted to Perl. Unless otherwise stated, Copilot (and other AI tools) have NOT been used to generate the solution. It's a great way for us all to practice some coding.
Task 1: Rearrange Spaces
Task
You are given a string text of words that are placed among number of spaces.
Write a script to rearrange the spaces so that there is an equal number of spaces between every pair of adjacent words and that number is maximized. If you can’t distribute, place the extra spaces at the end. Finally return the string.
My solution
This is a little more straight forward than last weeks challenge one task. For this task, I take start by separating the words on whitespace, and count the number of spaces. This is stored in the words list (array in Perl) and spaces variable.
def rearrange_space(input_string: str) -> str:
words = input_string.split()
spaces = input_string.count(" ")
To avoid a division by zero error, I handle the case where there is a single word. For this I return the word followed by the required number of spaces. In Python, a string multiple by an integer will repeat the string the specified number of times.
if len(words) == 1:
return words[0] + " " * spaces
If there is more than one word, I calculate the spaces_between_words and spaces_at_end using the divmod function. I return the string with the spaces in the appropriate places.
spaces_between_words, spaces_at_end = divmod(spaces, len(words)-1)
return (" " * spaces_between_words).join(words) + " " * spaces_at_end
The Perl solution uses the same logic. As it does not have a divmod function, I calculate the two values separately. The x operator in Perl is used for repetition. The expression $#words returns one less than the length of the words array.
sub main ($input_string) {
my @words = grep { $_ ne "" } split /\s+/, $input_string;
my $spaces = ( $input_string =~ tr/ / / );
if ( $#words == 0 ) {
say '"' . $words[0] . " " x $spaces . '"';
}
else {
my $spaces_between_words = int( $spaces / $#words );
my $spaces_at_end = $spaces % $#words;
say '"'
. join( " " x $spaces_between_words, @words )
. " " x $spaces_at_end . '"';
}
}
Examples
$ ./ch-1.py " challenge "
"challenge "
$ ./ch-1.py "coding is fun"
"coding is fun"
$ ./ch-1.py "a b c d"
"a b c d "
$ ./ch-1.py " team pwc "
"team pwc"
$ ./ch-1.py " the weekly challenge "
"the weekly challenge "
Task 2: Largest Substring
Task
You are given a string.
Write a script to return the length of the largest substring between two equal characters excluding the two characters. Return -1 if there is no such substring.
My solution
For this task, I start by creating a dict (hash in Perl) called freq with the frequency of each letter. In Python, this is achieved with the Counters function from the collections module.
I then iterate through the dict. If the letter occurs more than once, I calculate the difference between position of the first and last occurrence, minus one. Both Python and Perl have the index and rindex methods to do this. I update the largest value if this is greater than previously found values.
from collections import Counter
def largest_substring(input_string: str) -> int:
freq = Counter(input_string)
largest = -1
for letter, count in freq.items():
if count > 1:
substr = input_string.rindex(letter) - input_string.index(letter) - 1
if substr > largest:
largest = substr
return largest
The Perl solution follows the same logic.
sub main ($input_string) {
my %freq = ();
$freq{$_}++ foreach ( split //, $input_string );
my $largest = -1;
while ( my ( $letter, $count ) = each %freq ) {
if ( $count > 1 ) {
my $substr = rindex( $input_string, $letter ) -
index( $input_string, $letter ) - 1;
$largest = $substr if ( $substr > $largest );
}
}
say $largest;
}
Examples
$ ./ch-2.py aaaaa
3
$ ./ch-2.py abcdeba
5
$ ./ch-2.py abbc
0
$ ./ch-2.py abcaacbc
4
$ ./ch-2.py laptop
2
$ ./ch-2.py abc
-1
Introduction
Perl is often referred to as the "duct tape of the Internet,” due to its versatility and practical applications in various domains. Developed by Larry Wall in 1987, Perl is a high-level programming language that's well-suited for text processing, system administration, web development, and network programming. This article explores its unique features, practical tips, and why you should consider learning Perl today.
Key Features of Perl
Perl is known for several unique features that make it appealing for both beginners and experienced programmers:
- Easy to Learn: Perl’s syntax is flexible and forgiving, which allows new programmers to grasp concepts quickly.
- Powerful Text Processing: With robust regular expressions and string manipulation capabilities, Perl excels in tasks involving text.
- CPAN Repository: The Comprehensive Perl Archive Network (CPAN) hosts thousands of modules that simplify development.
- Cross-Platform: Perl runs on various platforms, including Unix, Linux, Windows, and macOS, increasing its usability across different environments.
- Community Support: Perl has a large, active community that creates resources, tutorials, and forums to help learners.
Practical Tips for Learning Perl
If you're interested in beginning your journey with Perl, consider the following tips:
-
Start with the Basics:
- Learn about variables, scalars, arrays, and hashes.
- Understand control structures like loops and conditionals.
-
Utilize Online Resources:
- Explore Perl training programs or tutorials for structured learning.
- Leverage communities such as PerlMonks and the Perl subreddit for advice and problem-solving.
-
Practice Regularly:
- Write small scripts to automate mundane tasks, such as file manipulation and text parsing.
- Contribute to open-source projects that utilize Perl; this will boost your practical skills.
-
Embrace Debugging:
- Learn to use Perl's built-in debugging tools, such as the
-dflag, to troubleshoot and enhance your scripts.
- Learn to use Perl's built-in debugging tools, such as the
-
Explore CPAN:
- Familiarize yourself with CPAN and incorporate various modules into your projects to save time and effort.
-
Read Perl Books and Documentation:
- Books like "Learning Perl" and "Programming Perl" are solid resources for in-depth understanding.
- Official Perl documentation provides a wealth of information on syntax and modules.
Use Cases for Perl
Understanding where Perl is commonly applied can help you focus your learning efforts:
-
Web Development:
- Perl is used in server-side programming, notably with frameworks like Catalyst and Dancer.
-
System Administration:
- Admins rely on Perl for automation scripts that manage file systems, users, and processes.
-
Bioinformatics:
- Perl’s text processing capabilities make it suitable for analyzing biological data.
-
Network Programming:
- Perl's sockets provide the tools for network communication and building client/server applications.
-
Game Development:
- Although less common, Perl can be utilized in developing games or game-related tools.
Conclusion
Perl may not be the most publicized language, but its strengths in text processing and quick script development make it invaluable in various tech fields.
By investing time in learning Perl, you equip yourself with skills that enhance your programming capabilities and open doors to diverse opportunities. Engage with communities, practice regularly, and consider structured training to master this powerful language.
-
App::ClusterSSH - Cluster administration tool
- Version: 4.19 on 2026-04-28, with 980 votes
- Previous CPAN version: 4.18_09 was released 1 month, 7 days before
- Author: DUNCS
-
App::cpm - a fast CPAN module installer
- Version: v1.0.3 on 2026-05-02, with 178 votes
- Previous CPAN version: v1.0.2 was released the same day
- Author: SKAJI
-
App::Music::ChordPro - A lyrics and chords formatting program
- Version: v6.101.0 on 2026-04-30, with 469 votes
- Previous CPAN version: v6.100.0 was released 8 days before
- Author: JV
-
Cache::FastMmap - Uses an mmap'ed file to act as a shared memory interprocess cache
- Version: 1.61 on 2026-04-30, with 25 votes
- Previous CPAN version: 1.60 was released 10 months, 12 days before
- Author: ROBM
-
CPANSA::DB - the CPAN Security Advisory data as a Perl data structure, mostly for CPAN::Audit
- Version: 20260430.001 on 2026-04-30, with 25 votes
- Previous CPAN version: 20260426.001 was released the same day
- Author: BRIANDFOY
-
DBD::Pg - DBI PostgreSQL interface
- Version: 3.20.2 on 2026-05-02, with 103 votes
- Previous CPAN version: 3.20.1 was released 2 days before
- Author: TURNSTEP
-
Encode - character encodings in Perl
- Version: 3.24 on 2026-04-29, with 65 votes
- Previous CPAN version: 3.23 was released 2 days before
- Author: DANKOGAI
-
MetaCPAN::Client - A comprehensive, DWIM-featured client to the MetaCPAN API
- Version: 2.043000 on 2026-04-29, with 29 votes
- Previous CPAN version: 2.042000 was released 3 days before
- Author: MICKEY
-
Plack - Perl Superglue for Web frameworks and Web Servers (PSGI toolkit)
- Version: 1.0053 on 2026-04-28, with 496 votes
- Previous CPAN version: 1.0052 was released 1 day before
- Author: MIYAGAWA
-
PPI - Parse, Analyze and Manipulate Perl (without perl)
- Version: 1.291 on 2026-04-25, with 64 votes
- Previous CPAN version: 1.290 was released the same day
- Author: MITHALDU
-
SPVM - The SPVM Language
- Version: 0.990168 on 2026-04-27, with 36 votes
- Previous CPAN version: 0.990167 was released 2 days before
- Author: KIMOTO
-
Starman - High-performance preforking PSGI/Plack web server
- Version: 0.4018 on 2026-04-27, with 297 votes
- Previous CPAN version: 0.4017 was released 2 years, 7 months, 13 days before
- Author: MIYAGAWA
-
Test::MockModule - Override subroutines in a module for unit testing
- Version: v0.183.0 on 2026-05-01, with 18 votes
- Previous CPAN version: v0.182.0 was released the same day
- Author: GFRANKS
-
Test::Most - Most commonly needed test functions and features.
- Version: 0.41 on 2026-04-30, with 37 votes
- Previous CPAN version: 0.40 was released the same day
- Author: DCANTRELL
-
Test2::Harness - A new and improved test harness with better Test2 integration.
- Version: 1.000172 on 2026-04-29, with 28 votes
- Previous CPAN version: 1.000171 was released 5 days before
- Author: EXODIST
-
Text::CSV_XS - Comma-Separated Values manipulation routines
- Version: 1.62 on 2026-04-29, with 104 votes
- Previous CPAN version: 1.61 was released 9 months, 2 days before
- Author: HMBRAND
-
YAML::LibYAML - Perl YAML Serialization using XS and libyaml
- Version: v0.906.0 on 2026-04-26, with 60 votes
- Previous CPAN version: v0.906.0 was released the same day
- Author: TINITA

TPRC is proud to announce the post-conference class on Monday, June 29. Steven Lembark is presenting: Teaching AI New Tricks: Perly MCP’s for Claude. The class will be from 9am to 4pm in the Palmetto Room at the Conference Hotel. SHORT DESCRIPTION: MCP definitions are not your mother’s API call. They are natural language descriptions meant for the tool to process and integrate into its own workflow. With proper description, the MCP’s are simple to use and can be daisy-chained to produce quite flexible results. Claude’s different models — Haiku, Sonnet, Opus — offer different options for cost, speed, and depth and require different definitions. In the spirit of the Perl Tester’s Notebook, this class covers a very basic tool then expands it for general use, one step at a time, looking at the necessary changes to Perl and the MCP. The result is a lightweight, fast interface that handles a common task easily and can be incorporated with other tools. For a more complete class description, please visit: https://tprc.us/tprc-2026-gsp/class/ Tickets for the class are $120 and are available at: https://perlfoundation.fcsuite.com/erp/donate/list/event?event_date_id=1002
Last week the Perl Toolchain Summit took place in Vienna (the second time, we also hosted the event in 2010). I participated mostly in the role of local orga, helping the international team finding a venue and a hotel, a place for the social event etc.
For the venue, Michael suggested Hauswirtschaft, where Geizhals sometimes hold meetings and events. This turned out to be an excellent choice. Not only did we get the rooms at a reasonable price (at least when compared to some of the more mainstream hotels), but the rooms and the whole venue were very nice and extremely accommodating: We were served vegetarian lunch each day, could bring our own snacks and drinks (the delicious apple juice Hauswirtschaft provided was not enough for some of the very specific Cola needs of some attendees; luckily there's a supermarket right across) and have a Pizza-and-Chartreuse-Party on Saturday (which I have been told ended quite late). Transporting 16 boxes of pizza on my bike trailer was also fun!
I had little time for technical contributions, as I was busy organizing dinners, answering peoples questions about Vienna and generally having a nice time. But I managed to code up a local development environment for PAUSE based on docker compose. Andreas merged it on Sunday, so if you want to have a local PAUSE (for testing and developing new features), you can now do so via a simple docker compose up pause paused. Detailed instructions can be found here and I would appreciate it if people give it a try and report back any problems they have. Having a working local dev setup is IMO crucial for getting meaningful contributions.
Together with Michael I was interviewed by Philippe for his podcast. We talked about the history and future of Vienna.pm, Geizhals, Koha and some other topics. The interview and the whole event motivated me to keep Vienna.pm going and maybe try to host a Perl-and-related-tech-events (again) in the future. We'll see...
To summarize, I really enjoyed the event, hanging around with Perl people & talking about basically everything. It was a nice reminder that this community is exactly my kind of crazy! Looking forward to the next PTS or similar event!
And no post about the PTS is complete without thanking the sponsors (is it ok to thank yourself, as I'm involved with two of the sponsors (Vienna.pm and HSK3)? I say it is...)
Another explosive weekly challenge. And by explosive I mean exponential complexity. Let's see what we've got. First I'll adjust the background music for equilibrium ... Comfortably Numb
Relax, I'll Need Some Information First
You are given an array of numbers. Write a script to find all subsets where the sum of elements equals the sum of their indices.
# Example 1 Input: @nums = (2, 1, 4, 3)
#. Output: (2, 1), (1, 4), (4, 3), (2, 3)
# Subset 1: (2, 1) Values: 2 + 1 = 3 Positions: 1 + 2 = 3
# Subset 2: (1, 4) Values: 1 + 4 = 5 Positions: 2 + 3 = 5
# Subset 3: (4, 3) Values: 4 + 3 = 7 Positions: 3 + 4 = 7
# Subset 4: (2, 3) Values: 2 + 3 = 5 Positions: 1 + 4 = 5
#
# Example 2 Input: @nums = (3, 0, 3, 0)
# Output: (3, 0), (3, 0, 3)
# Subset 1: (3, 0) Values: 3 + 0 = 3 Positions: 1 + 2 = 3
# Subset 2: (3, 0, 3) Values: 3 + 0 + 3 = 6 Positions: 1 + 2 + 3 = 6
#
# Example 3 Input: @nums = (5, 1, 1, 1)
# Output: (5, 1, 1)
# Subset 1: (5, 1, 1) Values: 5 + 1 + 1 = 7 Positions: 1 + 2 + 4 = 7
#
# Example 4 Input: @nums = (3, -1, 4, 2)
# Output: (3, 2), (3, -1, 4)
# Subset 1: (3, 2) Values: 3 + 2 = 5 Positions: 1 + 4 = 5
# Subset 2: (3, -1, 4) Values: 3 + (-1) + 4 = 6 Positions: 1 + 2 + 3 = 6
#
# Example 5 Input: @nums = (10, 20, 30, 40)
# Output: ()
Just the Basic Facts, Can You Show Me Where It Hurts?
Perusing the examples, I see that there are a couple of unstated requirements. From example 1, the improper subset (1,2,3,4) should work, but it's not in the given output, so apparently that should be excluded. And in example two, the subset (3) should work, but it's not in the output, so apparently subsets have to have at least two members.
I don't see any way of doing this other than generating all possible subsets and checking if they work. My approach is going to be to number the positions, and generate all possible subsets of the positions. Extracting members from the given list will be done with a hash slice. There's going to be a little annoyance that the task numbers positions starting from 1, while Perl array indexing is based from 0.
For a set of n members, there are 2n-1 possible subsets. I know that a way to generate subsets is to count from 1 to 2n-1 and check which bits are 1s in the binary representation. I know how to generate all possible subsets, but do I want to? Because I also know that there are CPAN modules that do this, and I have Algorithm::Combinatorics hanging around from previous problems, with its convenient subsets function, so let's use that.
I Do Believe It's Working, Good
sub sseq(@nums)
{
use Algorithm::Combinatorics qw/subsets/;
use List::Util qw/sum0/;
my @result;
my $s = subsets( [0 .. $#nums] );
while ( my $position = $s->next() )
{
my $size = scalar(@$position);
# Only proper subsets of size 2 or more
next if $size == scalar(@nums) || $size < 2;
# Subsets are indexed at zero, but positions at 1, so compensate in sum
my $sumPosition = $size + sum0 @$position;
my $sumNumbers = sum0 @nums[ @$position ];
if ( $sumNumbers == $sumPosition )
{
push @result, [@nums[@$position]]
}
}
return \@result;
}
Your Lips Move, But I Can't Hear What You're Saying
Explanatory notes:
use Algorithm::Combinatorics qw/subsets/-- This function creates an iterator to generate consecutive subsets from a given list of members. We're going to pass it a list of positions, from 0 to the last index of@nums.use List::Util qw/sum0/--sum0handles empty lists by returning zero instead of an error, assumdoes. That shouldn't come up in this particular function, but I generally usesum0as my default, because I invariably get a warning fromsumabout an empty array and end up changing it anyway.my $size = scalar(@$position)-- the size of thepositionarray comes up repeatedly, so let's get a handle on it.my @sumPosition = $size + sum0(...)-- The sum we're looking for is based on using positions numbered from 1, but we have them numbered from 0. We need to add 1 to each of the positions, which is the same as adding the number of positions.my @sumNumbers = sum0 @nums[ @$position ]-- Here's why I wanted positions to be indexed from zero: so that they could be used in a hash slice to extract the corresponding numbers.push @result, [@nums[@$position]]-- the result of this function is going to be a reference to an array of arrays (it's array reference turtles all the way down). Once again, the hash slice, now encased in[]to create an array reference.
That'll Keep You Going Through the Show
This function answers the question, but it leaves two things undone.
First, the order of the lists is unspecified. There's no apparent reason for the order in the examples, and I quickly found out that the subsets iterator doesn't return subsets in the same order as the examples.
To create unit tests, I want to check that arrays contain the same members, but order doesn't matter. In the Test2 suite, this can be accomplished by putting the expected elements into a bag data structure.
sub runTest
{
use Test2::V0;
use Test2::Tools::Compare qw/bag item/;
my $check;
$check = bag { item [1,4]; item [2,1]; item [2,3]; item [4,3]; end(); };
is( sseq(2,1,4,3), $check, "Example 1");
[...]
Our bags are checked, so ...
Come On, It's Time To Go
Second, to display the output, we have to unwrap the array references and render them as formatted strings.
say join ", ", map { "(" . join(", ", $_->@*) . ")" } sseq(@ARGV)->@*;
What's going on here?
-
sseq(@ARGV)->@*-- Call our function, using the command line arguments, and dereference it (which yields an array of array references). -
map { ... }-- Transform each of the array references -
"(" . join(", ", $_->@*) . ")"--$_is a reference to an array, so$_->@*is the list of elements in the array. Make it a comma-separated string, encased in parentheses. -
say join ", ", ...-- output each of those parenthesized strings, separated by commas
I Have Become Comfortably Numb
Equilibrium achieved.
I am currently trying to install this module with :
perl -MCPAN -e shell install XML::Parser::Style::Tree
It did not install it, but it installed cpan. Inside cpan, I issued again:
install XML::Parser::Style::Tree
At beginning, the process was going well. Then suddenly I receive these error messages:
Can't locate File/ShareDir/Install.pm in @INC (@INC contains: ./inc /etc/perl /usr/local/lib/perl/5.10.1 /usr/local/share/perl/5.10.1 /usr/lib/perl5 /usr/share/perl5 /usr/lib/perl/5.10 /usr/share/perl/5.10 /usr/local/lib/site_perl .) at Makefile.PL line 7.
BEGIN failed--compilation aborted at Makefile.PL line 7.
Warning: No success on command[/usr/bin/perl Makefile.PL INSTALLDIRS=site]
Warning (usually harmless): 'YAML' not installed, will not store persistent state
TODDR/XML-Parser-2.58.tar.gz
/usr/bin/perl Makefile.PL INSTALLDIRS=site -- NOT OK
Running make test
Make had some problems, won't test
Running make install
Make had some problems, won't install
Could not read '/home/leopoldo/.cpan/build/XML-Parser-2.58-jE9k8s/META.yml'. Falling back to other methods to determine prerequisites
Failed during this command:
TODDR/XML-Parser-2.58.tar.gz : writemakefile NO '/usr/bin/perl Makefile.PL INSTALLDIRS=site' returned status 512
So, I am open to suggestions on how to solve it.
On this site and elsewhere, there are quite a few questions about how to handle errors when printing to a pipe in Perl. But none of the answers, solutions and explanations I have seen so far are applicable to my specific case. Please consider the following situation:
Under Linux (Debian 13), I have a folder /path where only the root user has write permissions. Further, I have a Perl script that includes the following snippet:
if (!(open($fh_Pipe, '|-:utf8', '/usr/bin/weasyprint',
'-',
'/path/test.pdf')))
{
# Perform error action here.
}
if (!(print($fh_Pipe 'Foo')))
{
# Perform error action here.
}
If I execute that script as root, everything works: A PDF file /path/test.pdf is created that contains the text "Foo".
But if I execute that script as another user, it does not behave as expected: Indeed, the PDF file /path/test.pdf is not created, and Perl outputs a warning regarding the missing write permission. But to my surprise, none of the error action blocks executes.
That is a problem because I'd like to detect all errors with writing to the pipe from within the script, of course with reasonable effort, and the behavior I have observed seems to contradict the documentation. From the documentation for print:
Prints a string or a list of strings. Returns true if successful. [...]
Although it is formally not correct, I interpret this as it would say "... and false if not successful ..." in addition. But then it contradicts what I observe.
So my question is:
Is there a general method to detect all errors that may occur when printing to a pipe, including situations where the user that executes the respective script simply does not have write permission in the respective directory?
And why does Perl not behave as documented?
[ Side note #1: To be clear, I'm not interested in which error exactly has occurred. I just would like to know whether or not the print has succeeded, completely regardless of the kind of the possible error. ]
[ Side note #2: The page linked above later on explains that a SIGPIPE signal will be raised if we try to print to a closed pipe or socket. However, I didn't try to implement an error handling for print based on signals, because that would be a high effort for a very simple thing, and because I am not sure if a missing write permission would effect the same as a closed pipe. ]
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
We know this will disappoint many of you, but it was not feasible to keep it running anymore with a reasonable quality.
Several factors led to the decision:
Communication has moved on. 25 years ago it was important to provide a consistent way for users to contact CPAN module authors. Today people use rt.cpan.org, forums, and other channels..
Over 99% of mail was spam. Even two layers of spam filtering let some through, hurting deliverability for our other outbound mail.
Modern email requires ARC, DKIM, DMARC, and SPF. Providing these correctly on a forwarding service is hard, so legitimate mail often fails to be delivered.
We approached several email providers, but were unable to find one to host the service.
The number of active users was small compared to the number of CPAN authors. During a recent extended, unannounced outage, we received only two inquiries.
We will work with the cpan-security team so they keep a way to reach authors.
Bounces will soon report that the MX cpan.org-email-is-gone-use-http-rt.cpan.org cannot be found as a breadcrumb pointing toward alternatives.
cpan.org email was one of the first services we ran for the Perl community; over the years we've added others, some for the wider internet. The perl.org mailing lists have been running for 28 years and continue to operate. We also still help run PAUSE and the CPAN archive system. rt.cpan.org remains available, thanks to The Perl Foundation and Request Tracker.
For most of its 25 years, cpan.org email was widely used, and running it was a pleasure. The last seven or eight became mostly spam fights and deliverability work — a big part of why we're stopping now.
Please don't ask us to reconsider. We recognize this change will affect some users more than others. We wrestled with this for a long time and are confident that it is the right call.
Sincerely,
The perl noc volunteers
TL;DR
This is a rant, a developer war story of how to use docker to work around a problem that shouldn’t exist in the first place. Configure your PHP project with a different, lower version, of PHP than you are using yourself. Read how I spent an evening fighting PHP.
The bare minimum
Can someone in PHP land please tell me why composer is unable to create a project on PHP version 8.2 when I’m running PHP version 8.4? I’m creating a PHP app that needs to run on bookworm, aka, Debian 12, which ships PHP 8.2.

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.
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
version - Structured version objects
- Version: 0.9934 on 2026-04-12, with 22 votes
- Previous CPAN version: 0.9933 was released 1 year, 7 months, 17 days before
- Author: LEONT
Zsh has regexp-replace, you don’t need sed:
Also, instead of hard coding values, use zstyle:

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
fieldrefalias memory leak- https://github.com/Perl/perl5/pull/24254
- 2 = Improved
fieldperformance- 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
-
The IO::Async::Loop module
-
The IO::Async::Timer::Periodic module
-
The Math::Prime::XS module
-
The MIDI::RtMidi::FFI::Device module
-
The Music::CreatingRhythms module
-
The Music::SimpleDrumMachine WIP module based on this logic
-
The cross-platform fluidsynth application
-
My original music: https://www.youtube.com/@GeneBoggs
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
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:
- Get commits for the month
- Skip if there are none
- Generate a summary
- 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.
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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
-
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