| #!/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) |