Beispiel #1
0
def main():

    parser = ArgumentParser()
    parser.add_argument('--partreport', '-p', default=None, metavar='F',
                        help='specify participant report file to use')
    parser.add_argument('--tbreport', '-t', default=None, metavar='F',
                        help='specify trybooking report file to use')
    parser.add_argument('--verbose', '-v', action='store_true',
                        help='print verbose messages')
    args = parser.parse_args()

    config = load_config()

    teams = fetch_teams()

    fetch_participants(teams, args.partreport, args.verbose)

    tb = fetch_trybooking(config['tbmap'], args.tbreport, args.verbose)

    def match_names(fn1, fn2):
        if fn1.lower() == fn2.lower():
            return True
        else:
            return False

    unpaid = []
    paid = []

    for t in teams.values():

        for p in t.players:

            e = find_in_tb(tb, to_fullname(p['first name'], p['last name']))

            if e is None:
                unpaid.append(p)
            else:
                paid.append(p)

    if paid:
        print('Paid: ({})'.format(len(paid)))
        for p in sorted(
            paid, key=lambda p: to_fullname(p['first name'], p['last name'])
        ):
            print('    {}'.format(to_fullname(p['first name'], p['last name'])))
    if unpaid:
        print('Unpaid: ({})'.format(len(unpaid)))
        for p in sorted(
            unpaid, key=lambda p: to_fullname(p['first name'], p['last name'])
        ):
            print('    {}'.format(to_fullname(p['first name'], p['last name'])))
    if tb['by-name']:
        print('Unknown: ({})'.format(len(tb['by-name'])))
        for fn, e in sorted(tb['by-name'].items(), key=lambda e: e[0]):
            print('    {}'.format(fn))

    return 0
def main():

    parser = ArgumentParser()
    parser.add_argument('--verbose', '-v', action='store_true',
                        help='print verbose messages')
    parser.add_argument('teams', nargs='*',
                        help='limit checks to specified teams')
    args = parser.parse_args()

    teams = fetch_teams()

    if args.teams:
        newteams = {}
        for name in args.teams:
            if name not in teams:
                print('unknown team name: {}'.format(name), file=sys.stderr)
                sys.exit(1)
            newteams[name] = teams[name]
        teams = newteams

    for t in teams.values():

        if args.verbose:
            print('{} [{}]:'.format(t.sname, t.edjba_id))

        for label, attrs in descs:
            ident, name, dobstr, wwcnum, wwcname = map(
                lambda a: getattr(t, a), attrs
            )
            dob = to_date(dobstr, '%Y-%m-%d %H:%M:%S.%f')
            res = wwc_check(
                WWCCheckPerson(ident, name, wwcnum, dob, wwcname),
                args.verbose
            )
            # result is a WWCCheckStatus (IntEnum) and can be one of:
            #     NONE, UNKNOWN, EMPTY, UNDER18, TEACHER, BADNUMBER,
            #     FAILED, SUCCESS, EXPIRED, INVALID, BADRESPONSE
            if args.verbose:
                print('\t{}: {}\t\t- {}'.format(label, name, res))
            if res.status not in (
                WWCCheckStatus.NONE,
                WWCCheckStatus.UNKNOWN,
                WWCCheckStatus.SUCCESS,
                WWCCheckStatus.UNDER18,
                WWCCheckStatus.TEACHER,
            ):
                print(
                    '{} [{}]: {} ({}): {}'.format(
                        t.sname, t.edjba_id, name, label, res
                    ),
                    file=sys.stderr
                )

    return 0
