May 2009

Perl Glue: Wrapping a Utility with Perl Interfaces

In an earlier text the idea/ implementation of a local Perl lib was discussed. In this text a better example of writing a Perl library is performed but in the context of creating Perl interfaces to a command line utility which can in turn be called as subroutines from a Perl program.

Example & Scenario: Simplified Image Scaling

The enlightenment transform utility or etu for short takes a variety of options to scale up or down the size of an image leveraging imlib2. The syntax (as of this writing) is:

etu [options][arguments]
etu [-D|--daemonize interval]
etu [-d|--dir   dir][-s|--src][-h|--height height]
etu [-w|--width width][-q|--quality percent]
etu [-f|--file  filename][-o|--output filename]
Single file Options:
 -f|--file   file  Single input image filename
 -o|--output file  Output image filename
Directory Options:
 -s|--src dir   Original images directory
 -d|--dir dir   Output directory
Global  Options:
 -h|--height  size  Height in pixels      default: 96px
 -q|--quality level Quality level (1-100) default: 75%
 -w|--width   size  Width in pixels       default: 128px

For the sake of argument the goal is to write a Perl lib which other Perl programs can call which performs the following:

  • scales a given image
  • scales a set of given images in a directory
  • updates an image
  • updates a set of given images in a directory

Essentially wrap the program's full functionality less the daemonize capability. Given that the nature of etu is known and the desired functions are known the sub routines and their arguments can be laid out easily:

  • etu_image_scale source_image destination_image width height quality
  • etu_update_image source_image destination_image width height quality
  • etu_dir_scale source_directory destination_directory width height quality
  • etu_dir_update source_directory destination_directory width height quality

Note the redundant arguments of width, height and quality. In order to simplify the work those could be commoned up into an initializer that sets up the arguments then calls the appropriate function.

Setting up etu_init()


$etu = "/usr/local/bin/etu"; # We do not protect this so the programmer
                             # might override it

# Match the defaults of etu for intilization
my ($width, $height, $quality) = (0,0,0);

###
# etu_init: Initialize the common arguments for the functions
#           Although it looks useless we can always use it for
#           for more stuff later and it makes the caller look cleaner
#           
sub etu_init()
{
        ($width, $height, $quality) = $@_;
}

1; # Main - return a true value

=head1 DESCRIPTION

This small library provides a Perl interface to the etu program. There are
five subroutines to initialize image parameters, scale single images,
update single images, scale entire directories and update entire
directories. Note that etu does not support recursion so the directories 
must be flat. Also note that if etu_init() is not used the etu program
will simply use the defaults (see etu -u for details).

The usage for each is:

    etu_init (image_width_in_pixels, image_height_in_pixels, 
              image_quality_percent);

=head1 EXAMPLES

    etu_init (800, 600, 90); 

=head1 PREREQUISITES

The etu utility must be installed on your OS.

=head1 COREQUISITES

none

=cut

Note that the documentation part of the lib is already started, this will save time later as well. Here is what the perldoc output looks like:

ETU(1)                User Contributed Perl Documentation               ETU(1)



DESCRIPTION
       This small library provides a Perl interface to the etu program. There
       are five subroutines to initialize image parameters, scale single
       images, update single images, scale entire directories and update
       entire directories. Note that etu does not support recursion so the
       directories must be flat. Also note that if etu_init() is not used the
       etu program will simply use the defaults (see etu −u for details).

       The usage for each is:

           etu_init(image_width_in_pixels, image_height_in_pixels,
                    image_quality_percent_value);

EXAMPLES
           etu_init (800, 600, 90);

PREREQUISITES
       The etu utility must be installed on your OS.

COREQUISITES
       none



perl v5.8.8                       2009—05—15                            ETU(1)

Now time to do all of the transform functions but there is one small problem; when invoking etu if any of the three initializer variables (width, height, quality) were not set in etu_init and they are passed they will all equal zero! There is a cure, build the argument string by seeing if they exist:

###
# argvector: A helper for each of the transform calls determine
#            whether or not the 3 globals have been set (w,h,q)
###
sub argvector()
{
        my @args;

        if ($width) {
                push (@args, " -w $width ");
        }

        if ($height) {
                push (@args, " -h $height ");
        }

        if ($quality) {
                push (@args, " -q $quality");
        }

        return (@args);
}

Note that all three are checked, that is in case someone for some bizarre reason passed a 0 to etu_init().

Also the vector function is not being documented, it is internal to the library therefore there is no reason the programmer needs to know more about it.

The Transform Functions

Time to create the transform functions. Since etu checks on the existence of directories and files and creates new ones as needed, there really isn't much to the functions at all, first the single image scaling:

###
# etu_scale_image: Scale an image
###
sub etu_scale_image
{
        my ($src, $dst) = @_;
        my @etu_extargs = argvector();

        system("$etu -f $src -d $dst @etu_extargs");
}

Ironically, etu will update a pre-existing image if any of the parameters have changed or the source file has changed automatically using the scale function. In order to force an update all that is needed is a thin wrapper to etu_scale_image:

###
# etu_update_image: etu automatically updates so we just wrap scale
###
sub etu_update_image
{
        etu_scale_image(@_);
}

Now of course doing the same for the directories is trivial:

###
# etu_directory_scale/update: Setup and call etu to scale. Note we update
#                             by default so just thin-wrap the scale call.
###
sub etu_scale_dir
{
        my ($src, $dst) = @_;
        my @etu_extargs = argvector();

        system("$etu -s $src -d $dst @etu_extargs");
}
sub etu_update_dir { etu__scale_dir(@_); }

