#!/usr/bin/perl
# $Id: dont-guess-benchmark-it.pl 161 2009-07-22 21:03:25Z whynot $
# Copyright 2009 Eric Pozharski <whynot@pozharski.name>
# AS-IS/NO-WARRANTY/HOPE-TO-BE-USEFUL -- GNU GPLv3

package main;

=head1 NAME

dont-guess-benchmark-it - generator of perl code snippets benchmarks

=head1 README

Generate your I<-MBenchmark> code quickly.
That's generator -- it doesn't benchmark itself.

=head1 USAGE

    # that will request running each snippet for 250sec
    dont-guess-benchmark-it.pl --time 250
    # that will request running each snippet twice
    dont-guess-benchmark-it.pl --count 2
    # I<--time> and I<--count> are mutually exclusive

    # that flag places some code before B<cmpthese()> invocation
    dont-guess-benchmark-it.pl --preamble
    # that flag places some data in I<__DATA__> section,
    # but before I<__END__>
    dont-guess-benchmark-it.pl --postamble

    # that disables invoking B<perl>, only dumps ready to run code
    dont-guess-benchmark-it.pl --dump

=head1 DESCRIPTION

As requested by certain authorites, Perl developer shouldn't guess
("citation needed" (TM)).
While I believe that quote has much wider scope, B<dont-guess-benchmark-it.pl>
(hereafter -- B<dgbi>) concentrates only on L<Benchmark.pm>.
B<dgbi> by itself doesn't run any tests -- it's a plain generator of B<perl>
code.
The generated code must be in L<strict.pm> and L<warnings.pm> domain --
that's implied explicitly in the generated code.
Providing that B<strict>E<sol>B<warnings> clean code is your responcibility.
If you need funny tricks originated from B<Perl4> then you've picked wrong
tool.
Although, B<taint> checks aren't enabled.

B<dgbi> operates like this:

=over

=item *

After parsing the I<@ARGV> B<dgbi> knows what sections are supposed to be in an
input.

=item *

The sections are read from I<*STDIN> linewise.
If there would be more than one section, then they are separated by empty lines
(line of spaces (I<qrZ<>E<sol>\s+E<sol>>) is empty line anyway).

=item *

First could come a B<preamble> section (if enabled, L</I<--preamble>>).
That code will be put as-is between C<use Benchmark;> and B<cmpthese()>
invocation.

=item *

Then B<snippet> section comes.
Each line will be wrapped in something like this:

    code00 => sub { 'here is your line, quotes omitted' },

and passed in anonymous HASH to B<timethese()>.

(B<bug>)
No support for multiline snippets.

(B<bug>)
Editing capabilities are provided by I<$ENV{SHELL}>.

(B<bug>)
There should be a way to configure andZ<>E<sol>or set snippet names at
run-time.

While snippet section is enabled by default
(what would be worth benchmark of no code?)
B<dgbi> itself doesn't check for it presence.
If you omit snippet section, B<dgbi> will generate empty hash, and
L</DIAGNOSTICS> has more.

=item *

Then B<postamble> comes (if enabled, L</I<--postamble>>).
Those lines will be treated as data, but code (while linewise anyway) and will
be put in I<__DATA__> section of the generated code.
(If postamble wasn't requested, then I<__DATA__> section isn't generated.)

(B<caveat>)
If you request postamble while omitting any input, then empty I<__DATA__>
section is generated.

=item *

Then just generated code is dumped on STDOUT, and (unless L</I<--dump>> was
requested) the same code is passed to B<perl> interpreter.

=item *

(B<caveat>)
In the generated code there will be terminating C<__END__> line.
L<perlrun/DESCRIPTION> has more.

=back

=cut 

use strict;
use warnings;
#use version 0.50;
use Getopt::Long qw| :config bundling |;

my $VERSION = 0.000_007;

=head1 PREREQUISITES

Getopt::Long

=head1 DEPENDENCIES

=over

=item B<Getopt::Long>

Command-line parsing.
Features in use are C<bundling> and arg-type checks.
Thus, if I've got F<CHANGES> correctly, I<2.24> is required.
I<2.37> works for me.

=item B<Benchmark>

That's not required.
But the generated code won't compile without it.
That seems, that B<Benchmark.pm> of September 1999 is OK (unknown version).
I<1.1> works for me.

=back

=cut

#=head1 OSNAMES

#=head1 INCOMPATIBILITIES

my %opts;
GetOptions \%opts, qw[
  help|h! version|v!
  dump|d!
  preamble|p! postamble|P!
  time|t=i count|c=i ] or
  die q|parsing command-line failed|;