Beispiel #3
0
def main():

    parser = ArgumentParser()
    parser.add_argument('--tmemail',
                        action='store_true',
                        help='just print team manager email addrs')
    parser.add_argument('--cemail',
                        action='store_true',
                        help='just print coach email addrs')
    parser.add_argument('--caddrs',
                        action='store_true',
                        help='just print coach names and addresses')
    parser.add_argument('--noresponse',
                        action='store_true',
                        help='print list of teams that have not responded')
    parser.add_argument('--csv',
                        action='store_true',
                        help='print info as csv instead of multi-line text')
    parser.add_argument('--terse',
                        action='store_true',
                        help='print one terse line instead of multi-line text')
    parser.add_argument('--verbose',
                        action='store_true',
                        help='be verbose about actions')
    parser.add_argument('--details',
                        action='store_true',
                        help='print more detail in multi-line text')
    parser.add_argument('--urls',
                        action='store_true',
                        help='print list of teams and urls')
    args = parser.parse_args()

    teams = fetch_teams(verbose=args.verbose)

    # info to print for a person
    p_fmt = '{} <{}>, {} [WWC: #{}, exp: {}]'

    def dateonly(s):
        if s is None:
            return None
        else:
            return sub(r' [0:\.]+$', '', s)

    if args.csv:
        print('Name,Id,Code,Age Group,Gender,Number,Grade,'
              'Team Manager,Email,Mobile')

    first = True

    for t in teams.values():

        if args.tmemail:
            print(t.tm_email)
            continue

        if args.cemail:
            if t.co_email:
                print(t.co_email)
            if t.ac_email:
                print(t.ac_email)
            continue

        if args.caddrs:
            if t.co_address:
                print('{}, {}'.format(t.co_name, t.co_address))
            if t.ac_address:
                print('{}, {}'.format(t.ac_name, t.ac_address))
            continue

        if args.noresponse and t.responded == 1:
            continue

        if args.csv:
            print('{},{},{},{:02d},{},{:02d},{},{},{},{}'.format(
                t.sname, t.edjba_id, t.edjba_code, t.age_group, t.gender,
                t.number, t.grade if t.grade else '', t.tm_name, t.tm_email,
                t.tm_mobile))
            continue

        if args.terse:
            # snm = t.sname[:9] + '...' if len(t.sname) > 12 else t.sname
            # tmn = t.tm_name[:15] + '...' if len(t.tm_name) > 18 else t.tm_name
            # tme = t.tm_email[:22] + '...' if len(t.tm_email) > 25 \
            #     else t.tm_email
            print('{} {:12} {:20} {:40} {}'.format(t.edjba_code, t.sname,
                                                   t.tm_name, t.tm_email,
                                                   t.tm_mobile))
        elif args.urls:
            print('{},{},{}'.format(t.sname, t.edjba_id, t.regurl))
        else:
            if first:
                first = False
            else:
                print('')
            print(t.sname + ':')
            print('      Id: ' + t.edjba_id)
            if args.details:
                print('    Code: ' + t.edjba_code)
                if t.regurl:
                    print('     URL: ' + t.regurl)
                if t.compats:
                    print(' Compats: ' + t.compats)
            if t.grade:
                print('  Grade?: ' + t.grade)
            print('     T/M: ' +
                  p_fmt.format(t.tm_name, t.tm_email, t.tm_mobile, t.tm_wwcnum,
                               dateonly(t.tm_wwcexp)))
            if args.details:
                if t.co_name and t.co_name != 'Nothing':
                    print('       C: ' +
                          p_fmt.format(t.co_name, t.co_email, t.co_mobile,
                                       t.co_wwcnum, dateonly(t.co_wwcexp)))
                if t.ac_name and t.ac_name != 'Nothing':
                    print('     A/C: ' +
                          p_fmt.format(t.ac_name, t.ac_email, t.ac_mobile,
                                       t.ac_wwcnum, dateonly(t.ac_wwcexp)))

    return 0
