Index: An Incremental Blog
Newer: A Note on W-plus String Encoding
[ Vlado Keselj | 2020-05-13..2020-08-09 ]

Publishing a Perl Script or Scripts as an App to CPAN

A nice feature of the Perl programming language is existence of the CPAN archive, where the wider community contributes Perl modules with various functionalities. Aside from modules, it is not obvious what is the recommended way to share self-contained Perl programs; i.e., scripts, and individual applications. One approach is to submit program as an individual uncompressed file to CPAN, following certain documentation conventions as described in the article "How to submit a script to CPAN", written by Kurt Starsinic in 2002. This straightforward way has a disadvantage that the submitted script is not a part of a module and so the standard module managing tools cannot be used for easy installation. Another popular approach is to create an App:: module for the program. We will go here through an exercise of packing several programs into an App:: module, by following but also modifying the steps posted by David Farell in his article How to upload a script to CPAN, published on 14-Nov-2016.

Farell in his article shows a simple and straightforward way how to publish a single Perl script as a module. In addition to some minor modifications, we extend the discussion to include more than one program in one module.

Before proceeding, let us clarify that the use of words script and program are interchangeable for a Perl program in this blog. The term Perl script is more popular, but the term program seems to be more appropriate because a Perl script is more of a program in the sense used in other programming languages, than a script of shell commands as used in shell scripting. There are also good arguments for the term script, with Perl being particularely useful as a "glue" language with external commands and systems.

Setting up a Distribution Directory

The first step is to create and setup a distribution directory (i.e., folder), which will be shared via CPAN. Not all files and subdirectories in it are necessarily shared, but some basic structure and file naming should be standardized. Farell in his article starts with a simple program foo and creates the directory App-foo with intention to prepare the Perl module App::foo.

I decided to start with a group of Perl programs, which are useful in working with files and directories in a Linux file system, and publish them on CPAN in a Perl module named App::Utils. It is a general and visible name, and it is not taken, so let's use it. App::Utils is a reasonable name for command-line utilities for work with files and directories. The name implies that my directory name should be App-Utils. The convention of replacing the double colon (::) in module name with the minus sign (-) to create directory name is a good convention because the colon character (:) should be avoided in file and directory names and the minus character (-) is a very convenient separator in file names due to its visibility and it is easy to type. I make this directory as a sub-directory of the directory where I generally keep useful Perl programs, so I go there and run the commands:

 $ mkdir App-Utils
 $ cd App-Utils
Farell suggests creating the subdirectory script to keep the script, and subdirectory lib/App to keep the "stub" module. These are not hard requirements. We will also keep the stub module subdirectory lib/App, and create it with command:
 $ mkdir -p lib/App
Regarding the location of the script, in addition to the option of using the script directory, we could place it in the main module directory. Another option which is sometimes used is the bin subdirectory, since the command-line utilities are usually stored in a subdirectory named bin in a Unix-line system. We do plan to add more utilities, so we will create subdirectory bin to keep them there:
 $ mkdir bin

Preparing a Perl Script (Program)

In this step, we need to prepare our main Perl program. I start with a very simple Perl program named date-tag, which can be useful in some shell aliases, for example. The program prints out a date-time tag in the format YYYY-MM-DD-hhmmss of the current time. The program is named date-tag. As a general rule, another natural name alternative would be date-tag.pl if we want to include Perl file name extension, or if we want to distinguish it from a possible existing command with the same name. For example, if we implement the command tree in Perl, it is better to call it tree.pl because the command does exist on some Linux systems. On the systems where it does not exist, we can use our tree.pl replacement, and just create a soft link tree to point to it. A reason for not using the .pl Perl extension when there is no potential collision as described above, is that, at the end of the day, we do not care in which programming language a utility is written, as long as it performs an expected functionality.

We create a file in the directory bin, named bin/date-tag, and enter the following source code:

 #!/usr/bin/perl
 
 use POSIX;
 print strftime("%Y-%m-%d-%H%M%S\n", localtime(time));

Farell gives an explanation of different appropriate options for the shebang line, and the following ones seem to be equally good:

 #!/usr/bin/perl
 #!/usr/bin/env perl
 #!perl
I prefer the use the first one: #!/usr/bin/perl because it works directly in a typical Unix-like (Linux) system.

