Introduction

So this is a reasonably quickly hacked up script to make it easier to maintain the Debian release team's architecture status page for squeeze. It was prompted by a mail from Philipp Kern complaining that managing the HTML tables was a bit of a pain.

So, boring preamble.

#!/usr/bin/env python

# Copyright (c) 2009 Anthony Towns
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

The obvious idea to maintain the page would be exporting from a spreadsheet -- it's already tabular, so minimal interface changes, and there's plenty of usable spreadsheets around. But it's not easy to do things that way, and automating it further would also suck.

So the next obvious solution is to have some plain text files, and generate HTML from them. And the easiest way to to text files at the moment is YAML.

import sys, yaml

Formatting Helpers

The way we'll handle the YAML contents is to pull each particular bit of information about an architecture out, interpret it, and then dump some HTML code for the table cell. In order to make that a bit easier, we'll use a bunch of interpretation functions that will take the data from YAML, and spit back a state (fail/pass), and the actual contents for the cell (colouring happens later).

Boring helpers:

def FAIL(value): return ("fail",value)
def WARN(value): return ("warn",value)
def PASS(value): return ("pass",value)

Dealing with "yes"/"no" states -- note YAML automatically translates "yes" to True, etc.

def c_truth(value):
    if value == True:
        return PASS("yes")
    elif value == False:
        return FAIL("no")
    else:
        return WARN(value)

def c_untruth(value):
    if value == True:
        return FAIL("yes")
    elif value == False:
        return PASS("no")
    else:
        return WARN(value)

def c_str(value):
    if not value: return FAIL("-")
    return PASS(value)

For the porterbox field, we'll try turning it into a link to the machine db:

def c_host(value):
    if not value: return FAIL("none")
    return PASS('<a href="http://db.debian.org/machines.cgi?host=%s">yes</a>'%(value))

For numbers, the function needs to know what value ranges are acceptable -- so we specify a maybe value (which will demote a failure to a warning), and an okay value (which gets a pass). Bigger numbers are assumed to be better.

def c_num(maybe,okay):
    def c_list_f(value):
        if value < maybe: return FAIL(value)
        if value >= okay: return PASS(value)
        return WARN(value)
    return c_list_f

Same thing for lists of values: how many elements are okay, how many just need a warning? Since the cell can only display a count, we also have a scrollover field that gives the full list.

def c_list(maybe,okay):
    def c_list_f(value):
        n=len(value)
        str='<acronym title="%s">%s</acronym>' % (", ".join(value),n)
        if n < maybe: return FAIL(str)
        if n >= okay: return PASS(str)
        return WARN(str)
    return c_list_f

Criteria

So that gets us to the point where we can defined the release criteria for architectures:

criteria = [
    ("available",         c_truth),
    ("portbox",           c_host),
    ("porters",           c_list(2,5)),
    ("users",             c_num(30,50)),
    ("installer",         c_str),
    ("archive-coverage",  c_num(90,95)),
    ("archive-uptodate",  c_num(95,97)),
    ("buildds",           c_list(2,2)),
    ("buildd-redundancy", c_truth),
    ("buildd-fast-sec",   c_truth),
    ("buildd-247",        c_truth),
    ("concerns-rm",       c_untruth),
    ("concerns-srm",      c_untruth),
    ("concerns-dsa",      c_untruth),
    ("concerns-sec",      c_untruth),
    ("candidate",         c_truth),
]

Table Output

Output of the HTML table is pretty straightforward.

def dump_table(info,waivers):
    arches=info.keys()
    arches.sort()

    colour = {"pass":"lime", "warn":"yellow", "fail":"red", "waiv":"cyan"}

    candidacy_at_risk = {}

    print "<table>"
    print "<tr><td></td>"
    for arch in arches:
        print "<td>%s</td>" % (arch)
        candidacy_at_risk[arch] = False
    print "</tr>"

    for c,c_fn in criteria:
        print "<tr><td>%s</td>" % (c)
        for arch in arches:
            v=info[arch].get(c,None)

            if c=="candidate" and candidacy_at_risk[arch]:
                if v == True: v = "at risk"

            if v is not None:
                col,contents = c_fn(v)
            else:
                col,contents = FAIL("-")

            w = waivers.get(arch,{}).get(c,None)
            if w:
                col="waiv"
                contents += ' <a href="%s">(w)</a>' % (w)

            if col=="fail":
                candidacy_at_risk[arch]=True

            print '<td bgcolor="%s">%s</td>' % (colour[col],contents)

        print "</tr>"

    print "</table>"

main()

Having done all the interesting work, we just need a way to invoke it. The main() function here loads one or two YAML files (the arch details and the waivers list), and then hands them over.

def main(argv):
    if len(argv) < 2 or argv[1] == "-h":
        print "Usage: %s <arch-spec.yaml> [<waivers-spec.yaml>]" % argv[0]
        sys.exit(1)

    info = yaml.load(open(argv[1]))
    if len(argv) >= 3:
        waivers=yaml.load(open(argv[2]))
    else:
        waivers={}

    dump_table(info,waivers)

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

Voila!

Powered by Sputnik | XHTML 1.1