def main():

    parser = ArgumentParser()
    parser.add_argument('--club',
                        '-c',
                        default='Fairfield-Shooters',
                        help='change club id')
    parser.add_argument('--summary',
                        '-R',
                        action='store_true',
                        help='print results summary')
    parser.add_argument('--upcoming',
                        '-F',
                        action='store_true',
                        help='print results summary')
    parser.add_argument('--html',
                        '-H',
                        action='store_true',
                        help='print html instead of csv')
    parser.add_argument('--nrounds',
                        '-n',
                        type=int,
                        default=0,
                        metavar='N',
                        help='specify report file to use')
    parser.add_argument('--report',
                        '-r',
                        default=None,
                        metavar='F',
                        help='specify report file to use')
    parser.add_argument('--verbose',
                        '-v',
                        action='store_true',
                        help='print verbose messages')
    args = parser.parse_args()

    teams = fetch_teams()

    def result(roundno, grade, team_name, score_for=None, score_against=None):
        t = find_team(teams, edjba_id=team_name)
        if t is None:
            print('team {} not found!'.format(team_name), file=sys.stderr)
            return
        if not hasattr(t, 'results'):
            t.results = OrderedDict()
        if score_for is None:
            value = None
        else:
            value = score_for, score_against
        if (roundno, grade) in t.results:
            raise RuntimeError('duplicate game: {}, {}'.format(roundno, grade))
        t.results[roundno, grade] = value

    report_file = args.report
    if report_file is None:
        report_file, _ = latest_report('advanced_fixture')
        if report_file is None:
            raise RuntimeError('no advanced fixture report found!')
        if args.verbose:
            print('[advanced fixture report selected: {}]'.format(report_file),
                  file=sys.stderr)

    if args.upcoming:
        print('grade,team1,team2')

    with open(report_file, 'r', newline='') as csvfile:

        reader = DictReader(csvfile)

        for r in reader:

            _gdate = date(*strptime(r['game date'], '%d/%m/%Y')[:3])
            grade = r['grade']
            roundno = r['round']
            status = r['game status']
            bye = r['bye']
            team_a_name = r['team a']
            team_b_name = r['team b']
            _venue = r['venue']
            if not r['time']:
                _gtime = time()
            else:
                _gtime = time(*strptime(r['time'], '%H:%M:%S')[3:5])

            if status == 'UPCOMING':
                if args.upcoming:
                    if team_a_name.startswith('Fairfield'):
                        t = find_team(teams, edjba_id=team_a_name)
                        xa = ' [' + t.name[9:] + ']'
                    else:
                        xa = ''
                    if team_b_name.startswith('Fairfield'):
                        t = find_team(teams, edjba_id=team_b_name)
                        xb = ' [' + t.name[9:] + ']'
                    else:
                        xb = ''
                    print('{},{}{},{}{}'.format(
                        grade,
                        team_a_name,
                        xa,
                        team_b_name,
                        xb,
                    ))
                continue

            if bye:
                result(roundno, grade, bye)
            else:
                team_a_score = r['team a score']
                _team_a_result = r['team a result']
                team_b_score = r['team b score']
                _team_b_result = r['team b result']

                if team_a_name.startswith(args.club):
                    result(roundno, grade, team_a_name, team_a_score,
                           team_b_score)
                if team_b_name.startswith(args.club):
                    result(roundno, grade, team_b_name, team_b_score,
                           team_a_score)

    results = []
    for t in teams.values():
        e = [t.sname, t.edjba_code, t.grade]
        rcnt = totfor = totag = totmarg = 0
        for (r, _), v in t.results.items():
            rcnt += 1
            if args.nrounds > 0 and rcnt > args.nrounds:
                break
            if v is None:
                e.append('BYE')
            else:
                f, a = v
                if f and a:
                    f = int(f)
                    a = int(a)
                    if f > a:
                        s = 'W'
                    elif f < a:
                        s = 'L'
                    else:
                        s = 'D'
                    e.append('{}{:02d}-{:02d}'.format(s, f, a))
                    totfor += f
                    totag += a
                    totmarg += f - a
        e.append('{:.2f}'.format(float(totfor * 100) / float(totag)))
        e.append('{:.2f}'.format(float(totmarg) / float(rcnt)))
        results.append(e)

    if args.summary:
        if args.html:
            print('<html><body><table>')
            for r in results:
                print('\t<tr>')
                print('\t\t<td>' + '</td><td>'.join(r) + '</td>')
                print('\t</tr>')
            print('</table></body></html>')
        else:
            for r in results:
                print(','.join(r))

    return 0
