#!perl
use strict;
use warnings;
use OptArgs2;

our $VERSION = '1.0.0';

cmd 'App::githook::perltidy' => (
    name    => 'githook-perltidy',
    comment => 'tidy perl and pod files before Git commits',
    optargs => [
        command => {
            isa      => 'SubCmd',
            comment  => '',
            required => 1,
        },
        verbose => {
            isa     => '--Flag',
            comment => 'be explicit about underlying actions',
            alias   => 'v',
            default => sub { $ENV{GITHOOK_PERLTIDY_VERBOSE} },
        },
        version => {
            isa     => '--Flag',
            comment => 'print version and exit',
            alias   => 'V',
            trigger => sub {
                require File::Basename;
                die File::Basename::basename($0)
                  . ' version '
                  . $VERSION . "\n";
            },
        },
    ],
);

subcmd 'App::githook::perltidy::install' => (
    comment => 'install a Git pre-commit hook',
    optargs => [
        absolute => {
            isa     => '--Flag',
            comment => 'Use full path to script in hook',
            alias   => 'a',
        },
        force => {
            isa     => '--Flag',
            comment => 'Overwrite existing git commit hooks',
            alias   => 'f',
        },
    ],
);

subcmd 'App::githook::perltidy::pre_commit' =>
  ( comment => 'tidy Perl and POD files in the Git index', );

my ( $class, $opts, $file ) = class_optargs('App::githook::perltidy');
require $file;
$class->new($opts)->run;

1;
__END__

=head1 NAME

githook-perltidy - run perltidy before Git commits

=head1 VERSION

1.0.0 (2022-10-14)

=head1 SYNOPSIS

    githook-perltidy COMMAND [OPTIONS...]

=head1 DESCRIPTION

B<githook-perltidy> is a script designed to run from a Git pre-commit
hook. It ensures that your Perl files are always cleanly commited with
L<Perl::Tidy> (or L<Perl::Tidy::Sweetened>). The script can also call
L<Perl::Critic> and L<Pod::Tidy> if you want.

This script is is efficient: it only modifies files that are being
committed and not every file in your repository. It also tries its
hardest to be safe: tidying is performed in a temporary location so
that your own working files are not left in a bad state in the event of
failure.

=head2 Repository Setup

Before you can use B<githook-perltidy> you need to make sure everyone
working on your code uses the the same L<Perl::Tidy> and (probably)
L<Pod::Tidy> options:

    $ perltidy -b -w -dop | grep -v dump-options > .perltidyrc
    $ echo '--columns 72' > .podtidy-opts
    $ echo '^\.perltidyrc' >> MANIFEST.SKIP
    $ echo '^\.podtidy-opts' >> MANIFEST.SKIP
    $ git add .perltidyrc .podtidy-opts MANIFEST.SKIP
    $ git commit -m 'githook-perltidy support' && git push

You should also add L<App::githook::perltidy> as an explicit "develop"
dependency in your F<cpanfile>, F<Makefile.PL> or F<Build.PL>, so that
B<githook-perltidy> gets installed when developers install the rest of
your project's dependencies.

	# For example in your cpanfile:
	on develop => sub {
		requires 'App::githook::perltidy' => 0;
	};

=head3 Sweeter Tidying

You may prefer to tidy with L<Perl::Tidy::Sweetened> instead of plain
L<Perl::Tidy>. To enable that you commit a F<.perltidyrc.sweetened>
file instead of F<.perltidyrc>.  If you use this feature you will want
to add L<Perl::Tidy::Sweetened> as an explicit "develop" dependency in
your F<cpanfile>, F<Makefile.PL> or F<Build.PL>.

=head3 Critical Checks

You may additionally wish to have L<Perl::Critic> run against your
commits.  To enable that you simply commit a F<.perlcriticrc> file to
the repository. If you use this feature you will want to add
L<Perl::Critic> as an explicit "develop" dependency in your
F<cpanfile>, F<Makefile.PL> or F<Build.PL>.

=head3 README from POD

B<githook-perltidy> also has an automatic README-from-POD feature. To
enable it you create and commit a file called F<.readme_from>
containing the name of the POD source file:

    $ echo 'lib/Your/App.pm' > .readme_from
    $ echo '^\.readme_from' >> MANIFEST.SKIP
    $ git add .readme_from MANIFEST.SKIP
    $ git commit -m 'githook-perltidy readme_from' && git push

With the above in place the F<README> file will be updated (and
potentially committed) whenever F<lib/Your/App.pm> is committed.

