The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
# ABSTRACT: Syntactic sugar for a script's main routine
use strict;
use warnings;

package Devel::Main;

our $VERSION = 0.005;

sub import {
    my $class  = shift;
    @_ = 'main' unless @_;
    
    my $opts   = ref($_[0]) ? shift : {};
    my $caller = $opts->{into} // scalar(caller($opts->{into_level} // 0));
    
    while (@_) {
        my $name = shift;
        my $args = ref($_[0]) ? shift : {};
        my $gen  = $class->can("$name\_generator")
            or do { require Carp; Carp::croak("$class does not export sub '$name'") };
        my $code = $gen->( $class, $name, $args );
        
        my $as = join '', grep defined, (
            $args->{ -prefix },
            ($args->{ -as } // $name),
            $args->{ -suffix },
        );
        
        no strict 'refs';
        *{"$caller\::$as"} = $code;
    }
}

sub main_generator {
    my ( $class, $name, $args ) = @_;

    my $run_sub_name = $args->{'run_sub_name'} // "run_$name";
    my $exit         = $args->{'exit'}         // 1;

    return sub (&) {
        my ($main_sub) = @_;

        # If we're called from a script, run main and exit
        if ( !defined caller(1) ) {
            $main_sub->();
            exit(0) if $exit;
        }

        # Otherwise, create a sub that turns its arguments into @ARGV
        else {
            no strict 'refs';
            my $package = caller;
            *{"${package}::$run_sub_name"} = sub {
                local @ARGV = @_;
                return $main_sub->();
            };

            # Return 1 to make the script pass 'require'
            return 1;
        }
    };
}

1;

__END__

=pod

=head1 NAME

Devel::Main - Syntactic sugar for a script's main routine

=head1 VERSION

version 0.005

=head1 SYNOPSIS

  use Devel::Main 'main';
  
  main {
    # Your main routine goes here
  };

=head1 DESCRIPTION

This module provides a clean way of specifying your script's main routine.

=head1 METHODS

=head2 main()

Declares your script's main routine. Exits when done.

If, instead of executing your script, you load it with C<use> or C<require>, C<main> creates a subroutine named C<run_main> in the current package. You can then call this subroutine to run your main routine. Arguments passed to this subroutine will override C<@ARGV>.

Example:

  require './my_script.pl';
  
  run_main( 'foo' );  # Calls the main routine with @ARGV = ('foo')

If you alias the 'main' routine to another name, the "run" method will also be aliased. For example, if 'my_script.pl' had said:

  use Devel::Main main => { -as => 'primary' };
  
  primary {
    # Main code here
  };

then the installed subroutine would be called 'run_primary'.

You can also control whether or not the script exits after the main routine via the import parameter 'exit'.

   use Devel::Main 'main' => { 'exit' => 0 };
   
   main {
     # Main routine
   };
   print "Still running\n";

Finally, you can change the name of the subroutine to call the main routine via the import parameter 'run_sub_name'.

   # In 'my_script.pl'
   use Devel::Main 'main' => { 'run_sub_name' => 'run_the_main_routine' };
   
   # In other (test?) script
   require './my_script.pl';
   
   run_the_main_routine('bar'); # Calls the main routine with @ARGV = ('bar');

=head1 METHODS

=head1 CREDITS

This module was inspired by brian d foy's article
L<Five Ways to Improve Your Perl Programming|http://www.onlamp.com/2007/04/12/five-ways-to-improve-your-perl-programming.html>.

=head1 AUTHOR

Stephen Nelson <stephenenelson@mac.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2013 by Stephen Nelson.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut