Last week I found myself developing a Perl script to catalog some information for our quality assurance team. Unfortunately, as these things sometimes do, the script’s complexity and requirements started increasing. I still wanted to keep it as a simple script. Yet, it was growing command line arguments that needed extra validation. I also needed to test some functions without waiting for the entire script to run.
As with many things Perl, the basic solution is fairly old. Over twenty years ago, brian d foy popularized the modulino pattern. in which Perl scripts that you execute from the command line can also act as Perl modules. You can even use these modules in other contexts, for example testing.* A modulino seemed like the perfect solution for testing individual script functions, but writing object-oriented Perl outside of a framework (or the new Perl class syntax) can be challenging and verbose.
Enter the cow (Moo)
The Moo system of modules are billed as a lightweight way “to concisely define objects and roles with a convenient syntax that avoids the details of Perl’s object system.” It doesn’t have any XS code. Thus, it doesn’t need a C compiler to install. Unlike its inspiration, Moose, it’s optimized for the fast startup time needed for a command-line script. Sure, you don’t get a full-strength meta-object protocol for querying and manipulating classes, objects, and attributes—those capabilities are concerns for larger applications or libraries. In keeping with the lightweight theme, you can use Type::Tiny constraints for parameter validation. Additionally, there are several solutions for turning command-line arguments into object attributes. (I chose to use MooX::Options, mainly because of its easy availability as an Ubuntu Linux package.)
I’m not about to dump a proprietary script here on my blog. Yet, I have worked up an illustrative example of how to incorporate Moo into a modulino. Call it a “moodulino” if you like; here’s a short-ish script to tell Perl just how you feel at this time of day:
#!/usr/bin/env perl
use v5.38;
package moodulino;
use Moo;
use MooX::Options;
use Types::Standard qw(ArrayRef Str);
option name => (
is => 'ro',
isa => Str,
required => 1,
short => 'n',
doc => 'your name here',
format => 's',
);
option moods => (
is => 'ro',
isa => ArrayRef [Str],
predicate => 1,
short => 'm',
doc => 'a list of how you might feel',
format => 's@',
autosplit => ',',
);
has time_of_day => (
is => 'ro',
isa => Str,
builder => 1,
);
sub _build_time_of_day ($self) {
my %hours = (
5 => ‘morning’,
12 => 'afternoon',
17 => 'evening',
21 => 'night',
);
for ( sort { $b <=> $a } keys %hours ) {
return $hours{$_} if (localtime)[2] >= $_;
}
return 'night';
}
sub run ($self) {
printf "Good %s, %s!\n",
$self->time_of_day,
$self->name;
if ( $self->has_moods ) {
say 'How are you feeling?';
say "- $_?" for $self->moods->@*;
}
}
package main;
main() unless caller;
sub main { moodulino->new_with_options->run() }
And here’s what happens when I run it:
% chmod a+x moodulino.pm
% ./moodulino.pm
name is missing
USAGE: moodulino.pm [-h] [long options ...]
-m --moods=[Strings] a list of how you might feel
-n --name=String your name here
--usage show a short help message
-h show a compact help message
--help show a long help message
--man show the manual
% ./moodulino.pm --name Mark
Good afternoon, Mark!
% ./moodulino.pm —name Mark --moods happy --moods sad --moods excited
Good afternoon, Mark!
How are you feeling?
- happy?
- sad?
- excited?
% ./moodulino.pm —name Mark --moods happy,sad,excited
Good afternoon, Mark!
How are you feeling?
- happy?
- sad?
- excited?
If the mood strikes, I can even write a test script for my script:
#!/usr/bin/env perl
use v5.38;
use Test2::V0;
use moodulino;
plan(3);
my $mood = moodulino->new( name => 'Bessy' );
isa_ok( $mood, 'moodulino' );
can_ok( $mood, 'time_of_day' );
is( $mood->time_of_day,
in_set( qw(
morning
afternoon
evening
night
) ) );
And run it:
% prove -I. t/time_of_day.t
t/daytime.t .. ok
All tests successful.
Files=1, Tests=3, 0 wallclock secs ( 0.00 usr 0.00 sys + 0.07 cusr 0.01 csys = 0.08 CPU)
Result: PASS
* foy later expanded this idea into the chapter “Modules as Programs” in Mastering Perl (2007). You can also read more in his 2014 article “Rescue legacy code with modulinos”. Also explore Gábor Szabó’s articles on the topic. ↩︎


