File Coverage

blib/lib/Git/Mailmap.pm
Criterion Covered Total %
statement 183 215 85.1
branch 57 68 83.8
condition 32 50 64.0
subroutine 17 18 94.4
pod 7 7 100.0
total 296 358 82.6


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   66640 use strict;
  4         19  
  4         114  
8 4     4   20 use warnings;
  4         8  
  4         99  
9 4     4   67 use 5.010000;
  4         12  
10              
11             # ABSTRACT: Construct and read/write Git mailmap file.
12              
13             our $VERSION = '0.005'; # VERSION: generated by DZP::OurPkgVersion
14              
15 4     4   2096 use Hash::Util 0.06 qw{lock_keys};
  4         10794  
  4         22  
16 4     4   404 use Scalar::Util qw(blessed);
  4         8  
  4         157  
17 4     4   22 use Carp;
  4         8  
  4         169  
18 4     4   2016 use Carp::Assert;
  4         4916  
  4         26  
19 4     4   2382 use Carp::Assert::More;
  4         10696  
  4         659  
20              
21 4     4   2005 use Params::Validate qw(:all);
  4         35547  
  4         727  
22 4     4   2233 use Readonly;
  4         15707  
  4         246  
23 4     4   1914 use Log::Any qw{$log};
  4         34655  
  4         24  
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 29 my $class = shift;
39 4         53 my %params = validate(
40             @_, {}, # No parameters when creating object!
41             );
42              
43 4         40 $log->tracef( 'Entering new(%s, %s)', $class, \%params );
44 4         16 my $self = {};
45 4         10 my @self_keys = (
46             'committers', # Object's data.
47             );
48 4         9 bless $self, $class;
49 4         18 $self->{'committers'} = [];
50 4         7 lock_keys( %{$self}, @self_keys );
  4         17  
