<?xml version="1.0" encoding="us-ascii"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Perl Advent Calendar 2025</title><id>https://perladvent.org/2025/</id><link href="https://perladvent.org/2025/atom.xml" rel="self"/><updated>2026-02-18T19:25:53Z</updated><author><name>PerlAdvent Org</name></author><generator uri="https://metacpan.org/pod/XML::Atom::SimpleFeed" version="0.905">XML::Atom::SimpleFeed</generator><entry><title type="html">Pecan&#38;#39;s Tale: Migrating a terminal application from Term::ReadLine to Tickit</title><link href="https://perladvent.org/2025/2025-12-24.html"/><id>https://perladvent.org/2025/2025-12-24.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;Pecan: &#38;quot;I&#38;#39;ve got a terminal application[0] based on a mature audio processing library[1]. The terminal interface uses Gnu ReadLine, a widely used C library accessed via the CPAN module Term::ReadLine::Gnu. Nama was previously covered in the Perl Advent calendar of 2023[2], which began a great deal of head-scratching.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;The problem is I want a facility to process keystrokes immediately, for example hotkeys to control volume or adjust playback position. Not only a command line with parameters, we have that.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;But I have an answer, a second-generation terminal library that represents each cluster of keyboard scan codes as an object. The are a lot of questions you might want to ask this object, and Tickit provides methods for anything you might want to know. Being an OO design, you can use subclassing to alter any of the default behaviors. Another example besides volume control would be overriding the cursor keys to simulate a pager that stops scrolling at the top line of the supplied text.&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;Step-1:-Create-widget-hierarchy&#34;&gt;Step 1: Create widget hierarchy&lt;/h3&gt;

&lt;p&gt;&#38;quot;I use a scrollbox widget to display terminal output, with a line reserved at the bottom for a text entry widget. Simple, right?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;The widget hierarchy looks like this:&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;word&#34;&gt;Tickit::Async::Loop&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;Tickit::Term&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;an&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;object&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;exposing&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;the&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;underlying&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;TermKey&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;library&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;Tickit::Vbox&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;root&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;widget&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;displays&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;its&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;child&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;widgets&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;as&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;column&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;Tickit::Scrollbox&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;scrollable&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;eliminates&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;the&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;need&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;shelling&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;out&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;to&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;less&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;label&#34;&gt;TODO:&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;set&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;a&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;stop&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;scrolling&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;back&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;only&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;the&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;last&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;N&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;lines&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;of&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;output&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;Tickit::VBox&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;Tickit::Widget::Text::Static&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;one&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;line&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;of&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;display&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;Tickit::Widget::Text::Static&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;...&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;Tickit::Widget::Text::Entry&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;anchored&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;to&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;bottom&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;screen&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;line&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;prompt&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;and&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;command&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;entry&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;Step-2:-Redirect-file-handles&#34;&gt;Step 2: Redirect file handles&lt;/h3&gt;

&lt;p&gt;&#38;quot;I&#38;#39;m facing hours of work and tedious bugfixing to convert every routine printing to STDOUT or STDERR to generate a static text object and add it to the scroller widget. There has to be a better way.&#38;quot;&lt;/p&gt;

&lt;p&gt;In perl, on CPAN, there usually is! Here, it&#38;#39;s Tie::Simple[3].&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;our&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$old_output_fh&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;redirect_stdout&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;open&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;FH&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#38;gt;&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;/dev/null&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;die&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;		# TODO: Replace the bareword filehandle FH with a scalar, $fh.&lt;br /&gt;&#38;nbsp;&#38;nbsp;# That&#39;s recommended practice, right?&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;		# On second thought, no. A bareword filehandle is necessary&lt;br /&gt;&#38;nbsp;&#38;nbsp;# because Tie:Simple alters the associated typeglob&lt;br /&gt;&#38;nbsp;&#38;nbsp;# object *FH. That object would not be available in a&lt;br /&gt;&#38;nbsp;&#38;nbsp;# reference $fh, which points directly to the associated memory&lt;br /&gt;&#38;nbsp;&#38;nbsp;# location.&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;FH&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;autoflush&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$old_output_fh&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;select&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;FH&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;tie&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;*FH&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;Tie::Simple&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;WRITE&lt;/span&gt;      &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;  &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# stub methods are needed&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;PRINT&lt;/span&gt;      &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$text&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;magic&#34;&gt;$_&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;];&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;print_to_terminal&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$text&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;PRINTF&lt;/span&gt;     &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;  &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;READ&lt;/span&gt;       &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;  &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;READLINE&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;  &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;GETC&lt;/span&gt;       &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;  &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;CLOSE&lt;/span&gt;      &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;  &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;print_to_terminal&lt;/span&gt; &lt;span class=&#34;prototype&#34;&gt;($text)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$vbox&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;add&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Tickit::Widget::Static&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;text&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$text&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;));&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$scrollbox&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;scroll_to&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;exp&#34;&gt;1e5&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# move cursor to bottom, TODO: there must be a neater way&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# called on program exit&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;restore_stdout&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;select&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$old_output_fh&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;close&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;FH&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;I also need to redirect warnings. Here&#38;#39;s the new code:&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;BEGIN&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$SIG&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;__WARN__&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;cast&#34;&gt;\&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;&#38;amp;filter_print_to_terminal&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;filter_print_to_terminal&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;print_to_terminal&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;magic&#34;&gt;@_&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;unless&lt;/span&gt; &lt;span class=&#34;magic&#34;&gt;$_&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=~&lt;/span&gt; &lt;span class=&#34;match&#34;&gt;/ScrollBox/&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# skip spurious warnings related to the ScrollBox object&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;Our fix doesn&#38;#39;t break the following line, which a naive search-and-replace for &#38;#39;print&#38;#39; or &#38;#39;say&#38;#39; would miss.&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# produce a stacktrace on exception when any logging filters enabled&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;SIG&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;__DIE__&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Carp::confess&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;magic&#34;&gt;@_&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$category_tags&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;Our-intrepid-elf-hero-and-his-mentor&#34;&gt;Our intrepid elf-hero and his mentor&lt;/h3&gt;

&lt;p&gt;Hunched over the terminal, in his usual coding posture with keyboard on lap, Pecan the elf knits his brows. Pages of printouts cover his desk and most of the floor around him.&lt;/p&gt;

&lt;p&gt;Lybinthia, an elf with decades of CS and IT experience, is watching him struggle. She knows Pecan is not the sharpest knife in the drawer. He makes up for his lack of effortless facility in coding with persistence and a willingness to learn.&lt;/p&gt;

&lt;p&gt;After decades of plodding progress and countless dead ends, he&#38;#39;s learned to read (at least browse) the documentation of the libraries he uses as fully as possible, knowing that those before him have crystallized their own experience of blood, sweat and tears into the man pages generally supplied with each library distribution installed from CPAN, the Comprehensive Perl Archive Network.&lt;/p&gt;

&lt;p&gt;Elves have lots of work to do. Pecan&#38;#39;s section of the North Pole Coding Collective is largely a perl shop. They&#38;#39;ve ignored the fads and marketing, sticking to a language with excellent features, backward compatibility, and a culture of software testing that ensures libraries substantially accomplish what they promise on the package wrapper. (The package metaphor tickles Pecan, because if elves love anything, it is presents with cheerful wrappings and ribbons.)&lt;/p&gt;

&lt;p&gt;But in this instance he&#38;#39;s been disappointed, three months of hard work paddling upstream against a recalcitrant C-library all for nothing.&lt;/p&gt;

&lt;p&gt;Our intrepid reporter, the Ghost of Perl Programming&#38;#39;s Future, listens in on the conversation.&lt;/p&gt;

&lt;p&gt;&#38;quot;What&#38;#39;s your hangup?&#38;quot; Lybinthia (who grew up in the 60s) asks him.&lt;/p&gt;

&lt;p&gt;&#38;quot;I&#38;#39;m an elf, not a cat,&#38;quot; Pecan sniffs self-righteously, &#38;quot;and therefore never much of a mouser. I work best at the terminal, sharing several consoles with a multiplexing application, usually screen or tmux.&#38;quot;&lt;/p&gt;

&lt;p&gt;Lybinthia senses a big picture introduction, unfortunately, the only way Pecan talks.&lt;/p&gt;

&lt;p&gt;&#38;quot;Besides satisfying my own itches,&#38;quot; he says, stretching and scratching his armpit thoughtfully, &#38;quot;my applications are usable by all elves, including those whose accessibility needs require use of a braille display or screen reader, as well as old-fashioned console cowboys.&#38;quot;&lt;/p&gt;

&lt;p&gt;The latter group is easily recognizable at the North Pole by their Stetson hats, western boots and large belt buckles, a significant contingent of stylish elves that Pecan wants to serve.&lt;/p&gt;

&lt;p&gt;Lybinthia: &#38;quot;So, tell me about your program.&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;The-program-and-its-command-line-includes-shameless-elf-promotion&#34;&gt;The program and its command line (includes shameless elf promotion)&lt;/h3&gt;

&lt;p&gt;Pecan introduces it with his usual elevator spiel. (Who knew there were elevators at the North Pole?)&lt;/p&gt;

&lt;p&gt;&#38;quot;The program&#38;#39;s a multitrack audio editing application providing functions for recording, editing, mixing and mastering sound files via a command prompt, In other words, it&#38;#39;s modeled on the unix command shell. Despite its small code footprint, it provides a lot of what you&#38;#39;d find in other DAW software such as Audacity or ProTools. I also think it&#38;#39;s easier to use, as you never need to remember which command is under which menu.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;The prompt tells the user key information: the currently selected project, bus, track, effect plugin, etc. With most commands, you don&#38;#39;t need to specify the object to modify, making inputs concise.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Like my hero programmers who created unix, I&#38;#39;m trying to get the most results from the fewest keystrokes, while keeping it all memorable. When memory fails, there&#38;#39;s a help facility with listings by category, command and text search. I really am proud of it.&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;Grammar&#34;&gt;Grammar&lt;/h3&gt;

&lt;p&gt;Lybinthia: &#38;quot;How do you process the commands?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;While there are more modern libraries nowadays, I use Parse::RecDescent[4] to generate the parser for Nama&#38;#39;s command grammar.&lt;/p&gt;

&lt;p&gt;&#38;quot;The parser takes a command line, identifies the command name (generally the first token in the command line) and fires off the corresponding subroutine with whatever parameters are provided.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;And if the command has a syntax error?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;It falls through the grammar, printing the unparseable command with a warning and (generally) no other effects.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;What about features like command history and tab completion?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;They come for free with ReadLine.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;So all is good then, isn&#38;#39;t it?&#38;quot; she asks. &#38;quot;What is the exact problem you&#38;#39;re having with ReadLine?&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;No-direction-HOME&#34;&gt;No direction HOME&lt;/h3&gt;

&lt;p&gt;&#38;quot;The problem is I want a facility to process keystrokes immediately, for example using cursor keys to control volume or adjust playback position, not always a command name with parameters terminated by newline.&lt;/p&gt;

&lt;p&gt;&#38;quot;I&#38;#39;ve been trying to do this for months, now feel like I&#38;#39;m ready to slit my wrists, both of them, with a rusty knife.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;One wrist would probably be enough,&#38;quot; notes Lybinthia with a grimace. &#38;quot;Doesn&#38;#39;t ReadLine provide all this? I mean, any terminal based music player available for linux has keys for these functions.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;That&#38;#39;s the rub. While all that works well enough from C, in perl we use Term::ReadLine::Gnu to access the ReadLine library, and this glue code doesn&#38;#39;t support all the bells and whistles available through straight C.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Didn&#38;#39;t you know that from the docs?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;No, the main reference is the docs for the C library, and of course, they have no idea what ReadLine-via-perl can or cannot do.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;It took me quite a while to get as far as I have. Especially setting up an event loop so I can have a background process like playing an audio clip while executing and processing commands in the foreground, firing off subroutines when certain events occur, like reaching the end of a clip.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;I had to cargo cult my way through it. It took forever, now I have to migrate to a new library. At least I have a library to migrate to.&#38;quot; Pecan pauses to touch his scalp.&lt;/p&gt;

&lt;p&gt;Lybinthia: &#38;quot;It&#38;#39;s a good thing elves live a long time.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Well, my supervisor doesn&#38;#39;t like it. He&#38;#39;s worried about my graying hair and the sores I developed from scratching my head.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;So where are we now?&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;Thats-the-Tickit&#34;&gt;That&#38;#39;s the Tickit&lt;/h3&gt;

&lt;p&gt;&#38;quot;As I was saying, I found a solution, now just trying to migrate over. The new shiny is Tickit, a terminal handling library by Paul Evans. Paul is a prolific perl core contributor, maybe even luminary. It&#38;#39;s a high level approach that builds on his previous effort in this area, a C library and perl wrapper called TermKey. It&#38;#39;s easy to intercept any keystroke and fire off whatever subroutine you want.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Isn&#38;#39;t Tickit the library Paul used as a testbed while developing Object::Pad?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Yes, the same.&#38;quot; Tickit integrates well with IO::Async, also by Paul Evans, with adapters to several other event loops.&#38;quot;&lt;/p&gt;

&lt;p&gt;Object::Pad is great, by the way, and as late-generation take on perl OO, it&#38;#39;s quite a bit easier to use and to read. Now finding its way, step by step, into the perl core. You can look at the new class syntax.[5]&lt;/p&gt;

&lt;p&gt;&#38;quot;If Tickit is a high level API using widgets, it should be easier than ReadLine was.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Yes, that&#38;#39;s true, but there is a bit of a mismatch. For ReadLine all I need to do is print lines to STDOUT, however for outputs longer than the number of screen lines, we currently call out to a pager application, in this case &#38;#39;less.&#38;#39;&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;With Tickit, I use a scrollbox widget to mimic the way text scrolls up a terminal.&lt;/p&gt;

&lt;p&gt;&#38;quot;The problem is that I have various subroutines that print to STDOUT. I feel daunted about having to change them one-by-one, having suffered through previous refactoring efforts, such as converting Nama&#38;#39;s data structures from hashes to objects. Without AI help (which I&#38;#39;d mistrust anyway) I&#38;#39;m facing hours of work and tedious bugfixing to convert some 70 routines printing to STDOUT or STDERR to generate a static text object and add it to the scroller widget. I&#38;#39;ve engaged in the whack-a-mole before, and don&#38;#39;t like it. I always end up missing something.&#38;quot;&lt;/p&gt;

&lt;p&gt;Here was where Lybinthia gave Pecan some vital advice. In perl (and probably any other language worth its salt) you can redirect any file descriptor including STDOUT and STDERR.&lt;/p&gt;

&lt;p&gt;After a couple of false starts, he ended up using Tie::Simple to trigger a subroutine on each print action.&lt;/p&gt;

&lt;p&gt;&#38;quot;Also, I&#38;#39;m finding I need to change over event loops because of some compatibility issues with AnyEvent.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Well, apply yourself, Pecan, I&#38;#39;m sure you&#38;#39;ll find a suitable hack, you always do -- usually in the shower. We hear you talking to yourself there, a dialogue of two parts in squeaky and basso profundo voices.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Yes&#38;quot; said Pecan, turning a beet red that matched his cap perfectly, &#38;quot;that&#38;#39;s what works best for me.&#38;quot;&lt;/p&gt;

&lt;p&gt;Within a short time, he&#38;#39;d whipped up a wrapper, mapping the AnyEvent AE::timer function calls to IO::Async event loop that better integrates with Tickit. Depending on the parameters, it calls either IO::Async::Timer::Countdown ( for a one shot timer) or IO::Async::Timer::Periodic (for polling.)&lt;/p&gt;

&lt;p&gt;Converting the code was a simple search-and-replace. For example:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt;       &lt;span class=&#34;symbol&#34;&gt;$project&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;events&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;poll_engine&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;AE::timer&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;float&#34;&gt;0.5&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;cast&#34;&gt;\&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;&#38;amp;poll_progress&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt;       &lt;span class=&#34;symbol&#34;&gt;$project&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;events&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;poll_engine&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;timer&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;float&#34;&gt;0.5&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;cast&#34;&gt;\&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;&#38;amp;poll_progress&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And the wrapper code looks like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;timer&lt;/span&gt; &lt;span class=&#34;prototype&#34;&gt;($delay, $interval, $coderef )&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$timer&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$interval&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;){&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$timer&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;IO::Async::Timer::Countdown&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;delay&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$delay&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;on_expire&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$coderef&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$timer&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;IO::Async::Timer::Periodic&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;interval&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$interval&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;on_tick&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$coderef&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$timer&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;start&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$text&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;loop&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;add&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$timer&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$timer&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When the basic coding was done, and both had clocked out for the day, the two elves poured themselves a shot of brandy, toasting Larry Wall for a language of manipulexity and whipuptude, bywords he&#38;#39;d coined for Perl&#38;#39;s facility at making easy things very easy and hard things possible. Which other language can boast such a combination!&lt;/p&gt;

&lt;ol start=&#34;0&#34;&gt;
&lt;li&gt;&lt;a href=&#34;https://metacpan.org/dist/Audio-Nama&#34;&gt;Nama multitrack recorder and DAW&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://nosignal.fi/ecasound/index.php&#34;&gt;Ecasound, a software package designed for multitrack audio processing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://perladvent.org/2023/2023-12-18.html&#34;&gt;Trimming sound files with Audio::Nama&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://metacpan.org/dist/Tie-Simple&#34;&gt;Tie::Simple&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://metacpan.org/dist/Parse-RecDescent&#34;&gt;Parse::RecDescent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://perldoc.perl.org/perlclass&#34;&gt;perlclass - Perl core class syntax&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;/div&gt;</summary><updated>2025-12-24T00:00:00Z</updated><category term="Perl"/><author><name>Joel Roth</name></author></entry><entry><title>A Quick Response</title><link href="https://perladvent.org/2025/2025-12-23.html"/><id>https://perladvent.org/2025/2025-12-23.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;Cautiocus smiled as he checked over the Workshop Safety Questionnaire one last time. In all his years as Assistant Head of the Elf &#38;amp; Safety Directorate at the North Pole&#38;#39;s busy Toy Workshops, no-one had so much as caught their thumb with a hammer in the run-up to Christmas, despite the huge demand for beautifully fashioned Jack-in-the-Boxes, spinning tops, walkable dogs, dolls, yo-yos and all sorts of toys. Cautiocus&#38;#39; attention to detail was considered impressive even for an elf.&lt;/p&gt;

&lt;p&gt;He flipped the intercom switch on his desk. &#38;quot;Fry, I&#38;#39;m happy with the survey. Let&#38;#39;s get it out!&#38;quot; The Workshop Safety Questionnaire was a vital part of Cautiocus&#38;#39; carefully layered procedures to make sure no elf had to spend the festive season nursing a sore thumb due to an overlooked unsafe working practice.&lt;/p&gt;

&lt;p&gt;The elf paused again, thinking. The survey had a long URL, and the workshops&#38;#39; Lead Joinery Elves were very busy. He had already arranged free tickets for all respondents to a Yuletide performance with Bilbo Frostby, the North Pole&#38;#39;s favourite crooner. Incentives were all very well, but he also had to make answering the survey as frictionless as possible. He flipped the switch again. &#38;quot;Fry, what was that article you were talking about?&#38;quot;&lt;/p&gt;

&lt;p&gt;The Assistant Head&#38;#39;s assistant entered holding the latest issue of &lt;i&gt;Polar Web Monthly&lt;/i&gt;. &#38;quot;Here you go, boss. It&#38;#39;s called &lt;i&gt;10 ways to boost online survey responses&lt;/i&gt;.&#38;quot; Cautiocus scanned the text; one of the sections caught his eye.&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    5. Use a QR code for the survey link

    The folks you want to hear from have plenty to do already! Do they all have time to type in a long, cumbersome URL, especially on a portable device? Even if they do, what if they mistype it? Will they have the patience to try again, or will they conclude that the survey is down? If your survey link is longer than about 15 characters, or has any kind of numbers in it, let alone hex or a GUID, you&#38;#39;ll be lucky to hear from anyone!

    Instead, use a &#38;#39;QR code&#38;#39;: a square design encoded with your weblink which respondents can scan with their mobile devices to take them straight to your questionnaire!

    Create a free QR code at:
    https://gr8.arcticinternet.northpole/qrcode&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;This is it!&#38;quot; Cautiocus said. &#38;quot;Don&#38;#39;t send the actual survey link out on the workshop order sheets. Hardly any elf got back to us last year because the SurveyFlunkey URL was hard to type. Use one of these instead!&#38;quot;&lt;/p&gt;

&lt;p&gt;Fry generated a &#38;#39;QR code&#38;#39; using the handy webapp mentioned in the magazine and uploaded it to the Central Workshop server so it could be added to the toy order lists sent out to all elf workshops. Cautiocus checked the QR code one more time with his Elfone and smiled in quiet satisfaction, looking forward to the replies rolling in.&lt;/p&gt;

&lt;p&gt;Fry brought up the survey dashboard on a large display on the wall of Cautiocus&#38;#39; office in the warm Safety Cabin so they could monitor the responses in real time. After returning with two mugs of cocoa, he was delighted to see that five responses had already appeared.&lt;/p&gt;

&lt;h3 id=&#34;The-next-day&#34;&gt;The next day&lt;/h3&gt;

&lt;p&gt;Cautiocus entered the office the next morning. He stared at the display with concern. Still the same five responses! He summoned Fry, who tried refreshing the page. It came back just the same!&lt;/p&gt;

&lt;p&gt;Feeling queasy, Cautiocus tested the QR code again. &lt;b&gt;Calamity!&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;qrcode_gr8arctic.gif&#34; alt=&#34;the unfortunate QR code&#34;&gt; &lt;p style=&#34;font-size: smaller&#34;&gt;the unfortunate QR code&lt;/p&gt;

&lt;/p&gt;



&lt;p&gt;Gr8 Arctic Internet apparently stood for &#38;#39;Grinch Ate the Arctic Internet&#38;#39;. His QR code now led to a page on &lt;i&gt;their&lt;/i&gt; website demanding &lt;i&gt;all the toys bound for children in the Nordic countries&lt;/i&gt; in order to reenable the survey link &#38;ndash; and suggesting the children deserved coal instead!&lt;/p&gt;

&lt;p&gt;This was an emergency. Cautiocus put his pointy hard hat on and called the IT Helpdesk. &#38;quot;We&#38;#39;re sending down Tech Elf Tania now,&#38;quot; he was told.&lt;/p&gt;

&lt;h3 id=&#34;Techie-Tania-whirls-into-action&#34;&gt;Techie Tania whirls into action&lt;/h3&gt;

&lt;p&gt;Tania shot in 45 seconds later. &#38;quot;I hear you have a problem with your survey!&#38;quot;&lt;/p&gt;

&lt;p&gt;Cautiocus glumly indicated the sample order sheet. Tania scanned it with her own Elfone. Up popped the Grinch&#38;#39;s webpage, with his grisly green mien glowering out of it, eager to ruin Christmas for millions of children.&lt;/p&gt;

&lt;p&gt;&#38;quot;What happened?!&#38;quot; cried Cautiocus. &#38;quot;Did that old rogue hack into our servers?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Well, no,&#38;quot; Tania responded. &#38;quot;Did you get this QR code from an external provider?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Yes... I thought they were called &#38;#39;Great Arctic Internet&#38;#39;.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;There&#38;#39;s the problem. By sweet-talking a local journalist and masquerading as a respectable Arctic business, the Grinch has managed to persuade local elves, including you, to use his QR code generator. It never pointed to your survey, just to a GrinchNet URL that originally redirected to your survey ... but now to something far less beneficent!&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;What can we do?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Well, the first thing is to generate a QR code to your actual survey. Fortunately I have just the Perl script! It uses the &lt;code&gt;Imager::QRCode&lt;/code&gt; distribution by Yoshiki Kurihara to output QR codes of various kinds. You can even choose the colours! Though, say, dark blue and purple together won&#38;#39;t work very well, of course.&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;#!/usr/bin/env perl&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;strict&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;warnings&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Imager::QRCode&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$qrcode&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Imager::QRCode&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;size&lt;/span&gt;          &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;margin&lt;/span&gt;        &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;version&lt;/span&gt;       &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;level&lt;/span&gt;         &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;L&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;casesensitive&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;lightcolor&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Imager::Color&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;255&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;255&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;255&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;darkcolor&lt;/span&gt;     &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Imager::Color&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$message&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;core&#34;&gt;shift&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;die&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;must provide message&#39;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;unless&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$message&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$img&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$qrcode&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;plot&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$message&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;symbol&#34;&gt;$img&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;write&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;file&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;qrcode_&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;.gif&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;die&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;Failed to write: &#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$img&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;errstr&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Tania ran the script inputting the URL for Cautiocus&#38;#39; safety survey and a new QR code appeared.&lt;/p&gt;

&lt;p&gt;She scanned the new QR code on her Elfone and up popped the safety survey. Cautiocus and Fry beamed!&lt;/p&gt;

&lt;p&gt;&#38;quot;Well done, Techie Tania! But what about the old QR code?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;That&#38;#39;s a bit more difficult. But we can certainly configure our enterprise routers to redirect the GrinchNet URL back to your survey. That&#38;#39;ll work for any device connected to the workshop WiFi. Not so much if they&#38;#39;re outside, though.&#38;quot;&lt;/p&gt;

&lt;p&gt;Cautiocus was delighted. &#38;quot;That&#38;#39;ll do. Merry Christmas, Tania. Will we see you at the Bilbo Frostby gig?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;I&#38;#39;m looking forward to it. Merry Christmas.&#38;quot;&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-23T00:00:00Z</updated><category term="Perl"/><author><name>Ludo Tolhurst-Cleaver</name></author></entry><entry><title type="html">Bit vectors save space on Santa&#38;#39;s list</title><link href="https://perladvent.org/2025/2025-12-22.html"/><id>https://perladvent.org/2025/2025-12-22.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;The number of children in the modern world has strained Santa&#38;#39;s meager on-sleigh systems. Back when he first started, he was consulting a scroll the elf handed him as he left the North Pole. When the scroll got too unwieldy, an elf intern created a small Naughty/Nice (NN) database that Santa could install on the ancient navigation system, but space was running tight since Santa had once been told that 640k should be enough for anyone (someone is permanently on the Naughty list). He had plans for upgrades, but there was always something more important. The bar code scanner subsystem he added had cut down on mis-gifting (ever got a present you didn&#38;#39;t expect?), but that didn&#38;#39;t leave enough space for the NN database. Something needed to happen.&lt;/p&gt;

&lt;p&gt;There was an ancient elf, long since retired, who had an idea, and Santa brought him out of retirement for a special assignment. This was a make-or-break effort; mess this up and Santa might have to cut out the parts of the world he couldn&#38;#39;t fit into the meager memory he had left. &#38;quot;Greybeard Elf,&#38;quot; Santa said, &#38;quot;you have to save Christmas.&#38;quot; Greybeard Elf responded &#38;quot;Again?&#38;quot; There&#38;#39;s a reason these elves retire.&lt;/p&gt;

&lt;p&gt;Greybeard Elf had fixed all the COBOL programs on December 24, 1999, so Santa was expecting big things. Greybeard Elf corrected Santa: &#38;quot;No, you expect small things, and I&#38;#39;m going to use the smallest of small things.&#38;quot;&lt;/p&gt;

&lt;p&gt;Since every present was already bar-coded and every child had been assigned a special Santa Number, the trick was to see whether that number was Naughty or Nice. Santa doesn&#38;#39;t have time for anything fancy. Just tell him yes or no.&lt;/p&gt;

&lt;p&gt;Greybeard Elf, versed in the ways of C and Perl, did everything with bit fiddling even before the definition of the byte standardized on 8 bits. If he represented every child as a single bit, he could do it all in about 300 MB on an SD card he could put in a Raspberry Pi he would stick to the sleigh. &#38;quot;Green means Nice, red means Naughty&#38;quot;, Greybread Elf said.&lt;/p&gt;

&lt;p&gt;Perl, in particular, makes bit fiddling easy through `vec`, a built-in function to manipulate regular strings as if they were bit fields. It lets you set the width of the group of bits (in powers of two up to 64), then access it similarly to a list.&lt;/p&gt;

&lt;p&gt;Greybeard Elf showed Santa an example of setting a single bit:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;v5.42&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$bitmap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$WIDTH&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$pos&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;13&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;vec&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$bitmap&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$pos&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$WIDTH&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;say&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;show_bitmap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$bitmap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;show_bitmap&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$b&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$bits&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;8&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;length&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$b&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$s&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;join&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;map&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;vec&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$b&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;magic&#34;&gt;$_&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$WIDTH&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;+&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;.&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$bits&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This set the 13th bit, extending the string as necessary to access a bit position. The output was a visual representation of the entire bit string, with a dot representing 0 and a plus sign representing 1. The 12th bit, counting from 0, is on (true, 1, whatever):&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; .............+..&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Reading the bit field is just as easy. If you don&#38;#39;t assign to `vec`, you get the answer back:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$nice&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;vec&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$b&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$pos&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$WIDTH&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Short and simple. No need for tricky services or programs. Santa said &#38;quot;Okay, whatever, I just need it to work!&#38;quot; in the way managers try to get out of these sorts of conversations quickly.&lt;/p&gt;

&lt;p&gt;Greybeard Elf showed off another trick. These are just strings, so you can change characters in that string. What&#38;#39;s the difference between &#38;quot;perl&#38;quot; and &#38;quot;Perl&#38;quot;? It&#38;#39;s just one bit:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$bitmap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;perl&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;say&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;show_bitmap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$bitmap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;vec&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$bitmap&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# bits are reversed within the byte&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;say&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$bitmap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;   &lt;span class=&#34;comment&#34;&gt;# &#38;quot;Perl&#38;quot;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And, this even works on memory-mapped files. For example, you can start with a file that holds a string that is wide enough for all the bits that you might want to set. The string could be all null bytes (which is different than the character &#38;quot;0&#38;quot;, which has set bits). Once you have the file, you can then read or set bits, and whatever you do is persistent:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;v5.36&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;File::Map&lt;/span&gt; &lt;span class=&#34;words&#34;&gt;qw(map_file)&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$FILENAME&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;naughty-nice.db&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$BITS&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;56&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# this is just to set up the &#38;quot;pre-existing&#38;quot; file&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;open&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$fh&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#38;gt;&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$FILENAME&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;print&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$fh&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;\000&#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;x&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$BITS&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;close&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$fh&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;map_file&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$bitmap&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$FILENAME&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;+&#38;lt;&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;  &lt;span class=&#34;comment&#34;&gt;# file must exist&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;foreach&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$i&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;random_numbers&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$BITS&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;13&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;vec&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$bitmap&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$i&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;printf&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;%3d: %s\n&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$i&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;show_bitmap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$bitmap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;random_numbers&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$max&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$BITS&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$n&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@a&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;map&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$max&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$n&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;show_bitmap&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$b&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$bits&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;8&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;length&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$b&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$s&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;join&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;map&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;vec&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$b&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;magic&#34;&gt;$_&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;+&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;.&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$bits&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The output shows the bit vector after each bit operation. Note that some bit positions (10, 14, 43) are &#38;quot;set&#38;quot; twice, which just means they remain set. You can only be Nice once, no matter how nice you are:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;  27: ...........................+............................
  54: ...........................+..........................+.
  43: ...........................+...............+..........+.
  12: ............+..............+...............+..........+.
  45: ............+..............+...............+.+........+.
  10: ..........+.+..............+...............+.+........+.
  43: ..........+.+..............+...............+.+........+.
  49: ..........+.+..............+...............+.+...+....+.
  35: ..........+.+..............+.......+.......+.+...+....+.
  14: ..........+.+.+............+.......+.......+.+...+....+.
  10: ..........+.+.+............+.......+.......+.+...+....+.
  14: ..........+.+.+............+.......+.......+.+...+....+.
  17: ..........+.+.+..+.........+.......+.......+.+...+....+.&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;But that&#38;#39;s not all. Greybeard Elf wants to know Nice children count. You could test each bit individually, but there&#38;#39;s a trick with `unpack` that counts the set bits. The `%` provides a checksum of the value (and the `*` is just a repetition of the field):&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;word&#34;&gt;say&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;Unique numbers seen: &#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;count_bits&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$bitmap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;count_bits&lt;/span&gt; &lt;span class=&#34;prototype&#34;&gt;($b)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;unpack&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;%32b*&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$b&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;&#34;&gt;&lt;/h3&gt;

&lt;p&gt;I (the author) adapted this example from a particular task I had. Working in a shared container provided to me in which I had no special privileges, I had to count unique IPv4 addresses found in a series of PCAP files. There can be about 3.7 billion of those, excluding the private or reserved addresses. Accumulating these in a hash not only created billions of keys, but each key was itself 4 (packed) bytes. That&#38;#39;s a lot of memory, and it was a bit slow.&lt;/p&gt;

&lt;p&gt;I only needed to know if a set of IP addresses was part of the data, and I didn&#38;#39;t care at all about how many times. In a hash, this would use `exists` to check the key and ignore the value (so undef would be a fine value). With `vec`, I merely look at the bit position that matches the numeric value of the IPv4 address.&lt;/p&gt;

&lt;p&gt;And, I could easily save the result and inspect it later without loading a big data file that I would have to parse. I did try freezing with `Storable`, but I knew that wasn&#38;#39;t going to work. But it gave me something to do as the problem worked itself out in the back of my head. The output sizes were just too large, and I had too many files to create. With the bit string, roughly one file per every hour of the last four years, with each one file about 400 MB (about 10 GB a day, 4 TB a year).&lt;/p&gt;

&lt;p&gt;Part of the problem is that Perl values aren&#38;#39;t just values, but carry along all the baggage of Perl SVs, the underlying data structure that tracks all the magic and other things a scalar value knows about itself. This is many times larger than the size of a small value, such as an IPv4 address, and it&#38;#39;s repeated for every value. Even an undefined value in an SV takes up quite a bit of space, and a short string takes up much more:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt; $ perl -MDevel::Size=total_size -E &#38;#39;my $x; say total_size(\$x)&#38;#39;
 24
 $ perl -MDevel::Size=total_size -E &#38;#39;my $x=q(1); say total_size(\$x)&#38;#39;
 48&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So, using a hash to count even a small fraction of the IP space takes up much more space, and counting a significant portion of it smashes the stack.&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-22T00:00:00Z</updated><category term="Perl"/><author><name>brian d foy</name></author></entry><entry><title>The Gift of Readability</title><link href="https://perladvent.org/2025/2025-12-21.html"/><id>https://perladvent.org/2025/2025-12-21.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;h3 id=&#34;Night-Shift-at-PDN-Headquarters&#34;&gt;Night Shift at PDN Headquarters&lt;/h3&gt;

&lt;p&gt;At the Present Delivery Network headquarters, December nights were always long.&lt;/p&gt;

&lt;p&gt;The elves &#38;mdash; engineers by trade, magicians by necessity &#38;mdash; sat beneath glowing dashboards tracking sleigh routes, Aurora Borealis hit ratios, and reindeer latency.&lt;/p&gt;

&lt;p&gt;All of it powered by Perl. Old Perl. New Perl. Perl written by elves long retired to toy-making or cloud-making. Perl written by skilled masters. Perl written by babies.&lt;/p&gt;

&lt;p&gt;She rubbed her eyes.&lt;/p&gt;

&lt;p&gt;Three hours. Three hours she&#38;#39;d been trying to understand this function. Three hours to fix what should have been a five-minute bug.&lt;/p&gt;

&lt;p&gt;The Present Delivery Network ran flawlessly &#38;mdash; millions of gifts delivered every year with magical precision. But behind the scenes, the elves maintaining the codebase were drowning in their own cleverness.&lt;/p&gt;

