Home | History | Annotate | Download | only in scripts
      1 #!/usr/bin/ksh -p
      2 #
      3 # CDDL HEADER START
      4 #
      5 # The contents of this file are subject to the terms of the
      6 # Common Development and Distribution License (the "License").
      7 # You may not use this file except in compliance with the License.
      8 #
      9 # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
     10 # or http://www.opensolaris.org/os/licensing.
     11 # See the License for the specific language governing permissions
     12 # and limitations under the License.
     13 #
     14 # When distributing Covered Code, include this CDDL HEADER in each
     15 # file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     16 # If applicable, add the following below this CDDL HEADER, with the
     17 # fields enclosed by brackets "[]" replaced with your own identifying
     18 # information: Portions Copyright [yyyy] [name of copyright owner]
     19 #
     20 # CDDL HEADER END
     21 #
     22 
     23 #
     24 # Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
     25 # Use is subject to license terms.
     26 #
     27 
     28 #
     29 # This script takes a file list and a workspace and builds a set of html files
     30 # suitable for doing a code review of source changes via a web page.
     31 # Documentation is available via the manual page, webrev.1, or just
     32 # type 'webrev -h'.
     33 #
     34 # Acknowledgements to contributors to webrev are listed in the webrev(1)
     35 # man page.
     36 #
     37 
     38 REMOVED_COLOR=brown
     39 CHANGED_COLOR=blue
     40 NEW_COLOR=blue
     41 
     42 HTML='<?xml version="1.0"?>
     43 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     44     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
     45 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
     46 
     47 FRAMEHTML='<?xml version="1.0"?>
     48 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
     49     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
     50 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">\n'
     51 
     52 STDHEAD='<meta http-equiv="cache-control" content="no-cache"></meta>
     53 <meta http-equiv="Pragma" content="no-cache"></meta>
     54 <meta http-equiv="Expires" content="-1"></meta>
     55 <!--
     56    Note to customizers: the body of the webrev is IDed as SUNWwebrev
     57    to allow easy overriding by users of webrev via the userContent.css
     58    mechanism available in some browsers.
     59 
     60    For example, to have all "removed" information be red instead of
     61    brown, set a rule in your userContent.css file like:
     62 
     63        body#SUNWwebrev span.removed { color: red ! important; }
     64 -->
     65 <style type="text/css" media="screen">
     66 body {
     67     background-color: #eeeeee;
     68 }
     69 hr {
     70     border: none 0;
     71     border-top: 1px solid #aaa;
     72     height: 1px;
     73 }
     74 div.summary {
     75     font-size: .8em;
     76     border-bottom: 1px solid #aaa;
     77     padding-left: 1em;
     78     padding-right: 1em;
     79 }
     80 div.summary h2 {
     81     margin-bottom: 0.3em;
     82 }
     83 div.summary table th {
     84     text-align: right;
     85     vertical-align: top;
     86     white-space: nowrap;
     87 }
     88 span.lineschanged {
     89     font-size: 0.7em;
     90 }
     91 span.oldmarker {
     92     color: red;
     93     font-size: large;
     94     font-weight: bold;
     95 }
     96 span.newmarker {
     97     color: green;
     98     font-size: large;
     99     font-weight: bold;
    100 }
    101 span.removed {
    102     color: brown;
    103 }
    104 span.changed {
    105     color: blue;
    106 }
    107 span.new {
    108     color: blue;
    109     font-weight: bold;
    110 }
    111 span.chmod {
    112     font-size: 0.7em;
    113     color: #db7800;
    114 }
    115 a.print { font-size: x-small; }
    116 a:hover { background-color: #ffcc99; }
    117 </style>
    118 
    119 <style type="text/css" media="print">
    120 pre { font-size: 0.8em; font-family: courier, monospace; }
    121 span.removed { color: #444; font-style: italic }
    122 span.changed { font-weight: bold; }
    123 span.new { font-weight: bold; }
    124 span.newmarker { font-size: 1.2em; font-weight: bold; }
    125 span.oldmarker { font-size: 1.2em; font-weight: bold; }
    126 a.print {display: none}
    127 hr { border: none 0; border-top: 1px solid #aaa; height: 1px; }
    128 </style>
    129 '
    130 
    131 #
    132 # UDiffs need a slightly different CSS rule for 'new' items (we don't
    133 # want them to be bolded as we do in cdiffs or sdiffs).
    134 #
    135 UDIFFCSS='
    136 <style type="text/css" media="screen">
    137 span.new {
    138     color: blue;
    139     font-weight: normal;
    140 }
    141 </style>
    142 '
    143 
    144 #
    145 # input_cmd | html_quote | output_cmd
    146 # or
    147 # html_quote filename | output_cmd
    148 #
    149 # Make a piece of source code safe for display in an HTML <pre> block.
    150 #
    151 html_quote()
    152 {
    153 	sed -e "s/&/\&amp;/g" -e "s/</\&lt;/g" -e "s/>/\&gt;/g" "$@" | expand
    154 }
    155 
    156 #
    157 # input_cmd | bug2url | output_cmd
    158 #
    159 # Scan for bugids and insert <a> links to the relevent bug database.
    160 #
    161 bug2url()
    162 {
    163 	sed -e 's|[0-9]\{5,\}|<a href=\"'$BUGURL'&\">&</a>|g'
    164 }
    165 
    166 #
    167 # input_cmd | sac2url | output_cmd
    168 #
    169 # Scan for ARC cases and insert <a> links to the relevent SAC database.
    170 # This is slightly complicated because inside the SWAN, SAC cases are
    171 # grouped by ARC: PSARC/2006/123.  But on OpenSolaris.org, they are
    172 # referenced as 2006/123 (without labelling the ARC).
    173 #
    174 sac2url()
    175 {
    176 	if [[ -z "$Oflag" ]]; then
    177 	    sed -e 's|\([A-Z]\{1,2\}ARC\)[ /]\([0-9]\{4\}\)/\([0-9]\{3\}\)|<a href=\"'$SACURL'/\1/\2/\3\">\1 \2/\3</a>|g'
    178 	else
    179 	    sed -e 's|\([A-Z]\{1,2\}ARC\)[ /]\([0-9]\{4\}\)/\([0-9]\{3\}\)|<a href=\"'$SACURL'/\2/\3\">\1 \2/\3</a>|g'
    180 	fi
    181 }
    182 
    183 #
    184 # strip_unchanged <infile> | output_cmd
    185 #
    186 # Removes chunks of sdiff documents that have not changed. This makes it
    187 # easier for a code reviewer to find the bits that have changed.
    188 #
    189 # Deleted lines of text are replaced by a horizontal rule. Some
    190 # identical lines are retained before and after the changed lines to
    191 # provide some context.  The number of these lines is controlled by the
    192 # variable C in the $AWK script below.
    193 #
    194 # The script detects changed lines as any line that has a "<span class="
    195 # string embedded (unchanged lines have no particular class and are not
    196 # part of a <span>).  Blank lines (without a sequence number) are also
    197 # detected since they flag lines that have been inserted or deleted.
    198 #
    199 strip_unchanged()
    200 {
    201 	$AWK '
    202 	BEGIN	{ C = c = 20 }
    203 	NF == 0 || /<span class="/ {
    204 		if (c > C) {
    205 			c -= C
    206 			inx = 0
    207 			if (c > C) {
    208 				print "\n</pre><hr></hr><pre>"
    209 				inx = c % C
    210 				c = C
    211 			}
    212 
    213 			for (i = 0; i < c; i++)
    214 				print ln[(inx + i) % C]
    215 		}
    216 		c = 0;
    217 		print
    218 		next
    219 	}
    220 	{	if (c >= C) {
    221 			ln[c % C] = $0
    222 			c++;
    223 			next;
    224 		}
    225 		c++;
    226 		print
    227 	}
    228 	END	{ if (c > (C * 2)) print "\n</pre><hr></hr>" }
    229 
    230 	' $1
    231 }
    232 
    233 #
    234 # sdiff_to_html
    235 #
    236 # This function takes two files as arguments, obtains their diff, and
    237 # processes the diff output to present the files as an HTML document with
    238 # the files displayed side-by-side, differences shown in color.  It also
    239 # takes a delta comment, rendered as an HTML snippet, as the third
    240 # argument.  The function takes two files as arguments, then the name of
    241 # file, the path, and the comment.  The HTML will be delivered on stdout,
    242 # e.g.
    243 #
    244 #   $ sdiff_to_html old/usr/src/tools/scripts/webrev.sh \
    245 #         new/usr/src/tools/scripts/webrev.sh \
    246 #         webrev.sh usr/src/tools/scripts \
    247 #         '<a href="http://monaco.sfbay.sun.com/detail.jsp?cr=1234567">
    248 #          1234567</a> my bugid' > <file>.html
    249 #
    250 # framed_sdiff() is then called which creates $2.frames.html
    251 # in the webrev tree.
    252 #
    253 # FYI: This function is rather unusual in its use of awk.  The initial
    254 # diff run produces conventional diff output showing changed lines mixed
    255 # with editing codes.  The changed lines are ignored - we're interested in
    256 # the editing codes, e.g.
    257 #
    258 #      8c8
    259 #      57a61
    260 #      63c66,76
    261 #      68,93d80
    262 #      106d90
    263 #      108,110d91
    264 #
    265 #  These editing codes are parsed by the awk script and used to generate
    266 #  another awk script that generates HTML, e.g the above lines would turn
    267 #  into something like this:
    268 #
    269 #      BEGIN { printf "<pre>\n" }
    270 #      function sp(n) {for (i=0;i<n;i++)printf "\n"}
    271 #      function wl(n) {printf "<font color=%s>%4d %s </font>\n", n, NR, $0}
    272 #      NR==8           {wl("#7A7ADD");next}
    273 #      NR==54          {wl("#7A7ADD");sp(3);next}
    274 #      NR==56          {wl("#7A7ADD");next}
    275 #      NR==57          {wl("black");printf "\n"; next}
    276 #        :               :
    277 #
    278 #  This script is then run on the original source file to generate the
    279 #  HTML that corresponds to the source file.
    280 #
    281 #  The two HTML files are then combined into a single piece of HTML that
    282 #  uses an HTML table construct to present the files side by side.  You'll
    283 #  notice that the changes are color-coded:
    284 #
    285 #   black     - unchanged lines
    286 #   blue      - changed lines
    287 #   bold blue - new lines
    288 #   brown     - deleted lines
    289 #
    290 #  Blank lines are inserted in each file to keep unchanged lines in sync
    291 #  (side-by-side).  This format is familiar to users of sdiff(1) or
    292 #  Teamware's filemerge tool.
    293 #
    294 sdiff_to_html()
    295 {
    296 	diff -b $1 $2 > /tmp/$$.diffs
    297 
    298 	TNAME=$3
    299 	TPATH=$4
    300 	COMMENT=$5
    301 
    302 	#
    303 	#  Now we have the diffs, generate the HTML for the old file.
    304 	#
    305 	$AWK '
    306 	BEGIN	{
    307 		printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
    308 		printf "function removed() "
    309 		printf "{printf \"<span class=\\\"removed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
    310 		printf "function changed() "
    311 		printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
    312 		printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
    313 }
    314 	/^</	{next}
    315 	/^>/	{next}
    316 	/^---/	{next}
    317 
    318 	{
    319 	split($1, a, /[cad]/) ;
    320 	if (index($1, "a")) {
    321 		if (a[1] == 0) {
    322 			n = split(a[2], r, /,/);
    323 			if (n == 1)
    324 				printf "BEGIN\t\t{sp(1)}\n"
    325 			else
    326 				printf "BEGIN\t\t{sp(%d)}\n",\
    327 				(r[2] - r[1]) + 1
    328 			next
    329 		}
    330 
    331 		printf "NR==%s\t\t{", a[1]
    332 		n = split(a[2], r, /,/);
    333 		s = r[1];
    334 		if (n == 1)
    335 			printf "bl();printf \"\\n\"; next}\n"
    336 		else {
    337 			n = r[2] - r[1]
    338 			printf "bl();sp(%d);next}\n",\
    339 			(r[2] - r[1]) + 1
    340 		}
    341 		next
    342 	}
    343 	if (index($1, "d")) {
    344 		n = split(a[1], r, /,/);
    345 		n1 = r[1]
    346 		n2 = r[2]
    347 		if (n == 1)
    348 			printf "NR==%s\t\t{removed(); next}\n" , n1
    349 		else
    350 			printf "NR==%s,NR==%s\t{removed(); next}\n" , n1, n2
    351 		next
    352 	}
    353 	if (index($1, "c")) {
    354 		n = split(a[1], r, /,/);
    355 		n1 = r[1]
    356 		n2 = r[2]
    357 		final = n2
    358 		d1 = 0
    359 		if (n == 1)
    360 			printf "NR==%s\t\t{changed();" , n1
    361 		else {
    362 			d1 = n2 - n1
    363 			printf "NR==%s,NR==%s\t{changed();" , n1, n2
    364 		}
    365 		m = split(a[2], r, /,/);
    366 		n1 = r[1]
    367 		n2 = r[2]
    368 		if (m > 1) {
    369 			d2  = n2 - n1
    370 			if (d2 > d1) {
    371 				if (n > 1) printf "if (NR==%d)", final
    372 				printf "sp(%d);", d2 - d1
    373 			}
    374 		}
    375 		printf "next}\n" ;
    376 
    377 		next
    378 	}
    379 	}
    380 
    381 	END	{ printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
    382 	' /tmp/$$.diffs > /tmp/$$.file1
    383 
    384 	#
    385 	#  Now generate the HTML for the new file
    386 	#
    387 	$AWK '
    388 	BEGIN	{
    389 		printf "function sp(n) {for (i=0;i<n;i++)printf \"\\n\"}\n"
    390 		printf "function new() "
    391 		printf "{printf \"<span class=\\\"new\\\">%%4d %%s</span>\\n\", NR, $0}\n"
    392 		printf "function changed() "
    393 		printf "{printf \"<span class=\\\"changed\\\">%%4d %%s</span>\\n\", NR, $0}\n"
    394 		printf "function bl() {printf \"%%4d %%s\\n\", NR, $0}\n"
    395 	}
    396 
    397 	/^</	{next}
    398 	/^>/	{next}
    399 	/^---/	{next}
    400 
    401 	{
    402 	split($1, a, /[cad]/) ;
    403 	if (index($1, "d")) {
    404 		if (a[2] == 0) {
    405 			n = split(a[1], r, /,/);
    406 			if (n == 1)
    407 				printf "BEGIN\t\t{sp(1)}\n"
    408 			else
    409 				printf "BEGIN\t\t{sp(%d)}\n",\
    410 				(r[2] - r[1]) + 1
    411 			next
    412 		}
    413 
    414 		printf "NR==%s\t\t{", a[2]
    415 		n = split(a[1], r, /,/);
    416 		s = r[1];
    417 		if (n == 1)
    418 			printf "bl();printf \"\\n\"; next}\n"
    419 		else {
    420 			n = r[2] - r[1]
    421 			printf "bl();sp(%d);next}\n",\
    422 			(r[2] - r[1]) + 1
    423 		}
    424 		next
    425 	}
    426 	if (index($1, "a")) {
    427 		n = split(a[2], r, /,/);
    428 		n1 = r[1]
    429 		n2 = r[2]
    430 		if (n == 1)
    431 			printf "NR==%s\t\t{new() ; next}\n" , n1
    432 		else
    433 			printf "NR==%s,NR==%s\t{new() ; next}\n" , n1, n2
    434 		next
    435 	}
    436 	if (index($1, "c")) {
    437 		n = split(a[2], r, /,/);
    438 		n1 = r[1]
    439 		n2 = r[2]
    440 		final = n2
    441 		d2 = 0;
    442 		if (n == 1) {
    443 			final = n1
    444 			printf "NR==%s\t\t{changed();" , n1
    445 		} else {
    446 			d2 = n2 - n1
    447 			printf "NR==%s,NR==%s\t{changed();" , n1, n2
    448 		}
    449 		m = split(a[1], r, /,/);
    450 		n1 = r[1]
    451 		n2 = r[2]
    452 		if (m > 1) {
    453 			d1  = n2 - n1
    454 			if (d1 > d2) {
    455 				if (n > 1) printf "if (NR==%d)", final
    456 				printf "sp(%d);", d1 - d2
    457 			}
    458 		}
    459 		printf "next}\n" ;
    460 		next
    461 	}
    462 	}
    463 	END	{ printf "{printf \"%%4d %%s\\n\", NR, $0 }\n" }
    464 	' /tmp/$$.diffs > /tmp/$$.file2
    465 
    466 	#
    467 	# Post-process the HTML files by running them back through $AWK
    468 	#
    469 	html_quote < $1 | $AWK -f /tmp/$$.file1 > /tmp/$$.file1.html
    470 
    471 	html_quote < $2 | $AWK -f /tmp/$$.file2 > /tmp/$$.file2.html
    472 
    473 	#
    474 	# Now combine into a valid HTML file and side-by-side into a table
    475 	#
    476 	print "$HTML<head>$STDHEAD"
    477 	print "<title>$WNAME Sdiff $TPATH/$TNAME</title>"
    478 	print "</head><body id=\"SUNWwebrev\">"
    479         print "<a class=\"print\" href=\"javascript:print()\">Print this page</a>"
    480 	print "<pre>$COMMENT</pre>\n"
    481 	print "<table><tr valign=\"top\">"
    482 	print "<td><pre>"
    483 
    484 	strip_unchanged /tmp/$$.file1.html
    485 
    486 	print "</pre></td><td><pre>"
    487 
    488 	strip_unchanged /tmp/$$.file2.html
    489 
    490 	print "</pre></td>"
    491 	print "</tr></table>"
    492 	print "</body></html>"
    493 
    494 	framed_sdiff $TNAME $TPATH /tmp/$$.file1.html /tmp/$$.file2.html \
    495 	    "$COMMENT"
    496 }
    497 
    498 
    499 #
    500 # framed_sdiff <filename> <filepath> <lhsfile> <rhsfile> <comment>
    501 #
    502 # Expects lefthand and righthand side html files created by sdiff_to_html.
    503 # We use insert_anchors() to augment those with HTML navigation anchors,
    504 # and then emit the main frame.  Content is placed into:
    505 #
    506 #    $WDIR/DIR/$TNAME.lhs.html
    507 #    $WDIR/DIR/$TNAME.rhs.html
    508 #    $WDIR/DIR/$TNAME.frames.html
    509 #
    510 # NOTE: We rely on standard usage of $WDIR and $DIR.
    511 #
    512 function framed_sdiff
    513 {
    514 	typeset TNAME=$1
    515 	typeset TPATH=$2
    516 	typeset lhsfile=$3
    517 	typeset rhsfile=$4
    518 	typeset comments=$5
    519 	typeset RTOP
    520 
    521 	# Enable html files to access WDIR via a relative path.
    522 	RTOP=$(relative_dir $TPATH $WDIR)
    523 
    524 	# Make the rhs/lhs files and output the frameset file.
    525 	print "$HTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.lhs.html
    526 
    527 	cat >> $WDIR/$DIR/$TNAME.lhs.html <<-EOF
    528 	    <script type="text/javascript" src="$RTOP/ancnav.js"></script>
    529 	    </head>
    530 	    <body id="SUNWwebrev" onkeypress="keypress(event);">
    531 	    <a name="0"></a>
    532 	    <pre>$comments</pre><hr></hr>
    533 	EOF
    534 
    535 	cp $WDIR/$DIR/$TNAME.lhs.html $WDIR/$DIR/$TNAME.rhs.html
    536 
    537 	insert_anchors $lhsfile >> $WDIR/$DIR/$TNAME.lhs.html
    538 	insert_anchors $rhsfile >> $WDIR/$DIR/$TNAME.rhs.html
    539 
    540 	close='</body></html>'
    541 
    542 	print $close >> $WDIR/$DIR/$TNAME.lhs.html
    543 	print $close >> $WDIR/$DIR/$TNAME.rhs.html
    544 
    545 	print "$FRAMEHTML<head>$STDHEAD" > $WDIR/$DIR/$TNAME.frames.html
    546 	print "<title>$WNAME Framed-Sdiff " \
    547 	    "$TPATH/$TNAME</title> </head>" >> $WDIR/$DIR/$TNAME.frames.html
    548 	cat >> $WDIR/$DIR/$TNAME.frames.html <<-EOF
    549 	  <frameset rows="*,60">
    550 	    <frameset cols="50%,50%">
    551 	      <frame src="$TNAME.lhs.html" scrolling="auto" name="lhs"></frame>
    552 	      <frame src="$TNAME.rhs.html" scrolling="auto" name="rhs"></frame>
    553 	    </frameset>
    554 	  <frame src="$RTOP/ancnav.html" scrolling="no" marginwidth="0"
    555 	   marginheight="0" name="nav"></frame>
    556 	  <noframes>
    557             <body id="SUNWwebrev">
    558 	      Alas 'frames' webrev requires that your browser supports frames
    559 	      and has the feature enabled.
    560             </body>
    561 	  </noframes>
    562 	  </frameset>
    563 	</html>
    564 	EOF
    565 }
    566 
    567 
    568 #
    569 # fix_postscript
    570 #
    571 # Merge codereview output files to a single conforming postscript file, by:
    572 # 	- removing all extraneous headers/trailers
    573 #	- making the page numbers right
    574 #	- removing pages devoid of contents which confuse some
    575 #	  postscript readers.
    576 #
    577 # From Casper.
    578 #
    579 function fix_postscript
    580 {
    581 	infile=$1
    582 
    583 	cat > /tmp/$$.crmerge.pl << \EOF
    584 
    585 	print scalar(<>);		# %!PS-Adobe---
    586 	print "%%Orientation: Landscape\n";
    587 
    588 	$pno = 0;
    589 	$doprint = 1;
    590 
    591 	$page = "";
    592 
    593 	while (<>) {
    594 		next if (/^%%Pages:\s*\d+/);
    595 
    596 		if (/^%%Page:/) {
    597 			if ($pno == 0 || $page =~ /\)S/) {
    598 				# Header or single page containing text
    599 				print "%%Page: ? $pno\n" if ($pno > 0);
    600 				print $page;
    601 				$pno++;
    602 			} else {
    603 				# Empty page, skip it.
    604 			}
    605 			$page = "";
    606 			$doprint = 1;
    607 			next;
    608 		}
    609 
    610 		# Skip from %%Trailer of one document to Endprolog
    611 		# %%Page of the next
    612 		$doprint = 0 if (/^%%Trailer/);
    613 		$page .= $_ if ($doprint);
    614 	}
    615 
    616 	if ($page =~ /\)S/) {
    617 		print "%%Page: ? $pno\n";
    618 		print $page;
    619 	} else {
    620 		$pno--;
    621 	}
    622 	print "%%Trailer\n%%Pages: $pno\n";
    623 EOF
    624 
    625 	$PERL /tmp/$$.crmerge.pl < $infile
    626 }
    627 
    628 
    629 #
    630 # input_cmd | insert_anchors | output_cmd
    631 #
    632 # Flag blocks of difference with sequentially numbered invisible
    633 # anchors.  These are used to drive the frames version of the
    634 # sdiffs output.
    635 #
    636 # NOTE: Anchor zero flags the top of the file irrespective of changes,
    637 # an additional anchor is also appended to flag the bottom.
    638 #
    639 # The script detects changed lines as any line that has a "<span
    640 # class=" string embedded (unchanged lines have no class set and are
    641 # not part of a <span>.  Blank lines (without a sequence number)
    642 # are also detected since they flag lines that have been inserted or
    643 # deleted.
    644 #
    645 function insert_anchors
    646 {
    647 	$AWK '
    648 	function ia() {
    649 		printf "<a name=\"%d\" id=\"anc%d\"></a>", anc, anc++;
    650 	}
    651 
    652 	BEGIN {
    653 		anc=1;
    654 		inblock=1;
    655 		printf "<pre>\n";
    656 	}
    657 	NF == 0 || /^<span class=/ {
    658 		if (inblock == 0) {
    659 			ia();
    660 			inblock=1;
    661 		}
    662 		print;
    663 		next;
    664 	}
    665 	{
    666 		inblock=0;
    667 		print;
    668 	}
    669 	END {
    670 		ia();
    671 
    672 		printf "<b style=\"font-size: large; color: red\">";
    673 		printf "--- EOF ---</b>"
    674         	for(i=0;i<8;i++) printf "\n\n\n\n\n\n\n\n\n\n";
    675 		printf "</pre>"
    676 		printf "<form name=\"eof\">";
    677 		printf "<input name=\"value\" value=\"%d\" " \
    678 		    "type=\"hidden\"></input>", anc - 1;
    679 		printf "</form>";
    680 	}
    681 	' $1
    682 }
    683 
    684 
    685 #
    686 # relative_dir
    687 #
    688 # Print a relative return path from $1 to $2.  For example if
    689 # $1=/tmp/myreview/raw_files/usr/src/tools/scripts and $2=/tmp/myreview,
    690 # this function would print "../../../../".
    691 #
    692 # In the event that $1 is not in $2 a warning is printed to stderr,
    693 # and $2 is returned-- the result of this is that the resulting webrev
    694 # is not relocatable.
    695 #
    696 function relative_dir
    697 {
    698 	typeset cur="${1##$2?(/)}"
    699 	typeset ret=""
    700 	if [[ $2 == $cur ]]; then   # Should never happen.
    701 		# Should never happen.
    702 		print -u2 "\nWARNING: relative_dir: \"$1\" not relative "
    703 		print -u2 "to \"$2\".  Check input paths.  Framed webrev "
    704 		print -u2 "will not be relocatable!"
    705 		print $2
    706 		return
    707 	fi
    708 
    709 	while [[ -n ${cur} ]];
    710 	do
    711 		cur=${cur%%*(/)*([!/])}
    712 		if [[ -z $ret ]]; then
    713 			ret=".."
    714 		else
    715 			ret="../$ret"
    716 		fi
    717 	done
    718 	print $ret
    719 }
    720 
    721 
    722 #
    723 # frame_nav_js
    724 #
    725 # Emit javascript for frame navigation
    726 #
    727 function frame_nav_js
    728 {
    729 cat << \EOF
    730 var myInt;
    731 var scrolling=0;
    732 var sfactor = 3;
    733 var scount=10;
    734 
    735 function scrollByPix() {
    736 	if (scount<=0) {
    737 		sfactor*=1.2;
    738 		scount=10;
    739 	}
    740 	parent.lhs.scrollBy(0,sfactor);
    741 	parent.rhs.scrollBy(0,sfactor);
    742 	scount--;
    743 }
    744 
    745 function scrollToAnc(num) {
    746 
    747 	// Update the value of the anchor in the form which we use as
    748 	// storage for this value.  setAncValue() will take care of
    749 	// correcting for overflow and underflow of the value and return
    750 	// us the new value.
    751 	num = setAncValue(num);
    752 
    753 	// Set location and scroll back a little to expose previous
    754 	// lines.
    755 	//
    756 	// Note that this could be improved: it is possible although
    757 	// complex to compute the x and y position of an anchor, and to
    758 	// scroll to that location directly.
    759 	//
    760 	parent.lhs.location.replace(parent.lhs.location.pathname + "#" + num);
    761 	parent.rhs.location.replace(parent.rhs.location.pathname + "#" + num);
    762 
    763 	parent.lhs.scrollBy(0,-30);
    764 	parent.rhs.scrollBy(0,-30);
    765 }
    766 
    767 function getAncValue()
    768 {
    769 	return (parseInt(parent.nav.document.diff.real.value));
    770 }
    771 
    772 function setAncValue(val)
    773 {
    774 	if (val <= 0) {
    775 		val = 0;
    776 		parent.nav.document.diff.real.value = val;
    777 		parent.nav.document.diff.display.value = "BOF";
    778 		return (val);
    779 	}
    780 
    781 	//
    782 	// The way we compute the max anchor value is to stash it
    783 	// inline in the left and right hand side pages-- it's the same
    784 	// on each side, so we pluck from the left.
    785 	//
    786 	maxval = parent.lhs.document.eof.value.value;
    787 	if (val < maxval) {
    788 		parent.nav.document.diff.real.value = val;
    789 		parent.nav.document.diff.display.value = val.toString();
    790 		return (val);
    791 	}
    792 
    793 	// this must be: val >= maxval
    794 	val = maxval;
    795 	parent.nav.document.diff.real.value = val;
    796 	parent.nav.document.diff.display.value = "EOF";
    797 	return (val);
    798 }
    799 
    800 function stopScroll() {
    801 	if (scrolling==1) {
    802 		clearInterval(myInt);
    803 		scrolling=0;
    804 	}
    805 }
    806 
    807 function startScroll() {
    808 	stopScroll();
    809 	scrolling=1;
    810 	myInt=setInterval("scrollByPix()",10);
    811 }
    812 
    813 function handlePress(b) {
    814 
    815 	switch (b) {
    816 	    case 1 :
    817 		scrollToAnc(-1);
    818 		break;
    819 	    case 2 :
    820 		scrollToAnc(getAncValue() - 1);
    821 		break;
    822 	    case 3 :
    823 		sfactor=-3;
    824 		startScroll();
    825 		break;
    826 	    case 4 :
    827 		sfactor=3;
    828 		startScroll();
    829 		break;
    830 	    case 5 :
    831 		scrollToAnc(getAncValue() + 1);
    832 		break;
    833 	    case 6 :
    834 		scrollToAnc(999999);
    835 		break;
    836 	}
    837 }
    838 
    839 function handleRelease(b) {
    840 	stopScroll();
    841 }
    842 
    843 function keypress(ev) {
    844 	var keynum;
    845 	var keychar;
    846 
    847 	if (window.event) { // IE
    848 		keynum = ev.keyCode;
    849 	} else if (ev.which) { // non-IE
    850 		keynum = ev.which;
    851 	}
    852 
    853 	keychar = String.fromCharCode(keynum);
    854 
    855 	if (keychar == "k") {
    856 		handlePress(2);
    857 		return (0);
    858 	} else if (keychar == "j" || keychar == " ") {
    859 		handlePress(5);
    860 		return (0);
    861 	}
    862 	return (1);
    863 }
    864 
    865 function ValidateDiffNum(){
    866 	val = parent.nav.document.diff.display.value;
    867 	if (val == "EOF") {
    868 		scrollToAnc(999999);
    869 		return;
    870 	}
    871 
    872 	if (val == "BOF") {
    873 		scrollToAnc(0);
    874 		return;
    875 	}
    876 
    877         i=parseInt(val);
    878         if (isNaN(i)) {
    879                 parent.nav.document.diff.display.value = getAncValue();
    880         } else {
    881                 scrollToAnc(i);
    882         }
    883         return false;
    884 }
    885 
    886 EOF
    887 }
    888 
    889 #
    890 # frame_navigation
    891 #
    892 # Output anchor navigation file for framed sdiffs.
    893 #
    894 function frame_navigation
    895 {
    896 	print "$HTML<head>$STDHEAD"
    897 
    898 	cat << \EOF
    899 <title>Anchor Navigation</title>
    900 <meta http-equiv="Content-Script-Type" content="text/javascript">
    901 <meta http-equiv="Content-Type" content="text/html">
    902 
    903 <style type="text/css">
    904     div.button td { padding-left: 5px; padding-right: 5px;
    905 		    background-color: #eee; text-align: center;
    906 		    border: 1px #444 outset; cursor: pointer; }
    907     div.button a { font-weight: bold; color: black }
    908     div.button td:hover { background: #ffcc99; }
    909 </style>
    910 EOF
    911 
    912 	print "<script type=\"text/javascript\" src=\"ancnav.js\"></script>"
    913 
    914 	cat << \EOF
    915 </head>
    916 <body id="SUNWwebrev" bgcolor="#eeeeee" onload="document.diff.real.focus();"
    917 	onkeypress="keypress(event);">
    918     <noscript lang="javascript">
    919       <center>
    920 	<p><big>Framed Navigation controls require Javascript</big><br></br>
    921 	Either this browser is incompatable or javascript is not enabled</p>
    922       </center>
    923     </noscript>
    924     <table width="100%" border="0" align="center">
    925 	<tr>
    926           <td valign="middle" width="25%">Diff navigation:
    927           Use 'j' and 'k' for next and previous diffs; or use buttons
    928           at right</td>
    929 	  <td align="center" valign="top" width="50%">
    930 	    <div class="button">
    931 	      <table border="0" align="center">
    932                   <tr>
    933 		    <td>
    934 		      <a onMouseDown="handlePress(1);return true;"
    935 			 onMouseUp="handleRelease(1);return true;"
    936 			 onMouseOut="handleRelease(1);return true;"
    937 			 onClick="return false;"
    938 			 title="Go to Beginning Of file">BOF</a></td>
    939 		    <td>
    940 		      <a onMouseDown="handlePress(3);return true;"
    941 			 onMouseUp="handleRelease(3);return true;"
    942 			 onMouseOut="handleRelease(3);return true;"
    943 			 title="Scroll Up: Press and Hold to accelerate"
    944 			 onClick="return false;">Scroll Up</a></td>
    945 		    <td>
    946 		      <a onMouseDown="handlePress(2);return true;"
    947 			 onMouseUp="handleRelease(2);return true;"
    948 			 onMouseOut="handleRelease(2);return true;"
    949 			 title="Go to previous Diff"
    950 			 onClick="return false;">Prev Diff</a>
    951 		    </td></tr>
    952 
    953 		  <tr>
    954 		    <td>
    955 		      <a onMouseDown="handlePress(6);return true;"
    956 			 onMouseUp="handleRelease(6);return true;"
    957 			 onMouseOut="handleRelease(6);return true;"
    958 			 onClick="return false;"
    959 			 title="Go to End Of File">EOF</a></td>
    960 		    <td>
    961 		      <a onMouseDown="handlePress(4);return true;"
    962 			 onMouseUp="handleRelease(4);return true;"
    963 			 onMouseOut="handleRelease(4);return true;"
    964 			 title="Scroll Down: Press and Hold to accelerate"
    965 			 onClick="return false;">Scroll Down</a></td>
    966 		    <td>
    967 		      <a onMouseDown="handlePress(5);return true;"
    968 			 onMouseUp="handleRelease(5);return true;"
    969 			 onMouseOut="handleRelease(5);return true;"
    970 			 title="Go to next Diff"
    971 			 onClick="return false;">Next Diff</a></td>
    972 		  </tr>
    973               </table>
    974 	    </div>
    975 	  </td>
    976 	  <th valign="middle" width="25%">
    977 	    <form action="" name="diff" onsubmit="return ValidateDiffNum();">
    978 		<input name="display" value="BOF" size="8" type="text"></input>
    979 		<input name="real" value="0" size="8" type="hidden"></input>
    980 	    </form>
    981 	  </th>
    982 	</tr>
    983     </table>
    984   </body>
    985 </html>
    986 EOF
    987 }
    988 
    989 
    990 
    991 #
    992 # diff_to_html <filename> <filepath> { U | C } <comment>
    993 #
    994 # Processes the output of diff to produce an HTML file representing either
    995 # context or unified diffs.
    996 #
    997 diff_to_html()
    998 {
    999 	TNAME=$1
   1000 	TPATH=$2
   1001 	DIFFTYPE=$3
   1002 	COMMENT=$4
   1003 
   1004 	print "$HTML<head>$STDHEAD"
   1005 	print "<title>$WNAME ${DIFFTYPE}diff $TPATH</title>"
   1006 
   1007 	if [[ $DIFFTYPE == "U" ]]; then
   1008 		print "$UDIFFCSS"
   1009 	fi
   1010 
   1011 	cat <<-EOF
   1012 	</head>
   1013 	<body id="SUNWwebrev">
   1014         <a class="print" href="javascript:print()">Print this page</a>
   1015 	<pre>$COMMENT</pre>
   1016         <pre>
   1017 	EOF
   1018 
   1019 	html_quote | $AWK '
   1020 	/^--- new/	{ next }
   1021 	/^\+\+\+ new/	{ next }
   1022 	/^--- old/	{ next }
   1023 	/^\*\*\* old/	{ next }
   1024 	/^\*\*\*\*/	{ next }
   1025 	/^-------/	{ printf "<center><h1>%s</h1></center>\n", $0; next }
   1026 	/^\@\@.*\@\@$/	{ printf "</pre><hr></hr><pre>\n";
   1027 			  printf "<span class=\"newmarker\">%s</span>\n", $0;
   1028 			  next}
   1029 
   1030 	/^\*\*\*/	{ printf "<hr></hr><span class=\"oldmarker\">%s</span>\n", $0;
   1031 			  next}
   1032 	/^---/		{ printf "<span class=\"newmarker\">%s</span>\n", $0;
   1033 			  next}
   1034 	/^\+/		{printf "<span class=\"new\">%s</span>\n", $0; next}
   1035 	/^!/		{printf "<span class=\"changed\">%s</span>\n", $0; next}
   1036 	/^-/		{printf "<span class=\"removed\">%s</span>\n", $0; next}
   1037 			{printf "%s\n", $0; next}
   1038 	'
   1039 
   1040 	print "</pre></body></html>\n"
   1041 }
   1042 
   1043 
   1044 #
   1045 # source_to_html { new | old } <filename>
   1046 #
   1047 # Process a plain vanilla source file to transform it into an HTML file.
   1048 #
   1049 source_to_html()
   1050 {
   1051 	WHICH=$1
   1052 	TNAME=$2
   1053 
   1054 	print "$HTML<head>$STDHEAD"
   1055 	print "<title>$WNAME $WHICH $TNAME</title>"
   1056 	print "<body id=\"SUNWwebrev\">"
   1057 	print "<pre>"
   1058 	html_quote | $AWK '{line += 1 ; printf "%4d %s\n", line, $0 }'
   1059 	print "</pre></body></html>"
   1060 }
   1061 
   1062 #
   1063 # comments_from_teamware {text|html} parent-file child-file
   1064 #
   1065 # Find the first delta in the child that's not in the parent.  Get the
   1066 # newest delta from the parent, get all deltas from the child starting
   1067 # with that delta, and then get all info starting with the second oldest
   1068 # delta in that list (the first delta unique to the child).
   1069 #
   1070 # This code adapted from Bill Shannon's "spc" script
   1071 #
   1072 comments_from_teamware()
   1073 {
   1074 	fmt=$1
   1075 	pfile=$PWS/$2
   1076 	cfile=$CWS/$3
   1077 
   1078 	if [[ ! -f $PWS/${2%/*}/SCCS/s.${2##*/} && -n $RWS ]]; then
   1079 		pfile=$RWS/$2
   1080 	fi
   1081 
   1082 	if [[ -f $pfile ]]; then
   1083 		psid=$($SCCS prs -d:I: $pfile 2>/dev/null)
   1084 	else
   1085 		psid=1.1
   1086 	fi
   1087 
   1088 	set -A sids $($SCCS prs -l -r$psid -d:I: $cfile 2>/dev/null)
   1089 	N=${#sids[@]}
   1090 
   1091 	nawkprg='
   1092 		/^COMMENTS:/	{p=1; continue}
   1093 		/^D [0-9]+\.[0-9]+/ {printf "--- %s ---\n", $2; p=0; }
   1094 		NF == 0u	{ continue }
   1095 		{if (p==0) continue; print $0 }'
   1096 
   1097 	if [[ $N -ge 2 ]]; then
   1098 		sid1=${sids[$((N-2))]}	# Gets 2nd to last sid
   1099 
   1100 		if [[ $fmt == "text" ]]; then
   1101 			$SCCS prs -l -r$sid1 $cfile  2>/dev/null | \
   1102 			    $AWK "$nawkprg"
   1103 			return
   1104 		fi
   1105 
   1106 		$SCCS prs -l -r$sid1 $cfile  2>/dev/null | \
   1107 		    html_quote | bug2url | sac2url | $AWK "$nawkprg"
   1108 	fi
   1109 }
   1110 
   1111 #
   1112 # comments_from_wx {text|html} filepath
   1113 #
   1114 # Given the pathname of a file, find its location in a "wx" active
   1115 # file list and print the following comment.  Output is either text or
   1116 # HTML; if the latter, embedded bugids (sequence of 5 or more digits)
   1117 # are turned into URLs.
   1118 #
   1119 # This is also used with Mercurial and the file list provided by hg-active.
   1120 #
   1121 comments_from_wx()
   1122 {
   1123 	typeset fmt=$1
   1124 	typeset p=$2
   1125 
   1126 	comm=`$AWK '
   1127 	$1 == "'$p'" {
   1128 		do getline ; while (NF > 0)
   1129 		getline
   1130 		while (NF > 0) { print ; getline }
   1131 		exit
   1132 	}' < $wxfile`
   1133 
   1134 	if [[ -z $comm ]]; then
   1135 		comm="*** NO COMMENTS ***"
   1136 	fi
   1137 
   1138 	if [[ $fmt == "text" ]]; then
   1139 		print -- "$comm"
   1140 		return
   1141 	fi
   1142 
   1143 	print -- "$comm" | html_quote | bug2url | sac2url
   1144 
   1145 }
   1146 
   1147 #
   1148 # getcomments {text|html} filepath parentpath
   1149 #
   1150 # Fetch the comments depending on what SCM mode we're in.
   1151 #
   1152 getcomments()
   1153 {
   1154 	typeset fmt=$1
   1155 	typeset p=$2
   1156 	typeset pp=$3
   1157 
   1158 	if [[ -n $Nflag ]]; then
   1159 		return
   1160 	fi
   1161 	#
   1162 	# Mercurial support uses a file list in wx format, so this
   1163 	# will be used there, too
   1164 	#
   1165 	if [[ -n $wxfile ]]; then
   1166 		comments_from_wx $fmt $p
   1167 	else
   1168 		if [[ $SCM_MODE == "teamware" ]]; then
   1169 			comments_from_teamware $fmt $pp $p
   1170 		fi
   1171 	fi
   1172 }
   1173 
   1174 #
   1175 # printCI <total-changed> <inserted> <deleted> <modified> <unchanged>
   1176 #
   1177 # Print out Code Inspection figures similar to sccs-prt(1) format.
   1178 #
   1179 function printCI
   1180 {
   1181 	integer tot=$1 ins=$2 del=$3 mod=$4 unc=$5
   1182 	typeset str
   1183 	if (( tot == 1 )); then
   1184 		str="line"
   1185 	else
   1186 		str="lines"
   1187 	fi
   1188 	printf '%d %s changed: %d ins; %d del; %d mod; %d unchg\n' \
   1189 	    $tot $str $ins $del $mod $unc
   1190 }
   1191 
   1192 
   1193 #
   1194 # difflines <oldfile> <newfile>
   1195 #
   1196 # Calculate and emit number of added, removed, modified and unchanged lines,
   1197 # and total lines changed, the sum of added + removed + modified.
   1198 #
   1199 function difflines
   1200 {
   1201 	integer tot mod del ins unc err
   1202 	typeset filename
   1203 
   1204 	eval $( diff -e $1 $2 | $AWK '
   1205 	# Change range of lines: N,Nc
   1206 	/^[0-9]*,[0-9]*c$/ {
   1207 		n=split(substr($1,1,length($1)-1), counts, ",");
   1208 		if (n != 2) {
   1209 		    error=2
   1210 		    exit;
   1211 		}
   1212 		#
   1213 		# 3,5c means lines 3 , 4 and 5 are changed, a total of 3 lines.
   1214 		# following would be 5 - 3 = 2! Hence +1 for correction.
   1215 		#
   1216 		r=(counts[2]-counts[1])+1;
   1217 
   1218 		#
   1219 		# Now count replacement lines: each represents a change instead
   1220 		# of a delete, so increment c and decrement r.
   1221 		#
   1222 		while (getline != /^\.$/) {
   1223 			c++;
   1224 			r--;
   1225 		}
   1226 		#
   1227 		# If there were more replacement lines than original lines,
   1228 		# then r will be negative; in this case there are no deletions,
   1229 		# but there are r changes that should be counted as adds, and
   1230 		# since r is negative, subtract it from a and add it to c.
   1231 		#
   1232 		if (r < 0) {
   1233 			a-=r;
   1234 			c+=r;
   1235 		}
   1236 
   1237 		#
   1238 		# If there were more original lines than replacement lines, then
   1239 		# r will be positive; in this case, increment d by that much.
   1240 		#
   1241 		if (r > 0) {
   1242 			d+=r;
   1243 		}
   1244 		next;
   1245 	}
   1246 
   1247 	# Change lines: Nc
   1248 	/^[0-9].*c$/ {
   1249 		# The first line is a replacement; any more are additions.
   1250 		if (getline != /^\.$/) {
   1251 			c++;
   1252 			while (getline != /^\.$/) a++;
   1253 		}
   1254 		next;
   1255 	}
   1256 
   1257 	# Add lines: both Na and N,Na
   1258 	/^[0-9].*a$/ {
   1259 		while (getline != /^\.$/) a++;
   1260 		next;
   1261 	}
   1262 
   1263 	# Delete range of lines: N,Nd
   1264 	/^[0-9]*,[0-9]*d$/ {
   1265 		n=split(substr($1,1,length($1)-1), counts, ",");
   1266 		if (n != 2) {
   1267 			error=2
   1268 			exit;
   1269 		}
   1270 		#
   1271 		# 3,5d means lines 3 , 4 and 5 are deleted, a total of 3 lines.
   1272 		# following would be 5 - 3 = 2! Hence +1 for correction.
   1273 		#
   1274 		r=(counts[2]-counts[1])+1;
   1275 		d+=r;
   1276 		next;
   1277 	}
   1278 
   1279 	# Delete line: Nd.   For example 10d says line 10 is deleted.
   1280 	/^[0-9]*d$/ {d++; next}
   1281 
   1282 	# Should not get here!
   1283 	{
   1284 		error=1;
   1285 		exit;
   1286 	}
   1287 
   1288 	# Finish off - print results
   1289 	END {
   1290 		printf("tot=%d;mod=%d;del=%d;ins=%d;err=%d\n",
   1291 		    (c+d+a), c, d, a, error);
   1292 	}' )
   1293 
   1294 	# End of $AWK, Check to see if any trouble occurred.
   1295 	if (( $? > 0 || err > 0 )); then
   1296 		print "Unexpected Error occurred reading" \
   1297 		    "\`diff -e $1 $2\`: \$?=$?, err=" $err
   1298 		return
   1299 	fi
   1300 
   1301 	# Accumulate totals
   1302 	(( TOTL += tot ))
   1303 	(( TMOD += mod ))
   1304 	(( TDEL += del ))
   1305 	(( TINS += ins ))
   1306 	# Calculate unchanged lines
   1307 	unc=`wc -l < $1`
   1308 	if (( unc > 0 )); then
   1309 		(( unc -= del + mod ))
   1310 		(( TUNC += unc ))
   1311 	fi
   1312 	# print summary
   1313 	print "<span class=\"lineschanged\">"
   1314 	printCI $tot $ins $del $mod $unc
   1315 	print "</span>"
   1316 }
   1317 
   1318 
   1319 #
   1320 # flist_from_wx
   1321 #
   1322 # Sets up webrev to source its information from a wx-formatted file.
   1323 # Sets the global 'wxfile' variable.
   1324 #
   1325 function flist_from_wx
   1326 {
   1327 	typeset argfile=$1
   1328 	if [[ -n ${argfile%%/*} ]]; then