ExtUtils::ModuleMaker::TT - Makes skeleton modules with Template Toolkit


ExtUtils-ModuleMaker-TT documentation Contained in the ExtUtils-ModuleMaker-TT distribution.

Index


Code Index:

NAME

Top

ExtUtils::ModuleMaker::TT - Makes skeleton modules with Template Toolkit templates

SYNOPSIS

Top

 use ExtUtils::ModuleMaker;
 my $mmtt = ExtUtils::ModuleMaker->new (
     NAME => 'My::New::Module',
     ALT_BUILD => 'ExtUtils::ModuleMaker::TT',
     TEMPLATE_DIR => '~/.perltemplates',
 );
 $mmtt->complete_build();

DESCRIPTION

Top

Note: ExtUtils::ModuleMaker has changed substantially in recent releases and ExtUtils::ModuleMaker::TT has similarly changed substantially to be compatible with these changes. Please report any bugs you may find.

This module extends ExtUtils::ModuleMaker to use Template Toolkit 2 (TT2) to build skeleton files for a new module. Templates may either be default templates supplied within the module or user-customized templates in a directory specified with the TEMPLATE_DIR parameter.

Summary of Features/Enhancements:

Notable changes from ExtUtils::ModuleMaker:

USAGE

Top

ExtUtils::ModuleMaker::TT is designed to be used with the ALT_BUILD parameter of ExtUtils::ModuleMaker. It replaces much of the functionality of ExtUtils::ModuleMaker::StandardText.

 use ExtUtils::ModuleMaker;
 my $mmtt = ExtUtils::ModuleMaker->new (
     NAME => 'My::New::Module',
     ALT_BUILD => 'ExtUtils::ModuleMaker::TT',
 );

Generally, users should just use the included command-line program, makeperlmod. For example, the following command will create a module distribution using default settings:

    makeperlmod -n Sample::Module

See the makeperlmod manual page for details on creating a custom configuration file (for setting author details and other ExtUtils::ModuleMaker options) that will extend or override defaults set in an ExtUtils::ModuleMaker::Personal::Defaults file. The CUSTOMIZING TEMPLATES section below contains other examples.

When specified as the ALT_BUILD, ExtUtils::ModuleMaker::TT provides several additional methods as described below. The makeperlmod source provides a practical example of such usage.

PUBLIC METHODS

Top

build_single_pm

    $mmtt->build_single_pm( $module );

Creates a new .pm file and a corresponding .t file.

The $module parameter may be either a hash reference containing configuration options (including NAME) or a string containing the name of a module, in which case the default configuration will be used. E.g.:

    $module = { NAME => 'Sample::Module', NEED_POD => 0 };

or

    $module = 'Sample::Module';

This method must be able to locate the base directory of the distribution in order to correctly place the .pm and .t files. A complete_build() call sets the Base_Dir parameter appropriately as it creates the distribution directory. When called on a standalone basis (without a prior complete_build() call), the caller must be in a directory within the distribution tree.

When Base_Dir is not set, this method will look in the current directory for both a 'MANIFEST' file and a 'lib' directory. If neither are found, it will scan upwards in the directory tree for an appropriate directory. Requiring both files prevents mistakenly using either a template directory or a unix root directory. The method will croak if a proper directory cannot be found. The working directory in use prior to the method being called will be restored when the method completes or croaks. Returns a true value if successful.

build_single_method

    $mmtt->build_single_method( $method_name );

Returns a string with a skeleton subroutine for the given $method_name. Used internally, but made available for use in scripts to be called from your favorite editor.

create_template_directory

    $mmtt->create_template_directory( $directory );

Creates the named $directory and populates it with a file for each default template. These can be customized and the directory used in conjunction with the TEMPLATE_DIR configuration options. See CUSTOMIZING TEMPLATES, below. Returns a true value if successful.

process_template

    $mmtt->process_template( $template, \%data, $outputfile );

Calls TT2 to fill in the given template and write it to the output file. Requires a template name, a hash reference of parameters (typically just the $mmtt object itself), and an outputfile (relative to the base distribution directory). If the TEMPLATE_DIR parameter is set, templates will be taken from there, otherwise the default templates are used. Returns a true value if successful.

CUSTOMIZING TEMPLATES

Top

Overview

