Clean up the HTML output. Add a -filedetails option to report the number of
[oota-llvm.git] / utils / userloc.pl
1 #!/usr/bin/perl -w
2 #
3 # Program:  userloc.pl
4 #
5 # Synopsis: This program uses "cvs annotate" to get a summary of how many lines
6 #           of code the various developres are responsible for. It takes one
7 #           argument, the directory to process. If the argument is not specified
8 #           then the cwd is used. The directory must be an LLVM tree checked out
9 #           from cvs. 
10 #
11 # Syntax:   userloc.pl [-recurse|-tag=tag|-html... <directory>...
12 #
13 # Options:
14 #           -recurse
15 #               Recurse through sub directories. Without this, only the
16 #               specified directory is examined
17 #           -tag=tag
18 #               Use "tag" to select the revision (as per cvs -r option)
19 #           -filedetails
20 #               Report details about lines of code in each file for each user
21 #           -html
22 #               Generate HTML output instead of text output
23
24 die "Usage userloc.pl [-recurse|-tag=tag|-html] <directories>..." 
25   if ($#ARGV < 0);
26
27 my $tag = "";
28 my $recurse = 0;
29 my $html = 0;
30 my $debug = 0;
31 my $filedetails = "";
32 while ( substr($ARGV[0],0,1) eq '-' )
33 {
34   if ($ARGV[0] eq "-recurse") {
35     $recurse = 1;
36   } elsif ($ARGV[0] =~ /-tag=.*/) {
37     $tag = $ARGV[0];
38     $tag =~ s#-tag=(.*)#$1#;
39   } elsif ($ARGV[0] =~ /-filedetails/) {
40     $filedetails = 1;
41   } elsif ($ARGV[0] eq "-html") {
42     $html = 1;
43   } elsif ($ARGV[0] eq "-debug") {
44     $debug = 1;
45   } else {
46     die "Invalid option: $ARGV[0]";
47   }
48   shift;
49 }
50
51 die "Usage userloc.pl [-recurse|-tag=tag|-html] <directories>..." 
52   if ($#ARGV < 0);
53
54 my %Stats;
55 my %FileStats;
56
57 sub ValidateFile
58 {
59   my $f = $_[0];
60   my $d = $_[1];
61
62   if ( $d =~ ".*autoconf.*")
63   {
64     return 1 if ($f eq "configure.ac");
65     return 1 if ($f eq "AutoRegen.sh");
66     return 0;
67   }
68   return 0 if ( "$f" eq "configure");
69   return 0 if ( "$f" eq 'PPCPerfectShuffle.h' );
70   return 0 if ( $f =~ /.*\.cvs/);
71   return 1;
72 }
73
74 sub GetCVSFiles
75 {
76   my $d = $_[0];
77   my $files ="";
78   open STATUS, "cvs -nfz6 status $d -l 2>/dev/null |" 
79     || die "Can't 'cvs status'";
80   while ( defined($line = <STATUS>) )
81   {
82     if ( $line =~ /^File:.*/ )
83     {
84       chomp($line);
85       $line =~ s#^File: ([A-Za-z0-9._-]*)[ \t]*Status:.*#$1#;
86       $files = "$files $d/$line" if (ValidateFile($line,$d));
87     }
88
89   }
90   return $files;
91 }
92
93 my $annotate = "cvs -z6 annotate -lf ";
94 if (length($tag) > 0)
95 {
96   $annotate = $annotate . " -r" . $tag;
97 }
98
99 sub ScanDir
100 {
101   my $Dir = $_[0];
102   my $files = GetCVSFiles($Dir);
103
104   open (DATA,"$annotate $files 2>&1 |")
105     || die "Can't read cvs annotation data";
106
107   my $curfile = "";
108   while ( defined($line = <DATA>) )
109   {
110     chomp($line);
111     if ($line =~ '^Annotations for.*') {
112       $curfile = $line;
113       $curfile =~ s#^Annotations for ([[:print:]]*)#$1#;
114     } elsif ($line =~ /^[0-9.]*[ \t]*\([^)]*\):/) {
115       $uname = $line;
116       $uname =~ s#^[0-9.]*[ \t]*\(([a-zA-Z0-9_.-]*) [^)]*\):.*#$1#;
117       $Stats{$uname}++;
118       if ($filedetails) {
119         $FileStats{$uname} = {} unless exists $FileStats{$uname};
120         ${$FileStats{$uname}}{$curfile}++;
121       }
122     }
123   }
124   close DATA;
125 }
126
127 sub ValidateDirectory
128 {
129   my $d = $_[0];
130   return 0 if (! -d "$d" || ! -d "$d/CVS");
131   return 0 if ($d =~ /.*CVS.*/);
132   return 0 if ($d =~ /.*Debug.*/);
133   return 0 if ($d =~ /.*Release.*/);
134   return 0 if ($d =~ /.*Profile.*/);
135   return 0 if ($d =~ /.*docs\/CommandGuide\/html.*/);
136   return 0 if ($d =~ /.*docs\/CommandGuide\/man.*/);
137   return 0 if ($d =~ /.*docs\/CommandGuide\/ps.*/);
138   return 0 if ($d =~ /.*docs\/CommandGuide\/man.*/);
139   return 0 if ($d =~ /.*docs\/HistoricalNotes.*/);
140   return 0 if ($d =~ /.*docs\/img.*/);
141   return 0 if ($d =~ /.*bzip2.*/);
142   return 1 if ($d =~ /.*projects\/Stacker.*/);
143   return 1 if ($d =~ /.*projects\/sample.*/);
144   return 0 if ($d =~ /.*projects\/llvm-.*/);
145   return 0 if ($d =~ /.*win32.*/);
146   return 0 if ($d =~ /.*\/.libs\/.*/);
147   return 1;
148 }
149
150 sub printStats
151 {
152   my $dir = $_[0];
153   my $hash = $_[1];
154   my $usr;
155   my $total = 0;
156
157   foreach $usr (keys %Stats) { $total += $Stats{$usr}; }
158
159   if ($html) { 
160     print "<table>";
161     print " <tr><th style=\"text-align:right\">LOC</th>\n";
162     print " <th style=\"text-align:right\">\%LOC</th>\n";
163     print " <th style=\"text-align:left\">User</th>\n";
164     print "</tr>\n";
165   }
166
167   foreach $usr ( sort keys %Stats )
168   {
169     my $v = $Stats{$usr};
170     if (defined($v))
171     {
172       if ($html) {
173         printf "<tr><td style=\"text-align:right\">%d</td><td style=\"text-align:right\">(%4.1f%%)</td><td style=\"text-align:left\">%s</td></tr>", $v, (100.0/$total)*$v,$usr;
174       } else {
175         printf "%8d  (%4.1f%%)  %s\n", $v, (100.0/$total)*$v, $usr;
176       }
177     }
178   }
179   print "</table>\n" if ($html);
180
181   if ($filedetails) {
182     foreach $user (sort keys %FileStats) {
183       my $total = 0;
184       foreach $file (sort keys %{$FileStats{$user}}) { 
185         $total += ${$FileStats{$user}}{$file}
186       }
187       if ($html) {
188         print "<table><tr><th style=\"text-align:left\" colspan=\"3\">$user</th></tr>\n";
189       } else {
190         print $user,":\n";
191       }
192       foreach $file (sort keys %{$FileStats{$user}}) {
193         my $v = ${$FileStats{$user}}{$file};
194         if ($html) { 
195           printf "<tr><td style=\"text-align:right\">&nbsp;&nbsp;%d</td><td
196           style=\"text-align:right\">&nbsp;%4.1f%%</td><td
197           style=\"text-align:left\">%s</td></tr>",$v, (100.0/$total)*$v,$file;
198         } else {
199           printf "%8d  (%4.1f%%)  %s\n", $v, (100.0/$total)*$v, $file;
200         }
201       }
202       if ($html) { print "</table>\n"; }
203     }
204   }
205 }
206
207 my @ALLDIRS = @ARGV;
208
209 if ($recurse)
210 {
211   $Dirs = join(" ", @ARGV);
212   $Dirs = `find $Dirs -type d \! -name CVS -print`;
213   @ALLDIRS = split(' ',$Dirs);
214 }
215
216 if ($html)
217 {
218 print "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n";
219 print "<html>\n<head>\n";
220 print "  <title>LLVM LOC Based On CVS Annotation</title>\n";
221 print "  <link rel=\"stylesheet\" href=\"llvm.css\" type=\"text/css\"/>\n";
222 print "</head>\n";
223 print "<body><div class=\"doc_title\">LLVM LOC Based On CVS Annotation</div>\n";
224 print "<p>This document shows the total lines of code per user in each\n";
225 print "LLVM directory. Lines of code are attributed by the user that last\n";
226 print "committed the line. This does not necessarily reflect authorship.</p>\n";
227 }
228
229 my @ignored_dirs;
230
231 for $Dir (@ALLDIRS) 
232
233   if ( ValidateDirectory($Dir) )
234   {
235     ScanDir($Dir); 
236   }
237   elsif ($html)
238   {
239     push @ignored_dirs, $Dir;
240   }
241 }
242
243 printStats;
244
245 if ($html) 
246 {
247   if (scalar @ignored_dirs > 0) {
248     print "<p>The following directories were skipped:</p>\n";
249     print "<ol>\n";
250     foreach $index (0 .. $#ignored_dirs) {
251       print " <li>", $ignored_dirs[$index], "</li>\n";
252     }
253     print "</ol>\n";
254   }
255   print "</body></html>\n";
256 }