2012-09-25 17:03:58 -04:00
#!/usr/bin/python
# Licensed 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.
import re
import sys
from optparse import OptionParser
import httplib
import urllib
import cgi
try :
import json
except ImportError :
import simplejson as json
namePattern = re . compile ( r ' \ ([0-9]+ \ ) ' )
def clean ( str ) :
return quoteHtml ( re . sub ( namePattern , " " , str ) )
def formatComponents ( str ) :
str = re . sub ( namePattern , ' ' , str ) . replace ( " ' " , " " )
if str != " " :
ret = " ( " + str + " ) "
else :
ret = " "
return quoteHtml ( ret )
def quoteHtml ( str ) :
return cgi . escape ( str ) . encode ( ' ascii ' , ' xmlcharrefreplace ' )
def mstr ( obj ) :
if ( obj == None ) :
return " "
return unicode ( obj )
class Version :
""" Represents a version number """
def __init__ ( self , data ) :
self . mod = False
self . data = data
found = re . match ( ' ^(( \ d+)( \ . \ d+)*).*$ ' , data )
if ( found ) :
self . parts = [ int ( p ) for p in found . group ( 1 ) . split ( ' . ' ) ]
else :
self . parts = [ ]
# backfill version with zeroes if missing parts
self . parts . extend ( ( 0 , ) * ( 3 - len ( self . parts ) ) )
def decBugFix ( self ) :
self . mod = True
self . parts [ 2 ] - = 1
return self
def __str__ ( self ) :
if ( self . mod ) :
return ' . ' . join ( [ str ( p ) for p in self . parts ] )
return self . data
def __cmp__ ( self , other ) :
return cmp ( self . parts , other . parts )
class Jira :
""" A single JIRA """
def __init__ ( self , data , parent ) :
self . key = data [ ' key ' ]
self . fields = data [ ' fields ' ]
self . parent = parent
self . notes = None
def getId ( self ) :
return mstr ( self . key )
def getDescription ( self ) :
return mstr ( self . fields [ ' description ' ] )
def getReleaseNote ( self ) :
if ( self . notes == None ) :
field = self . parent . fieldIdMap [ ' Release Note ' ]
if ( self . fields . has_key ( field ) ) :
self . notes = mstr ( self . fields [ field ] )
else :
self . notes = self . getDescription ( )
return self . notes
def getPriority ( self ) :
ret = " "
pri = self . fields [ ' priority ' ]
if ( pri != None ) :
ret = pri [ ' name ' ]
return mstr ( ret )
def getAssignee ( self ) :
ret = " "
mid = self . fields [ ' assignee ' ]
if ( mid != None ) :
ret = mid [ ' displayName ' ]
return mstr ( ret )
def getComponents ( self ) :
return " , " . join ( [ comp [ ' name ' ] for comp in self . fields [ ' components ' ] ] )
def getSummary ( self ) :
return self . fields [ ' summary ' ]
def getType ( self ) :
ret = " "
mid = self . fields [ ' issuetype ' ]
if ( mid != None ) :
ret = mid [ ' name ' ]
return mstr ( ret )
def getReporter ( self ) :
ret = " "
mid = self . fields [ ' reporter ' ]
if ( mid != None ) :
ret = mid [ ' displayName ' ]
return mstr ( ret )
def getProject ( self ) :
ret = " "
mid = self . fields [ ' project ' ]
if ( mid != None ) :
ret = mid [ ' key ' ]
return mstr ( ret )
class JiraIter :
""" An Iterator of JIRAs """
def __init__ ( self , versions ) :
self . versions = versions
resp = urllib . urlopen ( " https://issues.apache.org/jira/rest/api/2/field " )
data = json . loads ( resp . read ( ) )
self . fieldIdMap = { }
for part in data :
self . fieldIdMap [ part [ ' name ' ] ] = part [ ' id ' ]
self . jiras = [ ]
at = 0
end = 1
count = 100
while ( at < end ) :
2013-01-28 14:18:10 -05:00
params = urllib . urlencode ( { ' jql ' : " project in (HADOOP,HDFS,MAPREDUCE,YARN) and fixVersion in ( ' " + " ' , ' " . join ( versions ) + " ' ) and resolution = Fixed " , ' startAt ' : at , ' maxResults ' : count } )
2012-09-25 17:03:58 -04:00
resp = urllib . urlopen ( " https://issues.apache.org/jira/rest/api/2/search? %s " % params )
data = json . loads ( resp . read ( ) )
if ( data . has_key ( ' errorMessages ' ) ) :
raise Exception ( data [ ' errorMessages ' ] )
at = data [ ' startAt ' ] + data [ ' maxResults ' ]
end = data [ ' total ' ]
self . jiras . extend ( data [ ' issues ' ] )
self . iter = self . jiras . __iter__ ( )
def __iter__ ( self ) :
return self
def next ( self ) :
data = self . iter . next ( )
j = Jira ( data , self )
return j
class Outputs :
""" Several different files to output to at the same time """
def __init__ ( self , base_file_name , file_name_pattern , keys , params = { } ) :
self . params = params
self . base = open ( base_file_name % params , ' w ' )
self . others = { }
for key in keys :
both = dict ( params )
both [ ' key ' ] = key
self . others [ key ] = open ( file_name_pattern % both , ' w ' )
def writeAll ( self , pattern ) :
both = dict ( self . params )
both [ ' key ' ] = ' '
self . base . write ( pattern % both )
for key in self . others . keys ( ) :
both = dict ( self . params )
both [ ' key ' ] = key
self . others [ key ] . write ( pattern % both )
def writeKeyRaw ( self , key , str ) :
self . base . write ( str )
if ( self . others . has_key ( key ) ) :
self . others [ key ] . write ( str )
def close ( self ) :
self . base . close ( )
for fd in self . others . values ( ) :
fd . close ( )
def main ( ) :
parser = OptionParser ( usage = " usage: % prog [options] [USER-ignored] [PASSWORD-ignored] [VERSION] " )
parser . add_option ( " -v " , " --version " , dest = " versions " ,
action = " append " , type = " string " ,
help = " versions in JIRA to include in releasenotes " , metavar = " VERSION " )
parser . add_option ( " --previousVer " , dest = " previousVer " ,
action = " store " , type = " string " ,
help = " previous version to include in releasenotes " , metavar = " VERSION " )
( options , args ) = parser . parse_args ( )
if ( options . versions == None ) :
options . versions = [ ]
if ( len ( args ) > 2 ) :
options . versions . append ( args [ 2 ] )
if ( len ( options . versions ) < = 0 ) :
parser . error ( " At least one version needs to be supplied " )
versions = [ Version ( v ) for v in options . versions ] ;
versions . sort ( ) ;
maxVersion = str ( versions [ - 1 ] )
if ( options . previousVer == None ) :
options . previousVer = str ( versions [ 0 ] . decBugFix ( ) )
print >> sys . stderr , " WARNING: no previousVersion given, guessing it is " + options . previousVer
list = JiraIter ( options . versions )
version = maxVersion
outputs = Outputs ( " releasenotes. %(ver)s .html " ,
" releasenotes. %(key)s . %(ver)s .html " ,
[ " HADOOP " , " HDFS " , " MAPREDUCE " , " YARN " ] , { " ver " : maxVersion , " previousVer " : options . previousVer } )
head = ' <META http-equiv= " Content-Type " content= " text/html; charset=UTF-8 " > \n ' \
' <title>Hadoop %(key)s %(ver)s Release Notes</title> \n ' \
' <STYLE type= " text/css " > \n ' \
' H1 { font-family: sans-serif} \n ' \
' H2 { font-family: sans-serif; margin-left: 7mm} \n ' \
' TABLE { margin-left: 7mm} \n ' \
' </STYLE> \n ' \
' </head> \n ' \
' <body> \n ' \
' <h1>Hadoop %(key)s %(ver)s Release Notes</h1> \n ' \
' These release notes include new developer and user-facing incompatibilities, features, and major improvements. \n ' \
' <a name= " changes " /> \n ' \
' <h2>Changes since Hadoop %(previousVer)s </h2> \n ' \
' <ul> \n '
outputs . writeAll ( head )
for jira in list :
line = ' <li> <a href= " https://issues.apache.org/jira/browse/ %s " > %s </a>. \n ' \
' %s %s reported by %s and fixed by %s %s <br> \n ' \
' <b> %s </b><br> \n ' \
' <blockquote> %s </blockquote></li> \n ' \
% ( quoteHtml ( jira . getId ( ) ) , quoteHtml ( jira . getId ( ) ) , clean ( jira . getPriority ( ) ) , clean ( jira . getType ( ) ) . lower ( ) ,
quoteHtml ( jira . getReporter ( ) ) , quoteHtml ( jira . getAssignee ( ) ) , formatComponents ( jira . getComponents ( ) ) ,
quoteHtml ( jira . getSummary ( ) ) , quoteHtml ( jira . getReleaseNote ( ) ) )
outputs . writeKeyRaw ( jira . getProject ( ) , line )
outputs . writeAll ( " </ul> \n </body></html> \n " )
outputs . close ( )
if __name__ == " __main__ " :
main ( )