#!/usr/bin/env python

"""Provides functions to convert the `git blame` information for a file to JSON
suitable to read in with Spark.

Attributes:
    JSON_LINE (dict): A dictionary that stores information from `git blame` on
        a file. Each entry corresponds to the information from one line in the
        source file. The variables are as follows:
            - repo_name (str): The name of the repository.
            - commit_id (str): The commit hash.
            - author (str): The author of the commit.
            - author_mail (str): The author's email address.
            - author_time (int): The time the commit was written in seconds
                since the unix epoch.
            - author_timezone (int): The timezone of the author, as an integer.
                The two right most digits are the minute offsets, and the
                remaining leftmost digits are the hour offsets.
            - committer (str): The committer of the commit.
            - committer_mail (str): The committer's email address.
            - committer_time (int): The time the commit was written in seconds
                since the unix epoch.
            - committer_timezone (int): The timezone of the committer, in the
                same form as author_timezone.
            - comment (str): The commit message.
            - filename (str): The name of the file.
            - line_num (int): The line number of the text in the file after the
                commit was applied.
            - line (str): The text of the source line.
"""

from subprocess import check_output
import json
from copy import deepcopy

# The JSON object to
JSON_LINE = {
    "repo_name": None,
    "commit_id": None,
    "author": None,
    "author_mail": None,
    "author_time": None,
    "author_timezone": None,
    "committer": None,
    "committer_mail": None,
    "committer_time": None,
    "committer_timezone": None,
    "comment": None,
    "filename": None,
    "line_num": None,
    "line": None,
}


def block_generator(filename):
    """Generates a set of "data blocks" using `git blame` from a file.

    Each block corresponds to the information about a single line of code as
    reported by `git blame --line-porcelain -- $FILE`.

    The porcelain output from `git` guarantees that the content line for a
    block of data starts with a '\\t' character. Lines are buffered by the
    generator until one starting with a '\\t' is found, at which point the
    buffer is yielded and cleared.

    Args:
        filename (str): The name of the file to run `git blame` on.

    Yields:
        list: A list of the lines in the porcelain output corresponding to a
            single line in the source file.
    """
    # Get the text from blame
    command = [
        'git',
        'blame',
        '--line-porcelain',
        '--',
        filename,
    ]

    blame = check_output(command)

    # Buffer the lines from the output until we find one that starts with \t,
    # indicating
    buffer = []
    for line in blame.splitlines():
        buffer.append(line)
        if line.startswith('\t'):
            yield buffer
            buffer = []


def block_to_JSON(block, repo_name=None):
    """Take a data block and generate a JSON objects containing the
    same information.

    Args:
        block (list): A data block as generated by block_generator()
        repo_name (str, optional): The name of the repository.
            Defaults to None.

    Returns:
        str: A JSON object as a string (encoded using json.dumps()).
    """
    current_json = deepcopy(JSON_LINE)

    # Function to strip < and > from around email addresses
    def clean_email(email): return email.strip("<>")

    # Function to convert timezone to hour office integer
    def tz_int(tz): return int(tz, 10)

    # Translation from the porcelain key to the key in our JSON object, as well
    # as an option transformation to apply first
    porcelain_to_json = {
        "author": ("author", None),
        "author-mail": ("author_mail", clean_email),
        "author-time": ("author_time", int),
        "author-tz": ("author_timezone", tz_int),
        "committer": ("committer", None),
        "committer-mail": ("committer_mail", clean_email),
        "committer-time": ("committer_time", int),
        "committer-tz": ("committer_timezone", tz_int),
        "summary": ("comment", None),
        "filename": ("filename", None),
    }

    # Set the name of the repo
    current_json["repo_name"] = repo_name

    # The header is of the form:
    #
    # hash original_line_number final_line_number group_end_line_number
    #
    # where group_end_line_number is optional and unused by us (it is redundant
    # when using `--line-porcelain`)
    header = block[0].split()
    current_json["commit_id"] = header[0]
    current_json["line_num"] = int(header[2])

    # The remaining body, except the final line, are not guaranteed to exist,
    # but are identified by the first word on the line
    for line in block[1:-1]:
        sline = line.split()
        key = sline[0]
        content = ' '.join(sline[1:])

        try:
            (json_key, transform) = porcelain_to_json[key]
        except KeyError:
            # The spec allows additional lines to be added to the body of the
            # porcelain block, in which case the key may not exist in our
            # mapping. In this case, the best option is to skip the line and
            # continue.
            continue
        if transform is not None:
            content = transform(content)
        current_json[json_key] = content

    # The final line is the raw text of line's content, with an extra '\\t'
    # prepended
    current_json["line"] = block[-1][1:]  # Remove the leading tab

    return json.dumps(current_json)


def file_to_json(filename, repo_name=None):
    """Returns a list of JSON objects containing the blame information
    associated with each line of the input file.


    Args:
        filename (str): The name of the file to run `git blame` on.
        repo_name (str, optional): The name of the repository.
            Defaults to None.

    Returns:
        list: A list containing strings of the JSON objects.

    """
    lines = []
    for block in block_generator(filename):
        lines.append(block_to_JSON(block))

    return lines

@@ -5970,17 +5970,16 @@
  file.%0A%0A
-%0A
     Args
@@ -6324,16 +6324,27 @@
 ON(block
+, repo_name
 ))%0A%0A    

Fix bug where repo_name was always None