<commit_before>#!/usr/bin/env python3
"""Advent of Code 2015, Day 13: Knights of the Dinner Table"""

from itertools import permutations
import re

import pytest

SAMPLE_INPUT = '''\
Alice would gain 54 happiness units by sitting next to Bob.
Alice would lose 79 happiness units by sitting next to Carol.
Alice would lose 2 happiness units by sitting next to David.
Bob would gain 83 happiness units by sitting next to Alice.
Bob would lose 7 happiness units by sitting next to Carol.
Bob would lose 63 happiness units by sitting next to David.
Carol would lose 62 happiness units by sitting next to Alice.
Carol would gain 60 happiness units by sitting next to Bob.
Carol would gain 55 happiness units by sitting next to David.
David would gain 46 happiness units by sitting next to Alice.
David would lose 7 happiness units by sitting next to Bob.
David would gain 41 happiness units by sitting next to Carol.
'''

def unique_seating_permutations(guests):
    """Return a list of all permutations of guests with reversals removed

    Comparing the first and last elements works because at the point
    this starts to fail, the reverse permutation is already stored in
    the list.

    You can verify this by building a set and only storing elements whose
    reversals aren’t already present in the set. That set contains the
    same elements as building one in this manner.
    """
    perms = permutations(guests)
    return [p for p in perms if p[0] < p[-1]]

def neighbours(guest, plan):
    """Return guests’s adjacent neighbours in plan

    Guests at one end of the plan are considered to be sitting next
    to the guest at the opposite end.
    """
    g_index = plan.index(guest)
    left = plan[g_index - 1]
    right = plan[(g_index + 1) % len(plan)]  # Wrap around to zero
    return (left, right)

def parse_happiness(text):
    """Parse the instructions and return a dict of happiness relations"""
    regex = re.compile(
        '^(\w)\w+ \w+ (gain|lose) (\d+)[ a-z]+([A-Z])[a-z]+\.$'
    )
    happiness_dict = dict()
    for line in text.splitlines():
        match = regex.match(line)
        person, change, amount, neighbour = match.groups()
        amount = int(amount)
        if change == 'lose':
            amount = -amount
        if person not in happiness_dict:
            happiness_dict[person] = dict()
        happiness_dict[person][neighbour] = amount
    return happiness_dict

def sum_happiness(happiness_dict, plan):
    """Sum the happiness change of the seating plan"""
    total = 0
    for guest in plan:
        left, right = neighbours(guest, plan)
        total += happiness_dict[guest][left]
        total += happiness_dict[guest][right]
    return total

def find_best_plan(happiness_dict):
    best_happiness = 0
    best_plan = None
    perms = unique_seating_permutations(happiness_dict.keys())
    for plan in perms:
        happiness_change = sum_happiness(happiness_dict, plan)
        if happiness_change > best_happiness:
            best_happiness = happiness_change
            best_plan = plan
    return (best_happiness, best_plan)

def test_parse():
    expected = dict(
        A=dict(B=54, C=-79, D=-2),
        B=dict(A=83, C=-7, D=-63),
        C=dict(A=-62, B=60, D=55),
        D=dict(A=46, B=-7, C=41))
    assert parse_happiness(SAMPLE_INPUT) == expected

def test_sum_happiness():
    happiness_dict = parse_happiness(SAMPLE_INPUT)
    assert sum_happiness(happiness_dict, 'ABCD') == 330

@pytest.mark.parametrize('guests', ['A', 'AB', 'ABC'])
def test_unique_seat_plans_only_one_plan(guests):
    """unique_seating_permutations gives expected number of plans """
    assert len(unique_seating_permutations(guests)) == 1

@pytest.mark.parametrize('plan', ['ABCD', 'ABCDE', 'ABCDEF'])
def test_unique_plans_are_unique(plan):
    """unique_seating_permutations returns only unique plans"""
    plan_pairs_set = set()
    for char_tuple in unique_seating_permutations(plan):
        pair_set = {''.join(sorted([c1, c2]))
                    for c1, c2 in zip(char_tuple, char_tuple[1:])}
        pair_set.add(''.join(sorted([char_tuple[0], char_tuple[-1]])))
        assert pair_set not in plan_pairs_set
        plan_pairs_set.add(frozenset(pair_set))

def main(puzzle_input):
    happiness_dict = parse_happiness(puzzle_input)

    # Part one
    p1_change, p1_plan = find_best_plan(happiness_dict)
    print(f'Part one: {p1_change}, {"".join(p1_plan)}')

    # Part two
    # Add myself to happiness dict, with 0 change
    guests_only = list(happiness_dict.keys())
    happiness_dict['Z'] = dict()
    for g in guests_only:
        happiness_dict[g]['Z'] = 0
        happiness_dict['Z'][g] = 0

    p2_change, p2_plan = find_best_plan(happiness_dict)
    print(f'Part two: {p2_change}, {"".join(p2_plan)}')

if __name__ == '__main__':
    with open('../input/2015-13.txt') as f:
        puzzle_input = f.read()
    main(puzzle_input)
<commit_msg>2015-13.py: unique_seating_permutations output is unique<commit_after>#!/usr/bin/env python3
"""Advent of Code 2015, Day 13: Knights of the Dinner Table"""

from itertools import permutations
import re

import pytest

SAMPLE_INPUT = '''\
Alice would gain 54 happiness units by sitting next to Bob.
Alice would lose 79 happiness units by sitting next to Carol.
Alice would lose 2 happiness units by sitting next to David.
Bob would gain 83 happiness units by sitting next to Alice.
Bob would lose 7 happiness units by sitting next to Carol.
Bob would lose 63 happiness units by sitting next to David.
Carol would lose 62 happiness units by sitting next to Alice.
Carol would gain 60 happiness units by sitting next to Bob.
Carol would gain 55 happiness units by sitting next to David.
David would gain 46 happiness units by sitting next to Alice.
David would lose 7 happiness units by sitting next to Bob.
David would gain 41 happiness units by sitting next to Carol.
'''

def unique_seating_permutations(guests):
    """Yield unique circular permutations of guests """
    if len(guests) < 3:
        # Only one plan for groups of three or less
        yield guests
        return

    def pair(*args):
        return tuple(sorted(args))

    perms = permutations(guests)
    seen_uniques = set()
    for p in perms:
        char_pairs = {pair(c1, c2) for c1, c2 in zip(p, p[1:])}
        char_pairs.add(pair(p[0], p[-1]))
        if char_pairs not in seen_uniques:
            yield p
            seen_uniques.add(frozenset(char_pairs))

def neighbours(guest, plan):
    """Return guests’s adjacent neighbours in plan

    Guests at one end of the plan are considered to be sitting next
    to the guest at the opposite end.
    """
    g_index = plan.index(guest)
    left = plan[g_index - 1]
    right = plan[(g_index + 1) % len(plan)]  # Wrap around to zero
    return (left, right)

def parse_happiness(text):
    """Parse the instructions and return a dict of happiness relations"""
    regex = re.compile(
        '^(\w)\w+ \w+ (gain|lose) (\d+)[ a-z]+([A-Z])[a-z]+\.$'
    )
    happiness_dict = dict()
    for line in text.splitlines():
        match = regex.match(line)
        person, change, amount, neighbour = match.groups()
        amount = int(amount)
        if change == 'lose':
            amount = -amount
        if person not in happiness_dict:
            happiness_dict[person] = dict()
        happiness_dict[person][neighbour] = amount
    return happiness_dict

def sum_happiness(happiness_dict, plan):
    """Sum the happiness change of the seating plan"""
    total = 0
    for guest in plan:
        left, right = neighbours(guest, plan)
        total += happiness_dict[guest][left]
        total += happiness_dict[guest][right]
    return total

def find_best_plan(happiness_dict):
    best_happiness = 0
    best_plan = None
    perms = unique_seating_permutations(happiness_dict.keys())
    for plan in perms:
        happiness_change = sum_happiness(happiness_dict, plan)
        if happiness_change > best_happiness:
            best_happiness = happiness_change
            best_plan = plan
    return (best_happiness, best_plan)

def test_parse():
    expected = dict(
        A=dict(B=54, C=-79, D=-2),
        B=dict(A=83, C=-7, D=-63),
        C=dict(A=-62, B=60, D=55),
        D=dict(A=46, B=-7, C=41))
    assert parse_happiness(SAMPLE_INPUT) == expected

def test_sum_happiness():
    happiness_dict = parse_happiness(SAMPLE_INPUT)
    assert sum_happiness(happiness_dict, 'ABCD') == 330

@pytest.mark.parametrize('guests', ['A', 'AB', 'ABC'])
def test_unique_seat_plans_only_one_plan(guests):
    """unique_seating_permutations gives expected number of plans """
    assert len(list(unique_seating_permutations(guests))) == 1

@pytest.mark.parametrize('plan', ['ABCD', 'ABCDE', 'ABCDEF'])
def test_unique_plans_are_unique(plan):
    """unique_seating_permutations returns only unique plans"""
    plan_pairs_set = set()
    for char_tuple in unique_seating_permutations(plan):
        pair_set = {''.join(sorted([c1, c2]))
                    for c1, c2 in zip(char_tuple, char_tuple[1:])}
        pair_set.add(''.join(sorted([char_tuple[0], char_tuple[-1]])))
        assert pair_set not in plan_pairs_set
        plan_pairs_set.add(frozenset(pair_set))

def main(puzzle_input):
    happiness_dict = parse_happiness(puzzle_input)

    # Part one
    p1_change, p1_plan = find_best_plan(happiness_dict)
    print(f'Part one: {p1_change}, {"".join(p1_plan)}')

    # Part two
    # Add myself to happiness dict, with 0 change
    guests_only = list(happiness_dict.keys())
    happiness_dict['Z'] = dict()
    for g in guests_only:
        happiness_dict[g]['Z'] = 0
        happiness_dict['Z'][g] = 0

    p2_change, p2_plan = find_best_plan(happiness_dict)
    print(f'Part two: {p2_change}, {"".join(p2_plan)}')

if __name__ == '__main__':
    with open('../input/2015-13.txt') as f:
        puzzle_input = f.read()
    main(puzzle_input)
<|endoftext|>


<commit_before># structure.py
# Ronald L. Rivest (with Karim Husayn Karimi)
# July 7, 2017
# python3

