Skip to content

Instantly share code, notes, and snippets.

@MansourM61
Last active January 15, 2026 14:47
Show Gist options
  • Select an option

  • Save MansourM61/02cdde0b72a470b62b20589215b766ec to your computer and use it in GitHub Desktop.

Select an option

Save MansourM61/02cdde0b72a470b62b20589215b766ec to your computer and use it in GitHub Desktop.

Perl Project Setup

Install Perl and Perlbrew

Windows OS

There are two options to install Perl:

  1. ActiveState Perl: ActiveState offers both a free community version and a commercially supported binary distribution of Perl for Win32 and Perl for Win64.
  2. Strawberry Perl: A 100% Open Source Perl for Windows that is exactly the same as Perl everywhere else; this includes using modules from CPAN, without the need for binary packages.

UNIX OS

Install Perlbrew that is an admin-free installation management tool for Perl.

  1. To install:

    curl -L https://install.perlbrew.pl | bash
  2. To see the available versions:

    perlbrew available
  3. To install a Perl version:

    perlbrew install perl-5.42.0
  4. To switch to a Perl version:

    perlbrew switch perl-5.42.0

Packages

  1. Install the followings:

    cpan App::cpanminus
    cpan local::lib
    perl -Mlocal::lib
    cpanm Reply
    cpan Module::Build
    cpan ExtUtils::MakeMaker
    cpan Module::Starter
    cpanm Getopt::Long
    cpanm Test::More
    cpanm Test::File
    cpanm Test::Output
    cpanm Test::MockObject
    cpanm Benchmark
    cpanm Devel::Cover
    cpanm Devel::NYTProf
  2. Install Perl's LanguageServer:

  3. on Unix:

    cpanm Perl::LanguageServer
  4. on Windows install PerlNavigator

VSCode Extension

  1. Add .vscode/extensions.json with the following contents:

    {
      "recommendations": [
        "Nihilus118.perl-debugger",
        "formulahendry.code-runner",
        "bscan.perlnavigator",
        "richterger.perl",
        "d9705996.perl-toolbox",
        "JOROL.perl-completions",
        "ms-vscode.test-adapter-converter",
        "hbenl.vscode-test-explorer"
      ]
    }
  2. Add .vscode/settings.json with the following contents:

        "[perl]": {
            "editor.defaultFormatter": "esbenp.prettier-vscode"
        },

Project Setup

Create the project scaffold:

module−starter --builder="Module::Build" --module=TestModule --author="Mojtaba Mansour Abadi" --email=myemail.email.com --verbose