def main():

    parser = ArgumentParser()
    parser.add_argument('--verbose',
                        '-v',
                        action='store_true',
                        help='print verbose messages')
    parser.add_argument('--club',
                        '-c',
                        default='Fairfield-Shooters',
                        help='change club id')
    parser.add_argument('xlsxfiles',
                        nargs='+',
                        help='xlsx file containing edjba fixtures')
    args = parser.parse_args()

    teams = fetch_teams()

    rounds = {}

    bye = datetime.strptime('00:00', '%H:%S').time()

    for xlsxfile in args.xlsxfiles:

        if args.verbose:
            print('processing file {}'.format(xlsxfile), file=sys.stderr)

        book = open_workbook(xlsxfile)

        sheet = book.sheet_by_index(0)

        rows = sheet.get_rows()

        _ = next(rows)

        for r in rows:

            grade, rdate, rnd, home, away, venue, crt, gtime = map(
                lambda c: c.value, r)

            clubmatches = [
                home.startswith(args.club),
                away.startswith(args.club)
            ]

            if any(clubmatches):

                rounds.setdefault(str(rnd), []).append([
                    clubmatches,
                    grade,
                    xldate_as_datetime(rdate, book.datemode).date(),
                    home,
                    '' if away == 'Bye' else away,
                    venue,
                    crt,
                    bye if away == 'Bye' else xldate_as_datetime(
                        gtime, book.datemode).time(),
                ])

    print('''<html>
     <head>
      <style>
       @media print {
        .pagebreak {
         clear: both;
         page-break-after: always;
        }
       }
       table tr td, table tr th {
        page-break-inside: avoid;
       }
       table, th, td {
        border: 1px solid black;
        border-collapse: collapse;
       }
       th, td {
        padding: 5px;
        font-size: small;
       }
       table.center {
        margin-left: auto;
        margin-right: auto;
       }
       h3 {
        text-align: center;
       }
       caption {
         display: table-caption;
         text-align: center;
         font-weight: bold;
         margin-top: 36px;
         margin-bottom: 36px;
       }
      </style>
     </head>
     <body>''')

    rcount = 0

    for rnd, games in sorted(rounds.items(), key=itemgetter(0)):

        ngames = sum(2 if all(g[0]) else 1 for g in games)

        if args.verbose:
            print('Round {} - {} games'.format(rnd, ngames), file=sys.stderr)

        if ngames == 0:
            continue

        rcount += 1
        rdate = games[0][2]

        dom = rdate.strftime('%d')
        while dom[0] == '0':
            dom = dom[1:]
        mon = rdate.strftime('%B')
        header = 'Round {} - {} {}'.format(rnd, mon, dom)

        if rcount > 1:
            print('  <div class="pagebreak"></div>')
        print('  <table class="center">')
        print('   <caption>{}</caption>'.format(escape(header, True)))

        print('   <tr>'
              '<th>Grade</th>'
              '<th>Home</th>'
              '<th>Away</th>'
              '<th>Time</th>'
              '<th>Venue</th>'
              '<th>Court</th>'
              '</tr>')

        n = 0
        for (clubmatches, grade, rdate1, home, away, venue, crt,
             gtime) in sorted(games, key=itemgetter(7)):
            n += 1

            if rdate1 != rdate:
                raise RuntimeError('differing Round dates ({}!={})'.format(
                    rdate, rdate1))

            if clubmatches[0]:
                home_team = find_team(teams, edjba_id=home)
                home += ' [<b>{}</b>]'.format(home_team.name[9:])
            if clubmatches[1]:
                away_team = find_team(teams, edjba_id=away)
                away += ' [<b>{}</b>]'.format(away_team.name[9:])

            print('   <tr>'
                  '<td>{}</td>'
                  '<td>{}</td>'
                  '<td>{}</td>'
                  '<td>{}</td>'
                  '<td>{}</td>'
                  '<td>{}</td>'
                  '</tr>'.format(
                      grade,
                      home,
                      away,
                      'BYE' if gtime is bye else gtime.strftime('%I:%M%p'),
                      venue,
                      crt,
                  ))

        print('  </table>')

    print(' </body>')
    print('</html>')

    return 0
