#!/usr/bin/perl # # Transforms Lucene Java's CHANGES.txt into Changes.html # # Input is on STDIN, output is to STDOUT # # # Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # use strict; use warnings; my $jira_url_prefix = 'http://issues.apache.org/jira/browse/'; my $bugzilla_url_prefix = 'http://issues.apache.org/bugzilla/show_bug.cgi?id='; my %release_dates = &setup_release_dates; my $month_regex = &setup_month_regex; my %month_nums = &setup_month_nums; my %bugzilla_jira_map = &setup_bugzilla_jira_map; my $title = undef; my $release = undef; my $reldate = undef; my $relinfo = undef; my $sections = undef; my $items = undef; my $first_relid = undef; my $second_relid = undef; my @releases = (); my @lines = <>; # Get all input at once # # Parse input and build hierarchical release structure in @releases # for (my $line_num = 0 ; $line_num <= $#lines ; ++$line_num) { $_ = $lines[$line_num]; next unless (/\S/); # Skip blank lines next if (/^\s*\$Id(?::.*)?\$/); # Skip $Id$ lines unless ($title) { if (/\S/) { s/^\s+//; # Trim leading whitespace s/\s+$//; # Trim trailing whitespace } s/^[^Ll]*//; # Trim leading BOM characters if exists $title = $_; next; } if (/\s*===+\s*(.*?)\s*===+\s*/) { # New-style release headings $release = $1; $release =~ s/^release\s*//i; # Trim "Release " prefix ($release, $relinfo) = ($release =~ /^(\d+(?:\.\d+)*|Trunk)\s*(.*)/i); $relinfo =~ s/\s*:\s*$//; # Trim trailing colon $relinfo =~ s/^\s*,\s*//; # Trim leading comma ($reldate, $relinfo) = get_release_date($release, $relinfo); $sections = []; push @releases, [ $release, $reldate, $relinfo, $sections ]; ($first_relid = lc($release)) =~ s/\s+/_/g if ($#releases == 0); ($second_relid = lc($release)) =~ s/\s+/_/g if ($#releases == 1); $items = undef; next; } if (/^\s*([01](?:\.[0-9]{1,2}){1,2}[a-z]?(?:\s*(?:RC\d+|final))?)\s* ((?:200[0-7]-.*|.*,.*200[0-7].*)?)$/x) { # Old-style release heading $release = $1; $relinfo = $2; $relinfo =~ s/\s*:\s*$//; # Trim trailing colon $relinfo =~ s/^\s*,\s*//; # Trim leading comma ($reldate, $relinfo) = get_release_date($release, $relinfo); $sections = []; push @releases, [ $release, $reldate, $relinfo, $sections ]; $items = undef; next; } # Section heading: no leading whitespace, initial word capitalized, # five words or less, and no trailing punctuation if (/^([A-Z]\S*(?:\s+\S+){0,4})(?[0]; # 0th position of items array is list type } else { $type = get_list_type($_); push @$items, $type; } if ($type eq 'numbered') { # The modern items list style # List item boundary is another numbered item or an unindented line my $line; my $item = $_; $item =~ s/^(\s{0,2}\d+\.\d?\s*)//; # Trim the leading item number my $leading_ws_width = length($1); $item =~ s/\s+$//; # Trim trailing whitespace $item .= "\n"; while ($line_num < $#lines and ($line = $lines[++$line_num]) !~ /^(?:\s{0,2}\d+\.\s*\S|\S)/) { $line =~ s/^\s{$leading_ws_width}//; # Trim leading whitespace $line =~ s/\s+$//; # Trim trailing whitespace $item .= "$line\n"; } $item =~ s/\n+\Z/\n/; # Trim trailing blank lines push @$items, $item; --$line_num unless ($line_num == $#lines); } elsif ($type eq 'paragraph') { # List item boundary is a blank line my $line; my $item = $_; $item =~ s/^(\s+)//; my $leading_ws_width = defined($1) ? length($1) : 0; $item =~ s/\s+$//; # Trim trailing whitespace $item .= "\n"; while ($line_num < $#lines and ($line = $lines[++$line_num]) =~ /\S/) { $line =~ s/^\s{$leading_ws_width}//; # Trim leading whitespace $line =~ s/\s+$//; # Trim trailing whitespace $item .= "$line\n"; } push @$items, $item; --$line_num unless ($line_num == $#lines); } else { # $type is one of the bulleted types # List item boundary is another bullet or a blank line my $line; my $item = $_; $item =~ s/^(\s*\Q$type\E\s*)//; # Trim the leading bullet my $leading_ws_width = length($1); $item =~ s/\s+$//; # Trim trailing whitespace $item .= "\n"; while ($line_num < $#lines and ($line = $lines[++$line_num]) !~ /^(?:\S|\s*\Q$type\E)/) { $line =~ s/^\s{$leading_ws_width}//; # Trim leading whitespace $line =~ s/\s+$//; # Trim trailing whitespace $item .= "$line\n"; } push @$items, $item; --$line_num unless ($line_num == $#lines); } } # # Print HTML-ified version to STDOUT # print<<"__HTML_HEADER__"; $title