In case the script fails, the solution is to start from a project code with all boilerplate files (e.g. https://github.com/rouzier/perl-Boilerplate.git or https://github.com/lskatz/template-perl.git) or create the project structure manually:

Perl_Project/
├── lib
│   ├── Mylib
│   │   └── Part
│   │        └── Function.pm
│   ├── SomeModule
│   │   └── Command
│   │       ├── initialize.pm
│   │       └── reset.pm
│   ├── SomeModule.pm
│   └── Mylib.pm
├── src
│   └── main.pl
└── t
    └── test.t

The only downside is that the project cannot be built the same way.

Another solution is to create the Perl file setup.pl:

use strict;
use warnings;
use v5.20;

use Module::Starter::App;

Module::Starter::App->run;

And then use it to create the project:

perl setup.pl --builder="Module::Build" --module=TestModule --author="Mojtaba Mansour Abadi" --email=myemail.email.com --verbose

Code Structure

  1. Create src/main.pl

    #!/usr/bin/perl
    
    # src/main.pl
    package main;
    use strict;
    use warnings;
    use diagnostics;
    
    # to include the user-defined modules from "lib" folder, there are two options:
    
    # 1. use the following two lines of codes
    use FindBin;
    use lib "$FindBin::Bin/../lib";    ## points to /path/to/lib
    
    use FindBin qw($Bin);
    use lib "$Bin/../lib";
    # and run the script using:
    # perl main.pl
    
    # 2. include -Ilib when running the script from the command line in the root directory.
    # perl -Ilib src/main.pl
    
    use MyLib;
    
    # To pass on the arguments use the following command
    # perl src/main.pl --length=1 --file=data.txt --verbose --find
    # or
    # perl -Ilib src/main.pl --length=1 --file=data.txt --verbose --find
    use Getopt::Long;
    my $data   = "file.dat";
    my $length = 24;
    my $verbose;
    my $find;
    GetOptions ("length=i" => \$length,    # numeric
                "file=s"   => \$data,      # string
                "verbose"  => \$verbose,   # flag
                "find+"    => \$find)      # flag; both --find and --nofind are allowed (oprional argument)
    or die("Error in command line arguments\n");
    
    run() unless caller;  # call run() is there is no caller (main is invoked as a script)
    
    sub run {  # at the bottom of the file.
      # code to run in the main loop
     MyLib::func1();
     MyLib->func1();
     MyLib::func3();
     say $data;
     say $length;
     say $verbose;
     say $MyLib::VERSION;
    }
  2. The module files in the lib folder have the following template:

    package Some::Module;  # assumes Some/Module.pm
    
    use strict;
    use warnings;
    use diagnostics;
    use v5.36;
    
    # Get the import method from Exporter to export functions and
    # variables
    use Exporter 5.57 'import';
    
    BEGIN { ... }       # module initialisation code here (global constructor)
    
    # set the version for version checking
    our $VERSION     = '1.00';
    
    # Functions and variables which are exported by default
    our @EXPORT      = qw(func1 func2);
    
    # Functions and variables which can be optionally exported
    our @EXPORT_OK   = qw($Var1 %Hashit func3);
    
    # exported package globals go here
    our $Var1    = '';
    our %Hashit  = ();
    
    # non-exported package globals go here
    # (they are still accessible as $Some::Module::stuff)
    our @more    = ();
    our $stuff   = '';
    
    # file-private lexicals go here, before any functions which use them
    my $priv_var    = '';
    my %secret_hash = ();
    
    # here's a file-private function as a closure,
    # callable as $priv_func->();
    my $priv_func = sub {
        ...
    };
    
    # make all your functions, whether exported or not;
    # remember to put something interesting in the {} stubs
    sub func1      { ... }
    sub func2      { ... }
    
    # this one isn't always exported, but could be called directly
    # as Some::Module::func3()
    sub func3      { ... }
    
    END { ... }       # module clean-up code here (global destructor)
    
    1;  # don't forget to return a true value from the file

Test Code

  1. Create the test code test-mymodule.t based on the following template:

    use Test::More tests => 23;  # the number is used only if the total number of tests are known
    # or
    use Test::More skip_all => $reason;  # to skip an entire testing script
    # or
    use Test::More;   # see done_testing()
    
    use Test::File;  # file-related tests
    use Test::Output;  # test STDOUT or STDERR
    use Test::MockObject;  # mock object
    use Benchmark;  # to benchmark a code
    
    require_ok( 'Some::Module' );
    
    # Various ways to say "ok"
    ok($got eq $expected, $test_name);
    
    is  ($got, $expected, $test_name);
    isnt($got, $expected, $test_name);
    
    # Rather than print STDERR "# here's what went wrong\n"
    diag("here's what went wrong");
    
    like  ($got, qr/expected/, $test_name);
    unlike($got, qr/expected/, $test_name);
    
    cmp_ok($got, '==', $expected, $test_name);
    
    is_deeply($got_complex_structure, $expected_complex_structure, $test_name);
    
    diag(@diagnostic_message);  # to print out a message without interfering with test output
    note(@diagnostic_message);  # to print out a message only in verbose mode
    my @dump = explain @diagnostic_message;  # to dump contents of any references in a human readable format
    is_deeply($have, $want) || diag @dump;
    
    SKIP: {  # declares a block of tests that might be skipped, $how_many tests there are, $why and under what $condition to skip them.
        skip $why, $how_many unless $have_some_feature;
    
        ok( foo(),       $test_name );
        is( foo(42), 23, $test_name );
    };
    
    TODO: {  # declares a block of tests you expect to fail and $why.
        local $TODO = $why;
    
        ok( foo(),       $test_name );
        is( foo(42), 23, $test_name );
    };
    
    can_ok($module, @methods);
    isa_ok($object, $class);
    
    pass($test_name);
    fail($test_name);
    
    # Test::File
    file_exists_ok(file_name);
    file_not_exists_ok(file_name);
    file_not_empty_ok(file_name);
    file_readable_ok(file_name);
    file_min_size_ok(file_name, size);
    
    # Test::Output
    stdout_is(\&function, "expected value");
    stderr_like(\&function, regular_expression);
    
    # Test::MockObject
    my $fake_obj = Test::MockObject->New();
    $fake_obj->set_true( 'fake_func1' );
    $fake_obj->set_false( 'fake_func2' );
    $fake_obj->mock( 'list_of_values' => sub { qw ( list of values ) } );
    $fake_obj->set_series( 'amicae', 'Sunny', 'Kylie', 'Bella' );
    my @values = $fake_obj->list_of_values;
    ok( $fake_obj->fake_func1, "Test 1 is a pass");
    ok( ! $fake_obj->fake_func1, "Test 2 is a pass");
    is( $values[0], 'Val', 'Test 3 is a pass')
    
     # Benchmark
     my $start = Benchmark->new;
     #
     # code to benchmark
     #
     my $end = Benchmark->new;
     my $diff = timediff($end, $start);
    
    BAIL_OUT($why);  # indicates to the harness that things are going so badly all testing should terminate.
    
    done_testing();  # if you don't know the total number of tests, use this line instead
  2. To test the converage:

    Build testcover
    cover
  3. To run test manually:

    perl -I/path/DUT/module  test_file.t
  4. To debug a perl program:

    perl -d some_perl.pl
  5. To profile a perl program and generate HTML or CSV reports:

    perl -d:NYTProf some_perl.pl
    nytprofhtml --open
    nytprofcsv

Documentation

  1. Perl documentation is based on POD comments that is extracted by:

    perldoc -d output.pod some_perl.pl
  2. Once the POD file is ready, generate HTML by running:

    pod2html -outfile index.html some_perl.pl

For the documentation use this template:

#!/usr/bin/perl
package GenerateAboutPage;

=pod

=head1 <Title>

=head2 Written by Mojtaba Mansour Abadi

To use this script, install Perl v5.40, then run the following in the command line:

B<perl> F<ScriptName.pl> I<argument>

I<argument> can be any of the followings:

=over 4

=item B<arg1>: <description of argument>

=back

=cut

################################################################################
# required packages
use v5.20;
use strict;
use warnings;
use diagnostics;


################################################################################
# constants
use constant CONSTANT1 => "value";
use constant MY_OS => $^O;    # use (MY_OS) in a hash, to avoid bareword quoting mechanism.
use constant VERSIONS =>
  { "MSWin32" => "Scripts", "linux" => "bin", "darwin" => "bin" };
use constant NAMES    => qw/N1 N2 N3/;

################################################################################
# declaration of heredoc definitions
my $content_text_1;                  # <descriiption>

################################################################################
# declaration and definition of global variables
my $ignore_errors;    # ignore all errors and continue with scaffolding

my $ReportError = sub {
    if ( $ignore_errors == 0 ) {
        die shift;
    }
    else {
        say shift;
    }
};

################################################################################
# format definitions
my $Parameter_Index = 1, my $Parameter_Name, my $Parameter_Value;
format DATA_TOP =


@|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"'About' Page Parameters"

@<<<<<<<<<@<<<<<<<<<<<<<<<<<<<<<<<<<<<@<<<<<<<<<<<<<<<<<<<<<<<<<<<
"Index", "Name", "Value"
==================================================================
.

format DATA =
@<<<<<<<<<@<<<<<<<<<<<<<<<<<<<<<<<<<<<@<<<<<<<<<<<<<<<<<<<<<<<<<<<
$Parameter_Index, $Parameter_Name, $Parameter_Value
.

################################################################################
# <short description of the function>

=pod

=head3 <Function Name>

=head4 Description

<Longer descrioption with list of arguments>

=cut

sub <Function Name> {
# ...
}

#...

################################################################################
# main logic function called from main scope

=pod

=head3 MainFile

=head4 Description

<long description>

=cut

sub MainFile {
# ...

    system( Command ) == 0
      or $ReportError->("Error: $?");

# ...

    my $old_h = select(STDOUT);
    my $Parameter_Index = 0;
    $^ = "DATA_TOP";
    $~ = "DATA";

    # print a row of the table
    my $PrintParams = sub {
        $Parameter_Name  = shift;
        $Parameter_Value = shift;
        write;
        ++$Parameter_Index;
    };

    $PrintParams->( 'Output Directory',    $output_dir );
    $PrintParams->( 'Output File',         $output_file_name );

    select $old_h;

# ...

    my $file_content = "";
    while ( my $data_line = <DATA> ) {
        if ( $data_line eq "\n" ) {
            next;
        }
        $file_content .= $data_line;
    }

# ...
}

################################################################################
# definition of heredoc definitions

# <descriiption>
$content_text_1 = <<'FILE_CONTENT';
content
FILE_CONTENT

################################################################################
# main entry of the script
main: {
    &MainFile(@ARGV);
}

1;

################################################################################
# POD

=pod

=back

=head2 VERSION

00.02.00.a

=head2 AUTHOR

Mojtaba Mansour Abadi

=head2 LICENCE

This program is licenced under MIT License.

=cut

################################################################################
# embedded data
__DATA__
<Embedded Data>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment