PyLucene 3.4.0-1 import
[pylucene.git] / lucene-java-3.4.0 / lucene / src / site / changes / changes2html.pl
1 #!/usr/bin/perl
2 #
3 # Transforms Lucene Java's CHANGES.txt into Changes.html
4 #
5 # Input is on STDIN, output is to STDOUT
6 #
7 #
8 # Licensed to the Apache Software Foundation (ASF) under one or more
9 # contributor license agreements.  See the NOTICE file distributed with
10 # this work for additional information regarding copyright ownership.
11 # The ASF licenses this file to You under the Apache License, Version 2.0
12 # (the "License"); you may not use this file except in compliance with
13 # the License.  You may obtain a copy of the License at
14 #
15 #     http://www.apache.org/licenses/LICENSE-2.0
16 #
17 # Unless required by applicable law or agreed to in writing, software
18 # distributed under the License is distributed on an "AS IS" BASIS,
19 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 # See the License for the specific language governing permissions and
21 # limitations under the License.
22 #
23
24 use strict;
25 use warnings;
26
27 my $project_info_url = 'https://issues.apache.org/jira/rest/api/2.0.alpha1/project/LUCENE';
28 my $jira_url_prefix = 'http://issues.apache.org/jira/browse/';
29 my $bugzilla_url_prefix = 'http://issues.apache.org/bugzilla/show_bug.cgi?id=';
30 my %release_dates = &setup_release_dates;
31 my $month_regex = &setup_month_regex;
32 my %month_nums = &setup_month_nums;
33 my %bugzilla_jira_map = &setup_bugzilla_jira_map;
34 my $title = undef;
35 my $release = undef;
36 my $reldate = undef;
37 my $relinfo = undef;
38 my $sections = undef;
39 my $items = undef;
40 my $first_relid = undef;
41 my $second_relid = undef;
42 my @releases = ();
43
44 my @lines = <>;                        # Get all input at once
45
46 #
47 # Parse input and build hierarchical release structure in @releases
48 #
49 for (my $line_num = 0 ; $line_num <= $#lines ; ++$line_num) {
50   $_ = $lines[$line_num];
51   next unless (/\S/);                  # Skip blank lines
52   next if (/^\s*\$Id(?::.*)?\$/);      # Skip $Id$ lines
53
54   unless ($title) {
55     if (/\S/) {
56       s/^\s+//;                        # Trim leading whitespace
57       s/\s+$//;                        # Trim trailing whitespace
58     }
59     s/^[^Ll]*//;                       # Trim leading BOM characters if exists
60     $title = $_;
61     next;
62   }
63
64   if (/\s*===+\s*(.*?)\s*===+\s*/) {   # New-style release headings
65     $release = $1;
66     $release =~ s/^(?:release|lucene)\s*//i;  # Trim "Release " or "Lucene " prefix
67     ($release, $relinfo) = ($release =~ /^(\d+(?:\.(?:\d+|[xyz]))*|Trunk)\s*(.*)/i);
68     $relinfo =~ s/\s*:\s*$//;          # Trim trailing colon
69     $relinfo =~ s/^\s*,\s*//;          # Trim leading comma
70     ($reldate, $relinfo) = get_release_date($release, $relinfo);
71     $sections = [];
72     push @releases, [ $release, $reldate, $relinfo, $sections ];
73     ($first_relid = lc($release)) =~ s/\s+/_/g   if ($#releases == 0);
74     ($second_relid = lc($release)) =~ s/\s+/_/g  if ($#releases == 1);
75     $items = undef;
76     next;
77   }
78
79   if (/^\s*([01](?:\.[0-9]{1,2}){1,2}[a-z]?(?:\s*(?:RC\d+|final))?)\s*
80        ((?:200[0-7]-.*|.*,.*200[0-7].*)?)$/x) { # Old-style release heading
81     $release = $1;
82     $relinfo = $2;
83     $relinfo =~ s/\s*:\s*$//;          # Trim trailing colon
84     $relinfo =~ s/^\s*,\s*//;          # Trim leading comma
85     ($reldate, $relinfo) = get_release_date($release, $relinfo);
86     $sections = [];
87     push @releases, [ $release, $reldate, $relinfo, $sections ];
88     $items = undef;
89     next;
90   }
91
92   # Section heading: no leading whitespace, initial word capitalized,
93   #                  five words or less, and no trailing punctuation
94   if (/^([A-Z]\S*(?:\s+\S+){0,4})(?<![-.:;!()])\s*$/) {
95     my $heading = $1;
96     $items = [];
97     push @$sections, [ $heading, $items ];
98     next;
99   }
100
101   # Handle earlier releases without sections - create a headless section
102   unless ($items) {
103     $items = [];
104     push @$sections, [ undef, $items ];
105   }
106
107   my $type;
108   if (@$items) { # A list item has been encountered in this section before
109     $type = $items->[0];  # 0th position of items array is list type
110   } else {
111     $type = get_list_type($_);
112     push @$items, $type;
113   }
114
115   if ($type eq 'numbered') { # The modern items list style
116     # List item boundary is another numbered item or an unindented line
117     my $line;
118     my $item = $_;
119     $item =~ s/^(\s{0,2}\d+\.\d?\s*)//;    # Trim the leading item number
120     my $leading_ws_width = length($1);
121     $item =~ s/\s+$//;                     # Trim trailing whitespace
122     $item .= "\n";
123
124     while ($line_num < $#lines
125            and ($line = $lines[++$line_num]) !~ /^(?:\s{0,2}\d+\.\s*\S|\S)/) {
126       $line =~ s/^\s{$leading_ws_width}//; # Trim leading whitespace
127       $line =~ s/\s+$//;                   # Trim trailing whitespace
128       $item .= "$line\n";
129     }
130     $item =~ s/\n+\Z/\n/;                  # Trim trailing blank lines
131     push @$items, $item;
132     --$line_num unless ($line_num == $#lines);
133   } elsif ($type eq 'paragraph') {         # List item boundary is a blank line
134     my $line;
135     my $item = $_;
136     $item =~ s/^(\s+)//;
137     my $leading_ws_width = defined($1) ? length($1) : 0;
138     $item =~ s/\s+$//;                     # Trim trailing whitespace
139     $item .= "\n";
140
141     while ($line_num < $#lines and ($line = $lines[++$line_num]) =~ /\S/) {
142       $line =~ s/^\s{$leading_ws_width}//; # Trim leading whitespace
143       $line =~ s/\s+$//;                   # Trim trailing whitespace
144       $item .= "$line\n";
145     }
146     push @$items, $item;
147     --$line_num unless ($line_num == $#lines);
148   } else { # $type is one of the bulleted types
149     # List item boundary is another bullet or a blank line
150     my $line;
151     my $item = $_;
152     $item =~ s/^(\s*\Q$type\E\s*)//;           # Trim the leading bullet
153     my $leading_ws_width = length($1);
154     $item =~ s/\s+$//;                     # Trim trailing whitespace
155     $item .= "\n";
156
157     while ($line_num < $#lines
158            and ($line = $lines[++$line_num]) !~ /^(?:\S|\s*\Q$type\E)/) {
159       $line =~ s/^\s{$leading_ws_width}//; # Trim leading whitespace
160       $line =~ s/\s+$//;                   # Trim trailing whitespace
161       $item .= "$line\n";
162     }
163     push @$items, $item;
164     --$line_num unless ($line_num == $#lines);
165   }
166 }
167
168 # Recognize IDs of top level nodes of the most recent two releases,
169 # escaping JavaScript regex metacharacters, e.g.: "^(?:trunk|2\\\\.4\\\\.0)"
170 my $first_relid_regex = $first_relid;
171 $first_relid_regex =~ s!([.+*?{}()|^$/\[\]\\])!\\\\\\\\$1!g;
172 my $second_relid_regex = $second_relid;
173 $second_relid_regex =~ s!([.+*?{}()|^$/\[\]\\])!\\\\\\\\$1!g;
174 my $newer_version_regex = "^(?:$first_relid_regex|$second_relid_regex)";
175
176 #
177 # Print HTML-ified version to STDOUT
178 #
179 print<<"__HTML_HEADER__";
180 <!--
181 **********************************************************
182 ** WARNING: This file is generated from CHANGES.txt by the 
183 **          Perl script 'changes2html.pl'.
184 **          Do *not* edit this file!
185 **********************************************************
186           
187 ****************************************************************************
188 * Licensed to the Apache Software Foundation (ASF) under one or more
189 * contributor license agreements.  See the NOTICE file distributed with
190 * this work for additional information regarding copyright ownership.
191 * The ASF licenses this file to You under the Apache License, Version 2.0
192 * (the "License"); you may not use this file except in compliance with
193 * the License.  You may obtain a copy of the License at
194 *
195 *     http://www.apache.org/licenses/LICENSE-2.0
196 *
197 * Unless required by applicable law or agreed to in writing, software
198 * distributed under the License is distributed on an "AS IS" BASIS,
199 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 * See the License for the specific language governing permissions and
201 * limitations under the License.
202 ****************************************************************************
203 -->
204 <html>
205 <head>
206   <title>$title</title>
207   <link rel="stylesheet" href="ChangesFancyStyle.css" title="Fancy">
208   <link rel="alternate stylesheet" href="ChangesSimpleStyle.css" title="Simple">
209   <link rel="alternate stylesheet" href="ChangesFixedWidthStyle.css" title="Fixed Width">
210   <META http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
211   <SCRIPT>
212     function toggleList(id) {
213       listStyle = document.getElementById(id + '.list').style;
214       anchor = document.getElementById(id);
215       if (listStyle.display == 'none') {
216         listStyle.display = 'block';
217         anchor.title = 'Click to collapse';
218         location.href = '#' + id;
219       } else {
220         listStyle.display = 'none';
221         anchor.title = 'Click to expand';
222       }
223       var expandButton = document.getElementById('expand.button');
224       expandButton.disabled = false;
225       var collapseButton = document.getElementById('collapse.button');
226       collapseButton.disabled = false;
227     }
228
229     function collapseAll() {
230       var unorderedLists = document.getElementsByTagName("ul");
231       for (var i = 0; i < unorderedLists.length; i++) {
232         if (unorderedLists[i].className != 'bulleted-list')
233           unorderedLists[i].style.display = "none";
234         else
235           unorderedLists[i].style.display = "block";
236       }
237       var orderedLists = document.getElementsByTagName("ol");
238       for (var i = 0; i < orderedLists.length; i++)
239         orderedLists[i].style.display = "none"; 
240       var anchors = document.getElementsByTagName("a");
241       for (var i = 0 ; i < anchors.length; i++) {
242         if (anchors[i].id != '')
243           anchors[i].title = 'Click to expand';
244       }
245       var collapseButton = document.getElementById('collapse.button');
246       collapseButton.disabled = true;
247       var expandButton = document.getElementById('expand.button');
248       expandButton.disabled = false;
249     }
250
251     function expandAll() {
252       var unorderedLists = document.getElementsByTagName("ul");
253       for (var i = 0; i < unorderedLists.length; i++)
254         unorderedLists[i].style.display = "block";
255       var orderedLists = document.getElementsByTagName("ol");
256       for (var i = 0; i < orderedLists.length; i++)
257         orderedLists[i].style.display = "block"; 
258       var anchors = document.getElementsByTagName("a");
259       for (var i = 0 ; i < anchors.length; i++) {
260         if (anchors[i].id != '')
261           anchors[i].title = 'Click to collapse';
262       }
263       var expandButton = document.getElementById('expand.button');
264       expandButton.disabled = true;
265       var collapseButton = document.getElementById('collapse.button');
266       collapseButton.disabled = false;
267
268     }
269
270     var newerRegex = new RegExp("$newer_version_regex");
271     function isOlder(listId) {
272       return ! newerRegex.test(listId);
273     }
274
275     function escapeMeta(s) {
276       return s.replace(/(?=[.*+?^\${}()|[\\]\\/\\\\])/g, '\\\\');
277     }
278
279     function shouldExpand(currentList, currentAnchor, listId) {
280       var listName = listId.substring(0, listId.length - 5);
281       var parentRegex = new RegExp("^" + escapeMeta(listName) + "\\\\.");
282       return currentList == listId
283              || (isOlder(currentAnchor) && listId == 'older.list')
284              || parentRegex.test(currentAnchor);
285     }
286
287     function collapse() {
288       /* Collapse all but the first and second releases. */
289       var unorderedLists = document.getElementsByTagName("ul");
290       var currentAnchor = location.hash.substring(1);
291       var currentList = currentAnchor + ".list";
292
293       for (var i = 0; i < unorderedLists.length; i++) {
294         var list = unorderedLists[i];
295         /* Collapse the current item, unless either the current item is one of
296          * the first two releases, or the current URL has a fragment and the
297          * fragment refers to the current item or one of its ancestors.
298          */
299         if (list.id != '$first_relid.list' 
300             && list.id != '$second_relid.list'
301             && list.className != 'bulleted-list'
302             && (currentAnchor == ''
303                 || ! shouldExpand(currentList, currentAnchor, list.id))) {
304           list.style.display = "none";
305         }
306       }
307       var orderedLists = document.getElementsByTagName("ol");
308       for (var i = 0; i < orderedLists.length; i++) {
309         var list = orderedLists[i];
310         /* Collapse the current item, unless the current URL has a fragment
311          * and the fragment refers to the current item or one of its ancestors.
312          */
313         if (currentAnchor == ''
314             || ! shouldExpand(currentList, currentAnchor, list.id)) {
315           list.style.display = "none";
316         }
317       }
318       /* Add "Click to collapse/expand" tooltips to the release/section headings */
319       var anchors = document.getElementsByTagName("a");
320       for (var i = 0 ; i < anchors.length; i++) {
321         var anchor = anchors[i];
322         if (anchor.id != '') {
323           if (anchor.id == '$first_relid' || anchor.id == '$second_relid') {
324             anchor.title = 'Click to collapse';
325           } else {
326             anchor.title = 'Click to expand';
327           }
328         }
329       }
330
331       /* Insert "Expand All" and "Collapse All" buttons */
332       var buttonsParent = document.getElementById('buttons.parent');
333       var expandButton = document.createElement('button');
334       expandButton.appendChild(document.createTextNode('Expand All'));
335       expandButton.onclick = function() { expandAll(); }
336       expandButton.id = 'expand.button';
337       buttonsParent.appendChild(expandButton);
338       var collapseButton = document.createElement('button');
339       collapseButton.appendChild(document.createTextNode('Collapse All'));
340       collapseButton.onclick = function() { collapseAll(); }
341       collapseButton.id = 'collapse.button';
342       buttonsParent.appendChild(collapseButton);
343     }
344
345     window.onload = collapse;
346   </SCRIPT>
347 </head>
348 <body>
349
350 <h1>$title</h1>
351
352 <div id="buttons.parent"></div>
353
354 __HTML_HEADER__
355
356 my $heading;
357 my $relcnt = 0;
358 my $header = 'h2';
359 for my $rel (@releases) {
360   if (++$relcnt == 3) {
361     $header = 'h3';
362     print "<h2><a id=\"older\" href=\"javascript:toggleList('older')\">";
363     print "Older Releases";
364     print "</a></h2>\n";
365     print "<ul id=\"older.list\">\n"
366   }
367       
368   ($release, $reldate, $relinfo, $sections) = @$rel;
369
370   # The first section heading is undefined for the older sectionless releases
371   my $has_release_sections = has_release_sections($sections);
372
373   (my $relid = lc($release)) =~ s/\s+/_/g;
374   print "<$header><a id=\"$relid\" href=\"javascript:toggleList('$relid')\">";
375   print "Release " unless ($release =~ /^trunk$/i);
376   print "$release $relinfo";
377   print " [$reldate]" unless ($reldate eq 'unknown');
378   print "</a></$header>\n";
379   print "<ul id=\"$relid.list\">\n"
380     if ($has_release_sections);
381
382   for my $section (@$sections) {
383     ($heading, $items) = @$section;
384     (my $sectid = lc($heading)) =~ s/\s+/_/g;
385     my $numItemsStr = $#{$items} > 0 ? "($#{$items})" : "(none)";  
386
387     print "  <li><a id=\"$relid.$sectid\"",
388           " href=\"javascript:toggleList('$relid.$sectid')\">",
389           ($heading || ''), "</a>&nbsp;&nbsp;&nbsp;$numItemsStr\n"
390       if ($has_release_sections and $heading);
391
392     my $list_type = $items->[0] || '';
393     my $list = ($has_release_sections || $list_type eq 'numbered' ? 'ol' : 'ul');
394     my $listid = $sectid ? "$relid.$sectid" : $relid;
395     print "    <$list id=\"$listid.list\">\n"
396       unless ($has_release_sections and not $heading);
397
398     for my $itemnum (1..$#{$items}) {
399       my $item = $items->[$itemnum];
400       $item =~ s:&:&amp;:g;               # Escape HTML metachars, but leave 
401       $item =~ s:<(?!/?code>):&lt;:gi;    #   <code> tags intact and add <pre>
402       $item =~ s:(?<!code)>:&gt;:gi;      #   wrappers for non-inline sections
403       $item =~ s{((?:^|.*\n)\s*)<code>(?!</code>.+)(.+)</code>(?![ \t]*\S)}
404                 { 
405                   my $prefix = $1; 
406                   my $code = $2;
407                   $code =~ s/\s+$//;
408                   "$prefix<code><pre>$code</pre></code>"
409                 }gise;
410
411       # Put attributions on their own lines.
412       # Check for trailing parenthesized attribution with no following period.
413       # Exclude things like "(see #3 above)" and "(use the bug number instead of xxxx)" 
414       unless ($item =~ s:\s*(\((?!see #|use the bug number)[^()"]+?\))\s*$:\n<br /><span class="attrib">$1</span>:) {
415         # If attribution is not found, then look for attribution with a
416         # trailing period, but try not to include trailing parenthesized things
417         # that are not attributions.
418         #
419         # Rule of thumb: if a trailing parenthesized expression with a following
420         # period does not contain "LUCENE-XXX", and it either has three or 
421         # fewer words or it includes the word "via" or the phrase "updates from",
422             # then it is considered to be an attribution.
423
424         $item =~ s{(\s*(\((?!see \#|use the bug number)[^()"]+?\)))
425                    ((?:\.|(?i:\.?\s*Issue\s+\d{3,}|LUCENE-\d+)\.?)\s*)$}
426                   {
427                     my $subst = $1;  # default: no change
428                     my $parenthetical = $2;
429                                 my $trailing_period_and_or_issue = $3;
430                     if ($parenthetical !~ /LUCENE-\d+/) {
431                       my ($no_parens) = $parenthetical =~ /^\((.*)\)$/s;
432                       my @words = grep {/\S/} split /\s+/, $no_parens;
433                       if ($no_parens =~ /\b(?:via|updates\s+from)\b/i || scalar(@words) <= 3) {
434                         $subst = "\n<br /><span class=\"attrib\">$parenthetical</span>";
435                       }
436                     }
437                     $subst . $trailing_period_and_or_issue;
438                   }ex;
439       }
440
441       $item =~ s{(.*?)(<code><pre>.*?</pre></code>)|(.*)}
442                 {
443                   my $uncode = undef;
444                   if (defined($2)) {
445                     $uncode = $1 || '';
446                     $uncode =~ s{((?<=\n)[ ]*-.*\n(?:.*\n)*)}
447                                 {
448                                   my $bulleted_list = $1;
449                                   $bulleted_list 
450                                     =~ s{(?:(?<=\n)|\A)[ ]*-[ ]*(.*(?:\n|\z)(?:[ ]+[^ -].*(?:\n|\z))*)}
451                                         {<li class="bulleted-list">\n$1</li>\n}g;
452                                   $bulleted_list
453                                     =~ s!(<li.*</li>\n)!<ul class="bulleted-list">\n$1</ul>\n!s;
454                                   $bulleted_list;
455                                 }ge;
456                     "$uncode$2";
457                   } else {
458                     $uncode = $3 || '';
459                     $uncode =~ s{((?<=\n)[ ]*-.*\n(?:.*\n)*)}
460                                 {
461                                   my $bulleted_list = $1;
462                                   $bulleted_list 
463                                     =~ s{(?:(?<=\n)|\A)[ ]*-[ ]*(.*(?:\n|\z)(?:[ ]+[^ -].*(?:\n|\z))*)}
464                                         {<li class="bulleted-list">\n$1</li>\n}g;
465                                   $bulleted_list
466                                     =~ s!(<li.*</li>\n)!<ul class="bulleted-list">\n$1</ul>\n!s;
467                                   $bulleted_list;
468                                 }ge;
469                     $uncode;
470                   }
471                 }sge;
472
473       $item =~ s:\n{2,}:\n<p/>\n:g;                   # Keep paragraph breaks
474       # Link LUCENE-XXX, SOLR-XXX and INFRA-XXX to JIRA
475       $item =~ s{(?:${jira_url_prefix})?((?:LUCENE|SOLR|INFRA)-\d+)}
476                 {<a href="${jira_url_prefix}$1">$1</a>}g;
477       $item =~ s{(issue\s*\#?\s*(\d{3,}))}            # Link Issue XXX to JIRA
478                 {<a href="${jira_url_prefix}LUCENE-$2">$1</a>}gi;
479       # Link Lucene XXX, SOLR XXX and INFRA XXX to JIRA
480       $item =~ s{((LUCENE|SOLR|INFRA)\s+(\d{3,}))}
481                 {<a href="${jira_url_prefix}\U$2\E-$3">$1</a>}gi;
482       # Find single Bugzilla issues
483       $item =~ s~((?i:bug|patch|issue)\s*\#?\s*(\d+))
484                 ~ my $issue = $1;
485                   my $jira_issue_num = $bugzilla_jira_map{$2}; # Link to JIRA copies
486                   $issue = qq!<a href="${jira_url_prefix}LUCENE-$jira_issue_num">!
487                          . qq!$issue&nbsp;[LUCENE-$jira_issue_num]</a>!
488                     if (defined($jira_issue_num));
489                   $issue;
490                 ~gex;
491       # Find multiple Bugzilla issues
492       $item =~ s~(?<=(?i:bugs))(\s*)(\d+)(\s*(?i:\&|and)\s*)(\d+)
493                             ~ my $leading_whitespace = $1;
494                               my $issue_num_1 = $2;
495                               my $interlude = $3;
496                   my $issue_num_2 = $4;
497                   # Link to JIRA copies
498                   my $jira_issue_1 = $bugzilla_jira_map{$issue_num_1};
499                   my $issue1
500                                   = qq!<a href="${jira_url_prefix}LUCENE-$jira_issue_1">!
501                       . qq!$issue_num_1&nbsp;[LUCENE-$jira_issue_1]</a>!
502                     if (defined($jira_issue_1));
503                   my $jira_issue_2 = $bugzilla_jira_map{$issue_num_2};
504                   my $issue2
505                                   = qq!<a href="${jira_url_prefix}LUCENE-$jira_issue_2">!
506                       . qq!$issue_num_2&nbsp;[LUCENE-$jira_issue_2]</a>!
507                     if (defined($jira_issue_2));
508                   $leading_whitespace . $issue1 . $interlude . $issue2;
509                 ~gex;
510
511       print "      <li>$item</li>\n";
512     }
513     print "    </$list>\n" unless ($has_release_sections and not $heading);
514     print "  </li>\n" if ($has_release_sections);
515   }
516   print "</ul>\n" if ($has_release_sections);
517 }
518 print "</ul>\n" if ($relcnt > 3);
519 print "</body>\n</html>\n";
520
521
522 #
523 # Subroutine: has_release_sections
524 #
525 # Takes one parameter:
526 #
527 #    - The $sections array reference
528 #
529 # Returns one scalar:
530 #
531 #    - A boolean indicating whether there are release sections 
532 #
533 sub has_release_sections {
534   my $sections = shift;
535   my $has_release_sections = 0;
536   my $first_titled_section_num = -1;
537   for my $section_num (0 .. $#{$sections}) {
538     if ($sections->[$section_num][0]) {
539       $has_release_sections = 1;
540       last;
541     }
542   }
543   return $has_release_sections;
544 }
545
546
547 #
548 # Subroutine: get_list_type
549 #
550 # Takes one parameter:
551 #
552 #    - The first line of a sub-section/point
553 #
554 # Returns one scalar:
555 #
556 #    - The list type: 'numbered'; or one of the bulleted types '-', or '.' or
557 #      'paragraph'.
558 #
559 sub get_list_type {
560   my $first_list_item_line = shift;
561   my $type = 'paragraph'; # Default to paragraph type
562
563   if ($first_list_item_line =~ /^\s{0,2}\d+\.\s+\S+/) {
564     $type = 'numbered';
565   } elsif ($first_list_item_line =~ /^\s*([-.*])\s+\S+/) {
566     $type = $1;
567   }
568   return $type;
569 }
570
571
572 #
573 # Subroutine: get_release_date
574 #
575 # Takes two parameters:
576 #
577 #    - Release name
578 #    - Release info, potentially including a release date
579 #
580 # Returns two scalars:
581 #
582 #    - The release date, in format YYYY-MM-DD
583 #    - The remainder of the release info (if any), with release date stripped
584 #
585 sub get_release_date {
586   my $release = shift;
587   my $relinfo = shift;
588
589   my ($year, $month, $dom, $reldate);
590
591   if ($relinfo) {
592     if ($relinfo =~ s:\s*(2\d\d\d)([-./])
593                       (1[012]|0?[1-9])\2
594                       ([12][0-9]|30|31|0?[1-9])\s*: :x) {
595       # YYYY-MM-DD   or   YYYY-M-D   or   YYYY-MM-D   or   YYYY-M-DD
596       $year = $1;
597       $month = $3;
598       $dom = $4;
599       $dom = "0$dom" if (length($dom) == 1);
600       $reldate = "$year-$month-$dom";
601     } elsif ($relinfo =~ s:\s*(1[012]|0?[1-9])([-./])
602                            ([12][0-9]|30|31|0?[1-9])\2
603                            (2\d\d\d)\s*: :x) {
604       # MM-DD-YYYY   or   M-D-YYYY   or   MM-D-YYYY   or   M-DD-YYYY
605       $month = $1;
606       $dom = $3;
607       $dom = "0$dom" if (length($dom) == 1);
608       $year = $4;
609       $reldate = "$year-$month-$dom";
610     } elsif ($relinfo =~ s:($month_regex)\s*
611                            ([12][0-9]|30|31|0?[1-9])((st|rd|th)\.?)?,?\s*
612                            (2\d\d\d)\s*: :x) {
613       # MMMMM DD, YYYY   or   MMMMM DDth, YYYY
614       $month = $month_nums{$1};
615       $dom = $2;
616       $dom = "0$dom" if (length($dom) == 1);
617       $year = $5;
618       $reldate = "$year-$month-$dom";
619     } elsif ($relinfo =~ s:([12][0-9]|30|31|0?[1-9])(\s+|[-/.])
620                            ($month_regex)\2
621                            (2\d\d\d)\s*: :x) {
622       # DD MMMMM YYYY
623       $dom = $1;
624       $dom = "0$dom" if (length($dom) == 1);
625       $month = $month_nums{$3};
626       $year = $4;
627       $reldate = "$year-$month-$dom";
628     }
629   }
630
631   unless ($reldate) {     # No date found in $relinfo
632     # Handle '1.2 RC6', which should be '1.2 final'
633     $release = '1.2 final' if ($release eq '1.2 RC6');
634
635     $reldate = ( exists($release_dates{$release}) 
636                ? $release_dates{$release}
637                : 'unknown');
638   }
639
640   $relinfo =~ s/,?\s*$//; # Trim trailing comma and whitespace
641
642   return ($reldate, $relinfo);
643 }
644
645
646 #
647 # setup_release_dates
648 #
649 # Returns a list of alternating release names and dates, for use in populating
650 # the %release_dates hash.
651 #
652 # Pulls release dates via the JIRA REST API.  JIRA does not list
653 # X.Y RCZ releases independently from releases X.Y, so the RC dates
654 # as well as those named "final" are included below.
655 #
656 sub setup_release_dates {
657   my %release_dates
658        = ( '0.01' => '2000-03-30',      '0.04' => '2000-04-19',
659            '1.0' => '2000-10-04',       '1.01b' => '2001-06-02',
660            '1.2 RC1' => '2001-10-02',   '1.2 RC2' => '2001-10-19',
661            '1.2 RC3' => '2002-01-27',   '1.2 RC4' => '2002-02-14',
662            '1.2 RC5' => '2002-05-14',   '1.2 final' => '2002-06-13',
663            '1.3 RC1' => '2003-03-24',   '1.3 RC2' => '2003-10-22',
664            '1.3 RC3' => '2003-11-25',   '1.3 final' => '2003-12-26',
665            '1.4 RC1' => '2004-03-29',   '1.4 RC2' => '2004-03-30',
666            '1.4 RC3' => '2004-05-11',   '1.4 final' => '2004-07-01',
667            '1.4.1' => '2004-08-02',     '1.4.2' => '2004-10-01',
668            '1.4.3' => '2004-12-07',     '1.9 RC1' => '2006-02-21',
669            '1.9 final' => '2006-02-27', '1.9.1' => '2006-03-02',
670            '2.0.0' => '2006-05-26',     '2.1.0' => '2007-02-14',
671            '2.2.0' => '2007-06-19',     '2.3.0' => '2008-01-21',
672            '2.3.1' => '2008-02-22',     '2.3.2' => '2008-05-05',
673            '2.4.0' => '2008-10-06',     '2.4.1' => '2009-03-09',
674            '2.9.0' => '2009-09-23',     '2.9.1' => '2009-11-06',
675            '3.0.0' => '2009-11-25');
676
677   my $project_info_json = get_url_contents($project_info_url);
678   
679   my $project_info = json2perl($project_info_json);
680   for my $version (@{$project_info->{versions}}) {
681     if ($version->{releaseDate}) {
682       my $date = substr($version->{releaseDate}, 0, 10);
683       my $version_name = $version->{name};
684       $release_dates{$version->{name}} = $date;
685       if ($version_name =~ /^\d+\.\d+$/) {
686         my $full_version_name = "$version->{name}.0";
687         $release_dates{$full_version_name} = $date;
688       }
689     }
690   }
691   return %release_dates;
692 }
693
694 #
695 # returns contents of the passed in url
696 #
697 sub get_url_contents {
698   my $url = shift;
699   my $tryWget = `wget --no-check-certificate -O - $url`;
700   if ($? eq 0) {
701     return $tryWget;
702   }
703   my $tryCurl = `curl $url`;
704   if ($? eq 0) {
705     return $tryCurl;
706   }
707   die "could not retrieve $url with either wget or curl!";
708 }
709
710 #
711 # setup_month_regex
712 #
713 # Returns a string containing a regular expression with alternations for
714 # the standard month representations in English.
715 #
716 sub setup_month_regex {
717   return '(?i:Jan(?:|\.|uary)|Feb(?:|\.|ruary)|Mar(?:|\.|ch)'
718        . '|Apr(?:|\.|il)|May|Jun(?:|\.|e)|Jul(?:|\.|y)|Aug(?:|\.|ust)'
719        . '|Sep(?:|\.|t(?:|\.|ember))|Oct(?:|\.|ober)|Nov(?:|\.|ember)'
720        . '|Dec(?:|\.|ember))';
721 }
722
723
724 #
725 # setup_month_nums
726 #
727 # Returns a list of alternating English month representations and the two-digit
728 # month number corresponding to them, for use in populating the %month_nums
729 # hash.
730 #
731 sub setup_month_nums {
732   return ( 'Jan' => '01', 'Jan.' => '01', 'January' => '01',
733            'Feb' => '02', 'Feb.' => '02', 'February' => '02',
734            'Mar' => '03', 'Mar.' => '03', 'March' => '03',
735            'Apr' => '04', 'Apr.' => '04', 'April' => '04',
736            'May' => '05',
737            'Jun' => '06', 'Jun.' => '06', 'June' => '06',
738            'Jul' => '07', 'Jul.' => '07', 'July' => '07',
739            'Aug' => '08', 'Aug.' => '08', 'August' => '08',
740            'Sep' => '09', 'Sep.' => '09',
741            'Sept' => '09', 'Sept.' => '09', 'September' => '09',
742            'Oct' => '10', 'Oct.' => '10', 'October' => '10',
743            'Nov' => '11', 'Nov.' => '11', 'November' => '11',
744            'Dec' => '12', 'Dec.' => '12', 'December' => '12' );
745 }
746
747
748 #
749 # setup_bugzilla_jira_map
750 #
751 # Returns a list of alternating Bugzilla bug IDs and LUCENE-* JIRA issue
752 # numbers, for use in populating the %bugzilla_jira_map hash
753 #
754 sub setup_bugzilla_jira_map {
755   return (  4049 =>   1,  4102 =>   2,  4105 =>   3,  4254 =>   4,
756             4555 =>   5,  4568 =>   6,  4754 =>   7,  5313 =>   8,
757             5456 =>   9,  6078 =>  10,  6091 =>  11,  6140 =>  12,
758             6292 =>  13,  6315 =>  14,  6469 =>  15,  6914 =>  16,
759             6968 =>  17,  7017 =>  18,  7019 =>  19,  7088 =>  20,
760             7089 =>  21,  7275 =>  22,  7412 =>  23,  7461 =>  24,
761             7574 =>  25,  7710 =>  26,  7750 =>  27,  7782 =>  28,
762             7783 =>  29,  7912 =>  30,  7974 =>  31,  8307 =>  32,
763             8525 =>  33,  9015 =>  34,  9110 =>  35,  9347 =>  36,
764             9454 =>  37,  9782 =>  38,  9853 =>  39,  9906 =>  40,
765             9970 =>  41, 10340 =>  42, 10341 =>  43, 10342 =>  44,
766            10343 =>  45, 10849 =>  46, 11109 =>  47, 11359 =>  48,
767            11636 =>  49, 11918 =>  50, 12137 =>  51, 12273 =>  52,
768            12444 =>  53, 12569 =>  54, 12588 =>  55, 12619 =>  56,
769            12667 =>  57, 12723 =>  58, 12749 =>  59, 12761 =>  60,
770            12950 =>  61, 13102 =>  62, 13166 =>  63, 14028 =>  64,
771            14355 =>  65, 14373 =>  66, 14412 =>  67, 14485 =>  68,
772            14585 =>  69, 14665 =>  70, 14900 =>  71, 15739 =>  72,
773            16025 =>  73, 16043 =>  74, 16167 =>  75, 16245 =>  76,
774            16364 =>  77, 16437 =>  78, 16438 =>  79, 16470 =>  80,
775            16677 =>  81, 16719 =>  82, 16730 =>  83, 16816 =>  84,
776            16952 =>  85, 17242 =>  86, 17954 =>  88, 18014 =>  89,
777            18088 =>  90, 18177 =>  91, 18410 =>  87, 18833 =>  92,
778            18847 =>  93, 18914 =>  94, 18927 =>  95, 18928 =>  96,
779            18929 =>  97, 18931 =>  98, 18932 =>  99, 18933 => 100,
780            18934 => 101, 19058 => 102, 19149 => 103, 19189 => 104,
781            19253 => 105, 19468 => 106, 19686 => 107, 19736 => 108,
782            19751 => 109, 19834 => 110, 19844 => 111, 20024 => 112,
783            20081 => 113, 20123 => 114, 20196 => 115, 20283 => 116,
784            20290 => 117, 20461 => 118, 20901 => 119, 21128 => 120,
785            21149 => 121, 21150 => 122, 21189 => 123, 21446 => 124,
786            21921 => 126, 22344 => 128, 22469 => 130, 22987 => 131,
787            23307 => 133, 23308 => 134, 23422 => 135, 23466 => 136,
788            23505 => 137, 23534 => 138, 23545 => 139, 23650 => 140,
789            23655 => 141, 23685 => 142, 23702 => 143, 23727 => 144,
790            23730 => 145, 23750 => 146, 23754 => 147, 23770 => 148,
791            23771 => 149, 23773 => 150, 23774 => 151, 23782 => 152,
792            23784 => 153, 23786 => 154, 23838 => 155, 23964 => 156,
793            24084 => 129, 24237 => 157, 24265 => 158, 24301 => 159,
794            24370 => 160, 24665 => 161, 24786 => 162, 24902 => 163,
795            24903 => 164, 24913 => 165, 25666 => 125, 25793 => 166,
796            25820 => 167, 25945 => 168, 26120 => 169, 26196 => 170,
797            26268 => 171, 26360 => 172, 26396 => 173, 26397 => 174,
798            26624 => 175, 26634 => 176, 26666 => 177, 26702 => 178,
799            26716 => 179, 26763 => 180, 26884 => 181, 26939 => 182,
800            27168 => 183, 27174 => 184, 27182 => 185, 27268 => 186,
801            27326 => 187, 27354 => 188, 27408 => 189, 27423 => 190,
802            27433 => 191, 27491 => 192, 27587 => 193, 27626 => 194,
803            27638 => 195, 27743 => 196, 27772 => 197, 27799 => 198,
804            27819 => 199, 27865 => 200, 27868 => 201, 27903 => 202,
805            27987 => 203, 28030 => 204, 28050 => 205, 28065 => 206,
806            28074 => 207, 28108 => 208, 28181 => 209, 28182 => 210,
807            28183 => 211, 28187 => 212, 28285 => 213, 28336 => 214,
808            28339 => 215, 28405 => 216, 28462 => 217, 28601 => 218,
809            28640 => 219, 28748 => 220, 28827 => 221, 28855 => 222,
810            28856 => 223, # Clone: 28856 => 507,
811            28858 => 224, 28960 => 132, 28964 => 127, 29033 => 225,
812            29256 => 226, 29299 => 227, 29302 => 228, 29370 => 229,
813            29432 => 230, 29548 => 231, 29749 => 232, 29756 => 233,
814            29774 => 234, 29931 => 235, 29984 => 236, 30013 => 237,
815            30016 => 238, 30026 => 239, 30027 => 240, 30049 => 241,
816            30058 => 242, 30232 => 243, 30237 => 244, 30240 => 245,
817            30242 => 246, 30265 => 247, 30327 => 248, 30330 => 249,
818            30360 => 250, 30376 => 251, 30382 => 252, 30421 => 253,
819            30429 => 254, 30452 => 255, 30480 => 256, 30522 => 257,
820            30617 => 258, 30621 => 259, 30628 => 260, 30629 => 261,
821            30668 => 262, 30678 => 263, 30685 => 264, 30736 => 265,
822            30785 => 266, 30818 => 267, 30835 => 268, 30844 => 269,
823            30977 => 270, 30985 => 271, 31061 => 272, 31120 => 273,
824            31149 => 274, 31174 => 275, 31240 => 276, 31241 => 277,
825            31294 => 278, 31350 => 279, 31368 => 280, 31420 => 281,
826            31469 => 282, 31508 => 283, 31554 => 284, 31617 => 285,
827            31619 => 286, 31690 => 287, 31706 => 288, 31708 => 289,
828            31746 => 290, 31747 => 291, 31748 => 292, 31784 => 293,
829            31785 => 294, 31841 => 295, 31882 => 296, 31926 => 297,
830            31976 => 298, 32053 => 299, 32055 => 300, 32088 => 301,
831            32090 => 302, 32109 => 303, 32115 => 304, 32143 => 305,
832            32167 => 306, 32171 => 307, 32192 => 308, 32227 => 309,
833            32228 => 310, 32234 => 311, 32291 => 312, 32307 => 313,
834            32334 => 314, 32353 => 315, 32365 => 316, 32403 => 317,
835            32432 => 318, 32467 => 319, 32468 => 320, 32580 => 321,
836            32626 => 322, 32674 => 323, 32687 => 324, 32712 => 325,
837            32847 => 326, 32887 => 327, 32921 => 328, 32942 => 329,
838            32965 => 330, 32981 => 331, 32999 => 332, 33019 => 333,
839            33076 => 334, 33134 => 335, 33158 => 336, 33161 => 337,
840            33197 => 338, 33239 => 339, 33389 => 340, 33395 => 341,
841            33397 => 342, 33442 => 343, 33449 => 344, 33459 => 345,
842            33472 => 346, 33642 => 347, 33648 => 348, 33649 => 349,
843            33654 => 350, 33678 => 351, 33725 => 352, 33799 => 353,
844            33820 => 354, 33835 => 355, 33848 => 356, 33851 => 357,
845            33877 => 358, 33884 => 359, 33974 => 360, 34028 => 361,
846            34066 => 362, 34149 => 363, 34154 => 364, 34193 => 365,
847            34279 => 366, 34320 => 367, 34331 => 368, 34359 => 369,
848            34407 => 370, 34408 => 371, 34447 => 372, 34453 => 373,
849            34477 => 374, # Clone: 34477 => 459,
850            34486 => 375, 34528 => 376, 34544 => 377, 34545 => 378,
851            34563 => 379, 34570 => 380, 34585 => 381, 34629 => 382,
852            34673 => 383, 34684 => 384, 34695 => 385, 34816 => 386,
853            34882 => 387, 34930 => 388, 34946 => 389, 34995 => 390,
854            35029 => 391, 35037 => 392, 35157 => 393, 35241 => 394,
855            35284 => 395, # Clone: 35284 => 466,
856            35388 => 396, 35446 => 397, 35454 => 398, 35455 => 399,
857            35456 => 400, 35468 => 401, 35491 => 402, 35518 => 403,
858            35626 => 404, 35664 => 405, 35665 => 406, 35668 => 407,
859            35729 => 408, 35730 => 409, 35731 => 410, 35796 => 411,
860            35822 => 412, 35823 => 413, 35838 => 414, 35879 => 415,
861            # Clone: 35879 => 616,
862            35886 => 416, 35971 => 417, 36021 => 418, 36078 => 419,
863            36101 => 420, 36135 => 421, 36147 => 422, 36197 => 423,
864            36219 => 424, 36241 => 425, 36242 => 426, 36292 => 427,
865            36296 => 428, 36333 => 429, 36622 => 430, 36623 => 431,
866            36628 => 432);
867 }
868
869 #
870 # json2perl
871 #
872 # Converts a JSON string to the equivalent Perl data structure
873 #
874 sub json2perl {
875   my $json_string = shift;
876   $json_string =~ s/(:\s*)(true|false)/$1"$2"/g;
877   $json_string =~ s/":/",/g;
878   return eval $json_string;
879 }
880
881 1;