$title

__HTML_HEADER__ my $heading; my $relcnt = 0; my $header = 'h2'; for my $rel (@releases) { if (++$relcnt == 3) { $header = 'h3'; print "

"; print "Older Releases"; print "

\n"; print "\n" if ($relcnt > 3); print "\n\n"; # # Subroutine: has_release_sections # # Takes one parameter: # # - The $sections array reference # # Returns one scalar: # # - A boolean indicating whether there are release sections # sub has_release_sections { my $sections = shift; my $has_release_sections = 0; my $first_titled_section_num = -1; for my $section_num (0 .. $#{$sections}) { if ($sections->[$section_num][0]) { $has_release_sections = 1; last; } } return $has_release_sections; } # # Subroutine: get_list_type # # Takes one parameter: # # - The first line of a sub-section/point # # Returns one scalar: # # - The list type: 'numbered'; or one of the bulleted types '-', or '.' or # 'paragraph'. # sub get_list_type { my $first_list_item_line = shift; my $type = 'paragraph'; # Default to paragraph type if ($first_list_item_line =~ /^\s{0,2}\d+\.\s+\S+/) { $type = 'numbered'; } elsif ($first_list_item_line =~ /^\s*([-.*])\s+\S+/) { $type = $1; } return $type; } # # Subroutine: get_release_date # # Takes two parameters: # # - Release name # - Release info, potentially including a release date # # Returns two scalars: # # - The release date, in format YYYY-MM-DD # - The remainder of the release info (if any), with release date stripped # sub get_release_date { my $release = shift; my $relinfo = shift; my ($year, $month, $dom, $reldate); if ($relinfo) { if ($relinfo =~ s:\s*(2\d\d\d)([-./]) (1[012]|0?[1-9])\2 ([12][0-9]|30|31|0?[1-9])\s*: :x) { # YYYY-MM-DD or YYYY-M-D or YYYY-MM-D or YYYY-M-DD $year = $1; $month = $3; $dom = $4; $dom = "0$dom" if (length($dom) == 1); $reldate = "$year-$month-$dom"; } elsif ($relinfo =~ s:\s*(1[012]|0?[1-9])([-./]) ([12][0-9]|30|31|0?[1-9])\2 (2\d\d\d)\s*: :x) { # MM-DD-YYYY or M-D-YYYY or MM-D-YYYY or M-DD-YYYY $month = $1; $dom = $3; $dom = "0$dom" if (length($dom) == 1); $year = $4; $reldate = "$year-$month-$dom"; } elsif ($relinfo =~ s:($month_regex)\s* ([12][0-9]|30|31|0?[1-9])((st|rd|th)\.?)?,?\s* (2\d\d\d)\s*: :x) { # MMMMM DD, YYYY or MMMMM DDth, YYYY $month = $month_nums{$1}; $dom = $2; $dom = "0$dom" if (length($dom) == 1); $year = $5; $reldate = "$year-$month-$dom"; } elsif ($relinfo =~ s:([12][0-9]|30|31|0?[1-9])(\s+|[-/.]) ($month_regex)\2 (2\d\d\d)\s*: :x) { # DD MMMMM YYYY $dom = $1; $dom = "0$dom" if (length($dom) == 1); $month = $month_nums{$3}; $year = $4; $reldate = "$year-$month-$dom"; } } unless ($reldate) { # No date found in $relinfo # Handle '1.2 RC6', which should be '1.2 final' $release = '1.2 final' if ($release eq '1.2 RC6'); $reldate = ( exists($release_dates{$release}) ? $release_dates{$release} : 'unknown'); } $relinfo =~ s/,?\s*$//; # Trim trailing comma and whitespace return ($reldate, $relinfo); } # # setup_release_dates # # Returns a list of alternating release names and dates, for use in populating # the %release_dates hash. # sub setup_release_dates { return ( '0.01' => '2000-03-30', '0.04' => '2000-04-19', '1.0' => '2000-10-04', '1.01b' => '2001-06-02', '1.2 RC1' => '2001-10-02', '1.2 RC2' => '2001-10-19', '1.2 RC3' => '2002-01-27', '1.2 RC4' => '2002-02-14', '1.2 RC5' => '2002-05-14', '1.2 final' => '2002-06-13', '1.3 RC1' => '2003-03-24', '1.3 RC2' => '2003-10-22', '1.3 RC3' => '2003-11-25', '1.3 final' => '2003-12-26', '1.4 RC1' => '2004-03-29', '1.4 RC2' => '2004-03-30', '1.4 RC3' => '2004-05-11', '1.4 final' => '2004-07-01', '1.4.1' => '2004-08-02', '1.4.2' => '2004-10-01', '1.4.3' => '2004-12-07', '1.9 RC1' => '2006-02-21', '1.9 final' => '2006-02-27', '1.9.1' => '2006-03-02', '2.0.0' => '2006-05-26', '2.1.0' => '2007-02-14', '2.2.0' => '2007-06-19', '2.3.0' => '2008-01-21', '2.3.1' => '2008-02-22', '2.3.2' => '2008-05-05', '2.4.0' => '2008-10-06', '2.4.1' => '2009-03-09', '2.9.0' => '2009-09-23', '2.9.1' => '2009-11-06', '3.0.0' => '2009-11-25'); } # # setup_month_regex # # Returns a string containing a regular expression with alternations for # the standard month representations in English. # sub setup_month_regex { return '(?i:Jan(?:|\.|uary)|Feb(?:|\.|ruary)|Mar(?:|\.|ch)' . '|Apr(?:|\.|il)|May|Jun(?:|\.|e)|Jul(?:|\.|y)|Aug(?:|\.|ust)' . '|Sep(?:|\.|t(?:|\.|ember))|Oct(?:|\.|ober)|Nov(?:|\.|ember)' . '|Dec(?:|\.|ember))'; } # # setup_month_nums # # Returns a list of alternating English month representations and the two-digit # month number corresponding to them, for use in populating the %month_nums # hash. # sub setup_month_nums { return ( 'Jan' => '01', 'Jan.' => '01', 'January' => '01', 'Feb' => '02', 'Feb.' => '02', 'February' => '02', 'Mar' => '03', 'Mar.' => '03', 'March' => '03', 'Apr' => '04', 'Apr.' => '04', 'April' => '04', 'May' => '05', 'Jun' => '06', 'Jun.' => '06', 'June' => '06', 'Jul' => '07', 'Jul.' => '07', 'July' => '07', 'Aug' => '08', 'Aug.' => '08', 'August' => '08', 'Sep' => '09', 'Sep.' => '09', 'Sept' => '09', 'Sept.' => '09', 'September' => '09', 'Oct' => '10', 'Oct.' => '10', 'October' => '10', 'Nov' => '11', 'Nov.' => '11', 'November' => '11', 'Dec' => '12', 'Dec.' => '12', 'December' => '12' ); } # # setup_bugzilla_jira_map # # Returns a list of alternating Bugzilla bug IDs and LUCENE-* JIRA issue # numbers, for use in populating the %bugzilla_jira_map hash # sub setup_bugzilla_jira_map { return ( 4049 => 1, 4102 => 2, 4105 => 3, 4254 => 4, 4555 => 5, 4568 => 6, 4754 => 7, 5313 => 8, 5456 => 9, 6078 => 10, 6091 => 11, 6140 => 12, 6292 => 13, 6315 => 14, 6469 => 15, 6914 => 16, 6968 => 17, 7017 => 18, 7019 => 19, 7088 => 20, 7089 => 21, 7275 => 22, 7412 => 23, 7461 => 24, 7574 => 25, 7710 => 26, 7750 => 27, 7782 => 28, 7783 => 29, 7912 => 30, 7974 => 31, 8307 => 32, 8525 => 33, 9015 => 34, 9110 => 35, 9347 => 36, 9454 => 37, 9782 => 38, 9853 => 39, 9906 => 40, 9970 => 41, 10340 => 42, 10341 => 43, 10342 => 44, 10343 => 45, 10849 => 46, 11109 => 47, 11359 => 48, 11636 => 49, 11918 => 50, 12137 => 51, 12273 => 52, 12444 => 53, 12569 => 54, 12588 => 55, 12619 => 56, 12667 => 57, 12723 => 58, 12749 => 59, 12761 => 60, 12950 => 61, 13102 => 62, 13166 => 63, 14028 => 64, 14355 => 65, 14373 => 66, 14412 => 67, 14485 => 68, 14585 => 69, 14665 => 70, 14900 => 71, 15739 => 72, 16025 => 73, 16043 => 74, 16167 => 75, 16245 => 76, 16364 => 77, 16437 => 78, 16438 => 79, 16470 => 80, 16677 => 81, 16719 => 82, 16730 => 83, 16816 => 84, 16952 => 85, 17242 => 86, 17954 => 88, 18014 => 89, 18088 => 90, 18177 => 91, 18410 => 87, 18833 => 92, 18847 => 93, 18914 => 94, 18927 => 95, 18928 => 96, 18929 => 97, 18931 => 98, 18932 => 99, 18933 => 100, 18934 => 101, 19058 => 102, 19149 => 103, 19189 => 104, 19253 => 105, 19468 => 106, 19686 => 107, 19736 => 108, 19751 => 109, 19834 => 110, 19844 => 111, 20024 => 112, 20081 => 113, 20123 => 114, 20196 => 115, 20283 => 116, 20290 => 117, 20461 => 118, 20901 => 119, 21128 => 120, 21149 => 121, 21150 => 122, 21189 => 123, 21446 => 124, 21921 => 126, 22344 => 128, 22469 => 130, 22987 => 131, 23307 => 133, 23308 => 134, 23422 => 135, 23466 => 136, 23505 => 137, 23534 => 138, 23545 => 139, 23650 => 140, 23655 => 141, 23685 => 142, 23702 => 143, 23727 => 144, 23730 => 145, 23750 => 146, 23754 => 147, 23770 => 148, 23771 => 149, 23773 => 150, 23774 => 151, 23782 => 152, 23784 => 153, 23786 => 154, 23838 => 155, 23964 => 156, 24084 => 129, 24237 => 157, 24265 => 158, 24301 => 159, 24370 => 160, 24665 => 161, 24786 => 162, 24902 => 163, 24903 => 164, 24913 => 165, 25666 => 125, 25793 => 166, 25820 => 167, 25945 => 168, 26120 => 169, 26196 => 170, 26268 => 171, 26360 => 172, 26396 => 173, 26397 => 174, 26624 => 175, 26634 => 176, 26666 => 177, 26702 => 178, 26716 => 179, 26763 => 180, 26884 => 181, 26939 => 182, 27168 => 183, 27174 => 184, 27182 => 185, 27268 => 186, 27326 => 187, 27354 => 188, 27408 => 189, 27423 => 190, 27433 => 191, 27491 => 192, 27587 => 193, 27626 => 194, 27638 => 195, 27743 => 196, 27772 => 197, 27799 => 198, 27819 => 199, 27865 => 200, 27868 => 201, 27903 => 202, 27987 => 203, 28030 => 204, 28050 => 205, 28065 => 206, 28074 => 207, 28108 => 208, 28181 => 209, 28182 => 210, 28183 => 211, 28187 => 212, 28285 => 213, 28336 => 214, 28339 => 215, 28405 => 216, 28462 => 217, 28601 => 218, 28640 => 219, 28748 => 220, 28827 => 221, 28855 => 222, 28856 => 223, # Clone: 28856 => 507, 28858 => 224, 28960 => 132, 28964 => 127, 29033 => 225, 29256 => 226, 29299 => 227, 29302 => 228, 29370 => 229, 29432 => 230, 29548 => 231, 29749 => 232, 29756 => 233, 29774 => 234, 29931 => 235, 29984 => 236, 30013 => 237, 30016 => 238, 30026 => 239, 30027 => 240, 30049 => 241, 30058 => 242, 30232 => 243, 30237 => 244, 30240 => 245, 30242 => 246, 30265 => 247, 30327 => 248, 30330 => 249, 30360 => 250, 30376 => 251, 30382 => 252, 30421 => 253, 30429 => 254, 30452 => 255, 30480 => 256, 30522 => 257, 30617 => 258, 30621 => 259, 30628 => 260, 30629 => 261, 30668 => 262, 30678 => 263, 30685 => 264, 30736 => 265, 30785 => 266, 30818 => 267, 30835 => 268, 30844 => 269, 30977 => 270, 30985 => 271, 31061 => 272, 31120 => 273, 31149 => 274, 31174 => 275, 31240 => 276, 31241 => 277, 31294 => 278, 31350 => 279, 31368 => 280, 31420 => 281, 31469 => 282, 31508 => 283, 31554 => 284, 31617 => 285, 31619 => 286, 31690 => 287, 31706 => 288, 31708 => 289, 31746 => 290, 31747 => 291, 31748 => 292, 31784 => 293, 31785 => 294, 31841 => 295, 31882 => 296, 31926 => 297, 31976 => 298, 32053 => 299, 32055 => 300, 32088 => 301, 32090 => 302, 32109 => 303, 32115 => 304, 32143 => 305, 32167 => 306, 32171 => 307, 32192 => 308, 32227 => 309, 32228 => 310, 32234 => 311, 32291 => 312, 32307 => 313, 32334 => 314, 32353 => 315, 32365 => 316, 32403 => 317, 32432 => 318, 32467 => 319, 32468 => 320, 32580 => 321, 32626 => 322, 32674 => 323, 32687 => 324, 32712 => 325, 32847 => 326, 32887 => 327, 32921 => 328, 32942 => 329, 32965 => 330, 32981 => 331, 32999 => 332, 33019 => 333, 33076 => 334, 33134 => 335, 33158 => 336, 33161 => 337, 33197 => 338, 33239 => 339, 33389 => 340, 33395 => 341, 33397 => 342, 33442 => 343, 33449 => 344, 33459 => 345, 33472 => 346, 33642 => 347, 33648 => 348, 33649 => 349, 33654 => 350, 33678 => 351, 33725 => 352, 33799 => 353, 33820 => 354, 33835 => 355, 33848 => 356, 33851 => 357, 33877 => 358, 33884 => 359, 33974 => 360, 34028 => 361, 34066 => 362, 34149 => 363, 34154 => 364, 34193 => 365, 34279 => 366, 34320 => 367, 34331 => 368, 34359 => 369, 34407 => 370, 34408 => 371, 34447 => 372, 34453 => 373, 34477 => 374, # Clone: 34477 => 459, 34486 => 375, 34528 => 376, 34544 => 377, 34545 => 378, 34563 => 379, 34570 => 380, 34585 => 381, 34629 => 382, 34673 => 383, 34684 => 384, 34695 => 385, 34816 => 386, 34882 => 387, 34930 => 388, 34946 => 389, 34995 => 390, 35029 => 391, 35037 => 392, 35157 => 393, 35241 => 394, 35284 => 395, # Clone: 35284 => 466, 35388 => 396, 35446 => 397, 35454 => 398, 35455 => 399, 35456 => 400, 35468 => 401, 35491 => 402, 35518 => 403, 35626 => 404, 35664 => 405, 35665 => 406, 35668 => 407, 35729 => 408, 35730 => 409, 35731 => 410, 35796 => 411, 35822 => 412, 35823 => 413, 35838 => 414, 35879 => 415, # Clone: 35879 => 616, 35886 => 416, 35971 => 417, 36021 => 418, 36078 => 419, 36101 => 420, 36135 => 421, 36147 => 422, 36197 => 423, 36219 => 424, 36241 => 425, 36242 => 426, 36292 => 427, 36296 => 428, 36333 => 429, 36622 => 430, 36623 => 431, 36628 => 432); } 1;