&lt;p&gt;This code worked perfectly for ages, yet recently it stopped feeding Rudolph. And it is utterly incomprehensible for her.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;cast&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$rslts&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;){&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;push&lt;/span&gt;&lt;span class=&#34;cast&#34;&gt;@$&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;cast&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;feed&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;//&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;reindeer&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;//&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;getPreferredReindeerFuel&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;//&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;prefs&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;})&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;!!&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;active&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;==!!&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&#38;ldquo;Why,&#38;rdquo; she whispered to the empty office, &#38;ldquo;why do we do this to ourselves?&#38;rdquo;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&#38;quot;The Present Delivery Network was designed to be efficient.&#38;quot;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&#38;quot;This code &#38;hellip; less so.&#38;quot;&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;She thought about the astigmatic, colour-blind elf, squinting at compressed code through his magnifier. She thought about the foreigner elf, parsing English as their second language. She thought about the new elf starting after the holidays, terrified to touch anything.&lt;/p&gt;

&lt;p&gt;And so began the quiet refactoring ...&lt;/p&gt;

&lt;h3 id=&#34;The-Invisible-Barrier&#34;&gt;The Invisible Barrier&lt;/h3&gt;

&lt;p&gt;The Capability to Read is Not Given&lt;/p&gt;

&lt;p&gt;Readable code isn&#38;#39;t just about aesthetics or dogma.&lt;/p&gt;

&lt;p&gt;It is about politeness. It is about inclusiveness.&lt;/p&gt;

&lt;p&gt;It is about respecting the time others have to spend dealing with our, ehm, work. Time spent in code review. Time lost while hunting bugs. Time newcomers need before they dare to change a line.&lt;/p&gt;

&lt;p&gt;We often forget that reading code is a learned skill layered on top of another learned skill: reading English.&lt;/p&gt;

&lt;p&gt;Whilst most programming languages and source code use English-based syntax, only about 400 million people speak English as their native language. Another about 1.5 billion speak it as a second language. Even among those who do, reading dense technical English is a different skill from ordering coffee or chatting about the weather.&lt;/p&gt;

&lt;p&gt;As an example, try to quickly read my favourite German word:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;Rindfleischetikettierungs&#38;uuml;berwachungsaufgaben&#38;uuml;bertragungsgesetz&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For those who do not speak German, it means:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;The law for the delegation of duties for the supervision of the labelling of beef.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without delimiters, the brain struggles to find word boundaries. Code is no different.&lt;/p&gt;

&lt;p&gt;Machines execute instructions flawlessly; humans do not. Code is written once, but read many times, often by people who were not present when the first line was typed.&lt;/p&gt;

&lt;h3 id=&#34;How-Humans-Actually-Read&#34;&gt;How Humans Actually Read&lt;/h3&gt;

&lt;p&gt;When children learn to read they don&#38;#39;t read words as whole units. They decode letters, syllables, fragments. Adults learning a foreign language do the same.&lt;/p&gt;

&lt;p&gt;Reading is a learned skill.&lt;/p&gt;

&lt;p&gt;The cognitive process of reading involves a delicate dance of fixations (pauses where the eye rests to process information) and saccades (rapid eye movements between fixation points).&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Efficient readers generally have shorter fixations and longer saccades.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better comprehension comes from familiarity with language, vocabulary, and topic.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;word&#34;&gt;calculateUserAccountBalance&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;calculate_user_account_balance&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Snake case provides explicit visual boundaries. The eyes don&#38;rsquo;t have to guess where words end. For non-native readers, tired eyes, or magnified screens, those underscores are not just style: they are guidance.&lt;/p&gt;

&lt;p&gt;Yes, you can learn to read &lt;code&gt;camelCase&lt;/code&gt; efficiently, just like you can learn vocabulary of a foreign language. But learning costs time and energy. Readable code spends less of both.&lt;/p&gt;

&lt;p&gt;As an extreme example, try to quickly read my favourite German word:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;Rindfleischetikettierungs&#38;uuml;berwachungsaufgaben&#38;uuml;bertragungsgesetz&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Do Germans have a problem with such long words? Perhaps they do.&lt;/p&gt;

&lt;p&gt;Reading is a learned skill, and reading also includes the vocabulary of words you are familiar with. Yes, you can learn to recognise even such monsters.&lt;/p&gt;

&lt;p&gt;However, since you should learn the business domain rules regardless, you should use that knowledge and your brain&#38;#39;s capability to combine common words into known expressions.&lt;/p&gt;

&lt;h3 id=&#34;The-Table-Principle&#34;&gt;The Table Principle&lt;/h3&gt;

&lt;p&gt;The human brain excels at processing tabular organization.&lt;/p&gt;

&lt;p&gt;Compare the hash definition in the PDN code:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%config&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;host&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt;&lt;span class=&#34;single&#34;&gt;&#39;localhost&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;port&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;5432&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;user&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt;&lt;span class=&#34;single&#34;&gt;&#39;admin&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;timeout&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;30&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Versus:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%config&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;host&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;localhost&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;port&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;5432&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;user&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;admin&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;timeout&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;30&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;The-Version-Control-Revolution&#34;&gt;The Version Control Revolution&lt;/h3&gt;

&lt;p&gt;Modern development is collaborative. We live in the age of Git, where every change is tracked, reviewed, and discussed. Yet how often do we write code that makes reviewing diffs a nightmare?&lt;/p&gt;

&lt;p&gt;She pulled up a recent code review. The commit showed:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;-my %user = (name =&#38;gt; &#38;#39;Alice&#38;#39;, email =&#38;gt; &#38;#39;alice@examp1e.com&#38;#39;, role =&#38;gt; &#38;#39;admin&#38;#39;);&lt;br /&gt;+my %user = (name =&#38;gt; &#38;#39;Alice&#38;#39;, email =&#38;gt; &#38;#39;alice@example.com&#38;#39;, role =&#38;gt; &#38;#39;admin&#38;#39;, department =&#38;gt; &#38;#39;Engineering&#38;#39;);&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Wouldn&#38;#39;t it be easier to review this?&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;my %config = (&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;name       =&#38;gt; &#38;#39;Alice&#38;#39;,&lt;br /&gt;-    email      =&#38;gt; &#38;#39;alice@examp1e.com&#38;#39;,&lt;br /&gt;+    email      =&#38;gt; &#38;#39;alice@example.com&#38;#39;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;role       =&#38;gt; &#38;#39;admin&#38;#39;,&lt;br /&gt;+    department =&#38;gt; &#38;#39;Engineering&#38;#39;,&lt;br /&gt;);&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;One line fixed. One line added. One concept changed. Perfect clarity.&lt;/p&gt;

&lt;h3 id=&#34;Trailing-Commas:-The-Unsung-Heroes&#34;&gt;Trailing Commas: The Unsung Heroes&lt;/h3&gt;

&lt;p&gt;In the diff-friendly example above, notice the trailing comma after &lt;code&gt;admin&lt;/code&gt;? That comma means when you add &lt;code&gt;department&lt;/code&gt;, you only change one line. Without it:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;-    role       =&#38;gt; &#38;#39;admin&#38;#39;&lt;br /&gt;+    role       =&#38;gt; &#38;#39;admin&#38;#39;,&lt;br /&gt;+    department =&#38;gt; &#38;#39;Engineering&#38;#39;,&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Two lines changed for one concept. Trailing commas keep your diffs clean and prevent noisy changes. The goal is not minimal characters, but minimal surprise.&lt;/p&gt;

&lt;h3 id=&#34;The-Lexical-Consistency-Principle&#34;&gt;The Lexical Consistency Principle&lt;/h3&gt;

&lt;p&gt;She opened another file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$user&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;get_user&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$id&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$order&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;fetch_order&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$order_id&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$product&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;lookup_product&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$sku&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&#38;quot;Are these different operations? Or just different words for the same thing?&#38;quot; she wondered.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Humans build mental dictionaries while reading code. If one word means one thing here and something else there, the dictionary collapses.&lt;/p&gt;

&lt;p&gt;This is cognitive overload in action. When every developer uses their favourite verb, readers must constantly translate. Consistent vocabulary is kindness.&lt;/p&gt;

&lt;p&gt;Prefer verbs with contracts, e.g.:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&lt;b&gt;find&lt;/b&gt; &#38;mdash; Always returns at least one element; or throws a &#38;quot;not found&#38;quot; exception&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;search&lt;/b&gt; &#38;mdash; Try to find; or return an empty result set&lt;/p&gt;

&lt;p&gt;Look into dictionaries. Google&#38;#39;s query &lt;code&gt;explain search&lt;/code&gt; literally returns &lt;code&gt;try to find&lt;/code&gt;.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;fetch&lt;/b&gt; &#38;mdash; Retrieve data from an external source (API, file, database)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;create&lt;/b&gt; &#38;mdash; Create a domain entity (external interface)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;build&lt;/b&gt; &#38;mdash; Create a domain entity (internal interface)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;ensure&lt;/b&gt; &#38;mdash; Returns true if state matches expectations, throws exceptions otherwise&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;validate&lt;/b&gt; &#38;mdash; Check if data meets criteria without throwing exceptions, returning true on success&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;exclude&lt;/b&gt; &#38;mdash; Filter-out operation&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;include&lt;/b&gt; &#38;mdash; Filter-in operation&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid verbs like:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&lt;b&gt;get&lt;/b&gt; &#38;mdash; Too generic. Consult a thesaurus; this verb has dozens of meanings&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;filter&lt;/b&gt; &#38;mdash; Inconclusive. Better to use &lt;code&gt;include&lt;/code&gt; or &lt;code&gt;exclude&lt;/code&gt;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;maybe&lt;/b&gt; &#38;mdash; Such logic is part of the method&#38;#39;s contract; the method should decide what to do&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;return&lt;/b&gt; &#38;mdash; Redundant when used as a verb in method names&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;sanction&lt;/b&gt; - autoantonym (e.g.: &lt;code&gt;sanction_trade_with_russia&lt;/code&gt;)&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is always a good idea to summarize such verbs and their project meaning in a glossary.&lt;/p&gt;

&lt;p&gt;When everyone speaks the same language, code becomes self-documenting.&lt;/p&gt;

&lt;p&gt;For more examples, see: &lt;a href=&#34;https://gist.github.com/happy-barney/1ee36e693751dbbfc046a268cde258de&#34;&gt;https://gist.github.com/happy-barney/1ee36e693751dbbfc046a268cde258de&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;The-Gift-of-Readability-Manifesto&#34;&gt;The Gift of Readability Manifesto&lt;/h3&gt;

&lt;p&gt;As dawn broke on Christmas morning, she compiled her learnings into a gift for her team - The Gift of Readability Manifesto.&lt;/p&gt;

&lt;h4 id=&#34;Use-snake_case-Everywhere&#34;&gt;1. Use snake_case Everywhere&lt;/h4&gt;

&lt;p&gt;Even in package names. &lt;code&gt;UserAccountManager&lt;/code&gt; becomes &lt;code&gt;User::Account::Manager&lt;/code&gt; or &lt;code&gt;User_Account_Manager&lt;/code&gt;, variables become &lt;code&gt;$user_account_balance&lt;/code&gt;, constants &lt;code&gt;USER_ACCOUNT_MANAGER&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Exercise: play with terms &#38;quot;settable&#38;quot; and &#38;quot;set table&#38;quot;.&lt;/p&gt;

&lt;h4 id=&#34;Indent-with-Tabs&#34;&gt;2. Indent with Tabs&lt;/h4&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;One tab equals one indentation level. No such things as &#38;quot;half-level&#38;quot;.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do not specify tab width in your project.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Let each contributor configure their editor to their preferred width (2 spaces, 4 spaces, 8 spaces - whatever they like).&lt;/p&gt;

&lt;p&gt;Every modern editor supports this.&lt;/p&gt;

&lt;p&gt;The visualisation of indentation is not a choice of the writer or the project; it is choice of the reader.&lt;/p&gt;

&lt;p&gt;Every person prefers different visual length of indentation. I have worked on projects that used 1, 2, 3, and 4 spaces for indentation. I even worked on one project that consisted of three different parts, each using a different language and a different indent width.&lt;/p&gt;

&lt;p&gt;Conversely, when you begin to treat a hard tab as the declaration of one indent level, life becomes easier for everyone.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;Never-Align-on-Lines-with-Different-Indentation&#34;&gt;3. Never Align on Lines with Different Indentation&lt;/h4&gt;

&lt;h4 id=&#34;Use-Paired-Characters-as-Quoted-Operator-Delimiters&#34;&gt;4. Use Paired Characters as Quoted Operator Delimiters&lt;/h4&gt;

&lt;p&gt;It reduces the number of backslashes you need to use:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;literal&#34;&gt;q (function () shouldn&#39;t return &#38;quot;$foo&#38;quot;)&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;regexp&#34;&gt;qr (a/b)&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# vs&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;single&#34;&gt;&#39;function () shouldn\&#39;t return &#38;quot;$foo&#38;quot;&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;function () shouldn&#39;t return \&#38;quot;\$foo\&#38;quot;&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;regexp&#34;&gt;qr/a\/b/&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;Space-Around-Operators-and-Keywords&#34;&gt;5. Space Around Operators and Keywords&lt;/h4&gt;

&lt;p&gt;Make breathing room:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;!&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$condition&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;function_call&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$arg&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=~&lt;/span&gt; &lt;span class=&#34;match&#34;&gt;m (...)&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Not:&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$condition&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;){&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;function_call&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$arg&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;=~&lt;/span&gt;&lt;span class=&#34;match&#34;&gt;m/.../&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Examples of proper and consistent spacing:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;if ()&lt;/code&gt;, &lt;code&gt;function ()&lt;/code&gt;, &lt;code&gt;q ()&lt;/code&gt;, &lt;code&gt;s () ()&lt;/code&gt;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;(! condition)&lt;/code&gt;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;$x = $y + $z&lt;/code&gt;&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;One-Line-One-Concept&#34;&gt;6. One Line, One Concept&lt;/h4&gt;

&lt;p&gt;When writing multiline expressions, each line should contain either:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Complete information (opening and closing parenthesis on the same line), or&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A single piece of information (opening parenthesis as the last character)&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The left parenthesis is either on the same line as its counterpart or is the last character on a given line.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# Good: complete expression on one line&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$result&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;calculate&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$param&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;other_function&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$other&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Good: leading operators on line signals that there is more in usage of such symbol.&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$good&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;calculate&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$param&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;other_function&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$other&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Good: opening paren ends line, closing paren starts line&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$good&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;calculate&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$param&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;other_function&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$other&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Bad: mixed structure&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$bad&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;calculate&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$param&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;other_function&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$other&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Bad: opening paren mid-line&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$bad&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;calculate&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$param&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;other_function&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$other&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;));&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# Good: each element on its own line&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$good&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;single&#34;&gt;&#39;alpha&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;single&#34;&gt;&#39;beta&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;single&#34;&gt;&#39;gamma&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Bad: inconsistent breaks&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@bad&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;single&#34;&gt;&#39;alpha&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;beta&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;single&#34;&gt;&#39;gamma&#39;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;dl&gt;

&lt;dt&gt;What if these values belong to logical groups?&lt;/dt&gt;
&lt;dd&gt;

&lt;p&gt;Solution: Name the logical group and use the group name (or an appropriate data structure) to contain these values instead of defining them individually.&lt;/p&gt;

&lt;p&gt;Principle: Do not mix different kinds of information or concepts within a single grouping. Groupings should follow the Single Responsibility Principle.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@modern&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;single&#34;&gt;&#39;alpha&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;single&#34;&gt;&#39;beta&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@deprecated&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;single&#34;&gt;&#39;gamma&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@good&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;@modern&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;@deprecated&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/dd&gt;
&lt;dt&gt;What if these values form lists that are too long?&lt;/dt&gt;
&lt;dd&gt;

&lt;p&gt;Put list into function / constant. Order them (usually alphabetically). Split them into groups.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@good&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;My::Good::Values&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;ALL_GOOD_VALUES&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/dd&gt;
&lt;/dl&gt;

&lt;h3 id=&#34;Epilogue&#34;&gt;Epilogue&lt;/h3&gt;

&lt;p&gt;The office filled again. They gathered around her monitor.&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&#38;ldquo;It&#38;rsquo;s so much longer,&#38;rdquo;&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;someone said.&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&#38;ldquo;Yes,&#38;rdquo; she replied.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&#38;ldquo;But look at the diff.&#38;quot;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&#38;quot;Look at elf reading this with a magnifier.&#38;quot;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&#38;quot;Think about the newcomers touching this file for the first time.&#38;rdquo;&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;She smiled.&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&#38;ldquo;Readable code isn&#38;rsquo;t about writing less now.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It&#38;rsquo;s about respecting those who read it tomorrow.&#38;rdquo;&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The team was silent for a moment.&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&#38;quot;Thank you. This... this actually makes my work easier.&#38;quot;&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that, she thought, was the greatest gift of all.&lt;/p&gt;

&lt;h2 id=&#34;P.S&#34;&gt;P.S.&lt;/h2&gt;

&lt;p&gt;This kind of document always sparks heated discussions so I&#38;#39;ve created a GitHub project with discussion enabled: &lt;a href=&#34;https://github.com/happy-barney/the-gift-of-readability/discussions&#34;&gt;https://github.com/happy-barney/the-gift-of-readability/discussions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I&#38;#39;ve been many times rightfully accused of omitting information I consider too obvious. If you found some place where you think I did it again, please start related discussion as well.&lt;/p&gt;

&lt;p&gt;This article is summary of my experiences and things I learned. See for example books:&lt;/p&gt;

&lt;dl&gt;

&lt;dt&gt;&lt;a href=&#34;https://learning.oreilly.com/library/view/clean-code-a/9780135398586/&#34;&gt;Clean Code&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;

&lt;/dd&gt;
&lt;dt&gt;&lt;a href=&#34;https://learning.oreilly.com/library/view/the-art-of/9781449318482/&#34;&gt;The Art of Readable Code&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;

&lt;/dd&gt;
&lt;/dl&gt;

&lt;p&gt;&lt;b&gt;brian d foy&lt;/b&gt; left a comment when I submitted this article:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    &#38;quot;One of the great mistakes of Perl Best Practices is that it led people to believe
    that there were hard rules, even though that was not the authors intent. This sort
    of article makes a similar mistake. Readability isn&#38;#39;t some sacred knowledge hidden
    across books stored away in dark library basements.&#38;quot;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;He is right. This article should not be treated as a set of strict hard rules. It should spark the creation of some kind of &lt;i&gt;base class&lt;/i&gt;, some kind of &lt;i&gt;readability framework&lt;/i&gt;, real projects can be based on.&lt;/p&gt;

&lt;p&gt;I believe that every rule must be explained, complete with reasoning, pros, and cons. This explanation should also cover complementing rules to explain why they were rejected.&lt;/p&gt;

&lt;p&gt;For example, imagine an early advocacy argument for camel case:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    &#38;quot;To save space, as source code storage is limited to 1.44 MB.&#38;quot;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;(Do people even remember 1.44 MB nowadays?)&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-21T00:00:00Z</updated><category term="Perl"/><author><name>Branislav Zahradn&#237;k</name></author></entry><entry><title>How SUSE is Using Perl</title><link href="https://perladvent.org/2025/2025-12-20.html"/><id>https://perladvent.org/2025/2025-12-20.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;Last year a new Perl Advent tradition was started, where a remote presentation is made as part of the calendar. That presentation was &lt;a href=&#34;https://perladvent.org/2024/2024-12-19.html&#34;&gt;Half My Life With Perl&lt;/a&gt; by Randal Schwartz. This year we we had the pleasure of hearing from Michael Schr&#38;ouml;der, Tina M&#38;uuml;ller and Oliver Kurz talking about how Perl is in use at &lt;a href=&#34;https://www.suse.com&#34;&gt;SUSE&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The primary coverage of the talk concerns SUSE&#38;#39;s &lt;a href=&#34;https://open.qa/&#34;&gt;openQA&lt;/a&gt; and the &lt;a href=&#34;https://openbuildservice.org/&#34;&gt;Open Build Service&lt;/a&gt;. These both lean heavily on Perl and are tools that you may find useful in your own work.&lt;/p&gt;

&lt;p&gt;Now, please enjoy &#38;quot;How SUSE is Using Perl&#38;quot;:&lt;/p&gt;

&lt;p&gt;&lt;iframe width=&#34;560&#34; height=&#34;315&#34; src=&#34;https://www.youtube.com/embed/2TDYM1ajNjc?si=bBV3LAT3jDfE3Fer&#34; title=&#34;YouTube video player&#34; frameborder=&#34;0&#34; allow=&#34;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&#34; referrerpolicy=&#34;strict-origin-when-cross-origin&#34; allowfullscreen&gt;&lt;/iframe&gt;

&lt;/p&gt;



&lt;/div&gt;</summary><updated>2025-12-20T00:00:00Z</updated><category term="Perl"/><author><name>Michael Schr&#246;der, Tina M&#252;ller and Oliver Kurz</name></author></entry><entry><title>Advent of the Underbar</title><link href="https://perladvent.org/2025/2025-12-19.html"/><id>https://perladvent.org/2025/2025-12-19.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;h3 id=&#34;Advent-of-the-Underbar&#34;&gt;Advent of the Underbar&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;advent-of-the-Underbar.png&#34; /&gt;

&lt;/p&gt;



&lt;p&gt;According to podcast index, there are (at the time of writing) 4,558,912 podcasts, 432,264 of which have been published in the last 60 days.&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://underbar.cpan.io&#34;&gt;The Underbar&lt;/a&gt; is one of them.&lt;/p&gt;

&lt;p&gt;That is a lot of podcasts, and the first question that comes to mind is &#38;quot;why create another one?&#38;quot; (quickly followed by &#38;quot;how long do you think it&#38;#39;s going to last?&#38;quot;).&lt;/p&gt;

&lt;h4 id=&#34;A-tangent:-my-pandemic-projects&#34;&gt;A tangent: my pandemic projects&lt;/h4&gt;

&lt;p&gt;Before I can describe the podcast, I have to go on a tangent, which will set the stage for how I ended up creating yet another computing podcast.&lt;/p&gt;

&lt;p&gt;During the 2020 pandemic (I sadly think we&#38;#39;ll have to date them), and more specifically during the global lockdown, a lot of people were telling the world how they spent their newly discovered free time baking bread or exploring their artistic side.&lt;/p&gt;

&lt;p&gt;Having worked mostly remotely for over a decade at the time (my team was in Amsterdam, Netherlands, and I worked from the local office in Lyon, France), I didn&#38;#39;t feel much change (except that my office was now in my home).&lt;/p&gt;

&lt;p&gt;I too wanted to have lockdown projects, so I came up with three. My hope was to complete at least one of them in a reasonable amount of time (before the end of 2021, I think).&lt;/p&gt;

&lt;h5 id=&#34;Le-b-ton-de-joie&#34;&gt;&#38;#x1F5F9; Le b&#38;acirc;ton de joie&lt;/h5&gt;

&lt;p&gt;My first idea was to pick up and finally finish a 20 years old project: building an arcade joystick to play arcade games on an emulator (such as &lt;a href=&#34;https://mamedev.org&#34;&gt;MAME&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That project was started with two friends in 2000. Back then, we had bought all buttons and sticks from an arcade repair shop, pulled the electronics out of a PS/2 &lt;a href=&#34;https://en.wikipedia.org/wiki/Model_M_keyboard&#34;&gt;IBM Model M&lt;/a&gt; keyboard, mapped the keys to the pins, and wired one joystick and two buttons. The whole lot went into a box after we plugged it into a computer and we managed to play a game of &lt;a href=&#34;https://en.wikipedia.org/wiki/Frogger&#34;&gt;Frogger&lt;/a&gt; with it.&lt;/p&gt;

&lt;p&gt;Another reason for putting on hold is that one of the friends&#38;#39; wife cheated with the other, forgetting the first rule of gaming: don&#38;#39;t cheat with friends. Over the course of 20 years, another friend gifted me a chassis, that sat alongside the box for over a decade.&lt;/p&gt;

&lt;p&gt;Picking up that project after 20 years, everything was easier. Now I could buy USB zero delay joystick adapters on the Internet, and throw away the keyboard electronics. I just had to drill 17 or so holes in the chassis, screw in all the buttons and joysticks, and wire the lot to the USB cards. After attaching a Raspberry Pi to the side (another thing that didn&#38;#39;t exist in 2000), I could temporarily move the Nintendo Switch away, and show my kids what videogames looked like in the eighties.&lt;/p&gt;

&lt;p&gt;This took a few months to complete in real time. We played our first games in May 2021.&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;joystick.jpg&#34; /&gt;

&lt;/p&gt;



&lt;h5 id=&#34;Programming-Sentimental-Advice-Column&#34;&gt;&#38;#x1F5F7; Programming Sentimental Advice Column&lt;/h5&gt;

&lt;p&gt;My second project I dubbed the &#38;quot;Perl Love Letters&#38;quot;. The idea was to set up a web site to collect and publish &#38;quot;love letters&#38;quot; (and possibly breakup letters too), written to Perl by its users or ex-users as if it were an old flame.&lt;/p&gt;

&lt;p&gt;I&#38;#39;ve been part of the Perl community since 1999, and I&#38;#39;ve had numerous conversations with lots of people about Perl, what we love about it, its quirks and its future. I really believe there is a trove of interesting stories of people discovering Perl, some sticking with it, some moving on, etc. We collectively have a lot to say about this programming language, and how it changed our lives.&lt;/p&gt;

&lt;p&gt;This idea was inspired in particular with a late night bar conversation with my then work and later &lt;a href=&#34;https://perldoc.perl.org/perlgov##The-Steering-Council&#34;&gt;Perl Steering Council&lt;/a&gt; team mate &lt;a href=&#34;https://metacpan.org/author/HAARG&#34;&gt;Graham Knop&lt;/a&gt;, who told me sincerely, almost within the same breath, that Perl was the worst programming language, and also his favorite one.&lt;/p&gt;

&lt;p&gt;Alas, although I thought about it many times, I never managed to write my own love letter to Perl. This project remains a cute idea to this day.&lt;/p&gt;

&lt;p&gt;(As the quote goes, &#38;quot;ideas are easy&#38;quot;: if you&#38;#39;re interested in making this happen, please do. I promise I&#38;#39;ll write my love letter to Perl and send it to you once you have set up the web site.)&lt;/p&gt;

&lt;h5 id=&#34;The-book-that-did-not-exist&#34;&gt;&#38;#x2610; The book that did not exist&lt;/h5&gt;

&lt;p&gt;As I considered the shelves in my office, filled with computer books, from those I have barely skimmed to those I read from cover to cover, I realized that there was one book that I wanted to read. The main problem was that no-one had written it.&lt;/p&gt;

&lt;p&gt;I wanted to read a Larry Wall biography.&lt;/p&gt;

&lt;p&gt;Instead of badly rephrasing it, I&#38;#39;ll quote the email I sent Gloria Wall in 2023 (!), to present the project:&lt;/p&gt;

&lt;p&gt;&lt;blockquote&gt;

&lt;/p&gt;



&lt;p&gt;In late 2020, at the height of the pandemic, I looked at my computer books collection, and realized there was one book that was missing in there. One book that I would really really love to read. A book that would not be afraid of technical jargon, or showing some source code or raw emails: a book written for people like me. A book that details the enormous influence Larry Wall has had on Open Source and programming in general.&lt;/p&gt;

&lt;p&gt;It turned out no one had written that book yet.&lt;/p&gt;

&lt;p&gt;For the past two years, my fantasy project has been to collect material for whomever would write that book. I&#38;#39;m not an author, I&#38;#39;m not even a native English speaker, so I knew from the beginning the author couldn&#38;#39;t be me. A year ago, I was chatting with my good friend Wendy van Dijk, and I exposed the project to her in more detail than I had ever written before:&lt;/p&gt;

&lt;p&gt;&lt;blockquote&gt;

&lt;/p&gt;



&lt;p&gt;I want to know about &lt;code&gt;patch&lt;/code&gt;, about &lt;code&gt;rn&lt;/code&gt;. About how &lt;code&gt;pack&lt;/code&gt; and &lt;code&gt;unpack&lt;/code&gt; came to be. Sure, some of the personal details too, but his philosophy of computing and collaboration is more interesting.&lt;/p&gt;

&lt;p&gt;Also, biography is a specific kind of book. We&#38;#39;d need someone who knows how to write one, and an editor, etc.&lt;/p&gt;

&lt;p&gt;There&#38;#39;s also the story of the early days of Open Source. Wasn&#38;#39;t he in the meeting where the name &#38;quot;open source&#38;quot; was coined?&lt;/p&gt;

&lt;p&gt;He&#38;#39;s been both influential and humble.&lt;/p&gt;

&lt;p&gt;I also feel I can&#38;#39;t be the one doing it. Maybe ask some questions, run some interviews, but one has to be a native speaker, and a writer, and I&#38;#39;m neither.&lt;/p&gt;

&lt;p&gt;&lt;/blockquote&gt;

&lt;/p&gt;



&lt;p&gt;[...]&lt;/p&gt;

&lt;p&gt;At the last Perl Toolchain Summit, which I organised in my home city of Lyon, France, I talked about this with Ingy d&#38;ouml;t Net and Leon Timmermans. They both thought this was a great idea, and someone should definitely do it.&lt;/p&gt;

&lt;p&gt;[...]&lt;/p&gt;

&lt;p&gt;I don&#38;#39;t know if this project will actually lead anywhere. Best case, an actual book will exist in a few years. Worst case, some material will be available for someone better equipped than me to pick this project up.&lt;/p&gt;

&lt;p&gt;&lt;/blockquote&gt;

&lt;/p&gt;



&lt;p&gt;Gloria&#38;#39;s reply is a wonderful example of her wit and Larry&#38;#39;s humility:&lt;/p&gt;

&lt;p&gt;&lt;blockquote&gt;

&lt;/p&gt;



&lt;p&gt;Personally I don&#38;#39;t think Larry&#38;#39;s life is all that compelling--he has spent most of it typing on a computer keyboard or in long bouts of just thinking (while staring at a computer screen). But yeah, he changed the world.&lt;/p&gt;

&lt;p&gt;&lt;/blockquote&gt;

&lt;/p&gt;



&lt;p&gt;The story of Larry Wall is intertwined with the story of Perl and the early Internet (between the &lt;a href=&#34;https://en.wikipedia.org/wiki/Unix_wars&#34;&gt;Unix wars&lt;/a&gt; and the &lt;a href=&#34;https://en.wikipedia.org/wiki/Dot-com_bubble&#34;&gt;Dot-com bubble&lt;/a&gt; burst).&lt;/p&gt;

&lt;p&gt;The way I saw this biographical project involved a data collection step: find and save as much of what was already available (interviews, videos, etc). Another side of it was producing more data: get Larry&#38;#39;s answers to the specific questions I had, and get the people around him to talk.&lt;/p&gt;

&lt;p&gt;The project is currently in a limbo state: I have collected some material, but Larry is mostly retired, and doesn&#38;#39;t seem very interested in talking about all this. I eventually lost steam, and moved to other things.&lt;/p&gt;

&lt;h4 id=&#34;Why-create-another-podcast&#34;&gt;Why create another podcast?&lt;/h4&gt;

&lt;p&gt;Back to the podcast and why I created it.&lt;/p&gt;

&lt;p&gt;I slowly made my way into recording a podcast by listening to a few of them.&lt;/p&gt;

&lt;p&gt;In 2021, my colleague &lt;a href=&#34;https://hachyderm.io/@bailey_writes&#34;&gt;Bailey Stewart&lt;/a&gt; suggested to me the &lt;a href=&#34;https://onthemetal.transistor.fm/&#34;&gt;On the Metal&lt;/a&gt; podcast. After listening to all episodes (and waiting for the next one for a very long time, not realizing they had moved on to another format), I started listening to &lt;a href=&#34;https://www.fastmail.com/digitalcitizen/&#34;&gt;Digital Citizen&lt;/a&gt;, by our very own &lt;a href=&#34;https://metacpan.org/author/RJBS&#34;&gt;Ricardo Signes&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;It was Ricardo who suggested to me in January 2024:&lt;/p&gt;

&lt;p&gt;&lt;blockquote&gt;

&lt;/p&gt;



&lt;p&gt;Advent of Computing is excellent, and I think you&#38;#39;d really like it.&lt;/p&gt;

&lt;p&gt;&lt;/blockquote&gt;

&lt;/p&gt;



&lt;p&gt;He was right on both counts: &lt;a href=&#34;https://adventofcomputing.com/&#34;&gt;Advent of Computing&lt;/a&gt; is excellent, and I really like it.&lt;/p&gt;

&lt;p&gt;The history of computing and programming is one of my favorite topics (my shelves contain both books about the history of computing, and historical computing books).&lt;/p&gt;

&lt;p&gt;The unwritten book project, combined with my recent interest in computing podcasts, planted the idea of making my own podcast in my head. Telling the story of Larry Wall&#38;#39;s enormous influence on modern computing is a herculean task, probably beyond my reach, but that wouldn&#38;#39;t prevent me from trying to record some fireside stories of the Perl community.&lt;/p&gt;

&lt;p&gt;A little bit over a year ago, as I was trying to write my &lt;a href=&#34;https://perladvent.org/2024/2024-12-22.html&#34;&gt;Advent Calendar entry&lt;/a&gt; for 2024, I started talking with &lt;a href=&#34;https://metacpan.org/author/OALDERS&#34;&gt;Olaf Alders&lt;/a&gt; about the idea that had taken root in my head:&lt;/p&gt;

&lt;p&gt;&lt;blockquote&gt;

&lt;/p&gt;



&lt;p&gt;I&#38;#39;m listening to podcasts, and there&#38;#39;s one I&#38;#39;m slowly getting into &lt;a href=&#34;https://oxide.computer/podcasts/oxide-and-friends&#34;&gt;https://oxide.computer/podcasts/oxide-and-friends&lt;/a&gt; It feels like being in a bar conversation with other technical people. I don&#38;#39;t always fully understand what they&#38;#39;re talking about, but I like what I hear.&lt;/p&gt;

&lt;p&gt;I&#38;#39;m been thinking about having something like this with people I know for some time, thinking about doing it at work (but being worried that the suits would not like the idea of a recording, and that maybe the participants wouldn&#38;#39;t like it too much either). I already do &#38;quot;old men ranting calls&#38;quot; with colleagues, but we haven&#38;#39;t recorded them so far. Would probably be too controversial anyway...&lt;/p&gt;

&lt;p&gt;Now I&#38;#39;m wondering if an idea like that could work with Perl people. We&#38;#39;re used to hanging out in bars, after all! What do you think?&lt;/p&gt;

&lt;p&gt;&lt;/blockquote&gt;

&lt;/p&gt;



&lt;p&gt;To which Olaf replied:&lt;/p&gt;

&lt;p&gt;&lt;blockquote&gt;

&lt;/p&gt;



&lt;p&gt;I think it would be an interesting experiment. I have fond memories of Perlcast: &#38;lt;https://perlcast.net/&#38;gt;&lt;/p&gt;

&lt;p&gt;&lt;/blockquote&gt;

&lt;/p&gt;



&lt;p&gt;I believe Olaf&#38;#39;s superpower is to encourage people to actually do what they already wanted to do in the first place, and just jump in.&lt;/p&gt;

&lt;h5 id=&#34;JFDI-in-action&#34;&gt;JFDI in action&lt;/h5&gt;

&lt;p&gt;This was also an experiment in getting things done in spite of my perfectionist mindset. Better make something now and improve it over time, than plan for the perfect outcome and never achieve anything. (As noted earlier, ideas are easy. The real effort is in delivering on those ideas.)&lt;/p&gt;

&lt;p&gt;Over the years, I&#38;#39;ve tried to think less and do more, because I also know that (according to &lt;a href=&#34;https://medium.com/@bre/the-cult-of-done-manifesto-724ca1c2ff13&#34;&gt;The Cult of Done Manifesto&lt;/a&gt;), &#38;quot;Done is the engine of more&#38;quot;. And I want to have produced more than just ideas with my time in this world.&lt;/p&gt;

&lt;p&gt;Anyway, at the end of December 2024, we picked the topic &lt;i&gt;du jour&lt;/i&gt; (the new Perl logo), grabbed everyone who was involved, and eventually recorded our conversation. &lt;a href=&#34;https://metacpan.org/CONTRA&#34;&gt;Thibault Duponchelle&lt;/a&gt; helped with editing this first episode (which was numbered 0, obviously), while I slapped the new Perl logo on the podcast logo (I&#38;#39;ve been having a lot of fun creating the logos for the individual episodes!) and created a &lt;a href=&#34;https://underbar.cpan.io&#34;&gt;static web site&lt;/a&gt; to host &lt;a href=&#34;https://underbar.cpan.io/feed.rss&#34;&gt;the feed&lt;/a&gt;. In our excitement, we went from recording to publication in little more than a week!&lt;/p&gt;

&lt;p&gt;I managed to convince Olaf to be my co-host: I think we complement each other well, and that widens the scope of questions we ask to our guests.&lt;/p&gt;

&lt;h5 id=&#34;Podcasting-as-data-collection&#34;&gt;Podcasting as data collection&lt;/h5&gt;

&lt;p&gt;My goal is not just to record those interesting conversations with the people who make things happen. Using modern technology, it&#38;#39;s very easy to have good enough transcripts. Apple Podcasts generates one automatically after publication, and when using Zoom (thanks to &lt;a href=&#34;https://perlfoundation.org/&#34;&gt;The Perl and Raku Foundation&lt;/a&gt; for letting us use their license to record some of our episodes), we have a transcript along the raw recording.&lt;/p&gt;

&lt;p&gt;My plan is not only to edit and add those as closed captions for the podcast, but also put them on the &lt;a href=&#34;https://underbar.cpan.io/&#34;&gt;web site&lt;/a&gt;, for better searchability and indexing. Adding notes for the readers is also a goal. This would let us explain the jargon, and point to more sources for those who want to dig in.&lt;/p&gt;

&lt;p&gt;I want to record witness accounts, from primary sources, of what the Perl and Open Source communities have achieved over the years.&lt;/p&gt;

&lt;h4 id=&#34;How-long-will-this-last&#34;&gt;How long will this last?&lt;/h4&gt;

&lt;p&gt;I didn&#38;#39;t want to fall into the classic blunder of buying new gear for a new project, and then leaving it gathering dust on a shelf because the project went nowhere. So I promised myself I&#38;#39;d only get a proper microphone when I have published enough episodes with a regular schedule. My self-imposed target was the end of the year and the holiday season. By then, we&#38;#39;ll have published &lt;a href=&#34;https://underbar.cpan.io/episodes/&#34;&gt;9 episodes&lt;/a&gt;, and I expect to find a nice microphone under the tree this year. (Here is your connection to Santa Claus for this Advent Calendar entry!)&lt;/p&gt;

&lt;p&gt;Hopefully, as long as there are people interested in talking about past, present and future Perl, we&#38;#39;ll be able to get them to talk to us. I&#38;#39;ve set my sights on a relaxed schedule (one episode a month), which doesn&#38;#39;t put me under too much pressure. I&#38;#39;m also not doing this alone: Olaf Alders is co-hosting the podcast with me, Salve Nielsen is bringing his ideas and contact list to the talk, and Thibault Duponchelle is regularly reminding me of my responsibilities as a publisher. Dave Cross made one of the first pull requests, to add a prominent link to the podcast feed on every page of the web site. And of course, &lt;a href=&#34;https://underbar.cpan.io/cast/&#34;&gt;over 20 people&lt;/a&gt; have been talking with us on the podcast!&lt;/p&gt;

&lt;p&gt;I&#38;#39;ve paced myself, because I&#38;#39;m in this for the long run. One episode a month seem about right as a schedule. I want to have more of those conversations, I want to hear the story of Perl and its communities, and more generally, of Open Source and Unix communities and projects.&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/book/the_underbar/blob/main/EPISODE-IDEAS.md&#34;&gt;We haven&#38;#39;t run out of topics yet.&lt;/a&gt;&lt;/p&gt;

&lt;h5 id=&#34;The-first-year&#34;&gt;The first year&lt;/h5&gt;

&lt;p&gt;After the first episode, we just ran from there: at the end of February we had recorded another episode, on a more controversial topic (&lt;a href=&#34;https://github.com/Perl/PPCs/blob/main/ppcs/ppc0025-perl-version.md&#34;&gt;Perl 42&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It took more time than expected to get it published (May), but we respected the self-imposed schedule of one episode a month since (the August episode was delayed until September; since there was also a September episode, so I count this as within the margin of error).&lt;/p&gt;

&lt;p&gt;Here&#38;#39;s a summary of our published episodes:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Episode 0 - &lt;a href=&#34;https://underbar.cpan.io/episodes/0&#34;&gt;The New Perl Logo&lt;/a&gt;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Episode 1 and 2 - &lt;a href=&#34;https://underbar.cpan.io/episodes/0&#34;&gt;Perl leadership&lt;/a&gt; and &lt;a href=&#34;https://underbar.cpan.io/episodes/2&#34;&gt;Perl 42&lt;/a&gt;&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During the Perl Toolchain Summit 2025 in Leipzig, &lt;a href=&#34;https://metacpan.org/SJN&#34;&gt;Salve J. Nilsen&lt;/a&gt; lended his recording material, and we spent the entire Sunday recording conversations with teams assembled there. This led to four more episodes:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Episode 3 - &lt;a href=&#34;https://underbar.cpan.io/episodes/3&#34;&gt;MetaCPAN&lt;/a&gt;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Episode 5 - &lt;a href=&#34;https://underbar.cpan.io/episodes/4&#34;&gt;Test::Smoke&lt;/a&gt;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Episode 6 - &lt;a href=&#34;https://underbar.cpan.io/episodes/6&#34;&gt;CPAN Testers&lt;/a&gt;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Episode 7 - &lt;a href=&#34;https://underbar.cpan.io/episodes/7&#34;&gt;CPAN Security Group&lt;/a&gt;&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During PTS, I also spent some time talking with Salve about the upcoming European CRA law, and we thought it would be useful to record a version of that conversation for a larger audience:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Episode 4 - &lt;a href=&#34;https://underbar.cpan.io/episodes/4&#34;&gt;The Cyber Resilience Act&lt;/a&gt;&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Salve has a lot of contacts in the Open Source community, and he managed to get a slot on &lt;a href=&#34;https://www.internetsociety.org/author/kolkman/&#34;&gt;Olaf Kolkman&lt;/a&gt;&#38;#39;s busy schedule. We recorded a three hour conversation with the pioneer of DNSSEC and prominent Internet Society member Olaf Kolkman, which will make up our next two episodes.&lt;/p&gt;

&lt;h5 id=&#34;Help-needed&#34;&gt;Help needed&lt;/h5&gt;

&lt;p&gt;The podcast format captures the conversations, and makes it easy to enjoy those conversations while doing something else (I do most of my own podcast listening while walking outside or cooking). But if this material is meant to be used as &#38;quot;primary source&#38;quot; for some future work about the history of Perl and its community, text is going to be the best format long term.&lt;/p&gt;

&lt;p&gt;If there are topics you want to listen to, or corrections you want to make to the web site, you can write to &lt;code&gt;underbar&lt;/code&gt; AT &lt;code&gt;cpan.io&lt;/code&gt; or submit a pull request to &lt;a href=&#34;https://github.com/book/the_underbar&#34;&gt;the repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can&#38;#39;t wait to hear from you!&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-19T00:00:00Z</updated><category term="Perl"/><author><name>Philippe Bruhat</name></author></entry><entry><title>Safer last-minute hotfixes before Christmas</title><link href="https://perladvent.org/2025/2025-12-18.html"/><id>https://perladvent.org/2025/2025-12-18.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;&lt;img src=&#34;this-is-fine.jpg&#34; alt=&#34;This is fine.&#34;&gt;

&lt;/p&gt;



&lt;p&gt;Christmas is just around the corner, but a critical misconfiguration in &lt;i&gt;ChristmasPresentDistributor&lt;/i&gt; service has been unearthed! Angry parents start calling the Christmas Inc. Support Center arguing that their children haven&#38;#39;t gotten their presents yet. Social media is in utter chaos and the stock price took a nosedive. What a disaster! An incident response team consisting of a single elf, Frosty, has been dispatched to deal with the situation ASAP while the programmer team develops, reviews and publishes the fix in the &lt;i&gt;ServiceConfigurator&lt;/i&gt; service codebase. There could be many hours before it is complete and all the services are reloaded!&lt;/p&gt;

&lt;p&gt;Frosty was trusted with temporary admin permissions to make sure he can set it straight. The fix turned out to actually be pretty straightforward - just modify a couple of config files, but... &lt;i&gt;*gasp*&lt;/i&gt; directly on a &lt;b&gt;production server&lt;/b&gt;! To make sure he doesn&#38;#39;t make the situation worse, he should at the very least:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Back all the files up&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make changes in all the files, preferably simultaneously&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Be able to restore the files from backups at any time, preferably simultaneously&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As it turned out, there&#38;#39;s a tool called &lt;a href=&#34;https://metacpan.org/module/App::Transpierce&#34;&gt;App::Transpierce&lt;/a&gt; that was developed by a fellow Perl hacker who often had to deal with this kind of... &lt;i&gt;incidents&lt;/i&gt;. It is written in perl 5.10 and abuses the ubiquity of perl interpreter on Linux machines. The script can export itself into a single file, and then get copied into any remote environment using &lt;code&gt;scp&lt;/code&gt; or alike, to do the dirty (but much needed) work. Since perl is everywhere, it should work everywhere with only core modules installed!&lt;/p&gt;

&lt;p&gt;How does it work? First, Frosty created a &lt;code&gt;transpierce.conf&lt;/code&gt; config file for it, which could be as easy as a list of files he wanted to modify:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;        /path/to/file1.conf
        /path/to/file2.yml&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This file can be placed in regular user&#38;#39;s home directory. With the &lt;code&gt;transpierce&lt;/code&gt; script copied there too, he could prepare a target directory for his working environment:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;mkdir PROD_HACK&lt;br /&gt;cp transpierce.conf PROD_HACK&lt;br /&gt;./transpierce --describe PROD_HACK&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;--describe&lt;/code&gt; call looked at his configuration, checked the files he listed and dumped a list of actions. This was a dry run, so nothing actually got done yet!&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;        Files specified in the config file
        /path/to/file1.conf
          mode -&#38;gt; 0644
          uid -&#38;gt; 0
          gid -&#38;gt; 0
        -------
        /path/to/file2.yml
          mode -&#38;gt; 0644
          uid -&#38;gt; 0
          gid -&#38;gt; 0
        -------

        Actions:
        Create a directory
          mkdir -&#38;gt; PROD_HACK/restore
        -------
        Create a directory
          mkdir -&#38;gt; PROD_HACK/deploy
        -------
        Make copies of /path/to/file1.conf
          copy -&#38;gt; PROD_HACK/restore/__path__to__file1.conf
          copy -&#38;gt; PROD_HACK/deploy/__path__to__file1.conf
        -------
        Make copies of /path/to/file2.yml
          copy -&#38;gt; PROD_HACK/restore/__path__to__file2.yml
          copy -&#38;gt; PROD_HACK/deploy/__path__to__file2.yml
        -------
        Create script in PROD_HACK/restore.sh
          restore -&#38;gt; /path/to/file1.conf
          restore -&#38;gt; /path/to/file2.yml
        -------
        Create script in PROD_HACK/deploy.sh
          deploy -&#38;gt; /path/to/file1.conf
          deploy -&#38;gt; /path/to/file2.yml
        -------
        Create script in PROD_HACK/diff.sh
          diff -&#38;gt; /path/to/file1.conf
          diff -&#38;gt; /path/to/file2.yml
        -------&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Frosty had a long look at that output. The script inspected his actual production files to determine their original mode, uid and gid. It listed a bunch of actions to perform: create &lt;code&gt;restore&lt;/code&gt; and &lt;code&gt;deploy&lt;/code&gt; directories, copy production files into them and create three scripts: &lt;code&gt;restore.sh&lt;/code&gt;, &lt;code&gt;deploy.sh&lt;/code&gt; and &lt;code&gt;diff.sh&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Frosty was satisfied with the description, so he ran the script again without the &lt;code&gt;--describe&lt;/code&gt; flag, which performed all the listed actions. But wait, shell scripts? I thought this story was about Perl?!&lt;/p&gt;

&lt;p&gt;You see, in a production environment, it is &lt;b&gt;much&lt;/b&gt; better to have a shell script that contains no magic and can be audited before actually running it as root. For example, this is what the &lt;code&gt;deploy.sh&lt;/code&gt; script looked like:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;cp &#38;quot;deploy/__path__to__file1.conf&#38;quot; &#38;quot;/path/to/file1.conf&#38;quot;&lt;br /&gt;chmod 0644 &#38;quot;/path/to/file1.conf&#38;quot;&lt;br /&gt;chown 0 &#38;quot;/path/to/file1.conf&#38;quot;&lt;br /&gt;chgrp 0 &#38;quot;/path/to/file1.conf&#38;quot;&lt;br /&gt;&lt;br /&gt;cp &#38;quot;deploy/__path__to__file2.yml&#38;quot; &#38;quot;/path/to/file2.yml&#38;quot;&lt;br /&gt;chmod 0644 &#38;quot;/path/to/file2.yml&#38;quot;&lt;br /&gt;chown 0 &#38;quot;/path/to/file2.yml&#38;quot;&lt;br /&gt;chgrp 0 &#38;quot;/path/to/file2.yml&#38;quot;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is easy to understand. This does not put the fate of a production server into the hands of a 450-line third-party script. This is easy to extend if some additional tasks need doing (like reloading the configuration).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;restore.sh&lt;/code&gt; script looked exactly the same, but it copied from &lt;code&gt;restore&lt;/code&gt; directory instead. The third script, &lt;code&gt;diff.sh&lt;/code&gt;, had a different purpose: it compares the files from &lt;code&gt;restore&lt;/code&gt; directory and their actual locations using &lt;code&gt;diff&lt;/code&gt; command. If it returns no output, it means the files are unchanged.&lt;/p&gt;

&lt;p&gt;With everything set up, the next step was to go into the &lt;code&gt;deploy&lt;/code&gt; directory and modify the files. Their names looked a bit goofy now, with all directory separators replaced with double underscores, but at least it made the structure flat. Frosty was already sure about the required changes, so this step was actually really easy. He made sure not to touch the &lt;code&gt;restore&lt;/code&gt; directory, because that could mess up his backup files.&lt;/p&gt;

&lt;p&gt;It was time for the grand finale. Up until this point, the risk of breaking the production installation was exactly zero. Since he was done, Frosty had to run the dreaded &lt;code&gt;sudo ./deploy.sh&lt;/code&gt; command and hope for the best. His hands were shaking as he typed his password. His throat felt dry, even though he&#38;#39;d been sipping on a cup of mulled wine all this time. It was however a great consolation to know that he could always run &lt;code&gt;sudo ./restore.sh&lt;/code&gt; and undo it all within seconds. So &lt;i&gt;&#38;quot;YOLO&#38;quot;&lt;/i&gt;, as young people say!&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-18T00:00:00Z</updated><category term="Perl"/><author><name>Bartosz Jarzyna</name></author></entry><entry><title>The Elves Learn to be Lazy</title><link href="https://perladvent.org/2025/2025-12-17.html"/><id>https://perladvent.org/2025/2025-12-17.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;&lt;img src=&#34;two_bulls_clash_antlers.jpg&#34; alt=&#34;Not actually moose, but two elk bulls locking antlers in the snow.&#34; /&gt; &lt;p class=&#34;attribution&#34;&gt;&#34;&lt;a rel=&#34;noopener noreferrer&#34; href=&#34;https://www.flickr.com/photos/51986662@N05/6258468123&#34;&gt;Two Bulls Clash Antlers&lt;/a&gt;&#34; by &lt;a rel=&#34;noopener noreferrer&#34; href=&#34;https://www.flickr.com/photos/51986662@N05&#34;&gt;USFWS Mountain Prairie&lt;/a&gt; is licenced under &lt;a rel=&#34;noopener noreferrer&#34; href=&#34;https://creativecommons.org/licenses/by/2.0/?ref=openverse&#34;&gt;CC BY 2.0 &lt;img src=&#34;https://mirrors.creativecommons.org/presskit/icons/cc.svg&#34; style=&#34;height: 1em; margin-right: 0.125em; display: inline;&#34; /&gt;&lt;img src=&#34;https://mirrors.creativecommons.org/presskit/icons/by.svg&#34; style=&#34;height: 1em; margin-right: 0.125em; display: inline;&#34; /&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;/p&gt;



&lt;h3 id=&#34;The-Naughty-or-Nice-Subsystem&#34;&gt;The Naughty or Nice Subsystem&lt;/h3&gt;

&lt;p&gt;Putting together Santa&#38;#39;s naughty or nice list is one of the elves&#38;#39; most important tasks. To assist them a Naughty or Nice Subsystem (NORN) has been developed over many years.&lt;/p&gt;

&lt;p&gt;Initially the system was very simple. The &lt;code&gt;norn&lt;/code&gt; table had a row for every child and Santa would go through and set the &lt;code&gt;is_nice&lt;/code&gt; column to either true or false. The elves would then run the list generation routine, Santa would check it twice, and Christmas would proceed.&lt;/p&gt;

&lt;p&gt;After a particularly difficult year Holly the elf, wearing her PM hat, decided that this decision couldn&#38;#39;t be left to Santa&#38;#39;s whims and that each child&#38;#39;s actions over the year should be categorised and stored as either &lt;code&gt;good&lt;/code&gt; or &lt;code&gt;bad&lt;/code&gt; in the &lt;code&gt;norn&lt;/code&gt; table. The child was considered &lt;i&gt;nice&lt;/i&gt; if they had been good more often than bad.&lt;/p&gt;

&lt;p&gt;Some time later Holly thought that some actions were either &lt;i&gt;very_good&lt;/i&gt; or &lt;i&gt;very_bad&lt;/i&gt; and should count double.&lt;/p&gt;

&lt;p&gt;As each set of modifications took place the &lt;code&gt;is_nice&lt;/code&gt; method became more complicated, but the tests didn&#38;#39;t. The elves were loath to work on such important code with a limited safety net.&lt;/p&gt;

&lt;h3 id=&#34;A-New-Algorithm&#34;&gt;A New Algorithm&lt;/h3&gt;

&lt;p&gt;Last year young Sophie Farrington from Finsbury Park, somewhat upset at finding only coal in her stocking, issued an FOI request. She subsequently claimed that many of her actions which had been categorised as bad were, at worst, cheeky and were perhaps even charming. Mrs Claus agreed, and tasked the elves in the R&#38;amp;D department to come up with a more fair algorithm.&lt;/p&gt;

&lt;p&gt;Eventually the algorithm was finalised and Holly created a new story for the backlog. It replaced the old &lt;i&gt;very_good&lt;/i&gt; and &lt;i&gt;very_bad&lt;/i&gt; categories with more nuanced categories of &lt;i&gt;noble&lt;/i&gt;, &lt;i&gt;virtuous&lt;/i&gt;, &lt;i&gt;errant&lt;/i&gt; and &lt;i&gt;iniquitous&lt;/i&gt; to go with the original &lt;i&gt;good&lt;/i&gt; and &lt;i&gt;bad&lt;/i&gt; classifications. And there was a suitably complicated algorithm to accompany them to determine the ultimate judgement.&lt;/p&gt;

&lt;p&gt;But none of the elves wanted to pick it up. The code was just too brittle and, since the external consulting company had recommended Santa only checking the list once as a cost-saving measure, and later that the checking should be completely skipped, no one wanted to be known as the elf who messed up the Naughty or Nice list and ruined Christmas.&lt;/p&gt;

&lt;p&gt;But doing nothing wasn&#38;#39;t an option either.&lt;/p&gt;

&lt;h3 id=&#34;The-Reindeers-Cousins&#34;&gt;The Reindeer&#38;#39;s Cousins&lt;/h3&gt;

&lt;p&gt;With Christmas on the line, Santa called everyone in to find a solution. It was Blitzen who had the bright idea of getting the reindeer&#38;#39;s cousins, the moose, involved.&lt;/p&gt;

&lt;p&gt;Donner explained the concept. By converting the plain OO class to use &lt;a href=&#34;https://metacpan.org/module/Moose&#34;&gt;Moose&lt;/a&gt; it becomes easy to break up large methods. The key is to use lazy attributes to calculate variables within the algorithm. This simplifies the code around the algorithm and, crucially, makes it easy to test parts of the algorithm rather than just the end result.&lt;/p&gt;

&lt;p&gt;Prancer further explained that by using lazy attributes the return value is only calculated once, and then it is stored and immediately returned on subsequent calls. Even better, each attribute is calculated only when first accessed. If the code never calls &lt;code&gt;$norn-&#38;gt;noble_count&lt;/code&gt;, that expensive calculation never runs at all. This helps keep the code efficient too, which is important with a list of over one billion entries.&lt;/p&gt;

&lt;h3 id=&#34;Laziness&#34;&gt;Laziness&lt;/h3&gt;

&lt;p&gt;The elves took on the project. And it wasn&#38;#39;t too difficult. The class API didn&#38;#39;t really change, it just expanded. So existing code using &lt;code&gt;Norn-&#38;gt;new(...)-&#38;gt;is_nice&lt;/code&gt; worked identically with &lt;code&gt;NornV2&lt;/code&gt;, making the migration straightforward and low-risk.&lt;/p&gt;

&lt;p&gt;The original code contained one large method with lots of expensive calculations. It looked something like:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;package&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Norn&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;5.42.0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$class&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%args&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;magic&#34;&gt;@_&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;die&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;child_id is required&#38;quot;&lt;/span&gt;    &lt;span class=&#34;word&#34;&gt;unless&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;exists&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$args&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;child_id&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;die&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;child_id must be an Int&#38;quot;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;unless&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$args&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;child_id&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=~&lt;/span&gt; &lt;span class=&#34;match&#34;&gt;/^\d+$/&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;bless&lt;/span&gt; &lt;span class=&#34;cast&#34;&gt;\&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;%args&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$class&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is_nice&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;magic&#34;&gt;@_&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$sum&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # expensive calculation to get event data&lt;br /&gt;&lt;/span&gt;  &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$event_data&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;...&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # expensive calculation of good count from $event_data&lt;br /&gt;&lt;/span&gt;  &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$good&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$sum&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$good&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # expensive calculation of bad count from $event_data&lt;br /&gt;&lt;/span&gt;  &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$bad&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$sum&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$bad&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # expensive calculation of very good count from $event_data&lt;br /&gt;&lt;/span&gt;  &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$very_good&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$sum&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$very_good&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # expensive calculation of very bad count from $event_data&lt;br /&gt;&lt;/span&gt;  &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$very_bad&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$sum&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$very_bad&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$sum&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The test code was fairly simple:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;#!/usr/bin/perl&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;5.42.0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Test2::V0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Norn&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;subtest&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;setup&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # set up test fixtures&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;subtest&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is_nice&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$norn&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Norn&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;child_id&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$norn&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;child_id&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;child_id is 1&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;ok&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$norn&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;is_nice&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;is nice&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;subtest&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;teardown&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # tear down test fixtures&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;done_testing&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The updated code was obviously more involved but was broken into individual methods, each of which was simple enough to understand and test individually.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;package&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;NornV2&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;5.42.0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Moose&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;child_id&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;ro&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;isa&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;Int&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;required&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;event_data&lt;/span&gt;       &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;ro&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;lazy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;builder&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;_build_event_data&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;noble_count&lt;/span&gt;      &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;ro&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;lazy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;builder&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;_build_noble_count&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;good_count&lt;/span&gt;       &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;ro&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;lazy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;builder&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;_build_good_count&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;virtuous_count&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;ro&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;lazy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;builder&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;_build_virtuous_count&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;errant_count&lt;/span&gt;     &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;ro&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;lazy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;builder&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;_build_errant_count&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;bad_count&lt;/span&gt;        &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;ro&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;lazy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;builder&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;_build_bad_count&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;iniquitous_count&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;ro&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;lazy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;builder&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;_build_iniquitous_count&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;_build_event_data&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # expensive calculation to get event data&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;_build_noble_count&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$event_data&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;event_data&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # expensive calculation of noble count from $event_data&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;_build_good_count&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$event_data&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;event_data&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # expensive calculation of good count from $event_data&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;_build_virtuous_count&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$event_data&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;event_data&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # expensive calculation of virtuous count from $event_data&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;_build_errant_count&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$event_data&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;event_data&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # expensive calculation of errant count from $event_data&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;_build_bad_count&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$event_data&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;event_data&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # expensive calculation of bad count from $event_data&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;_build_iniquitous_count&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$event_data&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;event_data&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # expensive calculation of iniquitous count from $event_data&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is_nice&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$sum&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # assign $sum from a complicated algorithm involving&lt;br /&gt;&#38;nbsp;&#38;nbsp;# $self-&#38;gt;noble_count, $self-&#38;gt;good_count, $self-&#38;gt;virtuous_count,&lt;br /&gt;&#38;nbsp;&#38;nbsp;# $self-&#38;gt;errant_count, $self-&#38;gt;bad_count, $self-&#38;gt;iniquitous_count&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$sum&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;no&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Moose&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;__PACKAGE__&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;meta&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;make_immutable&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And the test code was still fairly simple but, importantly, was able to test individual data values stored in the lazy attributes.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;#!/usr/bin/perl&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;5.42.0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Test2::V0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;NornV2&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;subtest&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;setup&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # set up test fixtures&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;subtest&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is_nice&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$norn&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;NornV2&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;child_id&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$norn&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;child_id&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;child_id is 1&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$gold_event_data&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;  &lt;span class=&#34;comment&#34;&gt;# expected event data for child_id 1&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$norn&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;event_data&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$gold_event_data&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;event data&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$norn&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;noble_count&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;      &lt;span class=&#34;number&#34;&gt;15&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;  &lt;span class=&#34;double&#34;&gt;&#38;quot;noble count&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$norn&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;good_count&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;       &lt;span class=&#34;number&#34;&gt;104&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;good count&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$norn&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;virtuous_count&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;   &lt;span class=&#34;number&#34;&gt;226&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;virtuous count&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$norn&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;errant_count&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;     &lt;span class=&#34;number&#34;&gt;51&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;  &lt;span class=&#34;double&#34;&gt;&#38;quot;errant count&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$norn&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;bad_count&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;        &lt;span class=&#34;number&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;  &lt;span class=&#34;double&#34;&gt;&#38;quot;bad count&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$norn&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;iniquitous_count&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;   &lt;span class=&#34;double&#34;&gt;&#38;quot;iniquitous count&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;ok&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$norn&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;is_nice&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;is nice&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;subtest&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;teardown&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # tear down test fixtures&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;done_testing&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;Conclusion&#34;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;The elves were very happy with their new code. With it broken down into manageable and individually testable methods they didn&#38;#39;t fear making changes to the Naughty or Nice system.&lt;/p&gt;

&lt;p&gt;Dasher and Comet noted that not only was the code improved, but the elves were also able to crank it out faster and with fewer bugs.&lt;/p&gt;

&lt;p&gt;Mrs Claus was glad that the coal deliveries were reduced.&lt;/p&gt;

&lt;p&gt;And young Sophie Farrington turned out to be nice after all, so Santa rewarded her with a stocking full of chocolate coins and tangerines.&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-17T00:00:00Z</updated><category term="Perl"/><author><name>Paul Johnson</name></author></entry><entry><title>Auto-instrument your code with OpenTelemetry</title><link href="https://perladvent.org/2025/2025-12-16.html"/><id>https://perladvent.org/2025/2025-12-16.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;&lt;figure&gt; &lt;img src=&#34;hot-chocolate.jpg&#34;&gt; &lt;figcaption&gt; &lt;a href=&#34;https://www.flickr.com/photos/fatty/2226622699&#34;&gt;&#34;Hot chocolate&#34;&lt;/a&gt; by &lt;a href=&#34;https://www.flickr.com/photos/fatty/&#34;&gt;David Thompson&lt;/a&gt;, &lt;a href=&#34;https://creativecommons.org/licenses/by-nc-sa/2.0/deed.en&#34;&gt;CC BY-NC-SA 2.0&lt;/a&gt;&lt;/figcaption&gt; &lt;/figure&gt;

&lt;/p&gt;



&lt;p&gt;A whole year had gone by since the elves at Santa&#38;#39;s workshop had started using &lt;a href=&#34;https://metacpan.org/module/OpenTelemetry&#34;&gt;OpenTelemetry&lt;/a&gt; to collect and export telemetry from their multiple services, and things had been going well. But in a meeting room deep under the icy cover of the North Pole, trouble was brewing.&lt;/p&gt;

&lt;p&gt;&#38;quot;Well what do you suggest, then?&#38;quot;, asked Duende Juniorsson, the junior elf in the team.&lt;/p&gt;

&lt;p&gt;&#38;quot;I don&#38;#39;t have a solution, I just know where the problem is&#38;quot;, answered Gnomo Knullpointer perhaps a little more exasperated than he should be. &#38;quot;I know that OpenTelemetry has been a game changer, and that we want to have more of it. But instrumenting code has a cost, and I&#38;#39;m just wondering how much longer we can keep paying it&#38;quot;.&lt;/p&gt;

&lt;p&gt;The elves had been relying on instrumentation libraries to generate telemetry without having to make changes to their own codebase. This &lt;a href=&#34;https://opentelemetry.io/docs/concepts/instrumentation/zero-code&#34;&gt;&#38;quot;zero-code instrumentation&#38;quot;&lt;/a&gt; had been the key selling point when they started using OpenTelemetry. But it meant that they were limited either by the libraries that were available, or by the resources they could dedicate to writing their own.&lt;/p&gt;

&lt;p&gt;&#38;quot;If we want to instrument something, and there is no instrumentation library for it, &lt;i&gt;and&lt;/i&gt; we don&#38;#39;t have the resources to write our own, then the only other option is to instrument it manually&#38;quot;, said Duende.&lt;/p&gt;

&lt;p&gt;&#38;quot;Sure, but that is only marginally less work. And anything you touch will have to be tested&#38;quot;, said Tess &#38;#39;t Moor, the QA elf. &#38;quot;So we might be saving time for you developer elves, but only because I&#38;#39;ll be the one testing things&#38;quot;.&lt;/p&gt;

&lt;p&gt;There was a pause in the conversation, as each elf stopped to gather their thoughts. Santa, who was sitting at one end of the table looking nervous, did not like this. He did not like his elves getting upset and forgetting what Christmas was all about, but more than anything, he did not like being in a meeting where nobody was talking. It made him feel like maybe they were waiting for &lt;i&gt;him&lt;/i&gt; to say something, and under pressure the only thing he could ever think of saying was &#38;quot;Ho ho ho&#38;quot;. He was pretty sure this was not the right time to say it.&lt;/p&gt;

&lt;p&gt;Ada Slashd&#38;oacute;ttir, the team&#38;#39;s senior elf, cleared her throat and absentmindedly started tapping her cup of hot chocolate with a candy cane. &#38;quot;What if we get Perl to write the instrumentation code for us?&#38;quot;.&lt;/p&gt;

&lt;h3 id=&#34;Auto-auto-instrumentation&#34;&gt;Auto-auto-instrumentation&lt;/h3&gt;

&lt;p&gt;&#38;quot;Most OpenTelemetry instrumentation libraries look very similar&#38;quot;, she continued. &#38;quot;They declare methods in a package that need to be instrumented, and then monkey-patch them to generate the necessary telemetry.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;But not all telemetry is the same&#38;quot;, replied Gnomo. &#38;quot;OpenTelemetry specifies which attributes need to be set in what kinds of spans: a span representing an HTTP transaction needs to specify the URL it is for, one for a database operation needs to specify what database it is for, etc. We cannot have a one-size-fits-all approach&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;We can probably get 90% of the way with a generic approach&#38;quot;, continued Ada. &#38;quot;And worry about the last 10% when we get there&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;Isn&#38;#39;t this making the problem worse?&#38;quot;, asked Duende. &#38;quot;A minute ago we didn&#38;#39;t have the resources to write an instrumentation library, and now we are talking about writing a library to write instrumentation libraries?&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;Ah, but we don&#38;#39;t need to write it ourselves&#38;quot;, said Ada. &#38;quot;We can use the new &lt;a href=&#34;https://metacpan.org/module/OpenTelemetry::Instrumentation::namespace&#34;&gt;OpenTelemetry::Instrumentation::namespace&lt;/a&gt;&#38;quot;.&lt;/p&gt;

&lt;p&gt;The elves loaded up the documentation and dove in.&lt;/p&gt;

&lt;p&gt;Ada brought up her terminal. &#38;quot;Say you have some code that calls some package to do something&#38;quot;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;v5.36&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;UUID4::Tiny&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;create_uuid_string&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;say&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;create_uuid_string&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;That will print out a UUID&#38;quot;, said Duende.&lt;/p&gt;

&lt;p&gt;&#38;quot;Exactly&#38;quot;, replied Ada. &#38;quot;And if we add OpenTelemetry?&#38;quot;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;v5.36&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Dump spans to STDERR in prettified JSON format&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;BEGIN&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$ENV&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;OTEL_TRACES_EXPORTER&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;              &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;console&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$ENV&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;OTEL_PERL_EXPORTER_CONSOLE_FORMAT&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;json,pretty=1&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;OpenTelemetry&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;OpenTelemetry::SDK&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;UUID4::Tiny&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;create_uuid_string&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;say&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;create_uuid_string&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;Nothing will change&#38;quot;, said Gnomo. &#38;quot;We loaded the API and initialised the SDK, but since we are not generating any telemetry, nothing is different&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;Now let&#38;#39;s try loading the namespace instrumentation&#38;quot;, said Ada.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;v5.36&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Dump spans to STDERR in prettified JSON format&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;BEGIN&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$ENV&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;OTEL_TRACES_EXPORTER&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;              &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;console&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$ENV&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;OTEL_PERL_EXPORTER_CONSOLE_FORMAT&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;json,pretty=1&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;OpenTelemetry&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;OpenTelemetry::SDK&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;OpenTelemetry::Instrumentation&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;namespace&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;single&#34;&gt;&#39;UUID4::Tiny&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;];&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;UUID4::Tiny&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;create_uuid_string&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;say&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;create_uuid_string&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The output of the script had changed! The UUID was still being printed at the bottom of the screen, but above it were several lines with JSON encoded OpenTelemetry spans. Slightly edited for brevity, the output looked a little bit like this:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;    {
       &#38;quot;end_timestamp&#38;quot; : 1765582438.54618,
       &#38;quot;instrumentation_scope&#38;quot; : {
          &#38;quot;name&#38;quot; : &#38;quot;UUID4::Tiny&#38;quot;,
          &#38;quot;version&#38;quot; : &#38;quot;0.003&#38;quot;
       },
       &#38;quot;name&#38;quot; : &#38;quot;UUID4::Tiny::create_uuid&#38;quot;,
       &#38;quot;parent_span_id&#38;quot; : &#38;quot;dbed67eb5146795b&#38;quot;,
       &#38;quot;span_id&#38;quot; : &#38;quot;c2f03b363333f610&#38;quot;,
       &#38;quot;start_timestamp&#38;quot; : 1765582438.54615,
       &#38;quot;trace_id&#38;quot; : &#38;quot;386e98e4fa576fc5280e8da1f59d3031&#38;quot;,
       ...
    }
    {
       &#38;quot;end_timestamp&#38;quot; : 1765582438.54669,
       &#38;quot;instrumentation_scope&#38;quot; : {
          &#38;quot;name&#38;quot; : &#38;quot;UUID4::Tiny&#38;quot;,
          &#38;quot;version&#38;quot; : &#38;quot;0.003&#38;quot;
       },
       &#38;quot;name&#38;quot; : &#38;quot;UUID4::Tiny::is_uuid_string&#38;quot;,
       &#38;quot;parent_span_id&#38;quot; : &#38;quot;e266cf2b01725042&#38;quot;,
       &#38;quot;span_id&#38;quot; : &#38;quot;6165bab8a0b6f448&#38;quot;,
       &#38;quot;start_timestamp&#38;quot; : 1765582438.54666,
       &#38;quot;trace_id&#38;quot; : &#38;quot;386e98e4fa576fc5280e8da1f59d3031&#38;quot;,
       ...
    }
    {
       &#38;quot;end_timestamp&#38;quot; : 1765582438.54678,
       &#38;quot;instrumentation_scope&#38;quot; : {
          &#38;quot;name&#38;quot; : &#38;quot;UUID4::Tiny&#38;quot;,
          &#38;quot;version&#38;quot; : &#38;quot;0.003&#38;quot;
       },
       &#38;quot;name&#38;quot; : &#38;quot;UUID4::Tiny::uuid_to_string&#38;quot;,
       &#38;quot;parent_span_id&#38;quot; : &#38;quot;dbed67eb5146795b&#38;quot;,
       &#38;quot;span_id&#38;quot; : &#38;quot;e266cf2b01725042&#38;quot;,
       &#38;quot;start_timestamp&#38;quot; : 1765582438.54654,
       &#38;quot;trace_id&#38;quot; : &#38;quot;386e98e4fa576fc5280e8da1f59d3031&#38;quot;,
       ...
    }
    {
       &#38;quot;end_timestamp&#38;quot; : 1765582438.54686,
       &#38;quot;instrumentation_scope&#38;quot; : {
          &#38;quot;name&#38;quot; : &#38;quot;UUID4::Tiny&#38;quot;,
          &#38;quot;version&#38;quot; : &#38;quot;0.003&#38;quot;
       },
       &#38;quot;name&#38;quot; : &#38;quot;UUID4::Tiny::create_uuid_string&#38;quot;,
       &#38;quot;parent_span_id&#38;quot; : &#38;quot;0000000000000000&#38;quot;,
       &#38;quot;span_id&#38;quot; : &#38;quot;dbed67eb5146795b&#38;quot;,
       &#38;quot;start_timestamp&#38;quot; : 1765582438.54598,
       &#38;quot;trace_id&#38;quot; : &#38;quot;386e98e4fa576fc5280e8da1f59d3031&#38;quot;,
       ...
    }
    4c9debb9-6370-4d7c-94f2-a5f1799475ce&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;Hey, this is much more like it&#38;quot;, said Duende, who had got pretty familiar with OpenTelemetry spans. &#38;quot;You can see that they all share a &lt;code&gt;trace_id&lt;/code&gt;, and can track what span created which other span by looking at the &lt;code&gt;span_id&lt;/code&gt; and the &lt;code&gt;parent_span_id&lt;/code&gt;&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;Yeah, and look at the &lt;code&gt;name&lt;/code&gt;s&#38;quot;, said Gnomo. &#38;quot;They are the fully-qualified subroutine name of the code that executed. So you can tell that when we called &lt;code&gt;create_uuid_string&lt;/code&gt; it called &lt;code&gt;uuid_to_string&lt;/code&gt;, which itself called &lt;code&gt;is_uuid_string&lt;/code&gt;&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;And all of that without using a pre-made instrumentation library for &lt;a href=&#34;https://metacpan.org/module/UUID4::Tiny&#34;&gt;UUID4::Tiny&lt;/a&gt;, or manually instrumenting anything&#38;quot;, said Ada.&lt;/p&gt;

&lt;p&gt;&#38;quot;How does this even work?&#38;quot;, asked Duende after looking at the code a little more carefully. &#38;quot;We load the instrumentation before loading the code to be instrumented. How does it know what to instrument before we import it?&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;Good question!&#38;quot;, said Ada excitedly. &#38;quot;Let&#38;#39;s dive deeper.&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;Looking-under-the-hood&#34;&gt;Looking under the hood&lt;/h3&gt;

&lt;p&gt;&#38;quot;When you load the &lt;code&gt;namespace&lt;/code&gt; instrumentation with a rule like the one we gave it, it will look among the modules that have been loaded to see if any match the one we want to instrument&#38;quot;, said Ada. &#38;quot;In this case, since we haven&#38;#39;t loaded &lt;a href=&#34;https://metacpan.org/module/UUID4::Tiny&#34;&gt;UUID4::Tiny&lt;/a&gt;, it won&#38;#39;t find it&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;So how does it know when to instrument it?&#38;quot;, asked Gnomo.&lt;/p&gt;

&lt;p&gt;&#38;quot;It uses a &lt;a href=&#34;https://perldoc.perl.org/functions/require&#34;&gt;require hook&lt;/a&gt; that will execute when a module of interest is loaded. At that point, it installs the necessary instrumentation&#38;quot;, explained Ada.&lt;/p&gt;

&lt;p&gt;&#38;quot;Is that safe? Won&#38;#39;t that make everything slower?&#38;quot;, asked Duende.&lt;/p&gt;

&lt;p&gt;&#38;quot;To some extent, yes&#38;quot;, replied Ada. &#38;quot;Which is why this is not really made as a substitute for writing instrumentation libraries. It is meant as a way to facilitate the instrumentation of existing codebases, or as a stopgap measure to use when exploring what to instrument. But it&#38;#39;s hard to predict the impact in the abstract: we&#38;#39;ll always just have to benchmark things to see what the real impact is, and decide whether it makes sense to pay the price&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;That seems reasonable, but why does it need the hook at all?&#38;quot;, asked Gnomo. &#38;quot;Wouldn&#38;#39;t it be simpler to just make sure to load it after the import?&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;Ah, but there we hit another interesting caveat with this instrumentation&#38;quot;, replied Ada. &#38;quot;Let&#38;#39;s try it: what happens if we do that?&#38;quot;. Ada modified the code to look like this and re-ran it.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;v5.36&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Dump spans to STDERR in prettified JSON format&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;BEGIN&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$ENV&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;OTEL_TRACES_EXPORTER&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;              &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;console&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$ENV&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;OTEL_PERL_EXPORTER_CONSOLE_FORMAT&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;json,pretty=1&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Import the function first, then instrument&lt;br /&gt;# This probably won&#39;t do what you want!&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;UUID4::Tiny&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;create_uuid_string&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;OpenTelemetry&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;OpenTelemetry::SDK&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;OpenTelemetry::Instrumentation&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;namespace&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;single&#34;&gt;&#39;UUID4::Tiny&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;];&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;say&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;create_uuid_string&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;It still works&#38;quot;, said Gnomo. &#38;quot;I can see the OpenTelemetry traces&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;But there&#38;#39;s one span missing!&#38;quot;, said Duende. &#38;quot;We lost the one for &lt;code&gt;create_uuid_string&lt;/code&gt;&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;Precisely&#38;quot;, said Ada. &#38;quot;A case like this, where we load a package and import a function from that namespace into ours, is very common. But if we load the instrumentation &lt;i&gt;after&lt;/i&gt; we&#38;#39;ve imported it, when we execute the imported symbol from our own namespace we&#38;#39;ll be running the uninstrumented code. To solve this we have to instrument the code before importing it, but then we &lt;i&gt;have&lt;/i&gt; to use the require hook&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;This suddenly feels very Perlish&#38;quot;, said Duende. &#38;quot;In that it is very powerful but also a little risky&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;It&#38;#39;s all about taking managed risks&#38;quot;, replied Ada. &#38;quot;This instrumentation works best when used in a targeted way, aimed at having the smallest impact possible while still being useful. That&#38;#39;s why it tries to give you plenty of options to control what gets instrumented and when: you can match packages with literal strings or with regular expressions, and while we haven&#38;#39;t been doing it here, you can also do the same for individual functions within any package of interest&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;Is that why the options are in an array reference?&#38;quot;, asked Duende.&lt;/p&gt;

&lt;p&gt;&#38;quot;Yes&#38;quot;, answered Ada. &#38;quot;The instrumentation will process the rules in order. You can pass them in an array reference to control that order, or in a hash reference if you don&#38;#39;t care about the order. But the array reference is more predictable&#38;quot;.&lt;/p&gt;

&lt;h3 id=&#34;Here-a-use-there-a-use-everywhere-a-yule-use&#34;&gt;Here a use, there a use, everywhere a yule use&lt;/h3&gt;

&lt;p&gt;&#38;quot;I can see how this would be useful&#38;quot;, chimed in Tess. &#38;quot;But I could see this list of &#38;#39;rules&#38;#39; getting unwieldy, if we need to have them all in the same place and they include package and subroutine matchers, etc&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;Yes, that can become a problem&#38;quot;, agreed Ada. &#38;quot;So you don&#38;#39;t actually have to have everything in the same place. You can load the instrumentation multiple times with different rules, and each invocation will be independent. This is particularly true of &lt;a href=&#34;https://metacpan.org/pod/OpenTelemetry::Instrumentation::namespace#Options&#34;&gt;&lt;i&gt;options&lt;/i&gt;&lt;/a&gt;, which can be passed together with the rules to modify how that particular set of rules are interpreted, or to load the rules from an external source.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;That&#38;#39;s useful&#38;quot;, said Gnomo. &#38;quot;But even then, if the code we are instrumenting is our own code, it might be awkward to have the instrumentation live far away from the code itself. In those cases, manual instrumentation might still be better&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;Yeah, I agree. A small scope is always better&#38;quot;, replied Ada. &#38;quot;Luckily, there&#38;#39;s a related instrumentation that you can use in those cases: &lt;a href=&#34;https://metacpan.org/module/OpenTelemetry::Instrumentation::caller&#34;&gt;OpenTelemetry::Instrumentation::caller&lt;/a&gt;. Let me show you how using that one looks like&#38;quot;.&lt;/p&gt;

&lt;h3 id=&#34;The-instrumentation-is-coming-from-inside-the-package&#34;&gt;The instrumentation is coming from inside the package&lt;/h3&gt;

&lt;p&gt;&#38;quot;Say we have some utility package where we have some functions&#38;quot;, said Ada.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;package&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Santa::Workshop&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;v5.36&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;allocate_gifts&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;...&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is_naughty&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;...&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Sensitive data. Do not reveal!&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;dump_naughty_list&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;...&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;If we want to auto-instrument this code&#38;quot;, said Ada, &#38;quot;but we don&#38;#39;t want (or can&#38;#39;t) touch it, we can load the &lt;code&gt;caller&lt;/code&gt; instrumentation. We just need to make sure we load it after the functions have been defined&#38;quot;.&lt;/p&gt;

&lt;p&gt;&#38;quot;Does it also take the same options and rules as the &lt;code&gt;namespace&lt;/code&gt; instrumentation?&#38;quot;, asked Duende.&lt;/p&gt;

&lt;p&gt;&#38;quot;Yes&#38;quot;, said Ada, &#38;quot;but since we are already &#38;#39;inside&#38;#39; a package, so to speak, we skip the package-level matchers and go straight to the subroutine-level ones.&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;package&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Santa::Workshop&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;v5.36&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;allocate_gifts&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;...&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is_naughty&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;...&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Sensitive data. Do not reveal!&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;dump_naughty_list&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;...&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;OpenTelemetry::Instrumentation&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;caller&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;dump_naughty_list&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;    &lt;span class=&#34;comment&#34;&gt;# Ignore sensitive function&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;regexp&#34;&gt;qr/.*/&lt;/span&gt;            &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;    &lt;span class=&#34;comment&#34;&gt;# Instrument the rest&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;];&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;Wrapping-up&#34;&gt;Wrapping up&lt;/h3&gt;

&lt;p&gt;The elves were happy to have at least a path forward. They understood that these instrumentations were experimental, and that the best way to help was to use them and see where they felt awkward. And that&#38;#39;s exactly what they aimed to do.&lt;/p&gt;

&lt;p&gt;It was another successful day at Santa&#38;#39;s workshop, making hard things possible, and Santa was happy to see that his elves had once again managed to figure things out by themselves. He was proud of his elves. Proud to see them grow and learn and try new things. And proud as well to see that, even when deep in the weeds of some technical problem, they knew that what mattered was what they were working towards. The rest was just the tools for the job.&lt;/p&gt;

&lt;p&gt;Oh-oh. Santa had been lovingly staring at the elves for too long, and now they were staring back, as if waiting for him to say something. But this time, he knew exactly what to say.&lt;/p&gt;

&lt;p&gt;&#38;quot;Ho ho ho!&#38;quot;, laughed Santa, and everybody smiled.&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-16T00:00:00Z</updated><category term="Perl"/><author><name>Jos&#233; Joaqu&#237;n Atria</name></author></entry><entry><title>Using Mojolicious::Plugin::Mount to help test your applications</title><link href="https://perladvent.org/2025/2025-12-15.html"/><id>https://perladvent.org/2025/2025-12-15.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;h3 id=&#34;Introduction&#34;&gt;Introduction&lt;/h3&gt;

&lt;p&gt;When testing Perl applications we have many well known options and techniques but in this post we won&#38;#39;t cover any of those fancy tools. Instead we will present a simple trick that takes advantage of the &lt;a href=&#34;https://metacpan.org/module/Mojolicious::Plugin::Mount&#34;&gt;Mojolicious::Plugin::Mount&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id=&#34;Test-Scenario&#34;&gt;Test Scenario&lt;/h3&gt;

&lt;p&gt;When testing modules or applications that make requests to third party services, one technique is to mock the user agent to answer mocked data for each request.&lt;/p&gt;

&lt;p&gt;But when the module or application makes multiple requests to different external services that could be challenging.&lt;/p&gt;

&lt;p&gt;The idea here is to mock the external services instead.&lt;/p&gt;

&lt;h3 id=&#34;How-Mojolicious::Plugin::Mount-can-help&#34;&gt;How &lt;code&gt;Mojolicious::Plugin::Mount&lt;/code&gt; can help&lt;/h3&gt;

&lt;p&gt;This plugin basically allows us to glue together small Mojo applications into a single one that could be started by &lt;a href=&#34;https://metacpan.org/module/Test::Mojo&#34;&gt;Test::Mojo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Consider the two following small apps:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synPreProc&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;v5.42&lt;/span&gt;;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Mojolicious::Lite -signatures;&lt;br /&gt;&lt;br /&gt;get &lt;span class=&#34;synConstant&#34;&gt;&#39;/baz&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;c&lt;/span&gt;) { &lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;render( &lt;span class=&#34;synConstant&#34;&gt;text&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;baz&#39;&lt;/span&gt; ) };&lt;br /&gt;get &lt;span class=&#34;synConstant&#34;&gt;&#39;/etc&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;c&lt;/span&gt;) { &lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;render( &lt;span class=&#34;synConstant&#34;&gt;text&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;etc&#39;&lt;/span&gt; ) };&lt;br /&gt;&lt;br /&gt;app-&#38;gt;start;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synPreProc&#34;&gt;#!vim perl&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synPreProc&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;v5.42&lt;/span&gt;;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Mojolicious::Lite -signatures;&lt;br /&gt;&lt;br /&gt;get &lt;span class=&#34;synConstant&#34;&gt;&#39;/lala&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;c&lt;/span&gt;) { &lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;render( &lt;span class=&#34;synConstant&#34;&gt;text&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;lala&#39;&lt;/span&gt; ) };&lt;br /&gt;get &lt;span class=&#34;synConstant&#34;&gt;&#39;/lele&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;c&lt;/span&gt;) { &lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;render( &lt;span class=&#34;synConstant&#34;&gt;text&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;lele&#39;&lt;/span&gt; ) };&lt;br /&gt;&lt;br /&gt;app-&#38;gt;start;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can check their routes by saving each one to separate files, making them executable, and running these commands:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;user@host$ ./foo.pl routes&lt;br /&gt;/baz  GET  baz&lt;br /&gt;/etc  GET  etc&lt;br /&gt;&lt;br /&gt;user@host$ ./bar.pl routes&lt;br /&gt;/lala  GET  lala&lt;br /&gt;/lele  GET  lele&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;By using &lt;code&gt;Mojolicious::Plugin::Mount&lt;/code&gt; you can glue them together in this file named mock.pl:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synPreProc&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;v5.42&lt;/span&gt;;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Mojolicious::Lite;&lt;br /&gt;&lt;br /&gt;plugin &lt;span class=&#34;synConstant&#34;&gt;Mount&lt;/span&gt; =&#38;gt; { &lt;span class=&#34;synConstant&#34;&gt;&#39;/foo&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;./foo.pl&#39;&lt;/span&gt; };&lt;br /&gt;plugin &lt;span class=&#34;synConstant&#34;&gt;Mount&lt;/span&gt; =&#38;gt; { &lt;span class=&#34;synConstant&#34;&gt;&#39;/bar&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;./bar.pl&#39;&lt;/span&gt; };&lt;br /&gt;&lt;br /&gt;app-&#38;gt;start;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And check the routes:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;user@host$ ./mock.pl routes&lt;br /&gt;/foo  *  foo&lt;br /&gt;/bar  *  bar&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That indicates that all requests starting with &lt;code&gt;/foo&lt;/code&gt; will be delivered to the app &lt;code&gt;foo&lt;/code&gt; and all requests starting with &lt;code&gt;/bar&lt;/code&gt; will be delivered to the app &lt;code&gt;bar&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You could prefix both with &lt;code&gt;/&lt;/code&gt; meaning that the original routes will remain root-based.&lt;/p&gt;

&lt;p&gt;Let&#38;#39;s start the new app:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;user@host$ ./mock.pl daemon&lt;br /&gt;[2025-12-07 15:57:34.24246] [1437] [trace] Your secret passphrase needs to be changed (see FAQ for more)&lt;br /&gt;[2025-12-07 15:57:34.24515] [1437] [info] Listening at &#38;quot;http://*:3000&#38;quot;&lt;br /&gt;Web application available at http://127.0.0.1:3000&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And make some requests:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;user@host$ curl http://localhost:3000/foo/baz&lt;br /&gt;baz&lt;br /&gt;&lt;br /&gt;user@host$ curl http://localhost:3000/bar/lala&lt;br /&gt;lala&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note the app logs:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;  [2025-12-07 15:59:25.05863] [1437] [trace] [PJ0Qy_rY7rEA] GET &#38;quot;/foo/baz&#38;quot;
  [2025-12-07 15:59:25.05920] [1437] [trace] [PJ0Qy_rY7rEA] Routing to application &#38;quot;Mojolicious::Lite&#38;quot;
  [2025-12-07 15:59:25.06005] [1437] [trace] [PJ0Qy_rY7rEA] GET &#38;quot;/foo/baz&#38;quot;
  [2025-12-07 15:59:25.06020] [1437] [trace] [PJ0Qy_rY7rEA] Routing to a callback
  [2025-12-07 15:59:25.06046] [1437] [trace] [PJ0Qy_rY7rEA] 200 OK (0.000383s, 2610.966/s)

  [2025-12-07 15:59:38.70389] [1437] [trace] [1gOGBF3hsoml] GET &#38;quot;/bar/lala&#38;quot;
  [2025-12-07 15:59:38.70418] [1437] [trace] [1gOGBF3hsoml] Routing to application &#38;quot;Mojolicious::Lite&#38;quot;
  [2025-12-07 15:59:38.70511] [1437] [trace] [1gOGBF3hsoml] GET &#38;quot;/bar/lala&#38;quot;
  [2025-12-07 15:59:38.70529] [1437] [trace] [1gOGBF3hsoml] Routing to a callback
  [2025-12-07 15:59:38.70548] [1437] [trace] [1gOGBF3hsoml] 200 OK (0.000363s, 2754.821/s)&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That means the request &lt;code&gt;/foo/baz&lt;/code&gt; was delivered to the application &lt;code&gt;foo.pl&lt;/code&gt; and the request &lt;code&gt;/bar/lala&lt;/code&gt; was delivered to the application &lt;code&gt;bar.pl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can use that to set up multiple test scenarios by &#38;quot;mounting&#38;quot; different mock apps, like &#38;quot;successful &lt;code&gt;foo&lt;/code&gt; with failed &lt;code&gt;bar&lt;/code&gt;&#38;quot; or &#38;quot;failed &lt;code&gt;foo&lt;/code&gt; with successful &lt;code&gt;bar&lt;/code&gt;&#38;quot;.&lt;/p&gt;

&lt;h3 id=&#34;Testing-with-mocked-applications&#34;&gt;Testing with mocked applications&lt;/h3&gt;

&lt;p&gt;There are some gotchas when testing with mocked mojo apps. Since &lt;code&gt;Mojo::IOLoop&lt;/code&gt; is a singleton, both main app and mocked apps will share the same event loop. That means all blocking calls will make tests hang forever.&lt;/p&gt;

&lt;p&gt;So in order to be able to test with mocked apps, your application should:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;be able to overwrite the external URLs to point to mocked apps.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;have all external calls async.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an example which uses the &lt;code&gt;foo.pl&lt;/code&gt; and &lt;code&gt;bar.pl&lt;/code&gt; mock apps described above:&lt;/p&gt;

&lt;p&gt;Consider the main application below which we will save as &lt;code&gt;app.pl&lt;/code&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synPreProc&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;v5.42&lt;/span&gt;;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Mojolicious::Lite -signatures;&lt;br /&gt;&lt;br /&gt;get &lt;span class=&#34;synConstant&#34;&gt;&#39;/baz-lala&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;c&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;render_later;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# Getting external urls from &#39;config&#39;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$foo_url&lt;/span&gt; = Mojo::URL-&#38;gt;new( &lt;span class=&#34;synIdentifier&#34;&gt;$ENV{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;FOO_SERVER&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt; ) . &lt;span class=&#34;synConstant&#34;&gt;&#39;/baz&#39;&lt;/span&gt;;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$bar_url&lt;/span&gt; = Mojo::URL-&#38;gt;new( &lt;span class=&#34;synIdentifier&#34;&gt;$ENV{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;BAR_SERVER&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt; ) . &lt;span class=&#34;synConstant&#34;&gt;&#39;/lala&#39;&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# Making async requests&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$foo_p&lt;/span&gt; = &lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;ua-&#38;gt;get_p(&lt;span class=&#34;synIdentifier&#34;&gt;$foo_url&lt;/span&gt;);&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$bar_p&lt;/span&gt; = &lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;ua-&#38;gt;get_p(&lt;span class=&#34;synIdentifier&#34;&gt;$bar_url&lt;/span&gt;);&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Mojo::Promise-&#38;gt;all( &lt;span class=&#34;synIdentifier&#34;&gt;$foo_p&lt;/span&gt;, &lt;span class=&#34;synIdentifier&#34;&gt;$bar_p&lt;/span&gt; )-&#38;gt;then(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;sub&lt;/span&gt;( $&lt;span class=&#34;synIdentifier&#34;&gt;foo&lt;/span&gt;, $&lt;span class=&#34;synIdentifier&#34;&gt;bar &lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$res&lt;/span&gt; = &lt;span class=&#34;synIdentifier&#34;&gt;$foo-&#38;gt;[&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;]&lt;/span&gt;-&#38;gt;res-&#38;gt;text . &lt;span class=&#34;synConstant&#34;&gt;&#39;-&#39;&lt;/span&gt; . &lt;span class=&#34;synIdentifier&#34;&gt;$bar-&#38;gt;[&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;]&lt;/span&gt;-&#38;gt;res-&#38;gt;text;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;render( &lt;span class=&#34;synConstant&#34;&gt;text&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$res&lt;/span&gt; );&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;);&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;return&lt;/span&gt;;&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;app-&#38;gt;start;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here we get the external urls from &lt;code&gt;%ENV&lt;/code&gt; and then we make async requests using promises versions of &lt;code&gt;Mojo::UserAgent&lt;/code&gt; calls.&lt;/p&gt;

&lt;p&gt;Now let&#38;#39;s see the test:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synPreProc&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;v5.42&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;synType&#34;&gt; MyMock&lt;/span&gt;;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Mojolicious::Lite;&lt;br /&gt;&lt;br /&gt;plugin &lt;span class=&#34;synConstant&#34;&gt;Mount&lt;/span&gt; =&#38;gt; { &lt;span class=&#34;synConstant&#34;&gt;&#39;/foo&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;./foo.pl&#39;&lt;/span&gt; };&lt;br /&gt;plugin &lt;span class=&#34;synConstant&#34;&gt;Mount&lt;/span&gt; =&#38;gt; { &lt;span class=&#34;synConstant&#34;&gt;&#39;/bar&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;./bar.pl&#39;&lt;/span&gt; };&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;synType&#34;&gt; MyApp&lt;/span&gt;;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Mojolicious::Lite;&lt;br /&gt;&lt;br /&gt;plugin &lt;span class=&#34;synConstant&#34;&gt;Mount&lt;/span&gt; =&#38;gt; { &lt;span class=&#34;synConstant&#34;&gt;&#39;/&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;./app.pl&#39;&lt;/span&gt; };&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;synType&#34;&gt; main&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Test::More;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Test::Mojo;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$mock&lt;/span&gt; = Test::Mojo-&#38;gt;new(&lt;span class=&#34;synConstant&#34;&gt;&#39;MyMock&#39;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synComment&#34;&gt;# Checking mocks just in case&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$mock&lt;/span&gt;-&#38;gt;get_ok(&lt;span class=&#34;synConstant&#34;&gt;&#39;/foo/baz&#39;&lt;/span&gt;)-&#38;gt;status_is(&lt;span class=&#34;synConstant&#34;&gt;200&lt;/span&gt;)-&#38;gt;content_is(&lt;span class=&#34;synConstant&#34;&gt;&#39;baz&#39;&lt;/span&gt;);&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$mock&lt;/span&gt;-&#38;gt;get_ok(&lt;span class=&#34;synConstant&#34;&gt;&#39;/bar/lala&#39;&lt;/span&gt;)-&#38;gt;status_is(&lt;span class=&#34;synConstant&#34;&gt;200&lt;/span&gt;)-&#38;gt;content_is(&lt;span class=&#34;synConstant&#34;&gt;&#39;lala&#39;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synComment&#34;&gt;# Setting test&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$ENV{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;FOO_SERVER&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt; = &lt;span class=&#34;synIdentifier&#34;&gt;$mock&lt;/span&gt;-&#38;gt;ua-&#38;gt;server-&#38;gt;url-&#38;gt;path(&lt;span class=&#34;synConstant&#34;&gt;&#39;foo&#39;&lt;/span&gt;);&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$ENV{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;BAR_SERVER&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt; = &lt;span class=&#34;synIdentifier&#34;&gt;$mock&lt;/span&gt;-&#38;gt;ua-&#38;gt;server-&#38;gt;url-&#38;gt;path(&lt;span class=&#34;synConstant&#34;&gt;&#39;bar&#39;&lt;/span&gt;);&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$t&lt;/span&gt; = Test::Mojo-&#38;gt;new(&lt;span class=&#34;synConstant&#34;&gt;&#39;MyApp&#39;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synComment&#34;&gt;# Testing&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$t&lt;/span&gt;-&#38;gt;get_ok(&lt;span class=&#34;synConstant&#34;&gt;&#39;/baz-lala&#39;&lt;/span&gt;)-&#38;gt;status_is(&lt;span class=&#34;synConstant&#34;&gt;200&lt;/span&gt;)-&#38;gt;content_is(&lt;span class=&#34;synConstant&#34;&gt;&#39;baz-lala&#39;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;done_testing;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here you can see three packages: &lt;code&gt;MyMock&lt;/code&gt;, &lt;code&gt;MyApp&lt;/code&gt; and &lt;code&gt;main&lt;/code&gt;. They are put together for simplicity but you can have them in separate files.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MyMock&lt;/code&gt; is a package to glue together all mocks.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MyApp&lt;/code&gt; is a package just to load the main app. If you have a full Mojo app instead you can just load it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;main&lt;/code&gt; is the package where we will run the tests. It is not needed if the packages are in different files.&lt;/p&gt;

&lt;p&gt;After creating the &lt;code&gt;Test::Mojo&lt;/code&gt; we get the urls of mocked apps and set the environment so the main app can get them.&lt;/p&gt;

&lt;p&gt;Finally we make tests using &lt;code&gt;Test::Mojo&lt;/code&gt; api.&lt;/p&gt;

&lt;h3 id=&#34;Conclusion&#34;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Mojolicious::Plugin::Mount&lt;/code&gt; allow us to glue together small mojo apps that could be used to mock different scenarios of external services.&lt;/p&gt;

&lt;p&gt;You can mount different mocks depending on the need of each test.&lt;/p&gt;

&lt;p&gt;But in order to take advantage of them the application should keep external urls configurable and make external calls async.&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-15T00:00:00Z</updated><category term="Perl"/><author><name>Blabos de Blebe</name></author></entry><entry><title>The Twelve Slices Of Christmas: How Vasco Chained the Chaos</title><link href="https://perladvent.org/2025/2025-12-14.html"/><id>https://perladvent.org/2025/2025-12-14.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;h3 id=&#34;The-Missing-Piece&#34;&gt;The Missing Piece&lt;/h3&gt;

&lt;p&gt;After Vasco&#38;#39;s team shipped NaughtyNice 3.0 on Christmas Eve, everyone wanted to know the secret. Carmen&#38;#39;s No Estimates approach got all the attention&#38;mdash;measure throughput, use real data, ship daily.&lt;/p&gt;

&lt;p&gt;But here&#38;#39;s what nobody saw: on Day 1, Vasco nearly quit.&lt;/p&gt;

&lt;p&gt;Let me show you what happened behind the scenes.&lt;/p&gt;

&lt;h3 id=&#34;The-Crisis&#34;&gt;The Crisis&lt;/h3&gt;

&lt;p&gt;The team met that first afternoon to define the MVP and break the work into slices. Thirty slices, each small enough to complete in a day or two. They put them all on the whiteboard.&lt;/p&gt;

&lt;p&gt;Vasco stared at the board and did the math.&lt;/p&gt;

&lt;p&gt;30 slices &#38;times; 1 day each = 30 days of work.&lt;/p&gt;

&lt;p&gt;They had 24 days until Christmas Eve.&lt;/p&gt;

&lt;p&gt;He found Carmen, the Principal Elf for Special Projects, in her office. &#38;quot;No Estimates doesn&#38;#39;t work.&#38;quot;&lt;/p&gt;

&lt;p&gt;Carmen looked up from her coffee. &#38;quot;Interesting. We haven&#38;#39;t even started yet. Why not?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;The math. We broke everything into thirty slices. Even if we complete one per day, that&#38;#39;s thirty days of work.&#38;quot; Vasco gestured in frustration. &#38;quot;We only have twenty-four days. We&#38;#39;re going to miss.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Show me.&#38;quot;&lt;/p&gt;

&lt;p&gt;They walked back to the war room. Carmen studied the thirty slices scattered across the whiteboard like confetti.&lt;/p&gt;

&lt;p&gt;&#38;quot;Tell me something,&#38;quot; Carmen said. &#38;quot;Can you score behaviors before you ingest them?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;No, obviously not.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Can you generate lists before you have scores?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;No.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Could Wheeler work on database optimization while Joel builds the CSV loader?&#38;quot;&lt;/p&gt;

&lt;p&gt;Vasco paused. &#38;quot;Yes, actually. Those are independent.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Can Quinn and Alice write acceptance tests while Joel codes?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Yes, with TDD they should actually start immediately. And we have the current system to test against.&#38;quot;&lt;/p&gt;

&lt;p&gt;Carmen grabbed a marker. &#38;quot;Then not all thirty slices are on the critical chain. Some can happen in parallel. Some have slack. You&#38;#39;re not looking at thirty days of serial work&#38;mdash;you&#38;#39;re looking at a network.&#38;quot;&lt;/p&gt;

&lt;p&gt;She started drawing lines between slices. &#38;quot;Which ones have to happen in sequence? Which ones can happen alongside?&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;Finding-the-Critical-Chain&#34;&gt;Finding the Critical Chain&lt;/h3&gt;

&lt;p&gt;After thirty minutes of mapping dependencies, a pattern emerged. Most slices could happen in parallel or had flexibility in timing. But one path had to happen in strict sequence:&lt;/p&gt;

&lt;p&gt;Ingestion &#38;rarr; Validation &#38;rarr; Scoring &#38;rarr; Generate lists &#38;rarr; Integration testing&lt;/p&gt;

&lt;p&gt;Everything else could happen alongside or between these critical slices.&lt;/p&gt;

&lt;p&gt;&#38;quot;That&#38;#39;s your critical chain,&#38;quot; Carmen said. &#38;quot;Count those slices.&#38;quot;&lt;/p&gt;

&lt;p&gt;Vasco counted. &#38;quot;Twelve slices. Twelve days.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;And you&#38;#39;ll probably speed up a little,&#38;quot; said Carmen.&lt;/p&gt;

&lt;p&gt;Vasco sounded relieved. &#38;quot;Even at current pace, the critical chain fits easily in twenty-four days.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Exactly. The other eighteen slices? They have slack. They can happen in parallel or whenever there&#38;#39;s time. Here&#38;#39;s the trick: the critical chain is where you die. If anything on this path slips, Christmas slips. Everything else has slack&#38;mdash;time you can use to absorb problems or defer to January if things break.&#38;quot;&lt;/p&gt;

&lt;p&gt;Vasco stared at the diagram. The critical chain showed the minimum time to completion. Everything else was negotiable.&lt;/p&gt;

&lt;h3 id=&#34;The-Resource-Reality&#34;&gt;The Resource Reality&lt;/h3&gt;

&lt;p&gt;&#38;quot;Now the uncomfortable question,&#38;quot; Carmen continued. &#38;quot;Who does what on the critical chain?&#38;quot;&lt;/p&gt;

&lt;p&gt;They mapped it out:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Joel: API endpoints, CSV loading, data pipeline (critical chain: ingestion, validation)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wheeler: Database design, optimization, queries (critical chain: scoring, lists)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Quinn and Alice: Test framework, integration tests, data validation (critical chain: final integration)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Vasco: Architecture decisions, stakeholder communication, triage&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The critical chain bounced between people: Ingestion (Joel) &#38;rarr; Validation (Joel) &#38;rarr; Scoring (Wheeler) &#38;rarr; Lists (Wheeler) &#38;rarr; Testing (Quinn and Alice).&lt;/p&gt;

&lt;p&gt;&#38;quot;See the problem?&#38;quot; Carmen asked.&lt;/p&gt;

&lt;p&gt;Vasco saw it. &#38;quot;Handoffs. Joel finishes ingestion, but Wheeler might still be working on parallel database setup. So Joel starts something else. Then Wheeler&#38;#39;s ready but Joel is deep in the new task...&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Exactly. It&#38;#39;s like a relay race. You don&#38;#39;t start running when the previous runner finishes&#38;mdash;you start when they&#38;#39;re close enough to hand off the baton smoothly. That&#38;#39;s called full-kit: don&#38;#39;t start a critical chain task until everything it needs is ready AND the person is available.&#38;quot;&lt;/p&gt;

&lt;p&gt;They reorganized the first week:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&lt;b&gt;Day 1-2&lt;/b&gt;: Joel focuses on CSV ingestion (critical chain)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Day 2-3&lt;/b&gt;: Wheeler handles database setup in parallel (off critical chain)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Day 3&lt;/b&gt;: Joel starts validation (critical chain) - Wheeler ready to receive&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Day 4&lt;/b&gt;: Clean handoff to Wheeler for scoring algorithm&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&#38;quot;No multitasking on the critical chain,&#38;quot; Carmen emphasized. &#38;quot;Everything else can be parallel work.&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;What-Happens-When-a-Day-Isnt-a-Day&#34;&gt;What Happens When a Day Isn&#38;#39;t a Day&lt;/h3&gt;

&lt;p&gt;&#38;quot;What happens if something comes up and it takes longer than a day to finish a task?&#38;quot; Vasco asked.&lt;/p&gt;

&lt;p&gt;&#38;quot;Good question,&#38;quot; Carmen said. &#38;quot;How would you normally solve this?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;We&#38;#39;d add some buffer to the estimate, but we&#38;#39;re not estimating anymore.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Technically,&#38;quot; said Carmen, &#38;quot;you&#38;#39;re still estimating, just differently. You&#38;#39;re estimating the complexity to be what you can accomplish in a single day. That&#38;#39;s an easier thing to estimate, but you can still be wrong sometimes. That&#38;#39;s where buffers come in.&#38;quot;&lt;/p&gt;

&lt;p&gt;She grabbed a marker. &#38;quot;Traditional approach: estimate each task, then pad each one for safety. &#38;#39;This is 1 day but I&#38;#39;ll say 2 to be safe.&#38;#39; What happens?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Parkinson&#38;#39;s Law,&#38;quot; Vasco said. &#38;quot;Work expands to fill the time. Everyone uses the full time whether they need it or not.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Right. Plus student syndrome&#38;mdash;people don&#38;#39;t start until the deadline is close. So you get procrastination AND wasted padding. Then they still run late. So instead of padding each slice, we pool the buffer.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;How do we know how big the buffer should be?&#38;quot; Vasco asked.&lt;/p&gt;

&lt;p&gt;&#38;quot;If we were doing estimates, we could take the difference between an aggressive and a safe estimate for each task. With No Estimates, we&#38;#39;ve got to do it a little differently.&#38;quot; Carmen did the math on the whiteboard: &#38;quot;We start with a rule of thumb: a buffer of about 50% of your critical chain duration. You have twelve slices on the critical chain. Each sized to one day of complexity. That&#38;#39;s twelve days, so start with a six-day buffer. Eighteen days total. You have twenty-four days until Christmas&#38;mdash;plenty of room.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;That feels arbitrary.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;It is, to start. But you refine it with empirical data. As you measure your actual throughput, you&#38;#39;ll see your real variance. If your slices average 1.3 days instead of 1, that&#38;#39;s your actual variance&#38;mdash;and that tells you how to size future buffers. The 50% rule of thumb gets you started; reality calibrates you.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;And the buffer absorbs the times we&#38;#39;re wrong about complexity?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Exactly. You&#38;#39;re not padding each slice&#38;mdash;you&#38;#39;re pooling the expected variance at the end where you can manage it. And updating it as you learn more about your variance.&#38;quot; Carmen drew a diagram:&lt;/p&gt;

&lt;p&gt;&#38;quot;The rules are simple:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Size each slice to one day of complexity. If it&#38;#39;s bigger, break it down.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No padding individual slices. The buffer handles variance.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Watch buffer consumption. If slices consistently take 2+ days, you&#38;#39;re burning buffer fast.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Buffer consumption tells you when to cut scope.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&#38;quot;What about when tasks finish early?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;On the critical chain, that&#38;#39;s buffer you didn&#38;#39;t use&#38;mdash;it stays available for later. Off the critical chain, it&#38;#39;s just extra slack. Either way, keep moving.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;So &#38;#39;No Estimates&#38;#39; is really about estimating complexity instead of time?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Exactly. Complexity is easier to estimate than time. &#38;#39;This is about one day of work&#38;#39; is a much easier judgment than &#38;#39;this will take exactly 8 ideal development hours.&#38;#39; But you&#38;#39;re still making a judgment, and sometimes you&#38;#39;re wrong. The buffer handles that.&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;Week-1:-Testing-the-Theory&#34;&gt;Week 1: Testing the Theory&lt;/h3&gt;

&lt;p&gt;By Day 7, they&#38;#39;d completed 10 slices total. The critical chain had 4 slices done. Buffer still intact at 6 days.&lt;/p&gt;

&lt;p&gt;&#38;quot;We&#38;#39;re accelerating,&#38;quot; Vasco told Carmen at the daily check-in.&lt;/p&gt;

&lt;p&gt;&#38;quot;Good. But don&#38;#39;t celebrate yet,&#38;quot; Carmen warned. &#38;quot;This is where teams can get sloppy. Focus on releasing slices. Your buffer is for later.&#38;quot;&lt;/p&gt;

&lt;p&gt;The board showed clearly: critical chain progressing steadily, parallel work happening alongside. The visualization made it obvious what mattered.&lt;/p&gt;

&lt;h3 id=&#34;Week-2:-When-Theory-Meets-Reality&#34;&gt;Week 2: When Theory Meets Reality&lt;/h3&gt;

&lt;p&gt;Day 10 brought the first real problem. Joel finished the scoring algorithm slice and tried to hand off to Wheeler for database optimization. But Wheeler was still buried in parallel work&#38;mdash;setting up monitoring, building backup systems.&lt;/p&gt;

&lt;p&gt;Vasco called Carmen. &#38;quot;The critical chain is blocked. Wheeler&#38;#39;s not ready to receive.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;What did we say about the relay race?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Right. Full-kit principle.&#38;quot; Vasco looked at the board. &#38;quot;Wheeler needs to drop the monitoring work. That&#38;#39;s off the critical chain&#38;mdash;it can wait. The critical chain is blocked, which means Christmas is blocked.&#38;quot;&lt;/p&gt;

&lt;p&gt;Wheeler switched the next day. The handoff completed. Critical chain kept moving.&lt;/p&gt;

&lt;p&gt;Cost: They consumed one day of buffer. But the critical chain didn&#38;#39;t slip.&lt;/p&gt;

&lt;p&gt;&#38;quot;This is exactly why we have the buffer,&#38;quot; Carmen said later. &#38;quot;Not to absorb normal variance&#38;mdash;to give us decision power when the critical chain gets blocked.&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;Week-3:-The-Buffer-Saves-Christmas&#34;&gt;Week 3: The Buffer Saves Christmas&lt;/h3&gt;

&lt;p&gt;Day 16: Disaster. But not a technical one.&lt;/p&gt;

&lt;p&gt;The Mall Santa Union needed to be able to upload toy requests into the database. Marketing was demanding to know when that feature would be available. This had been promised by Project Veritas as part of their MVP&#38;mdash;but it was never in Vasco&#38;#39;s original scope.&lt;/p&gt;

&lt;p&gt;Vasco called Carmen. &#38;quot;They want us to add a feature. Toy request uploads from the mall Santas.&#38;quot;&lt;/p&gt;

&lt;p&gt;He broke down the new work:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Add database table for requests (0.5 days)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build upload endpoint (1 day)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add validation logic (0.5 days)&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&#38;quot;Two days of new work. Is any of it on the critical chain?&#38;quot;&lt;/p&gt;

&lt;p&gt;Vasco thought about it. &#38;quot;The upload endpoint would need the database schema to be finalized. That&#38;#39;s after ingestion and validation... yes, it would go on the critical chain.&#38;quot;&lt;/p&gt;

&lt;p&gt;Carmen pulled up the buffer tracking. &#38;quot;Show me the math.&#38;quot;&lt;/p&gt;

&lt;p&gt;Vasco did the calculation:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Original: 5 slices remaining on critical chain &#38;divide; 1.4/day = 4 days remaining&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After adding toy requests: 7 slices &#38;divide; 1.4/day = 5 days remaining&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Buffer remaining: 5 days (started with 6, used 1 in Week 2)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Total needed: 10 days&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Available: 8 days until Christmas&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&#38;quot;We&#38;#39;re over,&#38;quot; Carmen said. &#38;quot;What does that mean?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Time to cut scope?&#38;quot; Vasco suggested.&lt;/p&gt;

&lt;p&gt;&#38;quot;Show me what&#38;#39;s NOT on the critical chain.&#38;quot;&lt;/p&gt;

&lt;p&gt;Vasco examined the network diagram. The audit report generation system had three slices:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Design report schema&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement report generation&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build report UI&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None were on the critical chain. They had slack.&lt;/p&gt;

&lt;p&gt;&#38;quot;That&#38;#39;s the beauty of the critical chain,&#38;quot; Carmen explained. &#38;quot;Scope negotiation becomes objective, not political. Without this diagram, everyone argues their feature is critical. With it? The data shows you what actually has slack.&#38;quot;&lt;/p&gt;

&lt;p&gt;They showed Santa the network diagram with the buffer burn rate. &#38;quot;We can add the toy request uploads and ship core functionality on time&#38;mdash;but we need to push the audit reports to January. The data is all stored and auditable in the database. We just won&#38;#39;t have fancy reports on Christmas Eve.&#38;quot;&lt;/p&gt;

&lt;p&gt;Santa chose toy requests.&lt;/p&gt;

&lt;p&gt;They removed the three report slices from December scope (not from the critical chain&#38;mdash;they were already off it) and added the toy request slices.&lt;/p&gt;

&lt;p&gt;New math: 7 slices remaining &#38;divide; 1.4/day = 5 days + 3 days buffer cushion = 8 days. They&#38;#39;d ship on December 23rd, but with less margin than they&#38;#39;d like.&lt;/p&gt;

&lt;p&gt;&#38;quot;The buffer gave us decision time,&#38;quot; Carmen said. &#38;quot;And the critical chain told us WHAT to cut without politics. Anything not on the chain is negotiable. Anything on the chain determines whether Christmas happens.&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;Week-4:-The-Finish&#34;&gt;Week 4: The Finish&lt;/h3&gt;

&lt;p&gt;December 22nd: Final critical chain slice completed.&lt;/p&gt;

&lt;p&gt;December 23rd: Integration testing with buffer to spare.&lt;/p&gt;

&lt;p&gt;December 24th, 6 AM: NaughtyNice 3.0 went live.&lt;/p&gt;

&lt;p&gt;&#38;quot;We used half a day of buffer,&#38;quot; Vasco told Carmen afterward. &#38;quot;If we&#38;#39;d padded every slice instead, everyone would&#38;#39;ve used their padding and we&#38;#39;d be late.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Exactly,&#38;quot; Carmen said. &#38;quot;That&#38;#39;s the difference between spreading uncertainty across thirty tasks and pooling it where you can actually manage it.&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;What-Critical-Chain-Added-to-No-Estimates&#34;&gt;What Critical Chain Added to No Estimates&lt;/h3&gt;

&lt;p&gt;After Christmas, Vasco did a retrospective with the team.&lt;/p&gt;

&lt;p&gt;No Estimates gave them empirical data: throughput, velocity, actual progress. But Critical Chain gave them the structure to use that data effectively:&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Dependency management&lt;/b&gt;: Not all slices are equal. The critical chain determines project duration. Everything else has slack.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Resource protection&lt;/b&gt;: Prevent multitasking on critical work. Full-kit principle keeps handoffs smooth&#38;mdash;don&#38;#39;t start until you can finish.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Buffer management&lt;/b&gt;: Pool uncertainty at the end where you can manage it, not spread across individual tasks where it gets wasted.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Objective scope negotiation&lt;/b&gt;: This is huge. Without Critical Chain, cutting scope is political&#38;mdash;everyone argues their feature matters most. With Critical Chain, it&#38;#39;s data-driven: anything not on the critical chain can be deferred without affecting the deadline. Anything ON the chain is truly critical.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Focus and steering&lt;/b&gt;: When the buffer burns, you know exactly where to cut scope or add resources. The critical chain is your constraint map.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Relay race mentality&lt;/b&gt;: Next person prepares to receive work before it arrives. No &#38;quot;I&#38;#39;ll get to it when I&#38;#39;m free.&#38;quot;&lt;/p&gt;

&lt;p&gt;Together, Carmen&#38;#39;s approach answered three questions:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;No Estimates: &#38;quot;How fast are we actually going?&#38;quot;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Critical Chain: &#38;quot;Where should we be going, and what&#38;#39;s blocking us?&#38;quot;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fixed Scope + Critical Chain: &#38;quot;What can we defer without missing the deadline?&#38;quot;&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The irony? Vasco came to Carmen on Day 1 thinking No Estimates had failed before they&#38;#39;d even started. The problem wasn&#38;#39;t No Estimates&#38;mdash;it was not understanding which work had slack versus which didn&#38;#39;t.&lt;/p&gt;

&lt;h3 id=&#34;For-Your-Workshop&#34;&gt;For Your Workshop&lt;/h3&gt;

&lt;p&gt;Want to use Critical Chain to steer your Perl projects?&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Start with bounded scope.&lt;/b&gt; List all the features you WANT. Then identify which are must-haves for initial release. This creates negotiating room.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Break work to fixed size.&lt;/b&gt; Size each slice to one day of complexity. If it&#38;#39;s bigger, break it down. This is &#38;quot;No Estimates&#38;quot;&#38;mdash;estimating complexity instead of time. Complexity is easier to judge: &#38;quot;This feels like about a day of work&#38;quot; beats &#38;quot;This will take exactly 6.5 hours.&#38;quot;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Map your dependencies first.&lt;/b&gt; After breaking work into slices, draw the network. What has to happen in sequence? What can be parallel?&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Find the critical chain.&lt;/b&gt; Longest dependent path through the project. This is your constraint&#38;mdash;it determines minimum project duration.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Use the chain for scope decisions.&lt;/b&gt; When you need to cut scope (and you will), cut from slices NOT on the critical chain. They have slack. Anything on the chain is truly critical to shipping.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Protect the chain.&lt;/b&gt; No multitasking on critical work. Full-kit starts only.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Pool your buffer.&lt;/b&gt; Don&#38;#39;t pad individual slices. Add buffer time at the project level&#38;mdash;start with 50% of critical chain duration as a rule of thumb, then refine based on your actual throughput variance. The buffer is where reality meets your complexity estimates.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Watch buffer consumption.&lt;/b&gt; If you&#38;#39;re burning buffer faster than completing work, that&#38;#39;s your signal to negotiate scope. The critical chain tells you what&#38;#39;s negotiable.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Think about using No Estimates for the data.&lt;/b&gt; Throughput tells you how fast you&#38;#39;re moving through the chain.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Use Critical Chain for the structure.&lt;/b&gt; The chain tells you what to focus on and what can be deferred.&lt;/p&gt;

&lt;p&gt;This Christmas season, give yourself the gift of knowing both how fast you&#38;#39;re going and where you&#38;#39;re headed.&lt;/p&gt;

&lt;p&gt;May your critical chains be short and your buffers be sufficient.&lt;/p&gt;

&lt;h3 id=&#34;Epilogue&#34;&gt;Epilogue&lt;/h3&gt;

&lt;p&gt;&lt;i&gt;Carmen consults on projects across the entire workshop. Her advice is simple: &#38;quot;Find your constraint. Protect it. Exploit it. Everything else is noise.&#38;quot; The Veritas team is still trying to figure out their Critical Path. They estimate they&#38;#39;ll have it mapped by summer.&lt;/i&gt;&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-14T00:00:00Z</updated><category term="Perl"/><author><name>Chris Prather</name></author></entry><entry><title>Thirty Slices, Twenty-Four Days: How Christmas Was Saved By Abandoning Estimation</title><link href="https://perladvent.org/2025/2025-12-13.html"/><id>https://perladvent.org/2025/2025-12-13.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;h3 id=&#34;The-Crisis-That-Estimation-Built&#34;&gt;The Crisis That Estimation Built&lt;/h3&gt;

&lt;p&gt;December 1st. Vasco sat in the emergency management meeting with a sinking feeling in his stomach.&lt;/p&gt;

&lt;p&gt;&#38;quot;Project Veritas is &lt;i&gt;not&lt;/i&gt; going to be ready,&#38;quot; the Senior Elf for Software Development announced. &#38;quot;New estimate: March.&#38;quot;&lt;/p&gt;

&lt;p&gt;The room erupted. Back in January, that same team had promised the system would ship by Christmas. Management had approved Old Elf Morris&#38;#39;s retirement based on that promise. Morris was now somewhere in the Caribbean, sending postcards and ignoring emails with his out-of-office: &#38;quot;I&#38;#39;m retired. Not my problem.&#38;quot;&lt;/p&gt;

&lt;p&gt;The legacy NaughtyNice.pl&#38;mdash;50,000 lines of Perl4 written in 1991&#38;mdash;had been limping along all year. No one dared touch it. When someone tried a test run last week, it crashed with a cryptic error about &lt;code&gt;$biglist&lt;/code&gt; and package names. Nobody knew how Morris had kept it running.&lt;/p&gt;

&lt;p&gt;&#38;quot;Vasco,&#38;quot; Santa said, turning to him with that look. &#38;quot;I need a solution. Form a skunkworks team, get me something that will work. We only have 24 days.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;But&#38;mdash;&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;The Veritas team estimated it would be done. They&#38;#39;re already three months late and counting. I don&#38;#39;t want estimates. I want it working by Christmas Eve.&#38;quot;&lt;/p&gt;

&lt;p&gt;Vasco stared at his notepad. 24 days. 2 billion kids. A completely new system. His team knew Perl inside and out&#38;mdash;that was the obvious choice. But absolutely no time to figure out how long it would take.&lt;/p&gt;

&lt;h3 id=&#34;The-No-Estimates-Gambit&#34;&gt;The No Estimates Gambit&lt;/h3&gt;

&lt;p&gt;Vasco grabbed four of the best Perl elves he knew: Joel (web APIs), Wheeler (databases), and the testing team of Quinn and Alice. At a team meeting that afternoon, panic set in.&lt;/p&gt;

&lt;p&gt;&#38;quot;We should estimate how long&#38;mdash;&#38;quot; Joel started.&lt;/p&gt;

&lt;p&gt;&#38;quot;No.&#38;quot; Carmen appeared behind them. She&#38;#39;d been at the workshop since before Scrum was cool. &#38;quot;You&#38;#39;ve already lost if you start by guessing.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;But management needs to know&#38;mdash;&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Management knows when you need to be done. Management needs &lt;i&gt;confidence&lt;/i&gt; that you&#38;#39;ll actually be done on time. They just watched a team spend a year being wrong about their estimates. Give them something better: the truth.&#38;quot;&lt;/p&gt;

&lt;p&gt;Carmen laid out the approach: Stop trying to predict how long each task takes. Break the work into the smallest possible deployable slices&#38;mdash;all the same size. Ship something working every single day. Measure your actual throughput. Use real data to forecast.&lt;/p&gt;

&lt;p&gt;&#38;quot;We list everything that needs to work,&#38;quot; Carmen explained. &#38;quot;Then we break each piece until it&#38;#39;s small enough to finish in one day&#38;mdash;about a day&#38;#39;s complexity. Everything gets normalized to that same size. We ship it. We count how many slices we&#38;#39;re completing. After a few days, we know our velocity. Then it&#38;#39;s just math.&#38;quot;&lt;/p&gt;

&lt;p&gt;Vasco was skeptical. &#38;quot;What do I tell Santa when he asks when we&#38;#39;ll be done?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;After three days, you tell him based on what you&#38;#39;ve &lt;i&gt;actually&lt;/i&gt; accomplished, not what you &lt;i&gt;hope&lt;/i&gt; to accomplish. Way more honest than &#38;#39;six weeks&#38;#39; or whatever number feels safe to say.&#38;quot;&lt;/p&gt;

&lt;p&gt;They had nothing to lose. The Veritas team had already proven that traditional estimation wasn&#38;#39;t going to work.&lt;/p&gt;

&lt;h3 id=&#34;Week-1:-Finding-the-Rhythm&#34;&gt;Week 1: Finding the Rhythm&lt;/h3&gt;

&lt;p&gt;Day 1, Vasco and the team listed everything the system needed to do. Then they broke it down:&lt;/p&gt;

&lt;p&gt;&#38;quot;&#38;#39;Build behavior ingestion system&#38;#39; That&#38;#39;s too vague, too big.&#38;quot; Vasco complained.&lt;/p&gt;

&lt;p&gt;So they split it into smaller pieces:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Read CSV file of behavior reports &#38;rarr; parse basic fields &#38;rarr; store in database&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add field validation&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add deduplication logic&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handle different field elf data formats&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Optimize for 2 billion records&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each piece was something they could finish and deploy in a day.&lt;/p&gt;

&lt;p&gt;They counted: 30 total slices needed. Quick math: 30 slices &#38;times; 1 day each = 30 days of work. They had 24 days.&lt;/p&gt;

&lt;p&gt;&#38;quot;We&#38;#39;re not gonna get everything done,&#38;quot; Vasco said.&lt;/p&gt;

&lt;p&gt;&#38;quot;Maybe,&#38;quot; Carmen replied. &#38;quot;But you&#38;#39;ll get faster as time goes on. Focus on shipping.&#38;quot;&lt;/p&gt;

&lt;p&gt;By end of Day 1, they had working code:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;v5.40&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;experimental&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;class&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;class&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;BehaviorIngestor&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;field&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$dbh&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;param&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;method&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;ingest_csv&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$filename&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;open&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$fh&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#38;lt;&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$filename&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;die&lt;/span&gt; &lt;span class=&#34;magic&#34;&gt;$!&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$line&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;readline&#34;&gt;&#38;lt;$fh&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$kid_id&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$behavior&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$date&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;split&lt;/span&gt; &lt;span class=&#34;match&#34;&gt;/,/&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$line&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$dbh&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;do&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;single&#34;&gt;&#39;INSERT INTO behaviors (kid_id, behavior, date) VALUES (?, ?, ?)&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;core&#34;&gt;undef&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$kid_id&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$behavior&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$date&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Shipped. Tested. Working in staging. One slice complete.&lt;/p&gt;

&lt;p&gt;Day 2: Field validation added. Another slice done.&lt;/p&gt;

&lt;p&gt;Day 3: Deduplication logic. Third slice.&lt;/p&gt;

&lt;h3 id=&#34;Week-2:-The-Acceleration&#34;&gt;Week 2: The Acceleration&lt;/h3&gt;

&lt;p&gt;Something unexpected happened. By Day 7, they&#38;#39;d completed 10 slices total. The team was accelerating&#38;mdash;they understood the codebase now, had their tooling working, found their rhythm.&lt;/p&gt;

&lt;p&gt;New velocity: ~1.4 slices per day.&lt;/p&gt;

&lt;p&gt;Remaining slices: 20.&lt;/p&gt;

&lt;p&gt;New math: 20 &#38;divide; 1.4 = 14 days = December 22nd.&lt;/p&gt;

&lt;p&gt;They were going to make it.&lt;/p&gt;

&lt;p&gt;But Vasco didn&#38;#39;t trust it yet. He&#38;#39;d seen too many projects look good early and then crater. He kept shipping, kept measuring.&lt;/p&gt;

&lt;h3 id=&#34;The-Scope-Creeps&#34;&gt;The Scope Creeps&lt;/h3&gt;

&lt;p&gt;Day 16: Disaster. But not a technical one.&lt;/p&gt;

&lt;p&gt;The Mall Santa Union needed to be able to upload toy requests into the database. Marketing was demanding to know when that feature would be available. This had been promised by Project Veritas as part of their MVP&#38;mdash;but it was never in Vasco&#38;#39;s original scope.&lt;/p&gt;

&lt;p&gt;&#38;quot;We need this,&#38;quot; the Senior Elf for Software Development said. &#38;quot;The union is getting anxious.&#38;quot;&lt;/p&gt;

&lt;p&gt;In the old estimation world, this would trigger re-planning, re-estimating, panic, schedule slips, and probably a few arguments about whose fault it was.&lt;/p&gt;

&lt;p&gt;Vasco looked at the board. He broke down the toy request feature:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Add database table for requests (0.5 days)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Build upload endpoint (1 day)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add validation logic (0.5 days)&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two days of new work. He checked the remaining slices and their pace.&lt;/p&gt;

&lt;p&gt;&#38;quot;We can do it,&#38;quot; Vasco told Santa during a project review. &#38;quot;But we&#38;#39;ll need to drop the audit report generation and add it in January. The data is all stored and auditable in the database. We just won&#38;#39;t have fancy reports on Christmas Eve.&#38;quot;&lt;/p&gt;

&lt;p&gt;The key difference: They could &lt;i&gt;see&lt;/i&gt; the trade-off. The board showed exactly what was left. They weren&#38;#39;t guessing about invisible remaining work&#38;mdash;it was all visible.&lt;/p&gt;

&lt;p&gt;Santa nodded. Real data. Real options. No fantasy. &#38;quot;Do the toy requests. We&#38;#39;ll get reports later.&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;Week-4:-The-Finish&#34;&gt;Week 4: The Finish&lt;/h3&gt;

&lt;p&gt;December 22nd, 4 PM: The last core slice shipped. The system was working.&lt;/p&gt;

&lt;p&gt;December 23rd: Integration testing with real field elf data. A few bugs found and fixed (each was small because the slices were small).&lt;/p&gt;

&lt;p&gt;December 24th, 6 AM: NaughtyNice 3.0 went live.&lt;/p&gt;

&lt;p&gt;2 billion kids processed. Lists generated. Workshop shipping presents on schedule.&lt;/p&gt;

&lt;p&gt;Meanwhile, the Veritas team was still in a planning meeting, re-estimating their remaining work.&lt;/p&gt;

&lt;h3 id=&#34;What-Made-It-Work&#34;&gt;What Made It Work&lt;/h3&gt;

&lt;p&gt;After Christmas, Vasco and Carmen did a retrospective.&lt;/p&gt;

&lt;p&gt;The No Estimates approach worked because:&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Fixed scope from the start.&lt;/b&gt; Santa gave them clear, non-negotiable requirements: ingest behavior data, score kids against criteria, generate nice/naughty lists, handle 2 billion records, be auditable. The scope was bounded. What WAS negotiable: which features shipped in December versus January. This is critical&#38;mdash;No Estimates works when you can trade scope for time, not when everything is mandatory.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Small slices kept problems small.&lt;/b&gt; When the database scaling issue hit, it was one slice to fix, not a three-week replanning disaster.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;One-day slices protected focus.&lt;/b&gt; The smallest unit of work became a contract: developers wouldn&#38;#39;t be asked to context-switch in smaller units than one day. This meant uninterrupted focus time and fewer &#38;quot;quick questions&#38;quot; derailing progress.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Daily shipping caught integration bugs immediately.&lt;/b&gt; No &#38;quot;it works on my machine&#38;quot; surprises in week 3.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Real data enabled real decisions.&lt;/b&gt; When velocity dipped, they could see exactly what was left and have honest conversations about scope. &#38;quot;We can ship these 8 slices by Christmas, or these 12 slices by New Year&#38;#39;s&#38;mdash;your choice.&#38;quot;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;No time wasted on estimation theater.&lt;/b&gt; They spent zero hours in planning poker, zero hours defending estimates, zero hours re-estimating. They spent those hours shipping instead.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Stakeholders had real visibility.&lt;/b&gt; Santa could see the board. He knew what worked, what was left, what was at risk. Way better than &#38;quot;we&#38;#39;re 73% complete.&#38;quot;&lt;/p&gt;

&lt;p&gt;The irony? The whole crisis existed because someone had estimated Project Veritas would be done by Christmas. They&#38;#39;re now three months late and still not shipping. Vasco&#38;#39;s team delivered in 24 days by normalizing work to a single size and measuring reality instead of predicting the future.&lt;/p&gt;

&lt;h3 id=&#34;For-Your-Workshop&#34;&gt;For Your Workshop&lt;/h3&gt;

&lt;p&gt;Want to try No Estimates on your Perl projects?&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Start with bounded scope.&lt;/b&gt; No Estimates works best when you can negotiate WHICH features ship, not WHEN you ship. If every feature is mandatory AND the deadline is fixed, you don&#38;#39;t have a planning problem&#38;mdash;you have a self-honesty problem. Define your must-haves clearly.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Normalize to one size.&lt;/b&gt; Traditional estimation is two steps from reality: estimate complexity, then estimate time. Fix time to one day and you only estimate complexity. What can you finish by tomorrow? &#38;quot;Add user authentication&#38;quot; becomes &#38;quot;login form,&#38;quot; &#38;quot;password validation,&#38;quot; &#38;quot;session handling.&#38;quot; Complexity is easier to judge, and variance gets absorbed by the buffer.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Measure throughput.&lt;/b&gt; Count completed slices per week. That&#38;#39;s your data.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Make work visible.&lt;/b&gt; A kanban board or even gantt chart showing what&#38;#39;s done, what&#38;#39;s next, what&#38;#39;s left. No hiding.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Use data to forecast.&lt;/b&gt; After a few days, you know your pace. Remaining work &#38;divide; pace = honest forecast. This is where you negotiate: &#38;quot;At current pace, these 10 slices ship by deadline. These other 5 slip to next month. Which matters more?&#38;quot;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Keep stakeholders honest too.&lt;/b&gt; Show them real progress. Let them reprioritize based on what&#38;#39;s actually left.&lt;/p&gt;

&lt;p&gt;This Christmas season, give yourself the gift of predictability through measurement.&lt;/p&gt;

&lt;p&gt;May your deployments be frequent and your predictions be accurate.&lt;/p&gt;

&lt;h3 id=&#34;Epilogue&#34;&gt;Epilogue&lt;/h3&gt;

&lt;p&gt;&lt;i&gt;Vasco&#38;#39;s NaughtyNice 3.0 processed 2,147,483,647 kids in 47 minutes on Christmas Eve. Project Veritas launched a Demo the following April. It was deprecated immediately because Vasco&#38;#39;s system was already handling everything.&lt;/i&gt;&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-13T00:00:00Z</updated><category term="Perl"/><author><name>Chris Prather</name></author></entry><entry><title>Teaching Art to Computers the Hard Way</title><link href="https://perladvent.org/2025/2025-12-11.html"/><id>https://perladvent.org/2025/2025-12-11.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;h2 id=&#34;You-shouldnt-do-art&#34;&gt;&#38;quot;You shouldn&#38;#39;t do art&#38;quot;&lt;/h2&gt;

&lt;p&gt;I was told this over and over growing up as a kid. &#38;quot;Your mind doesn&#38;#39;t work that way,&#38;quot; being the rationale for this. Teachers, if you are reading this, NEVER tell a kid that. Seriously. I believed it for nearly a half-century, then wrote my &lt;a href=&#34;https://www.amazon.com/dp/1736421409&#34;&gt;first novel&lt;/a&gt;, and later painted my first-ever oil painting. Anyone can do art of &lt;b&gt;some&lt;/b&gt; kind.&lt;/p&gt;

&lt;p&gt;To be fair to those teachers of long ago, my mind does like orderly art. I struggle with understanding impressionist art, but realism and abstraction both move me in ways I never thought possible: Realism, because it tells a story, and abstraction, because I strive to understand what is being abstracted, and can spend hours staring at an abstract painting and trying to grok the fullness of it.&lt;/p&gt;

&lt;p&gt;Which brings us to Piet Mondrian. Surely you&#38;#39;ve heard of this famous Dutch man, a pioneer of 20th-century abstract art who believed that to properly abstract reality, we should use &lt;b&gt;as little reality as possible&lt;/b&gt;? No? Well, maybe you&#38;#39;ve seen his work:&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://upload.wikimedia.org/wikipedia/commons/thumb/3/36/Tableau_I%2C_by_Piet_Mondriaan.jpg/960px-Tableau_I%2C_by_Piet_Mondriaan.jpg&#34; alt=&#34;Tableau I, 1921, by Piet Mondrian, currently at Kunstmuseum Den Haag&#34;&gt;

&lt;/p&gt;



&lt;p&gt;So looking at &lt;i&gt;Tableau I&lt;/i&gt; here, I think to myself, &#38;quot;Self, could you teach a computer to do that?&#38;quot; and I answered, &#38;quot;Self, I betcha we can.&#38;quot;&lt;/p&gt;

&lt;p&gt;A bit of noodling around on the web discovered that someone had already done this &lt;a href=&#34;https://inventwithpython.com/bigbookpython/project47.html&#34;&gt;in Python&lt;/a&gt;, and I liked their basic approach&#38;mdash;use some configurable random number generators to generate a virtual canvas. But let&#38;#39;s extend that some in the Perl instance. I&#38;#39;d like to have it working with close to Mondrian-correct colors as shown in &lt;i&gt;Tableau I&lt;/i&gt;, and be able to output JPG, PNG, and SVG instead of the terminal. I&#38;#39;ll use &lt;a href=&#34;https://metacpan.org/pod/Moo&#34;&gt;Moo&lt;/a&gt;, mostly because I like Moo, and it makes this generator into a modular object you could use later to store/retrieve specifications of Mondrian-styled paintings, if you want. Let&#38;#39;s get started:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# Required:&lt;br /&gt;#&lt;br /&gt;#   cpan Moo Svg::Simple Imager Imager::File::PNG Imager::File::JPEG&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;package&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Acme::Mondrian::Generator&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;strict&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;warnings&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Moo&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Svg::Simple&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Imager&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Imager::File::PNG&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;  &lt;span class=&#34;comment&#34;&gt;# We don&#39;t actually have to include these here, but they must&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Imager::File::JPEG&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# be installed!&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Stuff the user can control on creation, with defaults&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;resolution&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;ro&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;80&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;      &lt;span class=&#34;comment&#34;&gt;# width in cells&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;aspect&lt;/span&gt;     &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;ro&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;float&#34;&gt;0.75&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;    &lt;span class=&#34;comment&#34;&gt;# height = width * aspect&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;line_thickness&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;ro&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;4&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;   &lt;span class=&#34;comment&#34;&gt;# SVG black line thickness&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;scale_px&lt;/span&gt;       &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;ro&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;20&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;  &lt;span class=&#34;comment&#34;&gt;# px per cell in PNG/SVG&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;palette&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt;      &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;ro&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;      # I did some googling around for these, then asked ChatGPT for the&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;# RGB numbers.&lt;br /&gt;&lt;/span&gt;      &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;   &lt;span class=&#34;comment&#34;&gt;# I used I&#38;lt;Tableau I&#38;gt; and an eyedropper tool for the web for these.&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;white&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;224&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;220&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;221&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;        &lt;span class=&#34;comment&#34;&gt;# lead white approximation&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;black&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;54&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;  &lt;span class=&#34;number&#34;&gt;37&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;  &lt;span class=&#34;number&#34;&gt;36&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;         &lt;span class=&#34;comment&#34;&gt;# bone + carbon black&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;red&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;246&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;68&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;58&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;          &lt;span class=&#34;comment&#34;&gt;# hematite / iron-oxide red&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;yellow&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;238&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;184&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;42&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;         &lt;span class=&#34;comment&#34;&gt;# mix: cadmium yellow / ochre / chrome yellow&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;blue&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;48&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;50&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;124&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;          &lt;span class=&#34;comment&#34;&gt;# dark blue&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Logical canvas (grid)&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;canvas&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;rw&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;width&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;rw&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;  &lt;span class=&#34;comment&#34;&gt;# We&#39;ll calculate these at runtime.&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;height&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;rw&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Okay, let&#38;#39;s start working on the generator; this is the fiddly bit of the whole problem. In &lt;code&gt;sub generate&lt;/code&gt;, which will need no parameters other than the object it&lt;code&gt;$self&lt;/code&gt;, we can start by calculating and initializing the whole &#38;quot;canvas&#38;quot;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;generate&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;core&#34;&gt;shift&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;resolution&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;resolution&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;aspect&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;cast&#34;&gt;\&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;%canvas&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;  # Initialize white grid&lt;br /&gt;&lt;/span&gt;  &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$x,$y&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Next, we need some lines, both vertical and horizontal. You could do these in either order, I suppose, but I&#38;#39;ll start with vertical lines, then do horizontals. I&#38;#39;m not usually a fan of lexical blocks like this, but it keeps &lt;a href=&#34;https://metacpan.org/pod/Perl::Critic&#34;&gt;Perl::Critic&lt;/a&gt; and &lt;a href=&#34;https://metacpan.org/pod/Test::NoWarnings&#34;&gt;Test::NoWarnings&lt;/a&gt; happier, by avoiding redefining &lt;code&gt;$x&lt;/code&gt; and &lt;code&gt;$y&lt;/code&gt; in a haphazard way.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# Random thick grid lines like Mondrian&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_x_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$max_x_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_y_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$max_y_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$numberOfSegmentsToDelete&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Vertical lines&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@vlines&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$max_x_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_x_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_x_gap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;lt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_x_gap&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$numberOfSegmentsToDelete&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;push&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@vlines&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$x,$y&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$max_x_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_x_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_x_gap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Horizontal lines&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@hlines&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$max_y_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_y_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_y_gap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;lt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_y_gap&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;push&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@hlines&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$x,$y&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$max_y_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_y_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_y_gap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If we were to output &lt;code&gt;$canvas&lt;/code&gt;, we&#38;#39;d get a random canvas of black lines and white blocks, with the lines unevenly spaced. There should be between five and ten of each, based on the max and min gaps above.&lt;/p&gt;

&lt;p&gt;Mondrian did not always have his lines going all the way across; as you can see in &lt;i&gt;Tableau I&lt;/i&gt;, he would occasionally delete one. This part is a bit tricky, as we need to find a single segment of line at a time.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$numberOfRectanglesToPaint&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$numberOfSegmentsToDelete&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;symbol&#34;&gt;$numberOfSegmentsToDelete&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$numberOfSegmentsToDelete&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;float&#34;&gt;1.5&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Delete a segment&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$numberOfSegmentsToDelete&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;    &lt;span class=&#34;comment&#34;&gt;# let&#39;s look for a black spot on the canvas.&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$startx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$starty&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;next&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$startx,$starty&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$orientation&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;    &lt;span class=&#34;comment&#34;&gt;# Are we on a vertical line, or a horizontal&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;   &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$startx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;,$starty&#38;quot;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$startx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;,$starty&#38;quot;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$orientation&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;vertical&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;elsif&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$startx,&#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$starty&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$startx,&#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$starty&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$orientation&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;horizontal&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;    &lt;span class=&#34;comment&#34;&gt;# We&#39;re at an intersection, so don&#39;t delete here.&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@points&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$startx,$starty&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canDelete&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;        # now find the collection of points on the rest of that segment, and push&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;# them into @points.&lt;br /&gt;&lt;/span&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$orientation&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;vertical&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$dy&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;-1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$yy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$starty&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$yy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$yy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;lt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$yy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$dy&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$l&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$startx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;,$yy&#38;quot;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$startx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;,$yy&#38;quot;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$l&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;elsif&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;((&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$l&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$l&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canDelete&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;push&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@points&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$startx,$yy&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;unless&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canDelete&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;    &lt;span class=&#34;comment&#34;&gt;# horizontal&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$dx&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;-1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$xx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$startx&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$xx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$xx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;lt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$xx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$dx&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$u&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$xx,&#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$starty&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$xx,&#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$starty&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$u&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;elsif&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;((&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$u&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$u&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canDelete&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;push&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@points&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$xx,$starty&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;unless&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canDelete&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;next&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;unless&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canDelete&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;magic&#34;&gt;$_&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@points&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A 1-cell black border around the image would be nice:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# Border&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$xx&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$xx,0&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$xx,&#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$yy&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;0,$yy&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;,$yy&#38;quot;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Okay, now the next hard part, painting in a few rectangles. In this case, we&#38;#39;re going to look for white space, then continue filling white space around it recursively until we hit a line.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;# Flood fill rectangles&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@fillcolors&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;words&#34;&gt;qw(red yellow blue white white white)&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;    &lt;span class=&#34;comment&#34;&gt;# biased toward white&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%seen&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$numberOfRectanglesToPaint&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$sx&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$sy&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;200&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$sx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$sy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$sx,$sy&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$color&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$fillcolors&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@fillcolors&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;];&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@stack&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$sx,$sy&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;@stack&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$pt&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;pop&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@stack&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;next&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$seen&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$pt&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;split&lt;/span&gt; &lt;span class=&#34;match&#34;&gt;/,/&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$pt&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$pt&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$color&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$seen&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$pt&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;-1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;-1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$nx&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$ny&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;next&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$nx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;lt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$ny&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;lt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$nx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$ny&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$k&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$nx,$ny&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;push&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@stack&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$k&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$k&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And that&#38;#39;s the end of &lt;code&gt;sub generate&lt;/code&gt;. I&#38;#39;ll &lt;code&gt;return 1&lt;/code&gt; at the end, just for tidiness&#38;#39; sake. At that point, the object now has a well-defined canvas that should look somewhat like a Mondrian painting. Now we need an output method. Nothing fancy here, but &lt;a href=&#34;https://metacpan.org/pod/Imager&#34;&gt;Imager&lt;/a&gt; will let you output either jpg or png, by just specifying the filename you want to use.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;save_png_or_jpg&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$file&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;magic&#34;&gt;@_&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;scale_px&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt;      &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;      &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%canvas&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;cast&#34;&gt;%&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;canvas&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$img&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Imager&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;xsize&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;ysize&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;channels&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;3&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$k&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;keys&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%canvas&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;split&lt;/span&gt; &lt;span class=&#34;match&#34;&gt;/,/&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$k&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$rgb&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;palette&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$k&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$color&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Imager::Color&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;cast&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$rgb&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$img&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;box&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;xmin&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;ymin&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;xmax&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;ymax&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;color&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$color&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;filled&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$img&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;write&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;file&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$file&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;die&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$img&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;errstr&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After that, I scribbled up a little script to generate a Mondrian-styled painting using the default behaviors, and have it output a JPG.&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;mondrian_by_perl.jpg&#34; alt=&#34;Mondrian-style painting generated by Perl&#34;&gt;

&lt;/p&gt;



&lt;p&gt;...and that&#38;#39;s how I started teaching art classes to Perl.&lt;/p&gt;

&lt;p&gt;In the module I&#38;#39;ll be releasing to CPAN this holiday season (look for &#38;quot;Acme::Mondrian::Generator&#38;quot; in the next week or so, and I&#38;#39;ll be sure to update this to a link), I&#38;#39;ll also include a script to let you play around with the parameters of the generator, and output PNGs and SVGs as well as JPGs.&lt;/p&gt;

&lt;p&gt;For completeness, here&#38;#39;s the whole Acme::Mondrian::Generator package after a bit of tidying-up:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;package&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Acme::Mondrian::Generator&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;strict&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;warnings&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Moo&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Svg::Simple&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Imager&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Stuff the user can control on creation, with defaults&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;resolution&lt;/span&gt;     &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;ro&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;80&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;   &lt;span class=&#34;comment&#34;&gt;# width in cells&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;aspect&lt;/span&gt;         &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;ro&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;float&#34;&gt;0.75&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt; &lt;span class=&#34;comment&#34;&gt;# height = width * aspect&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;line_thickness&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;ro&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;4&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;    &lt;span class=&#34;comment&#34;&gt;# SVG black line thickness&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;scale_px&lt;/span&gt;       &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;ro&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;20&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;   &lt;span class=&#34;comment&#34;&gt;# px per cell in PNG/SVG&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;palette&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt;      &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;ro&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;   &lt;span class=&#34;comment&#34;&gt;# I used I&#38;lt;Tableau I&#38;gt; and an eyedropper tool for the web for these.&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;white&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;224&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;220&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;221&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;        &lt;span class=&#34;comment&#34;&gt;# lead white approximation&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;black&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;54&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;  &lt;span class=&#34;number&#34;&gt;37&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;  &lt;span class=&#34;number&#34;&gt;36&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;         &lt;span class=&#34;comment&#34;&gt;# bone + carbon black&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;red&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;246&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;68&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;58&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;          &lt;span class=&#34;comment&#34;&gt;# hematite / iron-oxide red&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;yellow&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;238&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;184&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;42&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;         &lt;span class=&#34;comment&#34;&gt;# mix: cadmium yellow / ochre / chrome yellow&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;blue&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;48&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;50&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;124&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;          &lt;span class=&#34;comment&#34;&gt;# dark blue&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Logical canvas (grid)&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;canvas&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;rw&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;default&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;width&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;rw&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;                   &lt;span class=&#34;comment&#34;&gt;# We&#39;ll calculate these&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;has&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;height&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;rw&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;generate&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;core&#34;&gt;shift&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;resolution&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;resolution&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;aspect&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;cast&#34;&gt;\&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;%canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;   # Initialize white grid&lt;br /&gt;&lt;/span&gt;   &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$x,$y&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;   # Random thick grid lines like Mondrian&lt;br /&gt;&lt;/span&gt;   &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_x_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$max_x_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_y_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$max_y_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;/&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$numberOfSegmentsToDelete&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;   # Vertical lines&lt;br /&gt;&lt;/span&gt;   &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@vlines&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$max_x_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_x_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_x_gap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;lt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_x_gap&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$numberOfSegmentsToDelete&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;push&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@vlines&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$x,$y&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$max_x_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_x_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_x_gap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;   # Horizontal lines&lt;br /&gt;&lt;/span&gt;   &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@hlines&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$max_y_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_y_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_y_gap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;lt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_y_gap&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;push&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@hlines&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$x,$y&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$max_y_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_y_gap&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$min_y_gap&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$numberOfRectanglesToPaint&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$numberOfSegmentsToDelete&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$numberOfSegmentsToDelete&lt;/span&gt;     &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$numberOfSegmentsToDelete&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;float&#34;&gt;1.5&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;   # Delete segments&lt;br /&gt;&lt;/span&gt;   &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$numberOfSegmentsToDelete&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;                   &lt;span class=&#34;comment&#34;&gt;# let&#39;s look for a black spot on the canvas.&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$startx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$starty&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;2&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;next&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$startx,$starty&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$orientation&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;           &lt;span class=&#34;comment&#34;&gt;# Are we on a vertical line, or a horizontal&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;  &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$startx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;,$starty&#38;quot;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$startx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;,$starty&#38;quot;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$orientation&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;vertical&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;elsif&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$startx,&#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$starty&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$startx,&#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$starty&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$orientation&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;horizontal&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;                     &lt;span class=&#34;comment&#34;&gt;# We&#39;re at an intersection, so don&#39;t delete here.&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@points&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$startx,$starty&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canDelete&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;         # Now find the collection of points on the rest of that segment, and push&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;# them into @points.&lt;br /&gt;&lt;/span&gt;         &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$orientation&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;vertical&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$dy&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;-1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$yy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$starty&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$yy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$yy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;lt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$yy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$dy&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$l&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$startx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;,$yy&#38;quot;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$startx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;,$yy&#38;quot;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$l&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;elsif&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$l&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$l&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$r&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canDelete&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;push&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@points&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$startx,$yy&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;unless&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canDelete&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;                     &lt;span class=&#34;comment&#34;&gt;# horizontal&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$dx&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;-1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$xx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$startx&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$xx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$xx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;lt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$xx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$dx&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$u&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$xx,&#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$starty&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$xx,&#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$starty&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$u&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;elsif&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$u&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$u&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;amp;&#38;amp;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canDelete&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;push&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@points&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$xx,$starty&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;unless&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canDelete&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;next&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;unless&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canDelete&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;magic&#34;&gt;$_&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@points&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;   # Border&lt;br /&gt;&lt;/span&gt;   &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$xx&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$xx,0&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$xx,&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$yy&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;0,$yy&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;,$yy&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;black&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;   # Flood fill rectangles&lt;br /&gt;&lt;/span&gt;   &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@fillcolors&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;words&#34;&gt;qw(red yellow blue white white)&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;    &lt;span class=&#34;comment&#34;&gt;# biased toward white&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%seen&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$numberOfRectanglesToPaint&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$sx&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$sy&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;200&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$sx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$sy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;last&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$sx,$sy&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$color&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$fillcolors&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@fillcolors&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;];&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@stack&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;$sx,$sy&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;@stack&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$pt&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;pop&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@stack&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;         # If our $color is &#39;white&#39;, this way we don&#39;t loop forever.&lt;br /&gt;&lt;/span&gt;         &lt;span class=&#34;word&#34;&gt;next&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$seen&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$pt&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;split&lt;/span&gt; &lt;span class=&#34;match&#34;&gt;/,/&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$pt&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$pt&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$color&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$seen&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$pt&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;         # If any adjacent points are white, push them in for coloring.&lt;br /&gt;&lt;/span&gt;         &lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;-1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;-1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$nx&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$ny&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$d&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;next&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$nx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;lt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$ny&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;lt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$nx&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$ny&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$k&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$nx,$ny&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;push&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@stack&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$k&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$k&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;white&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;save_png_or_jpg&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$file&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;magic&#34;&gt;@_&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;scale_px&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt;      &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;width&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt;      &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;height&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%canvas&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;cast&#34;&gt;%&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;canvas&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$img&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Imager&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;xsize&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$W&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;ysize&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$H&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;channels&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;3&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$k&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;keys&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%canvas&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;split&lt;/span&gt; &lt;span class=&#34;match&#34;&gt;/,/&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$k&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$rgb&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$self&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;palette&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$canvas&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$k&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$color&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Imager::Color&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;cast&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$rgb&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$img&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;box&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;xmin&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;ymin&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;xmax&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$x&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;ymax&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$y&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$scale&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;color&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$color&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;filled&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$img&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;write&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;file&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$file&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;die&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$img&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;errstr&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;...and my little script to create one!&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;#!/usr/bin/env perl&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;strict&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;warnings&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;lib&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;./lib&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Acme::Mondrian::Generator&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$gen&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Acme::Mondrian::Generator&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;();&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;symbol&#34;&gt;$gen&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;generate&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;();&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$f&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;mondrian_&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;()&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;.jpg&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;symbol&#34;&gt;$gen&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;save_png_or_jpg&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$f&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I hope this little bit of creativity inspires your own, whether in Perl, or on a canvas&#38;mdash;or even in Raku or Python! Happy painting, and the happiest of holidays to you and the people you love, wherever they may be!&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-11T00:00:00Z</updated><category term="Perl"/><author><name>D Ruth Holloway</name></author></entry><entry><title>The Ghost of Web Frameworks Future</title><link href="https://perladvent.org/2025/2025-12-10.html"/><id>https://perladvent.org/2025/2025-12-10.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;h3 id=&#34;A-message-from-Christmas-Yet-to-Come&#34;&gt;A message from Christmas Yet to Come&lt;/h3&gt;

&lt;p&gt;It was a cold December night when the Ghost of Web Frameworks Future visited young Petra the Programmer.&lt;/p&gt;

&lt;p&gt;&#38;quot;I have something to show you,&#38;quot; the spectral figure intoned, gesturing toward Petra&#38;#39;s laptop. &#38;quot;The future of Perl web development.&#38;quot;&lt;/p&gt;

&lt;p&gt;Petra squinted at her screen. She&#38;#39;d been wrestling with a familiar problem: her company&#38;#39;s venerable Catalyst application needed real-time features. WebSockets. Server-Sent Events. The kind of persistent, bidirectional connections that made traditional request-response frameworks break out in a cold sweat.&lt;/p&gt;

&lt;p&gt;&#38;quot;I&#38;#39;ve tried everything,&#38;quot; she sighed. &#38;quot;I could rewrite the whole thing in Mojolicious, but that&#38;#39;s months of work. Or I could bolt on a separate WebSocket server, but then I have two systems to maintain...&#38;quot;&lt;/p&gt;

&lt;p&gt;The Ghost smiled knowingly. &#38;quot;What if I told you there was another way? A specification designed from the ground up for asynchronous Perl web applications? One that could run your legacy PSGI apps alongside shiny new async code?&#38;quot;&lt;/p&gt;

&lt;p&gt;Petra leaned forward. &#38;quot;Tell me more.&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;Enter-PAGI:-The-Spiritual-Successor-to-PSGI&#34;&gt;Enter PAGI: The Spiritual Successor to PSGI&lt;/h3&gt;

&lt;p&gt;PAGI - the &lt;b&gt;Perl Asynchronous Gateway Interface&lt;/b&gt; - is a new specification for async-capable Perl web applications. If PSGI was Perl&#38;#39;s answer to Python&#38;#39;s WSGI, then PAGI is Perl&#38;#39;s answer to Python&#38;#39;s ASGI.&lt;/p&gt;

&lt;p&gt;The key insight is simple: modern web applications need more than request-response. They need:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&lt;b&gt;WebSockets&lt;/b&gt; for real-time bidirectional communication&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Server-Sent Events&lt;/b&gt; for efficient server push&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Streaming responses&lt;/b&gt; for large files and live data&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Lifecycle hooks&lt;/b&gt; for connection pooling and startup/shutdown&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PSGI, brilliant as it was, assumed a synchronous world where each request got a response and that was that. PAGI embraces the asynchronous reality of modern web development.&lt;/p&gt;

&lt;h3 id=&#34;The-PAGI-Interface&#34;&gt;The PAGI Interface&lt;/h3&gt;

&lt;p&gt;At its heart, a PAGI application is beautifully simple - an async coderef with three parameters:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Future::AsyncAwait;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use experimental&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;signatures&#39;&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;async &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;app &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;scope&lt;/span&gt;, $&lt;span class=&#34;synIdentifier&#34;&gt;receive&lt;/span&gt;, $&lt;span class=&#34;synIdentifier&#34;&gt;send&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# $scope   - hashref of connection metadata&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# $receive - async coderef to get events FROM the client&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# $send    - async coderef to send events TO the client&lt;/span&gt;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;$scope&lt;/code&gt; hashref tells you what kind of connection you&#38;#39;re dealing with:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;http&lt;/code&gt; - A standard HTTP request/response&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;websocket&lt;/code&gt; - A persistent WebSocket connection&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;sse&lt;/code&gt; - A Server-Sent Events stream&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;lifespan&lt;/code&gt; - Process startup/shutdown lifecycle&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Events flow in both directions as hashrefs with a &lt;code&gt;type&lt;/code&gt; key. It&#38;#39;s event-driven programming, but with the ergonomics of async/await.&lt;/p&gt;

&lt;h3 id=&#34;Your-First-PAGI-Application&#34;&gt;Your First PAGI Application&lt;/h3&gt;

&lt;p&gt;Let&#38;#39;s start with the classic: Hello World over HTTP.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synComment&#34;&gt;# examples/01-hello-http/app.pl&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Future::AsyncAwait;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use experimental&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;signatures&#39;&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;async &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;app &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;scope&lt;/span&gt;, $&lt;span class=&#34;synIdentifier&#34;&gt;receive&lt;/span&gt;, $&lt;span class=&#34;synIdentifier&#34;&gt;send&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# We only handle HTTP - throw for anything else&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;die&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;Unsupported: &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$scope-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;&#38;quot;&lt;/span&gt; &lt;span class=&#34;synStatement&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$scope-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;synStatement&#34;&gt;ne&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;http&#39;&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# Send the response headers&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;await &lt;span class=&#34;synIdentifier&#34;&gt;$send&lt;/span&gt;-&#38;gt;({&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;type&lt;/span&gt;    =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;http.response.start&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;status&lt;/span&gt;  =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;200&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;headers&lt;/span&gt; =&#38;gt; [[&lt;span class=&#34;synConstant&#34;&gt;&#39;content-type&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;&#39;text/plain&#39;&lt;/span&gt;]],&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;});&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# Send the body and signal we&#39;re done&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;await &lt;span class=&#34;synIdentifier&#34;&gt;$send&lt;/span&gt;-&#38;gt;({&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;type&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;http.response.body&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;body&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;Hello from PAGI!&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;more&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;0&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;});&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Run it with:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;pagi-server --app examples/01-hello-http/app.pl --port 5000&lt;br /&gt;curl http://localhost:5000/&lt;br /&gt;# =&#38;gt; Hello from PAGI!&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Notice how the response is split into two events: &lt;code&gt;http.response.start&lt;/code&gt; for headers, and &lt;code&gt;http.response.body&lt;/code&gt; for content. This separation is what enables streaming - you can send multiple body chunks with &lt;code&gt;more =&#38;gt; 1&lt;/code&gt; before the final &lt;code&gt;more =&#38;gt; 0&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id=&#34;Real-Time-with-WebSockets&#34;&gt;Real-Time with WebSockets&lt;/h3&gt;

&lt;p&gt;Now for the exciting part. Here&#38;#39;s a WebSocket echo server:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synComment&#34;&gt;# examples/04-websocket-echo/app.pl&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Future::AsyncAwait;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use experimental&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;signatures&#39;&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;async &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;app &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;scope&lt;/span&gt;, $&lt;span class=&#34;synIdentifier&#34;&gt;receive&lt;/span&gt;, $&lt;span class=&#34;synIdentifier&#34;&gt;send&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;if&lt;/span&gt; (&lt;span class=&#34;synIdentifier&#34;&gt;$scope-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;synStatement&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;websocket&#39;&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# Accept the WebSocket connection&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;await &lt;span class=&#34;synIdentifier&#34;&gt;$send&lt;/span&gt;-&#38;gt;({ &lt;span class=&#34;synConstant&#34;&gt;type&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;websocket.accept&#39;&lt;/span&gt; });&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# Event loop: receive messages and echo them back&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;while&lt;/span&gt; (&lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$event&lt;/span&gt; = await &lt;span class=&#34;synIdentifier&#34;&gt;$receive&lt;/span&gt;-&#38;gt;();&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;if&lt;/span&gt; (&lt;span class=&#34;synIdentifier&#34;&gt;$event-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;synStatement&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;websocket.receive&#39;&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$message&lt;/span&gt; = &lt;span class=&#34;synIdentifier&#34;&gt;$event-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt; // &lt;span class=&#34;synIdentifier&#34;&gt;$event-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;bytes&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;await &lt;span class=&#34;synIdentifier&#34;&gt;$send&lt;/span&gt;-&#38;gt;({&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;type&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;websocket.send&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;text&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;Echo: &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$message&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;&#38;quot;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;});&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;elsif&lt;/span&gt; (&lt;span class=&#34;synIdentifier&#34;&gt;$event-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;synStatement&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;websocket.disconnect&#39;&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;last&lt;/span&gt;;  &lt;span class=&#34;synComment&#34;&gt;# Client disconnected, exit loop&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;elsif&lt;/span&gt; (&lt;span class=&#34;synIdentifier&#34;&gt;$scope-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;synStatement&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;http&#39;&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# Serve a simple HTML page for testing&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;await &lt;span class=&#34;synIdentifier&#34;&gt;$send&lt;/span&gt;-&#38;gt;({&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;type&lt;/span&gt;    =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;http.response.start&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;status&lt;/span&gt;  =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;200&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;headers&lt;/span&gt; =&#38;gt; [[&lt;span class=&#34;synConstant&#34;&gt;&#39;content-type&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;&#39;text/html&#39;&lt;/span&gt;]],&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;});&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;await &lt;span class=&#34;synIdentifier&#34;&gt;$send&lt;/span&gt;-&#38;gt;({&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;type&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;http.response.body&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;body&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;&#38;lt;script&#38;gt;ws=new WebSocket(&#38;quot;ws://localhost:5000/ws&#38;quot;);&#39;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;. &lt;span class=&#34;synConstant&#34;&gt;&#39;ws.onmessage=e=&#38;gt;console.log(e.data)&#38;lt;/script&#38;gt;&#39;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;. &lt;span class=&#34;synConstant&#34;&gt;&#39;&#38;lt;p&#38;gt;Open console and type: ws.send(&#38;quot;Hello&#38;quot;)&#38;lt;/p&#38;gt;&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;more&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;0&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;});&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;else&lt;/span&gt; {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;die&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;Unsupported: &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$scope-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;type&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;&#38;quot;&lt;/span&gt;;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The pattern is the same: await events from &lt;code&gt;$receive&lt;/code&gt;, send responses via &lt;code&gt;$send&lt;/code&gt;. But now we have a persistent connection with an event loop that keeps running until the client disconnects.&lt;/p&gt;

&lt;p&gt;Test it:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;pagi-server --app examples/04-websocket-echo/app.pl --port 5000&lt;br /&gt;websocat ws://localhost:5000/ws&lt;br /&gt;Hello&lt;br /&gt;# =&#38;gt; Echo: Hello&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;The-PSGI-Bridge:-Bringing-Legacy-Apps-to-the-Future&#34;&gt;The PSGI Bridge: Bringing Legacy Apps to the Future&lt;/h3&gt;

&lt;p&gt;&#38;quot;But wait,&#38;quot; Petra interrupted. &#38;quot;I have thousands of lines of Catalyst code. I can&#38;#39;t rewrite everything!&#38;quot;&lt;/p&gt;

&lt;p&gt;The Ghost nodded sagely. &#38;quot;You don&#38;#39;t have to. PAGI includes a bridge.&#38;quot;&lt;/p&gt;

&lt;p&gt;One of PAGI&#38;#39;s killer features is its ability to wrap existing PSGI applications. Your battle-tested Catalyst, Dancer, or Plack app can run alongside new PAGI code on the same server.&lt;/p&gt;

&lt;p&gt;PAGI ships with &lt;code&gt;PAGI::App::WrapPSGI&lt;/code&gt; which handles all the translation for you:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;PAGI::App::WrapPSGI;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synComment&#34;&gt;# Your existing PSGI application (could be Catalyst, Dancer, etc.)&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$legacy_psgi_app&lt;/span&gt; = &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$env&lt;/span&gt; = &lt;span class=&#34;synStatement&#34;&gt;shift&lt;/span&gt;;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;return&lt;/span&gt; [&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;200&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;[&lt;span class=&#34;synConstant&#34;&gt;&#39;Content-Type&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;text/plain&#39;&lt;/span&gt;],&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;[&lt;span class=&#34;synConstant&#34;&gt;&#39;Hello from legacy PSGI!&#39;&lt;/span&gt;],&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;];&lt;br /&gt;};&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synComment&#34;&gt;# Wrap it for PAGI - that&#39;s it!&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$wrapper&lt;/span&gt; = PAGI::App::WrapPSGI-&#38;gt;new(&lt;span class=&#34;synConstant&#34;&gt;psgi_app&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$legacy_psgi_app&lt;/span&gt;);&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$wrapper&lt;/span&gt;-&#38;gt;to_app;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The wrapper handles all the complexity: building the PSGI &lt;code&gt;%env&lt;/code&gt; from the PAGI scope, collecting request bodies, translating headers, and converting responses back to PAGI events. It even supports PSGI&#38;#39;s streaming response interface.&lt;/p&gt;

&lt;p&gt;For a real-world Catalyst app, it&#38;#39;s just as simple:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;PAGI::App::WrapPSGI;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;MyApp;  &lt;span class=&#34;synComment&#34;&gt;# Your Catalyst application&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$wrapper&lt;/span&gt; = PAGI::App::WrapPSGI-&#38;gt;new(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;psgi_app&lt;/span&gt; =&#38;gt; MyApp-&#38;gt;psgi_app&lt;br /&gt;);&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$wrapper&lt;/span&gt;-&#38;gt;to_app;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This means you can:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Run your existing Catalyst app under PAGI&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add WebSocket endpoints alongside your legacy routes&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gradually migrate to async as needed&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Share connection pools and state between old and new code&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bridge handles the translation between PAGI&#38;#39;s event-based model and PSGI&#38;#39;s synchronous interface transparently.&lt;/p&gt;

&lt;h3 id=&#34;PAGI::Simple:-For-When-You-Want-Express-Not-Assembly&#34;&gt;PAGI::Simple: For When You Want Express, Not Assembly&lt;/h3&gt;

&lt;p&gt;&#38;quot;This is powerful,&#38;quot; Petra admitted, &#38;quot;but it&#38;#39;s also... verbose. Do I really need to manually send response headers every time?&#38;quot;&lt;/p&gt;

&lt;p&gt;The Ghost chuckled. &#38;quot;Of course not. Meet PAGI::Simple.&#38;quot;&lt;/p&gt;

&lt;p&gt;PAGI ships with a micro-framework inspired by Express.js and Sinatra. It handles the low-level event plumbing so you can focus on your application logic:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synComment&#34;&gt;# examples/simple-01-hello/app.pl&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;PAGI::Simple;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$app&lt;/span&gt; = PAGI::Simple-&#38;gt;new(&lt;span class=&#34;synConstant&#34;&gt;name&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;My App&#39;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synComment&#34;&gt;# Simple text response&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$app&lt;/span&gt;-&#38;gt;get(&lt;span class=&#34;synConstant&#34;&gt;&#39;/&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;c&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;text(&lt;span class=&#34;synConstant&#34;&gt;&#39;Hello, World!&#39;&lt;/span&gt;);&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synComment&#34;&gt;# Path parameters and JSON&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$app&lt;/span&gt;-&#38;gt;get(&lt;span class=&#34;synConstant&#34;&gt;&#39;/users/:id&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;c&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$id&lt;/span&gt; = &lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;path_params-&#38;gt;{id};&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;json({ &lt;span class=&#34;synConstant&#34;&gt;user_id&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$id&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;name&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;Santa Claus&#39;&lt;/span&gt; });&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synComment&#34;&gt;# POST with body parsing&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$app&lt;/span&gt;-&#38;gt;post(&lt;span class=&#34;synConstant&#34;&gt;&#39;/api/data&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;c&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$data&lt;/span&gt; = &lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;json_body;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;json({ &lt;span class=&#34;synConstant&#34;&gt;received&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$data&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;status&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ok&#39;&lt;/span&gt; });&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$app&lt;/span&gt;-&#38;gt;to_app;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;WebSockets are just as clean:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synComment&#34;&gt;# WebSocket chat with PAGI::Simple&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;PAGI::Simple;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$app&lt;/span&gt; = PAGI::Simple-&#38;gt;new(&lt;span class=&#34;synConstant&#34;&gt;name&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;Chat&#39;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$app&lt;/span&gt;-&#38;gt;websocket(&lt;span class=&#34;synConstant&#34;&gt;&#39;/chat&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;ws&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$ws&lt;/span&gt;-&#38;gt;on(&lt;span class=&#34;synConstant&#34;&gt;open&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$ws&lt;/span&gt;-&#38;gt;&lt;span class=&#34;synStatement&#34;&gt;send&lt;/span&gt;(&lt;span class=&#34;synConstant&#34;&gt;&#39;Welcome to the chat!&#39;&lt;/span&gt;);&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;});&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$ws&lt;/span&gt;-&#38;gt;on(&lt;span class=&#34;synConstant&#34;&gt;message&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;data&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# Broadcast to all connected clients&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$ws&lt;/span&gt;-&#38;gt;broadcast(&lt;span class=&#34;synConstant&#34;&gt;&#38;quot;Someone said: &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$data&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;&#38;quot;&lt;/span&gt;);&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;});&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$ws&lt;/span&gt;-&#38;gt;on(&lt;span class=&#34;synConstant&#34;&gt;close&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;print&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;Client disconnected&lt;/span&gt;&lt;span class=&#34;synSpecial&#34;&gt;\n&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;&#38;quot;&lt;/span&gt;;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;});&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$app&lt;/span&gt;-&#38;gt;to_app;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And Server-Sent Events for real-time dashboards:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synComment&#34;&gt;# SSE with PAGI::Simple&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;PAGI::Simple;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$app&lt;/span&gt; = PAGI::Simple-&#38;gt;new(&lt;span class=&#34;synConstant&#34;&gt;name&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;Dashboard&#39;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$app&lt;/span&gt;-&#38;gt;sse(&lt;span class=&#34;synConstant&#34;&gt;&#39;/events&#39;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;($&lt;span class=&#34;synIdentifier&#34;&gt;sse&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# Send events periodically&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$count&lt;/span&gt; = &lt;span class=&#34;synConstant&#34;&gt;0&lt;/span&gt;;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$sse&lt;/span&gt;-&#38;gt;on(&lt;span class=&#34;synConstant&#34;&gt;connect&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# This would typically be triggered by real events&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$sse&lt;/span&gt;-&#38;gt;&lt;span class=&#34;synStatement&#34;&gt;send&lt;/span&gt;({ &lt;span class=&#34;synConstant&#34;&gt;count&lt;/span&gt; =&#38;gt; ++&lt;span class=&#34;synIdentifier&#34;&gt;$count&lt;/span&gt; });&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;});&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$app&lt;/span&gt;-&#38;gt;to_app;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;PAGI::Simple includes:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Express-style routing with path parameters&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Built-in JSON parsing and response helpers&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Session management with pluggable stores&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Middleware support (CORS, logging, rate limiting, etc.)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Static file serving&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;WebSocket rooms and broadcasting&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SSE channels with pub/sub&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;The-Road-Ahead&#34;&gt;The Road Ahead&lt;/h3&gt;

&lt;p&gt;The Ghost began to fade. &#38;quot;Remember, young programmer: this is a vision of what could be, not what must be. PAGI needs champions to make it real.&#38;quot;&lt;/p&gt;

&lt;p&gt;PAGI is currently in early beta. It passes its tests, the examples work, but it&#38;#39;s not yet battle-tested for production. What it needs now is:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Adventurous developers willing to experiment&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Feedback on the API and specification&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Testing with real-world applications&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Framework authors interested in building on PAGI&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;PAGI is not on CPAN at the time of this writing but the repository is at &lt;a href=&#34;https://github.com/jjn1056/pagi&#34;&gt;https://github.com/jjn1056/pagi&lt;/a&gt; and it&#38;#39;s ready for your experimentation. Just don&#38;#39;t use it for production just yet, unless you really, really know what you are doing.&lt;/p&gt;

&lt;p&gt;If you&#38;#39;re interested in the future of asynchronous Perl web development - if you want WebSockets and SSE without abandoning your existing codebase - PAGI might be exactly what you&#38;#39;re looking for.&lt;/p&gt;

&lt;p&gt;Petra smiled as dawn broke over her keyboard. She had work to do, but for the first time in months, she felt excited about it.&lt;/p&gt;

&lt;p&gt;Maybe the future of Perl web development was brighter than she&#38;#39;d thought.&lt;/p&gt;

&lt;h3 id=&#34;Getting-Started&#34;&gt;Getting Started&lt;/h3&gt;

&lt;p&gt;Ready to try PAGI yourself?&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;git clone https://github.com/jjn1056/pagi.git&lt;br /&gt;cd pagi&lt;br /&gt;cpanm --installdeps .  # Install dependencies&lt;br /&gt;prove -l t/            # Run the tests&lt;br /&gt;&lt;br /&gt;# Try the examples&lt;br /&gt;pagi-server --app examples/01-hello-http/app.pl --port 5000&lt;br /&gt;pagi-server --app examples/simple-01-hello/app.pl --port 5000&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The repository includes:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;9 raw PAGI examples demonstrating the protocol&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;4 PAGI::Simple examples showing the micro-framework&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Complete specification documents&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A reference server implementation&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy holidays, and happy hacking!&lt;/p&gt;

&lt;h3 id=&#34;See-Also&#34;&gt;See Also&lt;/h3&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://github.com/jjn1056/pagi&#34;&gt;PAGI Repository&lt;/a&gt;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://asgi.readthedocs.io/&#34;&gt;ASGI (Python)&lt;/a&gt; - The inspiration&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://metacpan.org/pod/PSGI&#34;&gt;PSGI&lt;/a&gt; - The predecessor&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://metacpan.org/pod/IO::Async&#34;&gt;IO::Async&lt;/a&gt; - The async foundation&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://metacpan.org/pod/Future::AsyncAwait&#34;&gt;Future::AsyncAwait&lt;/a&gt; - Making async readable&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;</summary><updated>2025-12-10T00:00:00Z</updated><category term="Perl"/><author><name>John Napiorkowski</name></author></entry><entry><title>Run specific tests in Perl</title><link href="https://perladvent.org/2025/2025-12-09.html"/><id>https://perladvent.org/2025/2025-12-09.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;Have you ever had the experience of having many subtests in a single test file, making it difficult to see the test results for the parts you want to check?&lt;/p&gt;

&lt;p&gt;With &lt;a href=&#34;https://metacpan.org/module/Test2::Plugin::SubtestFilter&#34;&gt;Test2::Plugin::SubtestFilter&lt;/a&gt;, you can run only specific subtests. This is similar to &lt;a href=&#34;https://vitest.dev/config/testnamepattern&#34;&gt;vitest&#38;#39;s --testNamePattern&lt;/a&gt; or &lt;a href=&#34;https://pkg.go.dev/cmd/go#hdr-Testing_flags&#34;&gt;go test&#38;#39;s -run flag&lt;/a&gt; in other languages.&lt;/p&gt;

&lt;p&gt;If you use &lt;code&gt;Test2::Plugin::SubtestFilter&lt;/code&gt;, only subtests matching the &lt;code&gt;SUBTEST_FILTER&lt;/code&gt; environment variable will be executed. For example, given the following test file, if you specify &lt;code&gt;only_run_this&lt;/code&gt; in &lt;code&gt;SUBTEST_FILTER&lt;/code&gt;, only &lt;code&gt;only_run_this&lt;/code&gt; will be executed, and &lt;code&gt;do_not_run&lt;/code&gt; will be skipped.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Test2::V0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Test2::Plugin::SubtestFilter&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;subtest&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;only_run_this&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;pass&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;subtest&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;do_not_run&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;pass&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;done_testing&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Run the test like this:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;% SUBTEST_FILTER=only_run_this prove -lvr t/test.t&lt;br /&gt;# =&#38;gt; Test `only_run_this` / Skip `do_not_run`&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;Tips:-VSCode-Extension&#34;&gt;Tips: VSCode Extension&lt;/h3&gt;

&lt;p&gt;With the following VSCode extension, you can run tests simply by clicking the run icon for test execution. How convenient!&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=kfly8.test2-subtest-filter&#34;&gt;VSCode Extension: Perl Test&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;run-specific-tests-in-perl-vscode-extension.gif&#34; alt=&#34;VSCode extension demo&#34; width=&#34;600&#34;/&gt;

&lt;/p&gt;



&lt;h3 id=&#34;Tips:-Prompt-for-Coding-Agents&#34;&gt;Tips: Prompt for Coding Agents&lt;/h3&gt;

&lt;p&gt;If you give a coding agent a prompt like the following, it will use &lt;code&gt;SUBTEST_FILTER&lt;/code&gt;. By narrowing down the test results to only the areas of interest, you get the benefit of reducing context consumption.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synSpecial&#34;&gt;###&lt;/span&gt; Test Execution Rules (Required)&lt;br /&gt;&lt;br /&gt;**When adding or modifying functions or subtests, always use&lt;br /&gt;`SUBTEST_FILTER` to run only the relevant subtest.**&lt;br /&gt;&lt;br /&gt;Example: When adding a &lt;span class=&#34;synSpecial&#34;&gt;`&lt;/span&gt;power&lt;span class=&#34;synSpecial&#34;&gt;`&lt;/span&gt; function&lt;br /&gt;&lt;span class=&#34;synSpecial&#34;&gt;```bash&lt;/span&gt;&lt;br /&gt;# Good: Run only the relevant subtest&lt;br /&gt;SUBTEST_FILTER=power prove t/Hello/Math.t&lt;br /&gt;&lt;br /&gt;# Bad: Run all tests (slow, adds noise from unrelated tests)&lt;br /&gt;prove t/Hello/Math.t&lt;br /&gt;&lt;span class=&#34;synSpecial&#34;&gt;```&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Run all tests (&lt;span class=&#34;synSpecial&#34;&gt;`&lt;/span&gt;prove t/&lt;span class=&#34;synSpecial&#34;&gt;`&lt;/span&gt;) only in the following cases:&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;-&lt;/span&gt; When the user explicitly requests a full test run&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;-&lt;/span&gt; When making broad changes such as refactoring&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;-&lt;/span&gt; As a final check before committing&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That&#38;#39;s all. Please give it a try. Happy testing!&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-09T00:00:00Z</updated><category term="Perl"/><author><name>kobaken</name></author></entry><entry><title>Perl who is Naughty or Nice?</title><link href="https://perladvent.org/2025/2025-12-08.html"/><id>https://perladvent.org/2025/2025-12-08.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;&lt;img src=&#34;Elf-nice-list.png&#34; alt=&#34;Elf Nice List&#34;&gt;

&lt;/p&gt;



&lt;h2 id=&#34;Elves-are-scrambling&#34;&gt;Elves are scrambling&lt;/h2&gt;

&lt;p&gt;Santa&#38;#39;s ETL framework went down and now the elves are tasked with writing a Perl script to process the &#38;quot;Naughty or Nice&#38;quot; master feed files. To tackle this problem Chaz (one of Santa&#38;#39;s elves) will generate a sample feed file and use it as test data for a new Perl script.&lt;/p&gt;

&lt;h2 id=&#34;Test-Data-Format&#34;&gt;Test Data Format&lt;/h2&gt;

&lt;p&gt;Chaz needs to be able to conform with the original format of the feed file (a CSV file) with the following fields:&lt;/p&gt;

&lt;p&gt;first_name last_name street_address city state postal_code Naughty_or_Nice_flag&lt;/p&gt;

&lt;p&gt;While thinking about this problem, he remembered a Perl module named &lt;a href=&#34;https://metacpan.org/module/Data::Random::Contact&#34;&gt;Data::Random::Contact&lt;/a&gt; that he found while searching for a module to generate random data via MetaCPAN. So he borrowed a laptop from the toy factory and started working on a script.&lt;/p&gt;

&lt;h2 id=&#34;Random-Data-Generator-script&#34;&gt;Random Data Generator script&lt;/h2&gt;

&lt;p&gt;The data from &lt;a href=&#34;https://metacpan.org/module/Data::Random::Contact&#34;&gt;Data::Random::Contact&lt;/a&gt; is actually generated from Fakenamegenerator.com. To make the test data as close to production, Chaz wrote the script to generate at least 200 records.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;#!/usr/bin/perl&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;strict&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;warnings&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Data::Dumper&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Data::Random::Contact&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# write out a randomly generated list of demographic data&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$rand&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Data::Random::Contact&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;();&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$i&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$i&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;lt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;200&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$i&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;++&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;    #person() returns a hashref of contact data&lt;br /&gt;&lt;/span&gt;    &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$person&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$rand&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;person&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;();&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;    # only print if postal code is def ( some records can have empty postal code )&lt;br /&gt;&lt;/span&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$person&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;address&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;home&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;postal_code&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$n_or_n&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;rand&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;print&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;single&#34;&gt;&#39;,&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$person&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;given&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$person&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;surname&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$person&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;address&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;home&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;street_1&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$person&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;address&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;home&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;city&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$person&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;address&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;home&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;region&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$person&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;address&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;home&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;postal_code&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$n_or_n&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;\n&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;The-Test-Data&#34;&gt;The Test Data&lt;/h2&gt;

&lt;p&gt;It&#38;#39;s a few hours before Christmas and Chaz has already written a Perl script that will generate random data needed to test his new data extractor program.&lt;/p&gt;

&lt;p&gt;Chaz executed the script like so:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;$ generate_kids_data.pl &#38;gt; master_list.csv&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and generated the sample test data (truncated for visibility):&lt;/p&gt;

&lt;h2 id=&#34;Use-Getopt::Long-to-select-Naughty-and-Nice-output-files&#34;&gt;Use Getopt::Long to select Naughty and Nice output files&lt;/h2&gt;

&lt;p&gt;Chaz is very close to the last 2 hours of Christmas Eve and now has a working Perl script that will output Naughty and/or Nice kids from the list in separate files. Chaz also had to consider the fact that the master file of kids can be sent in batches of a few hundred records at a time so he had to add the ability to pass some argument options to allow for defining a filename as an argument and to select whether the output file will contain Naughty or Nice kids.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;#!/usr/bin/perl&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;strict&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;warnings&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Data::Dumper&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Getopt::Long&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$nice&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$naughty&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$file&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# parse options&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;GetOptions&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;double&#34;&gt;&#38;quot;nice&#38;quot;&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;cast&#34;&gt;\&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$nice&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;double&#34;&gt;&#38;quot;naughty&#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;cast&#34;&gt;\&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$naughty&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;double&#34;&gt;&#38;quot;file=s&#38;quot;&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;cast&#34;&gt;\&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$file&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;double&#34;&gt;&#38;quot;help&#38;quot;&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;cast&#34;&gt;\&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;&#38;amp;help&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;die&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;Unable to parse information&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;help&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;die&lt;/span&gt; &lt;span class=&#34;heredoc&#34;&gt;&#38;lt;&#38;lt;&#39;USAGE&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;heredoc_content&#34;&gt;Naughty_or_Nice.pl -nice -naughty&lt;br /&gt;&lt;br /&gt;Accepts the following arguments&lt;br /&gt;&lt;br /&gt;-nice :    generates a nice list &#38;quot;Nice_list.csv&#38;quot;.&lt;br /&gt;-naughty : generates a naughty list &#38;quot;Naughty_list&#38;quot;.&lt;br /&gt;-file :    file name as input. default filename &#38;quot;master_list.csv&#38;quot;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;heredoc_terminator&#34;&gt;USAGE&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$nice&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;&#38;quot;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;and&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$naughty&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;&#38;quot;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;die&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;$0 requires -nice or -naughty option&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$in_fh&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Create a filehandle from file option or default csv file.&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$file&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;open&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$in_fh&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#38;lt;&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$file&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;die&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;Unable to open &#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$file&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;else&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;open&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$in_fh&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#38;lt;&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;master_list.csv&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;die&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;Unable to open master_list.csv&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;our&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$nice_fh&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$naughty_fh&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# Create file handle for Nice and Naughty files if set.&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$nice&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;open&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$nice_fh&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#38;gt;&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;Nice_list.csv&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;die&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;Unable to open Nice_list.csv&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$naughty&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;open&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$naughty_fh&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#38;gt;&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;Naughty_list.csv&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;die&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;double&#34;&gt;&#38;quot;Unable to open Naughty_list.csv&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$rec&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;readline&#34;&gt;&#38;lt;$in_fh&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$rec&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=~&lt;/span&gt; &lt;span class=&#34;substitute&#34;&gt;s/\R//g&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;    # separate the fields&lt;br /&gt;&lt;/span&gt;    &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@fields&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;split&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;match&#34;&gt;/,/&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$rec&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;    #filter out the nice kids&lt;br /&gt;&lt;/span&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$nice&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;        # Nice kids get printed&lt;br /&gt;&lt;/span&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$fields&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;print&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$nice_fh&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$rec&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;\n&#38;quot;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;    #filter out the naughty kids&lt;br /&gt;&lt;/span&gt;    &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$naughty&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;        # Naughty kids get printed&lt;br /&gt;&lt;/span&gt;        &lt;span class=&#34;keyword&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$fields&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;==&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;print&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$naughty_fh&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$rec&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;\n&#38;quot;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In a rush Chaz executed the script to read the master_list.csv sample file and generate the Nice and Naughty output files:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;$ Naughty_or_Nice.pl -nice -naughty&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This resulted in two files: Naughty_list.csv and Nice_list.csv&lt;/p&gt;

&lt;p&gt;Happy with the results he quickly created a pull request so the release team can install the script in production and schedule a run of the script against the kids feed files.&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-08T00:00:00Z</updated><category term="Perl"/><author><name>Charlie Gonzalez</name></author></entry><entry><title>Abstract storage of Christmas letters</title><link href="https://perladvent.org/2025/2025-12-07.html"/><id>https://perladvent.org/2025/2025-12-07.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;&lt;img src=&#34;christmas-letters.png&#34; alt=&#34;Children writing Christmas letters&#34;&gt;

&lt;/p&gt;



&lt;p&gt;Few things capture childhood wonder quite like a child writing a letter to Santa. The joy of watching the little ones list all the toys and sweets they desire. It&#38;#39;s truly magical indeed. But have you ever wondered what happens to these letters after they get sent?&lt;/p&gt;

&lt;p&gt;Well, this isn&#38;#39;t something Christmas Inc. would consider sharing in a LinkedIn post. Up until recently, this was... purely manual labour. Having very numerous elf workers with unlimited energy at their disposal surely delays the decision to digitalize processes. However, this year that decision has finally been made. No more manual reading of letters - they would now be gathered and fed to an AI to read and pull kids&#38;#39; wishes out of them.&lt;/p&gt;

&lt;p&gt;There&#38;#39;s just one problem - the team responsible for gathering and storing the letters hadn&#38;#39;t yet decided on where they are going to be stored! They managed to decide that the storage must work like a filesystem, with directories named &lt;code&gt;naughty&lt;/code&gt; and &lt;code&gt;nice&lt;/code&gt; containing files named the same as the sender. But would it be stored in an actual filesystem? A database? A cloud storage? Maybe a mix of those? They just didn&#38;#39;t know yet! Even worse, that team consisted of only the most undecided elves in the entire corporation - you never know if this decision will be final or not. To make up for their clumsiness, the code needed to be written in a way that will make it easy to change the underlying source of data easily.&lt;/p&gt;

&lt;p&gt;An elf named Frosty was tasked with writing a LetterStore service, which must deliver an API action to serve contents of the letters. Frosty could write a proper abstraction that will fetch him a letter content and replace it later if needed, but maybe there&#38;#39;s a better way? Skimming through CPAN, he noticed a module called &lt;a href=&#34;https://metacpan.org/module/Storage::Abstract&#34;&gt;Storage::Abstract&lt;/a&gt;, which seemed to be doing exactly what he needed. He could get a module now and use it for testing in local filesystem, and if the location of the letters ever change, he will only need to write / acquire a driver for that storage, replace it in the configuration, and everything will work exactly the same!&lt;/p&gt;

&lt;p&gt;There were a couple of problems - the module was listed as beta quality, and not many storage drivers existed for it yet. But he heard that a stable release of the module was planned very soon, and last-minute suggestions or reviews were welcome. As for the drivers, well, he would have to write the code himself anyway, so he might as well write it as a Storage::Abstract driver.&lt;/p&gt;

&lt;p&gt;With his mind set on using the module, he started writing code. First, he wrote a small package that will build a Storage::Abstract instance and let it be reconfigured via a package variable:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;package&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;LetterStore&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;v5.42&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Storage::Abstract&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# for now, assume some local directory&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;our&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%STORAGE_CONFIG&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;driver&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;Directory&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;directory&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;/home/santa/letters&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;get&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;state&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$instance&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Storage::Abstract&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;%STORAGE_CONFIG&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$instance&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This package always returns the same instance, so it can only be reconfigured before &lt;code&gt;get&lt;/code&gt; function is used. This was okay, since Frosty did not expect his colleagues to be crazy enough to modify the storage location dynamically.&lt;/p&gt;

&lt;p&gt;Next, he wrote a small &lt;a href=&#34;https://metacpan.org/module/Mojolicious&#34;&gt;Mojolicious&lt;/a&gt; application which implemented the LetterStore service. It contained two actions - &lt;code&gt;/letters/count&lt;/code&gt; to fetch the total number of letters, and &lt;code&gt;/letters&lt;/code&gt; to fetch them. To avoid returning too many letters at once, he decided that they will be returned in pages, &lt;code&gt;5&lt;/code&gt; at a time. The action to return them returns &lt;code&gt;cursor&lt;/code&gt;, which may return an index of the next letter to fetch if there are more letters. This value can be appended into the action URL to fetch the next page:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;#!/usr/bin/env perl&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;v5.42&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Mojolicious::Lite&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;List::Util&lt;/span&gt; &lt;span class=&#34;words&#34;&gt;qw(min)&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;LetterStore&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;constant&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;RESPONSE_SIZE&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# get the storage and fetch the list of files. This assumes that new files&lt;br /&gt;# will not be added at runtime, but it can be refreshed periodically if needed&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$store&lt;/span&gt;      &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;LetterStore&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$store_list&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$store&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;list&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;get&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;/letters/count&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;prototype&#34;&gt;($c)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$c&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;render&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;json&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;count&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;scalar&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$store_list&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;cast&#34;&gt;@*&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;get&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;/letters/:cursor&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;cursor&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;0&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;prototype&#34;&gt;($c)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;    # cursor will be an index of next letter to fetch - must be a positive integer&lt;br /&gt;&lt;/span&gt;    &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$cursor&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$c&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;stash&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;single&#34;&gt;&#39;cursor&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$c&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;render&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;json&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;error&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;bad cursor&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;status&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;422&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$cursor&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=~&lt;/span&gt; &lt;span class=&#34;match&#34;&gt;/\D/&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;    # fetch a list of filenames&lt;br /&gt;&lt;/span&gt;    &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$next_cursor&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$cursor&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;RESPONSE_SIZE&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@files&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$store_list&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;cast&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$cursor&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;min&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$next_cursor&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$store_list&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;cast&#34;&gt;$#*&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;];&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@response_data&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;foreach&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$filename&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;@files&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;        # extract child name and conduct from path&lt;br /&gt;&lt;/span&gt;        &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$conduct&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$child_name&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$filename&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=~&lt;/span&gt; &lt;span class=&#34;match&#34;&gt;m{ ([^/]+) / ([^/]+) $}x&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;        # fetch file content&lt;br /&gt;&lt;/span&gt;        &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$fh&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$store&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;retrieve&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$filename&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$file_content&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;join&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$fh&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;getlines&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;        # add data to response&lt;br /&gt;&lt;/span&gt;        &lt;span class=&#34;word&#34;&gt;push&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;@response_data&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;conduct&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$conduct&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;child&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$child_name&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;letter&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$file_content&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;    # no cursor if we don&#39;t have any more data&lt;br /&gt;&lt;/span&gt;    &lt;span class=&#34;symbol&#34;&gt;$next_cursor&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;core&#34;&gt;undef&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$next_cursor&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$store_list&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;cast&#34;&gt;$#*&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$c&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;render&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;json&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;data&lt;/span&gt;   &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;cast&#34;&gt;\&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;@response_data&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;cursor&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$next_cursor&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;app&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;start&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;That wasn&#38;#39;t too hard and worked. Or did it? Errors at this stage would be unacceptable, as they could cause Christmas Inc. to lose credibility and crash its stock price. He had to test it, but how can he do it most efficiently? Use a temporary directory?&lt;/p&gt;

&lt;p&gt;As it turned out, there&#38;#39;s a better way. Thanks to Storage::Abstract being so abstract, you can set your test script to use &lt;code&gt;Memory&lt;/code&gt; driver, which keeps everything inside Perl&#38;#39;s memory, yet still acts the same as any other driver! This method is faster, requires no extra modules to be imported and no cleanup after the test:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;version&#34;&gt;v5.42&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Test2::V0&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Test::Mojo&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;LetterStore&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Mojo::File&lt;/span&gt; &lt;span class=&#34;words&#34;&gt;qw(curfile)&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# point our storage to Memory. Needs to be done early,&lt;br /&gt;# before the script is loaded&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;%LetterStore::STORAGE_CONFIG&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;driver&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;Memory&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# fill the storage with sample letters&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%wishlist&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;get_letters&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;();&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$storage&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;LetterStore&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;foreach&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$conduct&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$names&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;%wishlist&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;foreach&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$name&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$letter&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$names&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;cast&#34;&gt;%*&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;        # scalar reference marks content, unlike plain string which marks a&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;# local filename&lt;br /&gt;&lt;/span&gt;        &lt;span class=&#34;symbol&#34;&gt;$storage&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;store&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;/$conduct/$name&#38;quot;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;cast&#34;&gt;\&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$letter&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;# load LetterStore service&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$script&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;curfile&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;sibling&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;single&#34;&gt;&#39;letter_store.pl&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$t&lt;/span&gt;      &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;Test::Mojo&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$script&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;comment&#34;&gt;##################&lt;br /&gt;#    TEST IT!    #&lt;br /&gt;##################&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;subtest&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;should have the right number of letters&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$t&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;get_ok&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;single&#34;&gt;&#39;/letters/count&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;status_is&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;200&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;json_is&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;/count&#39;&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;count ok&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;subtest&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;should get first set of letters&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;test_letters&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;core&#34;&gt;undef&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;5&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;subtest&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;should get second set of letters&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;test_letters&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;number&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;core&#34;&gt;undef&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;done_testing&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;test_letters&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$cursor&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$expected_data_count&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$expected_next_cursor&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;symbol&#34;&gt;$t&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;get_ok&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;/letters&#39;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;.&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$cursor&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;/$cursor&#38;quot;&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;&#39;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;status_is&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;number&#34;&gt;200&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;json_has&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;single&#34;&gt;&#39;/cursor&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;json_has&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;single&#34;&gt;&#39;/data&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$response&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$t&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;tx&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;scalar&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$response&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;cast&#34;&gt;@*&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$expected_data_count&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;  &lt;span class=&#34;single&#34;&gt;&#39;data count ok&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$response&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;cursor&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;          &lt;span class=&#34;symbol&#34;&gt;$expected_next_cursor&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;cursor ok&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;foreach&lt;/span&gt; &lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$letter_data&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$response&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;cast&#34;&gt;@*&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$conduct&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$letter_data&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;conduct&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$name&lt;/span&gt;    &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$letter_data&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;child&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;};&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$conduct&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;in_set&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;keys&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;%wishlist&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;single&#34;&gt;&#39;conduct ok&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;is&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$wishlist&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$conduct&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}{&lt;/span&gt;&lt;span class=&#34;symbol&#34;&gt;$name&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$letter_data&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;letter&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;single&#34;&gt;&#39;letter content ok&#39;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;sub&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;get_letters&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;keyword&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;(&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;nice&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;Timmy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;heredoc&#34;&gt;&#38;lt;&#38;lt;~LETTER&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;heredoc_content&#34;&gt;            Dear Santa,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;I&#39;ve been really good this year! I&#39;d love some toys for Christmas -&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;maybe some action figures and a cool board game.&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Thank you for bringing presents to all the kids!&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;heredoc_terminator&#34;&gt;            LETTER&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;Susie&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;heredoc&#34;&gt;&#38;lt;&#38;lt;~LETTER&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;heredoc_content&#34;&gt;            Dear Santa,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;I&#39;ve been good all year and I&#39;m so excited for Christmas! Please bring&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;me a unicorn toy, some colored markers, and a dollhouse.&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Thank you for making Christmas magical!&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;heredoc_terminator&#34;&gt;            LETTER&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;Lisa&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;heredoc&#34;&gt;&#38;lt;&#38;lt;~LETTER&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;heredoc_content&#34;&gt;            Dear Santa,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Hi! I hope your reindeer are doing good. This year I was nice mostly.&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;For Christmas I want:&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;- A bicycle&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;- A puppy&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;- A Nintendo Switch&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;- A skateboard&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;- A guitar&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;- A trampoline&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Can you bring all of them? Please?&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;heredoc_terminator&#34;&gt;            LETTER&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;naughty&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;structure&#34;&gt;{&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;Sammy&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;heredoc&#34;&gt;&#38;lt;&#38;lt;~LETTER&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;heredoc_content&#34;&gt;            Dear Santa,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;This year I&#39;ve tried hard to be good. I hope you&#39;ll visit me on&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Christmas Eve! I really want a skateboard and some art supplies - oh,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;and maybe a video game too.&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;You&#39;re the best!&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;heredoc_terminator&#34;&gt;            LETTER&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;Maggie&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;heredoc&#34;&gt;&#38;lt;&#38;lt;~LETTER&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;heredoc_content&#34;&gt;            Dear Santa,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;I know I haven&#39;t been perfect this year - I&#39;ve talked back and gotten&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;into trouble a few times. Would you bring me a roller skate and a&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;gaming headset for Christmas? I promise to be better next year!&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;I hope you&#39;ll still visit me!&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;heredoc_terminator&#34;&gt;            LETTER&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;word&#34;&gt;Stevie&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&#38;gt;&lt;/span&gt; &lt;span class=&#34;heredoc&#34;&gt;&#38;lt;&#38;lt;~LETTER&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;heredoc_content&#34;&gt;            Hey Santa,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;I want a drone, a remote control car, and the new gaming console. I&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;better get them or I&#39;m telling everyone you&#39;re not real.&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;heredoc_terminator&#34;&gt;            LETTER&lt;br /&gt;&lt;/span&gt;        &lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;,&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;structure&#34;&gt;);&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;structure&#34;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Test script had proven that the service worked as expected. Moreover, &lt;a href=&#34;https://metacpan.org/module/Storage::Abstract&#34;&gt;Storage::Abstract&lt;/a&gt; had more cool tools at its disposal that could prove useful later, like a &lt;code&gt;Composite&lt;/code&gt; meta-driver which lets you use more than one driver at once. All it lacks is an ecosystem of drivers, but everyone has to start somewhere, right?&lt;/p&gt;

&lt;p&gt;Frosty shared his thoughts and experiences with the author of the module, so that he can make a more informed decision about releasing the stable version. Thanks to the elf&#38;#39;s work, every child &lt;i&gt;should&lt;/i&gt; get their present this year!&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-07T00:00:00Z</updated><category term="Perl"/><author><name>Bartosz Jarzyna</name></author></entry><entry><title>ToyCo want to push new toy updates</title><link href="https://perladvent.org/2025/2025-12-06.html"/><id>https://perladvent.org/2025/2025-12-06.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;&lt;img src=&#34;ernest-sophie.png&#34;&gt;

&lt;/p&gt;



&lt;h3 id=&#34;ToyCo-are-not-happy&#34;&gt;ToyCo are not happy...&lt;/h3&gt;

&lt;p&gt;Sophie cleared her throat, waited for Ernest, Santa&#38;#39;s right-hand elf, to look up from his screen. &#38;quot;Boss. We have a problem.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Oh?&#38;quot; He sighed. &#38;quot;What now?&#38;quot;&lt;/p&gt;

&lt;p&gt;She gestured at the tablet she was holding. &#38;quot;You know that script we had that fetched new arrivals from their API? Well... it seems Santa wants those updates the instant they appear, so every department is running that script once a minute, and it&#38;#39;s crippling ToyCo&#38;#39;s servers.&#38;quot; A pause. &#38;quot;Um, also we get duplicates because there are multiple copies of it running.&#38;quot;&lt;/p&gt;

&lt;p&gt;Ernest managed a slight frazzled grin. &#38;quot;You know how to fix that, at least.&#38;quot;&lt;/p&gt;

&lt;p&gt;She nodded. &#38;quot;Yes, but ToyCo also said they could push us a feed, if we set up a web server to accept it. How do we do that?&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;Mojolicious::Lite&#34;&gt;Mojolicious::Lite&lt;/h3&gt;

&lt;p&gt;He chuckled for real this time. &#38;quot;Oh? that all? Easy. Watch and learn, young elf.&#38;quot; With a few keystrokes, he opened an edit window on a new file called &lt;code&gt;toyco_app.pl&lt;/code&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synPreProc&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Mojolicious::Lite;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Sophie perked up. &#38;quot;What&#38;#39;s that?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;That? It&#38;#39;s a very simple module that allows us to run up a web server. Here...&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;post &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;/feeds/toyco&#38;quot;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt; = &lt;span class=&#34;synStatement&#34;&gt;shift&lt;/span&gt;;&lt;br /&gt;};&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;It comes,&#38;quot; he explained, &#38;quot;With a bunch of little helper subs that basically make up a tiny Domain Specific Language that will allow us to define stuff. So &lt;code&gt;post&lt;/code&gt; says we want to accept a POST request at that URL, and then the &lt;code&gt;$c&lt;/code&gt; argument contains the data we&#38;#39;ll need to handle.&#38;quot;&lt;/p&gt;

&lt;p&gt;Sophie hmmed. &#38;quot;And we just tell them the URL? How...?&#38;quot;&lt;/p&gt;

&lt;p&gt;He smirked. &#38;quot;I&#38;#39;ll get to that. First up, let&#38;#39;s parse the data. I&#38;#39;m assuming it&#38;#39;s coming in the request body in the same JSON format as the last time?&#38;quot;&lt;/p&gt;

&lt;p&gt;A nod. &#38;quot;Yes, but one item at a time, rather than a list.&#38;quot;&lt;/p&gt;

&lt;p&gt;Ok. That&#38;#39;s easy. Back to our POST handler. &lt;code&gt;$c-&#38;gt;req&lt;/code&gt; is basically our incoming request, and Mojo has a really fast JSON parser built in that will give us the request body as a perl data structure...&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;post &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;/feeds/toyco&#38;quot;&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt; = &lt;span class=&#34;synStatement&#34;&gt;shift&lt;/span&gt;;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$item&lt;/span&gt; = &lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;req-&#38;gt;json;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;add_to_db(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;supplier&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;ToyCo&#38;quot;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;SKU&lt;/span&gt;      =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$item-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;sku&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;name&lt;/span&gt;     =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$item-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;cost&lt;/span&gt;     =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$item-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;wholesale_price&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;RRP&lt;/span&gt;      =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$item-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;rrp&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;);&lt;br /&gt;};&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;Did they say if they expected any JSON back?&#38;quot;&lt;/p&gt;

&lt;p&gt;Another glance at her tablet. &#38;quot;Just an optional message field with some arbitrary text.&#38;quot;&lt;/p&gt;

&lt;p&gt;Ernest hrmed. &#38;quot;Ok. Seems a bit pointless, but we could just do this before the end of the subroutine...&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$c&lt;/span&gt;-&#38;gt;render( &lt;span class=&#34;synConstant&#34;&gt;json&lt;/span&gt; =&#38;gt; { &lt;span class=&#34;synConstant&#34;&gt;message&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;Accepted&#38;quot;&lt;/span&gt; }, &lt;span class=&#34;synConstant&#34;&gt;status&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;200&lt;/span&gt; );&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;...and there you go.&#38;quot; He paused, laughed. &#38;quot;Whoops. Helps to start the app. So we stick this line at the end of the script proper...&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;app-&#38;gt;start;&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;Running-and-testing&#34;&gt;Running and testing&lt;/h3&gt;

&lt;p&gt;Sophie hid a smile, then paused and worried at her bottom lip. &#38;quot;That&#38;#39;s all very well, but how do we run it?&#38;quot;&lt;/p&gt;

&lt;p&gt;Ernest gave her a pleased smile. &#38;quot;Thought you&#38;#39;d never ask. Ok. To test it, there&#38;#39;s &lt;code&gt;morbo&lt;/code&gt;, which is their development runner...&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;morbo ./toyco_app.pl&lt;br /&gt;Server available at http://*:3000&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;There. That opens it by default listening on port 3000, so you can now test it by feeding it data via something like &lt;code&gt;HTTPie&lt;/code&gt;.&#38;quot;&lt;/p&gt;

&lt;p&gt;Sophie&#38;#39;s eyes widened. &#38;quot;What&#38;#39;s that?&#38;quot;&lt;/p&gt;

&lt;p&gt;He opened another terminal window. &#38;quot;It&#38;#39;s a very handy script for making API requests - don&#38;#39;t tell anyone, but it&#38;#39;s written in Python. Google for it, and it&#38;#39;s pretty easy to install - we have it on all our systems. So much easier than trying to remember all the incredibly cryptic flags for &lt;code&gt;curl&lt;/code&gt;. And you can do this... You give it the body parameters as just key/value pairs in this case, and...&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;http POST localhost:3000/feeds/toyco Content-Type:application/json \&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;name=&#38;quot;Santa Hat&#38;quot; rrp=36.50 wholesale_price=20.00 SKU=TC-03451&lt;br /&gt;HTTP/1.1 200 OK&lt;br /&gt;Content-Length: 38&lt;br /&gt;Content-Type: application/json; charset=utf-8&lt;br /&gt;Date: Tue, 02 Dec 2025 11:33:52 GMT&lt;br /&gt;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;quot;message&#38;quot;: &#38;quot;Accepted&#38;quot;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;...and there you go...&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;But that&#38;#39;s only development.&#38;quot; Sophie frowned. &#38;quot;How do we get it up on production?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;There&#38;#39;s any number of ways. &lt;a href=&#34;https://docs.mojolicious.org&#34;&gt;https://docs.mojolicious.org&lt;/a&gt; has pointers, but I guess what we&#38;#39;ll do is ship it across to &lt;code&gt;prod.northpole.net&lt;/code&gt; when it&#38;#39;s good and tested...&#38;quot; He gave her a sly grin. &#38;quot;That&#38;#39;s your job, by the way... and then&#38;#39;ll we&#38;#39;ll run it up using the &lt;code&gt;prefork&lt;/code&gt; option under something like &lt;code&gt;supervisor&lt;/code&gt; to restart it if it ever gets killed for some reason.&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;./toyco_app.pl prefork -l https://prod.northpole.net:10000&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;And there we go.&#38;quot; He sighed. &#38;quot;Right. Off you go and tidy that up and test it, and I&#38;#39;ll get back to keeping Saint Nick out of Lima&#38;#39;s restricted airspace.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Still?&#38;quot; Sophie giggled.&lt;/p&gt;

&lt;p&gt;&#38;quot;Still. Off with you, young elf.&#38;quot;&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-06T00:00:00Z</updated><category term="Perl"/><author><name>Mike Whitaker</name></author></entry><entry><title>Santa needs to know about new toys...</title><link href="https://perladvent.org/2025/2025-12-05.html"/><id>https://perladvent.org/2025/2025-12-05.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;&lt;img src=&#34;ernest-sophie.png&#34;&gt;

&lt;/p&gt;



&lt;h3 id=&#34;Twas-a-few-nights-before-Christmas&#34;&gt;&#38;#39;Twas a few nights before Christmas...&lt;/h3&gt;

&lt;p&gt;&#38;quot;Boss?&#38;quot;&lt;/p&gt;

&lt;p&gt;Santa sighed, looked up from where he was programming the SledNav in advance of Christmas Day. &#38;quot;Yes?&#38;quot;&lt;/p&gt;

&lt;p&gt;The junior elf had their usual pre-Christmas too-many-nights-coding, too-much- pizza, too-much-caffeine, too-little-sleep look. &#38;quot;New script for you to fetch the new toys off the distributor&#38;#39;s site. Should save lots of time.&#38;quot; They treated him to a frazzled, hopeful smile. &#38;quot;new_toys.pl, in the usual place.&#38;quot;&lt;/p&gt;

&lt;p&gt;Saint Nick nodded. Waved them away. &#38;quot;Go. Go. Get some sleep.&#38;quot;&lt;/p&gt;

&lt;p&gt;He knew what to expect, so didn&#38;#39;t waste time this time.&lt;/p&gt;

&lt;p&gt;&#38;quot;ERNEST!&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;The-problem&#34;&gt;The problem&lt;/h3&gt;

&lt;p&gt;Santa&#38;#39;s right-hand elf hurried in. &#38;quot;Yes boss?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Your minion&#38;#39;s been at it again. See if you can figure out what this is supposed to do?&#38;quot;&lt;/p&gt;

&lt;p&gt;Ernest sighed. &#38;quot;If we weren&#38;#39;t always in such a pre-Christmas rush...&#38;quot;&lt;/p&gt;

&lt;p&gt;Back at his desk, he opened the source code, winced, &#38;quot;Oh dear, oh dear, oh... Ewww....&#38;quot;&lt;/p&gt;

&lt;p&gt;Right on cue, Sophie, &lt;i&gt;his&lt;/i&gt; right-hand elf, appeared, trying to read over his shoulder. &#38;quot;What... oh... Ewwww... Parsing HTML with regexps? That&#38;#39;s gross. What&#38;#39;s it for...?&#38;quot;&lt;/p&gt;

&lt;p&gt;Ernest hrmed. &#38;quot;They&#38;#39;re scraping the distributor&#38;#39;s new arrivals page, trying to parse the results and sticking them in our product DB.&#38;quot;&lt;/p&gt;

&lt;p&gt;She frowned. &#38;quot;Didn&#38;#39;t the distributor say they had an API for that?&#38;quot; She pulled out her tablet. &#38;quot;Ah, yes. Here we are. ToyCo sent us a mail just after Black Friday. Forwarding it now.&#38;quot;&lt;/p&gt;

&lt;p&gt;He gave her a grateful smile. &#38;quot;Thanks. Hopefully, this will be quick - I&#38;#39;m still trying to debug the SledNav code from &lt;a href=&#34;https://perladvent.org/2022/2022-12-02.html&#34;&gt;2022&lt;/a&gt; - it&#38;#39;s mostly brilliant, but apparently last year it managed to send the boss to London via Peru. Something about the wrong Paddington. But - watch and learn...&#38;quot; He scanned the email she&#38;#39;d just forwarded, started typing.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synStatement&#34;&gt;use constant&lt;/span&gt; {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;API_BASE&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;https://api.toyco.com/api/v2&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;API_KEY&lt;/span&gt;  =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;rk_4F92bA7qLmN82xPT9hQw3D6zKp0RsvJ&#39;&lt;/span&gt;,&lt;br /&gt;};&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;So those are just constants?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Yup. At compile time, too. And they&#38;#39;re at the top of the file if we need to change them for some reason, like ToyCo moving their endpoint, or expiring our key. And then we can use &lt;code&gt;LWP&lt;/code&gt; to do the fetching.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;&lt;code&gt;LWP&lt;/code&gt;?&#38;quot; she queried.&lt;/p&gt;

&lt;p&gt;&#38;quot;Short for &lt;code&gt;lib-www-perl&lt;/code&gt;. For historical reasons. Lost in the midst of time. It does all the heavy lifting of making web requests for us.&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;LWP::UserAgent;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;URI;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$ua&lt;/span&gt;  = LWP::UserAgent-&#38;gt;new();&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$uri&lt;/span&gt; = URI-&#38;gt;new( API_BASE . &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;/new_arrivals&#38;quot;&lt;/span&gt; );&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;That&#38;#39;s the right endpoint?&#38;quot; Ernest asked.&lt;/p&gt;

&lt;p&gt;She checked her tablet. &#38;quot;Yes. Says here it returns JSON. And there&#38;#39;s a link to the docs. But we do need to pass in the API key as an X-API-Key header or a query parameter.&#38;quot;&lt;/p&gt;

&lt;p&gt;He chuckled. &#38;quot;We&#38;#39;ll do the former. It&#38;#39;s cleaner. Besides, LWP just lets us set headers as extra hash arguments to the &lt;code&gt;get&lt;/code&gt; call.&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$response&lt;/span&gt; = &lt;span class=&#34;synIdentifier&#34;&gt;$ua&lt;/span&gt;-&#38;gt;get(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$uri&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;&#39;X-API-Key&#39;&lt;/span&gt; =&#38;gt; API_KEY&lt;br /&gt;);&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&#38;quot;Better make sure we can parse the result, too.&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;JSON;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$json_parser&lt;/span&gt; = JSON-&#38;gt;new-&#38;gt;utf8(&lt;span class=&#34;synConstant&#34;&gt;0&lt;/span&gt;);&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;She peered. &#38;quot;What&#38;#39;s that do?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Gives us a JSON parser object, then tells it to expect Perl Unicode strings. It is UTF8, right?&#38;quot;&lt;/p&gt;

&lt;p&gt;A glance down at the tablet. &#38;quot;Says so right here.&#38;quot; She grinned. &#38;quot;What now?&#38;quot;&lt;/p&gt;

&lt;p&gt;Ernest started typing again.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$data&lt;/span&gt; = &lt;span class=&#34;synIdentifier&#34;&gt;$json_parser&lt;/span&gt;-&#38;gt;decode( &lt;span class=&#34;synIdentifier&#34;&gt;$response&lt;/span&gt;-&#38;gt;decoded_content );&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Sophie scratched her head. &#38;quot;Uh. Woah, hold on. What&#38;#39;s the &lt;code&gt;$response&lt;/code&gt;?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Actually,&#38;quot; Ernest explained, &#38;quot;it&#38;#39;s an &lt;code&gt;HTTP::Message&lt;/code&gt; object. The &lt;code&gt;decoded_content&lt;/code&gt; method tells it to return the response body after it&#38;#39;s applied any &lt;code&gt;Content-Encoding&lt;/code&gt; headers, and specifically in our case it returns Perl Unicode strings, which is what we told JSON to expect.&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Cool. Now what?&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Now?&#38;quot; Ernest grinned. &#38;quot;Now we have a bunch of new product data in the JSON that we can loop through - what fields did they say were in it?&#38;quot;&lt;/p&gt;

&lt;p&gt;Sophie oh&#38;#39;ed. &#38;quot;There&#38;#39;s a SKU, an RRP, a wholesale price, a name...&#38;quot;&lt;/p&gt;

&lt;p&gt;&#38;quot;Right.&#38;quot;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synStatement&#34;&gt;foreach&lt;/span&gt; &lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$item&lt;/span&gt; (&lt;span class=&#34;synIdentifier&#34;&gt;@$data&lt;/span&gt;) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;add_to_db(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;supplier&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;ToyCo&#38;quot;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;SKU&lt;/span&gt;      =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$item-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;sku&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;name&lt;/span&gt;     =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$item-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;cost&lt;/span&gt;     =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$item-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;wholesale_price&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;RRP&lt;/span&gt;      =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$item-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;rrp&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;);&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;He winked at her. &#38;quot;I&#38;#39;ll leave the &lt;code&gt;add_to_db&lt;/code&gt; sub as an exercise for the eager student.&#38;quot; A grin. &#38;quot;That&#38;#39;d be you. Off you go, I need to get back to not having Himself routed via South America again this year.&#38;quot; A pause. &#38;quot;Oh, and for heavens sake add some error handling? You know what the Big Red Boss is like if things fall over with cryptic messages.&#38;quot;&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-05T00:00:00Z</updated><category term="Perl"/><author><name>Mike Whitaker</name></author></entry><entry><title>Stopping the Evil Grinch: A Holiday Defense Guide</title><link href="https://perladvent.org/2025/2025-12-04.html"/><id>https://perladvent.org/2025/2025-12-04.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;&lt;img src=&#34;elf_running_perl_script.png&#34; alt=&#34;Stopping the Evil Grinch&#34; style=&#34;float: right; margin: 0 0 1em 1em; width: 300px;&#34;/&gt;

&lt;/p&gt;



&lt;p&gt;During a December evening in Santa&#38;#39;s workshop, the security team received an urgent alert: a malicious actor, known as the &#38;quot;Evil Grinch,&#38;quot; intended to compromise the systems running the toy production environment. Fortunately, the team already relied on several essential security tools:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Lynis for system auditing&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ClamAV for malware scanning&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Perl to orchestrate everything&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;Installing-Lynis-Without-Sudo&#34;&gt;Installing Lynis Without Sudo&lt;/h3&gt;

&lt;p&gt;Because the environment restricted sudo usage, Lynis was installed in the user&#38;#39;s home directory using the following commands:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;wget https://downloads.cisofy.com/lynis/lynis-3.1.6.tar.gz&lt;br /&gt;tar -xvf lynis-3.1.6.tar.gz&lt;br /&gt;mkdir -p /home/user/bin&lt;br /&gt;chmod 775 /home/user/bin&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With this wrapper script, Lynis was executable without requiring sudo privileges.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;cat &#38;lt;&#38;lt; &#39;EOF&#39; &#38;gt; /home/user/bin/lynis&lt;br /&gt;#!/bin/bash&lt;br /&gt;LYNIS_DIR=&#38;quot;/home/user/lynis-3.1.6&#38;quot;&lt;br /&gt;export LYNIS_LOG_FILE=&#38;quot;/home/user/lynis-logs/lynis.log&#38;quot;&lt;br /&gt;export LYNIS_REPORT_FILE=&#38;quot;/home/user/lynis-logs/lynis-report.dat&#38;quot;&lt;br /&gt;cd &#38;quot;$LYNIS_DIR&#38;quot; || exit 1&lt;br /&gt;./lynis &#38;quot;$@&#38;quot;&lt;br /&gt;EOF&lt;br /&gt;&lt;br /&gt;chmod 775 /home/user/bin/lynis&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;Installing-ClamAV&#34;&gt;Installing ClamAV&lt;/h3&gt;

&lt;p&gt;ClamAV was installed using the system package manager:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;sudo apt update&lt;br /&gt;sudo apt install clamav clamav-daemon -y&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;Building-the-Perl-Cron-Script&#34;&gt;Building the Perl Cron Script&lt;/h3&gt;

&lt;p&gt;A Perl script was created to automate security reporting and deliver the results via email.&lt;/p&gt;

&lt;p&gt;First, the necessary Perl dependencies were installed:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;cpanm Moo \&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Email::Sender::Simple \&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Email::Sender::Transport::SMTP \&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Email::MIME \&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Try::Tiny \&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Types::Standard \&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;IO::All \&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;DateTime \&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Readonly \&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Log::Log4perl&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The script is meant to run daily and exits early if it is not the last day of the month, determined via:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;DateTime-&#38;gt;now()-&#38;gt;is_last_day_of_month()&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Logging is handled by the Log::Log4perl module. The core of the script is the run_report function, which deletes any previous report file and executes each command. Because ClamAV errors are expected when scanning protected or locked files, the ClamAV call is allowed to return a non-zero exit code.&lt;/p&gt;

&lt;p&gt;Below is the Perl script:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synPreProc&#34;&gt;#!/usr/bin/env perl&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use strict&lt;/span&gt;;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use warnings&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;DateTime;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Cwd;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Readonly;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Log::Log4perl &lt;span class=&#34;synConstant&#34;&gt;qw(:easy)&lt;/span&gt;;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Dotenv;&lt;br /&gt;&lt;br /&gt;Readonly::Scalar &lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$lynis_file&lt;/span&gt;  =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;/home/user/lynis-report.dat&#39;&lt;/span&gt;;&lt;br /&gt;Readonly::Scalar &lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$clamav_file&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;/home/user/clamav.log&#39;&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;if&lt;/span&gt; ( DateTime-&#38;gt;now()-&#38;gt;is_last_day_of_month() ) {&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$logger&lt;/span&gt; = Log::Log4perl-&#38;gt;easy_init(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;level&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$DEBUG&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;file&lt;/span&gt;  =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;&#38;gt;&#38;gt;test.log&#38;quot;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;);&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$logger&lt;/span&gt;-&#38;gt;debug(&lt;span class=&#34;synConstant&#34;&gt;&#39;Last day of month detected. Preparing reports.&#39;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$env&lt;/span&gt; = Dotenv-&#38;gt;load(&lt;span class=&#34;synConstant&#34;&gt;&#39;.mail_env&#39;&lt;/span&gt;);&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$logger&lt;/span&gt;-&#38;gt;debug(&lt;span class=&#34;synConstant&#34;&gt;&#39;Running Lynis report&#39;&lt;/span&gt;);&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;run_report(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;[&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;&#39;/home/user/bin/lynis&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;&#39;audit&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;&#39;system&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;&#39;--profile&#39;&lt;/span&gt;,            &lt;span class=&#34;synConstant&#34;&gt;&#39;/home/user/custom.prf&#39;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;],&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$lynis_file&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;);&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$logger&lt;/span&gt;-&#38;gt;debug(&lt;span class=&#34;synConstant&#34;&gt;&#39;Running ClamAV report&#39;&lt;/span&gt;);&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;run_report(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;[&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;&#39;clamscan&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;&#39;-r&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;&#39;-i&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;&#39;--exclude-dir=^/proc&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;&#39;--exclude-dir=^/sys&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;&#39;--exclude-dir=^/dev&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;&#39;/home/user&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;&#38;quot;--log=&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$clamav_file&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;&#38;quot;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;&#39;-v&#39;&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;],&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;$clamav_file&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;_execute_cmd &lt;/span&gt;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; ( &lt;span class=&#34;synIdentifier&#34;&gt;$cmd&lt;/span&gt;, &lt;span class=&#34;synIdentifier&#34;&gt;$file&lt;/span&gt; ) = &lt;span class=&#34;synIdentifier&#34;&gt;@_&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$cmd_str&lt;/span&gt; = &lt;span class=&#34;synStatement&#34;&gt;join&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#39; &#39;&lt;/span&gt;, &lt;span class=&#34;synIdentifier&#34;&gt;@{$cmd}&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;if&lt;/span&gt; ( &lt;span class=&#34;synIdentifier&#34;&gt;$file&lt;/span&gt; &lt;span class=&#34;synStatement&#34;&gt;eq&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;/home/user/clamav.log&#39;&lt;/span&gt; ) {&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synComment&#34;&gt;# Accept non-zero exit code for ClamAV&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;system&lt;/span&gt;( &lt;span class=&#34;synIdentifier&#34;&gt;@{$cmd}&lt;/span&gt; );&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;else&lt;/span&gt; {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;system&lt;/span&gt;( &lt;span class=&#34;synIdentifier&#34;&gt;@{$cmd}&lt;/span&gt; ) == &lt;span class=&#34;synConstant&#34;&gt;0&lt;/span&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;or&lt;/span&gt; &lt;span class=&#34;synStatement&#34;&gt;die&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;Cannot execute &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$cmd_str&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;: &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$?&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;&#38;quot;&lt;/span&gt;;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;return&lt;/span&gt;;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;run_report &lt;/span&gt;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; ( &lt;span class=&#34;synIdentifier&#34;&gt;$cmd&lt;/span&gt;, &lt;span class=&#34;synIdentifier&#34;&gt;$file&lt;/span&gt; ) = &lt;span class=&#34;synIdentifier&#34;&gt;@_&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;if&lt;/span&gt; ( &lt;span class=&#34;synStatement&#34;&gt;-f&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$file&lt;/span&gt; ) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;unlink&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$file&lt;/span&gt;;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;_execute_cmd( &lt;span class=&#34;synIdentifier&#34;&gt;$cmd&lt;/span&gt;, &lt;span class=&#34;synIdentifier&#34;&gt;$file&lt;/span&gt; );&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;return&lt;/span&gt;;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To send reports securely, a Gmail App Password was stored in a protected file:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;chmod 600 .mail_env&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then the SendMail class was implemented inside lib/SendMail.pm. The class defines the following attributes:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;has &lt;span class=&#34;synConstant&#34;&gt;sasl_username&lt;/span&gt; =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; Str );&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;sasl_password&lt;/span&gt; =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; Str );&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;from&lt;/span&gt;          =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; Str );&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;to&lt;/span&gt;            =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; ArrayRef );&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;email_body&lt;/span&gt;    =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; Str );&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;attachments&lt;/span&gt;   =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; ArrayRef );&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;subject&lt;/span&gt;       =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; Str );&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;which are used internally to create an Email::MIME object and an Email::Sender::Transport::SMTP object, stored in:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;has &lt;span class=&#34;synConstant&#34;&gt;message&lt;/span&gt;   =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;lazy&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; Object, &lt;span class=&#34;synConstant&#34;&gt;builder&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;_build_message&#39;&lt;/span&gt; );&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;transport&lt;/span&gt; =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;lazy&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; Object, &lt;span class=&#34;synConstant&#34;&gt;builder&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;_build_transport&#39;&lt;/span&gt; );&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;attributes. Finally, the class implements the send_email method, a simple wrapper over Email::Sender::Simple::sendmail.&lt;/p&gt;

&lt;p&gt;The full body of the class is below:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synStatement&#34;&gt;package&lt;/span&gt;&lt;span class=&#34;synType&#34;&gt; SendMail&lt;/span&gt;;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Moo;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Email::Sender::Simple &lt;span class=&#34;synConstant&#34;&gt;qw(sendmail)&lt;/span&gt;;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Email::Sender::Transport::SMTP;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Email::MIME;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Try::Tiny;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;Types::Standard &lt;span class=&#34;synConstant&#34;&gt;qw(Str ArrayRef Object)&lt;/span&gt;;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;IO::All;&lt;br /&gt;&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;sasl_username&lt;/span&gt; =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; Str );&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;sasl_password&lt;/span&gt; =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; Str );&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;from&lt;/span&gt;          =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; Str );&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;to&lt;/span&gt;            =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; ArrayRef );&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;email_body&lt;/span&gt;    =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; Str );&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;attachments&lt;/span&gt;   =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; ArrayRef );&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;subject&lt;/span&gt;       =&#38;gt; ( &lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;required&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;, &lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt; =&#38;gt; Str );&lt;br /&gt;&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;message&lt;/span&gt; =&#38;gt; (&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt;      =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;lazy&lt;/span&gt;    =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt;     =&#38;gt; Object,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;builder&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;_build_message&#39;&lt;/span&gt;&lt;br /&gt;);&lt;br /&gt;has &lt;span class=&#34;synConstant&#34;&gt;transport&lt;/span&gt; =&#38;gt; (&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;is&lt;/span&gt;      =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;ro&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;lazy&lt;/span&gt;    =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;isa&lt;/span&gt;     =&#38;gt; Object,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;builder&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;_build_transport&#39;&lt;/span&gt;&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;_build_message &lt;/span&gt;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$self&lt;/span&gt; = &lt;span class=&#34;synStatement&#34;&gt;shift&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;@file_list&lt;/span&gt;;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;foreach&lt;/span&gt; &lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$file&lt;/span&gt; ( &lt;span class=&#34;synIdentifier&#34;&gt;@{&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$self&lt;/span&gt;-&#38;gt;attachments &lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt; ) {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;@file_parts&lt;/span&gt; = &lt;span class=&#34;synStatement&#34;&gt;split&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;/&#39;&lt;/span&gt;, &lt;span class=&#34;synIdentifier&#34;&gt;$file&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$mime&lt;/span&gt; = Email::MIME-&#38;gt;create(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;attributes&lt;/span&gt; =&#38;gt; {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;filename&lt;/span&gt;     =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$file_parts[&lt;/span&gt;-&lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;]&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;content_type&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;text/plain&#38;quot;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;encoding&lt;/span&gt;     =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;quoted-printable&#38;quot;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;name&lt;/span&gt;         =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$file_parts[&lt;/span&gt;-&lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;]&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;},&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;body&lt;/span&gt; =&#38;gt; io(&lt;span class=&#34;synIdentifier&#34;&gt;$file&lt;/span&gt;)-&#38;gt;binary-&#38;gt;all,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;);&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;push&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;@file_list&lt;/span&gt;, &lt;span class=&#34;synIdentifier&#34;&gt;$mime&lt;/span&gt;;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;@parts&lt;/span&gt; = (&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synIdentifier&#34;&gt;@file_list&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;Email::MIME-&#38;gt;create(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;attributes&lt;/span&gt; =&#38;gt; {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;content_type&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;text/plain&#38;quot;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;disposition&lt;/span&gt;  =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;attachment&#38;quot;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;encoding&lt;/span&gt;     =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;quoted-printable&#38;quot;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;charset&lt;/span&gt;      =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;US-ASCII&#38;quot;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;},&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;body_str&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$self&lt;/span&gt;-&#38;gt;email_body,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;),&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;);&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;return&lt;/span&gt; Email::MIME-&#38;gt;create(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;header_str&lt;/span&gt; =&#38;gt; [&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;From&lt;/span&gt;    =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$self&lt;/span&gt;-&#38;gt;from,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;To&lt;/span&gt;      =&#38;gt; &lt;span class=&#34;synStatement&#34;&gt;join&lt;/span&gt;( &lt;span class=&#34;synConstant&#34;&gt;&#39;,&#39;&lt;/span&gt;, &lt;span class=&#34;synIdentifier&#34;&gt;@{&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$self&lt;/span&gt;-&#38;gt;to &lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt; ),&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;Subject&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$self&lt;/span&gt;-&#38;gt;subject,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;],&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;parts&lt;/span&gt; =&#38;gt; \&lt;span class=&#34;synIdentifier&#34;&gt;@parts&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;_build_transport &lt;/span&gt;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$self&lt;/span&gt; = &lt;span class=&#34;synStatement&#34;&gt;shift&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;return&lt;/span&gt; Email::Sender::Transport::SMTP-&#38;gt;new(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;host&lt;/span&gt;          =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;smtp.gmail.com&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;port&lt;/span&gt;          =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;465&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;ssl&lt;/span&gt;           =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;sasl_username&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$self&lt;/span&gt;-&#38;gt;sasl_username,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;sasl_password&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$self&lt;/span&gt;-&#38;gt;sasl_password,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;sub &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;send_email &lt;/span&gt;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$self&lt;/span&gt; = &lt;span class=&#34;synStatement&#34;&gt;shift&lt;/span&gt;;&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;try {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;sendmail( &lt;span class=&#34;synIdentifier&#34;&gt;$self&lt;/span&gt;-&#38;gt;message, { &lt;span class=&#34;synConstant&#34;&gt;transport&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$self&lt;/span&gt;-&#38;gt;transport } );&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;catch {&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;print&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;|&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$_&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;|&#38;quot;&lt;/span&gt;;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;};&lt;br /&gt;&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synStatement&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;1&lt;/span&gt;;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The package was integrated into the main script as follows:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synStatement&#34;&gt;use lib&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;lib&#39;&lt;/span&gt;;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;SendMail;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$mailer&lt;/span&gt; = SendMail-&#38;gt;new(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;{&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;sasl_username&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$env-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;cron_mail&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;sasl_password&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$env-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;cron_password&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;from&lt;/span&gt;          =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$env-&#38;gt;{&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;cron_mail&lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;}&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;to&lt;/span&gt;            =&#38;gt; &lt;span class=&#34;synIdentifier&#34;&gt;$to&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;email_body&lt;/span&gt;    =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;Security report attached.&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;attachments&lt;/span&gt;   =&#38;gt; [ &lt;span class=&#34;synIdentifier&#34;&gt;$lynis_file&lt;/span&gt;, &lt;span class=&#34;synIdentifier&#34;&gt;$clamav_file&lt;/span&gt; ],&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;subject&lt;/span&gt;       =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;Security Report&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&#38;nbsp;&#38;nbsp;}&lt;br /&gt;);&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;Adding-to-Crontab&#34;&gt;Adding to Crontab&lt;/h3&gt;

&lt;p&gt;The monthly automation was scheduled as follows:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;crontab -e&lt;br /&gt;0 10 * * * cd /home/user/scripts &#38;amp;&#38;amp; /home/user/perl5/perlbrew/bin/perlbrew exec --with perl-5.42.0 perl security_report.pl &#38;gt;/dev/null 2&#38;gt;&#38;amp;1&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Happy auditing and secure coding!&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-04T00:00:00Z</updated><category term="Perl"/><author><name>Dragos Trif</name></author></entry><entry><title type="html">Santa&#38;#39;s Secret Music Studio</title><link href="https://perladvent.org/2025/2025-12-03.html"/><id>https://perladvent.org/2025/2025-12-03.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;&lt;img src=&#34;santa.png&#34; alt=&#34;Santa!&#34; style=&#34;float: right; margin: 0 0 1em 1em; width: 300px;&#34;/&gt;

&lt;/p&gt;



&lt;h3 id=&#34;Santas-Secret-Studio&#34;&gt;Santa&#38;#39;s Secret Studio&lt;/h3&gt;

&lt;p&gt;Privately, Santa was a musician. He practiced his scales and arpeggios diligently any time he could find a spare minute. Although who his teacher is or was remains a mystery.&lt;/p&gt;

&lt;p&gt;In Santa&#38;#39;s little music studio next to the Workshop, he had some guitars, an old Fender Jazz bass, a modern &lt;a href=&#34;https://www.arturia.com/&#34;&gt;Arturia&lt;/a&gt; MIDI controller keyboard, a few classic synths, and even a bit of modular &lt;a href=&#34;https://en.wikipedia.org/wiki/Eurorack&#34;&gt;eurorack&lt;/a&gt; gear, too.&lt;/p&gt;

&lt;p&gt;Over the years, he has given many, many a present to the likes of Keith Emerson, Rick Wakeman, and Trent Reznor even.&lt;/p&gt;

&lt;p&gt;Anyway, he was fine with playing his individual instruments, by themselves, for a long time. He is old fashioned, you know. But he decided to hook his devices together with MIDI (&#38;quot;Musical Instrument Digital Interface&#38;quot;) and play them with his superior Arturia (a Christmas present to himself, recently). So he got the necessary cables and connected his synthesizers and computer.&lt;/p&gt;

&lt;p&gt;Now with a &#38;quot;digital audio workstation&#38;quot; app (&#38;quot;DAW&#38;quot;), you can orchestrate all your external MIDI instruments and record a masterpiece. But you can &lt;b&gt;also&lt;/b&gt; control everything with Perl programs!&lt;/p&gt;

&lt;h3 id=&#34;North-Pole-Perlers&#34;&gt;North Pole Perlers&lt;/h3&gt;

&lt;p&gt;Like any programmer worth his or her salt, he and the elves often used the &lt;a href=&#34;https://www.perl.org/&#34;&gt;Perl&lt;/a&gt; programming language to get things done without much fuss. And since he was also a musician, he wondered how to make musical things easier and fun with code.&lt;/p&gt;

&lt;p&gt;It turns out that Santa is a geek at heart and occasionally searches &lt;a href=&#34;https://metacpan.org/recent&#34;&gt;MetaCPAN&lt;/a&gt; and checks out the recent uploads. One he found and installed was the &lt;a href=&#34;https://metacpan.org/pod/idi&#34;&gt;idi&lt;/a&gt; module. That seemed to fit the bill for many quick and simple things, including playing any synth through MIDI with a controller.&lt;/p&gt;

&lt;h3 id=&#34;Controlling-MIDI&#34;&gt;Controlling MIDI&lt;/h3&gt;

&lt;p&gt;But before he could control anything, he had to know what his MIDI ports were named! In order to find that out, he ran the handy script he found while studying Perl real-time MIDI: &lt;a href=&#34;https://metacpan.org/release/JBARRETT/MIDI-RtMidi-FFI-0.08/source/examples/list_devices.pl&#34;&gt;list_devices.pl&lt;/a&gt;. Here it is:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;comment&#34;&gt;#!/usr/bin/env perl&lt;br /&gt;&lt;/span&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;strict&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;pragma&#34;&gt;warnings&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;use&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;MIDI::RtMidi::FFI::Device&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$midi_in&lt;/span&gt;  &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;RtMidiIn&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;keyword&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;symbol&#34;&gt;$midi_out&lt;/span&gt; &lt;span class=&#34;operator&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;word&#34;&gt;RtMidiOut&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;new&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;print&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;Input devices:\n&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;symbol&#34;&gt;$midi_in&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;print_ports&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;print&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;\n&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;word&#34;&gt;print&lt;/span&gt; &lt;span class=&#34;double&#34;&gt;&#38;quot;Output devices:\n&#38;quot;&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;br /&gt;&lt;span class=&#34;symbol&#34;&gt;$midi_out&lt;/span&gt;&lt;span class=&#34;operator&#34;&gt;-&#38;gt;&lt;/span&gt;&lt;span class=&#34;word&#34;&gt;print_ports&lt;/span&gt;&lt;span class=&#34;structure&#34;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Running this on his computer said,&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&#38;gt; perl list_devices.pl&lt;br /&gt;&lt;br /&gt;Input devices:&lt;br /&gt;0: USB MIDI Interface&lt;br /&gt;1: KeyLab mkII 49 MIDI&lt;br /&gt;&lt;br /&gt;Output devices:&lt;br /&gt;0: USB MIDI Interface&lt;br /&gt;1: KeyLab mkII 49 MIDI&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &#38;quot;USB MIDI Interface&#38;quot; was currently his &lt;a href=&#34;https://en.wikipedia.org/wiki/MicroKORG&#34;&gt;microKORG&lt;/a&gt; synth, and the &#38;quot;KeyLab mkII 49 MIDI&#38;quot; was his Arturia controller. So by using the &lt;a href=&#34;https://metacpan.org/pod/idi&#34;&gt;idi&lt;/a&gt; module on the terminal command line:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&#38;gt; perl -Midi -E &#38;quot;i(@ARGV)&#38;quot; &#38;#39;KeyLab mkII 49 MIDI&#38;#39; &#38;#39;USB MIDI Interface&#38;#39;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;He was able to control his synth! And what did it sound like?&lt;/p&gt;

&lt;audio controls&gt;&lt;source src=&#34;2025-Advent-1.mp3&#34; type=&#34;audio/mp3&#34;&gt;&lt;/audio&gt;

&lt;p&gt;Not especially Christmas-y at all, but it illustrates that with a single line, you can control a MIDI synthesizer without using a full-blown DAW.&lt;/p&gt;

&lt;p&gt;It also implies that you can make a program do &lt;b&gt;ANYTHING YOU WANT&lt;/b&gt; with live, real-time MIDI synthesizers. Hmmmmm...&lt;/p&gt;

&lt;p&gt;Santa was satisfied and set out to dive deeper after Christmas and study &lt;a href=&#34;https://metacpan.org/pod/MIDI::RtMidi::FFI::Device&#34;&gt;MIDI::RtMidi::FFI::Device&lt;/a&gt; and its derivatives: &lt;a href=&#34;https://metacpan.org/pod/MIDI::RtController&#34;&gt;MIDI::RtController&lt;/a&gt;, &lt;a href=&#34;https://metacpan.org/pod/MIDI::RtController::Filter::Tonal&#34;&gt;MIDI::RtController::Filter::Tonal&lt;/a&gt;, and related control modules next.&lt;/p&gt;

&lt;h3 id=&#34;Conclusion&#34;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;Be like Santa and, when you&#38;#39;re not busy delivering presents to the children of the world, control your MIDI devices with Perl!&lt;/p&gt;

&lt;h3 id=&#34;References&#34;&gt;References&lt;/h3&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://metacpan.org/pod/idi&#34;&gt;idi&lt;/a&gt;&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;/div&gt;</summary><updated>2025-12-03T00:00:00Z</updated><category term="Perl"/><author><name>Gene Boggs</name></author></entry><entry><title>All I Want for Christmas Is the Right Aspect Ratio</title><link href="https://perladvent.org/2025/2025-12-02.html"/><id>https://perladvent.org/2025/2025-12-02.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;h3 id=&#34;A-bad-start-to-Christmas&#34;&gt;A bad start to Christmas&lt;/h3&gt;

&lt;p&gt;&lt;img src=&#34;pixie-the-elf_blur.png&#34;&gt;

&lt;/p&gt;



&lt;p&gt;Pixie the Elf (whose parents had strange ideas about names) was not having a good December.&lt;/p&gt;

&lt;p&gt;This year she&#38;rsquo;d been seconded from Santa&#38;rsquo;s Workshop to the North Pole Communications Department. Her job was to prepare a huge batch of festive images: hero banners for the NaughtyOrNice dashboard, thumbnails for the Sleigh Tracking app, preview cards for Elfbook, and even a few social posts for Santa&#38;rsquo;s surprisingly popular &#38;ldquo;Reels from the Sleigh&#38;rdquo;.&lt;/p&gt;

&lt;p&gt;All of those systems wanted &lt;i&gt;different aspect ratios&lt;/i&gt;.&lt;/p&gt;

&lt;p&gt;The NaughtyOrNice dashboard wanted 16:9. The old in-house bulletin board insisted on 4:3. Elfbook square-cropped everything. One particularly opinionated goblin had even insisted on a tall &#38;ldquo;story&#38;rdquo; format.&lt;/p&gt;

&lt;p&gt;And all Pixie had were a couple of folders of lovely but badly sized photos: Santa in front of the Northern Lights, reindeer in flight, close-ups of toys, and the occasional candid shot of Mrs Claus trying out the new espresso machine.&lt;/p&gt;

&lt;p&gt;Every time she tried to resize them, the same thing happened:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Crop the image to fit the rectangle &#38;rarr; chop off a reindeer&#38;rsquo;s antlers.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add black bars &#38;rarr; looks like a dodgy pirate stream.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stretch to fit &#38;rarr; Santa suddenly looked like he&#38;rsquo;d gained even more weight.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There &lt;i&gt;had&lt;/i&gt; to be a better way.&lt;/p&gt;

&lt;p&gt;So she did what she always did when stuck: topped up her mug of hot chocolate, opened her laptop, and started scrolling through r/elftech.&lt;/p&gt;

&lt;p&gt;About three threads down, a title caught her eye:&lt;/p&gt;

&lt;p&gt;&lt;i&gt;&#38;ldquo;Reformatting images with blurred backgrounds in Perl&#38;rdquo;&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;The post was talking about a module called &lt;a href=&#34;https://metacpan.org/dist/App-BlurFill&#34;&gt;App::BlurFill&lt;/a&gt;. It promised to take a source image and produce a new one at a fixed width and height, filling the &#38;ldquo;spare&#38;rdquo; space with a blurred version of the same image instead of ugly borders.&lt;/p&gt;

&lt;p&gt;&#38;ldquo;That&#38;rsquo;s &lt;i&gt;exactly&lt;/i&gt; what I need,&#38;rdquo; Pixie muttered. &#38;ldquo;No more cropped reindeer noses.&#38;rdquo;&lt;/p&gt;

&lt;h3 id=&#34;What-App::BlurFill-does&#34;&gt;What App::BlurFill does&lt;/h3&gt;

&lt;p&gt;At its heart, App::BlurFill is a simple Perl class for generating images with a blurred background fill. You give it:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;an input image file&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a desired width and height&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and it:&lt;/p&gt;

&lt;ol&gt;

&lt;li&gt;&lt;p&gt;Loads the original image using Imager.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creates a blurred version of that image as the background.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scales the original image to fit inside the requested rectangle, preserving aspect ratio.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Centres the original image on top of the blurred background.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writes out a new image file, typically named something like picture_blur.jpg.&lt;/p&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By default, it aims at 650&#38;times;350 pixels, which turned out to be just about perfect for one of Santa&#38;rsquo;s dashboards.&lt;/p&gt;

&lt;p&gt;Even better, the distribution doesn&#38;rsquo;t just give you a Perl class and leave you to it. It comes with:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;A Perl API (the class itself).&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A command-line program: &lt;code&gt;blurfill&lt;/code&gt;.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Dancer2 web app so you can do it all in the browser.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A &lt;a href=&#34;https://hub.docker.com/r/davorg/app-blurfill&#34;&gt;Docker container&lt;/a&gt; that bundles the web app so any elf with Docker can run it.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pixie decided to try all four.&lt;/p&gt;

&lt;h3 id=&#34;Using-App::BlurFill-from-Perl&#34;&gt;1. Using App::BlurFill from Perl&lt;/h3&gt;

&lt;p&gt;Pixie&#38;rsquo;s first target was the NaughtyOrNice dashboard. That app already had a background job that prepared images whenever new photos arrived in the &#38;ldquo;Workshop Photos&#38;rdquo; bucket. Dropping a bit of Perl in there was easy.&lt;/p&gt;

&lt;p&gt;She installed the module the usual way:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;cpanm App::BlurFill&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then, in her image processing script, she added:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;App::BlurFill;&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$blur_fill&lt;/span&gt; = App::BlurFill-&#38;gt;new(&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;file&lt;/span&gt;   =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;&#39;images/santa-aurora.jpg&#39;&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;width&lt;/span&gt;  =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;1200&lt;/span&gt;,&lt;br /&gt;&#38;nbsp;&#38;nbsp;&lt;span class=&#34;synConstant&#34;&gt;height&lt;/span&gt; =&#38;gt; &lt;span class=&#34;synConstant&#34;&gt;630&lt;/span&gt;,   &lt;span class=&#34;synComment&#34;&gt;# Nice &#38;quot;social card&#38;quot; size&lt;/span&gt;&lt;br /&gt;);&lt;br /&gt;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;my&lt;/span&gt; &lt;span class=&#34;synIdentifier&#34;&gt;$output&lt;/span&gt; = &lt;span class=&#34;synIdentifier&#34;&gt;$blur_fill&lt;/span&gt;-&#38;gt;process;&lt;br /&gt;&lt;span class=&#34;synStatement&#34;&gt;print&lt;/span&gt; &lt;span class=&#34;synConstant&#34;&gt;&#38;quot;Blurred image saved to &lt;/span&gt;&lt;span class=&#34;synIdentifier&#34;&gt;$output&lt;/span&gt;&lt;span class=&#34;synSpecial&#34;&gt;\n&lt;/span&gt;&lt;span class=&#34;synConstant&#34;&gt;&#38;quot;&lt;/span&gt;;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The constructor accepts:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;file &#38;ndash; input image (required)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;width &#38;ndash; output width (defaults to 650)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;height &#38;ndash; output height (defaults to 350)&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;output &#38;ndash; optional explicit output filename; otherwise a _blur file is generated for you.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;process&lt;/code&gt; method does all the work and returns the path to the new file.&lt;/p&gt;

&lt;p&gt;She wired that into the job that prepared thumbnails for the dashboard, redeployed, and refreshed the page.&lt;/p&gt;

&lt;p&gt;Where she&#38;rsquo;d previously had:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;A letterboxed or cropped Santa&#38;hellip;&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&#38;hellip;she now had:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;A perfectly centred Santa in the middle, with a soft, blurred version of the same photo filling the rest of the space. No black bars, no weird stretching.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&#38;ldquo;Right,&#38;rdquo; she smiled. &#38;ldquo;That&#38;rsquo;s the dashboard sorted.&#38;rdquo;&lt;/p&gt;

&lt;h3 id=&#34;Letting-the-command-line-do-the-work&#34;&gt;2. Letting the command line do the work&lt;/h3&gt;

&lt;p&gt;The next problem was the content team.&lt;/p&gt;

&lt;p&gt;Not every elf is comfortable editing Perl code, but plenty of them are happy running commands in a terminal &#38;mdash; especially once Pixie had written &#38;ldquo;DO NOT PANIC&#38;rdquo; across the workshop cheat sheet.&lt;/p&gt;

&lt;p&gt;Luckily, App::BlurFill ships with a command-line program, &lt;code&gt;blurfill&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The syntax is:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;blurfill [-w width] [-h height] [-o output_filename] image_filename&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you omit the options, it uses the defaults:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;-w &#38;rarr; defaults to 650&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;-h &#38;rarr; defaults to 350&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;-o &#38;rarr; defaults to the input name with _blur appended, e.g. elfies.png &#38;rarr; elfies_blur.png&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So Pixie wrote a quick note on the internal wiki:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;# Standard hero image (1200x630)&lt;br /&gt;blurfill -w 1200 -h 630 aurora-santa.jpg&lt;br /&gt;&lt;br /&gt;# Story-style tall image (1080x1920)&lt;br /&gt;blurfill -w 1080 -h 1920 reindeer-selfie.png&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Within minutes, the marketing elves were gleefully pushing perfectly formatted images into their social media queues. They didn&#38;rsquo;t need to know how the magic worked &#38;mdash; just that the result looked better than anything they&#38;rsquo;d done in their old image editor.&lt;/p&gt;

&lt;h3 id=&#34;A-point-and-click-Dancer2-web-app&#34;&gt;3. A point-and-click Dancer2 web app&lt;/h3&gt;

&lt;p&gt;Of course, there were still a few elves who were allergic to terminals.&lt;/p&gt;

&lt;p&gt;&#38;ldquo;Is there a web page where I can just upload an image?&#38;rdquo; one of them asked plaintively. &#38;ldquo;Preferably without any of those scary flags?&#38;rdquo;&lt;/p&gt;

&lt;p&gt;Pixie was ready for this as well.&lt;/p&gt;

&lt;p&gt;The distribution includes a Dancer2 web interface, App::BlurFill::Web. There&#38;#39;s even a ready-made PSGI app in bin/app.psgi that just does:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;&lt;span class=&#34;synStatement&#34;&gt;use &lt;/span&gt;App::BlurFill::Web;&lt;br /&gt;&lt;br /&gt;App::BlurFill::Web-&#38;gt;to_app;&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Under the hood, that web app offers:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;GET /&lt;/code&gt; &#38;ndash; shows a form where you can upload an image and (optionally) choose width and height.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;POST /blur&lt;/code&gt; &#38;ndash; processes the upload, produces the blurred image, and returns a results page showing the output with a download link.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;GET /download/:filename&lt;/code&gt; &#38;ndash; serves the processed file directly for download.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Width and height default to the same familiar values (650&#38;times;350) if you don&#38;rsquo;t specify them.&lt;/p&gt;

&lt;p&gt;In a typical development environment, Pixie could run:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;code-listing&#34;&gt;plackup bin/app.psgi&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Suddenly any elf could:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Visit the page.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pick sleigh-team-photo.jpg.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Type 1200 and 675 into the width/height fields.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click &#38;ldquo;Generate resized image&#38;rdquo;&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Get back a nice preview and a link to the finished image.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;Putting-it-in-a-box:-the-Docker-container&#34;&gt;4. Putting it in a box: the Docker container&lt;/h3&gt;

&lt;p&gt;Everything worked beautifully on Pixie&#38;rsquo;s machine.&lt;/p&gt;

&lt;p&gt;But Pixie had been burned before by elves trying to reproduce her setup:&lt;/p&gt;

&lt;p&gt;&lt;i&gt;&#38;ldquo;It works on your laptop, but not in the Reindeer Barn!&#38;rdquo;&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;This time, she wanted a packaged solution: no &#38;ldquo;install Imager&#38;rdquo;, no &#38;ldquo;which version of Perl?&#38;rdquo;, no &#38;ldquo;do we have the right system libraries?&#38;rdquo;.&lt;/p&gt;

&lt;p&gt;Conveniently, the project provides a pre-built Docker container that runs the web app. The details are in the &lt;a href=&#34;https://github.com/davorg-cpan/app-blurfill/blob/main/docker/DOCKER.md&#34;&gt;DOCKER.md&lt;/a&gt; file on GitHub, with copy-and-paste commands for pulling and running the image in a single step.&lt;/p&gt;

&lt;p&gt;That meant:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;The DevOps goblins could run the same container on their Kubernetes cluster.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Workshop elves could run it on their own machines if they liked.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Everyone saw the same web UI, and everyone got the same results.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pixie just had to share one short internal link to the instructions, and the entire organisation suddenly had a standard way of producing correctly sized images.&lt;/p&gt;

&lt;h3 id=&#34;Result:-Happier-reindeer-better-images&#34;&gt;Result: Happier reindeer, better images&lt;/h3&gt;

&lt;p&gt;By Christmas week, the difference was obvious:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;The NaughtyOrNice dashboard was full of crisp hero images, all perfectly sized.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The sleigh-tracking mobile app had consistent, professional-looking cards instead of a strange mixture of crops and letterboxing.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Social posts looked like they&#38;rsquo;d been designed by a real creative studio, not rushed together in an ageing bitmap editor.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, the reindeer union stopped complaining about losing antlers, hooves, or tails in badly cropped shots.&lt;/p&gt;

&lt;p&gt;On Christmas Eve, Santa stopped by Pixie&#38;rsquo;s desk.&lt;/p&gt;

&lt;p&gt;&#38;ldquo;I&#38;rsquo;ve been seeing these lovely blurred-background images all over our systems,&#38;rdquo; he said. &#38;ldquo;It makes everything feel much more&#38;hellip; professional. What changed?&#38;rdquo;&lt;/p&gt;

&lt;p&gt;Pixie grinned.&lt;/p&gt;

&lt;p&gt;&#38;ldquo;I stopped cropping the photos,&#38;rdquo; she said. &#38;ldquo;And I started letting App::BlurFill do the hard work.&#38;rdquo;&lt;/p&gt;

&lt;p&gt;Santa nodded thoughtfully.&lt;/p&gt;

&lt;p&gt;&#38;ldquo;Very good. Put the module on the &#38;lsquo;Nice&#38;rsquo; list,&#38;rdquo; he said. &#38;ldquo;And while you&#38;rsquo;re at it, make sure we&#38;rsquo;ve got a blurred-background version of the sleigh for my new profile picture.&#38;rdquo;&lt;/p&gt;

&lt;h3 id=&#34;Your-own-festive-blur-filled-images&#34;&gt;Your own festive blur-filled images&lt;/h3&gt;

&lt;p&gt;If you find yourself juggling awkward aspect ratios this December, you can take the same approach as Pixie:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Use the Perl API when you want to integrate into existing scripts or web apps.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the blurfill CLI for batch processing from the command line.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the Dancer2 web app when you want a friendly upload-and-click interface.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the Docker container to give your whole team a standardised, reproducible setup.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the nice thing is: it isn&#38;rsquo;t just for Christmas dashboards. The same blurred-background trick works beautifully for:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;Website hero images&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Social media cards&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Video thumbnails&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anything where you need a fixed aspect ratio but don&#38;rsquo;t want to lose the important bits of your image&lt;/p&gt;

&lt;p&gt;Just like Pixie, you might find that a little bit of Perl and a tasteful blur are all you need to make your images look more magical.&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-02T00:00:00Z</updated><category term="Perl"/><author><name>Dave Cross</name></author></entry><entry><title>The Ghost of Perl Developer Surveys Past, Present, and Future</title><link href="https://perladvent.org/2025/2025-12-01.html"/><id>https://perladvent.org/2025/2025-12-01.html</id><summary type="html">&lt;div class=&#39;pod&#39;&gt;&lt;p&gt;&lt;img src=&#34;the-ghost-of-perl-developer-surveys-past-present-and-future.png&#34; alt=&#34;The Ghost of Perl Developer Surveys Past, Present, and Future&#34; style=&#34;float: right; margin: 0 0 1em 1em; width: 300px;&#34;/&gt;

&lt;/p&gt;



&lt;p&gt;It was a quiet December evening when a weary Perl developer&#38;mdash;curled up with a warm mug of cocoa&#38;mdash;received an unexpected visitor. The logs rustled. The LEDs flickered. And from the shimmering depths of CPAN emerged a familiar yet mysterious figure: The Ghost of Perl Surveys Past.&lt;/p&gt;

&lt;p&gt;&#38;quot;Come,&#38;quot; said the Ghost. &#38;quot;It is time to see what the community has been building, breaking, and dreaming over the years.&#38;quot;&lt;/p&gt;

&lt;p&gt;And so began our spectral journey through the Perl Developer Survey, culminating in the 2025 edition&#38;mdash;our newest glimpse into the life of the language and the tools that help keep it alive.&lt;/p&gt;

&lt;h3 id=&#34;The-Ghost-of-Surveys-Past&#34;&gt;The Ghost of Surveys Past&lt;/h3&gt;

&lt;p&gt;The Ghost whisked our developer back to the origins of Perl community surveying, when the questions were simpler, the spreadsheets smaller, and the editors... surprisingly similar.&lt;/p&gt;

&lt;p&gt;The early surveys of 2009&#38;ndash;2010 didn&#38;#39;t try to capture the entire ecosystem; they focused on the fundamentals of how Perl developers worked, what shaped their decisions, and what tools they trusted.&lt;/p&gt;

&lt;p&gt;The Ghost unfurled four parchment scrolls from those early years.&lt;/p&gt;

&lt;h4 id=&#34;October-2009-Which-editor-s-or-IDE-s-are-you-using-for-Perl-development&#34;&gt;October 2009 &#38;mdash; Which editor(s) or IDE(s) are you using for Perl development?&lt;/h4&gt;

&lt;p&gt;Vim and Emacs reigned like ancient kings, their terminal thrones rarely contested. Padre was young and promising. Komodo IDE had loyal supporters. Notepad++ and TextMate rounded out the tribe of early Perl scribes.&lt;/p&gt;

&lt;p&gt;Modern Perl tooling builds on this foundation, but the Ghost smiled knowingly: &lt;b&gt;Vim and Emacs are still with us. Some ghosts never leave.&lt;/b&gt;&lt;/p&gt;

&lt;h4 id=&#34;November-2009-What-other-technologies-languages-templating-systems-are-you-using-besides-Perl&#34;&gt;November 2009 &#38;mdash; What other technologies, languages, templating systems are you using besides Perl?&lt;/h4&gt;

&lt;p&gt;Early surveys discovered that Perl developers were well versed in the web ecosystem, often using JavaScript, HTML, CSS, SQL, and XML alongside Perl.&lt;/p&gt;

&lt;p&gt;This was a hint that Perl developers were not isolated in their own world, but active participants in the broader web development community.&lt;/p&gt;

&lt;h4 id=&#34;April-2010-What-is-the-primary-operating-system-you-use-for-developing-Perl-applications&#34;&gt;April 2010 &#38;mdash; What is the primary operating system you use for developing Perl applications?&lt;/h4&gt;

&lt;p&gt;Linux dominated. macOS was rising among laptop-toting developers. Windows was present but rarely central.&lt;/p&gt;

&lt;p&gt;The picture painted was unmistakable:&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Perl grew up in UNIX-like environments&#38;mdash;and thrives there still.&lt;/b&gt;&lt;/p&gt;

&lt;h4 id=&#34;May-2010-What-are-the-most-important-features-of-an-employer-or-a-job-opportunity-for-you&#34;&gt;May 2010 &#38;mdash; What are the most important features of an employer or a job opportunity for you?&lt;/h4&gt;

&lt;p&gt;Even in 2010, developers asked for:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;good compensation&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;interesting technical challenges&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;stability&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;respect for work-life balance&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;opportunities to use modern Perl practices and tools&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;respect for Perl as a language&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&#38;quot;These questions were seeds,&#38;quot; the Ghost said. &#38;quot;From them grew the surveys we know today.&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;The-Ghost-of-Survey-Present-2025&#34;&gt;The Ghost of Survey Present (2025)&lt;/h3&gt;

&lt;p&gt;The Ghost shimmered and shifted, becoming a modern figure surrounded by bar charts, editor icons, CI pipelines, and version numbers. It held up a scroll containing the freshly gathered results of the 2025 Perl Developer Survey.&lt;/p&gt;

&lt;p&gt;&#38;quot;What do Perl developers look like today?&#38;quot; the Ghost asked.&lt;/p&gt;

&lt;p&gt;The 2025 survey revealed a vibrant, diverse conglomeration of developers, using a variety of tools. The ghost&#38;#39;s scroll contained an &lt;code&gt;iframe&lt;/code&gt; embedding the full results:&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://survey.perlide.org/results/2025&#34;&gt;https://survey.perlide.org/results/2025&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some highlights included:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&lt;b&gt;Editors and IDEs&lt;/b&gt;: Vim led the pack, with Visual Studio Code taking a large share, and Emacs trailing behind.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Other Tools&lt;/b&gt;: Perl::Tidy was used by the majority of respondents.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Language Version&lt;/b&gt;: Perl 5.40 was the most commonly used version, with a significant number also using Perl 5.42 and 5.38.&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;CPAN Client&lt;/b&gt;: cpanm was the dominant CPAN client, with cpan and other clients also in use.&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;The-Ghost-of-Surveys-Yet-to-Come&#34;&gt;The Ghost of Surveys Yet to Come&lt;/h3&gt;

&lt;p&gt;A silent figure emerged&#38;mdash;hooded and watchful: &lt;b&gt;The Ghost of Surveys Yet to Come&lt;/b&gt;, revealing only outlines and possibilities.&lt;/p&gt;

&lt;p&gt;The ghost spoke in riddles:&lt;/p&gt;

&lt;p&gt;&lt;b&gt;&#38;quot;What if we could peer into the future of Perl development? What trends might we see? What tools might emerge? What challenges might we face?&#38;quot;&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;He unfurled an infernal scroll with speculative questions for future surveys:&lt;/p&gt;

&lt;ul&gt;

&lt;li&gt;&lt;p&gt;&lt;b&gt;Containerization&lt;/b&gt;: How extensively are Perl applications being containerized?&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Cloud Usage&lt;/b&gt;: What cloud platforms are most popular among Perl developers?&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;CI/CD Adoption&lt;/b&gt;: How widely have continuous integration and continuous deployment practices been adopted in Perl projects?&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;AI Integration&lt;/b&gt;: To what extent are Perl developers utilizing AI tools in their development workflows?&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Modern Frameworks&lt;/b&gt;: Which modern web frameworks are gaining traction in the Perl community?&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Performance Optimization&lt;/b&gt;: What strategies are Perl developers using to optimize application performance in modern environments?&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Security Practices&lt;/b&gt;: How are Perl developers addressing security concerns in their applications?&lt;/p&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;b&gt;Community Involvement&lt;/b&gt;: How engaged are Perl developers with the broader open-source community?&lt;/p&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ghost began to fade...&lt;/p&gt;

&lt;p&gt;&#38;quot;These questions are the seeds of future surveys,&#38;quot; the Ghost whispered. &#38;quot;From them will grow the next chapters in our shared story.&#38;quot;&lt;/p&gt;

&lt;h3 id=&#34;The-Gift-of-Insight&#34;&gt;The Gift of Insight&lt;/h3&gt;

&lt;p&gt;As dawn approached, our developer realized the purpose of the journey:&lt;/p&gt;

&lt;p&gt;&lt;b&gt;The Perl Developer Survey isn&#38;#39;t merely data; it&#38;#39;s a chronicle of how we work, what we value, and where we&#38;#39;re going.&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;From the early questions of 2009 and 2010&#38;hellip; to the detailed metrics of 2025&#38;hellip; to the futures waiting in tomorrow&#38;#39;s shadows&#38;hellip;&lt;/p&gt;

&lt;p&gt;the survey has emerged again to inform and inspire.&lt;/p&gt;

&lt;p&gt;So this year, may your December be filled with green test suites, stable builds, and cheerful hacking.&lt;/p&gt;

&lt;p&gt;See the full 2025 Perl Developer Survey results at &lt;a href=&#34;https://survey.perlide.org/results/2025&#34;&gt;https://survey.perlide.org/results/2025&lt;/a&gt;. Be sure to participate in future surveys to help influence the future of Perl tooling development!&lt;/p&gt;

&lt;p&gt;Merry Christmas, and happy Perl programming to all!&lt;/p&gt;

&lt;/div&gt;</summary><updated>2025-12-01T00:00:00Z</updated><category term="Perl"/><author><name>Rawley Fowler</name></author></entry></feed>