text: #!/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
  
  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 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):
          for jira in sorted(mylist):
              line = '| [%s](' + BASE_URL + '/browse/%s) ' +\
                     '| %s |  %s | %s | %s | %s |\n'
              line = line % (encode_utf8(jira.get_id()),
                             encode_utf8(jira.get_id()),
                             sanitize_text(jira.get_summary()),
                             sanitize_text(jira.get_priority()),
                             format_components(jira.get_components()),
                             sanitize_text(jira.get_reporter()),
                             sanitize_text(jira.get_assignee()))
              self.write_key_raw(jira.get_project(), line)
  