Use the makeperlmod script to create a directory containing a copy of the default templates. Alternatively, use the create_template_directory method directly. Edit these templates to suit personal taste or style guidelines. Be sure to specify a TEMPLATE_DIR configuration option when making modules or add it to either the ExtUtils::ModuleMaker::Personal::Defaults file or to a makeperlmod config file.

Changes from earlier versions

ExtUtils::ModuleMaker now stores author information in the main object rather than in a separate hash datastructure. This will break old templates.

 # Old
 { 
   AUTHOR => { NAME => "John Doe", EMAIL => "john@doe.org" } 
 }

 # New
 {
   AUTHOR => "John Doe",
   EMAIL  => "john@doe.org",
 }

Customizing with makeperlmod

This can all be done quite easily with makeperlmod. Begin with:

    makeperlmod -d 

This will create a default configuration file and print its location. See the makeperlmod manual for details on creating and using named configuration files.

Next, create a template directory. Choose a location that is appropriate for your operating system. E.g., for unix:

    makeperlmod -t ~/.makeperlmod.templates

Edit the templates as needed. Templates are written with the Template Toolkit to allow for easy user customization of the contents and layout. See the Template module for the full syntax or just examine the default templates for quick changes.

Edit the default configuration file and add a TEMPLATE_DIR parameter. Use whatever directory you chose to hold the templates. Make any other desired edits to AUTHOR, etc. For example:

  TEMPLATE_DIR ~/.makeperlmod.templates
  AUTHOR John Q. Public

Presto! Customization is done. Now start making modules with

    makeperlmod -n My::New::Module

Creating custom template variables (use with caution)

When templates are processed, the entire ExtUtils::ModuleMaker object is passed to the Template Toolkit. Thus any class data is available for use in templates. Users may add custom configuration options ( to new, to the ExtUtils::ModuleMaker::Personal::Defaults file, or to a makeperlmod config file) and use these in custom templates. Be careful not to overwrite any class data needed elsewhere in the module.

Default templates

Templates included are:

    * README
    * Changes
    * Todo
    * Build.PL
    * Makefile.PL
    * Proxy_Makefile.PL
    * MANIFEST.SKIP
    * test.t
    * module.pm
    * method
    * pod.t
    * pod_coverage.t

INSTALLATION

Top

 perl Build.PL
 perl Build
 perl Build test
 perl Build install

REQUIRES

Top

Required for operation

    Config::General
    Data::Dumper
    ExtUtils::ModuleMaker
    Getopt::Long
    Path::Class
    Pod::Usage
    Template

Required for testing

    File::Copy
    File::pushd
    IPC::Run3
    Probe::Perl
    Test::More

Template Toolkit PPM for ActiveState is available from the University of Winnipeg PPM repository:

http://theoryx5.uwinnipeg.ca/ppms/

BUGS

Top

Please report bugs using the CPAN Request Tracker at http://rt.cpan.org/NoAuth/Bugs.html?Dist=ExtUtils-ModuleMaker-TT

When submitting a bug or request, please include a test-file or a patch to an existing test-file that illustrates the bug or desired feature.

AUTHOR

Top

David A Golden (DAGOLDEN)

dagolden@cpan.org

http://dagolden.com/

COPYRIGHT

Top


ExtUtils-ModuleMaker-TT documentation Contained in the ExtUtils-ModuleMaker-TT distribution.
package ExtUtils::ModuleMaker::TT;

$VERSION = "0.93";

use strict;
local $^W = 1;

use Path::Class;
use Template;

# predeclare
our %templates;

sub _get_dir_and_file {
    my $module = shift;
    my @layers      = split( /::/, $module->{NAME} );
    my $file        = pop(@layers) . '.pm';
    my $dir         = join( '/', 'lib', @layers );
    return ($dir, $file);
}