"""
Routines to work with multi.py, to read in the
CSV files containing information about the "structure"
of an election:
   11-election-2017-09-08.csv
   12-contests-2017-09-08.csv
   14-collections-2017-09-08.csv
with structures represented by csv files of the form:

11-election.csv:
Attribute     , Value
Election name , Colorado general election
Election dirname , CO-2017-11-07,
Election date , 2017-11-07
Election URL  , https://sos.co.gov/election/2017-11-07/

12-contests.csv
Contest id      , Contest type , Winners   ,Write-ins  , Selections
Denver Prop 1   , Plurality    , 1         , No        , Yes        , No
Denver Prop 2   , Plurality    , 1         , No        , Yes        , No
Denver Mayor    , Plurality    , 1         , Qualified , John Smith , Bob Cat   , Mary Mee   ,+Jack Frost
Denver Clerk    , Plurality    , 1         , No        , Yet Again  , New Guy
Logan Mayor     , Plurality    , 1         , Arbitrary , Susan Hat  , Barry Su  , Benton Liu
Logan Water     , Plurality    , 1         , No        , Yes        , No
U.S. President  , Plurality    , 1         , Arbitrary , Don Brown  , Larry Pew
U.S. Senate 1   , Plurality    , 1         , Qualified , Deb O'Crat , Rhee Pub  , Val Green  , Sarah Day   , +Tom Cruz
U.S. Senate 2   , Plurality    , 1         , Qualified , Term Three , Old Guy   , +Hot Stuff
CO Prop A       , Plurality    , 1         , No        , Yes        , No

13-contest-groups.csv
Contest group id ,Contest or group id(s)
FEDERAL          ,U.S. President   ,U.S. Senate 1        ,U.S. Senate 2
STATE            ,CO Prop A
FED STATE        ,FEDERAL          ,STATE
DENVER LOCAL     ,Denver Mayor     ,Denver Clerk, Denver Prop 1, Denver Prop 2
DENVER           ,FED STATE        ,DENVER LOCAL
LOGAN REQ        ,FED STATE        ,Logan Mayor
LOGAN POSS       ,Logan Water

14-collections.csv
Collection id , Manager          , CVR type  , Required Contests, Possible Contests
DEN-A01       , abe@co.gov       , CVR       , DENVER,            DENVER
DEN-A02       , bob@co.gov       , CVR       , DENVER,            DENVER
LOG-B13       , carol@co.gov     , noCVR     , LOGAN REQ,         LOGAN POSS

The values are sanity checked, and put into the Election data structure (e)
from multi.py
"""

import os

import multi
import csv_readers
import groups
import utils

def read_election_structure(e, election_dirname):
    """
    Read file 1-structure/11-election.csv, put results into Election e.
    election_dirname is the name of the directory for the election
        (e.g. "CO-2017-11") with ELECTIONS_ROOT
    """

    election_pathname = os.path.join(multi.ELECTIONS_ROOT, election_dirname)
    structure_pathname = os.path.join(election_pathname, "1-structure")
    filename = utils.greatest_name(structure_pathname, "11-election", ".csv")
    file_pathname = os.path.join(structure_pathname, filename)
    fieldnames = ["Attribute", "Value"]
    rows = csv_readers.read_csv_file(file_pathname, fieldnames)
    for row in rows:
        if "Election name" == row["Attribute"]:
            e.election_name = row["Value"]
        elif "Election dirname" == row["Attribute"]:
            e.election_dirname = row["Value"]
        elif "Election date" == row["Attribute"]:
            e.election_date = row["Value"]
        elif "Election URL" == row["Attribute"]:
            e.election_url = row["Value"]
    for attribute in ["election_name", "election_dirname",
                      "election_date", "election_url"]:
        if attribute not in vars(e):
            utils.mywarning("Attribute {} not present in 11-election.csv."
                            .format(attribute))
    if utils.warnings_given > 0:
        utils.myerror("Too many errors; terminating.")

def test_read_election_structure(e):

    print("test_read_election_structure")
    read_election_structure(e, "ex1")

def read_contests(e):
    """
    Read file 12-contests.csv, put results into Election e.
    """

    election_pathname = os.path.join(multi.ELECTIONS_ROOT, e.election_dirname)
    structure_pathname = os.path.join(election_pathname, "1-structure")
    filename = utils.greatest_name(structure_pathname, "12-contests", ".csv")
    file_pathname = os.path.join(structure_pathname, filename)
    fieldnames = ["Contest id", "Contest type", "Winners", "Write-ins",
                  "Selections"]
    rows = csv_readers.read_csv_file(file_pathname, fieldnames, varlen=True)
    for row in rows:

        cid = row["Contest id"]

        e.cids.append(cid)

        e.contest_type_c[cid] = row["Contest type"].lower()

        e.winners_c[cid] = int(row["Winners"])

        e.write_ins_c[cid] = row["Write-ins"].lower()

        e.selids_c[cid] = {}
        for selid in row["Selections"]:
            e.selids_c[cid][selid] = True

def test_read_contests(e):

    print("test_read_contests")
    read_election_structure(e, "ex1/")
    read_contests(e)

def read_contest_groups(e):
    """
    Read file 13-contest-groups.csv, put results into Election e.
    """

    election_pathname = os.path.join(multi.ELECTIONS_ROOT, e.election_dirname)
    structure_pathname = os.path.join(election_pathname, "1-structure")
    filename = utils.greatest_name(structure_pathname, "13-contest-groups", ".csv")
    file_pathname = os.path.join(structure_pathname, filename)
    fieldnames = ["Contest group id", "Contest or group id(s)"]
    rows = csv_readers.read_csv_file(file_pathname, fieldnames, varlen=True)
    for row in rows:

        gid = row["Contest group id"]
        e.gids.append(gid)

        e.cgids_g[gid] = row["Contest or group id(s)"]

def test_read_contest_groups(e):

    print("test_read_contest_groups")
    read_contest_groups(e)
    pass

def read_collections(e):
    """
    Read file 14-collections.csv, put results into Election e.
    """

    election_pathname = os.path.join(multi.ELECTIONS_ROOT, e.election_dirname)
    structure_pathname = os.path.join(election_pathname, "1-structure")
    filename = utils.greatest_name(structure_pathname, "14-collections", ".csv")
    file_pathname = os.path.join(structure_pathname, filename)
    fieldnames = ["Collection id", "Manager", "CVR type",
                  "Required Contests", "Possible Contests"]
    rows = csv_readers.read_csv_file(file_pathname, fieldnames, varlen=False)
    for row in rows:

        pbcid = row["Collection id"]
        e.pbcids.append(pbcid)
        e.manager_p[pbcid] = row["Manager"]
        e.cvr_type_p[pbcid] = row["CVR type"]
        e.required_gid_p[pbcid] = row["Required Contests"]
        e.possible_gid_p[pbcid] = row["Possible Contests"]

def test_read_collections(e):

    print("test_read_collections")
    read_collections(e)

def get_election_structure(e):

    read_election_structure(e, e.election_dirname)
    read_contests(e)
    read_contest_groups(e)
    read_collections(e)
    finish_election_structure(e)
    check_election_structure(e)
    show_election_structure(e)

def finish_election_structure(e):

    finish_election_structure_groups(e)
    finish_election_structure_votes(e)

def finish_election_structure_groups(e):

    groups.expand_contest_group_defs(e)

    for pbcid in e.pbcids:
        e.possible_cid_p[pbcid] = {}
        e.required_cid_p[pbcid] = {}
    for cid in e.cids:
        e.possible_pbcid_c[cid] = {}
        e.required_pbcid_c[cid] = {}

    for pbcid in e.pbcids:
        req_gid = e.required_gid_p[pbcid]
        poss_gid = e.possible_gid_p[pbcid]
        for cid in e.cids:
            # "" req_gid means nothing is required.
            if req_gid!="" and cid in e.cids_g[req_gid]:
                utils.nested_set(e.required_cid_p, [pbcid, cid], "True")
                utils.nested_set(e.required_pbcid_c, [cid, pbcid], "True")
            # enforce that possible contests includes all contests
            # "" poss_gid means everything is possible
            if poss_gid=="" or cid in e.cids_g[poss_gid] or cid in e.cids_g[req_gid]:
                utils.nested_set(e.possible_cid_p, [pbcid, cid], "True")
                utils.nested_set(e.possible_pbcid_c, [cid, pbcid], "True")

def finish_election_structure_votes(e):

    noCVRvote = ("-noCVR",)
    for cid in e.cids:
        e.votes_c[cid] = {}
        for selid in e.selids_c[cid]:
            e.votes_c[cid][(selid,)] = True
    for pbcid in e.pbcids:
        if e.cvr_type_p[pbcid] == "noCVR":
            for cid in e.required_cid_p[pbcid]:
                e.votes_c[cid][noCVRvote] = True

def check_id(id, check_for_whitespace=False):

    if not isinstance(id, str) or not id.isprintable():
        utils.mywarning("id is not string or is not printable: {}".format(id))
    if check_for_whitespace:
        for c in id:
            if c.isspace():
                utils.mywarning("id `id` contains whitespace.")
                break

def check_election_structure(e):

    if not isinstance(e.cids, (list, tuple, set)):
        utils.myerror("e.cids is not a set.")
    if len(e.cids) == 0:
        utils.myerror("e.cids is an empty list of contests.")
    for cid in e.cids:
        check_id(cid)

    if not isinstance(e.pbcids, (list, tuple)):
        utils.myerror("e.pbcids is not a list or a tuple.")
    if len(e.pbcids) == 0:
        utils.myerror("e.pbcids is an empty list of pbcids.")
    for pbcid in e.pbcids:
        check_id(pbcid)

    for cid in e.selids_c:
        if cid not in e.cids:
            utils.myerror("e.selids_c has a key `{}` not in e.cids.".format(cid))
        for selid in e.selids_c[cid]:
            check_id(selid)
    for cid in e.cids:
        if cid not in e.selids_c:
            utils.mywarning("cid `{}` should be key in e.selids_c".format(cid))

    if not isinstance(e.cvr_type_p, dict):
        utils.myerror("e_cvr_type is not a dict.")
    for pbcid in e.cvr_type_p:
        if pbcid not in e.pbcids:
            utils.mywarning("pbcid `{}` is not in e.pbcids".format(pbcid))
        if e.cvr_type_p[pbcid] not in ["CVR", "noCVR"]:
            utils.mywarning("e.cvr_type_p[{}]==`{}` is not CVR or noCVR"
                      .format(pbcid, e.cvr_type_p[pbcid]))
    for pbcid in e.pbcids:
        if pbcid not in e.cvr_type_p:
            utils.mywarning("pbcid `{}` not key in e.cvr_type_p."
                      .format(pbcid))

    if utils.warnings_given > 0:
        utils.myerror("Too many errors; terminating.")

