File Coverage

blib/lib/Git/Mailmap.pm
Criterion Covered Total %
statement 181 215 84.1
branch 52 66 78.7
condition 16 27 59.2
subroutine 17 18 94.4
pod 7 7 100.0
total 273 333 81.9


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