if( $opts{help} )       {
    print <<'END_OF_HELP';
input:
  optional preamble and separating empty line
  supposed snippets
  optional separating empty line and postamble
    REMEMBER: close input when you've finished

options (don't use those 2 simultaneously):
    --time=INTEGER   how long to loop (secs)
    -t INTEGER
    --count=INTEGER  how many time to loop (count)
    -c INTEGER

other options:
    --dump           only dump code, don't run interpreter
    -d
    --preamble       enable preamble part
    -p
    --postamble      enable postamble part
    -P
    --help           obvious
    -h
    --version        obvious
    -v
END_OF_HELP
    exit 0;              }
elsif( $opts{version} ) {
    printf <<'END_OF_VERSION',
%s --
                 version: %f
                 license: %s
  generated code license: %s
END_OF_VERSION
    ( split m{/}, $0 )[-1], $VERSION, q|GNU GPLv3|, q|at user's option|;
    exit 0;              };

$opts{time} && $opts{count} and
  die qq|--time and --count are mutually exclusive\n|;

=head1 ARGUMENTS

The only (somewhat) required argument is any input.
While it's supposed to come from I<*STDIN>, B<dgbi> reads from I<*ARGV>, thus
that's possible to call it this way

    dont-guess-benchmark-it.pl -p 'my $x;' '' '$x++' '++$x'

or this (puzzled)

    dont-guess-benchmark-it.pl /dev/urandom

=head1 OPTIONS

=over

=item I<--count>

=item I<-c>

Takes one required argument -- an integer decimal.
Sets number of loops for B<timethese()> of B<Benchmark>.
It's mutually exclusive with L</I<--time>>.
L</I<--time> and I<--count>> has more.

=item I<--dump>

=item I<-d>

That flag (disabled by default) turns off pipeing B<perl> interpreter.
Thus only dumping the generated code happens.

=item I<--help>

=item I<-h>

Obvious.

=item I<--postamble>

=item I<-P>

Enables postamble section.
L</DESCRIPTION> has more.

=item I<--preamble>

=item I<-p>

Enables preamble section.
L</DESCRIPTION> has more.

=item I<--time>

=item I<-t>

Takes one required argument -- an integer decimal.
Sets number of seconds to loop each snippet
(passed to B<timethese()> of B<Benchmark>).
It's mutually exclusive with L</I<--count>>.
L</I<--time> and I<--count>> has more.

=item I<--version>

=item I<-v>

Obvious.

=back

=cut

my $empty_line = qr{^\s*$};

my $output = <<'END_OF_INPUT';
#!/usr/bin/perl

use strict;
use warnings;
use Benchmark qw{ cmpthese timethese };

END_OF_INPUT

if( $opts{preamble} )         {
    while( my $code = <> ) {
        $output .= $code;
        $code =~ m{$empty_line}o and
          last;             }; };

$output .=
  sprintf qq|cmpthese timethese %i, {\n|,
    $opts{count} || ($opts{time} && -$opts{time}) || -5;

my $code_base = ($. || 1) + ($opts{preamble} ? 1 : 0);
while( my $code = <> )       {
    chomp $code;
    $code =~ m{$empty_line}o and
      last;
    $output .= sprintf qq|  code%02i => sub { %s },\n|,
      $. - $code_base, $code; };

$output .= qq|};\n\n|;

if( $opts{postamble} )        {
    $output .= qq|__DATA__\n|;
    while( my $code = <> ) {
        $output .= $code;
        $code =~ m{$empty_line}o and
          last;             }; };

$output .= qq|__END__\n|;

unless( $opts{dump} )               {
    print $output;
    open STDOUT, q{|-}, qw| /usr/bin/perl | or
      die qq|can't fork (perl): $!|; };

print $output;

close STDOUT or
  die qq|can't close (STDOUT): $!|;

=head1 DIAGNOSTICS

=over

=item C<--time and --count are mutually exclusive>

You can't set them both.
B<dgbi> doesn't know what to choose.

=item C<can't close (STDOUT): %s>

The main reason would be that a process on the other side of pipe
(mostly B<perl>)
already died.
(BTW, in that case there's no explanation after colon, for me.)
(Me wonders, isn't B<dgbi> supposed to be killed with C<SIGPIPE> in this case?)
The main reason would be that just generated code fails F<strict.pm> or
F<warnings.pm>.
(That means, that's your fault!)

=item C<can't fork (perl): %s>

Obvious.

=item C<parsing command-line failed>

B<GetOptions()> of L<Getopt::Long> returned false.
Any reasons are supposed to come before.

=back

=head1 NOTES

=over

=item bugs and caveats

All (?) mentions are spread within this POD.

=item I<--time> and I<--count>

The values for these should be positive integers.
Because B<Getopt::Long> doesn't provide such restriction internally and such
extra check inside of B<dgbi> would void any use of internal of B<G::L> check,
any integer is accepted.
Thus, those pairs are each technically equivalent:

    dont-gusess-benchmark-it.pl --count 5
    dont-guesss-benchmark-it.pl --time -5

and

    dont-guess-benchmark-it.pl --time 5
    dont-guess-benchmark-it.pl --count -5

=back

=head1 SCRIPT CATEGORIES

Educational/ComputerScience

=head1 AUTHOR

Eric Pozharski, E<lt>whynot@cpan.orgZ<>E<gt>

=head1 COPYRIGHT & LICENSE

Copyright 2009 by Eric Pozharski

This utility is free in sense: AS-IS, NO-WARRANTY, HOPE-TO-BE-USEFUL.
This utility is released under GNU GPLv3.
The License of the Generated Code is at option of the User.
((B<bug>?) Should it be GNU APLv3?)

=cut

# vim: set filetype=perl