def show_election_structure(e):
    utils.myprint("====== Election structure ======")
    utils.myprint("Election name: (e.election_name):")
    utils.myprint("    {}".format(e.election_name))
    utils.myprint("Election dirname (e.election_dirname):")
    utils.myprint("    {}".format(e.election_dirname))
    utils.myprint("Election date (e.election date):")
    utils.myprint("    {}".format(e.election_date))
    utils.myprint("Election URL (e.election_url):")
    utils.myprint("    {}".format(e.election_url))
    utils.myprint("Number of contests:")
    utils.myprint("    {}".format(len(e.cids)))
    utils.myprint("Contest ids with contest type, number of winners, and write-ins mode")
    utils.myprint("(e.cids, e.contest_type_c, e.winners_c, e.write_ins_c):")
    for cid in e.cids:
        utils.myprint("    {} ({}, {} winner(s), {} write-ins)"
                      .format(cid, e.contest_type_c[cid], e.winners_c[cid], e.write_ins_c[cid]))
    utils.myprint("Valid selection ids for each cid (e.selids_c):")
    for cid in e.cids:
        utils.myprint("    {}: ".format(cid), end='')
        utils.myprint(", ".join(sorted(e.selids_c[cid])))
    utils.myprint("Number of paper ballot collections:")
    utils.myprint("    {}".format(len(e.pbcids)))
    utils.myprint("Paper ballot collection ids (e.pbcids), CVR types (e.cvr_type_p), and managers (e.manager_p):")
    for pbcid in sorted(e.pbcids):
        utils.myprint("    {} ({}, Manager:{})"
                      .format(pbcid, e.cvr_type_p[pbcid], e.manager_p[pbcid]))
    utils.myprint("Required pbcids for each cid (e.required_pbcid_c):")
    for cid in e.cids:
        utils.myprint("    {}: ".format(cid), end='')
        utils.myprint(", ".join(sorted(e.required_pbcid_c[cid])))
    utils.myprint("Possible pbcids for each cid (e.possible_pbcid_c):")
    for cid in e.cids:
        utils.myprint("    {}: ".format(cid), end='')
        utils.myprint(", ".join(sorted(e.possible_pbcid_c[cid])))

def test():

    e = multi.Election()
    test_read_election_structure(e)
    test_read_contests(e)
    test_read_contest_groups(e)
    test_read_collections(e)

if __name__=="__main__":

    test()
<commit_msg>Improve error message in structure.check_election_structure.<commit_after># structure.py
# Ronald L. Rivest (with Karim Husayn Karimi)
# July 7, 2017
# python3

"""
Routines to work with multi.py, to read in the
CSV files containing information about the "structure"
of an election:
   11-election-2017-09-08.csv
   12-contests-2017-09-08.csv
   14-collections-2017-09-08.csv
with structures represented by csv files of the form:

11-election.csv:
Attribute     , Value
Election name , Colorado general election
Election dirname , CO-2017-11-07,
Election date , 2017-11-07
Election URL  , https://sos.co.gov/election/2017-11-07/

12-contests.csv
Contest id      , Contest type , Winners   ,Write-ins  , Selections
Denver Prop 1   , Plurality    , 1         , No        , Yes        , No
Denver Prop 2   , Plurality    , 1         , No        , Yes        , No
Denver Mayor    , Plurality    , 1         , Qualified , John Smith , Bob Cat   , Mary Mee   ,+Jack Frost
Denver Clerk    , Plurality    , 1         , No        , Yet Again  , New Guy
Logan Mayor     , Plurality    , 1         , Arbitrary , Susan Hat  , Barry Su  , Benton Liu
Logan Water     , Plurality    , 1         , No        , Yes        , No
U.S. President  , Plurality    , 1         , Arbitrary , Don Brown  , Larry Pew
U.S. Senate 1   , Plurality    , 1         , Qualified , Deb O'Crat , Rhee Pub  , Val Green  , Sarah Day   , +Tom Cruz
U.S. Senate 2   , Plurality    , 1         , Qualified , Term Three , Old Guy   , +Hot Stuff
CO Prop A       , Plurality    , 1         , No        , Yes        , No

13-contest-groups.csv
Contest group id ,Contest or group id(s)
FEDERAL          ,U.S. President   ,U.S. Senate 1        ,U.S. Senate 2
STATE            ,CO Prop A
FED STATE        ,FEDERAL          ,STATE
DENVER LOCAL     ,Denver Mayor     ,Denver Clerk, Denver Prop 1, Denver Prop 2
DENVER           ,FED STATE        ,DENVER LOCAL
LOGAN REQ        ,FED STATE        ,Logan Mayor
LOGAN POSS       ,Logan Water

14-collections.csv
Collection id , Manager          , CVR type  , Required Contests, Possible Contests
DEN-A01       , abe@co.gov       , CVR       , DENVER,            DENVER
DEN-A02       , bob@co.gov       , CVR       , DENVER,            DENVER
LOG-B13       , carol@co.gov     , noCVR     , LOGAN REQ,         LOGAN POSS

The values are sanity checked, and put into the Election data structure (e)
from multi.py
"""

import os

import multi
import csv_readers
import groups
import utils

def read_election_structure(e, election_dirname):
    """
    Read file 1-structure/11-election.csv, put results into Election e.
    election_dirname is the name of the directory for the election
        (e.g. "CO-2017-11") with ELECTIONS_ROOT
    """

    election_pathname = os.path.join(multi.ELECTIONS_ROOT, election_dirname)
    structure_pathname = os.path.join(election_pathname, "1-structure")
    filename = utils.greatest_name(structure_pathname, "11-election", ".csv")
    file_pathname = os.path.join(structure_pathname, filename)
    fieldnames = ["Attribute", "Value"]
    rows = csv_readers.read_csv_file(file_pathname, fieldnames)
    for row in rows:
        if "Election name" == row["Attribute"]:
            e.election_name = row["Value"]
        elif "Election dirname" == row["Attribute"]:
            e.election_dirname = row["Value"]
        elif "Election date" == row["Attribute"]:
            e.election_date = row["Value"]
        elif "Election URL" == row["Attribute"]:
            e.election_url = row["Value"]
    for attribute in ["election_name", "election_dirname",
                      "election_date", "election_url"]:
        if attribute not in vars(e):
            utils.mywarning("Attribute {} not present in 11-election.csv."
                            .format(attribute))
    if utils.warnings_given > 0:
        utils.myerror("Too many errors; terminating.")

def test_read_election_structure(e):

    print("test_read_election_structure")
    read_election_structure(e, "ex1")

def read_contests(e):
    """
    Read file 12-contests.csv, put results into Election e.
    """

    election_pathname = os.path.join(multi.ELECTIONS_ROOT, e.election_dirname)
    structure_pathname = os.path.join(election_pathname, "1-structure")
    filename = utils.greatest_name(structure_pathname, "12-contests", ".csv")
    file_pathname = os.path.join(structure_pathname, filename)
    fieldnames = ["Contest id", "Contest type", "Winners", "Write-ins",
                  "Selections"]
    rows = csv_readers.read_csv_file(file_pathname, fieldnames, varlen=True)
    for row in rows:

        cid = row["Contest id"]

        e.cids.append(cid)

        e.contest_type_c[cid] = row["Contest type"].lower()

        e.winners_c[cid] = int(row["Winners"])

        e.write_ins_c[cid] = row["Write-ins"].lower()

        e.selids_c[cid] = {}
        for selid in row["Selections"]:
            e.selids_c[cid][selid] = True

def test_read_contests(e):

    print("test_read_contests")
    read_election_structure(e, "ex1/")
    read_contests(e)

def read_contest_groups(e):
    """
    Read file 13-contest-groups.csv, put results into Election e.
    """

    election_pathname = os.path.join(multi.ELECTIONS_ROOT, e.election_dirname)
    structure_pathname = os.path.join(election_pathname, "1-structure")
    filename = utils.greatest_name(structure_pathname, "13-contest-groups", ".csv")
    file_pathname = os.path.join(structure_pathname, filename)
    fieldnames = ["Contest group id", "Contest or group id(s)"]
    rows = csv_readers.read_csv_file(file_pathname, fieldnames, varlen=True)
    for row in rows:

        gid = row["Contest group id"]
        e.gids.append(gid)

        e.cgids_g[gid] = row["Contest or group id(s)"]

def test_read_contest_groups(e):

    print("test_read_contest_groups")
    read_contest_groups(e)
    pass

def read_collections(e):
    """
    Read file 14-collections.csv, put results into Election e.
    """

    election_pathname = os.path.join(multi.ELECTIONS_ROOT, e.election_dirname)
    structure_pathname = os.path.join(election_pathname, "1-structure")
    filename = utils.greatest_name(structure_pathname, "14-collections", ".csv")
    file_pathname = os.path.join(structure_pathname, filename)
    fieldnames = ["Collection id", "Manager", "CVR type",
                  "Required Contests", "Possible Contests"]
    rows = csv_readers.read_csv_file(file_pathname, fieldnames, varlen=False)
    for row in rows:

        pbcid = row["Collection id"]
        e.pbcids.append(pbcid)
        e.manager_p[pbcid] = row["Manager"]
        e.cvr_type_p[pbcid] = row["CVR type"]
        e.required_gid_p[pbcid] = row["Required Contests"]
        e.possible_gid_p[pbcid] = row["Possible Contests"]

def test_read_collections(e):

    print("test_read_collections")
    read_collections(e)

def get_election_structure(e):

    read_election_structure(e, e.election_dirname)
    read_contests(e)
    read_contest_groups(e)
    read_collections(e)
    finish_election_structure(e)
    check_election_structure(e)
    show_election_structure(e)

def finish_election_structure(e):

    finish_election_structure_groups(e)
    finish_election_structure_votes(e)

def finish_election_structure_groups(e):

    groups.expand_contest_group_defs(e)

    for pbcid in e.pbcids:
        e.possible_cid_p[pbcid] = {}
        e.required_cid_p[pbcid] = {}
    for cid in e.cids:
        e.possible_pbcid_c[cid] = {}
        e.required_pbcid_c[cid] = {}

    for pbcid in e.pbcids:
        req_gid = e.required_gid_p[pbcid]
        poss_gid = e.possible_gid_p[pbcid]
        for cid in e.cids:
            # "" req_gid means nothing is required.
            if req_gid!="" and cid in e.cids_g[req_gid]:
                utils.nested_set(e.required_cid_p, [pbcid, cid], "True")
                utils.nested_set(e.required_pbcid_c, [cid, pbcid], "True")
            # enforce that possible contests includes all contests
            # "" poss_gid means everything is possible
            if poss_gid=="" or cid in e.cids_g[poss_gid] or cid in e.cids_g[req_gid]:
                utils.nested_set(e.possible_cid_p, [pbcid, cid], "True")
                utils.nested_set(e.possible_pbcid_c, [cid, pbcid], "True")