sub build_single_pm {
    my ($self, $module) = @_;
    my $module_object;
    
    if ( ref($module) ) {
        if ( $module == $self) {
            $module_object = $module;
        } else {
            $module_object = { %{$self}, %{$module} };
        }
    } else {
        $module_object = { %{$self}, NAME => $module };
    }
    
    # To support calling this function on a standalone basis, look upwards
    # for a base directory
    # (abs->rel) is a workaround to drop the volume on MSWin32
    my $orig_wd = my $cwd = dir()->absolute;
    my $root_dir = dir(q{})->absolute;
    unless ($self->{Base_Dir}) {
        while (! $cwd->subsumes($root_dir)) {
            chdir $cwd;
            if ( -e 'MANIFEST' and -d 'lib' ) {
                $self->{Base_Dir} = $cwd; 
                last;
            }
            $cwd = $cwd->parent;
        }
        chdir $orig_wd;
        $self->death_message(["Can't locate base directory"]) 
            unless $self->{Base_Dir};
    }    

    my ( $dir, $file ) = _get_dir_and_file( $module_object );
    $self->create_directory( dir($self->{Base_Dir}, $dir ));

    $module_object->{new_method} = $self->build_single_method('new');
    # hack to remove subroutine bit    -- a real new sub is in module.pm template
     $module_object->{new_method} =~ s/sub new {.*}\n//s; 
    # hack to add class name to call new in example
     $module_object->{new_method} =~ s/\$rv = /\$rv = $module_object->{NAME}->/s; 
     
    $self->print_file( 
        file( $dir, $file ), 
        $self->text_pm_file( $module_object ) 
    );

    (my $clean_name = $module_object->{NAME} ) =~ s/::/_/g;
    my $testfile = file( "t", $clean_name . ".t" );
    $self->print_file(
        $testfile,
        $self->text_test( $clean_name, $module_object )
    );

    chdir $orig_wd;
    return 1;
}

sub build_single_method {
    my ($self,$method_name) = @_;
    my $results;
    
    my $tt = ( $self->{'TEMPLATE_DIR'} ? 
        Template->new({'INCLUDE_PATH' => $self->{'TEMPLATE_DIR'} }) :
        Template->new() )
        or $self->death_message([ "Template error: $Template::ERROR" ]);
    my $template_text = $templates{'method'};
    $tt->process( $self->{'TEMPLATE_DIR'} ? 'method' : \$template_text,
                  { %{ $self }, method_name => $method_name }, \$results )
        or $self->death_message([ "Could not write method '$method_name': " 
            . $tt->error()  ]);
    return $results;    
}


#--------------------------------------------------------------------------#
# subclassing ExtUtils::ModuleMaker::StandardText
#--------------------------------------------------------------------------#


sub text_README {
    my $self = shift;
    return $self->process_template( 'README', $self );
}

sub text_Todo {
    my $self = shift;
    return $self->process_template( 'Todo', $self );
}

sub text_Changes {
    my $self = shift;
    return $self->process_template( 'Changes', $self );
}

sub text_test {
    my ( $self, $test_filename, $module_data ) = @_;
    return $self->process_template( 'test.t', $module_data );
}

sub text_test_multi {
    my ( $self, $testfilename, $pmfilesref ) = @_;
    my @pmfiles = @{$pmfilesref};
    return $self->process_template( 'test.t', $self );
}

sub text_Makefile {
    my $self = shift;
    return $self->process_template( 'Makefile.PL', $self );
}


sub text_Buildfile {
    my $self = shift;
    return $self->process_template( 'Build.PL', $self );
}

sub text_proxy_makefile {
    my $self = shift;
    return $self->process_template( 'Proxy_Makefile.PL', $self );
}

sub text_MANIFEST_SKIP {
    my $self = shift;
    return $self->process_template( 'MANIFEST.SKIP', $self );
}

sub text_pod_coverage_test {
    my $self = shift;
    return $self->process_template( 'pod_coverage.t', $self );
}

sub text_pod_test {
    my $self = shift;
    return $self->process_template( 'pod.t', $self );
}

sub text_pm_file {
    my $self = shift;
    my $module_data = shift;
      
    return $self->process_template( 'module.pm', $module_data );
}

#--------------------------------------------------------------------------#
# Template handling
#--------------------------------------------------------------------------#


sub create_template_directory {
    my ($class, $dir) = @_;
    dir($dir)->mkpath;
    for my $template ( keys %templates ) {
        my $target = dir($dir, $template);
        open (FILE, ">", $target) or croak ("Could not write '$target', $!");
        print FILE ( $templates{$template} );
        close FILE;
    }
    return 1;
}