This is a very simple program and this could be all that we can put there. We could also add some documentation to it. As minimal record, I like to put a head comment with script name, one-line description (i.e., abstract in CPAN terminology), years of creation and maintenance, and author's name. We make a note that this is not suggested in the Farell's tutorial. The program now looks as follows:

 #!/usr/bin/perl
 # date-tag - print a date-tag in the format YYYY-MM-DD-hhmmss
 # 2020 Vlado Keselj vlado@dnlp.ca http://vlado.ca
 
 use POSIX;
 print strftime("%Y-%m-%d-%H%M%S\n", localtime(time));

It is good to have some further documentation in the file in the POD format to help users in using the script. One way to read the documentation is directly in the file, but POD format is used also to generate other accessible documents in different formats. For example, a one-line description at CPAN is automatically generated from POD, as well as the following CPAN documentation. Once we install the script in Linux, the command man date-tag will present the "man" page, also generated from the script. Farell suggest the following sections in the script documentation: NAME, DESCRIPTION, SYNOPSIS, AUTHOR, LICENSE, and INSTALLATION. We will follow a slightly different format:
(1) NAME,
(2) SYNOPSIS, or typical use. It generally comes before description, since it is typically shorter and likely more frequently looked up.
(3) DESCRIPTION,
(4) AUTHOR, it makes sense to include it again.
(5) LICENSE, license may be redundant, but it may be important if people want to cut-and-paste code or reuse it.
(6) SCRIPT CATEGORIES, this section should be included if we want script to be automatically included in the following list of scripts at CPAN, as required at the following instructions. Beware that the categories are ignored unless they fit into the existing hierarchy.
(7) README, this section seems to be redundant, but it is used by the above CPAN scripts repository. It requires a brief summary of the script, possibly a bit longer than abstract (a couple sentences). We will include INSTALLATION in the stub module documentation, so it is not necessary here.

The script now looks as:

 #!/usr/bin/perl
 # date-tag - print a date-tag in the format YYYY-MM-DD-hhmmss
 # 2020 Vlado Keselj vlado@dnlp.ca http://vlado.ca
 
 use POSIX;
 print strftime("%Y-%m-%d-%H%M%S\n", localtime(time));
 
 exit 0;
 __END__ # Documentation
 
 =head1 NAME
 
 date-tag - print a simple date-tag in the format YYYY-MM-DD-hhmmss

 =head1 SYNOPSIS
 
  $ date-tag
  2020-05-13-154542
  
 =head1 DESCRIPTION
 
 A simple command that prints one line of with the current date-time tag in
 the format C<YYYY-MM-DD-hhmmss>.
 
 =head1 AUTHOR
 
 Vlado Keselj vlado@dnlp.ca http://vlado.ca
 
 =head1 LICENSE
 
 Perl License (perl)
 
 =head1 SCRIPT CATEGORIES

 CPAN
 Unix/System_administration

 =head1 README

 date-tag - print a date-tag in the format C<YYYY-MM-DD-hhmmss>
 
 =cut
The LICENSE section is generally important if you want people to use your code. The most common options in Perl are the Artistic License, which is the original open-source Perl licence; or Perl 5 license.

Create a Stub Module

We write the following simple stub module in the file lib/App/Utils.pm:
 package App::Utils;
 # App::Utils - some useful command-line utilities
 # 2020 Vlado Keselj vlado@dnlp.ca http://vlado.ca
 
 our $VERSION = 0.052;
 
 =head1 NAME
 
 App::Utils - some useful command-line utilities
 
 =head1 DESCRIPTION
 
 This is a stub module that contains some useful command-line utilities,
 created for Linux environment.  Detailed descriptions are included in
 the programs.
 
 F<date-tag> - print a date-time tag in form C<YYYY-MM-DD-hhmmss>
 
 F<date-tag-file> - pre-tag filename with timestamp of last modification
 
 F<remove-empty-dirs> - remove recursively empty directories
 
 F<save> - save a snapshot of given files in C<saved.d> directory
 
 =head1 AUTHOR
 
 Vlado Keselj vlado@dnlp.ca http://vlado.ca
 
 =head1 INSTALLATION
 
 Using C<cpan>:
 
  $ cpan App::Utils
 
 Manual install:
 
  $ perl Makefile.PL
  $ make
  $ make install
 
 =cut
 
 1;

Create Makefile.PL