def finish_election_structure_votes(e):

    noCVRvote = ("-noCVR",)
    for cid in e.cids:
        e.votes_c[cid] = {}
        for selid in e.selids_c[cid]:
            e.votes_c[cid][(selid,)] = True
    for pbcid in e.pbcids:
        if e.cvr_type_p[pbcid] == "noCVR":
            for cid in e.required_cid_p[pbcid]:
                e.votes_c[cid][noCVRvote] = True

def check_id(id, check_for_whitespace=False):

    if not isinstance(id, str) or not id.isprintable():
        utils.mywarning("id is not string or is not printable: {}".format(id))
    if check_for_whitespace:
        for c in id:
            if c.isspace():
                utils.mywarning("id `id` contains whitespace.")
                break

def check_election_structure(e):

    if not isinstance(e.cids, (list, tuple, set)):
        utils.myerror("e.cids is not a set.")
    if len(e.cids) == 0:
        utils.myerror("e.cids is an empty list of contests.")
    for cid in e.cids:
        check_id(cid)

    if not isinstance(e.pbcids, (list, tuple)):
        utils.myerror("e.pbcids is not a list or a tuple.")
    if len(e.pbcids) == 0:
        utils.myerror("e.pbcids is an empty list of pbcids.")
    for pbcid in e.pbcids:
        check_id(pbcid)

    for cid in e.selids_c:
        if cid not in e.cids:
            utils.myerror("e.selids_c has a key `{}` not in e.cids.".format(cid))
        for selid in e.selids_c[cid]:
            check_id(selid)
    for cid in e.cids:
        if cid not in e.selids_c:
            utils.mywarning("cid `{}` should be key in e.selids_c".format(cid))

    if not isinstance(e.cvr_type_p, dict):
        utils.myerror("e_cvr_type is not a dict.")
    for pbcid in e.cvr_type_p:
        if pbcid not in e.pbcids:
            utils.mywarning("pbcid `{}` is not in e.pbcids".format(pbcid))
        if e.cvr_type_p[pbcid] not in ["CVR", "noCVR"]:
            utils.mywarning("e.cvr_type_p[{}]==`{}` is not CVR or noCVR"
                      .format(pbcid, e.cvr_type_p[pbcid]))
    for pbcid in e.pbcids:
        if pbcid not in e.cvr_type_p:
            utils.mywarning("pbcid `{}` not key in e.cvr_type_p."
                      .format(pbcid))

    if utils.warnings_given > 0:
        utils.myerror("structure.check_election_structure: Too many errors or warnings; terminating.")

def show_election_structure(e):
    utils.myprint("====== Election structure ======")
    utils.myprint("Election name: (e.election_name):")
    utils.myprint("    {}".format(e.election_name))
    utils.myprint("Election dirname (e.election_dirname):")
    utils.myprint("    {}".format(e.election_dirname))
    utils.myprint("Election date (e.election date):")
    utils.myprint("    {}".format(e.election_date))
    utils.myprint("Election URL (e.election_url):")
    utils.myprint("    {}".format(e.election_url))
    utils.myprint("Number of contests:")
    utils.myprint("    {}".format(len(e.cids)))
    utils.myprint("Contest ids with contest type, number of winners, and write-ins mode")
    utils.myprint("(e.cids, e.contest_type_c, e.winners_c, e.write_ins_c):")
    for cid in e.cids:
        utils.myprint("    {} ({}, {} winner(s), {} write-ins)"
                      .format(cid, e.contest_type_c[cid], e.winners_c[cid], e.write_ins_c[cid]))
    utils.myprint("Valid selection ids for each cid (e.selids_c):")
    for cid in e.cids:
        utils.myprint("    {}: ".format(cid), end='')
        utils.myprint(", ".join(sorted(e.selids_c[cid])))
    utils.myprint("Number of paper ballot collections:")
    utils.myprint("    {}".format(len(e.pbcids)))
    utils.myprint("Paper ballot collection ids (e.pbcids), CVR types (e.cvr_type_p), and managers (e.manager_p):")
    for pbcid in sorted(e.pbcids):
        utils.myprint("    {} ({}, Manager:{})"
                      .format(pbcid, e.cvr_type_p[pbcid], e.manager_p[pbcid]))
    utils.myprint("Required pbcids for each cid (e.required_pbcid_c):")
    for cid in e.cids:
        utils.myprint("    {}: ".format(cid), end='')
        utils.myprint(", ".join(sorted(e.required_pbcid_c[cid])))
    utils.myprint("Possible pbcids for each cid (e.possible_pbcid_c):")
    for cid in e.cids:
        utils.myprint("    {}: ".format(cid), end='')
        utils.myprint(", ".join(sorted(e.possible_pbcid_c[cid])))

def test():

    e = multi.Election()
    test_read_election_structure(e)
    test_read_contests(e)
    test_read_contest_groups(e)
    test_read_collections(e)

if __name__=="__main__":

    test()
<|endoftext|>


<commit_before>import json

import requests

LEG_WITH_SOCKET = [
    132369, 132410, 137044, 132444, 132449, 132452, 132460, 133973, 133974, 137037, 137038, 137039, 137040,
    137041, 137042, 137043, 132378, 137045, 137046, 137047, 137048, 137049, 137050, 137051, 137052, 137054, 137055,
    137220, 137223, 137276, 137382, 138854
]

EQUIPPABLE_SLOTS = ["neck", "back", "finger1", "finger2", "head", "shoulder", "wrist", "chest", "legs", "hands", "waist", "trinket1", "trinket2", "feet"]
ENCHANTABLE_SLOTS = ["neck", "back", "finger1", "finger2"]
QUALITY_GEMS = [130247, 130248, 130246, 130220, 130219, 130221, 130223, 130222]
QUALITY_ENCHANTS = [5428, ]

region_locale = {
    'us': ['us', 'en_US', 'en'],
#    'kr': ['kr', 'ko_KR', 'ko'],
#    'tw': ['tw', 'zh_TW', 'zh'],
    'eu': ['eu', 'en_GB', 'en']
}

def get_sockets(player_dictionary):
    """
    Return dict with total sockets and count of equipped gems and slots that are missing

    :param player_dictionary: Retrieved player dict from API
    :return: dict()
    """
    sockets = 0
    equipped_gems = 0
    quality = 0

    for item in player_dictionary["items"]:
        if item in "averageItemLevel" or item in "averageItemLevelEquipped":
            continue

        if int(player_dictionary["items"][item]["id"]) in LEG_WITH_SOCKET:
            sockets += 1
        else:
            for bonus in player_dictionary["items"][item]["bonusLists"]:
                if bonus == 1808:  # 1808 is Legion prismatic socket bonus
                    sockets += 1

            if item in ["neck", "finger1", "finger2"]:
                if player_dictionary["items"][item]["context"] == "trade-skill":
                    sockets += 1

        for ttip in player_dictionary["items"][item]["tooltipParams"]:
            if item in "mainHand" or item in "offHand":  # Ignore Relic
                continue
            if "gem" in ttip:  # Equipped gems are listed as gem0, gem1, etc...
                equipped_gems += 1
        for gem in QUALITY_GEMS:
            if str(gem) in str(player_dictionary["items"][item]["tooltipParams"]):
                quality += 1
                continue
    non_quality = sockets - quality
    return {"total_sockets": sockets,
            "equipped_gems": equipped_gems,
            "shit_gems" : non_quality}

def get_enchants(player_dictionary):
    """
    Get count of enchants missing and slots that are missing
    :param player_dictionary:
    :return: dict()
    """
    missing_enchant_slots = []
    for slot in ENCHANTABLE_SLOTS:
        if "enchant" not in player_dictionary["items"][slot]["tooltipParams"]:
            missing_enchant_slots.append(slot)

    return {
        "enchantable_slots": len(ENCHANTABLE_SLOTS),
        "missing_slots": missing_enchant_slots,
        "total_missing": len(missing_enchant_slots)
    }

def get_legs(player_dictionary):
    leg_qty = 0
    for slot in EQUIPPABLE_SLOTS:
        if player_dictionary["items"][slot]["context"] == "":
            if slot in "averageItemLevelEquipped" and slot in "averageItemLevel":
                continue
            leg_qty +=1
    return {"total_legs": leg_qty}

def get_raid_progression(player_dictionary, raid):
    r = [x for x in player_dictionary["progression"]
    ["raids"] if x["name"] in raid][0]
    normal = 0
    heroic = 0
    mythic = 0

    for boss in r["bosses"]:
        if boss["normalKills"] > 0:
            normal += 1
        if boss["heroicKills"] > 0:
            heroic += 1
        if boss["mythicKills"] > 0:
            mythic += 1

    return {"normal": normal,
            "heroic": heroic,
            "mythic": mythic,
            "total_bosses": len(r["bosses"])}

def get_artilvl(player_dictionary):
    """obtain the lvl of players artifact"""
    achievements = player_dictionary["achievements"]
    if 29395 in achievements["criteria"]:
        index = achievements["criteria"].index(29395)
        artilvl = achievements["criteriaQuantity"][index]

    return {
	    "Artifact Level": artilvl
        }

def get_mythic_progression(player_dictionary):
    achievements = player_dictionary["achievements"]
    plus_two = 0
    plus_five = 0
    plus_ten = 0
    plus_fifteen = 0

    if 11162 in achievements["achievementsCompleted"]:
        plus_fifteen +=1

    if 33096 in achievements["criteria"]:
        index = achievements["criteria"].index(33096)
        plus_two = achievements["criteriaQuantity"][index]

    if 33097 in achievements["criteria"]:
        index = achievements["criteria"].index(33097)
        plus_five = achievements["criteriaQuantity"][index]

    if 33098 in achievements["criteria"]:
        index = achievements["criteria"].index(33098)
        plus_ten = achievements["criteriaQuantity"][index]

    if 32028 in achievements["criteria"]:
        index = achievements["criteria"].index(32028)
        plus_fifteen = achievements["criteriaQuantity"][index]

    return {
        "plus_two": plus_two,
        "plus_five": plus_five,
        "plus_ten": plus_ten,
        "plus_fifteen": plus_fifteen
    }

