File Coverage

blib/lib/Git/Mailmap.pm
Criterion Covered Total %
statement 184 216 85.1
branch 57 68 83.8
condition 32 50 64.0
subroutine 17 18 94.4
pod 7 7 100.0
total 297 359 82.7


line stmt bran cond sub pod time code
1             ## no critic (Documentation::RequirePodAtEnd)
2             ## no critic (Documentation::RequirePodSections)
3             ## no critic (Subroutines::RequireArgUnpacking)
4              
5             package Git::Mailmap;
6              
7 4     4   70553 use strict;
  4         9  
  4         199  
8 4     4   24 use warnings;
  4         5  
  4         161  
9 4     4   135 use 5.010000;
  4         45  
  4         383  
10              
11             # ABSTRACT: Construct and read/write Git mailmap file.
12              
13             our $VERSION = '0.004'; # VERSION: generated by DZP::OurPkgVersion
14              
15 4     4   2750 use Hash::Util 0.06 qw{lock_keys};
  4         10756  
  4         20  
16 4     4   542 use Scalar::Util qw(blessed);
  4         25  
  4         238  
17 4     4   22 use Carp;
  4         7  
  4         189  
18 4     4   2651 use Carp::Assert;
  4         5184  
  4         25  
19 4     4   3259 use Carp::Assert::More;
  4         10346  
  4         691  
20              
21 4     4   2877 use Params::Validate qw(:all);
  4         38306  
  4         1160  
22 4     4   2546 use Readonly;
  4         15075  
  4         411  
23 4     4   4020 use Log::Any qw{$log};
  4         8487  
  4         20  