51 4         160 $log->tracef( 'Exiting new: %s', $self );
52 4         16 return $self;
53             }
54              
55             ## no critic (Subroutines::ProhibitBuiltinHomonyms)
56             sub map {
57 8     8 1 3770 my $self = shift;
58 8         39 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         469 $log->tracef( 'Entering map(%s)', \%params );
66 8         32 my @mapped_to = ( undef, undef );
67 8         12 my $committer;
68 8         13 foreach my $for_committer ( @{ $self->{'committers'} } ) {
  8         18  
69 22 100       48 if ( $for_committer->{'proper-email'} eq $params{'email'} ) {
70 2         3 $committer = $for_committer;
71 2         3 last;
72             }
73             else {
74 20         67 assert_listref( $for_committer->{'aliases'}, 'Item \'aliases\' exists.' );
75 20         301 my $aliases = $for_committer->{'aliases'};
76 20         24 foreach my $for_alias ( @{$aliases} ) {
  20         29  
77             assert_nonblank( $for_alias->{'commit-email'},
78             'Alias for \''
79             . ( $for_committer->{'proper-name'} // q{} ) . q{ }
80 27   100     108 . $for_committer->{'proper-email'}
81             . '\' is undef.' );
82 27 100 50     350 if ( $for_alias->{'commit-email'} eq ( $params{'email'} // q{} ) ) {
83 5 100 66     21 if ( !defined $params{'name'} ) {
    100          
84 3         4 $committer = $for_committer;
85 3         6 last;
86             }
87             elsif ( defined $params{'name'}
88             && $params{'name'} eq $for_alias->{'commit-name'} )
89             {
90 1         2 $committer = $for_committer;
91 1         3 last;
92             }
93              
94             # If name parameter is defined and not matches here,
95             # try the next alias!
96             }
97             }
98 20 100       39 if ($committer) {
99 4         8 last;
100             }
101             }
102             }
103 8 100       18 if ( defined $committer ) {
104 6         14 @mapped_to = ( $committer->{'proper-name'}, $committer->{'proper-email'} );
105             }
106 8         19 $log->tracef( 'Exiting map: %s', \@mapped_to );
107 8         65 return @mapped_to;
108             }
109              
110             sub add {
111 31     31 1 10126 my $self = shift;
112 31         120 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         3112 $log->tracef( 'Entering add(%s)', \%params );
122 31         102 my $committer;
123 31         48 foreach my $for_committer ( @{ $self->{'committers'} } ) {
  31         74  
124 87 100       179 if ( $for_committer->{'proper-email'} eq $params{'proper-email'} ) {
125 11 100       24 if ( $params{'proper-name'} ) {
126 9         18 $for_committer->{'proper-name'} = $params{'proper-name'};
127             }
128 11         37 assert_listref( $for_committer->{'aliases'}, 'Item \'aliases\' exists.' );
129 11         194 my $aliases = $for_committer->{'aliases'};
130 11         17 my $alias; ## Put here if we find any?
131 11         17 foreach my $for_alias ( @{$aliases} ) {
  11         18  
132 14 50 100     130 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     59 if ( !defined $alias && defined $params{'commit-email'} ) {
150 9         23 $alias = { 'commit-email' => $params{'commit-email'} };
151 9 100       20 if ( $params{'commit-name'} ) {
152 5         9 $alias->{'commit-name'} = $params{'commit-name'};
153             }
154 9         12 push @{$aliases}, $alias;
  9         28  
155             }
156 11         17 $committer = $for_committer;
157 11         17 last;
158             }
159             }
160 31 100       67 if ( !defined $committer ) {
161             ## Create new entry.
162 20         48 $committer = { 'proper-email' => $params{'proper-email'} };
163 20 100       44 if ( $params{'proper-name'} ) {
164 16         33 $committer->{'proper-name'} = $params{'proper-name'};
165             }
166 20         31 $committer->{'aliases'} = [];
167 20         34 my $alias;
168 20 100       38 if ( $params{'commit-email'} ) {
169 17         28 $alias = { 'commit-email' => $params{'commit-email'} };
170 17 100       35 if ( $params{'commit-name'} ) {
171 9         16 $alias->{'commit-name'} = $params{'commit-name'};
172             }
173 17         31 push @{ $committer->{'aliases'} }, $alias;
  17         39  
174             }
175 20         38 push @{ $self->{'committers'} }, $committer;
  20         41  
176             }
177 31         84 $log->tracef( 'Exiting add: %s', $self );
178 31         148 return;
179             }
180              
181             sub verify { ## no critic (Subroutines/ProhibitExcessComplexity)
182 18     18 1 5496 my $self = shift;
183 18         455 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         130 $log->tracef( 'Entering verify(%s)', \%params );
198 18         69 my $committers = $self->{'committers'};
199 18         43 my %found = (
200             $PROPER_EMAIL => -1,
201             $PROPER_NAME => -1,
202             $COMMIT_EMAIL => -1,
203             $COMMIT_NAME => -1,
204             );
205 18         37 foreach ( $PROPER_EMAIL, $PROPER_NAME, $COMMIT_EMAIL, $COMMIT_NAME ) {
206 72 100       155 $found{$_} = 0 if ( defined $params{$_} );
207             }
208 18         22 foreach my $committer ( @{$committers} ) {
  18         52  
209 90         129 foreach ( $PROPER_EMAIL, $PROPER_NAME, $COMMIT_EMAIL, $COMMIT_NAME ) {
210             $found{$_} = 1 if ( defined $committer->{$_}
211             && defined $params{$_}
212 360 100 100     1027 && $committer->{$_} eq $params{$_} );
      100        
213             }
214 90         110 my $aliases = $committer->{'aliases'};
215 90         103 foreach my $alias ( @{$aliases} ) {
  90         122  
216 144         214 foreach ( $PROPER_EMAIL, $PROPER_NAME, $COMMIT_EMAIL, $COMMIT_NAME ) {
217             $found{$_} = 1 if ( defined $alias->{$_}
218             && defined $params{$_}
219 576 100 100     1471 && $alias->{$_} eq $params{$_} );
      100        
220             }
221             }
222             }
223             my $match =
224 18 100 66     105 ( $found{$PROPER_EMAIL} != 0 && $found{$PROPER_NAME} != 0 && $found{$COMMIT_EMAIL} != 0 && $found{$COMMIT_NAME} != 0 )
225             ? 1
226             : 0;
227 18         50 $log->tracef( 'Exiting verify: %s', $match );
228 18         111 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             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 0   0     0 || 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 3463 my $self = shift;
304              
305 3 100       14 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         3 $self = $self->new();
312             }
313              
314 3         51 my %params = validate(
315             @_,
316             {
317             'mailmap' => { type => SCALAR, },
318             }
319             );
320 3         19 $log->tracef( 'Entering from_string(%s)', \%params );
321 3         18 assert_defined( $params{'mailmap'}, 'Parameter \'mailmap\' is a defined string.' );
322 3         31 foreach my $row ( split qr/\n/msx, $params{'mailmap'} ) {
323 24         63 $log->debug( 'from_string(): reading row:\'%s\'.', $row );
324 24 100       103 if ( $row !~ /^[[:space:]]*\#/msx ) { # Skip comment rows.
325             # Comments can also be at the end of the row. Remove them:
326 22         45 $row =~ s/(\#.*)$//msx;
327 22         32 my ( $proper_name, $proper_email, $commit_name, $commit_email );
328              
329             # The special case of 'Proper Name '
330 22 100       740 if ( $row =~ m/^([^<>]*)($EMAIL_ADDRESS_REGEXP)[[:space:]]*$/msx ) {
    50          
331 4         87 ( $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         203 ( $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         100 $proper_name =~ s/^\s+|\s+$//sxmg;
344 22         82 $commit_name =~ s/^\s+|\s+$//sxmg;
345              
346 22         67 $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         83 my %add_params = ( 'proper-email' => $proper_email );
349 22 100       53 if ( length $proper_name > 0 ) {
350 19         33 $add_params{'proper-name'} = $proper_name;
351             }
352 22 100       44 if ( length $commit_email > 0 ) {
353 18         26 $add_params{'commit-email'} = $commit_email;
354             }
355 22 100       41 if ( length $commit_name > 0 ) {
356 9         16 $add_params{'commit-name'} = $commit_name;
357             }
358 22         61 $self->add(%add_params);
359             }
360             }
361              
362 3         14 $log->tracef( 'Exiting from_string(): %s', $self );
363 3         13 return $self;
364             }
365              
366             sub to_string {
367 1     1 1 3495 my $self = shift;
368 1         24 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         1 foreach my $committer ( @{$committers} ) {
  1         3  
378 5         15 assert_nonblank( $committer->{'proper-email'}, 'Committer has nonblank item \'proper-email}\'.' );
379 5         62 my $proper_part = $EMPTY_STRING;
380 5 100       13 if ( defined $committer->{'proper-name'} ) {
381 4         5 $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         70 my $aliases = $committer->{'aliases'};
386 5 50       6 if ( scalar @{$aliases} > 0 ) {
  5         12  
387 5         6 foreach my $alias ( @{$aliases} ) {
  5         9  
388 8         17 assert_nonblank( $alias->{'commit-email'}, 'Alias has nonblank item \'commit-email}\'.' );
389 8         89 my $alias_part = $EMPTY_STRING;
390 8 100       14 if ( defined $alias->{'commit-name'} ) {
391 5         9 $alias_part .= $alias->{'commit-name'} . q{ };
392             }
393 8         14 $alias_part .= $alias->{'commit-email'};
394 8         19 $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         4 $log->tracef( 'Exiting to_string: %s', $file );
402 1         16 return $file;
403             }
404              
405             1;
406              
407             __END__