The observant will notice in the two worker functions there is redundancy:

  • $etu is called twice
  • argvector() is called twice
  • Outside of the switches the arguments are kind of generic

While the external functions will not change at all by adding a single function which:

  1. Sets up the extra arguments and
  2. calls etu with the correct options (file or directory)

Some coding can be saved:

###
# exec_etu: execute etu with all the arguments provided
###
sub exec_etu
{
        my @etu_extargs = argvector;
        system("$etu @_ @etu_extargs");
}

And now the functions look like so:

###
# etu_scale_image and update: Setup and call etu to scale. Note we update
#                             by default so just thin-wrap the scale call.
###
sub etu_scale_image
{
        my ($src, $dst) = @_;
        
        etu_exec("-f $src -o $dst");
}
sub etu_update_image { etu_scale_image(@_); }

###
# etu_directory_scale/update: Setup and call etu to scale. Note we update
#                             by default so just thin-wrap the scale call.
###
sub etu_scale_dir
{
        my ($src, $dst) = @_;
        etu_exec("-s $src -d $dst");
}
sub etu_update_dir { etu__scale_dir(@_); }

Full Source Listing Plus Perldoc

$etu = "/usr/local/bin/etu"; # We do not protect this so the programmer
                             # might override it

my ($width, $height, $quality) = (0,0,0); # We know we need em so set em up

###
# etu_init: Initialize the common arguments for the functions
#           Although it looks useless we can always use it for
#           for more stuff later and it makes the caller look cleaner
###           
sub etu_init
{
        ($width, $height, $quality) = @_;
}

###
# argvector: Build the tail end of the argument vector based on 
#                whether or not the 3 globals have been set (w,h,q)
###
sub argvector
{
        my @args;

        if ($width) {
                push (@args, " -w $width ");
        }

        if ($height) {
                push (@args, " -h $height ");
        }

        if ($quality) {
                push (@args, " -q $quality");
        }

        return (@args);
}

###
# exec_etu: execute etu with all the arguments provided
###
sub exec_etu
{
        my @etu_extargs = argvector;
        system("$etu @_ @etu_extargs");
}

###
# etu_scale_image and update: Setup and call etu to scale. Note we update
#                             by default so just thin-wrap the scale call.

sub etu_scale_image
{
        my ($src, $dst) = @_;

        etu_exec("-f $src -o $dst");
}
sub etu_update_image { etu_scale_image(@_); }

###
# etu_directory_scale/update: Setup and call etu to scale. Note we update
#                             by default so just thin-wrap the scale call.
###
sub etu_scale_dir
{
        my ($src, $dst) = @_;
        etu_exec("-s $src -d $dst");
}
sub etu_update_dir { etu__scale_dir(@_); }


1; # Main - return a true value


=head1 DESCRIPTION

This small library provides a Perl interface to the etu program. There are
five subroutines to initialize image parameters, scale single images,
update single images, scale entire directories and update entire
directories. Note that etu does not support recursion so the directories 
must be flat. Also note that if etu_init() is not used the etu program
will simply use the defaults (see etu -u for details).

The usage for each is:

    etu_init(image_width_in_pixels, image_height_in_pixels, 
             image_quality_percent_value);
    etu_scale_image(image_source_file, image_destination_file);
    etu_update_image(image_source_file, image_destination_file);
    etu_scale_dir(image_source_dir, image_destination_dir);
    etu_update_dir(image_source_dir, image_destination_dir);

=head1 EXAMPLES

    etu_init (80, 60, 80); 
    etu_scale_image (myphoto.jpg, myphoto_thumb.jpg);
    etu_update_image (myphoto.jpg, myphoto_thumb.jpg);
    etu_scale_dir (/home/me/my_party_photos, /home/me/my_party_photos_thumbs);

=head1 PREREQUISITES

The etu utility must be installed on your OS.
    
=head1 COREQUISITES
    
none

=cut

And the final perldoc output:

ETU(1)                User Contributed Perl Documentation               ETU(1)



DESCRIPTION
       This small library provides a Perl interface to the etu program. There
       are five subroutines to initialize image parameters, scale single
       images, update single images, scale entire directories and update
       entire directories. Note that etu does not support recursion so the
       directories must be flat. Also note that if etu_init() is not used the
       etu program will simply use the defaults (see etu −u for details).

       The usage for each is:

           etu_init(image_width_in_pixels, image_height_in_pixels,
                    image_quality_percent_value);
           etu_scale_image(image_source_file, image_destination_file);
           etu_update_image(image_source_file, image_destination_file);
           etu_scale_dir(image_source_dir, image_destination_dir);
           etu_update_dir(image_source_dir, image_destination_dir);

EXAMPLES
           etu_init (80, 60, 80);
           etu_scale_image (myphoto.jpg, myphoto_thumb.jpg);
           etu_update_image (myphoto.jpg, myphoto_thumb.jpg);
           etu_scale_dir (/home/me/my_party_photos, /home/me/my_party_photos_thu
mbs);

PREREQUISITES
       The etu utility must be installed on your OS.

COREQUISITES
       none



perl v5.8.8                       2009—05—15                            ETU(1)

Summary

With a little thinking simple command utilities that might appear to be difficult to wrestle with inside of Perl programs can be thin-wrapped into meaningful system interfaces.