24              
25             # CONSTANTS
26             Readonly::Scalar my $EMPTY_STRING => q{};
27             Readonly::Scalar my $LF => qq{\n};
28             Readonly::Scalar my $EMAIL_ADDRESS_REGEXP =>
29             q{<[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.>}; ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
30             Readonly::Hash my %VALIDATE_PARAM_EMAIL => ( 'regex' => qr/$EMAIL_ADDRESS_REGEXP/msx );
31             Readonly::Hash my %VALIDATE_PARAM_NONBLANK => ( 'regex' => qr/\S+/msx );
32             Readonly::Scalar my $PROPER_NAME => q{proper-name};
33             Readonly::Scalar my $PROPER_EMAIL => q{proper-email};
34             Readonly::Scalar my $COMMIT_NAME => q{commit-name};
35             Readonly::Scalar my $COMMIT_EMAIL => q{commit-email};
36              
37             sub new {
38 4     4 1 34 my $class = shift;
39 4         111 my %params = validate(
40             @_, {}, # No parameters when creating object!
41             );
42              
43 4         29 $log->tracef( 'Entering new(%s, %s)', $class, \%params );
44 4         13 my $self = {};
45 4         9 my @self_keys = (
46             'committers', # Object's data.
47             );
48 4         12 bless $self, $class;
49 4         30 $self->{'committers'} = [];
50 4         7 lock_keys( %{$self}, @self_keys );
  4         25  
51 4         212 $log->tracef( 'Exiting new: %s', $self );
52 4         24 return $self;
53             }
54              
55             ## no critic (Subroutines::ProhibitBuiltinHomonyms)
56             sub map {
57 9     9 1 4073 my $self = shift;
58 9         43 my %params = validate(
59             @_,
60             {
61             'email' => { 'type' => SCALAR, %VALIDATE_PARAM_NONBLANK, },
62             'name' => { 'type' => SCALAR, 'optional' => 1, %VALIDATE_PARAM_NONBLANK, },
63             }
64             );
65 8         505 $log->tracef( 'Entering map(%s)', \%params );
66 8         24 my @mapped_to = ( undef, undef );
67 8         8 my $committer;
68 8         8 foreach my $for_committer ( @{ $self->{'committers'} } ) {
  8         22  
69 22 100       42 if ( $for_committer->{'proper-email'} eq $params{'email'} ) {
70 2         2 $committer = $for_committer;
71 2         3 last;
72             }
73             else {
74 20         87 assert_listref( $for_committer->{'aliases'}, 'Item \'aliases\' exists.' );
75 20         237 my $aliases = $for_committer->{'aliases'};
76 20         20 foreach my $for_alias ( @{$aliases} ) {
  20         26  
77 27   100     137 assert_nonblank( $for_alias->{'commit-email'},
78             'Alias for \''
79             . ( $for_committer->{'proper-name'} // q{} ) . q{ }
80             . $for_committer->{'proper-email'}
81             . '\' is undef.' );
82 27 100 50     241 if ( $for_alias->{'commit-email'} eq ( $params{'email'} // q{} ) ) {
83 5 100 66     22 if ( !defined $params{'name'} ) {
    100          
84 3         4 $committer = $for_committer;
85 3         4 last;
86             }
87             elsif ( defined $params{'name'}
88             && $params{'name'} eq $for_alias->{'commit-name'} )
89             {
90 1         2 $committer = $for_committer;
91 1         2 last;
92             }
93              
94             # If name parameter is defined and not matches here,
95             # try the next alias!
96             }
97             }
98 20 100       55 if ($committer) {
99 4         6 last;
100             }
101             }
102             }
103 8 100       14 if ( defined $committer ) {
104 6         14 @mapped_to = ( $committer->{'proper-name'}, $committer->{'proper-email'} );
105             }
106 8         22 $log->tracef( 'Exiting map: %s', \@mapped_to );
107 8         60 return @mapped_to;
108             }
109              
110             sub add {
111 31     31 1 9148 my $self = shift;
112 31         206 my %params = validate(
113             @_,
114             {
115             'proper-email' => { type => SCALAR, %VALIDATE_PARAM_NONBLANK, },
116             'proper-name' => { type => SCALAR, optional => 1, %VALIDATE_PARAM_NONBLANK, depends => ['proper-email'], },
117             'commit-email' => { type => SCALAR, optional => 1, %VALIDATE_PARAM_NONBLANK, },
118             'commit-name' => { type => SCALAR, optional => 1, %VALIDATE_PARAM_NONBLANK, depends => ['commit-email'], },
119             },
120             );
121 31         3160 $log->tracef( 'Entering add(%s)', \%params );
122 31         79 my $committer;
123 31         34 foreach my $for_committer ( @{ $self->{'committers'} } ) {
  31         144  
124 87 100       252 if ( $for_committer->{'proper-email'} eq $params{'proper-email'} ) {
125 11 100       27 if ( $params{'proper-name'} ) {
126 9         23 $for_committer->{'proper-name'} = $params{'proper-name'};
127             }
128 11         34 assert_listref( $for_committer->{'aliases'}, 'Item \'aliases\' exists.' );
129 11         164 my $aliases = $for_committer->{'aliases'};
130 11         10 my $alias; ## Put here if we find any?
131 11         10 foreach my $for_alias ( @{$aliases} ) {
  11         23  
132 14 50 100     205 if ( $for_alias->{'commit-email'} eq ( $params{'commit-email'} // q{} )
    100 66        
      66        
      33        
      100        
      66        
133             && defined $for_alias->{'commit-name'}
134             && defined $params{'commit-name'}
135             && $for_alias->{'commit-name'} eq $params{'commit-name'} )
136             {
137             ## If both name and email are same, this is a duplicate.
138             ## We don't need duplicates!
139 0         0 $alias = $for_alias;
140 0         0 last;
141             }
142             elsif ($for_alias->{'commit-email'} eq ( $params{'commit-email'} // q{} )
143             && $for_alias->{'commit-name'} ne $params{'commit-name'} )
144             {
145             ## Different names. Needs a new entry!
146 2         6 last;
147             }
148             }
149 11 100 66     69 if ( !defined $alias && defined $params{'commit-email'} ) {
150 9         20 $alias = { 'commit-email' => $params{'commit-email'} };
151 9 100       21 if ( $params{'commit-name'} ) {
152 5         10 $alias->{'commit-name'} = $params{'commit-name'};
153             }
154 9         11 push @{$aliases}, $alias;
  9         12  
155             }
156 11         11 $committer = $for_committer;
157 11         23 last;
158             }
159             }
160 31 100       73 if ( !defined $committer ) {
161             ## Create new entry.
162 20         51 $committer = { 'proper-email' => $params{'proper-email'} };
163 20 100       98 if ( $params{'proper-name'} ) {
164 16         31 $committer->{'proper-name'} = $params{'proper-name'};
165             }
166 20         37 $committer->{'aliases'} = [];
167 20         24 my $alias;
168 20 100       45 if ( $params{'commit-email'} ) {
169 17         35 $alias = { 'commit-email' => $params{'commit-email'} };
170 17 100       46 if ( $params{'commit-name'} ) {
171 9         15 $alias->{'commit-name'} = $params{'commit-name'};
172             }
173 17         22 push @{ $committer->{'aliases'} }, $alias;
  17         72  
174             }
175 20         51 push @{ $self->{'committers'} }, $committer;
  20         46  
176             }
177 31         82 $log->tracef( 'Exiting add: %s', $self );
178 31         191 return;
179             }
180              
181             sub verify { ## no critic (Subroutines/ProhibitExcessComplexity)
182 18     18 1 4057 my $self = shift;
183 18         481 my %params = validate(
184             @_,
185             {
186             $PROPER_EMAIL => { type => SCALAR, optional => 1, },
187             $PROPER_NAME => { type => SCALAR, optional => 1, },
188             $COMMIT_EMAIL => { type => SCALAR, optional => 1, },
189             $COMMIT_NAME => { type => SCALAR, optional => 1, },
190              
191             # 'match-when-no-name' => {
192             # type => BOOLEAN, optional => 1, default => 1, },
193             # # If mailmap has no name, but caller has name, match if param is true.
194             }
195             );
196             ## no critic (ControlStructures::ProhibitPostfixControls)
197 18         220 $log->tracef( 'Entering verify(%s)', \%params );
198 18         45 my $committers = $self->{'committers'};
199 18         55 my %found = (
200             $PROPER_EMAIL => -1,
201             $PROPER_NAME => -1,
202             $COMMIT_EMAIL => -1,
203             $COMMIT_NAME => -1,
204             );
205 18         33 foreach ( $PROPER_EMAIL, $PROPER_NAME, $COMMIT_EMAIL, $COMMIT_NAME ) {
206 72 100       148 $found{$_} = 0 if ( defined $params{$_} );
207             }
208 18         23 foreach my $committer ( @{$committers} ) {
  18         24  
209 90         87 foreach ( $PROPER_EMAIL, $PROPER_NAME, $COMMIT_EMAIL, $COMMIT_NAME ) {
210 360 100 100     1389 $found{$_} = 1 if ( defined $committer->{$_}
      100        
211             && defined $params{$_}
212             && $committer->{$_} eq $params{$_} );
213             }
214 90         89 my $aliases = $committer->{'aliases'};
215 90         64 foreach my $alias ( @{$aliases} ) {
  90         98  
216 144         156 foreach ( $PROPER_EMAIL, $PROPER_NAME, $COMMIT_EMAIL, $COMMIT_NAME ) {
217 576 100 100     2286 $found{$_} = 1 if ( defined $alias->{$_}
      100        
218             && defined $params{$_}
219             && $alias->{$_} eq $params{$_} );
220             }
221             }
222             }
223 18 100 66     123 my $match =
224             ( $found{$PROPER_EMAIL} != 0 && $found{$PROPER_NAME} != 0 && $found{$COMMIT_EMAIL} != 0 && $found{$COMMIT_NAME} != 0 )
225             ? 1
226             : 0;
227 18         49 $log->tracef( 'Exiting verify: %s', $match );
228 18         130 return $match;
229             }
230              
231             sub remove { ## no critic (Subroutines/ProhibitExcessComplexity)
232 0     0 1 0 my $self = shift;
233 0         0 my %params = validate(
234             @_,
235             {
236             'proper-email' => { type => SCALAR, optional => 1, }, ## mutually exclusive with 'all'
237             'proper-name' => { type => SCALAR, optional => 1, },
238             'commit-email' => { type => SCALAR, optional => 1, },
239             'commit-name' => { type => SCALAR, optional => 1, },
240             'all' => { type => BOOLEAN, optional => 1, }, ## mutually exclusive with 'proper-email'
241             }
242             );
243 0         0 $log->tracef( 'Entering remove(%s)', \%params );
244 0   0     0 assert(
245             (
246             defined $params{'all'}
247             && !defined $params{'proper-email'}
248             && !defined $params{'proper-name'}
249             && !defined $params{'commit-email'}
250             && !defined $params{'commit-name'}
251             )
252             || (
253             !defined $params{'all'}
254             && ( defined $params{'proper-email'}
255             || defined $params{'proper-name'}
256             || defined $params{'commit-email'}
257             || defined $params{'commit-name'} )
258             ),
259             'Parameter \'all\' is only present when no other parameters are.'
260             );
261 0 0 0     0 if ( defined $params{'all'} && $params{'all'} eq '1' ) {
262 0         0 @{ $self->{'committers'} } = [];
  0         0  
263             }
264             else {
265 0         0 my $committers = $self->{'committers'};
266 0         0 for ( my $i = 0 ; $i < scalar @{$committers} ; ) { ## no critic (ControlStructures::ProhibitCStyleForLoops)
  0         0  
267 0         0 my $for_committer = $committers->[$i];
268 0 0 0     0 if ( $for_committer->{'proper-email'} eq $params{'proper-email'}
269             || !defined $params{'commit-email'} )
270             {
271 0 0       0 if ( !defined $params{'commit-email'} ) {
272              
273             # Cut away the whole list entry.
274 0         0 splice @{$committers}, $i, 1;
  0         0  
275             }
276             else {
277             # Don't cut away the whole entry, just the matching aliases.
278 0         0 assert_arrayref( $for_committer->{'aliases'}, 'Item \'aliases\' exists.' );
279 0         0 my $aliases = $for_committer->{'aliases'};
280 0         0 for ( my $j = 0 ; $j < scalar @{$aliases} ; ) { ## no critic (ControlStructures::ProhibitCStyleForLoops)
  0         0  
281 0         0 my $for_alias = $aliases->[$j];
282 0 0       0 if ( $for_alias->{'commit-email'} eq $params{'commit-email'} )
283             { ## no critic (ControlStructures::ProhibitDeepNests)
284 0         0 splice @{$aliases}, $j, 1;
  0         0  
285 0         0 last;
286             }
287             else {
288 0         0 $j++;
289             }
290             }
291             }
292             }
293             else {
294 0         0 $i++;
295             }
296             }
297             }
298 0         0 $log->tracef( 'Exiting remove: %s', $self );
299 0         0 return;
300             }
301              
302             sub from_string {
303 3     3 1 6090 my $self = shift;
304              
305 3 100       23 if ( !blessed $self ) {
306              
307             # Assuming called as:
308             # Git::Mailmap->from_string(mailmap => file)
309             # if ( $self ne __PACKAGE__ ) { unshift @_, $self; }
310             # $self now contains the package name.
311 1         4 $self = $self->new();
312             }
313              
314 3         84 my %params = validate(
315             @_,
316             {
317             'mailmap' => { type => SCALAR, },
318             }
319             );
320 3         64 $log->tracef( 'Entering from_string(%s)', \%params );
321 3         21 assert_defined( $params{'mailmap'}, 'Parameter \'mailmap\' is a defined string.' );
322 3         43 foreach my $row ( split qr/\n/msx, $params{'mailmap'} ) {
323 24         62 $log->debug( 'from_string(): reading row:\'%s\'.', $row );
324 24 100       104 if ( $row !~ /^[[:space:]]*\#/msx ) { # Skip comment rows.
325             # Comments can also be at the end of the row. Remove them:
326 22         42 $row =~ s/(\#.*)$//msx;
327 22         21 my ( $proper_name, $proper_email, $commit_name, $commit_email );
328              
329             # The special case of 'Proper Name '
330 22 100       478 if ( $row =~ m/^([^<>]*)($EMAIL_ADDRESS_REGEXP)[[:space:]]*$/msx ) {
    50          
331 4         85 ( $proper_name, $proper_email ) = $row =~ /^(.*)($EMAIL_ADDRESS_REGEXP)[[:space:]]*$/msx;
332 4         12 ( $commit_name, $commit_email ) = ( $EMPTY_STRING, $EMPTY_STRING );
333             }
334             elsif ( $row =~ /^(.*)($EMAIL_ADDRESS_REGEXP)(.+)($EMAIL_ADDRESS_REGEXP)[[:space:]]*$/msx ) {
335 18         244 ( $proper_name, $proper_email, $commit_name, $commit_email ) =
336             $row =~ /^(.*)($EMAIL_ADDRESS_REGEXP)(.+)($EMAIL_ADDRESS_REGEXP)[[:space:]]*$/msx;
337             }
338             else {
339 0         0 carp "Can not parse the following row: '$row'";
340             }
341              
342             # Remove beginning and end whitespace.
343 22         122 $proper_name =~ s/^\s+|\s+$//sxmg;
344 22         63 $commit_name =~ s/^\s+|\s+$//sxmg;
345              
346 22         61 $log->debugf( 'from_string():proper_name=\'%s\', proper_email=\'%s\', commit_name=\'%s\', commit_email=\'%s\'.',
347             $proper_name, $proper_email, $commit_name, $commit_email );
348 22         96 my %add_params = ( 'proper-email' => $proper_email );
349 22 100       88 if ( length $proper_name > 0 ) {
350 19         35 $add_params{'proper-name'} = $proper_name;
351             }
352 22 100       41 if ( length $commit_email > 0 ) {
353 18         28 $add_params{'commit-email'} = $commit_email;
354             }
355 22 100       44 if ( length $commit_name > 0 ) {
356 9         12 $add_params{'commit-name'} = $commit_name;
357             }
358 22         71 $self->add(%add_params);
359             }
360             }
361              
362 3         17 $log->tracef( 'Exiting from_string(): %s', $self );
363 3         15 return $self;
364             }
365              
366             sub to_string {
367 1     1 1 3318 my $self = shift;
368 1         15 my %params = validate(
369             @_, {}, # No parameters!
370             );
371 1         7 $log->tracef( 'Entering to_string(%s)', \%params );
372              
373             # proper_part + alias_part
374             # if !alias_parts, proper_part + proper_part
375 1         4 my $file = $EMPTY_STRING;
376 1         3 my $committers = $self->{'committers'};
377 1         2 foreach my $committer ( @{$committers} ) {
  1         12  
378 5         17 assert_nonblank( $committer->{'proper-email'}, 'Committer has nonblank item \'proper-email}\'.' );
379 5         54 my $proper_part = $EMPTY_STRING;
380 5 100       18 if ( defined $committer->{'proper-name'} ) {
381 4         7 $proper_part .= $committer->{'proper-name'} . q{ };
382             }
383 5         10 $proper_part .= $committer->{'proper-email'};
384 5         12 assert_listref( $committer->{'aliases'}, 'Item \'aliases\' exists.' );
385 5         69 my $aliases = $committer->{'aliases'};
386 5 50       7 if ( scalar @{$aliases} > 0 ) {
  5         14  
387 5         6 foreach my $alias ( @{$aliases} ) {
  5         9  
388 8         21 assert_nonblank( $alias->{'commit-email'}, 'Alias has nonblank item \'commit-email}\'.' );
389 8         71 my $alias_part = $EMPTY_STRING;
390 8 100       20 if ( defined $alias->{'commit-name'} ) {
391 5         12 $alias_part .= $alias->{'commit-name'} . q{ };
392             }
393 8         12 $alias_part .= $alias->{'commit-email'};
394 8         36 $file .= $proper_part . q{ } . $alias_part . "\n";
395             }
396             }
397             else {
398 0         0 $file .= $proper_part . q{ } . $proper_part . "\n";
399             }
400             }
401 1         6 $log->tracef( 'Exiting to_string: %s', $file );
402 1         8 return $file;
403             }
404              
405             1;
406              
407             __END__