File Coverage

blib/lib/Git/MoreHooks/CheckIndent.pm
Criterion Covered Total %
statement 36 111 32.4
branch 3 24 12.5
condition 4 21 19.0
subroutine 9 15 60.0
pod 0 3 0.0
total 52 174 29.8


line stmt bran cond sub pod time code
1             ## no critic (Documentation::PodSpelling)
2             ## no critic (Documentation::RequirePodAtEnd)
3             ## no critic (Documentation::RequirePodSections)
4             ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
5             ## no critic (Subroutines::RequireArgUnpacking)
6              
7             package Git::MoreHooks::CheckIndent;
8              
9 1     1   186337 use strict;
  1         13  
  1         23  
10 1     1   4 use warnings;
  1         2  
  1         23  
11 1     1   3 use utf8;
  1         2  
  1         5  
12              
13             # ABSTRACT: Check committed files for problems with indentation.
14              
15             our $VERSION = '0.016'; # VERSION: generated by DZP::OurPkgVersion
16              
17 1     1   350 use Git::MoreHooks::CheckCommitBase \&do_hook;
  1         3  
  1         7  
18              
19 1     1   5 use Git::Hooks 3.000000;
  1         12  
  1         84  
20 1     1   4 use Path::Tiny;
  1         2  
  1         30  
21 1     1   4 use Log::Any qw{$log};
  1         2  
  1         11  
22 1     1   135 use Params::Validate qw(:all);
  1         1  
  1         1383  
