2 # (c) 2007, Joe Perches <joe@perches.com>
3 # created from checkpatch.pl
5 # Print selected MAINTAINERS information for
6 # the files modified in a patch or for a file
8 # usage: perl scripts/get_maintainer.pl [OPTIONS] <patch>
9 # perl scripts/get_maintainer.pl [OPTIONS] -f <file>
11 # Licensed under the terms of the GNU GPL License version 2
18 use Getopt
::Long
qw(:config no_auto_abbrev);
22 my $email_usename = 1;
23 my $email_maintainer = 1;
25 my $email_subscriber_list = 0;
26 my $email_git_penguin_chiefs = 0;
28 my $email_git_all_signature_types = 0;
29 my $email_git_blame = 0;
30 my $email_git_blame_signatures = 1;
31 my $email_git_fallback = 1;
32 my $email_git_min_signatures = 1;
33 my $email_git_max_maintainers = 5;
34 my $email_git_min_percent = 5;
35 my $email_git_since = "1-year-ago";
36 my $email_hg_since = "-365";
38 my $email_remove_duplicates = 1;
39 my $output_multiline = 1;
40 my $output_separator = ", ";
42 my $output_rolestats = 0;
50 my $from_filename = 0;
51 my $pattern_depth = 0;
59 my %commit_author_hash;
60 my %commit_signer_hash;
62 my @penguin_chief = ();
63 push(@penguin_chief, "Linus Torvalds:torvalds\@linux-foundation.org");
64 #Andrew wants in on most everything - 2009/01/14
65 #push(@penguin_chief, "Andrew Morton:akpm\@linux-foundation.org");
67 my @penguin_chief_names = ();
68 foreach my $chief (@penguin_chief) {
69 if ($chief =~ m/^(.*):(.*)/) {
72 push(@penguin_chief_names, $chief_name);
75 my $penguin_chiefs = "\(" . join("|", @penguin_chief_names) . "\)";
77 # Signature types of people who are either
78 # a) responsible for the code in question, or
79 # b) familiar enough with it to give relevant feedback
80 my @signature_tags = ();
81 push(@signature_tags, "Signed-off-by:");
82 push(@signature_tags, "Reviewed-by:");
83 push(@signature_tags, "Acked-by:");
85 # rfc822 email address - preloaded methods go here.
86 my $rfc822_lwsp = "(?:(?:\\r\\n)?[ \\t])";
87 my $rfc822_char = '[\\000-\\377]';
89 # VCS command support: class-like functions and strings
94 "execute_cmd" => \
&git_execute_cmd
,
95 "available" => '(which("git") ne "") && (-d ".git")',
97 "git log --no-color --since=\$email_git_since " .
98 '--format="GitCommit: %H%n' .
99 'GitAuthor: %an <%ae>%n' .
104 "find_commit_signers_cmd" =>
105 "git log --no-color " .
106 '--format="GitCommit: %H%n' .
107 'GitAuthor: %an <%ae>%n' .
112 "find_commit_author_cmd" =>
113 "git log --no-color " .
114 '--format="GitCommit: %H%n' .
115 'GitAuthor: %an <%ae>%n' .
117 'GitSubject: %s%n"' .
119 "blame_range_cmd" => "git blame -l -L \$diff_start,+\$diff_length \$file",
120 "blame_file_cmd" => "git blame -l \$file",
121 "commit_pattern" => "^GitCommit: ([0-9a-f]{40,40})",
122 "blame_commit_pattern" => "^([0-9a-f]+) ",
123 "author_pattern" => "^GitAuthor: (.*)",
124 "subject_pattern" => "^GitSubject: (.*)",
128 "execute_cmd" => \
&hg_execute_cmd
,
129 "available" => '(which("hg") ne "") && (-d ".hg")',
130 "find_signers_cmd" =>
131 "hg log --date=\$email_hg_since " .
132 "--template='HgCommit: {node}\\n" .
133 "HgAuthor: {author}\\n" .
134 "HgSubject: {desc}\\n'" .
136 "find_commit_signers_cmd" =>
138 "--template='HgSubject: {desc}\\n'" .
140 "find_commit_author_cmd" =>
142 "--template='HgCommit: {node}\\n" .
143 "HgAuthor: {author}\\n" .
144 "HgSubject: {desc|firstline}\\n'" .
146 "blame_range_cmd" => "", # not supported
147 "blame_file_cmd" => "hg blame -n \$file",
148 "commit_pattern" => "^HgCommit: ([0-9a-f]{40,40})",
149 "blame_commit_pattern" => "^([ 0-9a-f]+):",
150 "author_pattern" => "^HgAuthor: (.*)",
151 "subject_pattern" => "^HgSubject: (.*)",
154 my $conf = which_conf
(".get_maintainer.conf");
157 open(my $conffile, '<', "$conf")
158 or warn "$P: Can't find a readable .get_maintainer.conf file $!\n";
160 while (<$conffile>) {
163 $line =~ s/\s*\n?$//g;
167 next if ($line =~ m/^\s*#/);
168 next if ($line =~ m/^\s*$/);
170 my @words = split(" ", $line);
171 foreach my $word (@words) {
172 last if ($word =~ m/^#/);
173 push (@conf_args, $word);
177 unshift(@ARGV, @conf_args) if @conf_args;
182 'git!' => \
$email_git,
183 'git-all-signature-types!' => \
$email_git_all_signature_types,
184 'git-blame!' => \
$email_git_blame,
185 'git-blame-signatures!' => \
$email_git_blame_signatures,
186 'git-fallback!' => \
$email_git_fallback,
187 'git-chief-penguins!' => \
$email_git_penguin_chiefs,
188 'git-min-signatures=i' => \
$email_git_min_signatures,
189 'git-max-maintainers=i' => \
$email_git_max_maintainers,
190 'git-min-percent=i' => \
$email_git_min_percent,
191 'git-since=s' => \
$email_git_since,
192 'hg-since=s' => \
$email_hg_since,
193 'i|interactive!' => \
$interactive,
194 'remove-duplicates!' => \
$email_remove_duplicates,
195 'm!' => \
$email_maintainer,
196 'n!' => \
$email_usename,
197 'l!' => \
$email_list,
198 's!' => \
$email_subscriber_list,
199 'multiline!' => \
$output_multiline,
200 'roles!' => \
$output_roles,
201 'rolestats!' => \
$output_rolestats,
202 'separator=s' => \
$output_separator,
203 'subsystem!' => \
$subsystem,
204 'status!' => \
$status,
207 'pattern-depth=i' => \
$pattern_depth,
208 'k|keywords!' => \
$keywords,
209 'sections!' => \
$sections,
210 'fe|file-emails!' => \
$file_emails,
211 'f|file' => \
$from_filename,
212 'v|version' => \
$version,
213 'h|help|usage' => \
$help,
215 die "$P: invalid argument - use --help if necessary\n";
224 print("${P} ${V}\n");
228 if (-t STDIN
&& !@ARGV) {
229 # We're talking to a terminal, but have no command line arguments.
230 die "$P: missing patchfile or -f file - use --help if necessary\n";
233 $output_multiline = 0 if ($output_separator ne ", ");
234 $output_rolestats = 1 if ($interactive);
235 $output_roles = 1 if ($output_rolestats);
247 my $selections = $email + $scm + $status + $subsystem + $web;
248 if ($selections == 0) {
249 die "$P: Missing required option: email, scm, status, subsystem or web\n";
254 ($email_maintainer + $email_list + $email_subscriber_list +
255 $email_git + $email_git_penguin_chiefs + $email_git_blame) == 0) {
256 die "$P: Please select at least 1 email option\n";
259 if (!top_of_kernel_tree
($lk_path)) {
260 die "$P: The current directory does not appear to be "
261 . "a linux kernel source tree.\n";
264 ## Read MAINTAINERS for type/value pairs
269 open (my $maint, '<', "${lk_path}MAINTAINERS")
270 or die "$P: Can't open MAINTAINERS: $!\n";
274 if ($line =~ m/^(\C):\s*(.*)/) {
278 ##Filename pattern matching
279 if ($type eq "F" || $type eq "X") {
280 $value =~ s@\
.@
\\\
.@g; ##Convert . to \.
281 $value =~ s/\*/\.\*/g; ##Convert * to .*
282 $value =~ s/\?/\./g; ##Convert ? to .
283 ##if pattern is a directory and it lacks a trailing slash, add one
285 $value =~ s@
([^/])$@$1/@
;
287 } elsif ($type eq "K") {
288 $keyword_hash{@typevalue} = $value;
290 push(@typevalue, "$type:$value");
291 } elsif (!/^(\s)*$/) {
293 push(@typevalue, $line);
300 if ($email_remove_duplicates) {
301 open(my $mailmap, '<', "${lk_path}.mailmap")
302 or warn "$P: Can't open .mailmap: $!\n";
306 next if ($line =~ m/^\s*#/);
307 next if ($line =~ m/^\s*$/);
309 my ($name, $address) = parse_email
($line);
310 $line = format_email
($name, $address, $email_usename);
312 next if ($line =~ m/^\s*$/);
314 if (exists($mailmap{$name})) {
315 my $obj = $mailmap{$name};
316 push(@
$obj, $address);
318 my @arr = ($address);
319 $mailmap{$name} = \
@arr;
325 ## use the filenames on the command line or find the filenames in the patchfiles
329 my @keyword_tvi = ();
330 my @file_emails = ();
333 push(@ARGV, "&STDIN");
336 foreach my $file (@ARGV) {
337 if ($file ne "&STDIN") {
338 ##if $file is a directory and it lacks a trailing slash, add one
340 $file =~ s@
([^/])$@$1/@
;
341 } elsif (!(-f
$file)) {
342 die "$P: file '${file}' not found\n";
345 if ($from_filename) {
347 if ($file ne "MAINTAINERS" && -f
$file && ($keywords || $file_emails)) {
348 open(my $f, '<', $file)
349 or die "$P: Can't open $file: $!\n";
350 my $text = do { local($/) ; <$f> };
353 foreach my $line (keys %keyword_hash) {
354 if ($text =~ m/$keyword_hash{$line}/x) {
355 push(@keyword_tvi, $line);
360 my @poss_addr = $text =~ m
$[A
-Za
-zÀ
-ÿ
\"\' \
,\
.\
+-]*\s
*[\
,]*\s
*[\
(\
<\
{]{0,1}[A
-Za
-z0
-9_\
.\
+-]+\@
[A
-Za
-z0
-9\
.-]+\
.[A
-Za
-z0
-9]+[\
)\
>\
}]{0,1}$g;
361 push(@file_emails, clean_file_emails
(@poss_addr));
365 my $file_cnt = @files;
368 open(my $patch, "< $file")
369 or die "$P: Can't open $file: $!\n";
372 if (m/^\+\+\+\s+(\S+)/) {
374 $filename =~ s@
^[^/]*/@@
;
376 $lastfile = $filename;
377 push(@files, $filename);
378 } elsif (m/^\@\@ -(\d+),(\d+)/) {
379 if ($email_git_blame) {
380 push(@range, "$lastfile:$1:$2");
382 } elsif ($keywords) {
383 foreach my $line (keys %keyword_hash) {
384 if ($patch_line =~ m/^[+-].*$keyword_hash{$line}/x) {
385 push(@keyword_tvi, $line);
392 if ($file_cnt == @files) {
393 warn "$P: file '${file}' doesn't appear to be a patch. "
394 . "Add -f to options?\n";
396 @files = sort_and_uniq
(@files);
400 @file_emails = uniq
(@file_emails);
403 my %email_hash_address;
411 my @interactive_to = ();
412 my $signature_pattern;
414 my @maintainers = get_maintainers
();
417 @maintainers = merge_email
(@maintainers);
418 output
(@maintainers);
427 @status = uniq
(@status);
432 @subsystem = uniq
(@subsystem);
443 sub get_maintainers
{
444 %email_hash_name = ();
445 %email_hash_address = ();
446 %commit_author_hash = ();
447 %commit_signer_hash = ();
455 @interactive_to = ();
456 if ($email_git_all_signature_types) {
457 $signature_pattern = "(.+?)[Bb][Yy]:";
459 $signature_pattern = "\(" . join("|", @signature_tags) . "\)";
462 # Find responsible parties
464 my %exact_pattern_match_hash;
466 foreach my $file (@files) {
469 my $tvi = find_first_section
();
470 while ($tvi < @typevalue) {
471 my $start = find_starting_index
($tvi);
472 my $end = find_ending_index
($tvi);
476 #Do not match excluded file patterns
478 for ($i = $start; $i < $end; $i++) {
479 my $line = $typevalue[$i];
480 if ($line =~ m/^(\C):\s*(.*)/) {
484 if (file_match_pattern
($file, $value)) {
493 for ($i = $start; $i < $end; $i++) {
494 my $line = $typevalue[$i];
495 if ($line =~ m/^(\C):\s*(.*)/) {
499 if (file_match_pattern
($file, $value)) {
500 my $value_pd = ($value =~ tr@
/@@
);
501 my $file_pd = ($file =~ tr@
/@@
);
502 $value_pd++ if (substr($value,-1,1) ne "/");
503 $value_pd = -1 if ($value =~ /^\.\*/);
504 if ($value_pd >= $file_pd) {
505 $exact_pattern_match_hash{$file} = 1;
507 if ($pattern_depth == 0 ||
508 (($file_pd - $value_pd) < $pattern_depth)) {
509 $hash{$tvi} = $value_pd;
519 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
520 add_categories
($line);
523 my $start = find_starting_index
($line);
524 my $end = find_ending_index
($line);
525 for ($i = $start; $i < $end; $i++) {
526 my $line = $typevalue[$i];
527 if ($line =~ /^[FX]:/) { ##Restore file patterns
528 $line =~ s/([^\\])\.([^\*])/$1\?$2/g;
529 $line =~ s/([^\\])\.$/$1\?/g; ##Convert . back to ?
530 $line =~ s/\\\./\./g; ##Convert \. to .
531 $line =~ s/\.\*/\*/g; ##Convert .* to *
533 $line =~ s/^([A-Z]):/$1:\t/g;
542 @keyword_tvi = sort_and_uniq
(@keyword_tvi);
543 foreach my $line (@keyword_tvi) {
544 add_categories
($line);
548 @interactive_to = (@email_to, @list_to);
550 foreach my $file (@files) {
552 ($email_git || ($email_git_fallback &&
553 !$exact_pattern_match_hash{$file}))) {
554 vcs_file_signoffs
($file);
556 if ($email && $email_git_blame) {
557 vcs_file_blame
($file);
562 foreach my $chief (@penguin_chief) {
563 if ($chief =~ m/^(.*):(.*)/) {
566 $email_address = format_email
($1, $2, $email_usename);
567 if ($email_git_penguin_chiefs) {
568 push(@email_to, [$email_address, 'chief penguin']);
570 @email_to = grep($_->[0] !~ /${email_address}/, @email_to);
575 foreach my $email (@file_emails) {
576 my ($name, $address) = parse_email
($email);
578 my $tmp_email = format_email
($name, $address, $email_usename);
579 push_email_address
($tmp_email, '');
580 add_role
($tmp_email, 'in file');
585 if ($email || $email_list) {
587 @to = (@to, @email_to);
590 @to = (@to, @list_to);
595 @interactive_to = @to;
596 @to = interactive_get_maintainers
(\
@interactive_to);
602 sub file_match_pattern
{
603 my ($file, $pattern) = @_;
604 if (substr($pattern, -1) eq "/") {
605 if ($file =~ m@
^$pattern@
) {
609 if ($file =~ m@
^$pattern@
) {
610 my $s1 = ($file =~ tr@
/@@
);
611 my $s2 = ($pattern =~ tr@
/@@
);
622 usage: $P [options] patchfile
623 $P [options] -f file|directory
626 MAINTAINER field selection options:
627 --email => print email address(es) if any
628 --git => include recent git \*-by: signers
629 --git-all-signature-types => include signers regardless of signature type
630 or use only ${signature_pattern} signers (default: $email_git_all_signature_types)
631 --git-fallback => use git when no exact MAINTAINERS pattern (default: $email_git_fallback)
632 --git-chief-penguins => include ${penguin_chiefs}
633 --git-min-signatures => number of signatures required (default: $email_git_min_signatures)
634 --git-max-maintainers => maximum maintainers to add (default: $email_git_max_maintainers)
635 --git-min-percent => minimum percentage of commits required (default: $email_git_min_percent)
636 --git-blame => use git blame to find modified commits for patch or file
637 --git-since => git history to use (default: $email_git_since)
638 --hg-since => hg history to use (default: $email_hg_since)
639 --interactive => display a menu (mostly useful if used with the --git option)
640 --m => include maintainer(s) if any
641 --n => include name 'Full Name <addr\@domain.tld>'
642 --l => include list(s) if any
643 --s => include subscriber only list(s) if any
644 --remove-duplicates => minimize duplicate email names/addresses
645 --roles => show roles (status:subsystem, git-signer, list, etc...)
646 --rolestats => show roles and statistics (commits/total_commits, %)
647 --file-emails => add email addresses found in -f file (default: 0 (off))
648 --scm => print SCM tree(s) if any
649 --status => print status if any
650 --subsystem => print subsystem name if any
651 --web => print website(s) if any
654 --separator [, ] => separator for multiple entries on 1 line
655 using --separator also sets --nomultiline if --separator is not [, ]
656 --multiline => print 1 entry per line
659 --pattern-depth => Number of pattern directory traversals (default: 0 (all))
660 --keywords => scan patch for keywords (default: 1 (on))
661 --sections => print the entire subsystem sections with pattern matches
662 --version => show version
663 --help => show this help information
666 [--email --git --m --n --l --multiline --pattern-depth=0 --remove-duplicates]
669 Using "-f directory" may give unexpected results:
670 Used with "--git", git signators for _all_ files in and below
671 directory are examined as git recurses directories.
672 Any specified X: (exclude) pattern matches are _not_ ignored.
673 Used with "--nogit", directory is used as a pattern match,
674 no individual file within the directory or subdirectory
676 Used with "--git-blame", does not iterate all files in directory
677 Using "--git-blame" is slow and may add old committers and authors
678 that are no longer active maintainers to the output.
679 Using "--roles" or "--rolestats" with git send-email --cc-cmd or any
680 other automated tools that expect only ["name"] <email address>
681 may not work because of additional output after <email address>.
682 Using "--rolestats" and "--git-blame" shows the #/total=% commits,
683 not the percentage of the entire file authored. # of commits is
684 not a good measure of amount of code authored. 1 major commit may
685 contain a thousand lines, 5 trivial commits may modify a single line.
686 If git is not installed, but mercurial (hg) is installed and an .hg
687 repository exists, the following options apply to mercurial:
689 --git-min-signatures, --git-max-maintainers, --git-min-percent, and
691 Use --hg-since not --git-since to control date selection
692 File ".get_maintainer.conf", if it exists in the linux kernel source root
693 directory, can change whatever get_maintainer defaults are desired.
694 Entries in this file can be any command line argument.
695 This file is prepended to any additional command line arguments.
696 Multiple lines and # comments are allowed.
700 sub top_of_kernel_tree
{
703 if ($lk_path ne "" && substr($lk_path,length($lk_path)-1,1) ne "/") {
706 if ( (-f
"${lk_path}COPYING")
707 && (-f
"${lk_path}CREDITS")
708 && (-f
"${lk_path}Kbuild")
709 && (-f
"${lk_path}MAINTAINERS")
710 && (-f
"${lk_path}Makefile")
711 && (-f
"${lk_path}README")
712 && (-d
"${lk_path}Documentation")
713 && (-d
"${lk_path}arch")
714 && (-d
"${lk_path}include")
715 && (-d
"${lk_path}drivers")
716 && (-d
"${lk_path}fs")
717 && (-d
"${lk_path}init")
718 && (-d
"${lk_path}ipc")
719 && (-d
"${lk_path}kernel")
720 && (-d
"${lk_path}lib")
721 && (-d
"${lk_path}scripts")) {
728 my ($formatted_email) = @_;
733 if ($formatted_email =~ /^([^<]+)<(.+\@.*)>.*$/) {
736 } elsif ($formatted_email =~ /^\s*<(.+\@\S*)>.*$/) {
738 } elsif ($formatted_email =~ /^(.+\@\S*).*$/) {
742 $name =~ s/^\s+|\s+$//g;
743 $name =~ s/^\"|\"$//g;
744 $address =~ s/^\s+|\s+$//g;
746 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
747 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
751 return ($name, $address);
755 my ($name, $address, $usename) = @_;
759 $name =~ s/^\s+|\s+$//g;
760 $name =~ s/^\"|\"$//g;
761 $address =~ s/^\s+|\s+$//g;
763 if ($name =~ /[^\w \-]/i) { ##has "must quote" chars
764 $name =~ s/(?<!\\)"/\\"/g; ##escape quotes
770 $formatted_email = "$address";
772 $formatted_email = "$name <$address>";
775 $formatted_email = $address;
778 return $formatted_email;
781 sub find_first_section
{
784 while ($index < @typevalue) {
785 my $tv = $typevalue[$index];
786 if (($tv =~ m/^(\C):\s*(.*)/)) {
795 sub find_starting_index
{
799 my $tv = $typevalue[$index];
800 if (!($tv =~ m/^(\C):\s*(.*)/)) {
809 sub find_ending_index
{
812 while ($index < @typevalue) {
813 my $tv = $typevalue[$index];
814 if (!($tv =~ m/^(\C):\s*(.*)/)) {
823 sub get_maintainer_role
{
827 my $start = find_starting_index
($index);
828 my $end = find_ending_index
($index);
831 my $subsystem = $typevalue[$start];
832 if (length($subsystem) > 20) {
833 $subsystem = substr($subsystem, 0, 17);
834 $subsystem =~ s/\s*$//;
835 $subsystem = $subsystem . "...";
838 for ($i = $start + 1; $i < $end; $i++) {
839 my $tv = $typevalue[$i];
840 if ($tv =~ m/^(\C):\s*(.*)/) {
850 if ($role eq "supported") {
852 } elsif ($role eq "maintained") {
853 $role = "maintainer";
854 } elsif ($role eq "odd fixes") {
856 } elsif ($role eq "orphan") {
857 $role = "orphan minder";
858 } elsif ($role eq "obsolete") {
859 $role = "obsolete minder";
860 } elsif ($role eq "buried alive in reporters") {
861 $role = "chief penguin";
864 return $role . ":" . $subsystem;
871 my $start = find_starting_index
($index);
872 my $end = find_ending_index
($index);
874 my $subsystem = $typevalue[$start];
875 if (length($subsystem) > 20) {
876 $subsystem = substr($subsystem, 0, 17);
877 $subsystem =~ s/\s*$//;
878 $subsystem = $subsystem . "...";
881 if ($subsystem eq "THE REST") {
892 my $start = find_starting_index
($index);
893 my $end = find_ending_index
($index);
895 push(@subsystem, $typevalue[$start]);
897 for ($i = $start + 1; $i < $end; $i++) {
898 my $tv = $typevalue[$i];
899 if ($tv =~ m/^(\C):\s*(.*)/) {
903 my $list_address = $pvalue;
904 my $list_additional = "";
905 my $list_role = get_list_role
($i);
907 if ($list_role ne "") {
908 $list_role = ":" . $list_role;
910 if ($list_address =~ m/([^\s]+)\s+(.*)$/) {
912 $list_additional = $2;
914 if ($list_additional =~ m/subscribers-only/) {
915 if ($email_subscriber_list) {
916 if (!$hash_list_to{lc($list_address)}) {
917 $hash_list_to{lc($list_address)} = 1;
918 push(@list_to, [$list_address,
919 "subscriber list${list_role}"]);
924 if (!$hash_list_to{lc($list_address)}) {
925 $hash_list_to{lc($list_address)} = 1;
926 push(@list_to, [$list_address,
927 "open list${list_role}"]);
931 } elsif ($ptype eq "M") {
932 my ($name, $address) = parse_email
($pvalue);
935 my $tv = $typevalue[$i - 1];
936 if ($tv =~ m/^(\C):\s*(.*)/) {
939 $pvalue = format_email
($name, $address, $email_usename);
944 if ($email_maintainer) {
945 my $role = get_maintainer_role
($i);
946 push_email_addresses
($pvalue, $role);
948 } elsif ($ptype eq "T") {
950 } elsif ($ptype eq "W") {
952 } elsif ($ptype eq "S") {
953 push(@status, $pvalue);
960 my ($name, $address) = @_;
962 return 1 if (($name eq "") && ($address eq ""));
963 return 1 if (($name ne "") && exists($email_hash_name{lc($name)}));
964 return 1 if (($address ne "") && exists($email_hash_address{lc($address)}));
969 sub push_email_address
{
970 my ($line, $role) = @_;
972 my ($name, $address) = parse_email
($line);
974 if ($address eq "") {
978 if (!$email_remove_duplicates) {
979 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
980 } elsif (!email_inuse
($name, $address)) {
981 push(@email_to, [format_email
($name, $address, $email_usename), $role]);
982 $email_hash_name{lc($name)}++;
983 $email_hash_address{lc($address)}++;
989 sub push_email_addresses
{
990 my ($address, $role) = @_;
992 my @address_list = ();
994 if (rfc822_valid
($address)) {
995 push_email_address
($address, $role);
996 } elsif (@address_list = rfc822_validlist
($address)) {
997 my $array_count = shift(@address_list);
998 while (my $entry = shift(@address_list)) {
999 push_email_address
($entry, $role);
1002 if (!push_email_address
($address, $role)) {
1003 warn("Invalid MAINTAINERS address: '" . $address . "'\n");
1009 my ($line, $role) = @_;
1011 my ($name, $address) = parse_email
($line);
1012 my $email = format_email
($name, $address, $email_usename);
1014 foreach my $entry (@email_to) {
1015 if ($email_remove_duplicates) {
1016 my ($entry_name, $entry_address) = parse_email
($entry->[0]);
1017 if (($name eq $entry_name || $address eq $entry_address)
1018 && ($role eq "" || !($entry->[1] =~ m/$role/))
1020 if ($entry->[1] eq "") {
1021 $entry->[1] = "$role";
1023 $entry->[1] = "$entry->[1],$role";
1027 if ($email eq $entry->[0]
1028 && ($role eq "" || !($entry->[1] =~ m/$role/))
1030 if ($entry->[1] eq "") {
1031 $entry->[1] = "$role";
1033 $entry->[1] = "$entry->[1],$role";
1043 foreach my $path (split(/:/, $ENV{PATH
})) {
1044 if (-e
"$path/$bin") {
1045 return "$path/$bin";
1055 foreach my $path (split(/:/, ".:$ENV{HOME}:.scripts")) {
1056 if (-e
"$path/$conf") {
1057 return "$path/$conf";
1068 foreach my $line (@lines) {
1069 my ($name, $address) = parse_email
($line);
1070 if (!exists($hash{$name})) {
1071 $hash{$name} = $address;
1072 } elsif ($address ne $hash{$name}) {
1073 $address = $hash{$name};
1074 $line = format_email
($name, $address, $email_usename);
1076 if (exists($mailmap{$name})) {
1077 my $obj = $mailmap{$name};
1078 foreach my $map_address (@
$obj) {
1079 if (($map_address eq $address) &&
1080 ($map_address ne $hash{$name})) {
1081 $line = format_email
($name, $hash{$name}, $email_usename);
1090 sub git_execute_cmd
{
1094 my $output = `$cmd`;
1095 $output =~ s/^\s*//gm;
1096 @lines = split("\n", $output);
1101 sub hg_execute_cmd
{
1105 my $output = `$cmd`;
1106 @lines = split("\n", $output);
1111 sub extract_formatted_signatures
{
1112 my (@signature_lines) = @_;
1114 my @type = @signature_lines;
1116 s/\s*(.*):.*/$1/ for (@type);
1119 s/\s*.*:\s*(.+)\s*/$1/ for (@signature_lines);
1121 ## Reformat email addresses (with names) to avoid badly written signatures
1123 foreach my $signer (@signature_lines) {
1124 my ($name, $address) = parse_email
($signer);
1125 $signer = format_email
($name, $address, 1);
1128 return (\
@type, \
@signature_lines);
1131 sub vcs_find_signers
{
1135 my @signatures = ();
1137 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1139 my $pattern = $VCS_cmds{"commit_pattern"};
1141 $commits = grep(/$pattern/, @lines); # of commits
1143 @signatures = grep(/^[ \t]*${signature_pattern}.*\@.*$/, @lines);
1145 return (0, @signatures) if !@signatures;
1147 save_commits_by_author
(@lines) if ($interactive);
1148 save_commits_by_signer
(@lines) if ($interactive);
1150 if (!$email_git_penguin_chiefs) {
1151 @signatures = grep(!/${penguin_chiefs}/i, @signatures);
1154 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
1156 return ($commits, @
$signers_ref);
1159 sub vcs_find_author
{
1163 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1165 if (!$email_git_penguin_chiefs) {
1166 @lines = grep(!/${penguin_chiefs}/i, @lines);
1169 return @lines if !@lines;
1172 foreach my $line (@lines) {
1173 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1175 my ($name, $address) = parse_email
($author);
1176 $author = format_email
($name, $address, 1);
1177 push(@authors, $author);
1181 save_commits_by_author
(@lines) if ($interactive);
1182 save_commits_by_signer
(@lines) if ($interactive);
1187 sub vcs_save_commits
{
1192 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1194 foreach my $line (@lines) {
1195 if ($line =~ m/$VCS_cmds{"blame_commit_pattern"}/) {
1208 return @commits if (!(-f
$file));
1210 if (@range && $VCS_cmds{"blame_range_cmd"} eq "") {
1211 my @all_commits = ();
1213 $cmd = $VCS_cmds{"blame_file_cmd"};
1214 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1215 @all_commits = vcs_save_commits
($cmd);
1217 foreach my $file_range_diff (@range) {
1218 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1220 my $diff_start = $2;
1221 my $diff_length = $3;
1222 next if ("$file" ne "$diff_file");
1223 for (my $i = $diff_start; $i < $diff_start + $diff_length; $i++) {
1224 push(@commits, $all_commits[$i]);
1228 foreach my $file_range_diff (@range) {
1229 next if (!($file_range_diff =~ m/(.+):(.+):(.+)/));
1231 my $diff_start = $2;
1232 my $diff_length = $3;
1233 next if ("$file" ne "$diff_file");
1234 $cmd = $VCS_cmds{"blame_range_cmd"};
1235 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1236 push(@commits, vcs_save_commits
($cmd));
1239 $cmd = $VCS_cmds{"blame_file_cmd"};
1240 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1241 @commits = vcs_save_commits
($cmd);
1244 foreach my $commit (@commits) {
1245 $commit =~ s/^\^//g;
1251 my $printed_novcs = 0;
1253 %VCS_cmds = %VCS_cmds_git;
1254 return 1 if eval $VCS_cmds{"available"};
1255 %VCS_cmds = %VCS_cmds_hg;
1256 return 2 if eval $VCS_cmds{"available"};
1258 if (!$printed_novcs) {
1259 warn("$P: No supported VCS found. Add --nogit to options?\n");
1260 warn("Using a git repository produces better results.\n");
1261 warn("Try Linus Torvalds' latest git repository using:\n");
1262 warn("git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git\n");
1269 return $vcs_used == 1;
1273 return $vcs_used == 2;
1276 sub interactive_get_maintainers
{
1277 my ($list_ref) = @_;
1278 my @list = @
$list_ref;
1287 #select maintainers by default
1288 foreach my $entry (@list) {
1289 my $role = $entry->[1];
1290 $selected{$count} = ($role =~ /^(maintainer|supporter|open list)/i);
1291 $maintained = 1 if ($role =~ /^(maintainer|supporter)/i);
1292 $authored{$count} = 0;
1293 $signed{$count} = 0;
1299 my $print_options = 0;
1304 printf STDERR
"\n%1s %2s %-65s",
1305 "*", "#", "email/list and role:stats";
1307 ($email_git_fallback && !$maintained) ||
1309 print STDERR
"auth sign";
1312 foreach my $entry (@list) {
1313 my $email = $entry->[0];
1314 my $role = $entry->[1];
1316 $sel = "*" if ($selected{$count});
1317 my $commit_author = $commit_author_hash{$email};
1318 my $commit_signer = $commit_signer_hash{$email};
1321 $authored++ for (@
{$commit_author});
1322 $signed++ for (@
{$commit_signer});
1323 printf STDERR
"%1s %2d %-65s", $sel, $count + 1, $email;
1324 printf STDERR
"%4d %4d", $authored, $signed
1325 if ($authored > 0 || $signed > 0);
1326 printf STDERR
"\n %s\n", $role;
1327 if ($authored{$count}) {
1328 my $commit_author = $commit_author_hash{$email};
1329 foreach my $ref (@
{$commit_author}) {
1330 print STDERR
" Author: @{$ref}[1]\n";
1333 if ($signed{$count}) {
1334 my $commit_signer = $commit_signer_hash{$email};
1335 foreach my $ref (@
{$commit_signer}) {
1336 print STDERR
" @{$ref}[2]: @{$ref}[1]\n";
1343 my $date_ref = \
$email_git_since;
1344 $date_ref = \
$email_hg_since if (vcs_is_hg
());
1345 if ($print_options) {
1349 "\nVersion Control options:\n" .
1350 "g use git history [$email_git]\n" .
1351 "gf use git-fallback [$email_git_fallback]\n" .
1352 "b use git blame [$email_git_blame]\n" .
1353 "bs use blame signatures [$email_git_blame_signatures]\n" .
1354 "c# minimum commits [$email_git_min_signatures]\n" .
1355 "%# min percent [$email_git_min_percent]\n" .
1356 "d# history to use [$$date_ref]\n" .
1357 "x# max maintainers [$email_git_max_maintainers]\n" .
1358 "t all signature types [$email_git_all_signature_types]\n";
1360 print STDERR
"\nAdditional options:\n" .
1362 "f emails in file [$file_emails]\n" .
1363 "k keywords in file [$keywords]\n" .
1364 "r remove duplicates [$email_remove_duplicates]\n" .
1365 "p# pattern match depth [$pattern_depth]\n";
1368 "\n#(toggle), A#(author), S#(signed) *(all), ^(none), O(options), Y(approve): ";
1370 my $input = <STDIN
>;
1375 my @wish = split(/[, ]+/, $input);
1376 foreach my $nr (@wish) {
1378 my $sel = substr($nr, 0, 1);
1379 my $str = substr($nr, 1);
1381 $val = $1 if $str =~ /^(\d+)$/;
1386 $output_rolestats = 0;
1389 } elsif ($nr =~ /^\d+$/ && $nr > 0 && $nr <= $count) {
1390 $selected{$nr - 1} = !$selected{$nr - 1};
1391 } elsif ($sel eq "*" || $sel eq '^') {
1393 $toggle = 1 if ($sel eq '*');
1394 for (my $i = 0; $i < $count; $i++) {
1395 $selected{$i} = $toggle;
1397 } elsif ($sel eq "0") {
1398 for (my $i = 0; $i < $count; $i++) {
1399 $selected{$i} = !$selected{$i};
1401 } elsif ($sel eq "a") {
1402 if ($val > 0 && $val <= $count) {
1403 $authored{$val - 1} = !$authored{$val - 1};
1404 } elsif ($str eq '*' || $str eq '^') {
1406 $toggle = 1 if ($str eq '*');
1407 for (my $i = 0; $i < $count; $i++) {
1408 $authored{$i} = $toggle;
1411 } elsif ($sel eq "s") {
1412 if ($val > 0 && $val <= $count) {
1413 $signed{$val - 1} = !$signed{$val - 1};
1414 } elsif ($str eq '*' || $str eq '^') {
1416 $toggle = 1 if ($str eq '*');
1417 for (my $i = 0; $i < $count; $i++) {
1418 $signed{$i} = $toggle;
1421 } elsif ($sel eq "o") {
1424 } elsif ($sel eq "g") {
1426 bool_invert
(\
$email_git_fallback);
1428 bool_invert
(\
$email_git);
1431 } elsif ($sel eq "b") {
1433 bool_invert
(\
$email_git_blame_signatures);
1435 bool_invert
(\
$email_git_blame);
1438 } elsif ($sel eq "c") {
1440 $email_git_min_signatures = $val;
1443 } elsif ($sel eq "x") {
1445 $email_git_max_maintainers = $val;
1448 } elsif ($sel eq "%") {
1449 if ($str ne "" && $val >= 0) {
1450 $email_git_min_percent = $val;
1453 } elsif ($sel eq "d") {
1455 $email_git_since = $str;
1456 } elsif (vcs_is_hg
()) {
1457 $email_hg_since = $str;
1460 } elsif ($sel eq "t") {
1461 bool_invert
(\
$email_git_all_signature_types);
1463 } elsif ($sel eq "f") {
1464 bool_invert
(\
$file_emails);
1466 } elsif ($sel eq "r") {
1467 bool_invert
(\
$email_remove_duplicates);
1469 } elsif ($sel eq "k") {
1470 bool_invert
(\
$keywords);
1472 } elsif ($sel eq "p") {
1473 if ($str ne "" && $val >= 0) {
1474 $pattern_depth = $val;
1477 } elsif ($sel eq "h" || $sel eq "?") {
1480 Interactive mode allows you to select the various maintainers, submitters,
1481 commit signers and mailing lists that could be CC'd on a patch.
1483 Any *'d entry is selected.
1485 If you have git or hg installed, You can choose to summarize the commit
1486 history of files in the patch. Also, each line of the current file can
1487 be matched to its commit author and that commits signers with blame.
1489 Various knobs exist to control the length of time for active commit
1490 tracking, the maximum number of commit authors and signers to add,
1493 Enter selections at the prompt until you are satisfied that the selected
1494 maintainers are appropriate. You may enter multiple selections separated
1495 by either commas or spaces.
1499 print STDERR
"invalid option: '$nr'\n";
1504 print STDERR
"git-blame can be very slow, please have patience..."
1505 if ($email_git_blame);
1506 goto &get_maintainers
;
1510 #drop not selected entries
1512 my @new_emailto = ();
1513 foreach my $entry (@list) {
1514 if ($selected{$count}) {
1515 push(@new_emailto, $list[$count]);
1519 return @new_emailto;
1523 my ($bool_ref) = @_;
1532 sub save_commits_by_author
{
1539 foreach my $line (@lines) {
1540 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1543 my ($name, $address) = parse_email
($author);
1544 foreach my $to (@interactive_to) {
1545 my ($to_name, $to_address) = parse_email
($to->[0]);
1546 if ($email_remove_duplicates &&
1547 ((lc($name) eq lc($to_name)) ||
1548 (lc($address) eq lc($to_address)))) {
1554 $author = format_email
($name, $address, 1) if (!$matched);
1555 push(@authors, $author);
1557 push(@commits, $1) if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1558 push(@subjects, $1) if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1561 for (my $i = 0; $i < @authors; $i++) {
1563 foreach my $ref(@
{$commit_author_hash{$authors[$i]}}) {
1564 if (@
{$ref}[0] eq $commits[$i] &&
1565 @
{$ref}[1] eq $subjects[$i]) {
1571 push(@
{$commit_author_hash{$authors[$i]}},
1572 [ ($commits[$i], $subjects[$i]) ]);
1577 sub save_commits_by_signer
{
1583 foreach my $line (@lines) {
1584 $commit = $1 if ($line =~ m/$VCS_cmds{"commit_pattern"}/);
1585 $subject = $1 if ($line =~ m/$VCS_cmds{"subject_pattern"}/);
1586 if ($line =~ /^[ \t]*${signature_pattern}.*\@.*$/) {
1587 my @signatures = ($line);
1588 my ($types_ref, $signers_ref) = extract_formatted_signatures
(@signatures);
1589 my @types = @
$types_ref;
1590 my @signers = @
$signers_ref;
1592 my $type = $types[0];
1593 my $signer = $signers[0];
1596 my ($name, $address) = parse_email
($signer);
1597 foreach my $to (@interactive_to) {
1598 my ($to_name, $to_address) = parse_email
($to->[0]);
1599 if ($email_remove_duplicates &&
1600 ((lc($name) eq lc($to_name)) ||
1601 (lc($address) eq lc($to_address)))) {
1606 $signer = format_email
($name, $address, 1) if (!$matched);
1610 foreach my $ref(@
{$commit_signer_hash{$signer}}) {
1611 if (@
{$ref}[0] eq $commit &&
1612 @
{$ref}[1] eq $subject &&
1613 @
{$ref}[2] eq $type) {
1619 push(@
{$commit_signer_hash{$signer}},
1620 [ ($commit, $subject, $type) ]);
1627 my ($role, $divisor, @lines) = @_;
1632 return if (@lines <= 0);
1634 if ($divisor <= 0) {
1635 warn("Bad divisor in " . (caller(0))[3] . ": $divisor\n");
1639 if ($email_remove_duplicates) {
1640 @lines = mailmap
(@lines);
1643 return if (@lines <= 0);
1645 @lines = sort(@lines);
1648 $hash{$_}++ for @lines;
1651 foreach my $line (sort {$hash{$b} <=> $hash{$a}} keys %hash) {
1652 my $sign_offs = $hash{$line};
1653 my $percent = $sign_offs * 100 / $divisor;
1655 $percent = 100 if ($percent > 100);
1657 last if ($sign_offs < $email_git_min_signatures ||
1658 $count > $email_git_max_maintainers ||
1659 $percent < $email_git_min_percent);
1660 push_email_address
($line, '');
1661 if ($output_rolestats) {
1662 my $fmt_percent = sprintf("%.0f", $percent);
1663 add_role
($line, "$role:$sign_offs/$divisor=$fmt_percent%");
1665 add_role
($line, $role);
1670 sub vcs_file_signoffs
{
1676 $vcs_used = vcs_exists
();
1677 return if (!$vcs_used);
1679 my $cmd = $VCS_cmds{"find_signers_cmd"};
1680 $cmd =~ s/(\$\w+)/$1/eeg; # interpolate $cmd
1682 ($commits, @signers) = vcs_find_signers
($cmd);
1683 vcs_assign
("commit_signer", $commits, @signers);
1686 sub vcs_file_blame
{
1690 my @all_commits = ();
1695 $vcs_used = vcs_exists
();
1696 return if (!$vcs_used);
1698 @all_commits = vcs_blame
($file);
1699 @commits = uniq
(@all_commits);
1700 $total_commits = @commits;
1701 $total_lines = @all_commits;
1703 if ($email_git_blame_signatures) {
1706 my @commit_signers = ();
1707 my $commit = join(" -r ", @commits);
1710 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1711 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1713 ($commit_count, @commit_signers) = vcs_find_signers
($cmd);
1715 push(@signers, @commit_signers);
1717 foreach my $commit (@commits) {
1719 my @commit_signers = ();
1722 $cmd = $VCS_cmds{"find_commit_signers_cmd"};
1723 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1725 ($commit_count, @commit_signers) = vcs_find_signers
($cmd);
1727 push(@signers, @commit_signers);
1732 if ($from_filename) {
1733 if ($output_rolestats) {
1735 if (vcs_is_hg
()) {{ # Double brace for last exit
1737 my @commit_signers = ();
1738 @commits = uniq
(@commits);
1739 @commits = sort(@commits);
1740 my $commit = join(" -r ", @commits);
1743 $cmd = $VCS_cmds{"find_commit_author_cmd"};
1744 $cmd =~ s/(\$\w+)/$1/eeg; #substitute variables in $cmd
1748 @lines = &{$VCS_cmds{"execute_cmd"}}($cmd);
1750 if (!$email_git_penguin_chiefs) {
1751 @lines = grep(!/${penguin_chiefs}/i, @lines);
1757 foreach my $line (@lines) {
1758 if ($line =~ m/$VCS_cmds{"author_pattern"}/) {
1760 my ($name, $address) = parse_email
($author);
1761 $author = format_email
($name, $address, 1);
1766 save_commits_by_author
(@lines) if ($interactive);
1767 save_commits_by_signer
(@lines) if ($interactive);
1769 push(@signers, @authors);
1772 foreach my $commit (@commits) {
1774 my $cmd = $VCS_cmds{"find_commit_author_cmd"};
1775 $cmd =~ s/(\$\w+)/$1/eeg; #interpolate $cmd
1776 my @author = vcs_find_author
($cmd);
1778 my $count = grep(/$commit/, @all_commits);
1779 for ($i = 0; $i < $count ; $i++) {
1780 push(@blame_signers, $author[0]);
1784 if (@blame_signers) {
1785 vcs_assign
("authored lines", $total_lines, @blame_signers);
1788 vcs_assign
("commits", $total_commits, @signers);
1790 vcs_assign
("modified commits", $total_commits, @signers);
1798 @parms = grep(!$saw{$_}++, @parms);
1806 @parms = sort @parms;
1807 @parms = grep(!$saw{$_}++, @parms);
1811 sub clean_file_emails
{
1812 my (@file_emails) = @_;
1813 my @fmt_emails = ();
1815 foreach my $email (@file_emails) {
1816 $email =~ s/[\(\<\{]{0,1}([A-Za-z0-9_\.\+-]+\@[A-Za-z0-9\.-]+)[\)\>\}]{0,1}/\<$1\>/g;
1817 my ($name, $address) = parse_email
($email);
1818 if ($name eq '"[,\.]"') {
1822 my @nw = split(/[^A-Za-zÀ-ÿ\'\,\.\+-]/, $name);
1824 my $first = $nw[@nw - 3];
1825 my $middle = $nw[@nw - 2];
1826 my $last = $nw[@nw - 1];
1828 if (((length($first) == 1 && $first =~ m/[A-Za-z]/) ||
1829 (length($first) == 2 && substr($first, -1) eq ".")) ||
1830 (length($middle) == 1 ||
1831 (length($middle) == 2 && substr($middle, -1) eq "."))) {
1832 $name = "$first $middle $last";
1834 $name = "$middle $last";
1838 if (substr($name, -1) =~ /[,\.]/) {
1839 $name = substr($name, 0, length($name) - 1);
1840 } elsif (substr($name, -2) =~ /[,\.]"/) {
1841 $name = substr($name, 0, length($name) - 2) . '"';
1844 if (substr($name, 0, 1) =~ /[,\.]/) {
1845 $name = substr($name, 1, length($name) - 1);
1846 } elsif (substr($name, 0, 2) =~ /"[,\.]/) {
1847 $name = '"' . substr($name, 2, length($name) - 2);
1850 my $fmt_email = format_email
($name, $address, $email_usename);
1851 push(@fmt_emails, $fmt_email);
1861 my ($address, $role) = @
$_;
1862 if (!$saw{$address}) {
1863 if ($output_roles) {
1864 push(@lines, "$address ($role)");
1866 push(@lines, $address);
1878 if ($output_multiline) {
1879 foreach my $line (@parms) {
1883 print(join($output_separator, @parms));
1891 # Basic lexical tokens are specials, domain_literal, quoted_string, atom, and
1892 # comment. We must allow for rfc822_lwsp (or comments) after each of these.
1893 # This regexp will only work on addresses which have had comments stripped
1894 # and replaced with rfc822_lwsp.
1896 my $specials = '()<>@,;:\\\\".\\[\\]';
1897 my $controls = '\\000-\\037\\177';
1899 my $dtext = "[^\\[\\]\\r\\\\]";
1900 my $domain_literal = "\\[(?:$dtext|\\\\.)*\\]$rfc822_lwsp*";
1902 my $quoted_string = "\"(?:[^\\\"\\r\\\\]|\\\\.|$rfc822_lwsp)*\"$rfc822_lwsp*";
1904 # Use zero-width assertion to spot the limit of an atom. A simple
1905 # $rfc822_lwsp* causes the regexp engine to hang occasionally.
1906 my $atom = "[^$specials $controls]+(?:$rfc822_lwsp+|\\Z|(?=[\\[\"$specials]))";
1907 my $word = "(?:$atom|$quoted_string)";
1908 my $localpart = "$word(?:\\.$rfc822_lwsp*$word)*";
1910 my $sub_domain = "(?:$atom|$domain_literal)";
1911 my $domain = "$sub_domain(?:\\.$rfc822_lwsp*$sub_domain)*";
1913 my $addr_spec = "$localpart\@$rfc822_lwsp*$domain";
1915 my $phrase = "$word*";
1916 my $route = "(?:\@$domain(?:,\@$rfc822_lwsp*$domain)*:$rfc822_lwsp*)";
1917 my $route_addr = "\\<$rfc822_lwsp*$route?$addr_spec\\>$rfc822_lwsp*";
1918 my $mailbox = "(?:$addr_spec|$phrase$route_addr)";
1920 my $group = "$phrase:$rfc822_lwsp*(?:$mailbox(?:,\\s*$mailbox)*)?;\\s*";
1921 my $address = "(?:$mailbox|$group)";
1923 return "$rfc822_lwsp*$address";
1926 sub rfc822_strip_comments
{
1928 # Recursively remove comments, and replace with a single space. The simpler
1929 # regexps in the Email Addressing FAQ are imperfect - they will miss escaped
1930 # chars in atoms, for example.
1932 while ($s =~ s
/^((?
:[^"\\]|\\.)*
1933 (?:"(?
:[^"\\]|\\.)*"(?
:[^"\\]|\\.)*)*)
1934 \((?:[^()\\]|\\.)*\)/$1 /osx) {}
1938 # valid: returns true if the parameter is an RFC822 valid address
1941 my $s = rfc822_strip_comments(shift);
1944 $rfc822re = make_rfc822re();
1947 return $s =~ m/^$rfc822re$/so && $s =~ m/^$rfc822_char*$/;
1950 # validlist: In scalar context, returns true if the parameter is an RFC822
1951 # valid list of addresses.
1953 # In list context, returns an empty list on failure (an invalid
1954 # address was found); otherwise a list whose first element is the
1955 # number of addresses found and whose remaining elements are the
1956 # addresses. This is needed to disambiguate failure (invalid)
1957 # from success with no addresses found, because an empty string is
1960 sub rfc822_validlist {
1961 my $s = rfc822_strip_comments(shift);
1964 $rfc822re = make_rfc822re();
1966 # * null list items are valid according to the RFC
1967 # * the '1' business is to aid in distinguishing failure from no results
1970 if ($s =~ m/^(?:$rfc822re)?(?:,(?:$rfc822re)?)*$/so &&
1971 $s =~ m/^$rfc822_char*$/) {
1972 while ($s =~ m/(?:^|,$rfc822_lwsp*)($rfc822re)/gos) {
1975 return wantarray ? (scalar(@r), @r) : 1;
1977 return wantarray ? () : 0;