Applify: Scripting without boilerplate
Applify is a module which helps you write scripts with less boilerplate code. The scripts written with Applify can also be tested easily.
I started out using plain Getopt::Long
, but I thought it was clumsy to
combine with my OO code. Then I started using Moose
and
MooseX::Getopt
and life was starting to get good – but not quite: The
problem was that it took “forever” to load the application. For a long
time I didn’t care much about it (since Moose spared me for so much
development time), but after having real users on my scripts I was
forced to pay attention: Scripts that used to start up instant took
about one second to start.
In addition, the MooseX::Getopt
scripts I wrote also had a lot of
unwanted boilerplate code which I found cumbersome.
Then I looked around on metacpan and found a number of other modules which tried to make writing scripts easy, but I still thought it was too complex to set up. That was when I wanted to try to do my own and the result is Applify.
Highlighs:
- Less boilerplate.
- Loads fast.
- Can extend other perl classes, based on Moose, Moo or any other
framework that you like, using
extends 'My::Generic::Class';
. Note: –help, –version and –man will still be blasting fast. - Auto –help, –man and –version if specified.
- You can embed other scripts using
do()
. Example on how to call another script:(do $script)->run;
- Moo and Applify can be used side-by-side.
- Works with fatpack.
Here is an example application, using Applify. The application reads the battery stats in Linux and prints it out to screen.
#!/usr/bin/perl
use Applify; # import strict and warnings
# define application options:
# --battery /path/to/battery/dir
# --format [human|perl]
option str => battery => 'Path to battery proc dir', '/proc/acpi/battery/BAT0';
option str => format => 'Output format', 'human';
# --version will print "1.23"
version 1.23;
# just a method to read file contents
sub read_proc_files {
my $self = shift;
local @ARGV = map { $self->battery ."/$_" } @_;
map {
chomp;
my($key, $value) = /(\w[^:]+):\s*(\w+)/ or next;
$key =~ s/\s/_/g;
$key => $value;
} <>
}
# another method to prepare human readable data
sub proc_data_to_human {
my($self, $data) = @_;
if($data->{'present_rate'} > 0) {
$data->{'time_left'}
= $data->{'remaining_capacity'} / $data->{'present_rate'};
$data->{'percent_left'}
= $data->{'remaining_capacity'} / $data->{'last_full_capacity'} * 100;
}
else {
@$data{qw/ time_left percent_left /} = (-1, -1);
}
}
# a method that knows how to print the --format human output
sub human_formatting {
[ charging_state => 'Battery state' => '%s' ],
[ present_rate => 'Discharge rate' => '%5i mW' ],
[ remaining_capacity => 'Remaining power' => '%5i mWh' ],
[ time_left => 'Remaining time' => '%5.2f h' ],
[ percent_left => 'Remaining percentage' => '%5.2f %%' ],
}
# app {} creates the main application method which is called
# when the script starts.
# Note: $self is not blessed to "main::", but to a class
# generated by Applify
app {
my($self, @extra) = @_;
my %data = $self->read_proc_files(qw/ info state /);
if($self->format eq 'human') {
$self->proc_data_to_human(\%data);
for my $f ($self->human_formatting) {
printf "%-22s $f->[2]\n", $f->[1], $data{$f->[0]};
}
}
elsif($self->format eq 'perl') {
require Data::Dumper;
print Data::Dumper::Dumper(\%data);
}
else {
die "Unknown output format: ", $self->format, "\n";
}
return 0; # need to return an exit value, or Applify will barf
};
Here is an example unittest:
use strict;
use warnings;
use Test::More;
my $application = do 'script/battery.pl'
or BAIL_OUT "Could not read script/battery.pl: $@";
my $script = $application->_script;
isa_ok $script, 'Applify';
can_ok $application, qw/ run human_formatting read_proc_files proc_data_to_human /;
is $script->options->[0]{'name'}, 'battery', '--battery option';
is $script->options->[0]{'default'}, '/proc/acpi/battery/BAT0', '..with default';
is $script->options->[1]{'name'}, 'format', '--format option';
is $script->options->[1]{'default'}, 'human', '..with default';
done_testing;
I’m using Applify for my new scripts. Which module will you be using?