sub process_template {
    my ($self, $template, $data) = @_;
    my $tt = ( $self->{'TEMPLATE_DIR'} ? 
        Template->new({'INCLUDE_PATH' => $self->{'TEMPLATE_DIR'} }) :
        Template->new() )
        or $self->death_message([ "Template error: $Template::ERROR" ]);
    my $template_text = $templates{$template};
    my $output_text;
    $tt->process( $self->{'TEMPLATE_DIR'} ? $template : \$template_text,
                  $data, \$output_text )
        or $self->death_message([ 
            "Could not generate $template contents: " . $tt->error() 
        ]);
    return $output_text;
}

#-------------------------------------------------------------------------#
    
    $templates{'README'} = <<'EOF';
If this is still here it means the programmer was too lazy to create the readme file.

You can create it now by using the command shown below from this directory:

pod2text [%  NAME %] > README

At the very least you should be able to use this set of instructions
to install the module...

[%- IF BUILD_SYSTEM == 'ExtUtils::MakeMaker' -%]
perl Makefile.PL
make
make test
make install
[%- ELSE -%]
perl Build.PL
perl Build
perl Build test
perl Build install
[%- END -%]

If you are on a windows box you should use 'nmake' rather than 'make'.
EOF

#-------------------------------------------------------------------------#
    
    $templates{'Changes'} = <<'EOF';
Revision history for Perl module [%  NAME %]

[%  VERSION %] [% timestamp %]
    - original skeleton created with ExtUtils::ModuleMaker::TT
EOF
    
    $templates{'Todo'} = <<'EOF';
TODO list for Perl module [%  NAME %]

- Nothing yet

EOF
    
#-------------------------------------------------------------------------#
    
    $templates{'Build.PL'} = <<'EOF';
use Module::Build;
# See perldoc Module::Build for details of how this works

Module::Build->new( 
    module_name         => '[%  NAME %]',
    dist_author         => '[%  AUTHOR %] <[% EMAIL %]>',
[%- IF LICENSE.match('perl|gpl|artistic') %]
    license             => '[%  LICENSE %]',
[%- END %]
    create_readme       => 1,
    create_makefile_pl  => 'traditional',
    requires        => {
        # module requirements here
    },
    build_requires  => { 
        Test::Simple => 0.44,
    },
)->create_build_script;
EOF

#-------------------------------------------------------------------------#
    
    $templates{'Makefile.PL'} = <<'EOF';
use ExtUtils::MakeMaker;
# See lib/ExtUtils/MakeMaker.pm for details of how to influence
# the contents of the Makefile that is written.
WriteMakefile(
    NAME         => '[%  NAME %]',
    VERSION_FROM => '[%  FILE %]', # finds $VERSION
    AUTHOR       => '[%  AUTHOR %] ([%  EMAIL %])',
    ABSTRACT     => '[% ABSTRACT %]',
    PREREQ_PM    => {
                     'Test::Simple' => 0.44,
                    },
);
EOF

#-------------------------------------------------------------------------#
    
    $templates{'Proxy_Makefile.PL'} = <<'EOF';
unless (eval "use Module::Build::Compat 0.02; 1" ) {
  print "This module requires Module::Build to install itself.\n";

  require ExtUtils::MakeMaker;
  my $yn = ExtUtils::MakeMaker::prompt
    ('  Install Module::Build from CPAN?', 'y');

  if ($yn =~ /^y/i) {
    require Cwd;
    require File::Spec;
    require CPAN;

    # Save this 'cause CPAN will chdir all over the place.
    my $cwd = Cwd::cwd();
    my $makefile = File::Spec->rel2abs($0);

    CPAN::Shell->install('Module::Build::Compat');

    chdir $cwd or die "Cannot chdir() back to $cwd: $!";
    exec $^X, $makefile, @ARGV;  # Redo now that we have Module::Build
  } else {
    warn " *** Cannot install without Module::Build.  Exiting ...\n";
    exit 1;
  }
}
Module::Build::Compat->run_build_pl(args => \@ARGV);
Module::Build::Compat->write_makefile();

EOF

#-------------------------------------------------------------------------#
    
    $templates{'MANIFEST.SKIP'} = <<'EOF';
# Version control files and dirs.
\bRCS\b
\bCVS\b
,v$
.svn/

# ExtUtils::MakeMaker generated files and dirs.
^MANIFEST\.(?!SKIP)
^Makefile$
^blib/
^blibdirs$
^PM_to_blib$
^MakeMaker-\d
                                                                                                                    