Beispiel #6
0
def main():

    parser = ArgumentParser()
    parser.add_argument('--verbose',
                        '-v',
                        action='store_true',
                        help='print verbose messages')
    args = parser.parse_args()

    report_file, _ = latest_report('team_entries')
    if report_file is None:
        print('No team_entries reports.')
        sys.exit(1)
    print('[team_entries report selected: {}]'.format(report_file))

    teams = fetch_teams()

    ilst = ('name', 'email', 'mobile')

    mismatches = {
        'name': {
            'megan fairbank': 'megan grimmer',
            'nat power': 'natalie power',
        },
    }

    with open(report_file, 'r', newline='') as csvfile:

        reader = DictReader(csvfile)

        for tent in reader:

            team_name = tent['team name']
            if not team_name:
                if args.verbose:
                    print('no team name in {}!'.format(tent))
                continue

            t = find_team(teams, edjba_id=team_name)
            if t is None:
                if args.verbose:
                    print('team not found for {}!'.format(team_name))
                continue

            if hasattr(t, 'found'):
                raise RuntimeError('team appears twice in report!!')
            t.found = True

            grade = tent['allocated grade']
            # if grade != t.grade:
            #    print(
            #        '    ***** team grade mismatch! ({}!={})'.format(
            #            grade, t.grade
            #        )
            #    )

            print('{} [{}] ({}):'.format(t.sname, t.edjba_code, grade))

            ttype = tent['team type']
            if ttype != 'Club':
                print('    ***** team type not Club! ({})'.format(ttype))

            tgen = tent['team gender']
            if tgen.lower() != t.gender.lower():
                print('    ***** team gender mismatch! ({})'.format(tgen))

            # tm1info = [tent['team manager1 ' + k] for k in ilst]
            for k in ilst:
                v1 = tent['team manager1 ' + k]
                if v1:
                    if k == 'mobile':
                        v1 = '0' + v1
                    else:
                        v1 = v1.lower()
                v2 = getattr(t, 'tm_' + k)
                if v2:
                    v2 = v2.lower()
                if v1 != v2:
                    mm = mismatches.get(k)
                    if mm and mm.get(v1) == v2:
                        pass
                    else:
                        print('    ***** team manager {} mismatch! '
                              '({}!={})'.format(k, v1, v2))

            tm2info = [tent['team manager2 ' + k] for k in ilst]
            if any(tm2info):
                print('    ***** team has 2nd team manager! ({})'.format(
                    tm2info))

    for t in teams.values():
        if not hasattr(t, 'found'):
            print('***** {} was not in report!'.format(t.name))

    return 0
