<commit_before>#!/usr/bin/python

import argparse
from collections import defaultdict
import datetime
import json
import subprocess
import sys

import numpy as np
import matplotlib.pyplot as plt
import pandas
from pystackalytics import Stackalytics

LOC_PERCENTILE = 75

def exec_cmd(command):
    process = subprocess.Popen(
        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    output, error = process.communicate()

    return output, error

parser = argparse.ArgumentParser(
    description='Generate graphs depicting how long it took patches to get '
                'merged over time for a given project or a subset of its '
                'contributors as a function of time, lines of code, number '
                'of reviews by the author and more.')
parser.add_argument(
    'project',
    help='The OpenStack project to query. For example openstack/neutron.')
parser.add_argument(
    'owner',
    nargs='*',
    help='A list of zero or more Gerrit usernames. For example foo bar.')
args = parser.parse_args()

def get_json_data_from_query(query):
    print(query)
    data = []
    start = 0

    while True:
        gerrit_cmd = (
            'ssh -p 29418 review.openstack.org gerrit query --current-patch-set --start %(start)s %(query)s --format=json' %
            {'start': start,
             'query': query})
        result, error = exec_cmd(gerrit_cmd)

        if error:
            print(error)
            sys.exit(1)

        lines = result.split('\n')[:-2]
        data += [json.loads(line) for line in lines]

        if not data:
            print('No patches found!')
            sys.exit(1)

        print('Found metadata for %s more patches, %s total so far' % (len(lines), len(data)))
        start += len(lines)
        more_changes = json.loads(result.split('\n')[-2])['moreChanges']
        if not more_changes:
            break

    data = sorted(data, key=lambda x: x['createdOn'])
    return data

def get_submission_timestamp(patch):
    try:
        approvals = patch['currentPatchSet']['approvals']  # Not all patches have approvals data
    except KeyError:
        return patch['lastUpdated']

    # Weirdly enough some patches don't have submission data. Take lastUpdated instead.
    return next(
        (approval['grantedOn'] for approval in approvals if approval['type'] == 'SUBM'), patch['lastUpdated'])

def get_loc(patch):
    return max(0, patch['currentPatchSet']['sizeInsertions'] + patch['currentPatchSet']['sizeDeletions'])

def get_color(loc, max_loc):
    """Calculate a color between green and red.
    :param loc: How many lines of code?
    :param max_loc: The value of lines of code over which we return full red
    :return: (r, g, b) tuple
    """

    loc = min(loc, max_loc)  # Patches may have more LOC than the max we calculated, for example 75th percentile.
    return (loc / max_loc, 1.0 - (loc / max_loc), 0)

def get_average_loc(lines_of_code):
    return np.percentile(lines_of_code, LOC_PERCENTILE)

def get_points_from_data(data):
    def get_patch_author(patch):
        try:
            return patch['owner']['username']
        except KeyError:  # Not all patches on Gerrit have an owner username interestingly enough
            return

    points = []

    start = datetime.date.fromtimestamp(data[0]['createdOn'])
    average_loc = get_average_loc([get_loc(patch) for patch in data])
    print('Lines of code %s percentile: %s' % (LOC_PERCENTILE, average_loc))

    for patch in data:
        creation = datetime.date.fromtimestamp(patch['createdOn'])
        submitted = datetime.date.fromtimestamp(
            get_submission_timestamp(patch))
        x_value = (creation - start).days
        y_value = (submitted - creation).days

        # Gerrit has a weird issue where some old patches have a bogus
        # createdOn value
        author = get_patch_author(patch)
        if y_value > 0 and author is not None:
            points.append({
                'date': x_value,
                'days_to_merge': y_value,
                'loc': get_loc(patch),
                'author': author})

    return points

def get_list_of_owners(people):
    people_query = '\('
    for person in people:
        people_query += 'owner:%s OR ' % person
    return '%s\)' % people_query[:-4]

def moving_average(data, window):
    return pandas.Series(data).rolling(window=window).mean()

def get_current_figure():
    global CURRENT_FIGURE
    plt.figure(CURRENT_FIGURE)
    CURRENT_FIGURE += 1

def set_fullscreen():
    # http://stackoverflow.com/questions/12439588/how-to-maximize-a-plt-show-window-using-python

    mng = plt.get_current_fig_manager()
    try:
        mng.frame.Maximize(True)
    except AttributeError:
        try:
            mng.window.showMaximized()
        except AttributeError:
            try:
                mng.resize(*mng.window.maxsize())
            except AttributeError:
                pass

def get_figure_title(prefix):
    owners = ' '.join(args.owner) + ' - ' if args.owner else ''
    return prefix + ' - ' + (owners + args.project).replace('/', '_')

def filter_top_5_percent_days_to_merge(points):
    percentile = np.percentile([point['days_to_merge'] for point in points], 95)
    return [point for point in points if point['days_to_merge'] < percentile]

def calculate_time_to_merge_figure(points):
    get_current_figure()
    plt.gcf().canvas.set_window_title(get_figure_title('Time to merge'))

    points = filter_top_5_percent_days_to_merge(points)
    x = [point['date'] for point in points]
    y = [point['days_to_merge'] for point in points]

    print('Average days to merge patches: %s, median: %s' % (
          (int(round(np.average(y))), int(round(np.median(y))))))

    plt.xlabel('%s patches' % len(data))
    plt.ylabel('Days to merge patch')
    plt.grid(axis='y')

    window = min(len(x) / 10, 60)
    averages = moving_average(y, window)

    # Plot the patches
    plt.plot(x, averages)

    average_loc = get_average_loc([get_loc(patch) for patch in data])
    colors = [get_color(point['loc'], average_loc) for point in points]

    def to_grey(r, g, b):
        return 0.21 * r + 0.72 * g + 0.07 * b

    size = [(1.0 - (to_grey(r, g, b))) * 70 for (r, g, b) in colors]
    plt.scatter(x, y, s=size, c=colors, alpha=0.75)

    x_axis = range(0, x[-1], max(1, x[-1] / 10))  # 0 to last point, 10 hops

    # Generate a date from each hop relative to the date the first patch was
    # contributed
    start = datetime.date.fromtimestamp(data[0]['createdOn'])
    x_axis_dates = [
        str(start + datetime.timedelta(days=day_delta)) for day_delta in x_axis]
    plt.xticks(x_axis, x_axis_dates, rotation=45)

    plt.xlim(xmin=-5)
    plt.ylim(ymin=-5)
    plt.legend(['Moving mean of the last %s patches' % window, 'Lines of code, small & green to large & red'])
    plt.gcf().subplots_adjust(bottom=0.15)

    set_fullscreen()

def calculate_loc_correlation(points):
    get_current_figure()
    plt.gcf().canvas.set_window_title(get_figure_title('Lines of code'))

    percentile_time = np.percentile([point['days_to_merge'] for point in points], 95)
    percentile_loc = np.percentile([point['loc'] for point in points], 95)
    points = [point for point in points if point['days_to_merge'] < percentile_time and point['loc'] < percentile_loc]

    x = [point['loc'] for point in points]
    y = [point['days_to_merge'] for point in points]

    plt.xlabel('Lines of code')
    plt.ylabel('Days to merge patch')

    plt.scatter(x, y, s=70, alpha=0.75)

    plt.xlim(xmin=-5)
    plt.ylim(ymin=-5)

    set_fullscreen()

def get_days_to_merge_by_author(points):
    authors = defaultdict(list)  # A map from author to how many days it took to merge each of his/her patches
    for point in points:
        authors[point['author']].append(point['days_to_merge'])
    return authors

def calculate_author_patches_time_to_merge(points):
    get_current_figure()
    plt.gcf().canvas.set_window_title(get_figure_title('Time to merge per author by commits'))

    points = filter_top_5_percent_days_to_merge(points)
    authors = get_days_to_merge_by_author(points)

    x = []
    y = []
    for author, patches in authors.items():
        x.append(len(patches))  # How many patches
        y.append(np.average(patches))  # The average of how long it took to merge the patches

    plt.xlabel('Patches by author')
    plt.ylabel('Average days to merge patch per author')
    plt.scatter(x, y, s=70, alpha=0.75)

    plt.xlim(-5, max(x) + 5)
    plt.ylim(-5, max(y) + 5)

    set_fullscreen()

def calculate_author_time_to_merge_histogram(points):
    get_current_figure()
    plt.gcf().canvas.set_window_title(get_figure_title('Time to merge per author distribution'))

    points = filter_top_5_percent_days_to_merge(points)
    authors = get_days_to_merge_by_author(points)

    x = []
    for author, patches in authors.items():
        if len(patches) >= 10:
            x.append(np.average(patches))  # The average of how long it took to merge the patches

    n, bins, patches = plt.hist(x, alpha=0.5)

    plt.xticks(bins)
    plt.xlabel('Days to merge patches')
    plt.ylabel('Amount of authors with 10 or more patches')

    set_fullscreen()

def calculate_author_reviews_time_to_merge(points):
    _calculate_author_time_to_merge_by_metric(points, 'marks')

def calculate_author_emails_time_to_merge(points):
    _calculate_author_time_to_merge_by_metric(points, 'emails')

def calculate_author_filed_bugs_time_to_merge(points):
    _calculate_author_time_to_merge_by_metric(points, 'filed-bugs')

def calculate_author_resolved_bugs_time_to_merge(points):
    _calculate_author_time_to_merge_by_metric(points, 'resolved-bugs')

def calculate_author_drafted_blueprints_time_to_merge(points):
    _calculate_author_time_to_merge_by_metric(points, 'bpd')

def calculate_author_implemented_blueprints_time_to_merge(points):
    _calculate_author_time_to_merge_by_metric(points, 'bpc')

def _calculate_author_time_to_merge_by_metric(points, metric):
    get_current_figure()

    METRIC_LABELS = {
        'marks': 'Reviews',
        'emails': 'Emails',
        'bpd': 'Drafted Blueprints',
        'bpc': 'Completed Blueprints',
        'filed-bugs': 'Filed Bugs',
        'resolved-bugs': 'Resolved Bugs'}
    plt.gcf().canvas.set_window_title(get_figure_title('Time to merge per author by %s') % METRIC_LABELS[metric])

    points = filter_top_5_percent_days_to_merge(points)
    authors = get_days_to_merge_by_author(points)

    stackalytics = Stackalytics()
    module = args.project.split('/')[-1]

    if metric == 'emails':
        module = None  # When retrieving via emails it looks like Stackalytics tries to find only emails with [module] in the title.
    s_result = stackalytics.engineers(module=module, release='all', metric=metric)['stats']

    if not s_result:
        print 'No result found from Stackalytics API for module %s and metric %s' % (module, metric)
        return

    s_result_by_author = {}
    for item in s_result:
        s_result_by_author[item['id']] = item['metric']

    x = []
    y = []
    for author, patches in authors.items():
        try:
            x.append(s_result_by_author[author])
        except KeyError:  # People don't always use the same Gerrit and Stackalytics/Launchpad user_ids
            continue
        y.append(np.average(patches))  # The average of how long it took to merge the patches

    plt.xlabel('%s by author' % METRIC_LABELS[metric])
    plt.ylabel('Average days to merge patch per author')

    plt.scatter(x, y, s=70, alpha=0.75)

    plt.xlim(-5, max(x) + 5)
    plt.ylim(-5, max(y) + 5)

    set_fullscreen()

query = "status:merged branch:master project:%s " % args.project
if args.owner:
    query += get_list_of_owners(args.owner)
data = get_json_data_from_query(query)
points = get_points_from_data(data)

if not points:
    print('Could not parse points from data. It is likely that the createdOn timestamp of the patches found is bogus.')
    sys.exit(1)

plt.style.use('fivethirtyeight')

CURRENT_FIGURE = 1

calculate_time_to_merge_figure(points)
calculate_loc_correlation(points)
calculate_author_patches_time_to_merge(points)
calculate_author_reviews_time_to_merge(points)
calculate_author_emails_time_to_merge(points)
calculate_author_filed_bugs_time_to_merge(points)
calculate_author_resolved_bugs_time_to_merge(points)
calculate_author_drafted_blueprints_time_to_merge(points)
calculate_author_implemented_blueprints_time_to_merge(points)
calculate_author_time_to_merge_histogram(points)
plt.show()
<commit_msg>Fix crash if no results from Stackalytics<commit_after>#!/usr/bin/python

import argparse
from collections import defaultdict
import datetime
import json
import subprocess
import sys

import numpy as np
import matplotlib.pyplot as plt
import pandas
from pystackalytics import Stackalytics

LOC_PERCENTILE = 75

def exec_cmd(command):
    process = subprocess.Popen(
        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    output, error = process.communicate()

    return output, error

parser = argparse.ArgumentParser(
    description='Generate graphs depicting how long it took patches to get '
                'merged over time for a given project or a subset of its '
                'contributors as a function of time, lines of code, number '
                'of reviews by the author and more.')
parser.add_argument(
    'project',
    help='The OpenStack project to query. For example openstack/neutron.')
parser.add_argument(
    'owner',
    nargs='*',
    help='A list of zero or more Gerrit usernames. For example foo bar.')
args = parser.parse_args()

def get_json_data_from_query(query):
    print(query)
    data = []
    start = 0

    while True:
        gerrit_cmd = (
            'ssh -p 29418 review.openstack.org gerrit query --current-patch-set --start %(start)s %(query)s --format=json' %
            {'start': start,
             'query': query})
        result, error = exec_cmd(gerrit_cmd)

        if error:
            print(error)
            sys.exit(1)

        lines = result.split('\n')[:-2]
        data += [json.loads(line) for line in lines]

        if not data:
            print('No patches found!')
            sys.exit(1)

        print('Found metadata for %s more patches, %s total so far' % (len(lines), len(data)))
        start += len(lines)
        more_changes = json.loads(result.split('\n')[-2])['moreChanges']
        if not more_changes:
            break

    data = sorted(data, key=lambda x: x['createdOn'])
    return data

def get_submission_timestamp(patch):
    try:
        approvals = patch['currentPatchSet']['approvals']  # Not all patches have approvals data
    except KeyError:
        return patch['lastUpdated']

    # Weirdly enough some patches don't have submission data. Take lastUpdated instead.
    return next(
        (approval['grantedOn'] for approval in approvals if approval['type'] == 'SUBM'), patch['lastUpdated'])

def get_loc(patch):
    return max(0, patch['currentPatchSet']['sizeInsertions'] + patch['currentPatchSet']['sizeDeletions'])

def get_color(loc, max_loc):
    """Calculate a color between green and red.
    :param loc: How many lines of code?
    :param max_loc: The value of lines of code over which we return full red
    :return: (r, g, b) tuple
    """

    loc = min(loc, max_loc)  # Patches may have more LOC than the max we calculated, for example 75th percentile.
    return (loc / max_loc, 1.0 - (loc / max_loc), 0)

def get_average_loc(lines_of_code):
    return np.percentile(lines_of_code, LOC_PERCENTILE)

def get_points_from_data(data):
    def get_patch_author(patch):
        try:
            return patch['owner']['username']
        except KeyError:  # Not all patches on Gerrit have an owner username interestingly enough
            return

    points = []

    start = datetime.date.fromtimestamp(data[0]['createdOn'])
    average_loc = get_average_loc([get_loc(patch) for patch in data])
    print('Lines of code %s percentile: %s' % (LOC_PERCENTILE, average_loc))

    for patch in data:
        creation = datetime.date.fromtimestamp(patch['createdOn'])
        submitted = datetime.date.fromtimestamp(
            get_submission_timestamp(patch))
        x_value = (creation - start).days
        y_value = (submitted - creation).days

        # Gerrit has a weird issue where some old patches have a bogus
        # createdOn value
        author = get_patch_author(patch)
        if y_value > 0 and author is not None:
            points.append({
                'date': x_value,
                'days_to_merge': y_value,
                'loc': get_loc(patch),
                'author': author})

    return points

def get_list_of_owners(people):
    people_query = '\('
    for person in people:
        people_query += 'owner:%s OR ' % person
    return '%s\)' % people_query[:-4]

def moving_average(data, window):
    return pandas.Series(data).rolling(window=window).mean()

def get_current_figure():
    global CURRENT_FIGURE
    plt.figure(CURRENT_FIGURE)
    CURRENT_FIGURE += 1

def set_fullscreen():
    # http://stackoverflow.com/questions/12439588/how-to-maximize-a-plt-show-window-using-python

    mng = plt.get_current_fig_manager()
    try:
        mng.frame.Maximize(True)
    except AttributeError:
        try:
            mng.window.showMaximized()
        except AttributeError:
            try:
                mng.resize(*mng.window.maxsize())
            except AttributeError:
                pass

def get_figure_title(prefix):
    owners = ' '.join(args.owner) + ' - ' if args.owner else ''
    return prefix + ' - ' + (owners + args.project).replace('/', '_')

def filter_top_5_percent_days_to_merge(points):
    percentile = np.percentile([point['days_to_merge'] for point in points], 95)
    return [point for point in points if point['days_to_merge'] < percentile]

def calculate_time_to_merge_figure(points):
    get_current_figure()
    plt.gcf().canvas.set_window_title(get_figure_title('Time to merge'))

    points = filter_top_5_percent_days_to_merge(points)
    x = [point['date'] for point in points]
    y = [point['days_to_merge'] for point in points]

    print('Average days to merge patches: %s, median: %s' % (
          (int(round(np.average(y))), int(round(np.median(y))))))

    plt.xlabel('%s patches' % len(data))
    plt.ylabel('Days to merge patch')
    plt.grid(axis='y')

    window = min(len(x) / 10, 60)
    averages = moving_average(y, window)

    # Plot the patches
    plt.plot(x, averages)

    average_loc = get_average_loc([get_loc(patch) for patch in data])
    colors = [get_color(point['loc'], average_loc) for point in points]

    def to_grey(r, g, b):
        return 0.21 * r + 0.72 * g + 0.07 * b

    size = [(1.0 - (to_grey(r, g, b))) * 70 for (r, g, b) in colors]
    plt.scatter(x, y, s=size, c=colors, alpha=0.75)

    x_axis = range(0, x[-1], max(1, x[-1] / 10))  # 0 to last point, 10 hops

    # Generate a date from each hop relative to the date the first patch was
    # contributed
    start = datetime.date.fromtimestamp(data[0]['createdOn'])
    x_axis_dates = [
        str(start + datetime.timedelta(days=day_delta)) for day_delta in x_axis]
    plt.xticks(x_axis, x_axis_dates, rotation=45)

    plt.xlim(xmin=-5)
    plt.ylim(ymin=-5)
    plt.legend(['Moving mean of the last %s patches' % window, 'Lines of code, small & green to large & red'])
    plt.gcf().subplots_adjust(bottom=0.15)

    set_fullscreen()

def calculate_loc_correlation(points):
    get_current_figure()
    plt.gcf().canvas.set_window_title(get_figure_title('Lines of code'))

    percentile_time = np.percentile([point['days_to_merge'] for point in points], 95)
    percentile_loc = np.percentile([point['loc'] for point in points], 95)
    points = [point for point in points if point['days_to_merge'] < percentile_time and point['loc'] < percentile_loc]

    x = [point['loc'] for point in points]
    y = [point['days_to_merge'] for point in points]

    plt.xlabel('Lines of code')
    plt.ylabel('Days to merge patch')

    plt.scatter(x, y, s=70, alpha=0.75)

    plt.xlim(xmin=-5)
    plt.ylim(ymin=-5)

    set_fullscreen()

def get_days_to_merge_by_author(points):
    authors = defaultdict(list)  # A map from author to how many days it took to merge each of his/her patches
    for point in points:
        authors[point['author']].append(point['days_to_merge'])
    return authors

def calculate_author_patches_time_to_merge(points):
    get_current_figure()
    plt.gcf().canvas.set_window_title(get_figure_title('Time to merge per author by commits'))

    points = filter_top_5_percent_days_to_merge(points)
    authors = get_days_to_merge_by_author(points)

    x = []
    y = []
    for author, patches in authors.items():
        x.append(len(patches))  # How many patches
        y.append(np.average(patches))  # The average of how long it took to merge the patches

    plt.xlabel('Patches by author')
    plt.ylabel('Average days to merge patch per author')
    plt.scatter(x, y, s=70, alpha=0.75)

    plt.xlim(-5, max(x) + 5)
    plt.ylim(-5, max(y) + 5)

    set_fullscreen()

def calculate_author_time_to_merge_histogram(points):
    get_current_figure()
    plt.gcf().canvas.set_window_title(get_figure_title('Time to merge per author distribution'))

    points = filter_top_5_percent_days_to_merge(points)
    authors = get_days_to_merge_by_author(points)

    x = []
    for author, patches in authors.items():
        if len(patches) >= 10:
            x.append(np.average(patches))  # The average of how long it took to merge the patches

    n, bins, patches = plt.hist(x, alpha=0.5)

    plt.xticks(bins)
    plt.xlabel('Days to merge patches')
    plt.ylabel('Amount of authors with 10 or more patches')

    set_fullscreen()

def calculate_author_reviews_time_to_merge(points):
    _calculate_author_time_to_merge_by_metric(points, 'marks')

def calculate_author_emails_time_to_merge(points):
    _calculate_author_time_to_merge_by_metric(points, 'emails')

def calculate_author_filed_bugs_time_to_merge(points):
    _calculate_author_time_to_merge_by_metric(points, 'filed-bugs')

def calculate_author_resolved_bugs_time_to_merge(points):
    _calculate_author_time_to_merge_by_metric(points, 'resolved-bugs')

def calculate_author_drafted_blueprints_time_to_merge(points):
    _calculate_author_time_to_merge_by_metric(points, 'bpd')

def calculate_author_implemented_blueprints_time_to_merge(points):
    _calculate_author_time_to_merge_by_metric(points, 'bpc')

def _calculate_author_time_to_merge_by_metric(points, metric):
    get_current_figure()

    METRIC_LABELS = {
        'marks': 'Reviews',
        'emails': 'Emails',
        'bpd': 'Drafted Blueprints',
        'bpc': 'Completed Blueprints',
        'filed-bugs': 'Filed Bugs',
        'resolved-bugs': 'Resolved Bugs'}
    plt.gcf().canvas.set_window_title(get_figure_title('Time to merge per author by %s') % METRIC_LABELS[metric])

    points = filter_top_5_percent_days_to_merge(points)
    authors = get_days_to_merge_by_author(points)

    stackalytics = Stackalytics()
    module = args.project.split('/')[-1]

    if metric == 'emails':
        module = None  # When retrieving via emails it looks like Stackalytics tries to find only emails with [module] in the title.
    s_result = stackalytics.engineers(module=module, release='all', metric=metric)['stats']

    if not s_result:
        print 'No result found from Stackalytics API for module %s and metric %s' % (module, metric)
        return

    s_result_by_author = {}
    for item in s_result:
        s_result_by_author[item['id']] = item['metric']

    x = []
    y = []
    for author, patches in authors.items():
        try:
            x.append(s_result_by_author[author])
        except KeyError:  # People don't always use the same Gerrit and Stackalytics/Launchpad user_ids
            continue
        y.append(np.average(patches))  # The average of how long it took to merge the patches

    if not x:
        print('Could not find results for %s by %s' % (authors.keys(), metric))
        return

    plt.xlabel('%s by author' % METRIC_LABELS[metric])
    plt.ylabel('Average days to merge patch per author')

    plt.scatter(x, y, s=70, alpha=0.75)

    plt.xlim(-5, max(x) + 5)
    plt.ylim(-5, max(y) + 5)

    set_fullscreen()

query = "status:merged branch:master project:%s " % args.project
if args.owner:
    query += get_list_of_owners(args.owner)
data = get_json_data_from_query(query)
points = get_points_from_data(data)

if not points:
    print('Could not parse points from data. It is likely that the createdOn timestamp of the patches found is bogus.')
    sys.exit(1)

plt.style.use('fivethirtyeight')

CURRENT_FIGURE = 1

calculate_time_to_merge_figure(points)
calculate_loc_correlation(points)
calculate_author_patches_time_to_merge(points)
calculate_author_reviews_time_to_merge(points)
calculate_author_emails_time_to_merge(points)
calculate_author_filed_bugs_time_to_merge(points)
calculate_author_resolved_bugs_time_to_merge(points)
calculate_author_drafted_blueprints_time_to_merge(points)
calculate_author_implemented_blueprints_time_to_merge(points)
calculate_author_time_to_merge_histogram(points)
plt.show()
<|endoftext|>


<commit_before>#!/usr/bin/python

import argparse
import datetime
import json
import subprocess
import sys

import numpy as np
import matplotlib.pyplot as plt

def exec_cmd(command):
    process = subprocess.Popen(
        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    output, error = process.communicate()

    return output, error

parser = argparse.ArgumentParser(
    description='Generate a graph depicting how long it took patches to get '
                'merged over time for a given project or a subset of its '
                'contributors.')
parser.add_argument(
    'project',
    help='The OpenStack project to query. For example openstack/neutron.')
parser.add_argument(
    'owner',
    nargs='*',
    help='A list of zero or more Gerrit usernames. For example foo bar.')
parser.add_argument(
    '--last',
    help='Only patches that were merged in the last, for example, 50d or 1y.')
args = parser.parse_args()

def get_json_data_from_query(query):
    print query
    data = []
    start = 0

    while True:
        gerrit_cmd = (
            'ssh -p 29418 review.openstack.org gerrit query --current-patch-set --start %(start)s %(query)s --format=json' %
            {'start': start,
             'query': query})
        if args.last:
            gerrit_cmd += ' -- -age:%s' % args.last
        result, error = exec_cmd(gerrit_cmd)

        if error:
            print error
            sys.exit(1)

        lines = result.split('\n')[:-2]
        data += [json.loads(line) for line in lines]

        if not data:
            print 'No patches found!'
            sys.exit(1)

        print 'Found metadata for %s more patches, %s total so far' % (len(lines), len(data))
        start += len(lines)
        more_changes = json.loads(result.split('\n')[-2])['moreChanges']
        if not more_changes:
            break

    data = sorted(data, key=lambda x: x['createdOn'])
    return data

def get_submission_timestamp(patch):
    approvals = patch['currentPatchSet']['approvals']
    return next(
        (approval['grantedOn'] for approval in approvals if approval['type'] == 'SUBM'))

def get_loc(patch):
    return patch['currentPatchSet']['sizeInsertions']

def get_color(loc, max_loc):
    max_loc = float(max_loc)
    loc = min(max_loc, loc)
    return (loc / max_loc,  1.0 - (loc / max_loc), 0)

def get_points_from_data(data):
    points = []

    max_loc = 0
    for patch in data:
        loc = get_loc(patch)
        if loc > max_loc:
            max_loc = loc
    max_loc = 0.666 * max_loc

    for patch in data:
        creation = datetime.date.fromtimestamp(patch['createdOn'])
        submitted = datetime.date.fromtimestamp(
            get_submission_timestamp(patch))
        x_value = (creation - start).days
        y_value = (submitted - creation).days
        # Gerrit has a weird issue where some old patches have a bogus
        # createdOn value
        if y_value > 0:
            points.append((x_value, y_value, get_color(get_loc(patch), max_loc)))

    return points

def filter_above_percentile(points, percentile):
    percentile = np.percentile([point[1] for point in points], percentile)
    return [point for point in points if point[1] < percentile]

def get_list_of_owners(people):
    people_query = '\('
    for person in people:
        people_query += 'owner:%s OR ' % person
    return '%s\)' % people_query[:-4]

def moving_average(x, n):
    """
    compute an n period moving average.
    Stolen from:
    http://matplotlib.org/examples/pylab_examples/finance_work2.html
    """
    x = np.asarray(x)
    weights = np.ones(n)
    weights /= weights.sum()

    a = np.convolve(x, weights, mode='full')[:len(x)]
    a[:n] = a[n]
    return a

query = "status:merged branch:master project:%s " % args.project
if args.owner:
    query += get_list_of_owners(args.owner)
data = get_json_data_from_query(query)

start = datetime.date.fromtimestamp(data[0]['createdOn'])

points = get_points_from_data(data)

if not points:
    print 'Could not parse points from data. It is likely that the createdOn timestamp of the patches found is bogus.'
    sys.exit(1)

points = filter_above_percentile(points, 95)

x = [point[0] for point in points]
y = [point[1] for point in points]

print 'Average: %s, median: %s' % (
    (int(round(np.average(y))), int(round(np.median(y)))))

plt.xlabel('%s - %s - %s patches' %
           (' '.join(args.owner), args.project, len(data)))
plt.ylabel('Days to merge patch')
plt.grid()

# Generate a linear regression line
regression_line = np.polyfit(x, y, 1)
regression_line_function = np.poly1d(regression_line)

averages = moving_average(y, len(x) / 10)

# Plot the data points as well as the regression line
plt.plot(x, averages)

plt.scatter(x, y, c=[point[2] for point in points])

x_axis = range(0, x[-1], max(1, x[-1] / 10))  # 0 to last point, 10 hops

# Generate a date from each hop relative to the date the first patch was
# contributed
x_axis_dates = [
    str(start + datetime.timedelta(days=day_delta)) for day_delta in x_axis]
plt.xticks(x_axis, x_axis_dates, rotation=45)

plt.xlim(xmin=0)
plt.ylim(ymin=0)
plt.show()
<commit_msg>Improve color code formula<commit_after>#!/usr/bin/python

import argparse
import datetime
import json
import subprocess
import sys

import numpy as np
import matplotlib.pyplot as plt

def exec_cmd(command):
    process = subprocess.Popen(
        command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    output, error = process.communicate()

    return output, error

parser = argparse.ArgumentParser(
    description='Generate a graph depicting how long it took patches to get '
                'merged over time for a given project or a subset of its '
                'contributors.')
parser.add_argument(
    'project',
    help='The OpenStack project to query. For example openstack/neutron.')
parser.add_argument(
    'owner',
    nargs='*',
    help='A list of zero or more Gerrit usernames. For example foo bar.')
parser.add_argument(
    '--last',
    help='Only patches that were merged in the last, for example, 50d or 1y.')
args = parser.parse_args()

def get_json_data_from_query(query):
    print query
    data = []
    start = 0

    while True:
        gerrit_cmd = (
            'ssh -p 29418 review.openstack.org gerrit query --current-patch-set --start %(start)s %(query)s --format=json' %
            {'start': start,
             'query': query})
        if args.last:
            gerrit_cmd += ' -- -age:%s' % args.last
        result, error = exec_cmd(gerrit_cmd)

        if error:
            print error
            sys.exit(1)

        lines = result.split('\n')[:-2]
        data += [json.loads(line) for line in lines]

        if not data:
            print 'No patches found!'
            sys.exit(1)

        print 'Found metadata for %s more patches, %s total so far' % (len(lines), len(data))
        start += len(lines)
        more_changes = json.loads(result.split('\n')[-2])['moreChanges']
        if not more_changes:
            break

    data = sorted(data, key=lambda x: x['createdOn'])
    return data

def get_submission_timestamp(patch):
    approvals = patch['currentPatchSet']['approvals']
    return next(
        (approval['grantedOn'] for approval in approvals if approval['type'] == 'SUBM'))

def get_loc(patch):
    return patch['currentPatchSet']['sizeInsertions']

def get_color(loc, max_loc):
    """Calculate a color between green and red.
    :param loc: How many lines of code?
    :param max_loc: The value of lines of code over which we return full red
    :return: (r, g, b) tuple
    """
    loc = min(loc, max_loc)  # Patches may have more LOC than the max we calculated, for example 75th percentile.
    return (loc / max_loc,  1.0 - (loc / max_loc), 0)

def get_points_from_data(data):
    points = []

    average_loc = np.percentile([get_loc(patch) for patch in data], 75)

    for patch in data:
        creation = datetime.date.fromtimestamp(patch['createdOn'])
        submitted = datetime.date.fromtimestamp(
            get_submission_timestamp(patch))
        x_value = (creation - start).days
        y_value = (submitted - creation).days
        # Gerrit has a weird issue where some old patches have a bogus
        # createdOn value
        if y_value > 0:
            points.append((x_value, y_value, get_color(get_loc(patch), average_loc)))

    return points

def filter_above_percentile(points, percentile):
    percentile = np.percentile([point[1] for point in points], percentile)
    return [point for point in points if point[1] < percentile]

def get_list_of_owners(people):
    people_query = '\('
    for person in people:
        people_query += 'owner:%s OR ' % person
    return '%s\)' % people_query[:-4]

def moving_average(x, n):
    """
    compute an n period moving average.
    Stolen from:
    http://matplotlib.org/examples/pylab_examples/finance_work2.html
    """
    x = np.asarray(x)
    weights = np.ones(n)
    weights /= weights.sum()

    a = np.convolve(x, weights, mode='full')[:len(x)]
    a[:n] = a[n]
    return a

query = "status:merged branch:master project:%s " % args.project
if args.owner:
    query += get_list_of_owners(args.owner)
data = get_json_data_from_query(query)

start = datetime.date.fromtimestamp(data[0]['createdOn'])

points = get_points_from_data(data)

if not points:
    print 'Could not parse points from data. It is likely that the createdOn timestamp of the patches found is bogus.'
    sys.exit(1)

points = filter_above_percentile(points, 95)

x = [point[0] for point in points]
y = [point[1] for point in points]

print 'Average: %s, median: %s' % (
    (int(round(np.average(y))), int(round(np.median(y)))))

plt.xlabel('%s - %s - %s patches' %
           (' '.join(args.owner), args.project, len(data)))
plt.ylabel('Days to merge patch')
plt.grid()

# Generate a linear regression line
regression_line = np.polyfit(x, y, 1)
regression_line_function = np.poly1d(regression_line)

averages = moving_average(y, len(x) / 10)

# Plot the data points as well as the regression line
plt.plot(x, averages)

plt.scatter(x, y, c=[point[2] for point in points])

x_axis = range(0, x[-1], max(1, x[-1] / 10))  # 0 to last point, 10 hops

# Generate a date from each hop relative to the date the first patch was
# contributed
x_axis_dates = [
    str(start + datetime.timedelta(days=day_delta)) for day_delta in x_axis]
plt.xticks(x_axis, x_axis_dates, rotation=45)

plt.xlim(xmin=0)
plt.ylim(ymin=0)
plt.show()
<|endoftext|>


<commit_before># coding: utf-8
# License: MIT, see LICENSE.txt
"""
django-tinymce4-lite views
"""

from __future__ import absolute_import
import json
import logging
from django import VERSION
from django.core.urlresolvers import reverse
from django.http import JsonResponse, HttpResponse
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.conf import settings
from django.views.decorators.cache import never_cache
from jsmin import jsmin
try:
    from enchant import checker, list_languages
except ImportError:
    pass

__all__ = ['spell_check', 'css', 'filebrowser']

logging.basicConfig(format='[%(asctime)s] %(module)s: %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

def spell_check(request):
    """
    Implements the TinyMCE 4 spellchecker protocol

    :param request: Django http request with JSON-RPC payload from TinyMCE 4
        containing a language code and a text to check for errors.
    :type request: django.http.request.HttpRequest
    :return: Django http response containing JSON-RPC payload
        with spellcheck results for TinyMCE 4
    :rtype: django.http.JsonResponse
    """
    data = json.loads(request.body.decode('utf-8'))
    output = {'id': data['id']}
    error = None
    status = 200
    try:
        if data['params']['lang'] not in list_languages():
            error = 'Missing {0} dictionary!'.format(data['params']['lang'])
            raise RuntimeError(error)
        spell_checker = checker.SpellChecker(data['params']['lang'])
        spell_checker.set_text(strip_tags(data['params']['text']))
        output['result'] = {spell_checker.word: spell_checker.suggest()
                            for err in spell_checker}
    except NameError:
        error = 'The pyenchant package is not installed!'
        logger.exception(error)
    except RuntimeError:
        logger.exception(error)
    except Exception:
        error = 'Unknown error!'
        logger.exception(error)
    if error is not None:
        output['error'] = error
        status = 500
    return JsonResponse(output, status=status)

@never_cache
def spell_check_callback(request):
    """
    JavaScript callback for TinyMCE4 spellchecker function

    :param request: Django http request
    :type request: django.http.request.HttpRequest
    :return: Django http response with spellchecker callback JavaScript code
    :rtype: django.http.HttpResponse
    """
    return HttpResponse(
        jsmin(render_to_string('tinymce/spellcheck-callback.js',
                               request=request)),
        content_type='application/javascript')

@never_cache
def css(request):
    """
    Custom CSS for TinyMCE 4 widget

    By default it fixes widget's position in Django Admin

    :param request: Django http request
    :type request: django.http.request.HttpRequest
    :return: Django http response with CSS file for TinyMCE 4
    :rtype: django.http.HttpResponse
    """
    if 'grappelli' in settings.INSTALLED_APPS:
        margin_left = 0
    elif VERSION[0] == 1 and VERSION[1] <= 8:
        margin_left = 110  # For old style admin
    else:
        margin_left = 170  # For Django >= 1.9 style admin
    return HttpResponse(render_to_string('tinymce/tinymce4.css',
                                         context={'margin_left': margin_left},
                                         request=request),
                        content_type='text/css')

@never_cache
def filebrowser(request):
    """
    JavaScript callback function for `django-filebrowser`_

    :param request: Django http request
    :type request: django.http.request.HttpRequest
    :return: Django http response with filebrowser JavaScript code for for TinyMCE 4
    :rtype: django.http.HttpResponse

    .. _django-filebrowser: https://github.com/sehmaschine/django-filebrowser
    """
    try:
        fb_url = request.build_absolute_uri(reverse('fb_browse'))
    except:
        fb_url = request.build_absolute_uri(reverse('filebrowser:fb_browse'))
    return HttpResponse(jsmin(render_to_string('tinymce/filebrowser.js',
                                               context={'fb_url': fb_url},
                                               request=request)),
                        content_type='application/javascript')
<commit_msg>Add spell_check_callback to __all__<commit_after># coding: utf-8
# License: MIT, see LICENSE.txt
"""
django-tinymce4-lite views
"""

from __future__ import absolute_import
import json
import logging
from django import VERSION
from django.core.urlresolvers import reverse
from django.http import JsonResponse, HttpResponse
from django.template.loader import render_to_string
from django.utils.html import strip_tags
from django.conf import settings
from django.views.decorators.cache import never_cache
from jsmin import jsmin
try:
    from enchant import checker, list_languages
except ImportError:
    pass

__all__ = ['spell_check', 'spell_check_callback', 'css', 'filebrowser']

logging.basicConfig(format='[%(asctime)s] %(module)s: %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

def spell_check(request):
    """
    Implements the TinyMCE 4 spellchecker protocol

    :param request: Django http request with JSON-RPC payload from TinyMCE 4
        containing a language code and a text to check for errors.
    :type request: django.http.request.HttpRequest
    :return: Django http response containing JSON-RPC payload
        with spellcheck results for TinyMCE 4
    :rtype: django.http.JsonResponse
    """
    data = json.loads(request.body.decode('utf-8'))
    output = {'id': data['id']}
    error = None
    status = 200
    try:
        if data['params']['lang'] not in list_languages():
            error = 'Missing {0} dictionary!'.format(data['params']['lang'])
            raise RuntimeError(error)
        spell_checker = checker.SpellChecker(data['params']['lang'])
        spell_checker.set_text(strip_tags(data['params']['text']))
        output['result'] = {spell_checker.word: spell_checker.suggest()
                            for err in spell_checker}
    except NameError:
        error = 'The pyenchant package is not installed!'
        logger.exception(error)
    except RuntimeError:
        logger.exception(error)
    except Exception:
        error = 'Unknown error!'
        logger.exception(error)
    if error is not None:
        output['error'] = error
        status = 500
    return JsonResponse(output, status=status)

@never_cache
def spell_check_callback(request):
    """
    JavaScript callback for TinyMCE4 spellchecker function

    :param request: Django http request
    :type request: django.http.request.HttpRequest
    :return: Django http response with spellchecker callback JavaScript code
    :rtype: django.http.HttpResponse
    """
    return HttpResponse(
        jsmin(render_to_string('tinymce/spellcheck-callback.js',
                               request=request)),
        content_type='application/javascript')

@never_cache
def css(request):
    """
    Custom CSS for TinyMCE 4 widget

    By default it fixes widget's position in Django Admin

    :param request: Django http request
    :type request: django.http.request.HttpRequest
    :return: Django http response with CSS file for TinyMCE 4
    :rtype: django.http.HttpResponse
    """
    if 'grappelli' in settings.INSTALLED_APPS:
        margin_left = 0
    elif VERSION[0] == 1 and VERSION[1] <= 8:
        margin_left = 110  # For old style admin
    else:
        margin_left = 170  # For Django >= 1.9 style admin
    return HttpResponse(render_to_string('tinymce/tinymce4.css',
                                         context={'margin_left': margin_left},
                                         request=request),
                        content_type='text/css')

@never_cache
def filebrowser(request):
    """
    JavaScript callback function for `django-filebrowser`_

    :param request: Django http request
    :type request: django.http.request.HttpRequest
    :return: Django http response with filebrowser JavaScript code for for TinyMCE 4
    :rtype: django.http.HttpResponse

    .. _django-filebrowser: https://github.com/sehmaschine/django-filebrowser
    """
    try:
        fb_url = request.build_absolute_uri(reverse('fb_browse'))
    except:
        fb_url = request.build_absolute_uri(reverse('filebrowser:fb_browse'))
    return HttpResponse(jsmin(render_to_string('tinymce/filebrowser.js',
                                               context={'fb_url': fb_url},
                                               request=request)),
                        content_type='application/javascript')
<|endoftext|>


<commit_before>import unittest
import ast
import sys

# Include src in the Python search path.
sys.path.insert(0, '../src')

from typecheck import typecheck, parse_type_declarations, debug
from pyty_types import PytyMod
from pyty_errors import TypeUnspecifiedError, TypeIncorrectlySpecifiedError

"""
This is just the core of the unit testing file. generate_tests.py must be run
to fill this file with the several unit tests (each of which tests one source
code file in the test_files directory).

Test file format:

### True|False|ErrorName

<Python code>
"""

class TestFileFormatError(Exception):
    """Exception subclass for error encountered when a test file is not
    specified correctly.

    @type msg: C{str}.
    @ivar msg: message specifying what in test file isn't specified correctly.
    """

    def __init__(self, msg):
       self.msg = msg

    def __str__(self):
        return self.msg

class PytyTests(unittest.TestCase):

    def setUp(self):
        self.pyty_mod_obj = PytyMod()

    def _check_file(self, filename):
        debug("Checking %s" % filename)

        with open(filename, 'r') as f:

            expected_str = f.readline().strip('###').strip()

            if expected_str == "TypeIncorrectlySpecifiedError":
                self.assertRaises(TypeIncorrectlySpecifiedError,
                    parse_type_declarations, filename)
                return

            env = parse_type_declarations(filename)

            a = ast.parse(f.read())

            if expected_str == "True" or expected_str == "False":
                try:
                    # test if it's looking for a true or false value.
                    exp = ast.literal_eval(expected_str)

                    self.assertEqual(exp, typecheck(env, a, self.pyty_mod_obj))
                except TypeUnspecifiedError:
                    self.fail("A variable type was not specified, but the " +
                           "program is either supposed to typecheck or " +
                            "be checked with no errors.")

            elif expected_str == "TypeUnspecifiedError":
                # test if it's looking for a TypeUnspecifiedError. if
                # there end up being a lot of possible errors, might want to
                # generalize this, but if there are only going to be a couple,
                # might as well just include each case explicitly.
                self.assertRaises(TypeUnspecifiedError, typecheck,
                        env, a, self.pyty_mod_obj)

            else:
                raise TestFileFormatError("Expected test value or error " +
                        "specified improperly")

    ##### Generated unit tests will go below here
    def test_one_line_compliated6(self):
        self._check_file("test_files/one_line_compliated6.py")

    def test_one_line_assign5(self):
        self._check_file("test_files/one_line_assign5.py")

    def test_one_line_add7(self):
        self._check_file("test_files/one_line_add7.py")

    def test_one_line_assign3(self):
        self._check_file("test_files/one_line_assign3.py")

    def test_one_line_assign12(self):
        self._check_file("test_files/one_line_assign12.py")

    def test_one_line_mod6(self):
        self._check_file("test_files/one_line_mod6.py")

    def test_one_line_mod4(self):
        self._check_file("test_files/one_line_mod4.py")

    def test_one_line_div5(self):
        self._check_file("test_files/one_line_div5.py")

    def test_one_line_assign4(self):
        self._check_file("test_files/one_line_assign4.py")

    def test_one_line_div1(self):
        self._check_file("test_files/one_line_div1.py")

    def test_one_line7(self):
        self._check_file("test_files/one_line7.py")

    def test_one_line_add8(self):
        self._check_file("test_files/one_line_add8.py")

    def test_one_line_assign6(self):
        self._check_file("test_files/one_line_assign6.py")

    def test_one_line_mod10(self):
        self._check_file("test_files/one_line_mod10.py")

    def test_one_line_complicated1(self):
        self._check_file("test_files/one_line_complicated1.py")

    def test_one_line_div3(self):
        self._check_file("test_files/one_line_div3.py")

    def test_one_line_div6(self):
        self._check_file("test_files/one_line_div6.py")

    def test_if9(self):
        self._check_file("test_files/if9.py")

    def test_one_line_add5(self):
        self._check_file("test_files/one_line_add5.py")

    def test_one_line_div8(self):
        self._check_file("test_files/one_line_div8.py")

    def test_one_line_complicated10(self):
        self._check_file("test_files/one_line_complicated10.py")

    def test_if6(self):
        self._check_file("test_files/if6.py")

    def test_one_line_add3(self):
        self._check_file("test_files/one_line_add3.py")

    def test_one_line_complicated11(self):
        self._check_file("test_files/one_line_complicated11.py")

    def test_one_line_mult2(self):
        self._check_file("test_files/one_line_mult2.py")

    def test_one_line_assign10(self):
        self._check_file("test_files/one_line_assign10.py")

    def test_one_line3(self):
        self._check_file("test_files/one_line3.py")

    def test_if8(self):
        self._check_file("test_files/if8.py")

    def test_one_line_mult3(self):
        self._check_file("test_files/one_line_mult3.py")

    def test_one_line_assign2(self):
        self._check_file("test_files/one_line_assign2.py")

    def test_one_line_sub4(self):
        self._check_file("test_files/one_line_sub4.py")

    def test_one_line_div4(self):
        self._check_file("test_files/one_line_div4.py")

    def test_one_line_div2(self):
        self._check_file("test_files/one_line_div2.py")

    def test_one_line_sub7(self):
        self._check_file("test_files/one_line_sub7.py")

    def test_one_line_mult4(self):
        self._check_file("test_files/one_line_mult4.py")

    def test_one_line_complicated7(self):
        self._check_file("test_files/one_line_complicated7.py")

    def test_one_line_mult5(self):
        self._check_file("test_files/one_line_mult5.py")

    def test_one_line_complicated3(self):
        self._check_file("test_files/one_line_complicated3.py")

    def test_if5(self):
        self._check_file("test_files/if5.py")

    def test_one_line_sub1(self):
        self._check_file("test_files/one_line_sub1.py")

    def test_if3(self):
        self._check_file("test_files/if3.py")

    def test_if11(self):
        self._check_file("test_files/if11.py")

    def test_one_line_complicated8(self):
        self._check_file("test_files/one_line_complicated8.py")

    def test_one_line_sub6(self):
        self._check_file("test_files/one_line_sub6.py")

    def test_one_line_add2(self):
        self._check_file("test_files/one_line_add2.py")

    def test_one_line_mod7(self):
        self._check_file("test_files/one_line_mod7.py")

    def test_one_line_add1(self):
        self._check_file("test_files/one_line_add1.py")

    def test_if10(self):
        self._check_file("test_files/if10.py")

    def test_one_line_mod5(self):
        self._check_file("test_files/one_line_mod5.py")

    def test_if2(self):
        self._check_file("test_files/if2.py")

    def test_one_line_mod2(self):
        self._check_file("test_files/one_line_mod2.py")

    def test_one_line_assign7(self):
        self._check_file("test_files/one_line_assign7.py")

    def test_one_line_div9(self):
        self._check_file("test_files/one_line_div9.py")

    def test_one_line_mod9(self):
        self._check_file("test_files/one_line_mod9.py")

    def test_one_line_mult6(self):
        self._check_file("test_files/one_line_mult6.py")

    def test_one_line_assign1(self):
        self._check_file("test_files/one_line_assign1.py")

    def test_one_line_sub5(self):
        self._check_file("test_files/one_line_sub5.py")

    def test_one_line_complicated2(self):
        self._check_file("test_files/one_line_complicated2.py")

    def test_one_line_assign9(self):
        self._check_file("test_files/one_line_assign9.py")

    def test_if7(self):
        self._check_file("test_files/if7.py")

    def test_one_line_complicated12(self):
        self._check_file("test_files/one_line_complicated12.py")

    def test_one_line6(self):
        self._check_file("test_files/one_line6.py")

    def test_one_line_mod3(self):
        self._check_file("test_files/one_line_mod3.py")

    def test_one_line_div7(self):
        self._check_file("test_files/one_line_div7.py")

    def test_one_line_complicated13(self):
        self._check_file("test_files/one_line_complicated13.py")

    def test_one_line_complicated9(self):
        self._check_file("test_files/one_line_complicated9.py")

    def test_one_line_add6(self):
        self._check_file("test_files/one_line_add6.py")

    def test_one_line_mult1(self):
        self._check_file("test_files/one_line_mult1.py")

    def test_one_line_complicated4(self):
        self._check_file("test_files/one_line_complicated4.py")

    def test_one_line_assign11(self):
        self._check_file("test_files/one_line_assign11.py")

    def test_one_line4(self):
        self._check_file("test_files/one_line4.py")

    def test_one_line_sub2(self):
        self._check_file("test_files/one_line_sub2.py")

    def test_if1(self):
        self._check_file("test_files/if1.py")

    def test_one_line_add4(self):
        self._check_file("test_files/one_line_add4.py")

    def test_one_line5(self):
        self._check_file("test_files/one_line5.py")

    def test_one_line_mod1(self):
        self._check_file("test_files/one_line_mod1.py")

    def test_if4(self):
        self._check_file("test_files/if4.py")

    def test_one_line_sub3(self):
        self._check_file("test_files/one_line_sub3.py")

    def test_one_line_mod8(self):
        self._check_file("test_files/one_line_mod8.py")

    def test_one_line_assign8(self):
        self._check_file("test_files/one_line_assign8.py")

    def test_one_line_complicated5(self):
        self._check_file("test_files/one_line_complicated5.py")

    def test_one_line2(self):
        self._check_file("test_files/one_line2.py")

    def test_one_line_mult7(self):
        self._check_file("test_files/one_line_mult7.py")

    def test_one_line_complicated6(self):
        self._check_file("test_files/one_line_complicated6.py")

    def test_one_line_assign13(self):
        self._check_file("test_files/one_line_assign13.py")
    ##### Generated unit tests will go above here

if __name__ == '__main__':
    unittest.main()

<commit_msg>Deleted pyty_tests.py from repository, since it is now a generated file.<commit_after><|endoftext|>


<commit_before>import argparse
import glob
import itertools
import logging
import logging.config
import os
import random
import sys

import calculate
import constants as co
import numpy as np
import compare
import datatypes
import gradient
import parameters
import simplex

logger = logging.getLogger(__name__)

class Loop(object):
    def __init__(self):
        self.convergence = 0.01
        self.cycle_num = 0
        self.direc = '.'
        self.ff = None
        self.ff_lines = None
        self.args_ff = None
        self.args_ref = None
        self.loop_lines = None
        self.ref_data = None
    def opt_loop(self):
        change = None
        last_score = None
        while last_score is None \
                or change is None \
                or change > self.convergence:
            self.cycle_num += 1
            last_score = self.ff.score
            self.ff = self.run_loop_input(
                self.loop_lines, score=self.ff.score)
            change = (last_score - self.ff.score) / last_score
            pretty_loop_summary(
                self.cycle_num, self.ff.score, change)
            mm3_files = glob.glob(os.path.join(self.direc, 'mm3_???.fld'))
            if mm3_files:
                mm3_files.sort()
                most_recent_mm3_file = mm3_files[-1]
                most_recent_mm3_file = most_recent_mm3_file.split('/')[-1]
                most_recent_num = most_recent_mm3_file[4:7]
                num = int(most_recent_num) + 1
                mm3_file = 'mm3_{:03d}.fld'.format(num)
                self.ff.export_ff(path=mm3_file)
            else:
                mm3_file = 'mm3_001.fld'
            mm3_file = os.path.join(self.direc, mm3_file)
            self.ff.export_ff(path=mm3_file)
            logger.log(20, '  -- Wrote best FF to {}'.format(mm3_file))
        return self.ff
    def run_loop_input(self, lines, score=None):
        lines_iterator = iter(lines)
        while True:
            try:
                line = lines_iterator.next()
            except StopIteration:
                return self.ff
            cols = line.split()
            if cols[0] == 'DIR':
                self.direc = cols[1]
            if cols[0] == 'FFLD':
                # Import FF data.
                if cols[1] == 'read':
                    self.ff = datatypes.MM3(os.path.join(self.direc, cols[2]))
                    self.ff.import_ff()
                    self.ff.method = 'READ'
                    with open(os.path.join(self.direc, cols[2]), 'r') as f:
                        self.ff.lines = f.readlines()
                # Export FF data.
                if cols[1] == 'write':
                    self.ff.export_ff(os.path.join(self.direc, cols[2]))
            # Trim parameters.
            if cols[0] == 'PARM':
                logger.log(20, '~~ SELECTING PARAMETERS ~~'.rjust(79, '~'))
                self.ff.params = parameters.trim_params_by_file(
                    self.ff.params, os.path.join(self.direc, cols[1]))
            if cols[0] == 'LOOP':
                # Read lines that will be looped over.
                inner_loop_lines = []
                line = lines_iterator.next()
                while line.split()[0] != 'END':
                    inner_loop_lines.append(line)
                    line = lines_iterator.next()
                # Make loop object and populate attributes.
                loop = Loop()
                loop.convergence = float(cols[1])
                loop.direc = self.direc
                loop.ff = self.ff
                loop.args_ff = self.args_ff
                loop.args_ref = self.args_ref
                loop.ref_data = self.ref_data
                loop.loop_lines = inner_loop_lines
                # Log commands.
                pretty_loop_input(
                    inner_loop_lines, name='OPTIMIZATION LOOP',
                    score=self.ff.score)
                # Run inner loop.
                self.ff = loop.opt_loop()
            # Note: Probably want to update this to append the directory given
            #       by the new DIR command.
            if cols[0] == 'RDAT':
                logger.log(
                    20, '~~ CALCULATING REFERENCE DATA ~~'.rjust(79, '~'))
                if len(cols) > 1:
                    self.args_ref = ' '.join(cols[1:]).split()
                self.ref_data = calculate.main(self.args_ref)
                self.ref_data = np.array(sorted(self.ref_data, key=datatypes.datum_sort_key))
            if cols[0] == 'CDAT':
                logger.log(
                    20, '~~ CALCULATING FF DATA ~~'.rjust(79, '~'))
                if len(cols) > 1:
                    self.args_ff = ' '.join(cols[1:]).split()
                self.ff.data = calculate.main(self.args_ff)
                self.ff.data = np.array(sorted(self.ff.data, key=datatypes.datum_sort_key))
            if cols[0] == 'COMP':
                self.ff.score = compare.compare_data(
                    self.ref_data, self.ff.data)
                if '-o' in cols:
                    compare.pretty_data_comp(
                        self.ref_data,
                        self.ff.data,
                        os.path.join(self.direc, cols[cols.index('-o') + 1]))
            if cols[0] == 'GRAD':
                grad = gradient.Gradient(
                    direc=self.direc,
                    ff=self.ff,
                    ff_lines=self.ff.lines,
                    args_ff=self.args_ff)
                self.ff = grad.run(ref_data=self.ref_data)
            if cols[0] == 'SIMP':
                simp = simplex.Simplex(
                    direc=self.direc,
                    ff=self.ff,
                    ff_lines=self.ff.lines,
                    args_ff=self.args_ff)
                self.ff = simp.run(r_data=self.ref_data)

def read_loop_input(filename):
    with open(filename, 'r') as f:
        lines = f.readlines()
    lines = [x.partition('#')[0].strip('\n') for x in lines if
             x.partition('#')[0].strip('\n') != '']
    pretty_loop_input(lines)
    return lines

def pretty_loop_input(lines, name='Q2MM', score=None):
    logger.log(20, ' {} '.format(name).center(79, '='))
    logger.log(20, 'COMMANDS:')
    for line in lines:
        logger.log(20, '> ' + line)
    if score is not None:
        logger.log(20, 'SCORE: {}'.format(score))
    logger.log(20, '=' * 79)
    logger.log(20, '')
    return lines

def pretty_loop_summary(cycle_num, score, change):
    logger.log(20, ' Cycle {} Summary '.format(
            cycle_num).center(50, '-'))
    logger.log(20, '| PF Score: {:36.15f} |'.format(score))
    logger.log(20, '| % change: {:36.15f} |'.format(change * 100))
    logger.log(20, '-' * 50)

def main(args):
    parser = argparse.ArgumentParser()
    parser.add_argument(
        'input', type=str, help='Filename containing loop commands.')
    opts = parser.parse_args(args)
    lines = read_loop_input(opts.input)
    loop = Loop()
    loop.run_loop_input(lines)

if __name__ == '__main__':
    # if os.path.isfile('root.log'):
    #     os.remove('root.log')
    logging.config.dictConfig(co.LOG_SETTINGS)
    main(sys.argv[1:])
<commit_msg>Clean up, and feature addition to loop.py<commit_after>import argparse
import glob
import itertools
import logging
import logging.config
import os
import random
import sys

import calculate
import constants as co
import numpy as np
import compare
import datatypes
import gradient
import parameters
import simplex

logger = logging.getLogger(__name__)

class Loop(object):
    def __init__(self):
        self.convergence = 0.01
        self.cycle_num = 0
        self.direc = '.'
        self.ff = None
        self.ff_lines = None
        self.args_ff = None
        self.args_ref = None
        self.loop_lines = None
        self.ref_data = None
    def opt_loop(self):
        change = None
        last_score = None
        while last_score is None \
                or change is None \
                or change > self.convergence:
            self.cycle_num += 1
            last_score = self.ff.score
            self.ff = self.run_loop_input(
                self.loop_lines, score=self.ff.score)
            change = (last_score - self.ff.score) / last_score
            pretty_loop_summary(
                self.cycle_num, self.ff.score, change)
            mm3_files = glob.glob(os.path.join(self.direc, 'mm3_???.fld'))
            if mm3_files:
                mm3_files.sort()
                most_recent_mm3_file = mm3_files[-1]
                most_recent_mm3_file = most_recent_mm3_file.split('/')[-1]
                most_recent_num = most_recent_mm3_file[4:7]
                num = int(most_recent_num) + 1
                mm3_file = 'mm3_{:03d}.fld'.format(num)
                self.ff.export_ff(path=mm3_file)
            else:
                mm3_file = 'mm3_001.fld'
            mm3_file = os.path.join(self.direc, mm3_file)
            self.ff.export_ff(path=mm3_file)
            logger.log(20, '  -- Wrote best FF to {}'.format(mm3_file))
        return self.ff
    def run_loop_input(self, lines, score=None):
        lines_iterator = iter(lines)
        while True:
            try:
                line = lines_iterator.next()
            except StopIteration:
                return self.ff
            cols = line.split()
            if cols[0] == 'DIR':
                self.direc = cols[1]
            if cols[0] == 'FFLD':
                # Import FF data.
                if cols[1] == 'read':
                    self.ff = datatypes.MM3(os.path.join(self.direc, cols[2]))
                    self.ff.import_ff()
                    self.ff.method = 'READ'
                    with open(os.path.join(self.direc, cols[2]), 'r') as f:
                        self.ff.lines = f.readlines()
                # Export FF data.
                if cols[1] == 'write':
                    self.ff.export_ff(os.path.join(self.direc, cols[2]))
            # Trim parameters.
            if cols[0] == 'PARM':
                logger.log(20, '~~ SELECTING PARAMETERS ~~'.rjust(79, '~'))
                self.ff.params = parameters.trim_params_by_file(
                    self.ff.params, os.path.join(self.direc, cols[1]))
            if cols[0] == 'LOOP':
                # Read lines that will be looped over.
                inner_loop_lines = []
                line = lines_iterator.next()
                while line.split()[0] != 'END':
                    inner_loop_lines.append(line)
                    line = lines_iterator.next()
                # Make loop object and populate attributes.
                loop = Loop()
                loop.convergence = float(cols[1])
                loop.direc = self.direc
                loop.ff = self.ff
                loop.args_ff = self.args_ff
                loop.args_ref = self.args_ref
                loop.ref_data = self.ref_data
                loop.loop_lines = inner_loop_lines
                # Log commands.
                pretty_loop_input(
                    inner_loop_lines, name='OPTIMIZATION LOOP',
                    score=self.ff.score)
                # Run inner loop.
                self.ff = loop.opt_loop()
            # Note: Probably want to update this to append the directory given
            #       by the new DIR command.
            if cols[0] == 'RDAT':
                logger.log(
                    20, '~~ CALCULATING REFERENCE DATA ~~'.rjust(79, '~'))
                if len(cols) > 1:
                    self.args_ref = ' '.join(cols[1:]).split()
                self.ref_data = calculate.main(self.args_ref)
            if cols[0] == 'CDAT':
                logger.log(
                    20, '~~ CALCULATING FF DATA ~~'.rjust(79, '~'))
                if len(cols) > 1:
                    self.args_ff = ' '.join(cols[1:]).split()
                self.ff.data = calculate.main(self.args_ff)
            if cols[0] == 'COMP':
                self.ff.score = compare.compare_data(
                    self.ref_data, self.ff.data)
                if '-o' in cols:
                    compare.pretty_data_comp(
                        self.ref_data,
                        self.ff.data,
                        os.path.join(self.direc, cols[cols.index('-o') + 1]))
                else:
                    compare.pretty_data_comp(
                        self.ref_data,
                        self.ff.data)
            if cols[0] == 'GRAD':
                grad = gradient.Gradient(
                    direc=self.direc,
                    ff=self.ff,
                    ff_lines=self.ff.lines,
                    args_ff=self.args_ff)
                self.ff = grad.run(ref_data=self.ref_data)
            if cols[0] == 'SIMP':
                simp = simplex.Simplex(
                    direc=self.direc,
                    ff=self.ff,
                    ff_lines=self.ff.lines,
                    args_ff=self.args_ff)
                self.ff = simp.run(r_data=self.ref_data)
            if cols[0] == 'WGHT':
                data_type = cols[1]
                co.WEIGHTS[data_type] = float(cols[2])

def read_loop_input(filename):
    with open(filename, 'r') as f:
        lines = f.readlines()
    lines = [x.partition('#')[0].strip('\n') for x in lines if
             x.partition('#')[0].strip('\n') != '']
    pretty_loop_input(lines)
    return lines

def pretty_loop_input(lines, name='Q2MM', score=None):
    logger.log(20, ' {} '.format(name).center(79, '='))
    logger.log(20, 'COMMANDS:')
    for line in lines:
        logger.log(20, '> ' + line)
    if score is not None:
        logger.log(20, 'SCORE: {}'.format(score))
    logger.log(20, '=' * 79)
    logger.log(20, '')
    return lines

def pretty_loop_summary(cycle_num, score, change):
    logger.log(20, ' Cycle {} Summary '.format(
            cycle_num).center(50, '-'))
    logger.log(20, '| PF Score: {:36.15f} |'.format(score))
    logger.log(20, '| % change: {:36.15f} |'.format(change * 100))
    logger.log(20, '-' * 50)

def main(args):
    parser = argparse.ArgumentParser()
    parser.add_argument(
        'input', type=str, help='Filename containing loop commands.')
    opts = parser.parse_args(args)
    lines = read_loop_input(opts.input)
    loop = Loop()
    loop.run_loop_input(lines)

if __name__ == '__main__':
    # if os.path.isfile('root.log'):
    #     os.remove('root.log')
    logging.config.dictConfig(co.LOG_SETTINGS)
    main(sys.argv[1:])
<|endoftext|>


<commit_before>import serial

# Functions
def send_no_ack(ser, strIn):
    # write bytes
    ser.write(b"%s\r\n" % strIn)

def send(ser, strIn, strOut=0):
    send_no_ack(ser, strIn)

    # wait for answer
    ret = ser.readline()
    if DEBUG:
        print ">> %s" % ret.strip()

    # if requested, verify return
    if strOut is not 0:
        # verify return
        if ret.strip() != strOut:
            raise IOError()
    return True

def set_rx_mode(ser, getsnr=0):
    try:
        # try to configure device as receiver
        send(ser, "mac pause")
        send(ser, "radio rx 0", "ok")
    except IOError:
        # abort if configuration failed
        print "RX: Configuration failed!"
    else:
        # wait for incoming data
        print "RX: Receiving..."
        ret = ser.readline()

        # strip data
        ret = ret[8:].strip()

        if DEBUG:
            print ">> %s" % ret

        if getsnr:
            send_no_ack(ser, "radio get snr")
            snr = ser.readline()
            # range -128 to 127
            print ">> SNR=%s" % snr
        return ret

def set_tx_mode(ser):
    try:
        # try to configure device as receiver
        send(ser, "mac pause")
    except IOError:
        # abort if configuration failed
        print "TX: Configuration failed!"
    else:
        try:
            # try to send data
            send(ser, "radio tx FF", "ok")
            ret = ser.readline()
            if ret.strip() != "radio_tx_ok":
                print("expecting radio_tx_ok, received:'" + ret.strip() + "'")
                raise IOError()
        except IOError:
            print "TX: Failed to Send!"

def open_pyb(port = 1):
    ser = UART(port, 57600)                      # init with given baudrate
    ser.init(57600, bits=8, parity=None, stop=1) # init with given parameters
    return ser

def open_pc(serPort):
    return serial.Serial('/dev/' + serPort,
                         baudrate=57600,
                         bytesize=serial.EIGHTBITS,
                         parity=serial.PARITY_NONE,
                         stopbits=serial.STOPBITS_ONE)

def init(port='pyb'):
    # open connection
    if port == 'pyb':
        ser = open_pyb()
    else:
        ser = open_pc(port)
    try:
        send(ser, "radio set mod lora", "ok")
        send(ser, "radio set freq 868000000", "ok")
        send(ser, "radio set pwr 14", "ok")
        send(ser, "radio set sf sf12", "ok")
        send(ser, "radio set afcbw 125", "ok")
        send(ser, "radio set rxbw 250", "ok")
        send(ser, "radio set fdev 5000", "ok")
        send(ser, "radio set prlen 8", "ok")
        send(ser, "radio set crc on", "ok")
        send(ser, "radio set cr 4/8", "ok")
        send(ser, "radio set wdt 0", "ok")
        send(ser, "radio set sync 12", "ok")
        send(ser, "radio set bw 250", "ok")
    except IOError:
        # abort if configuration failed
        print "Initial Configuration failed!"
    else:
        if radioModeTx:
            set_tx_mode(ser)
        else:
            rxCount = 0
            while(1):
                set_rx_mode(ser, 1)
                rxCount = rxCount + 1
                print "rx packet %d" % rxCount
    print "end"
    ser.close()

# execute only if run as a script
if __name__ == "__main__":
    import argparse

    # command line arguments and parsing
    parser = argparse.ArgumentParser(description='LoRa Script Arguments')
    parser.add_argument('-d', '--debug',
                        action='store_true', help='debug mode')
    parser.add_argument('-tx', '--transmit',
                        action='store_true', help='Set Radio TX Mode')
    parser.add_argument('-p', '--port',
                        default='pyb',
                        help='Serial Port ' +
                        'e.g. pyb, ttyUSB1, tty.usbserial-A5046HZ5')
    args = parser.parse_args()
    DEBUG = args.debug
    serPort = args.port
    radioModeTx = args.transmit

    init(serPort)
<commit_msg>new code structure for access from pc and pyb with ne low level abstraction<commit_after>DEBUG = True
DEV = None

# Create connection to LoRa module
def open_from_pyb(port=1):
    import pyb
    ser = pyb.UART(port, 57600)
    ser.init(57600,
             bits=8,
             parity=None,
             stop=1)

    global DEV
    DEV = 'pyb'

    return ser

def open_from_pc(serPort):
    import serial
    ser = serial.Serial('/dev/' + serPort,
                        baudrate=57600,
                        bytesize=serial.EIGHTBITS,
                        parity=serial.PARITY_NONE,
                        stopbits=serial.STOPBITS_ONE)
    global DEV
    DEV = 'pc'

    return ser

# Communicate with LoRa module
def write_from_pc(ser, strIn):
    # write bytes
    ser.write(b"%s\r\n" % strIn)
    return None

def write_from_pyb(ser, strIn):
    return None

def write(ser, strIn):
    if DEV == 'pc':
        return write_from_pc(ser, strIn)
    else:
        return write_from_pyb(ser, strIn)

def read_from_pc(ser):
    ret = ser.readline()
    if DEBUG:
        print '>> %s' % (ret.strip())
    return ret

def read_from_pyb(ser):
    return None

def read(ser):
    if DEV == 'pc':
        return read_from_pc(ser)
    else:
        return read_from_pyb(ser)

def send(ser, strIn, strOut=0):
    write(ser, strIn)

    # wait for answer
    ret = read(ser)

    # if requested, verify return
    if strOut is not 0:
        # verify return
        if ret.strip() != strOut:
            raise IOError()
    return True

# LoRa config functions
def set_rx_mode(ser, getsnr=0):
    try:
        # try to configure device as receiver
        send(ser, "mac pause")
        send(ser, "radio rx 0", "ok")
    except IOError:
        # abort if configuration failed
        print "RX: Configuration failed!"
    else:
        # wait for incoming data
        print "RX: Receiving..."
        ret = ser.readline()

        # strip data
        ret = ret[8:].strip()

        if DEBUG:
            print ">> %s" % ret

        if getsnr:
            write(ser, "radio get snr")
            snr = ser.readline()
            # range -128 to 127
            print ">> SNR=%s" % snr
        return ret

def set_tx_mode(ser):
    try:
        # try to configure device as receiver
        send(ser, "mac pause")
    except IOError:
        # abort if configuration failed
        print "TX: Configuration failed!"
    else:
        try:
            # try to send data
            send(ser, "radio tx FF", "ok")
            ret = ser.readline()
            if ret.strip() != "radio_tx_ok":
                print("expecting radio_tx_ok, received:'" + ret.strip() + "'")
                raise IOError()
        except IOError:
            print "TX: Failed to Send!"

def init(port='pyb'):
    # open connection
    if port == 'pyb':
        ser = open_from_pyb()
    else:
        ser = open_from_pc(port)
    try:
        send(ser, "radio set mod lora", "ok")
        send(ser, "radio set freq 868000000", "ok")
        send(ser, "radio set pwr 14", "ok")
        send(ser, "radio set sf sf12", "ok")
        send(ser, "radio set afcbw 125", "ok")
        send(ser, "radio set rxbw 250", "ok")
        send(ser, "radio set fdev 5000", "ok")
        send(ser, "radio set prlen 8", "ok")
        send(ser, "radio set crc on", "ok")
        send(ser, "radio set cr 4/8", "ok")
        send(ser, "radio set wdt 0", "ok")
        send(ser, "radio set sync 12", "ok")
        send(ser, "radio set bw 250", "ok")
    except IOError:
        # abort if configuration failed
        print "Initial Configuration failed!"
    else:
        if radioModeTx:
            set_tx_mode(ser)
        else:
            rxCount = 0
            while(1):
                set_rx_mode(ser, 1)
                rxCount = rxCount + 1
                print "rx packet %d" % rxCount
    print "end"
    ser.close()

# execute only if run as a script
if __name__ == "__main__":
    import argparse

    # command line arguments and parsing
    parser = argparse.ArgumentParser(description='LoRa Script Arguments')
    parser.add_argument('-d', '--debug',
                        action='store_true', help='debug mode')
    parser.add_argument('-tx', '--transmit',
                        action='store_true', help='Set Radio TX Mode')
    parser.add_argument('-p', '--port',
                        default='pyb',
                        help='Serial Port ' +
                        'e.g. pyb, ttyUSB1, tty.usbserial-A5046HZ5')
    args = parser.parse_args()
    DEBUG = args.debug
    serPort = args.port
    radioModeTx = args.transmit

    init(serPort)
<|endoftext|>


<commit_before>"""
Set up a minimal HTTP Server to preview a Toasty-generated
tile pyramid
"""
import os
import sys
from time import time
try:
    from SimpleHTTPServer import SimpleHTTPRequestHandler, test
    from cStringIO import StringIO
except:  # python 3.X
    from http.server import SimpleHTTPRequestHandler, test
    from io import BytesIO

from . import gen_wtml

class SimpleWWTHandler(SimpleHTTPRequestHandler):

    def serve_string(self, contents):
        if sys.version_info.major == 2:
            return StringIO(contents)

        return BytesIO(contents.encode('UTF-8'))

    @property
    def wtml(self):
        if not hasattr(self, '_wtml'):
            base_dir = sys.argv[-1]
            depths = next(os.walk(base_dir))[1]
            max_depth = max(map(int, depths))
            self._wtml = gen_wtml(base_dir, max_depth)
        return self._wtml

    def send_head(self):
        if self.path == '/toasty.wtml':
            self.send_response(200)
            self.send_header("Content-type", 'text/xml')
            self.send_header("Content-Length", str(len(self.wtml)))
            self.send_header("Last-Modified",
                             self.date_time_string(int(time())))
            self.end_headers()
            return self.serve_string(self.wtml)

        if self.path in ['/', '/index.html']:
            self.send_response(200)
            self.send_header("Content-type", 'text/html')
            self.send_header("Content-Length", str(len(html)))
            self.send_header("Last-Modified",
                             self.date_time_string(int(time())))
            self.end_headers()
            return self.serve_string(html)

        return SimpleHTTPRequestHandler.send_head(self)

html = """

<title> Toasty Viewer </title>

"""

if __name__ == "__main__":
    sys.argv.insert(1, '8000')
    test(HandlerClass=SimpleWWTHandler)
<commit_msg>Minimal layer UI<commit_after>"""
Set up a minimal HTTP Server to preview a Toasty-generated
tile pyramid
"""
import os
import sys
from time import time
try:
    from SimpleHTTPServer import SimpleHTTPRequestHandler, test
    from cStringIO import StringIO
except:  # python 3.X
    from http.server import SimpleHTTPRequestHandler, test
    from io import BytesIO

from . import gen_wtml

class SimpleWWTHandler(SimpleHTTPRequestHandler):

    def serve_string(self, contents):
        if sys.version_info.major == 2:
            return StringIO(contents)

        return BytesIO(contents.encode('UTF-8'))

    @property
    def wtml(self):
        if not hasattr(self, '_wtml'):
            base_dir = sys.argv[-1]
            depths = next(os.walk(base_dir))[1]
            max_depth = max(map(int, depths))
            self._wtml = gen_wtml(base_dir, max_depth)
        return self._wtml

    def send_head(self):
        if self.path == '/toasty.wtml':
            self.send_response(200)
            self.send_header("Content-type", 'text/xml')
            self.send_header("Content-Length", str(len(self.wtml)))
            self.send_header("Last-Modified",
                             self.date_time_string(int(time())))
            self.end_headers()
            return self.serve_string(self.wtml)

        if self.path in ['/', '/index.html']:
            self.send_response(200)
            self.send_header("Content-type", 'text/html')
            self.send_header("Content-Length", str(len(html)))
            self.send_header("Last-Modified",
                             self.date_time_string(int(time())))
            self.end_headers()
            return self.serve_string(html)

        return SimpleHTTPRequestHandler.send_head(self)

html = """

<title> Toasty Viewer </title>

<select id="select-foreground">
<option value="Digitized Sky Survey (Color)"> Optical </option>
<option value="WMAP ILC 5-Year Cosmic Microwave Background"> WMAP 5-Year </option>
<option value="SFD Dust Map (Infrared)"> SFD </option>
<option value="IRIS: Improved Reprocessing of IRAS Survey (Infrared)"> IRIS </option>
<option value="Hydrogen Alpha Full Sky Map"> H-alpha </option>
</select>

<input id='opacity' type="range" name="opacity" min="10" max="100">

"""

if __name__ == "__main__":
    sys.argv.insert(1, '8000')
    test(HandlerClass=SimpleWWTHandler)
<|endoftext|>