#!/usr/bin/env/python
#
# Copyright 2011 The Apache Software Foundation
#
# 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.
#
# Generates Apache-style release notes in an HTML file
# for a specific commit range.
#
# Run with '-h' to see usage.

import datetime
import os
import re
import sys

try:
  from xml.etree import ElementTree
except ImportError:
  print "Building release notes is not supported on this platform."
  sys.exit(0)



NUM_ARGS = 6

def print_usage(prgm_name):
  """ Print the usage for this program """
  print "Usage: " + prgm_name + " <target-dir> <git-src> <commit-range> " \
      + "<newversion> <oldversion>"
  print ""
  print "  <target-dir>: Directory where release notes should be written to."
  print "  <git-src>: Root of the git repository to collect info from."
  print "  <commit-range>: What set of commits form this release."
  print "  <newversion>: The version number to print in the release notes."
  print "  <oldversion>: The previous release version number."


def get_log(git_dir, commit_range):
  """ Return the set of lines corresponding to the git log for the specified
      commit range.
  """

  os.chdir(git_dir)
  cmd = "git log --no-color '--pretty=format:%s' '" + commit_range + "'"
  return os.popen(cmd).readlines()


def sanitize_log(in_log):
  """ 'sanitize' the log.
      Some entries do not have a separate subject and body by accident.
      Return a new log that only includes the first sentence of each
      subject. (Note that we also usually have a 'SQOOP-nn.' before this
      sentence.)
  """
  out_log = []
  for line in in_log:
    line = line.strip()
    sentences = line.split(". ")
    if len(sentences) <= 2:
      out_log.append(line) # Unchanged original input.
    else:
      out_log.append(sentences[0] + ". " + sentences[1] + ".")

  return out_log


def get_jira_doc(issue):
  """ Get the XML document from JIRA for a specified issue. """

  xml = os.popen("curl -s 'https://issues.cloudera.org/si/jira.issueviews:" \
      + "issue-xml/%s/%s.xml?field=key&field=type&field=parent'" % (issue, issue)).read()
  return ElementTree.fromstring(xml)


def get_jira_issue_types(log):
  """ Return a dict from issue-type -> ((issue-name, summary) list) by looking
      up the issues in our JIRA.
  """

  d = {}

  def add_issue(issue, typ, line):
    try:
      d[typ].append((issue, line))
    except KeyError:
      # This issue type hasn't been seen yet. Add a new list.
      d[typ] = [ (issue, line) ]

  jira_reg = r"^(SQOOP-\d+)"
  for line in log:
    matched_line = False
    for m in re.finditer(jira_reg, line, re.M):
      matched_line = True
      jira = m.group(1)
      doc = get_jira_doc(jira)
      issue_type = doc.find('./channel/item/type').text
      # Subtasks use the type of their parent item.
      if issue_type == "Sub-task":
        parent_doc = get_jira_doc(doc.find('./channel/item/parent').text)
        issue_type = parent_doc.find('./channel/item/type').text

      add_issue(jira, issue_type, line)
    if not matched_line and not line.startswith("CLOUDERA-BUILD."):
      # This line did not start with "SQOOP-.."
      # Unless it's a CDH buildfix, add it in as a "Task". 
      add_issue("", "Task", line)

  return d


def get_date():
  """ Return the current month and year formatted as a string. """
  return datetime.date.today().strftime("%B, %Y")


def add_links(summary_line):
  """ Given a line like "SQOOP-40. Do something", add links to the JIRA
      and any appropriate SIPs, and return the line with links.
  """

  initial_jira_reg = r"^(SQOOP-\d+)\. (.*)"

  # Reformat the issue id away from the summary.
  m = re.match(initial_jira_reg, summary_line)
  if m == None:
    # Line in unexpected format. Return as-is.
    return summary_line
  jira = m.group(1)
  text = m.group(2)

  # Add links to JIRA and SIP wiki.

  issue_reg = r"(SQOOP-\d+)"
  issue_subst = r'<a href="https://issues.cloudera.org/browse/\1">\1</a>'

  sip_reg = r"(SIP-\d+)"
  sip_subst = r'<a href="http://wiki.github.com/cloudera/sqoop/\1">\1</a>'

  output = "[" + jira + "] - " + text
  output = re.sub(issue_reg, issue_subst, output)
  output = re.sub(sip_reg, sip_subst, output)

  return output


__user_types = {
    "Bug" : "Bug fixes",
    "Improvement" : "Improvements",
    "New Feature" : "New features",
    "Task" : "Tasks"
}

def user_issue_type(typ):
  """ Return a user-friendly issue type string based on the JIRA issue
      type string.
  """
  global __user_types

  try:
    return __user_types[typ]
  except KeyError:
    # If we don't have a plural-form string set, just use the input.
    return typ



def format_html(newversion, oldversion, log, jira_info):
  """ Creates the HTML representation of the release notes and returns
      it as a string.
  """

  output_lines = []
  output_lines.append("""<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Sqoop %(newversion)s Release Notes</title>
<style type="Text/css">
h1 {font-family: sans-serif}
h2 {font-family: sans-serif; margin-left: 7mm}
h4 {font-family: sans-serif; margin-left: 7mm}
</style></head>
<body><h1>Release Notes for Sqoop %(newversion)s: %(date)s</h1>

<p>This document lists all Sqoop issues included in version %(newversion)s
not present in the previous release, %(oldversion)s.</p> 
""" % { "newversion" : newversion,
        "oldversion" : oldversion,
        "date"       : get_date() })

  
  # Sort the output list by issue type.
  types = jira_info.keys()
  types.sort()
  for typ in types:
    output_lines.append("<h4>" + user_issue_type(typ) + ":</h4><ul>\n")
    for (issue, summary) in jira_info[typ]:
      output_lines.append("<li>")
      output_lines.append(add_links(summary))
      output_lines.append("</li>\n")
    output_lines.append("</ul>\n")
    
  output_lines.append("</body></html>\n")
  return "".join(output_lines)


def main(argv):
  if len(argv) > 1 and argv[1] == '-h':
    print_usage(argv[0])
    return 0

  if len(argv) < NUM_ARGS:
    print "Missing required argument(s). Try " + argv[0] + " -h"
    return 1

  target_dir = os.path.abspath(os.path.expanduser(argv[1]))
  git_src = os.path.abspath(os.path.expanduser(argv[2]))
  commit_range = argv[3]
  newversion = argv[4]
  oldversion = argv[5]

  log = get_log(git_src, commit_range)
  log = sanitize_log(log)
  jira_info = get_jira_issue_types(log)   
  html = format_html(newversion, oldversion, log, jira_info)

  os.system("mkdir -p \"" + target_dir + "\"")
  handle = open(os.path.join(target_dir, \
      "sqoop-" + newversion + ".releasenotes.html"), "w")
  handle.write(html)
  handle.close()

  return 0


if __name__ == "__main__":
  sys.exit(main(sys.argv))


