blob: 5bedf5a334f4055c035dd93c5922c7c035d27c43 [file] [log] [blame]
#!/usr/bin/env python
#
# 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.
import re
import urllib2
import sys
import json
import httplib
sys.dont_write_bytecode = True
NAME_PATTERN = re.compile(r' \([0-9]+\)')
BASE_URL = "https://issues.apache.org/jira"
def clean(input_string):
return sanitize_markdown(re.sub(NAME_PATTERN, "", input_string))
def get_jira(jira_url):
""" Provide standard method for fetching content from apache jira and
handling of potential errors. Returns urllib2 response or
raises one of several exceptions."""
try:
response = urllib2.urlopen(jira_url)
except urllib2.HTTPError as http_err:
code = http_err.code
print "JIRA returns HTTP error %d: %s. Aborting." % \
(code, http_err.msg)
error_response = http_err.read()
try:
error_response = json.loads(error_response)
print "- Please ensure that specified projects, fixVersions etc."\
" are correct."
for message in error_response['errorMessages']:
print "-", message
except ValueError:
print "FATAL: Could not parse json response from server."
sys.exit(1)
except urllib2.URLError as url_err:
print "Error contacting JIRA: %s\n" % jira_url
print "Reason: %s" % url_err.reason
raise url_err
except httplib.BadStatusLine as err:
raise err
return response
def format_components(input_string):
input_string = re.sub(NAME_PATTERN, '', input_string).replace("'", "")
if input_string != "":
ret = input_string
else:
# some markdown parsers don't like empty tables
ret = "."
return clean(ret)
# Return the string encoded as UTF-8.
#
# This is necessary for handling markdown in Python.
def encode_utf8(input_string):
return input_string.encode('utf-8')
# Sanitize Markdown input so it can be handled by Python.
#
# The expectation is that the input is already valid Markdown,
# so no additional escaping is required.
def sanitize_markdown(input_string):
input_string = encode_utf8(input_string)
input_string = input_string.replace("\r", "")
input_string = input_string.rstrip()
return input_string
# Sanitize arbitrary text so it can be embedded in MultiMarkdown output.
#
# Note that MultiMarkdown is not Markdown, and cannot be parsed as such.
# For instance, when using pandoc, invoke it as `pandoc -f markdown_mmd`.
#
# Calls sanitize_markdown at the end as a final pass.
def sanitize_text(input_string):
escapes = dict()
# See: https://daringfireball.net/projects/markdown/syntax#backslash
# We only escape a subset of special characters. We ignore characters
# that only have significance at the start of a line.
slash_escapes = "_<>*|"
slash_escapes += "`"
slash_escapes += "\\"
all_chars = set()
# Construct a set of escapes
for c in slash_escapes:
all_chars.add(c)
for c in all_chars:
escapes[c] = "\\" + c
# Build the output string character by character to prevent double escaping
output_string = ""
for c in input_string:
o = c
if c in escapes:
o = escapes[c]
output_string += o
return sanitize_markdown(output_string.rstrip())
# if release notes have a special marker,
# we'll treat them as already in markdown format
def processrelnote(input_string):
relnote_pattern = re.compile('^\<\!\-\- ([a-z]+) \-\-\>')
fmt = relnote_pattern.match(input_string)
if fmt is None:
return sanitize_text(input_string)
else:
return {
'markdown': sanitize_markdown(input_string),
}.get(
fmt.group(1), sanitize_text(input_string))
def to_unicode(obj):
if obj is None:
return ""
return unicode(obj)
class Outputs(object):
"""Several different files to output to at the same time"""
def __init__(self, base_file_name, file_name_pattern, keys, params=None):
if params is None:
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 write_all(self, pattern):
both = dict(self.params)
both['key'] = ''
self.base.write(pattern % both)
for key in self.others:
both = dict(self.params)
both['key'] = key
self.others[key].write(pattern % both)
def write_key_raw(self, key, input_string):
self.base.write(input_string)
if key in self.others:
self.others[key].write(input_string)
def close(self):
self.base.close()
for value in self.others.values():
value.close()
def write_list(self, mylist, skip_credits):
for jira in sorted(mylist):
if skip_credits:
line = '| [{id}]({base_url}/browse/{id}) | {summary} | ' \
'{priority} | {component} |\n'
else:
line = '| [{id}]({base_url}/browse/{id}) | {summary} | ' \
'{priority} | {component} | {reporter} | {assignee} |\n'
args = {'id': encode_utf8(jira.get_id()),
'base_url': BASE_URL,
'summary': sanitize_text(jira.get_summary()),
'priority': sanitize_text(jira.get_priority()),
'component': format_components(jira.get_components()),
'reporter': sanitize_text(jira.get_reporter()),
'assignee': sanitize_text(jira.get_assignee())
}
line = line.format(**args)
self.write_key_raw(jira.get_project(), line)