We prepare Makefile.PL, the standard makefile template for Perl modules:
 use 5.008004;
 use ExtUtils::MakeMaker;
 
 WriteMakefile(
   NAME             => 'App::Utils',
   VERSION_FROM     => 'lib/App/Utils.pm',
   ABSTRACT_FROM    => 'lib/App/Utils.pm',
   AUTHOR           => 'Vlado Keselj',
   LICENSE          => 'perl',
   MIN_PERL_VERSION => '5.0008004',
   EXE_FILES        => ['date-tag'],
   PREREQ_PM        => {
     'POSIX' => 0,
   },
   (eval { ExtUtils::MakeMaker->VERSION(6.46) } ? (META_MERGE => {
     'meta-spec' => { version => 2 },
     resources => {
       repository => {
         type => 'git',
           url  => 'https://github.com/vkeselj/App-Utils.git',
           web  => 'https://github.com/vkeselj/App-Utils',
       },
     }})
   : ()
   ),
 );
The Makefile.PL file includes mostly boilerplate content, with a couple interesting details, such as a required module POSIX and my GitHub repository for the module.

If you actually check the file Makefile.PL published on CPAN, you will see that there is an additional part added to it as follows:

 # parts of Makefile used only in the development directory
 if (-f 'priv.make' ) {
     open(M, ">>Makefile") or die;
     open(I,"priv.make") or die;
     while (<I>) { print M }
     close(M); close(I);
  }		
which includes an additional part of the Makefile for development by myself.

Create a README

A simple way to create a README file is to generate README.pod from the script using the command:
  $ perldoc -u bin/date-tag > README.pod
or, if we have more files, as in our case with:
  $ perldoc -u lib/App/Utils.pm > README.pod

Create a LICENSE File

A LICENSE file can be created using the App::Software::License package. We run the following command:
  $ software-license --holder 'Vlado Keselj' --license Perl_5 > LICENSE
We could also use option --license Artistic_1_0 for an older version of the Perl license, as I understand, or some other; and we could the option --type fulltext if we want a full text of the license stored. We should also check and update if needed the year in the license.

Building Distribution File

The distribution file is in a tarball format, and we can follow the following steps to build it. We generate the Makefile, prepare the MANIFEST file, and set up the files:
 $ perl Makefile.PL
 $ make manifest
 $ make
We can install the script and module in our local system, but we need to have root permissions for it, and run the command:
 $ make install
The distribution tarball file is prepared using the command:
 $ make dist
and we can clean up the directory using the command:
 $ make clean
The name of the tarball is App-Utils-0.01.tar.gz.

Uploading Package to CPAN

The distribution file can be uploaded to CPAN using the web PAUSE interface, or even easier by using the command-line utility cpan-upload available in the CPAN::Uploader module. My CPAN username is VLADO, so I run the following command:
 $ cpan-upload -u VLADO App-Utils-0.01.tar.gz
I need to provide my password, and the module is uploaded to CPAN. It can be installed using the command "cpan App::Utils".

Adding the Package to GitHub

The publish the package to GitHub, we could first create a local git repository with:
 $ git init 
In order to decide which files to include in the git repository, we can take a look at the MANIFEST file:
 $ cat MANIFEST
We add all the files on the list:
 $ git add date-tag lib/App/Utils.pm LICENSE Makefile.PL MANIFEST README.pod
and we commit the files with:
 $ git commit -m'Initial commit'
Before pushing the distribution to GitHub, we have to use the manual GitHub interface to create a repository called App-Utils. After that we can add GitHub as a remote repository with a command such as:
 $ git remote add github git@github.com:vkeselj/App-Utils.git
I used my GitHub userid vkeselj in the above command. The actual command that I use is slightly different due to a particular ssh configuration that matches github.com server with a specific key:
 $ git remote add github git@github.com-vkeselj:vkeselj/App-Utils.git
Finally, we push the files to GitHub with the command:
 $ git push -u github master

Final Notes

The module App::Utils has been uploaded to CPAN and GitHub as described. Some further changes are made, such as adding new utilities, and the plan is to gather utilities under the subdirectory bin (unlike the directory script suggested in the Farell's article).

Updates

Even though I felt initially happy about writing this as my first blog, and about the result of a bit of time invested, I am getting more unhappy about additional time spent on revising it. I will try to freeze the post at this state, and any further changes and suggestiond add as updates in this section. It means that the actual module App::Utils may further changed without necessarily syncronized updates in this file.
created: 2020-05-13, last update: 2020-08-09, me comments

© 2020-2023 Vlado Keselj, last update: 14-Feb-2022