23              
24             my $PKG = __PACKAGE__;
25             my ($CFG) = __PACKAGE__ =~ /::([^:]+)$/msx;
26             $CFG = 'githooks.' . $CFG;
27              
28             ####################
29             # Hook configuration, check it and set defaults.
30              
31             sub _setup_config {
32 0     0   0 my ($git) = @_;
33 0         0 $log->debugf( __PACKAGE__ . '::_setup_config(%s):', '$git' );
34              
35 0         0 my $config = $git->get_config();
36 0         0 $log->tracef( __PACKAGE__ . '::_setup_config(): Current Git config:\n%s.', $config );
37              
38             # Put empty hash if there is no config items.
39 0   0     0 $config->{ lc $CFG } //= {};
40              
41             # Set default config values.
42 0         0 my $default = $config->{ lc $CFG };
43 0   0     0 $default->{'file'} //= [];
44 0   0     0 $default->{'exception'} //= [];
45              
46             # Check validity of config items.
47 0         0 foreach my $file_def ( @{ $default->{'file'} } ) {
  0         0  
48 0         0 $log->debugf( __PACKAGE__ . q{::} . '_setup_config(): Check for validity, config item: \'%s\'.', $file_def );
49 0 0       0 if (
50             ## no critic (RegularExpressions::ProhibitComplexRegexes)
51             $file_def !~ m{^
52             (?:[[:graph:]]+)
53             (?:
54             (?:[[:space:]]{1,}indent-size:[[:digit:]]+){1,}
55             | (?:[[:space:]]{1,}indent-char:(?:space|tab|both))
56             ){1,}
57             (?:[[:space:]]{0,})
58             $}msx
59             ## use critic (RegularExpressions::ProhibitComplexRegexes)
60             )
61             {
62 0         0 $git->error( $PKG, 'Faulty config item: \'' . $file_def . '\'.' );
63 0         0 return 0;
64             }
65             }
66 0         0 foreach my $exc_def ( @{ $default->{'exception'} } ) {
  0         0  
67 0         0 $log->debugf( __PACKAGE__ . q{::} . '_setup_config(): Check for validity, config item: \'%s\'.', $exc_def );
68 0 0       0 if (
69             ## no critic (RegularExpressions::ProhibitComplexRegexes)
70             $exc_def !~ m{^
71             (?:[[:space:]]{0,}) (?# Free spacing before)
72             (?:[[:graph:]]+) (?# File name pattern)
73             (?:[[:space:]]{1,}) (?# Required spacing)
74             (?:[[:graph:]]+) (?# Regular expression)
75             (?:[[:space:]]{0,}) (?# Free spacing after)
76             $}msx
77             ## use critic (RegularExpressions::ProhibitComplexRegexes)
78             )
79             {
80 0         0 $git->error( $PKG, 'Faulty config item: \'' . $exc_def . '\'.' );
81 0         0 return 0;
82             }
83             }
84 0         0 return 1;
85             }
86              
87             ####################
88             # Internal functions
89              
90             sub check_for_indent {
91 2     2 0 4879 my %params = validate(
92             @_,
93             {
94             file_as_string => { type => SCALAR, },
95             indent_char => {
96             type => SCALAR,
97             default => q{ },
98             regex => qr/[[:space:]]{1,}/msx,
99             },
100             indent_size => {
101             type => SCALAR,
102             default => 4,
103             regex => qr/[[:digit:]]{1,}/msx,
104             },
105             exceptions => {
106             type => ARRAYREF,
107             default => [],
108             },
109             },
110             );
111 2         45 my $ic = $params{'indent_char'};
112 2         3 my $row_nr = 1;
113 2         3 my %errors;
114 2         15 foreach my $row ( split qr/\n/msx, $params{'file_as_string'} ) {
115              
116             # Check for faulty tab chars (space/tab)
117 9         25 my ($indents) = $row =~ m/^([[:space:]]{0,})/msx;
118 9 100 66     78 if (
      66        
119             length $indents > 0
120             && ( $indents !~ m/^[$ic]{1,}$/msx
121             || ( ( $ic ne qq{\t} ) && ( length $indents ) % $params{'indent_size'} != 0 ) )
122             )
123             {
124             # If there is an exception regexp that matches this row,
125             # then skip logging it as error.
126 3 50       5 if ( !map { $row =~ m/$_/msx } @{ $params{'exceptions'} } ) {
  0         0  
  3         8  
127 3         6 $errors{$row_nr} = $row;
128             }
129             else {
130 0         0 $log->debugf( __PACKAGE__ . q{::} . 'check_for_indent(): Except this row: \'%s\'', $row );
131             }
132             }
133 9         16 $row_nr++;
134             }
135 2         12 return %errors;
136             }
137              
138             ####################
139             # Callback function
140              
141             sub do_hook {
142 0     0 0   my ( $git, $hook_name, $opts ) = @_;
143 0           $log->tracef( 'do_hook(%s)', ( join q{:}, @_ ) );
144              
145 0 0         return 1 if $git->im_admin();
146 0 0         if ( !_setup_config($git) ) {
147 0           return 0;
148             }
149              
150 0           my $errors = 0;
151 0 0 0       if ( $hook_name eq 'pre-commit' ) {
    0 0        
    0 0        
152 0           my @files = $git->filter_files_in_index('AM');
153 0           foreach my $file (@files) {
154 0     0     my $read_file_func_ptr = sub { return path(shift)->slurp( { 'binmode' => ':raw' } ) };
  0            
155 0           $errors += handle_file( $git, $file, $read_file_func_ptr, ':0' );
156             }
157             }
158             elsif ( $hook_name eq 'patchset-created' || $hook_name eq 'draft-published' ) {
159 0           my @files = $git->filter_files_in_commit( 'AM', $opts->{'gerrit-opts'}->{'--commit'} );
160 0           foreach my $file (@files) {
161             my $read_file_func_ptr = sub {
162 0     0     my ( $file, $commit ) = @_;
163 0           my $tmpfile_name = $git->blob( $commit, $file );
164 0           return path($tmpfile_name)->slurp( { 'binmode' => ':raw' } );
165 0           };
166 0           $errors += handle_file( $git, $file, $read_file_func_ptr, $opts->{'gerrit-opts'}->{'--commit'} );
167             }
168             }
169             elsif ( $hook_name eq 'update' || $hook_name eq 'pre-receive' || $hook_name eq 'ref-update' ) {
170 0           foreach my $ref ( $git->get_affected_refs() ) {
171 0           my ( $old_commit, $new_commit ) = $git->get_affected_ref_range($ref);
172 0           my @files = $git->filter_files_in_range( 'AM', $old_commit, $new_commit );
173 0           foreach my $file (@files) {
174             my $read_file_func_ptr = sub {
175 0     0     my ( $file, $commit ) = @_;
176 0           my $tmpfile_name = $git->blob( $commit, $file );
177 0           return path($tmpfile_name)->slurp( { 'binmode' => ':raw' } );
178 0           };
179 0           $errors += handle_file( $git, $file, $read_file_func_ptr, $new_commit );
180             }
181             }
182             }
183 0           return $errors == 0;
184             }
185              
186             sub handle_file {
187 0     0 0   my ( $git, $filename, $read_file_func_ptr, $commit ) = @_;
188 0           $log->tracef( 'handle_file(%s)', ( join q{:}, @_ ) );
189 0           my @file_defs = $git->get_config( $CFG => 'file' );
190 0           my %opts;
191 0           my $errors = 0;
192 0           foreach my $file_def (@file_defs) {
193 0           my ( $file_regexp, $options ) = split q{ }, $file_def, 2;
194 0 0         if ( $filename =~ m/$file_regexp/msx ) {
195 0           ( $opts{'indent_size'} ) = $options =~ m/indent-size:([[:digit:]]+)/msx;
196 0           ( $opts{'indent_char'} ) = $options =~ m/indent-char:(space|tab|both)/msx;
197 0           my $file_as_string = &{$read_file_func_ptr}( $filename, $commit );
  0            
198 0           my %exceptions;
199 0           foreach my $exc_row ( $git->get_config( $CFG => 'exception' ) ) {
200 0           my ( $exc_file_regexp, $exc ) = split qr{[[:space:]]+}msx, $exc_row;
201 0 0         if ( $filename =~ m/$exc_file_regexp/msx ) {
202 0           $exceptions{$exc_file_regexp} = $exc;
203             }
204             }
205             my %results = check_for_indent(
206             'file_as_string' => $file_as_string,
207             'indent_char' => $opts{'indent_char'} eq 'space' ? q{ } : qq{\t},
208 0 0         'indent_size' => $opts{'indent_size'},
209             'exceptions' => [ values %exceptions ],
210             );
211 0           foreach my $row_nr ( keys %results ) {
212 0           $git->error( $PKG, "Indent error ($commit, $filename:$row_nr): '$results{$row_nr}'" );
213 0           $errors++;
214             }
215 0           last;
216             }
217             }
218 0           return $errors;
219             }
220              
221             1;
222              
223             __END__