def get_char(name, server, target_region, api_key):
    r = requests.get("https://%s.api.battle.net/wow/character/%s/%s?fields=items+progression+achievements&locale=%s&apikey=%s" % (
            region_locale[target_region][0], server, name, region_locale[target_region][1], api_key))

    if r.status_code != 200:
        raise Exception("Could Not Find Character (No 200 from API)")

    player_dict = json.loads(r.text)

    r = requests.get(
        "https://%s.api.battle.net/wow/data/character/classes?locale=%s&apikey=%s" % (
            region_locale[target_region][0], region_locale[target_region][1], api_key))
    if r.status_code != 200:
        raise Exception("Could Not Find Character Classes (No 200 From API)")
    class_dict = json.loads(r.text)
    class_dict = {c['id']: c['name'] for c in class_dict["classes"]}
    equipped_ivl = player_dict["items"]["averageItemLevelEquipped"]
    en_CE ="N"
    guild = ""
    if int(11191) in player_dict["achievements"]["achievementsCompleted"]:
        en_CE = "Y"
    if "guild" in player_dict:
        guild = player_dict["guild"]["name"]
    sockets = get_sockets(player_dict)
    enchants = get_enchants(player_dict)
    legendaries = get_legs(player_dict)
    tov_progress = get_raid_progression(player_dict, "Trial of Valor")
    en_progress = get_raid_progression(player_dict, "The Emerald Nightmare")
    nh_progress = get_raid_progression(player_dict, "The Nighthold")
    mythic_progress = get_mythic_progression(player_dict)
    artifactlevel = get_artilvl(player_dict)

    armory_url = 'http://{}.battle.net/wow/{}/character/{}/{}/advanced'.format(
        region_locale[target_region][0], region_locale[target_region][2], server, name)

    return_string = ''
    return_string += "**%s** - **%s** - **%s %s**\n" % (
        name.title(), server.title(), player_dict['level'], class_dict[player_dict['class']])
    return_string += '<{}>\n'.format(armory_url)
    return_string += '```CSS\n'  # start Markdown

    # iLvL
    return_string += "Equipped Item Level: %s \n" % (equipped_ivl)

    #Guild
    return_string += "Guild:%s \n" % str(guild)

    # Artifact Level
    return_string += "Artifact Level: %s\n" % str(artifactlevel['Artifact Level'])

    # Mythic Progression
    return_string += "Mythics: +2: %s, +5: %s, +10: %s, +15: %s\n" % (mythic_progress["plus_two"],
                                                             mythic_progress["plus_five"],
                                                             mythic_progress["plus_ten"],
                                                             mythic_progress["plus_fifteen"])
    #EN CE
    return_string += "Emerald Nightmare Cutting Edge: [%s] \n" % (en_CE)

    # Raid Progression
    return_string += "EN: {1}/{0} (N), {2}/{0} (H), {3}/{0} (M)\n".format(en_progress["total_bosses"],
                                                                          en_progress["normal"],
                                                                          en_progress["heroic"],
                                                                          en_progress["mythic"])

    return_string += "TOV: {1}/{0} (N), {2}/{0} (H), {3}/{0} (M)\n".format(tov_progress["total_bosses"],
                                                                           tov_progress["normal"],
                                                                           tov_progress["heroic"],
                                                                           tov_progress["mythic"])
    return_string += "NH: {1}/{0} (N), {2}/{0} (H), {3}/{0} (M)\n".format(nh_progress["total_bosses"],
                                                                           nh_progress["normal"],
                                                                           nh_progress["heroic"],
                                                                           nh_progress["mythic"])

    # Gems
    return_string += "Gems Equipped: %s/%s. %s gem/s are bad\n" % (
        sockets["equipped_gems"], sockets["total_sockets"], sockets["shit_gems"])

    # Legendaries
    return_string += "Legendaries: %s/2\n" % (
        legendaries["total_legs"])

    # Enchants
    return_string += "Enchants: %s/%s\n" % (enchants["enchantable_slots"] - enchants["total_missing"],
                                            enchants["enchantable_slots"])
    if enchants["total_missing"] > 0:
        return_string += "Missing Enchants: {0}".format(
            ", ".join(enchants["missing_slots"]))

    return_string += '```'  # end Markdown
    return_string += 'Made by Reznok, Edited by Bsep#0415, Managed by NiczeAlind'
    return return_string

async def pug(client, region, api_key, message):
    target_region = region
    try:
        i = str(message.content).split(' ')
        name = i[1]
        server = i[2]
        if len(i) == 4 and i[3].lower() in region_locale.keys():
            target_region = i[3].lower()
        character_info = get_char(name, server, target_region, api_key)
        await client.send_message(message.channel, character_info)
    except Exception as e:
        print(e)
        await client.send_message(message.channel, "Error With Name or Server\n"
                                                   "Use: !pug <name> <server> <region>\n"
                                                   "Hyphenate Two Word Servers (Ex: Twisting-Nether)")
<commit_msg>Update pug.py<commit_after>import json

import requests

LEG_WITH_SOCKET = [
    132369, 132410, 137044, 132444, 132449, 132452, 132460, 133973, 133974, 137037, 137038, 137039, 137040,
    137041, 137042, 137043, 132378, 137045, 137046, 137047, 137048, 137049, 137050, 137051, 137052, 137054, 137055,
    137220, 137223, 137276, 137382, 138854
]

EQUIPPABLE_SLOTS = ["neck", "back", "finger1", "finger2", "head", "shoulder", "wrist", "chest", "legs", "hands", "waist", "trinket1", "trinket2", "feet"]
ENCHANTABLE_SLOTS = ["neck", "back", "finger1", "finger2"]
QUALITY_GEMS = [130247, 130248, 130246, 130220, 130219, 130221, 130223, 130222]
QUALITY_ENCHANTS = [5428, ]

region_locale = {
    'us': ['us', 'en_US', 'en'],
#    'kr': ['kr', 'ko_KR', 'ko'],
#    'tw': ['tw', 'zh_TW', 'zh'],
    'eu': ['eu', 'en_GB', 'en']
}

def get_sockets(player_dictionary):
    """
    Return dict with total sockets and count of equipped gems and slots that are missing

    :param player_dictionary: Retrieved player dict from API
    :return: dict()
    """
    sockets = 0
    equipped_gems = 0
    quality = 0

    for item in player_dictionary["items"]:
        if item in "averageItemLevel" or item in "averageItemLevelEquipped":
            continue

        if int(player_dictionary["items"][item]["id"]) in LEG_WITH_SOCKET:
            sockets += 1
        else:
            for bonus in player_dictionary["items"][item]["bonusLists"]:
                if bonus == 1808:  # 1808 is Legion prismatic socket bonus
                    sockets += 1

            if item in ["neck", "finger1", "finger2"]:
                if player_dictionary["items"][item]["context"] == "trade-skill":
                    sockets += 1

        for ttip in player_dictionary["items"][item]["tooltipParams"]:
            if item in "mainHand" or item in "offHand":  # Ignore Relic
                continue
            if "gem" in ttip:  # Equipped gems are listed as gem0, gem1, etc...
                equipped_gems += 1
        for gem in QUALITY_GEMS:
            if str(gem) in str(player_dictionary["items"][item]["tooltipParams"]):
                quality += 1
                continue
    non_quality = sockets - quality
    return {"total_sockets": sockets,
            "equipped_gems": equipped_gems,
            "shit_gems" : non_quality}

def get_enchants(player_dictionary):
    """
    Get count of enchants missing and slots that are missing
    :param player_dictionary:
    :return: dict()
    """
    missing_enchant_slots = []
    for slot in ENCHANTABLE_SLOTS:
        if "enchant" not in player_dictionary["items"][slot]["tooltipParams"]:
            missing_enchant_slots.append(slot)

    return {
        "enchantable_slots": len(ENCHANTABLE_SLOTS),
        "missing_slots": missing_enchant_slots,
        "total_missing": len(missing_enchant_slots)
    }

def get_legs(player_dictionary):
    leg_qty = 0
    for slot in EQUIPPABLE_SLOTS:
        if player_dictionary["items"][slot]["context"] == "":
            if slot in "averageItemLevelEquipped" and slot in "averageItemLevel":
                continue
            leg_qty +=1
    return {"total_legs": leg_qty}

def get_raid_progression(player_dictionary, raid):
    r = [x for x in player_dictionary["progression"]
    ["raids"] if x["name"] in raid][0]
    normal = 0
    heroic = 0
    mythic = 0

    for boss in r["bosses"]:
        if boss["normalKills"] > 0:
            normal += 1
        if boss["heroicKills"] > 0:
            heroic += 1
        if boss["mythicKills"] > 0:
            mythic += 1

    return {"normal": normal,
            "heroic": heroic,
            "mythic": mythic,
            "total_bosses": len(r["bosses"])}

def get_artilvl(player_dictionary):
    """obtain the lvl of players artifact"""
    achievements = player_dictionary["achievements"]
    if 29395 in achievements["criteria"]:
        index = achievements["criteria"].index(29395)
        artilvl = achievements["criteriaQuantity"][index]

    return {
	    "Artifact Level": artilvl
        }

def get_mythic_progression(player_dictionary):
    achievements = player_dictionary["achievements"]
    plus_two = 0
    plus_five = 0
    plus_ten = 0
    plus_fifteen = 0

    if 11162 in achievements["achievementsCompleted"]:
        plus_fifteen +=1

    if 33096 in achievements["criteria"]:
        index = achievements["criteria"].index(33096)
        plus_two = achievements["criteriaQuantity"][index]

    if 33097 in achievements["criteria"]:
        index = achievements["criteria"].index(33097)
        plus_five = achievements["criteriaQuantity"][index]

    if 33098 in achievements["criteria"]:
        index = achievements["criteria"].index(33098)
        plus_ten = achievements["criteriaQuantity"][index]

    if 32028 in achievements["criteria"]:
        index = achievements["criteria"].index(32028)
        plus_fifteen = achievements["criteriaQuantity"][index]

    return {
        "plus_two": plus_two,
        "plus_five": plus_five,
        "plus_ten": plus_ten,
        "plus_fifteen": plus_fifteen
    }

