package Git::MoreHooks::ScanCtags;

use strict;
use warnings;

use 5.030;

# ABSTRACT: GitHooks plugin to create or update ctags after commit or merge

our $VERSION = '0.019'; # VERSION: generated by DZP::OurPkgVersion

use utf8;
use Carp     qw( croak );
use Log::Any qw( $log );
use Git::Hooks 4.0;
use Git::Repository::Log;
use File::Find   qw(find);
use List::Util   qw( any );
use Module::Load qw{ load };
use Path::Tiny   qw( path );

my $PKG = __PACKAGE__;
my $CFG = __PACKAGE__ =~ s/.*::/githooks./rmsx;

#############
# Grok hook configuration, check it and set defaults.

sub _setup_config {
    my ($git) = @_;

    my $config = $git->get_config();

    # Place default values where values are missing
    $config->{ lc $CFG }->{tags}       //= ['tags'];
    $config->{ lc $CFG }->{dir}        //= [qw( lib bin t )];
    $config->{ lc $CFG }->{suffix}     //= [qw( pm pl t )];
    $config->{ lc $CFG }->{executable} //= [1];
    $config->{ lc $CFG }->{verbose}    //= [1];

    return;
}

##########

sub update_tags_file {
    my ( $git, $prev_head_ref, $new_head_ref, $is_branch_checkout ) = @_;

    _setup_config($git);

    my $errors = 0;
    eval {
        require Perl::Tags::Naive::Moose;
        require Perl::Tags::Naive::Lib;
        require Perl::Tags::PPI;
        require Perl::Tags::Hybrid;
        1;
    } or do {
        $log->errorf( $PKG . '::update_tags_file(): Cannot load Perl::Tags modules' );
        $git->fault( 'Cannot load Perl::Tags module', { prefix => $PKG, } );
        ++$errors;
        return $errors;
    };

    my %args = (
        max_level    => 1,    # Only tag the file, do not read any dependencies.
        do_variables => 1,
        exts         => 1,
    );

    my @taggers = ( Perl::Tags::Naive::Lib->new(%args), Perl::Tags::Naive::Moose->new(%args), Perl::Tags::PPI->new(%args), );

    my $ptag = Perl::Tags::Hybrid->new( %args, taggers => \@taggers );

    my $outfile          = $git->get_config( $CFG => 'tags' );
    my @dirs             = $git->get_config( $CFG => 'dir' );
    my @file_suffices    = $git->get_config( $CFG => 'suffix' );
    my $executable_files = $git->get_config( $CFG => 'executable' );
    $log->debugf( 'cfg: outfile:%s, dirs:%s, suffices:%s, executable:%s', $outfile, \@dirs, \@file_suffices, $executable_files );

    my @files;
    find(
        {
            follow   => 1,
            no_chdir => 1,
            wanted   => sub {
                if ( $executable_files && -f "$File::Find::name" ) {
                    if ( -x $File::Find::name ) {
                        push @files, $File::Find::name;
                        $log->debugf( 'Take tags from file: %s', $File::Find::name );
                    }
                    elsif ( $File::Find::name =~ m/[.] ( [[:alnum:]]{1,}) $/msx
                        && any { $_ eq ( $File::Find::name =~ m/[.] ( [[:alnum:]]{1,}) $/msx )[0] } @file_suffices )
                    {
                        push @files, $File::Find::name;
                        $log->debugf( 'Take tags from file: %s', $File::Find::name );
                    }
                }
            },
        },
        @dirs,
    );
    $log->info('Process tags ...');
    $ptag->process(
        files   => \@files,
        refresh => 1,
    );

    $log->info('Saving tags ...');
    $log->infof( 'Total %s tags in %s files.', scalar %{ $ptag->{tags} }, scalar %{ $ptag->{seen} } );

    $ptag->output( outfile => $outfile );
    if ( $git->get_config( $CFG => 'verbose' ) ) {
        say sprintf '[%s] Discovered total %s tags in %s files. Written to file \'%s\'.',
          __PACKAGE__, scalar %{ $ptag->{tags} }, scalar %{ $ptag->{seen} }, $outfile
          or croak 'Cannot say';
    }

    return;
}

# Install hooks

POST_CHECKOUT( \&update_tags_file );
POST_MERGE( \&update_tags_file );
POST_APPLYPATCH( \&update_tags_file );
POST_COMMIT( \&update_tags_file );

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

Git::MoreHooks::ScanCtags - GitHooks plugin to create or update ctags after commit or merge

=head1 VERSION

version 0.019

=head1 SYNOPSIS

As a C<Git::Hooks> plugin you don't use this Perl module directly. Instead, you
may configure it in a Git configuration file like this:

  [githooks]

    # Enable the plugin
    plugin = Git::MoreHooks::ScanCtags

  [githooks "scanctags"]
    # Default values underneath:

    # Outfile (tags) name
    tags = tags

    # Directories to scan
    dir = lib
    dir = bin
    dir = t

    # File types (by suffix) to scan (in above dirs)
    suffix = pm
    suffix = pl
    suffix = t

    # Scan all executable files (in above dirs)
    executable = 1

    # verbose
    verbose = 1

=head1 DESCRIPTION

This L<Git::Hooks> plugin hooks itself to the hooks below to perform
post-commit and post-merge actions.

=over

=item * B<post-commit>, B<post-applypatch>, B<post-checkout>, B<post-merge>

=back

Keep tags file updated and in sync with committed code.

To enable this plugin you should add it to the githooks.plugin configuration
option:

    [githooks]
      plugin = Git::MoreHooks::ScanCtags

=for Pod::Coverage update_tags_file

=head1 NAME

Git::MoreHooks::CreateTags - Git::Hooks plugin to create or update tags file

=head1 CONFIGURATION

The plugin is configured by the following git options under the
C<githooks.scanctags> subsection.

It can be disabled for specific references via the C<githooks.ref> and
C<githooks.noref> options about which you can read in the L<Git::Hooks>
documentation.

=head2 dir

Directories to scan.

=head2 suffix

Scan files with these file endings in the directories above.

=head2 executable BOOL, default: 1

Scan every executable file for tags in the directories above.

=head2 verbose BOOL, default: 1

Turn on or off verbose reporting.
Set this option to 0 if you don't want the hook to report
"Discovered total %s tags in %s files. Written to file '%s'."

=head1 REFERENCES

=over

=item * L<Perl::Tags>

Module used to scan Perl files for tags.

=back

=head1 AUTHOR

'Mikko Koivunalho <mikkoi@cpan.org>'

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2022 by Mikko Koivunalho.

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