Beispiel #7
0
def main():

    parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
    parser.add_argument('--auth',
                        '-a',
                        action='store_true',
                        help='authenticate to the email server')
    parser.add_argument('--coaches',
                        '-c',
                        action='store_true',
                        help='send to coaches instead of team managers')
    parser.add_argument('--details',
                        '-d',
                        action='store_true',
                        help='include coach and player details')
    parser.add_argument('--nocoach',
                        '-N',
                        action='store_true',
                        help='do not include coach details')
    parser.add_argument('--dryrun',
                        '-n',
                        action='store_true',
                        help='dont actually send email')
    parser.add_argument('--pause',
                        '-p',
                        action='store_true',
                        help='pause for 5 secs between messages')
    parser.add_argument('--partreport',
                        default=None,
                        metavar='F',
                        help='specify participants report file to use')
    parser.add_argument('--testing',
                        '-t',
                        action='store_true',
                        help='just send one test email to a test address')
    parser.add_argument('--verbose',
                        '-v',
                        action='store_true',
                        help='print verbose messages')
    parser.add_argument('--writefiles',
                        '-w',
                        action='store_true',
                        help='write to files instead of sending email')
    parser.add_argument('--tbreport',
                        default=None,
                        metavar='F',
                        help='specify trybooking report file to use')
    parser.add_argument('--trybooking',
                        '-T',
                        action='store_true',
                        help='check trybooking payment and include in details')
    parser.add_argument('--prepend',
                        '-P',
                        default=None,
                        metavar='F',
                        help='specify file to prepend to html body')
    parser.add_argument('--append',
                        '-A',
                        default=None,
                        metavar='F',
                        help='specify file to append to html body')
    parser.add_argument('--relay',
                        '-R',
                        default='smtp-relay.gmail.com:587',
                        metavar='H[:P]',
                        help='specify relay host and port')
    parser.add_argument('--fqdn',
                        '-F',
                        default=None,
                        metavar='H',
                        help='specify the fqdn for the EHLO request')
    args = parser.parse_args()

    config = load_config()

    if ':' in args.relay:
        smtp_host, smtp_port = args.relay.split(':')
    else:
        smtp_host = args.relay
        smtp_port = 587

    if args.auth:
        if 'email_auth' in config:
            smtp_user, smtp_pass = config['email_auth']
        else:
            smtp_user = input('SMTP User: '******'SMTP Password: '******'email_fqdn' in config:
        smtp_fqdn = config['email_fqdn']

    if args.writefiles:
        smtp_class = SMTP_dummy
    else:
        smtp_class = SMTP

    admin_email = '*****@*****.**'
    testing_email = '*****@*****.**'
    subject_fmt = 'Registration info for {}'
    season_label = config.get('season', season.replace('-', ' ').title())
    body_fmt = '''\
<html>
 <head>
  <style>
   .pt {}
    border: 1px solid black;
    margin-top: 1em;
   {}
  </style>
 </head>
 <body>
  <p><i>[automated email - send queries to: {}]</i></p>
{}\
  <table>
   <tr>
    <td>Season:</td><td>&nbsp;</td><td><b><tt>''' \
        + season_label + '''</tt></b></td>
   </tr>
   <tr>
    <td></td><td></td><td></td>
   </tr>
   <tr>
    <td>Team Name:</td><td>&nbsp;</td><td><tt>{}</tt></td>
   </tr>
   <tr>
    <td>EDJBA Name:</td><td>&nbsp;</td><td><tt>{}</tt></td>
   </tr>
   <tr>
    <td>EDJBA Code:</td><td>&nbsp;</td><td><tt>{}</tt></td>
   </tr>
{}{}{}\
   <tr>
     <td>Rego Link:</td><td>&nbsp;</td><td><a href="{}"><tt>{}</tt></a></td>
   </tr>
  </table>
{}{}\
 <body>
</html>'''

    teams = fetch_teams()

    if args.details:

        player_keys = [
            ('last name', 'surname'),
            ('first name', 'firstname'),
            ('date of birth', 'd.o.b'),
            ('parent/guardian1 first name', 'parent firstname'),
            ('parent/guardian1 last name', 'parent surname'),
            ('parent/guardian1 mobile number', 'parent mobile'),
            ('parent/guardian1 email', 'parent email'),
        ]

        fetch_participants(teams, args.partreport, args.verbose)

        if args.trybooking:
            tbmap = config['tbmap']

            tb = fetch_trybooking(tbmap, args.tbreport, args.verbose)

            if len(tb) == 0:
                raise RuntimeError('no trybooking data in {}'.format(
                    args.tbreport))

    # used this:
    # https://stackoverflow.com/a/60301124

    with smtp_class(smtp_host, int(smtp_port), smtp_fqdn) as smtp:

        # smtp.set_debuglevel(99)

        if smtp_class is SMTP:
            smtp.starttls(context=create_default_context(
                purpose=Purpose.CLIENT_AUTH))
            if args.auth:
                smtp.login(smtp_user, smtp_pass)

        for t in teams.values():

            print('{} [{}, {}]:'.format(t.name, t.edjba_id, t.edjba_code),
                  file=sys.stderr)

            recips = []
            if args.coaches:
                if t.co_email:
                    recips.append(t.co_email)
                if t.ac_email:
                    recips.append(t.ac_email)
            else:
                recips.append(t.tm_email)

            if not recips:
                print('\tSKIPPING (no recipients).', file=sys.stderr)
                continue

            prepend_html = []
            if args.prepend:
                with open(args.prepend, 'r') as fd:
                    for line in fd.read().splitlines():
                        prepend_html.append('{}\n'.format(line))

            pt = []  # player table
            if args.details:
                pt.append('  <table class="pt">\n')
                pt.append('   <thead>\n')
                pt.append('    <tr>\n')
                pt.append('     <th class="pt">#</th>\n')
                for _, h in player_keys:
                    pt.append('     <th class="pt">{}</th>\n'.format(
                        nesc(h.title())))
                if args.trybooking:
                    pt.append('     <th class="pt">{}</th>\n'.format(
                        nesc('Ticket#')))
                pt.append('    </tr>\n')
                pt.append('   </thead>\n')
                pt.append('   <tbody>\n')
                for n, p in enumerate(t.players):
                    pt.append('    <tr>\n')
                    pt.append('     <td class="pt">{}</td>\n'.format(n + 1))
                    for k, _ in player_keys:
                        pt.append('     <td class="pt">{}</td>\n'.format(
                            nesc(p[k])))
                    if args.trybooking:
                        e = find_in_tb(
                            tb, to_fullname(p['first name'], p['last name']))
                        pt.append('     <td class="pt">{}</td>\n'.format(
                            'unpaid' if e is None else nesc(e['Ticket Number']
                                                            )))
                    pt.append('    </tr>\n')
                pt.append('   </tbody>\n')
                pt.append('  </table>\n')

            append_html = []
            if args.append:
                with open(args.append, 'r') as fd:
                    for line in fd.read().splitlines():
                        append_html.append('{}\n'.format(line))

            msg = MIMEText(
                body_fmt.format(
                    '{',
                    '}',
                    nesc(admin_email),
                    ''.join(prepend_html),
                    nesc(t.name),
                    nesc(t.edjba_id),
                    nesc(t.edjba_code),
                    person_tr('Team Manager', t.tm_name, t.tm_email,
                              t.tm_mobile),
                    person_tr('Coach', t.co_name, t.co_email, t.co_mobile)
                    if args.details and not args.nocoach else '',
                    person_tr('Asst Coach', t.ac_name, t.ac_email, t.ac_mobile)
                    if args.details and not args.nocoach else '',
                    nesc(t.regurl),
                    nesc(t.regurl),
                    ''.join(pt),
                    ''.join(append_html),
                ),
                'html',
            )

            msg['From'] = admin_email
            msg['To'] = ', '.join(recips)
            msg['Cc'] = admin_email
            msg['Subject'] = subject_fmt.format(t.name)
            msg['Return-Path'] = admin_email

            print('\tsending to {}...'.format(recips), end='', file=sys.stderr)
            if args.testing:
                recips = [testing_email]
            else:
                recips.append(admin_email)
            try:
                if args.dryrun:
                    print('\n*****|{}|{}|\n{}'.format(admin_email, recips,
                                                      msg.as_string()),
                          file=sys.stderr)
                else:
                    smtp.sendmail(admin_email, recips, msg.as_string())
                print('done.', file=sys.stderr)
            except KeyboardInterrupt:
                print('\nInterrupted!', file=sys.stderr)
                sys.exit(0)
            except SMTPException as e:
                if hasattr(e, 'smtp_error'):
                    m = e.smtp_error
                else:
                    m = repr(e)
                print('exception - {}'.format(m), file=sys.stderr)

            if args.testing:
                break
            if args.pause:
                sleep(5)

    if args.details and args.trybooking:

        if tb['by-name']:

            print('{} trybooking tickets unmatched'.format(len(tb['by-name'])),
                  file=sys.stderr)

            for name, elist in tb['by-name'].items():

                print('\t{} [{}]'.format(
                    name, ','.join(e['Ticket Number'] for e in elist)),
                      file=sys.stderr)

        if tb['by-tnum']:

            print('{} trybooking tickets unused'.format(len(tb['by-tnum'])),
                  file=sys.stderr)

            for tnum, elist in tb['by-tnum'].items():

                if len(elist) != 1:
                    raise RuntimeError('huh? (1)')

                entry = elist[0]
                if entry['Ticket Number'] != tnum:
                    raise RuntimeError('huh? (2)')

                name = to_fullname(
                    entry['Ticket Data: Player First Name'],
                    entry['Ticket Data: Player Family Name'],
                )

                print('\t{} [{}]'.format(name, tnum), file=sys.stderr)

    return 0
def main():

    parser = ArgumentParser()
    parser.add_argument('--report',
                        '-r',
                        default=None,
                        metavar='FILE',
                        help='specify report file to use')
    parser.add_argument('--verbose',
                        '-v',
                        action='store_true',
                        help='print verbose messages')
    parser.add_argument('--younger',
                        '-y',
                        action='store_true',
                        help='print players younger than their age group')
    args = parser.parse_args()

    config = load_config()

    teams = fetch_teams()

    fetch_participants(teams, args.report, args.verbose)

    def pcmp(what, sname, code, p, name, email, mobile, wwcnum, wwcexp):

        pname = p['first name'] + ' ' + p['last name']
        if name is None:
            print('***** {:12} [{}] : {} name is None! ({})'.format(
                sname, code, what, pname))
        elif name.strip().lower() != pname.strip().lower():
            print('***** {:12} [{}] : {} name mismatch! ({}!={})'.format(
                sname, code, what, name, pname))

        if email is None:
            print('***** {:12} [{}] : [{}] : {} email is None! ({})'.format(
                sname, code, pname, what, p['email']))
        elif email.strip().lower() != p['email'].strip().lower():
            print(
                '***** {:12} [{}] : [{}] : {} email mismatch! ({}!={})'.format(
                    sname, code, pname, what, email, p['email']))

        if mobile is None:
            print('***** {:12} [{}] : [{}] : {} mobile is None! ({})'.format(
                sname, code, pname, what, p['mobile number']))
        else:
            pmobile = p['mobile number'].strip().lower()
            if not pmobile.startswith('0'):
                pmobile = '0' + pmobile
            if mobile.strip().lower() != pmobile:
                print('***** {:12} [{}] : [{}] : {} mobile mismatch! '
                      '({}!={})'.format(sname, code, pname, what, mobile,
                                        pmobile))

        if wwcnum is None:
            print('***** {:12} [{}] : [{}] : {} wwcnum is None! ({})'.format(
                sname, code, pname, what, p['wwc number']))
        else:
            pwwcnum = p['wwc number'].strip().lower()
            if pwwcnum.startswith('vit '):
                if len(pwwcnum) == 11 and pwwcnum[8] == '-':
                    pwwcnum = pwwcnum[:8] + pwwcnum[9:]
            else:
                if len(pwwcnum) == 8:
                    pwwcnum += '-01'
                elif (len(pwwcnum) == 10 and pwwcnum[8].isdigit()
                      and pwwcnum[9].isdigit()):
                    pwwcnum = pwwcnum[:8] + '-' + pwwcnum[8:]
            if wwcnum.strip().lower() != pwwcnum:
                print('***** {:12} [{}] : [{}] : {} wwc number mismatch! '
                      '({}!={})'.format(sname, code, pname, what, wwcnum,
                                        pwwcnum))

        if wwcexp is None:
            print('***** {:12} [{}] : [{}] : {} wwcexp is None! ({})'.format(
                sname, code, pname, what, p['wwc expiry date']))
        else:
            pwwcexp = p['wwc expiry date'].strip().lower()
            if not pwwcexp.endswith(' 00:00:00.000'):
                pwwcexp += ' 00:00:00.000'
            if wwcexp.strip().lower() != pwwcexp:
                print('***** {:12} [{}] : [{}] : {} wwcexp mismatch! '
                      '({}!={})'.format(sname, code, pname, what, wwcexp,
                                        pwwcexp))

    for t in teams.values():

        ag_start_s, ag_end_s = config['age_groups']['U{:d}'.format(
            t.age_group)]

        ag_start = to_date(ag_start_s)
        ag_end = to_date(ag_end_s)

        for p in t.players:

            dob = to_date(p['date of birth'])

            if dob < ag_start or (args.younger and ag_end and dob > ag_end):
                print('***** {:12} [{}] : {} - {}'.format(
                    t.sname,
                    t.edjba_code,
                    to_fullname(p['first name'], p['last name']),
                    p['date of birth'],
                ))
                # print('S={},E={},D={}'.format(ag_start, ag_end, dob))

        if len(t.managers) == 0:
            print('***** {:12} [{}] : No Team Managers!'.format(
                t.sname, t.edjba_code))
        else:
            pcmp('Team Manager', t.sname, t.edjba_code, t.managers[0],
                 t.tm_name, t.tm_email, t.tm_mobile, t.tm_wwcnum, t.tm_wwcexp)
            if len(t.managers) > 1:
                print('***** {:12} [{}] : more than 1 team manager!'.format(
                    t.sname, t.edjba_code))
                n = 2
                for m in t.managers[1:]:
                    print('***** {:12} [{}] : T/M{} = {}'.format(n, m))
                    n += 1

        if len(t.coaches) == 0:
            print('***** {:12} [{}] : No Coaches!'.format(
                t.sname, t.edjba_code))
        else:
            pcmp('Coach', t.sname, t.edjba_code, t.coaches[0], t.co_name,
                 t.co_email, t.co_mobile, t.co_wwcnum, t.co_wwcexp)
            if len(t.coaches) > 1:
                pcmp('Assistant Coach', t.sname, t.edjba_code, t.coaches[1],
                     t.ac_name, t.ac_email, t.ac_mobile, t.ac_wwcnum,
                     t.ac_wwcexp)
                if len(t.coaches) > 2:
                    print('***** {:12} [{}] : more than 2 coaches!'.format(
                        t.sname, t.edjba_code))
                    n = 3
                    for m in t.managers[1:]:
                        print('***** {:12} [{}] : Coach{} = {}'.format(n, m))
                        n += 1

    return 0