=head2 githook-perltidy install [--force, -f] [--absolute, -a]

Anyone making commits in your repository should ensure that
B<githook-perltidy> runs before the Git commit completes.  The
C<install> command is used to create a F<pre-commit> file in the
F<$GIT_DIR/hooks/> directory. It must be run from the top-level
directory of your repository.

    $ githook-perltidy install
    $ cat .git/hooks/pre-commit
	#!/bin/sh
	if [ "$NO_GITHOOK_PERLTIDY" != "1" ]; then
		PERL5LIB="" githook-perltidy pre-commit
	fi

The install command fails if there is no F<.perltidyrc> or
F<.perltidyrc.sweetened> file in the repository or if the hooks
directory isn't found. It will also fail if the Git F<pre-commit> file
already exists, unless C<--force> is used to replace it.

By default the hook finds B<githook-perltidy> via C<$PATH>. If regular
changes to your PATH (e.g. due to perlbrew, local::lib, etc) break
that, you I<may> wish to do an C<--absolute> install instead to use the
full path. However, be aware that upgrading your system perl and/or
B<githook-perltidy> might invalidate that, requiring you to reinstall
the hook to make it work again. Ideally you would install
B<githook-perltidy> in a system-wide location (e.g. /usr/local/bin)
that doesn't change and does not depend on particular PERL5LIB.

=head2 githook-perltidy pre-commit

The C<pre-commit> command loops through the Git index, checking out
files to a temporary working directory. Then on each file that looks
like a Perl or Pod file it:

=over

=item * Runs L<perlcritic> if F<.perlcriticrc> exists (for a Perl file)

=item * Runs L<perltidy> (or L<perltidy-sweet>) (for a Perl file)

=item * Runs L<podtidy> if F<.podtidy-opts> exists (for a Perl or Pod file)

=item * Updates the Git index with the tidied file.

=item * Creates a new F<README> file using L<Pod::Text> if the tidied
file matches F<.readme_from>. The F<README> file gets committed if it
is already being tracked by Git.

=item * Runs L<perltidy> and/or L<podtidy> on your working
tree file. This prevents C<git diff> from displaying an eroneous diff.

=back

Any error stops the script (and therefore the commit) immediately. Any
successful cleanups to the index and working tree up until that point
remain in place.

This command fails if there is no F<.perltidyrc> or
F<.perltidyrc.sweetened> file in the repository.

=head1 GLOBAL OPTIONS

=over

=item --help, -h

Print the full usage message and exit.

=item --verbose, -v

Print underlying Git commands or filesystem actions as they are run.

=item --version, -V

Print the version and exit.

=back

=head1 CAVEATS

There are two ways in which B<githook-perltidy> behaviour may affect
your existing workflow.

=over

=item * If you are accustomed to commiting
changes to files which are still open in your editor, your editor may
complain that the underlying file has changed on disk. Possibily your
editor doesn't even detect the change and your next write will not be
'tidy'.

=item * Aborting a commit with an empty commit message or via a later
command in the pre-commit hook will still result in changed (tidied)
files on disk and in the index.

=back

Previous versions of B<githook-perltidy> made use of a Git post-commit
hook. If that hook is still in place you will receive an usage error
message after you commit. The post-commit call to B<githook-perltidy>
(or possibly even the entire hook) can be removed.

=head1 FILES

=over

=item F<.perltidyrc>

L<Perl::Tidy> options file.

=item F<.perltidyrc.sweetened>

L<Perl::Tidy::Sweetened> options file. Conflicts with F<.perltidyrc>.

=item F<.podtidy-opts>

L<Pod::Tidy> options file. This is B<githook-perltidy> specific.

=item F<.perlcriticrc>

L<Perl::Critic> options file.

=item F<.readme_from>

Contains name of POD file to convert to a text README file. This is
B<githook-perltidy> specific.

=back

=head1 ENVIRONMENT

=over

=item NO_GITHOOK_PERLTIDY

Setting this to 1 makes the C<githook-perltidy pre-commit> command a
no-op. Useful if you want to make a non-tidy commit.

=back

=head1 SUPPORT

This tool is managed via github:

    https://github.com/mlawren/githook-perltidy

=head1 SEE ALSO

L<githooks>(5), L<perltidy>(1), L<podtidy>(1), L<perlcritic>(1)

=head1 AUTHOR

Mark Lawrence E<lt>nomad@null.netE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright 2011-2022 Mark Lawrence <nomad@null.net>

This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.