def get_char(name, server, target_region, api_key):
    r = requests.get("https://%s.api.battle.net/wow/character/%s/%s?fields=items+progression+guild+achievements&locale=%s&apikey=%s" % (
            region_locale[target_region][0], server, name, region_locale[target_region][1], API_KEY))
    l = requests.get("https://%s.api.battle.net/wow/character/%s/%s?fields=items+progression+achievements&locale=%s&apikey=%s" % (
            region_locale[target_region][0], server, name, region_locale[target_region][1], API_KEY))
    t = requests.get("https://%s.api.battle.net/wow/character/%s/%s?fields=guild&locale=%s&apikey=%s" % (
            region_locale[target_region][0], server, name, region_locale[target_region][1], API_KEY))

    if r.status_code != 200:
        raise Exception("Could Not Find Character (No 200 from API)")

    player_dict = json.loads(r.text)

    r = requests.get(
        "https://%s.api.battle.net/wow/data/character/classes?locale=%s&apikey=%s" % (
            region_locale[target_region][0], region_locale[target_region][1], api_key))
    if r.status_code != 200:
        raise Exception("Could Not Find Character Classes (No 200 From API)")
    class_dict = json.loads(r.text)
    class_dict = {c['id']: c['name'] for c in class_dict["classes"]}
    equipped_ivl = player_dict["items"]["averageItemLevelEquipped"]
    en_CE ="N"
    guild = ""
    if int(11191) in player_dict["achievements"]["achievementsCompleted"]:
        en_CE = "Y"
    if "guild" in player_dict:
        guild = player_dict["guild"]["name"]
    sockets = get_sockets(player_dict)
    enchants = get_enchants(player_dict)
    legendaries = get_legs(player_dict)
    tov_progress = get_raid_progression(player_dict, "Trial of Valor")
    en_progress = get_raid_progression(player_dict, "The Emerald Nightmare")
    nh_progress = get_raid_progression(player_dict, "The Nighthold")
    mythic_progress = get_mythic_progression(player_dict)
    artifactlevel = get_artilvl(player_dict)

    armory_url = 'http://{}.battle.net/wow/{}/character/{}/{}/advanced'.format(
        region_locale[target_region][0], region_locale[target_region][2], server, name)

    return_string = ''
    return_string += "**%s** - **%s** - **%s %s**\n" % (
        name.title(), server.title(), player_dict['level'], class_dict[player_dict['class']])
    return_string += '<{}>\n'.format(armory_url)
    return_string += '```CSS\n'  # start Markdown

    # iLvL
    return_string += "Equipped Item Level: %s \n" % (equipped_ivl)

    #Guild
    return_string += "Guild:%s \n" % str(guild)

    # Artifact Level
    return_string += "Artifact Level: %s\n" % str(artifactlevel['Artifact Level'])

    # Mythic Progression
    return_string += "Mythics: +2: %s, +5: %s, +10: %s, +15: %s\n" % (mythic_progress["plus_two"],
                                                             mythic_progress["plus_five"],
                                                             mythic_progress["plus_ten"],
                                                             mythic_progress["plus_fifteen"])
    #EN CE
    return_string += "Emerald Nightmare Cutting Edge: [%s] \n" % (en_CE)

    # Raid Progression
    return_string += "EN: {1}/{0} (N), {2}/{0} (H), {3}/{0} (M)\n".format(en_progress["total_bosses"],
                                                                          en_progress["normal"],
                                                                          en_progress["heroic"],
                                                                          en_progress["mythic"])

    return_string += "TOV: {1}/{0} (N), {2}/{0} (H), {3}/{0} (M)\n".format(tov_progress["total_bosses"],
                                                                           tov_progress["normal"],
                                                                           tov_progress["heroic"],
                                                                           tov_progress["mythic"])
    return_string += "NH: {1}/{0} (N), {2}/{0} (H), {3}/{0} (M)\n".format(nh_progress["total_bosses"],
                                                                           nh_progress["normal"],
                                                                           nh_progress["heroic"],
                                                                           nh_progress["mythic"])

    # Gems
    return_string += "Gems Equipped: %s/%s. %s gem/s are bad\n" % (
        sockets["equipped_gems"], sockets["total_sockets"], sockets["shit_gems"])

    # Legendaries
    return_string += "Legendaries: %s/2\n" % (
        legendaries["total_legs"])

    # Enchants
    return_string += "Enchants: %s/%s\n" % (enchants["enchantable_slots"] - enchants["total_missing"],
                                            enchants["enchantable_slots"])
    if enchants["total_missing"] > 0:
        return_string += "Missing Enchants: {0}".format(
            ", ".join(enchants["missing_slots"]))

    return_string += '```'  # end Markdown
    return_string += 'Made by Reznok, Edited by Bsep#0415, Managed by NiczeAlind'
    return return_string

async def pug(client, region, api_key, message):
    target_region = region
    try:
        i = str(message.content).split(' ')
        name = i[1]
        server = i[2]
        if len(i) == 4 and i[3].lower() in region_locale.keys():
            target_region = i[3].lower()
        character_info = get_char(name, server, target_region, api_key)
        await client.send_message(message.channel, character_info)
    except Exception as e:
        print(e)
        await client.send_message(message.channel, "Error With Name or Server\n"
                                                   "Use: !pug <name> <server> <region>\n"
                                                   "Hyphenate Two Word Servers (Ex: Twisting-Nether)")
<|endoftext|>


<commit_before>from itertools import chain

from django.db import models
from django.utils.translation import ugettext_lazy
from django.utils.translation import ugettext as _
from django.core.exceptions import ObjectDoesNotExist

class MenuItem(models.Model):
    parent = models.ForeignKey('self', verbose_name=ugettext_lazy('Parent'), null=True, blank=True)
    caption = models.CharField(ugettext_lazy('Caption'), max_length=50)
    url = models.CharField(ugettext_lazy('URL'), max_length=200, blank=True)
    named_url = models.CharField(ugettext_lazy('Named URL'), max_length=200, blank=True)
    rank = models.IntegerField(ugettext_lazy('Rank'), default=0, editable=False)
    menu = models.ForeignKey('Menu', related_name='contained_items', verbose_name=ugettext_lazy('Menu'), null=True, blank=True, editable=False)

    def __unicode__(self):
        return self.caption

    def save(self, force_insert=False, force_update=False):
        from treemenus.utils import clean_ranks

        if self.pk:
            new_parent = self.parent
            old_parent = MenuItem.objects.get(pk=self.pk).parent
            if old_parent != new_parent:
                #If so, we need to recalculate the new ranks for the item and its siblings (both old and new ones).
                if new_parent:
                    clean_ranks(new_parent.children()) # Clean ranks for new siblings
                    self.rank = new_parent.children().count()
                super(MenuItem, self).save(force_insert, force_update) # Save menu item in DB. It has now officially changed parent.
                if old_parent:
                    clean_ranks(old_parent.children()) # Clean ranks for old siblings
            else:
                super(MenuItem, self).save(force_insert, force_update) # Save menu item in DB

        else: # Saving the menu item for the first time (i.e creating the object)
            if not self.has_siblings():
                # No siblings - initial rank is 0.
                self.rank = 0
            else:
                # Has siblings - initial rank is highest sibling rank plus 1.
                siblings = self.siblings().order_by('-rank')
                self.rank = siblings[0].rank + 1
            super(MenuItem, self).save(force_insert, force_update)

    def delete(self):
        old_parent = self.parent
        super(MenuItem, self).delete()
        if old_parent:
            clean_ranks(old_parent.children())

    def caption_with_spacer(self):
        spacer = ''
        for i in range(0, self.level):
            spacer += u'     '
        if self.level > 0:
            spacer += u'|- '
        return spacer + self.caption

    def get_flattened(self):
        flat_structure = [self]
        for child in self.children():
            flat_structure = chain(flat_structure, child.get_flattened())
        return flat_structure

    def siblings(self):
        if not self.parent:
            return MenuItem.objects.none()
        else:
            if not self.pk: # If menu item not yet been saved in DB (i.e does not have a pk yet)
                return self.parent.children()
            else:
                return self.parent.children().exclude(pk=self.pk)

    def hasSiblings(self):
        import warnings
        warnings.warn('hasSiblings() is deprecated, use has_siblings() instead.', DeprecationWarning, stacklevel=2)
        return self.has_siblings()

    def has_siblings(self):
        return self.siblings().count() > 0

    def children(self):
        _children = MenuItem.objects.filter(parent=self).order_by('rank',)
        for child in _children:
            child.parent = self # Hack to not run a query in _level() each time
        return _children

    def hasChildren(self):
        import warnings
        warnings.warn('hasChildren() is deprecated, use has_children() instead.', DeprecationWarning, stacklevel=2)
        return self.has_children()

    def has_children(self):
        return self.children().count() > 0

    def _level(self):
        if not hasattr(self, '__level'):
            if self.parent:
                self.__level = self.parent.level + 1
            else:
                self.__level = 0
        return self.__level
    level = property(_level)

    def _bobo(self):
        if self.parent:
            return self.parent.bobo + 1
        else:
            return 0
    bobo = property(_bobo)

class Menu(models.Model):
    name = models.CharField(ugettext_lazy('Name'), max_length=50)
    root_item = models.ForeignKey(MenuItem, related_name='is_root_item_of', verbose_name=ugettext_lazy('Root Item'), null=True, blank=True, editable=False)
    def save(self, force_insert=False, force_update=False):
        if not self.root_item:
            root_item = MenuItem()
            root_item.caption = _('Root')
            if not self.pk: # If creating a new object (i.e does not have a pk yet)
                super(Menu, self).save() # Save, so that it gets a pk
            root_item.menu = self
            root_item.save() # Save, so that it gets a pk
            self.root_item = root_item
        super(Menu, self).save(force_insert, force_update)

    def delete(self):
        if self.root_item is not None:
            self.root_item.delete()
        super(Menu, self).delete()

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name = _('Menu')
        verbose_name_plural = _('Menus')
<commit_msg>Fixed level issues. Restored the use of level field, as this denormalization brings better performance, and that way it is also backwards-compatible with 0.6.<commit_after>from itertools import chain

from django.db import models
from django.utils.translation import ugettext_lazy
from django.utils.translation import ugettext as _
from django.core.exceptions import ObjectDoesNotExist

