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.
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-UtilsFarell 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/AppRegarding 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
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 #!perlI 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> =cutThe 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.
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;
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.
README.pod
from the script using the command:
$ perldoc -u bin/date-tag > README.podor, if we have more files, as in our case with:
$ perldoc -u lib/App/Utils.pm > README.pod
App::Software::License
package. We run the following command:
$ software-license --holder 'Vlado Keselj' --license Perl_5 > LICENSEWe 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.
$ perl Makefile.PL $ make manifest $ makeWe can install the script and module in our local system, but we need to have root permissions for it, and run the command:
$ make installThe distribution tarball file is prepared using the command:
$ make distand we can clean up the directory using the command:
$ make cleanThe name of the tarball is
App-Utils-0.01.tar.gz
.
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.gzI need to provide my password, and the module is uploaded to CPAN. It can be installed using the command "
cpan App::Utils
".
$ git initIn order to decide which files to include in the git repository, we can take a look at the MANIFEST file:
$ cat MANIFESTWe add all the files on the list:
$ git add date-tag lib/App/Utils.pm LICENSE Makefile.PL MANIFEST README.podand 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.gitI 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.gitFinally, we push the files to GitHub with the command:
$ git push -u github master
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).
App::Utils
may further changed without
necessarily syncronized updates in this file.