# Module::Build
^Build$
^_build

# Temp, old, vi and emacs files.
~$
\.old$
^#.*#$
^\.#
\.swp$
\.bak$
EOF

#-------------------------------------------------------------------------#
    
    $templates{'test.t'} = <<'EOF';
# [%  NAME %] - check module loading and create testing directory

use Test::More tests => [% IF NEED_NEW_METHOD %] 2 [% ELSE %] 1 [% END %];

BEGIN { use_ok( '[%  NAME %]' ); }
[% IF NEED_NEW_METHOD %]
my $object = [%  NAME %]->new ();
isa_ok ($object, '[%  NAME %]');
[%- END %]
EOF

#-------------------------------------------------------------------------#
    
    $templates{'module.pm'} = <<'EOF';
package [%  NAME %];
use strict;
use warnings;
use Carp;

BEGIN {
    use Exporter ();
    use vars qw ($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
    $VERSION     = "0.92";
    @ISA         = qw (Exporter);
    @EXPORT      = qw ();
    @EXPORT_OK   = qw ();
    %EXPORT_TAGS = ();
}

[%- IF NEED_POD %]
#--------------------------------------------------------------------------#
# main pod documentation 
#--------------------------------------------------------------------------#

# Below is the stub of documentation for your module. You better edit it!

!=head1 NAME

[%  NAME %] - Put abstract here 

!=head1 SYNOPSIS

 use [%  NAME %];
 blah blah blah

!=head1 DESCRIPTION

Description...

!=head1 USAGE

Usage...

!=cut

[% END %]
[%- IF NEED_NEW_METHOD -%]
[% new_method -%]
sub new {
    my ($class, $parameters) = @_;
    
    croak "new() can't be invoked on an object"
        if ref($class);
        
    my $self = bless ({ }, $class);
    return $self;
}
[% END %]
1; #this line is important and will help the module return a true value
__END__
[% IF NEED_POD %]
[%- IF CHANGES_IN_POD -%]
!=head1 HISTORY
[% END %]
!=head1 BUGS

Please report bugs using the CPAN Request Tracker at L<http://rt.cpan.org/>

!=head1 AUTHOR

[% AUTHOR %] [% IF CPANID %]([% CPANID %])[% END %]
[%- IF ORGANIZATION %]

[% ORGANIZATION %]
[%- END %]

[% EMAIL %]

[% WEBSITE %]

!=head1 COPYRIGHT

Copyright (c) [% COPYRIGHT_YEAR %] by [% AUTHOR %]

[%  LicenseParts.COPYRIGHT %]

!=cut
[%- END -%]
EOF

#-------------------------------------------------------------------------#
    
$templates{'method'} = <<'EOF';
#--------------------------------------------------------------------------#
# [% method_name %]()
#--------------------------------------------------------------------------#

[% IF NEED_POD -%]
!=head2 [% method_name %]

 $rv = [% method_name %]();

Description of [% method_name %]...

!=cut
[%- END %]

sub [% method_name %] {
[% IF NEED_NEW_METHOD -%]
    my ($self) = @_;
[% END -%]

}

EOF

#--------------------------------------------------------------------------#

$templates{'pod_coverage.t'} = <<'END_OF_POD_COVERAGE_TEST';
#!perl -T

use Test::More;

eval "use Test::Pod::Coverage 1.06";
plan skip_all => "Test::Pod::Coverage 1.06 required for testing POD coverage"
    if $@;

eval "use Pod::Coverage 0.17";
plan skip_all => "Pod::Coverage 0.17 required for testing POD coverage"
    if $@;

all_pod_coverage_ok();
END_OF_POD_COVERAGE_TEST

#----------------------------------------------------------------------#
 
$templates{'pod.t'} = <<'END_OF_POD_TEST';
#!perl -T

use Test::More;
eval "use Test::Pod 1.14";
plan skip_all => "Test::Pod 1.14 required for testing POD" if $@;
all_pod_files_ok();
END_OF_POD_TEST

#--------------------------------------------------------------------------#
# cleanup escaped pod in templates
#--------------------------------------------------------------------------#

for (values %templates) {
    s/^!=([a-z])/=$1/gxms;
}

#--------------------------------------------------------------------------#

1; #this line is important and will help the module return a true value
__END__