— my toolbox is filled with perl, javascript, css and html


Applify: Scripting without boilerplate

Friday, July 20, 2012

    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.


    Here is an example application, using Applify. The application reads the battery stats in Linux and prints it out to screen.

    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 {
            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->{'remaining_capacity'} / $data->{'present_rate'};
                = $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') {
            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';

    I’m using Applify for my new scripts. Which module will you be using?

    blog comments powered by Disqus