class MenuItem(models.Model):
    parent = models.ForeignKey('self', verbose_name=ugettext_lazy('Parent'), null=True, blank=True)
    caption = models.CharField(ugettext_lazy('Caption'), max_length=50)
    url = models.CharField(ugettext_lazy('URL'), max_length=200, blank=True)
    named_url = models.CharField(ugettext_lazy('Named URL'), max_length=200, blank=True)
    level = models.IntegerField(ugettext_lazy('Level'), default=0, editable=False)
    rank = models.IntegerField(ugettext_lazy('Rank'), default=0, editable=False)
    menu = models.ForeignKey('Menu', related_name='contained_items', verbose_name=ugettext_lazy('Menu'), null=True, blank=True, editable=False)

    def __unicode__(self):
        return self.caption

    def save(self, force_insert=False, force_update=False):
        from treemenus.utils import clean_ranks

        # Calculate level
        old_level = self.level
        if self.parent:
            self.level = self.parent.level + 1
        else:
            self.level = 0

        if self.pk:
            new_parent = self.parent
            old_parent = MenuItem.objects.get(pk=self.pk).parent
            if old_parent != new_parent:
                #If so, we need to recalculate the new ranks for the item and its siblings (both old and new ones).
                if new_parent:
                    clean_ranks(new_parent.children()) # Clean ranks for new siblings
                    self.rank = new_parent.children().count()
                super(MenuItem, self).save(force_insert, force_update) # Save menu item in DB. It has now officially changed parent.
                if old_parent:
                    clean_ranks(old_parent.children()) # Clean ranks for old siblings
            else:
                super(MenuItem, self).save(force_insert, force_update) # Save menu item in DB

        else: # Saving the menu item for the first time (i.e creating the object)
            if not self.has_siblings():
                # No siblings - initial rank is 0.
                self.rank = 0
            else:
                # Has siblings - initial rank is highest sibling rank plus 1.
                siblings = self.siblings().order_by('-rank')
                self.rank = siblings[0].rank + 1
            super(MenuItem, self).save(force_insert, force_update)

        # If level has changed, force children to refresh their own level
        if old_level != self.level:
            for child in self.children():
                child.save() # Just saving is enough, it'll refresh its level correctly.

    def delete(self):
        old_parent = self.parent
        super(MenuItem, self).delete()
        if old_parent:
            clean_ranks(old_parent.children())

    def caption_with_spacer(self):
        spacer = ''
        for i in range(0, self.level):
            spacer += u'     '
        if self.level > 0:
            spacer += u'|- '
        return spacer + self.caption

    def get_flattened(self):
        flat_structure = [self]
        for child in self.children():
            flat_structure = chain(flat_structure, child.get_flattened())
        return flat_structure

    def siblings(self):
        if not self.parent:
            return MenuItem.objects.none()
        else:
            if not self.pk: # If menu item not yet been saved in DB (i.e does not have a pk yet)
                return self.parent.children()
            else:
                return self.parent.children().exclude(pk=self.pk)

    def hasSiblings(self):
        import warnings
        warnings.warn('hasSiblings() is deprecated, use has_siblings() instead.', DeprecationWarning, stacklevel=2)
        return self.has_siblings()

    def has_siblings(self):
        return self.siblings().count() > 0

    def children(self):
        _children = MenuItem.objects.filter(parent=self).order_by('rank',)
        for child in _children:
            child.parent = self # Hack to avoid unnecessary DB queries further down the track.
        return _children

    def hasChildren(self):
        import warnings
        warnings.warn('hasChildren() is deprecated, use has_children() instead.', DeprecationWarning, stacklevel=2)
        return self.has_children()

    def has_children(self):
        return self.children().count() > 0

class Menu(models.Model):
    name = models.CharField(ugettext_lazy('Name'), max_length=50)
    root_item = models.ForeignKey(MenuItem, related_name='is_root_item_of', verbose_name=ugettext_lazy('Root Item'), null=True, blank=True, editable=False)
    def save(self, force_insert=False, force_update=False):
        if not self.root_item:
            root_item = MenuItem()
            root_item.caption = _('Root')
            if not self.pk: # If creating a new object (i.e does not have a pk yet)
                super(Menu, self).save() # Save, so that it gets a pk
            root_item.menu = self
            root_item.save() # Save, so that it gets a pk
            self.root_item = root_item
        super(Menu, self).save(force_insert, force_update)

    def delete(self):
        if self.root_item is not None:
            self.root_item.delete()
        super(Menu, self).delete()

    def __unicode__(self):
        return self.name

    class Meta:
        verbose_name = _('Menu')
        verbose_name_plural = _('Menus')
<|endoftext|>


<commit_before>#!/usr/bin/env python
# -*- coding:utf-8 -*-

import netrc
import tempfile
import subprocess
import datetime
import os
import re
from attributes import TPHAttributes
from edit import edit

class TPH(object):
    def __init__(self, server):
        self.server = server
        self.attrs = TPHAttributes(
            (
                'summary',
                'component',
                'estimatedhours',
                'keywords',
                'cc',
                'milestone',
                'owner',
                'parents',
                'blockedby',
                'blocking',
                'priority',
                'reporter',
                'resolution',
                'startdate',
                'status',
                'type',
            )
        )
        self.template_attributes = {}

    def edit_comment(self, comment="", info="", prefix=""):
        info_string = "\n".join([
            "# " + line for line in info.splitlines()
        ])
        info_string = "\n# lines beginning with # are avoided\n" + info_string
        comment = edit(comment + info_string, prefix=prefix)
        if not comment:
            return None
        # remove the lines beginning with #
        new_comment_lines = [
            line for line in comment.splitlines()
            if not re.search("^#", line)
        ]
        return "\n".join(new_comment_lines)

    def ticket_create(self, p_attributes, use_editor=False):
        attributes = self.template_attributes.copy()
        attributes.update(p_attributes)
        summary = attributes.get("summary","Summary")
        description = attributes.get("description","Description\n")
        if use_editor:
            attributes["summary"] = summary
            attributes["description"] = description
            attributes = self.attrs.edit(attributes)
            if attributes == None:
                return None

        return self.server.ticket.create(
            summary,
            description,
            attributes)

    def ticket_clone(self, ticket_number, attributes, use_editor=False, reporter=""):
        # get the ticket to clone
        ticket = self.ticket_get(ticket_number)
        ticket_attributes = ticket[3]
        # remove what is not relevant in the ticket
        for key in ("changetime", "_ts", "time",):
            del ticket_attributes[key]
            # addition of the overriding attributes
            ticket_attributes.update(attributes)
            # I am the new reporter of the ticket
            ticket_attributes["reporter"] = reporter
            #self.pp.pprint(ticket_attributes)

        return self.ticket_create(ticket_attributes, use_editor)

    def ticket_son_create(self, ticket_number, attributes, use_editor=False, reporter=""):
        # get the ticket to clone
        ticket = self.ticket_get(ticket_number)
        ticket_old_attributes = ticket[3]
        # get only the relevant info to copy from the parent ticket
        ticket_attributes = {
            "cc" : ticket_old_attributes["cc"],
            "component" : ticket_old_attributes["component"],
            "estimatedhours" : ticket_old_attributes["estimatedhours"],
            "keywords" : "",    # new set of keywords
            "milestone" : ticket_old_attributes["milestone"],
            "owner" : ticket_old_attributes["owner"],
            "parents" : "#" + str(ticket[0]),
            "priority" : ticket_old_attributes["priority"],
            "reporter" : reporter,
            "summary" : ticket_old_attributes["summary"] + " - Sub ticket",
            "status" : "new",
            "description" : ticket_old_attributes["description"],
            'type': 'Defect',
        }
        ticket_attributes.update(attributes)
        return self.ticket_create(ticket_attributes, use_editor)

    def ticket_batch_set(self, id_list, attributes):
        for id in id_list:
            self.server.ticket.update(int(id),
                                      "",
                                      attributes
                                  )

    def ticket_sibling_create(self, ticket_number, attributes, use_editor=False, reporter=""):
        # get the ticket to clone
        ticket = self.ticket_get(ticket_number)
        ticket_old_attributes = ticket[3]
        # get only the relevant info to copy from the sibling ticket
        ticket_attributes = {
            "cc" : ticket_old_attributes["cc"],
            "component" : ticket_old_attributes["component"],
            "estimatedhours" : ticket_old_attributes["estimatedhours"],
            "keywords" : ticket_old_attributes["keywords"],
            "milestone" : ticket_old_attributes["milestone"],
            "owner" : ticket_old_attributes["owner"],
            "parents" : ticket_old_attributes["parents"],
            "priority" : ticket_old_attributes["priority"],
            "reporter" : reporter,
            "summary" : ticket_old_attributes["summary"] + " - Sibling",
            "status" : "new",
            "description" : ticket_old_attributes["description"],
            'type': ticket_old_attributes["type"],
        }
        # update the attributes with the ones provided in argument
        ticket_attributes.update(attributes)
        return self.ticket_create(ticket_attributes, use_editor)

    def ticket_get(self, ticket):
        return self.server.ticket.get(ticket)

    def ticket_close(self, ticket):
        content = edit("fixed\n\nComment")
        if content == "":
            return False
        content_split = content.splitlines()
        resolution = content_split[0]
        comment = "\n".join(content_split[2:])
        self.server.ticket.update(ticket,
                                  comment,
                                  {
                                      'action':'resolve',
                                      'action_resolve_resolve_resolution': resolution,
                                  }
                              )
        return True

    def ticket_edit(self, ticket_number):
        ticket = self.ticket_get(ticket_number)
        attributes = ticket[3]
        attributes = self.attrs.edit(attributes, str(ticket_number))
        if attributes:
            attributes_string = self.attrs.dump(attributes)
            comment = self.edit_comment(info=attributes_string, prefix=str(ticket_number))
            if comment is None:
                return False
            self.server.ticket.update(
                ticket[0],
                comment,
                attributes
            )
            return True
        else:
            return False

    def ticket_accept(self, ticket_number, owner):
        ticket = self.ticket_get(ticket_number)
        attributes = ticket[3]
        attributes["status"] = "accepted"
        attributes["owner"] = owner
        attributes = self.attrs.edit(attributes, str(ticket_number))
        if attributes:
            attributes_string = self.attrs.dump(attributes)
            comment = self.edit_comment(
                comment="Accepted ticket",
                info=attributes_string,
                prefix="accept_"+str(ticket_number))
            if comment is None:
                return False
            self.server.ticket.update(
                ticket[0],
                comment,
                attributes
            )
            return True
        else:
            return False

    def ticket_sons(self, ticket_number):
        return self.server.ticket.query("parents=~%s" % (ticket_number))

    def ticket_parents(self, ticket_number):
        return self.ticket_get(ticket_number)[3]["parents"]

    def ticket_remaining_time(self, ticket_number):
        ticket = self.ticket_get(ticket_number)
        attributes = ticket[3]
        if attributes["estimatedhours"]:
            hours = int(attributes["estimatedhours"])
        else:
            hours = 0
        return hours

    def ticket_remaining_time_sum(self, ticket_number):
        time = self.ticket_remaining_time(ticket_number)
        for child in self.ticket_sons(ticket_number):
            time += self.ticket_remaining_time_sum(child)
        return time

    def ticket_query_time_sum(self, query):
        tickets = self.server.ticket.query(query)
        time = 0
        for ticket in tickets:
            time += self.ticket_remaining_time(ticket)

        return time

    def ticket_batch_edit(self, id_list):
        for id in id_list:
            ticket = self.ticket_get(id)
            attributes = self.attrs.edit(ticket[3])
            self.server.ticket.update(int(id),
                                      "",
                                      attributes
                                  )

    def ticket_changelog(self, ticket, filter=lambda x:True):
        cl = self.server.ticket.changeLog(ticket)
        return [[ticket] + l for l in cl if filter([ticket] + l)]

    def ticket_recent_changes(self, since, filter=lambda x:True):
        tickets = self.server.ticket.getRecentChanges(since)
        created_tickets = self.server.ticket.query(
            "created=%s.." % (since.strftime("%m/%d/%y"))
        )
        new_filter = lambda log:filter(log) and since <= log[1]
        # from the created tickets. Retrieve only those that have been created
        # after since
        created_tickets_changelogs = []
        for ticket_number in created_tickets:
            ticket = self.ticket_get(ticket_number)
            ticket_log = [ticket_number, ticket[1], ticket[3]["reporter"], "created", "", "", ""]
            if new_filter(ticket_log):
                created_tickets_changelogs.append(
                    ticket_log
                )

        changelogs = []
        for ticket in tickets:
            changelogs = changelogs + self.ticket_changelog(ticket, new_filter)
        return created_tickets_changelogs + changelogs

    def template_edit(self):
        attributes = \
                     self.attrs.edit(self.template_attributes)
        if attributes:
            self.template_attributes = attributes
            return True
        else:
            return False

    def template_save(self, file_name):
        assert not os.path.exists(file_name)
        with open(file_name, "w") as file:
            file.write(self.attrs.dump(self.template_attributes))

        return True

    def template_open(self, file_name):
        assert os.path.exists(file_name)
        with open(file_name, "r") as file:
            self.template_attributes = self.attrs.load(file.read())

        return True

    def list_components(self, filter):
        return [
            comp for comp in self.server.ticket.component.getAll()
            if filter(comp)
        ]

    def milestone_edit(self, milestone_name):
        milestone = self.server.ticket.milestone.get(milestone_name)
        desc = milestone["description"]
        new_desc = edit(desc, prefix=milestone_name.replace(" ", "_"))
        if new_desc:
            self.server.ticket.milestone.update(
                milestone_name,
                {
                    "description":new_desc,
                },
            )
            return True

        return False

    def milestone_list(self):
        return self.server.ticket.milestone.getAll()
<commit_msg>Erase the template file if asked to edit into an existing file<commit_after>#!/usr/bin/env python
# -*- coding:utf-8 -*-

import netrc
import tempfile
import subprocess
import datetime
import os
import re
from attributes import TPHAttributes
from edit import edit

class TPH(object):
    def __init__(self, server):
        self.server = server
        self.attrs = TPHAttributes(
            (
                'summary',
                'component',
                'estimatedhours',
                'keywords',
                'cc',
                'milestone',
                'owner',
                'parents',
                'blockedby',
                'blocking',
                'priority',
                'reporter',
                'resolution',
                'startdate',
                'status',
                'type',
            )
        )
        self.template_attributes = {}

    def edit_comment(self, comment="", info="", prefix=""):
        info_string = "\n".join([
            "# " + line for line in info.splitlines()
        ])
        info_string = "\n# lines beginning with # are avoided\n" + info_string
        comment = edit(comment + info_string, prefix=prefix)
        if not comment:
            return None
        # remove the lines beginning with #
        new_comment_lines = [
            line for line in comment.splitlines()
            if not re.search("^#", line)
        ]
        return "\n".join(new_comment_lines)

    def ticket_create(self, p_attributes, use_editor=False):
        attributes = self.template_attributes.copy()
        attributes.update(p_attributes)
        summary = attributes.get("summary","Summary")
        description = attributes.get("description","Description\n")
        if use_editor:
            attributes["summary"] = summary
            attributes["description"] = description
            attributes = self.attrs.edit(attributes)
            if attributes == None:
                return None

        return self.server.ticket.create(
            summary,
            description,
            attributes)

    def ticket_clone(self, ticket_number, attributes, use_editor=False, reporter=""):
        # get the ticket to clone
        ticket = self.ticket_get(ticket_number)
        ticket_attributes = ticket[3]
        # remove what is not relevant in the ticket
        for key in ("changetime", "_ts", "time",):
            del ticket_attributes[key]
            # addition of the overriding attributes
            ticket_attributes.update(attributes)
            # I am the new reporter of the ticket
            ticket_attributes["reporter"] = reporter
            #self.pp.pprint(ticket_attributes)

        return self.ticket_create(ticket_attributes, use_editor)

    def ticket_son_create(self, ticket_number, attributes, use_editor=False, reporter=""):
        # get the ticket to clone
        ticket = self.ticket_get(ticket_number)
        ticket_old_attributes = ticket[3]
        # get only the relevant info to copy from the parent ticket
        ticket_attributes = {
            "cc" : ticket_old_attributes["cc"],
            "component" : ticket_old_attributes["component"],
            "estimatedhours" : ticket_old_attributes["estimatedhours"],
            "keywords" : "",    # new set of keywords
            "milestone" : ticket_old_attributes["milestone"],
            "owner" : ticket_old_attributes["owner"],
            "parents" : "#" + str(ticket[0]),
            "priority" : ticket_old_attributes["priority"],
            "reporter" : reporter,
            "summary" : ticket_old_attributes["summary"] + " - Sub ticket",
            "status" : "new",
            "description" : ticket_old_attributes["description"],
            'type': 'Defect',
        }
        ticket_attributes.update(attributes)
        return self.ticket_create(ticket_attributes, use_editor)

    def ticket_batch_set(self, id_list, attributes):
        for id in id_list:
            self.server.ticket.update(int(id),
                                      "",
                                      attributes
                                  )

    def ticket_sibling_create(self, ticket_number, attributes, use_editor=False, reporter=""):
        # get the ticket to clone
        ticket = self.ticket_get(ticket_number)
        ticket_old_attributes = ticket[3]
        # get only the relevant info to copy from the sibling ticket
        ticket_attributes = {
            "cc" : ticket_old_attributes["cc"],
            "component" : ticket_old_attributes["component"],
            "estimatedhours" : ticket_old_attributes["estimatedhours"],
            "keywords" : ticket_old_attributes["keywords"],
            "milestone" : ticket_old_attributes["milestone"],
            "owner" : ticket_old_attributes["owner"],
            "parents" : ticket_old_attributes["parents"],
            "priority" : ticket_old_attributes["priority"],
            "reporter" : reporter,
            "summary" : ticket_old_attributes["summary"] + " - Sibling",
            "status" : "new",
            "description" : ticket_old_attributes["description"],
            'type': ticket_old_attributes["type"],
        }
        # update the attributes with the ones provided in argument
        ticket_attributes.update(attributes)
        return self.ticket_create(ticket_attributes, use_editor)

    def ticket_get(self, ticket):
        return self.server.ticket.get(ticket)

    def ticket_close(self, ticket):
        content = edit("fixed\n\nComment")
        if content == "":
            return False
        content_split = content.splitlines()
        resolution = content_split[0]
        comment = "\n".join(content_split[2:])
        self.server.ticket.update(ticket,
                                  comment,
                                  {
                                      'action':'resolve',
                                      'action_resolve_resolve_resolution': resolution,
                                  }
                              )
        return True

    def ticket_edit(self, ticket_number):
        ticket = self.ticket_get(ticket_number)
        attributes = ticket[3]
        attributes = self.attrs.edit(attributes, str(ticket_number))
        if attributes:
            attributes_string = self.attrs.dump(attributes)
            comment = self.edit_comment(info=attributes_string, prefix=str(ticket_number))
            if comment is None:
                return False
            self.server.ticket.update(
                ticket[0],
                comment,
                attributes
            )
            return True
        else:
            return False

    def ticket_accept(self, ticket_number, owner):
        ticket = self.ticket_get(ticket_number)
        attributes = ticket[3]
        attributes["status"] = "accepted"
        attributes["owner"] = owner
        attributes = self.attrs.edit(attributes, str(ticket_number))
        if attributes:
            attributes_string = self.attrs.dump(attributes)
            comment = self.edit_comment(
                comment="Accepted ticket",
                info=attributes_string,
                prefix="accept_"+str(ticket_number))
            if comment is None:
                return False
            self.server.ticket.update(
                ticket[0],
                comment,
                attributes
            )
            return True
        else:
            return False

    def ticket_sons(self, ticket_number):
        return self.server.ticket.query("parents=~%s" % (ticket_number))

    def ticket_parents(self, ticket_number):
        return self.ticket_get(ticket_number)[3]["parents"]

    def ticket_remaining_time(self, ticket_number):
        ticket = self.ticket_get(ticket_number)
        attributes = ticket[3]
        if attributes["estimatedhours"]:
            hours = int(attributes["estimatedhours"])
        else:
            hours = 0
        return hours

    def ticket_remaining_time_sum(self, ticket_number):
        time = self.ticket_remaining_time(ticket_number)
        for child in self.ticket_sons(ticket_number):
            time += self.ticket_remaining_time_sum(child)
        return time

    def ticket_query_time_sum(self, query):
        tickets = self.server.ticket.query(query)
        time = 0
        for ticket in tickets:
            time += self.ticket_remaining_time(ticket)

        return time

    def ticket_batch_edit(self, id_list):
        for id in id_list:
            ticket = self.ticket_get(id)
            attributes = self.attrs.edit(ticket[3])
            self.server.ticket.update(int(id),
                                      "",
                                      attributes
                                  )

    def ticket_changelog(self, ticket, filter=lambda x:True):
        cl = self.server.ticket.changeLog(ticket)
        return [[ticket] + l for l in cl if filter([ticket] + l)]

    def ticket_recent_changes(self, since, filter=lambda x:True):
        tickets = self.server.ticket.getRecentChanges(since)
        created_tickets = self.server.ticket.query(
            "created=%s.." % (since.strftime("%m/%d/%y"))
        )
        new_filter = lambda log:filter(log) and since <= log[1]
        # from the created tickets. Retrieve only those that have been created
        # after since
        created_tickets_changelogs = []
        for ticket_number in created_tickets:
            ticket = self.ticket_get(ticket_number)
            ticket_log = [ticket_number, ticket[1], ticket[3]["reporter"], "created", "", "", ""]
            if new_filter(ticket_log):
                created_tickets_changelogs.append(
                    ticket_log
                )

        changelogs = []
        for ticket in tickets:
            changelogs = changelogs + self.ticket_changelog(ticket, new_filter)
        return created_tickets_changelogs + changelogs

    def template_edit(self):
        attributes = \
                     self.attrs.edit(self.template_attributes)
        if attributes:
            self.template_attributes = attributes
            return True
        else:
            return False

    def template_save(self, file_name):
        if os.path.exists(file_name):
            "The file %s will be erased"
            os.unlink(file_name)
        with open(file_name, "w") as file:
            file.write(self.attrs.dump(self.template_attributes))

        return True

    def template_open(self, file_name):
        assert os.path.exists(file_name)
        with open(file_name, "r") as file:
            self.template_attributes = self.attrs.load(file.read())

        return True

    def list_components(self, filter):
        return [
            comp for comp in self.server.ticket.component.getAll()
            if filter(comp)
        ]

    def milestone_edit(self, milestone_name):
        milestone = self.server.ticket.milestone.get(milestone_name)
        desc = milestone["description"]
        new_desc = edit(desc, prefix=milestone_name.replace(" ", "_"))
        if new_desc:
            self.server.ticket.milestone.update(
                milestone_name,
                {
                    "description":new_desc,
                },
            )
            return True

        return False

    def milestone_list(self):
        return self.server.ticket.milestone.getAll()
<|endoftext|>