[Buildbot-commits] buildbot/buildbot/status/web base.py, NONE, 1.1 build.py, NONE, 1.1 builder.py, NONE, 1.1 changes.py, NONE, 1.1 logs.py, NONE, 1.1 step.py, NONE, 1.1 tests.py, NONE, 1.1 waterfall.py, 1.4, 1.5

Brian Warner warner at users.sourceforge.net
Wed Aug 1 22:08:13 UTC 2007


Update of /cvsroot/buildbot/buildbot/buildbot/status/web
In directory sc8-pr-cvs3.sourceforge.net:/tmp/cvs-serv29135/buildbot/status/web

Modified Files:
	waterfall.py 
Added Files:
	base.py build.py builder.py changes.py logs.py step.py 
	tests.py 
Log Message:
[project @ web-parts: split waterfall code into smaller pieces]

Original author: warner at lothar.com
Date: 2007-07-30 03:03:08+00:00

--- NEW FILE: base.py ---

class ITopBox(Interface):
    """I represent a box in the top row of the waterfall display: the one
    which shows the status of the last build for each builder."""
    pass

class ICurrentBox(Interface):
    """I represent the 'current activity' box, just above the builder name."""
    pass

class IBox(Interface):
    """I represent a box in the waterfall display."""
    pass

class IHTMLLog(Interface):
    pass

ROW_TEMPLATE = '''
<div class="row">
  <span class="label">%(label)s</span>
  <span class="field">%(field)s</span>
</div>'''

def make_row(label, field):
    """Create a name/value row for the HTML.

    `label` is plain text; it will be HTML-encoded.

    `field` is a bit of HTML structure; it will not be encoded in
    any way.
    """
    label = html.escape(label)
    return ROW_TEMPLATE % {"label": label, "field": field}

colormap = {
    'green': '#72ff75',
    }
def td(text="", parms={}, **props):
    data = ""
    data += "  "
    #if not props.has_key("border"):
    #    props["border"] = 1
    props.update(parms)
    if props.has_key("bgcolor"):
        props["bgcolor"] = colormap.get(props["bgcolor"], props["bgcolor"])
    comment = props.get("comment", None)
    if comment:
        data += "<!-- %s -->" % comment
    data += "<td"
    class_ = props.get('class_', None)
    if class_:
        props["class"] = class_
    for prop in ("align", "bgcolor", "colspan", "rowspan", "border",
                 "valign", "halign", "class"):
        p = props.get(prop, None)
        if p != None:
            data += " %s=\"%s\"" % (prop, p)
    data += ">"
    if not text:
        text = " "
    if type(text) == types.ListType:
        data += string.join(text, "<br />")
    else:
        data += text
    data += "</td>\n"
    return data

def build_get_class(b):
    """
    Return the class to use for a finished build or buildstep,
    based on the result.
    """
    # FIXME: this getResults duplicity might need to be fixed
    result = b.getResults()
    #print "THOMAS: result for b %r: %r" % (b, result)
    if isinstance(b, builder.BuildStatus):
        result = b.getResults()
    elif isinstance(b, builder.BuildStepStatus):
        result = b.getResults()[0]
        # after forcing a build, b.getResults() returns ((None, []), []), ugh
        if isinstance(result, tuple):
            result = result[0]
    else:
        raise TypeError, "%r is not a BuildStatus or BuildStepStatus" % b

    if result == None:
        # FIXME: this happens when a buildstep is running ?
        return "running"
    return builder.Results[result]

class Box:
    # a Box wraps an Event. The Box has HTML <td> parameters that Events
    # lack, and it has a base URL to which each File's name is relative.
    # Events don't know about HTML.
    spacer = False
    def __init__(self, text=[], color=None, class_=None, urlbase=None,
                 **parms):
        self.text = text
        self.color = color
        self.class_ = class_
        self.urlbase = urlbase
        self.show_idle = 0
        if parms.has_key('show_idle'):
            del parms['show_idle']
            self.show_idle = 1
            
        self.parms = parms
        # parms is a dict of HTML parameters for the <td> element that will
        # represent this Event in the waterfall display.

    def td(self, **props):
        props.update(self.parms)
        text = self.text
        if not text and self.show_idle:
            text = ["[idle]"]
        return td(text, props, bgcolor=self.color, class_=self.class_)


class HtmlResource(Resource):
    css = None
    contentType = "text/html; charset=UTF-8"
    title = "Dummy"

    def render(self, request):
        data = self.content(request)
        if isinstance(data, unicode):
            data = data.encode("utf-8")
        request.setHeader("content-type", self.contentType)
        if request.method == "HEAD":
            request.setHeader("content-length", len(data))
            return ''
        return data

    def getTitle(self, request):
        return self.title

    def content(self, request):
        data = ('<!DOCTYPE html PUBLIC'
                ' "-//W3C//DTD XHTML 1.0 Transitional//EN"\n'
                '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
                '<html'
                ' xmlns="http://www.w3.org/1999/xhtml"'
                ' lang="en"'
                ' xml:lang="en">\n')
        data += "<head>\n"
        data += "  <title>" + self.getTitle(request) + "</title>\n"
        if self.css:
            # TODO: use some sort of relative link up to the root page, so
            # this css can be used from child pages too
            data += ('  <link href="%s" rel="stylesheet" type="text/css"/>\n'
                     % "buildbot.css")
        data += "</head>\n"
        data += '<body vlink="#800080">\n'
        data += self.body(request)
        data += "</body></html>\n"
        return data

    def body(self, request):
        return "Dummy\n"

class StaticHTML(HtmlResource):
    def __init__(self, body, title):
        HtmlResource.__init__(self)
        self.bodyHTML = body
        self.title = title
    def body(self, request):
        return self.bodyHTML


--- NEW FILE: build.py ---


# $builder/builds/NN
class StatusResourceBuild(HtmlResource):
    title = "Build"

    def __init__(self, status, build, builderControl, buildControl):
        HtmlResource.__init__(self)
        self.status = status
        self.build = build
        self.builderControl = builderControl
        self.control = buildControl

    def body(self, request):
        b = self.build
        buildbotURL = self.status.getBuildbotURL()
        projectName = self.status.getProjectName()
        data = '<div class="title"><a href="%s">%s</a></div>\n'%(buildbotURL,
                                                                 projectName)
        # the color in the following line gives python-mode trouble
        data += ("<h1>Build <a href=\"%s\">%s</a>:#%d</h1>\n"
                 % (self.status.getURLForThing(b.getBuilder()),
                    b.getBuilder().getName(), b.getNumber()))
        data += "<h2>Buildslave:</h2>\n %s\n" % html.escape(b.getSlavename())
        data += "<h2>Reason:</h2>\n%s\n" % html.escape(b.getReason())

        branch, revision, patch = b.getSourceStamp()
        data += "<h2>SourceStamp:</h2>\n"
        data += " <ul>\n"
        if branch:
            data += "  <li>Branch: %s</li>\n" % html.escape(branch)
        if revision:
            data += "  <li>Revision: %s</li>\n" % html.escape(str(revision))
        if patch:
            data += "  <li>Patch: YES</li>\n" # TODO: provide link to .diff
        if b.getChanges():
            data += "  <li>Changes: see below</li>\n"
        if (branch is None and revision is None and patch is None
            and not b.getChanges()):
            data += "  <li>build of most recent revision</li>\n"
        data += " </ul>\n"
        if b.isFinished():
            data += "<h2>Results:</h2>\n"
            data += " ".join(b.getText()) + "\n"
            if b.getTestResults():
                url = request.childLink("tests")
                data += "<h3><a href=\"%s\">test results</a></h3>\n" % url
        else:
            data += "<h2>Build In Progress</h2>"
            if self.control is not None:
                stopURL = urllib.quote(request.childLink("stop"))
                data += """
                <form action="%s" class='command stopbuild'>
                <p>To stop this build, fill out the following fields and
                push the 'Stop' button</p>\n""" % stopURL
                data += make_row("Your name:",
                                 "<input type='text' name='username' />")
                data += make_row("Reason for stopping build:",
                                 "<input type='text' name='comments' />")
                data += """<input type="submit" value="Stop Builder" />
                </form>
                """

        if b.isFinished() and self.builderControl is not None:
            data += "<h3>Resubmit Build:</h3>\n"
            # can we rebuild it exactly?
            exactly = (revision is not None) or b.getChanges()
            if exactly:
                data += ("<p>This tree was built from a specific set of \n"
                         "source files, and can be rebuilt exactly</p>\n")
            else:
                data += ("<p>This tree was built from the most recent "
                         "revision")
                if branch:
                    data += " (along some branch)"
                data += (" and thus it might not be possible to rebuild it \n"
                         "exactly. Any changes that have been committed \n"
                         "after this build was started <b>will</b> be \n"
                         "included in a rebuild.</p>\n")
            rebuildURL = urllib.quote(request.childLink("rebuild"))
            data += ('<form action="%s" class="command rebuild">\n'
                     % rebuildURL)
            data += make_row("Your name:",
                             "<input type='text' name='username' />")
            data += make_row("Reason for re-running build:",
                             "<input type='text' name='comments' />")
            data += '<input type="submit" value="Rebuild" />\n'
            data += '</form>\n'

        data += "<h2>Steps and Logfiles:</h2>\n"
        if b.getLogs():
            data += "<ol>\n"
            for s in b.getSteps():
                data += (" <li><a href=\"%s\">%s</a> [%s]\n"
                         % (self.status.getURLForThing(s), s.getName(),
                            " ".join(s.getText())))
                if s.getLogs():
                    data += "  <ol>\n"
                    for logfile in s.getLogs():
                        data += ("   <li><a href=\"%s\">%s</a></li>\n" %
                                 (self.status.getURLForThing(logfile),
                                  logfile.getName()))
                    data += "  </ol>\n"
                data += " </li>\n"
            data += "</ol>\n"

        data += ("<h2>Blamelist:</h2>\n"
                 " <ol>\n")
        for who in b.getResponsibleUsers():
            data += "  <li>%s</li>\n" % html.escape(who)
        data += (" </ol>\n"
                 "<h2>All Changes</h2>\n")
        changes = b.getChanges()
        if changes:
            data += "<ol>\n"
            for c in changes:
                data += "<li>" + c.asHTML() + "</li>\n"
            data += "</ol>\n"
        #data += html.PRE(b.changesText()) # TODO
        return data

    def stop(self, request):
        log.msg("web stopBuild of build %s:%s" % \
                (self.build.getBuilder().getName(),
                 self.build.getNumber()))
        name = request.args.get("username", ["<unknown>"])[0]
        comments = request.args.get("comments", ["<no reason specified>"])[0]
        reason = ("The web-page 'stop build' button was pressed by "
                  "'%s': %s\n" % (name, comments))
        self.control.stopBuild(reason)
        # we're at http://localhost:8080/svn-hello/builds/5/stop?[args] and
        # we want to go to: http://localhost:8080/svn-hello/builds/5 or
        # http://localhost:8080/
        #
        #return Redirect("../%d" % self.build.getNumber())
        r = Redirect("../../..")
        d = defer.Deferred()
        reactor.callLater(1, d.callback, r)
        return DeferredResource(d)

    def rebuild(self, request):
        log.msg("web rebuild of build %s:%s" % \
                (self.build.getBuilder().getName(),
                 self.build.getNumber()))
        name = request.args.get("username", ["<unknown>"])[0]
        comments = request.args.get("comments", ["<no reason specified>"])[0]
        reason = ("The web-page 'rebuild' button was pressed by "
                  "'%s': %s\n" % (name, comments))
        if not self.builderControl or not self.build.isFinished():
            log.msg("could not rebuild: bc=%s, isFinished=%s"
                    % (self.builderControl, self.build.isFinished()))
            # TODO: indicate an error
        else:
            self.builderControl.resubmitBuild(self.build, reason)
        # we're at http://localhost:8080/svn-hello/builds/5/rebuild?[args] and
        # we want to go to the top, at http://localhost:8080/
        r = Redirect("../../..")
        d = defer.Deferred()
        reactor.callLater(1, d.callback, r)
        return DeferredResource(d)

    def getChild(self, path, request):
        if path == "tests":
            return StatusResourceTestResults(self.status,
                                             self.build.getTestResults())
        if path == "stop":
            return self.stop(request)
        if path == "rebuild":
            return self.rebuild(request)
        if path.startswith("step-"):
            stepname = path[len("step-"):]
            steps = self.build.getSteps()
            for s in steps:
                if s.getName() == stepname:
                    return StatusResourceBuildStep(self.status, s)
            return NoResource("No such BuildStep '%s'" % stepname)
        return NoResource("No such resource '%s'" % path)

class BuildBox(components.Adapter):
    # this provides the yellow "starting line" box for each build
    implements(IBox)

    def getBox(self):
        b = self.original
        name = b.getBuilder().getName()
        number = b.getNumber()
        url = "%s/builds/%d" % (urllib.quote(name, safe=''), number)
        reason = b.getReason()
        text = ('<a title="Reason: %s" href="%s">Build %d</a>'
                % (html.escape(reason), url, number))
        color = "yellow"
        class_ = "start"
        if b.isFinished() and not b.getSteps():
            # the steps have been pruned, so there won't be any indication
            # of whether it succeeded or failed. Color the box red or green
            # to show its status
            color = b.getColor()
            class_ = build_get_class(b)
        return Box([text], color=color, class_="BuildStep " + class_)
components.registerAdapter(BuildBox, builder.BuildStatus, IBox)

--- NEW FILE: builder.py ---

# $builder
class StatusResourceBuilder(HtmlResource):

    def __init__(self, status, builder, control):
        HtmlResource.__init__(self)
        self.status = status
        self.title = builder.getName() + " Builder"
        self.builder = builder
        self.control = control

    def body(self, request):
        b = self.builder
        slaves = b.getSlaves()
        connected_slaves = [s for s in slaves if s.isConnected()]

        buildbotURL = self.status.getBuildbotURL()
        projectName = self.status.getProjectName()
        data = "<a href=\"%s\">%s</a>\n" % (buildbotURL, projectName)
        data += make_row("Builder:", html.escape(b.getName()))
        b1 = b.getBuild(-1)
        if b1 is not None:
            data += make_row("Current/last build:", str(b1.getNumber()))
        data += "\n<br />BUILDSLAVES<br />\n"
        data += "<ol>\n"
        for slave in slaves:
            data += "<li><b>%s</b>: " % html.escape(slave.getName())
            if slave.isConnected():
                data += "CONNECTED\n"
                if slave.getAdmin():
                    data += make_row("Admin:", html.escape(slave.getAdmin()))
                if slave.getHost():
                    data += "<span class='label'>Host info:</span>\n"
                    data += html.PRE(slave.getHost())
            else:
                data += ("NOT CONNECTED\n")
            data += "</li>\n"
        data += "</ol>\n"

        if self.control is not None and connected_slaves:
            forceURL = urllib.quote(request.childLink("force"))
            data += (
                """
                <form action='%(forceURL)s' class='command forcebuild'>
                <p>To force a build, fill out the following fields and
                push the 'Force Build' button</p>"""
                + make_row("Your name:",
                           "<input type='text' name='username' />")
                + make_row("Reason for build:",
                           "<input type='text' name='comments' />")
                + make_row("Branch to build:",
                           "<input type='text' name='branch' />")
                + make_row("Revision to build:",
                           "<input type='text' name='revision' />")
                + """
                <input type='submit' value='Force Build' />
                </form>
                """) % {"forceURL": forceURL}
        elif self.control is not None:
            data += """
            <p>All buildslaves appear to be offline, so it's not possible
            to force this build to execute at this time.</p>
            """

        if self.control is not None:
            pingURL = urllib.quote(request.childLink("ping"))
            data += """
            <form action="%s" class='command pingbuilder'>
            <p>To ping the buildslave(s), push the 'Ping' button</p>

            <input type="submit" value="Ping Builder" />
            </form>
            """ % pingURL

        return data

    def force(self, request):
        name = request.args.get("username", ["<unknown>"])[0]
        reason = request.args.get("comments", ["<no reason specified>"])[0]
        branch = request.args.get("branch", [""])[0]
        revision = request.args.get("revision", [""])[0]

        r = "The web-page 'force build' button was pressed by '%s': %s\n" \
            % (name, reason)
        log.msg("web forcebuild of builder '%s', branch='%s', revision='%s'"
                % (self.builder.name, branch, revision))

        if not self.control:
            # TODO: tell the web user that their request was denied
            log.msg("but builder control is disabled")
            return Redirect("..")

        # keep weird stuff out of the branch and revision strings. TODO:
        # centralize this somewhere.
        if not re.match(r'^[\w\.\-\/]*$', branch):
            log.msg("bad branch '%s'" % branch)
            return Redirect("..")
        if not re.match(r'^[\w\.\-\/]*$', revision):
            log.msg("bad revision '%s'" % revision)
            return Redirect("..")
        if branch == "":
            branch = None
        if revision == "":
            revision = None

        # TODO: if we can authenticate that a particular User pushed the
        # button, use their name instead of None, so they'll be informed of
        # the results.
        s = SourceStamp(branch=branch, revision=revision)
        req = BuildRequest(r, s, self.builder.getName())
        try:
            self.control.requestBuildSoon(req)
        except interfaces.NoSlaveError:
            # TODO: tell the web user that their request could not be
            # honored
            pass
        return Redirect("..")

    def ping(self, request):
        log.msg("web ping of builder '%s'" % self.builder.name)
        self.control.ping() # TODO: there ought to be an ISlaveControl
        return Redirect("..")

    def getChild(self, path, request):
        if path == "force":
            return self.force(request)
        if path == "ping":
            return self.ping(request)
        if not path in ("events", "builds"):
            return NoResource("Bad URL '%s'" % path)
        num = request.postpath.pop(0)
        request.prepath.append(num)
        num = int(num)
        if path == "events":
            # TODO: is this dead code? .statusbag doesn't exist,right?
            log.msg("getChild['path']: %s" % request.uri)
            return NoResource("events are unavailable until code gets fixed")
            filename = request.postpath.pop(0)
            request.prepath.append(filename)
            e = self.builder.statusbag.getEventNumbered(num)
            if not e:
                return NoResource("No such event '%d'" % num)
            file = e.files.get(filename, None)
            if file == None:
                return NoResource("No such file '%s'" % filename)
            if type(file) == type(""):
                if file[:6] in ("<HTML>", "<html>"):
                    return static.Data(file, "text/html")
                return static.Data(file, "text/plain")
            return file
        if path == "builds":
            build = self.builder.getBuild(num)
            if build:
                control = None
                if self.control:
                    control = self.control.getBuild(num)
                return StatusResourceBuild(self.status, build,
                                           self.control, control)
            else:
                return NoResource("No such build '%d'" % num)
        return NoResource("really weird URL %s" % path)


class CurrentBox(components.Adapter):
    # this provides the "current activity" box, just above the builder name
    implements(ICurrentBox)

    def formatETA(self, eta):
        if eta is None:
            return []
        if eta < 0:
            return ["Soon"]
        abstime = time.strftime("%H:%M:%S", time.localtime(util.now()+eta))
        return ["ETA in", "%d secs" % eta, "at %s" % abstime]

    def getBox(self, status):
        # getState() returns offline, idle, or building
        state, builds = self.original.getState()

        # look for upcoming builds. We say the state is "waiting" if the
        # builder is otherwise idle and there is a scheduler which tells us a
        # build will be performed some time in the near future. TODO: this
        # functionality used to be in BuilderStatus.. maybe this code should
        # be merged back into it.
        upcoming = []
        builderName = self.original.getName()
        for s in status.getSchedulers():
            if builderName in s.listBuilderNames():
                upcoming.extend(s.getPendingBuildTimes())
        if state == "idle" and upcoming:
            state = "waiting"

        if state == "building":
            color = "yellow"
            text = ["building"]
            if builds:
                for b in builds:
                    eta = b.getETA()
                    if eta:
                        text.extend(self.formatETA(eta))
        elif state == "offline":
            color = "red"
            text = ["offline"]
        elif state == "idle":
            color = "white"
            text = ["idle"]
        elif state == "waiting":
            color = "yellow"
            text = ["waiting"]
        else:
            # just in case I add a state and forget to update this
            color = "white"
            text = [state]

        # TODO: for now, this pending/upcoming stuff is in the "current
        # activity" box, but really it should go into a "next activity" row
        # instead. The only times it should show up in "current activity" is
        # when the builder is otherwise idle.

        # are any builds pending? (waiting for a slave to be free)
        pbs = self.original.getPendingBuilds()
        if pbs:
            text.append("%d pending" % len(pbs))
        for t in upcoming:
            text.extend(["next at", 
                         time.strftime("%H:%M:%S", time.localtime(t)),
                         "[%d secs]" % (t - util.now()),
                         ])
            # TODO: the upcoming-builds box looks like:
            #  ['waiting', 'next at', '22:14:15', '[86 secs]']
            # while the currently-building box is reversed:
            #  ['building', 'ETA in', '2 secs', 'at 22:12:50']
            # consider swapping one of these to make them look the same. also
            # consider leaving them reversed to make them look different.
        return Box(text, color=color, class_="Activity " + state)

components.registerAdapter(CurrentBox, builder.BuilderStatus, ICurrentBox)


class BuildTopBox(components.Adapter):
    # this provides a per-builder box at the very top of the display,
    # showing the results of the most recent build
    implements(IBox)

    def getBox(self):
        assert interfaces.IBuilderStatus(self.original)
        b = self.original.getLastFinishedBuild()
        if not b:
            return Box(["none"], "white", class_="LastBuild")
        name = b.getBuilder().getName()
        number = b.getNumber()
        url = "%s/builds/%d" % (name, number)
        text = b.getText()
        # TODO: add logs?
        # TODO: add link to the per-build page at 'url'
        c = b.getColor()
        class_ = build_get_class(b)
        return Box(text, c, class_="LastBuild %s" % class_)
components.registerAdapter(BuildTopBox, builder.BuilderStatus, ITopBox)

--- NEW FILE: changes.py ---

# $changes/NN
class StatusResourceChanges(HtmlResource):
    def __init__(self, status, changemaster):
        HtmlResource.__init__(self)
        self.status = status
        self.changemaster = changemaster
    def body(self, request):
        data = ""
        data += "Change sources:\n"
        sources = list(self.changemaster)
        if sources:
            data += "<ol>\n"
            for s in sources:
                data += "<li>%s</li>\n" % s.describe()
            data += "</ol>\n"
        else:
            data += "none (push only)\n"
        return data
    def getChild(self, path, request):
        num = int(path)
        c = self.changemaster.getChangeNumbered(num)
        if not c:
            return NoResource("No change number '%d'" % num)
        return StaticHTML(c.asHTML(), "Change #%d" % num)


class ChangeBox(components.Adapter):
    implements(IBox)

    def getBox(self):
        url = "changes/%d" % self.original.number
        text = '<a href="%s">%s</a>' % (url, html.escape(self.original.who))
        return Box([text], color="white", class_="Change")
components.registerAdapter(ChangeBox, changes.Change, IBox)

--- NEW FILE: logs.py ---

textlog_stylesheet = """
<style type="text/css">
 div.data {
  font-family: "Courier New", courier, monotype;
 }
 span.stdout {
  font-family: "Courier New", courier, monotype;
 }
 span.stderr {
  font-family: "Courier New", courier, monotype;
  color: red;
 }
 span.header {
  font-family: "Courier New", courier, monotype;
  color: blue;
 }
</style>
"""

class ChunkConsumer:
    implements(interfaces.IStatusLogConsumer)

    def __init__(self, original, textlog):
        self.original = original
        self.textlog = textlog
    def registerProducer(self, producer, streaming):
        self.producer = producer
        self.original.registerProducer(producer, streaming)
    def unregisterProducer(self):
        self.original.unregisterProducer()
    def writeChunk(self, chunk):
        formatted = self.textlog.content([chunk])
        try:
            self.original.write(formatted)
        except pb.DeadReferenceError:
            self.producing.stopProducing()
    def finish(self):
        self.textlog.finished()

class TextLog(Resource):
    # a new instance of this Resource is created for each client who views
    # it, so we can afford to track the request in the Resource.
    implements(IHTMLLog)

    asText = False
    subscribed = False

    def __init__(self, original):
        Resource.__init__(self)
        self.original = original

    def getChild(self, path, request):
        if path == "text":
            self.asText = True
            return self
        return NoResource("bad pathname")

    def htmlHeader(self, request):
        title = "Log File contents"
        data = "<html>\n<head><title>" + title + "</title>\n"
        data += textlog_stylesheet
        data += "</head>\n"
        data += "<body vlink=\"#800080\">\n"
        texturl = request.childLink("text")
        data += '<a href="%s">(view as text)</a><br />\n' % texturl
        data += "<pre>\n"
        return data

    def content(self, entries):
        spanfmt = '<span class="%s">%s</span>'
        data = ""
        for type, entry in entries:
            if self.asText:
                if type != builder.HEADER:
                    data += entry
            else:
                data += spanfmt % (builder.ChunkTypes[type],
                                   html.escape(entry))
        return data

    def htmlFooter(self):
        data = "</pre>\n"
        data += "</body></html>\n"
        return data

    def render_HEAD(self, request):
        if self.asText:
            request.setHeader("content-type", "text/plain")
        else:
            request.setHeader("content-type", "text/html")

        # vague approximation, ignores markup
        request.setHeader("content-length", self.original.length)
        return ''

    def render_GET(self, req):
        self.req = req

        if self.asText:
            req.setHeader("content-type", "text/plain")
        else:
            req.setHeader("content-type", "text/html")

        if not self.asText:
            req.write(self.htmlHeader(req))

        self.original.subscribeConsumer(ChunkConsumer(req, self))
        return server.NOT_DONE_YET

    def finished(self):
        if not self.req:
            return
        try:
            if not self.asText:
                self.req.write(self.htmlFooter())
            self.req.finish()
        except pb.DeadReferenceError:
            pass
        # break the cycle, the Request's .notifications list includes the
        # Deferred (from req.notifyFinish) that's pointing at us.
        self.req = None

components.registerAdapter(TextLog, interfaces.IStatusLog, IHTMLLog)


class HTMLLog(Resource):
    implements(IHTMLLog)

    def __init__(self, original):
        Resource.__init__(self)
        self.original = original

    def render(self, request):
        request.setHeader("content-type", "text/html")
        return self.original.html

components.registerAdapter(HTMLLog, builder.HTMLLogFile, IHTMLLog)


--- NEW FILE: step.py ---


# $builder/builds/NN/stepname
class StatusResourceBuildStep(HtmlResource):
    title = "Build Step"

    def __init__(self, status, step):
        HtmlResource.__init__(self)
        self.status = status
        self.step = step

    def body(self, request):
        s = self.step
        b = s.getBuild()
        data = "<h1>BuildStep %s:#%d:%s</h1>\n" % \
               (b.getBuilder().getName(), b.getNumber(), s.getName())

        if s.isFinished():
            data += ("<h2>Finished</h2>\n"
                     "<p>%s</p>\n" % html.escape("%s" % s.getText()))
        else:
            data += ("<h2>Not Finished</h2>\n"
                     "<p>ETA %s seconds</p>\n" % s.getETA())

        exp = s.getExpectations()
        if exp:
            data += ("<h2>Expectations</h2>\n"
                     "<ul>\n")
            for e in exp:
                data += "<li>%s: current=%s, target=%s</li>\n" % \
                        (html.escape(e[0]), e[1], e[2])
            data += "</ul>\n"
        logs = s.getLogs()
        if logs:
            data += ("<h2>Logs</h2>\n"
                     "<ul>\n")
            for num in range(len(logs)):
                if logs[num].hasContents():
                    # FIXME: If the step name has a / in it, this is broken
                    # either way.  If we quote it but say '/'s are safe,
                    # it chops up the step name.  If we quote it and '/'s
                    # are not safe, it escapes the / that separates the
                    # step name from the log number.
                    data += '<li><a href="%s">%s</a></li>\n' % \
                            (urllib.quote(request.childLink("%d" % num)),
                             html.escape(logs[num].getName()))
                else:
                    data += ('<li>%s</li>\n' %
                             html.escape(logs[num].getName()))
            data += "</ul>\n"

        return data

    def getChild(self, path, request):
        logname = path
        try:
            log = self.step.getLogs()[int(logname)]
            if log.hasContents():
                return IHTMLLog(interfaces.IStatusLog(log))
            return NoResource("Empty Log '%s'" % logname)
        except (IndexError, ValueError):
            return NoResource("No such Log '%s'" % logname)


class StepBox(components.Adapter):
    implements(IBox)

    def getBox(self):
        b = self.original.getBuild()
        urlbase = "%s/builds/%d/step-%s" % (
            urllib.quote(b.getBuilder().getName(), safe=''),
            b.getNumber(),
            urllib.quote(self.original.getName(), safe=''))
        text = self.original.getText()
        if text is None:
            log.msg("getText() gave None", urlbase)
            text = []
        text = text[:]
        logs = self.original.getLogs()
        for num in range(len(logs)):
            name = logs[num].getName()
            if logs[num].hasContents():
                url = "%s/%d" % (urlbase, num)
                text.append("<a href=\"%s\">%s</a>" % (url, html.escape(name)))
            else:
                text.append(html.escape(name))
        urls = self.original.getURLs()
        ex_url_class = "BuildStep external"
        for name, target in urls.items():
            text.append('[<a href="%s" class="%s">%s</a>]' %
                        (target, ex_url_class, html.escape(name)))
        color = self.original.getColor()
        class_ = "BuildStep " + build_get_class(self.original)
        return Box(text, color, class_=class_)
components.registerAdapter(StepBox, builder.BuildStepStatus, IBox)

--- NEW FILE: tests.py ---

# $builder/builds/NN/tests/TESTNAME
class StatusResourceTestResult(HtmlResource):
    title = "Test Logs"

    def __init__(self, status, name, result):
        HtmlResource.__init__(self)
        self.status = status
        self.name = name
        self.result = result

    def body(self, request):
        dotname = ".".join(self.name)
        logs = self.result.getLogs()
        lognames = logs.keys()
        lognames.sort()
        data = "<h1>%s</h1>\n" % html.escape(dotname)
        for name in lognames:
            data += "<h2>%s</h2>\n" % html.escape(name)
            data += "<pre>" + logs[name] + "</pre>\n\n"

        return data


# $builder/builds/NN/tests
class StatusResourceTestResults(HtmlResource):
    title = "Test Results"

    def __init__(self, status, results):
        HtmlResource.__init__(self)
        self.status = status
        self.results = results

    def body(self, request):
        r = self.results
        data = "<h1>Test Results</h1>\n"
        data += "<ul>\n"
        testnames = r.keys()
        testnames.sort()
        for name in testnames:
            res = r[name]
            dotname = ".".join(name)
            data += " <li>%s: " % dotname
            # TODO: this could break on weird test names. At the moment,
            # test names only come from Trial tests, where the name
            # components must be legal python names, but that won't always
            # be a restriction.
            url = request.childLink(dotname)
            data += "<a href=\"%s\">%s</a>" % (url, " ".join(res.getText()))
            data += "</li>\n"
        data += "</ul>\n"
        return data

    def getChild(self, path, request):
        try:
            name = tuple(path.split("."))
            result = self.results[name]
            return StatusResourceTestResult(self.status, name, result)
        except KeyError:
            return NoResource("No such test name '%s'" % path)


Index: waterfall.py
===================================================================
RCS file: /cvsroot/buildbot/buildbot/buildbot/status/web/waterfall.py,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -d -r1.4 -r1.5
--- waterfall.py	1 Aug 2007 22:08:05 -0000	1.4
+++ waterfall.py	1 Aug 2007 22:08:11 -0000	1.5
@@ -23,934 +23,12 @@
 from buildbot.changes import changes
 from buildbot.process.base import BuildRequest
 
-class ITopBox(Interface):
-    """I represent a box in the top row of the waterfall display: the one
-    which shows the status of the last build for each builder."""
-    pass
-
-class ICurrentBox(Interface):
-    """I represent the 'current activity' box, just above the builder name."""
-    pass
-
-class IBox(Interface):
-    """I represent a box in the waterfall display."""
-    pass
-
-class IHTMLLog(Interface):
-    pass
-
-ROW_TEMPLATE = '''
-<div class="row">
-  <span class="label">%(label)s</span>
-  <span class="field">%(field)s</span>
-</div>'''
-
-def make_row(label, field):
-    """Create a name/value row for the HTML.
-
-    `label` is plain text; it will be HTML-encoded.
-
-    `field` is a bit of HTML structure; it will not be encoded in
-    any way.
-    """
-    label = html.escape(label)
-    return ROW_TEMPLATE % {"label": label, "field": field}
-
-colormap = {
-    'green': '#72ff75',
-    }
-def td(text="", parms={}, **props):
-    data = ""
-    data += "  "
-    #if not props.has_key("border"):
-    #    props["border"] = 1
-    props.update(parms)
-    if props.has_key("bgcolor"):
-        props["bgcolor"] = colormap.get(props["bgcolor"], props["bgcolor"])
-    comment = props.get("comment", None)
-    if comment:
-        data += "<!-- %s -->" % comment
-    data += "<td"
-    class_ = props.get('class_', None)
-    if class_:
-        props["class"] = class_
-    for prop in ("align", "bgcolor", "colspan", "rowspan", "border",
-                 "valign", "halign", "class"):
-        p = props.get(prop, None)
-        if p != None:
-            data += " %s=\"%s\"" % (prop, p)
-    data += ">"
-    if not text:
-        text = " "
-    if type(text) == types.ListType:
-        data += string.join(text, "<br />")
-    else:
-        data += text
-    data += "</td>\n"
-    return data
-
-def build_get_class(b):
-    """
-    Return the class to use for a finished build or buildstep,
-    based on the result.
-    """
-    # FIXME: this getResults duplicity might need to be fixed
-    result = b.getResults()
-    #print "THOMAS: result for b %r: %r" % (b, result)
-    if isinstance(b, builder.BuildStatus):
-        result = b.getResults()
-    elif isinstance(b, builder.BuildStepStatus):
-        result = b.getResults()[0]
-        # after forcing a build, b.getResults() returns ((None, []), []), ugh
-        if isinstance(result, tuple):
-            result = result[0]
-    else:
-        raise TypeError, "%r is not a BuildStatus or BuildStepStatus" % b
-
-    if result == None:
-        # FIXME: this happens when a buildstep is running ?
-        return "running"
-    return builder.Results[result]
-
-class Box:
-    # a Box wraps an Event. The Box has HTML <td> parameters that Events
-    # lack, and it has a base URL to which each File's name is relative.
-    # Events don't know about HTML.
-    spacer = False
-    def __init__(self, text=[], color=None, class_=None, urlbase=None,
-                 **parms):
-        self.text = text
-        self.color = color
-        self.class_ = class_
-        self.urlbase = urlbase
-        self.show_idle = 0
-        if parms.has_key('show_idle'):
-            del parms['show_idle']
-            self.show_idle = 1
-            
-        self.parms = parms
-        # parms is a dict of HTML parameters for the <td> element that will
-        # represent this Event in the waterfall display.
-
-    def td(self, **props):
-        props.update(self.parms)
-        text = self.text
-        if not text and self.show_idle:
-            text = ["[idle]"]
-        return td(text, props, bgcolor=self.color, class_=self.class_)
-
-
-class HtmlResource(Resource):
-    css = None
-    contentType = "text/html; charset=UTF-8"
-    title = "Dummy"
-
-    def render(self, request):
-        data = self.content(request)
-        if isinstance(data, unicode):
-            data = data.encode("utf-8")
-        request.setHeader("content-type", self.contentType)
-        if request.method == "HEAD":
-            request.setHeader("content-length", len(data))
-            return ''
-        return data
-
-    def getTitle(self, request):
-        return self.title
-
-    def content(self, request):
-        data = ('<!DOCTYPE html PUBLIC'
-                ' "-//W3C//DTD XHTML 1.0 Transitional//EN"\n'
-                '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n'
-                '<html'
-                ' xmlns="http://www.w3.org/1999/xhtml"'
-                ' lang="en"'
-                ' xml:lang="en">\n')
-        data += "<head>\n"
-        data += "  <title>" + self.getTitle(request) + "</title>\n"
-        if self.css:
-            # TODO: use some sort of relative link up to the root page, so
-            # this css can be used from child pages too
-            data += ('  <link href="%s" rel="stylesheet" type="text/css"/>\n'
-                     % "buildbot.css")
-        data += "</head>\n"
-        data += '<body vlink="#800080">\n'
-        data += self.body(request)
-        data += "</body></html>\n"
-        return data
-
-    def body(self, request):
-        return "Dummy\n"
-
-class StaticHTML(HtmlResource):
-    def __init__(self, body, title):
-        HtmlResource.__init__(self)
-        self.bodyHTML = body
-        self.title = title
-    def body(self, request):
-        return self.bodyHTML
-
-# $builder/builds/NN/stepname
-class StatusResourceBuildStep(HtmlResource):
-    title = "Build Step"
-
-    def __init__(self, status, step):
-        HtmlResource.__init__(self)
-        self.status = status
-        self.step = step
-
-    def body(self, request):
-        s = self.step
-        b = s.getBuild()
-        data = "<h1>BuildStep %s:#%d:%s</h1>\n" % \
-               (b.getBuilder().getName(), b.getNumber(), s.getName())
-
-        if s.isFinished():
-            data += ("<h2>Finished</h2>\n"
-                     "<p>%s</p>\n" % html.escape("%s" % s.getText()))
-        else:
-            data += ("<h2>Not Finished</h2>\n"
-                     "<p>ETA %s seconds</p>\n" % s.getETA())
-
-        exp = s.getExpectations()
-        if exp:
-            data += ("<h2>Expectations</h2>\n"
-                     "<ul>\n")
-            for e in exp:
-                data += "<li>%s: current=%s, target=%s</li>\n" % \
-                        (html.escape(e[0]), e[1], e[2])
-            data += "</ul>\n"
-        logs = s.getLogs()
-        if logs:
-            data += ("<h2>Logs</h2>\n"
-                     "<ul>\n")
-            for num in range(len(logs)):
-                if logs[num].hasContents():
-                    # FIXME: If the step name has a / in it, this is broken
-                    # either way.  If we quote it but say '/'s are safe,
-                    # it chops up the step name.  If we quote it and '/'s
-                    # are not safe, it escapes the / that separates the
-                    # step name from the log number.
-                    data += '<li><a href="%s">%s</a></li>\n' % \
-                            (urllib.quote(request.childLink("%d" % num)),
-                             html.escape(logs[num].getName()))
-                else:
-                    data += ('<li>%s</li>\n' %
-                             html.escape(logs[num].getName()))
-            data += "</ul>\n"
-
-        return data
-
-    def getChild(self, path, request):
-        logname = path
-        try:
-            log = self.step.getLogs()[int(logname)]
-            if log.hasContents():
-                return IHTMLLog(interfaces.IStatusLog(log))
-            return NoResource("Empty Log '%s'" % logname)
-        except (IndexError, ValueError):
-            return NoResource("No such Log '%s'" % logname)
-
-# $builder/builds/NN/tests/TESTNAME
-class StatusResourceTestResult(HtmlResource):
-    title = "Test Logs"
-
-    def __init__(self, status, name, result):
-        HtmlResource.__init__(self)
-        self.status = status
-        self.name = name
-        self.result = result
-
-    def body(self, request):
-        dotname = ".".join(self.name)
-        logs = self.result.getLogs()
-        lognames = logs.keys()
-        lognames.sort()
-        data = "<h1>%s</h1>\n" % html.escape(dotname)
-        for name in lognames:
-            data += "<h2>%s</h2>\n" % html.escape(name)
-            data += "<pre>" + logs[name] + "</pre>\n\n"
-
-        return data
-
-
-# $builder/builds/NN/tests
-class StatusResourceTestResults(HtmlResource):
-    title = "Test Results"
-
-    def __init__(self, status, results):
-        HtmlResource.__init__(self)
-        self.status = status
-        self.results = results
-
-    def body(self, request):
-        r = self.results
-        data = "<h1>Test Results</h1>\n"
-        data += "<ul>\n"
-        testnames = r.keys()
-        testnames.sort()
-        for name in testnames:
-            res = r[name]
-            dotname = ".".join(name)
-            data += " <li>%s: " % dotname
-            # TODO: this could break on weird test names. At the moment,
-            # test names only come from Trial tests, where the name
-            # components must be legal python names, but that won't always
-            # be a restriction.
-            url = request.childLink(dotname)
-            data += "<a href=\"%s\">%s</a>" % (url, " ".join(res.getText()))
-            data += "</li>\n"
-        data += "</ul>\n"
-        return data
-
-    def getChild(self, path, request):
-        try:
-            name = tuple(path.split("."))
-            result = self.results[name]
-            return StatusResourceTestResult(self.status, name, result)
-        except KeyError:
-            return NoResource("No such test name '%s'" % path)
-
-
-# $builder/builds/NN
-class StatusResourceBuild(HtmlResource):
-    title = "Build"
-
-    def __init__(self, status, build, builderControl, buildControl):
-        HtmlResource.__init__(self)
-        self.status = status
-        self.build = build
-        self.builderControl = builderControl
-        self.control = buildControl
-
-    def body(self, request):
-        b = self.build
-        buildbotURL = self.status.getBuildbotURL()
-        projectName = self.status.getProjectName()
-        data = '<div class="title"><a href="%s">%s</a></div>\n'%(buildbotURL,
-                                                                 projectName)
-        # the color in the following line gives python-mode trouble
-        data += ("<h1>Build <a href=\"%s\">%s</a>:#%d</h1>\n"
-                 % (self.status.getURLForThing(b.getBuilder()),
-                    b.getBuilder().getName(), b.getNumber()))
-        data += "<h2>Buildslave:</h2>\n %s\n" % html.escape(b.getSlavename())
-        data += "<h2>Reason:</h2>\n%s\n" % html.escape(b.getReason())
-
-        branch, revision, patch = b.getSourceStamp()
-        data += "<h2>SourceStamp:</h2>\n"
-        data += " <ul>\n"
-        if branch:
-            data += "  <li>Branch: %s</li>\n" % html.escape(branch)
-        if revision:
-            data += "  <li>Revision: %s</li>\n" % html.escape(str(revision))
-        if patch:
-            data += "  <li>Patch: YES</li>\n" # TODO: provide link to .diff
-        if b.getChanges():
-            data += "  <li>Changes: see below</li>\n"
-        if (branch is None and revision is None and patch is None
-            and not b.getChanges()):
-            data += "  <li>build of most recent revision</li>\n"
-        data += " </ul>\n"
-        if b.isFinished():
-            data += "<h2>Results:</h2>\n"
-            data += " ".join(b.getText()) + "\n"
-            if b.getTestResults():
-                url = request.childLink("tests")
-                data += "<h3><a href=\"%s\">test results</a></h3>\n" % url
-        else:
-            data += "<h2>Build In Progress</h2>"
-            if self.control is not None:
-                stopURL = urllib.quote(request.childLink("stop"))
-                data += """
-                <form action="%s" class='command stopbuild'>
-                <p>To stop this build, fill out the following fields and
-                push the 'Stop' button</p>\n""" % stopURL
-                data += make_row("Your name:",
-                                 "<input type='text' name='username' />")
-                data += make_row("Reason for stopping build:",
-                                 "<input type='text' name='comments' />")
-                data += """<input type="submit" value="Stop Builder" />
-                </form>
-                """
-
-        if b.isFinished() and self.builderControl is not None:
-            data += "<h3>Resubmit Build:</h3>\n"
-            # can we rebuild it exactly?
-            exactly = (revision is not None) or b.getChanges()
-            if exactly:
-                data += ("<p>This tree was built from a specific set of \n"
-                         "source files, and can be rebuilt exactly</p>\n")
-            else:
-                data += ("<p>This tree was built from the most recent "
-                         "revision")
-                if branch:
-                    data += " (along some branch)"
-                data += (" and thus it might not be possible to rebuild it \n"
-                         "exactly. Any changes that have been committed \n"
-                         "after this build was started <b>will</b> be \n"
-                         "included in a rebuild.</p>\n")
-            rebuildURL = urllib.quote(request.childLink("rebuild"))
-            data += ('<form action="%s" class="command rebuild">\n'
-                     % rebuildURL)
-            data += make_row("Your name:",
-                             "<input type='text' name='username' />")
-            data += make_row("Reason for re-running build:",
-                             "<input type='text' name='comments' />")
-            data += '<input type="submit" value="Rebuild" />\n'
-            data += '</form>\n'
-
-        data += "<h2>Steps and Logfiles:</h2>\n"
-        if b.getLogs():
-            data += "<ol>\n"
-            for s in b.getSteps():
-                data += (" <li><a href=\"%s\">%s</a> [%s]\n"
-                         % (self.status.getURLForThing(s), s.getName(),
-                            " ".join(s.getText())))
-                if s.getLogs():
-                    data += "  <ol>\n"
-                    for logfile in s.getLogs():
-                        data += ("   <li><a href=\"%s\">%s</a></li>\n" %
-                                 (self.status.getURLForThing(logfile),
-                                  logfile.getName()))
-                    data += "  </ol>\n"
-                data += " </li>\n"
-            data += "</ol>\n"
-
-        data += ("<h2>Blamelist:</h2>\n"
-                 " <ol>\n")
-        for who in b.getResponsibleUsers():
-            data += "  <li>%s</li>\n" % html.escape(who)
-        data += (" </ol>\n"
-                 "<h2>All Changes</h2>\n")
-        changes = b.getChanges()
-        if changes:
-            data += "<ol>\n"
-            for c in changes:
-                data += "<li>" + c.asHTML() + "</li>\n"
-            data += "</ol>\n"
-        #data += html.PRE(b.changesText()) # TODO
-        return data
-
-    def stop(self, request):
-        log.msg("web stopBuild of build %s:%s" % \
-                (self.build.getBuilder().getName(),
-                 self.build.getNumber()))
-        name = request.args.get("username", ["<unknown>"])[0]
-        comments = request.args.get("comments", ["<no reason specified>"])[0]
-        reason = ("The web-page 'stop build' button was pressed by "
-                  "'%s': %s\n" % (name, comments))
-        self.control.stopBuild(reason)
-        # we're at http://localhost:8080/svn-hello/builds/5/stop?[args] and
-        # we want to go to: http://localhost:8080/svn-hello/builds/5 or
-        # http://localhost:8080/
-        #
-        #return Redirect("../%d" % self.build.getNumber())
-        r = Redirect("../../..")
-        d = defer.Deferred()
-        reactor.callLater(1, d.callback, r)
-        return DeferredResource(d)
-
-    def rebuild(self, request):
-        log.msg("web rebuild of build %s:%s" % \
-                (self.build.getBuilder().getName(),
-                 self.build.getNumber()))
-        name = request.args.get("username", ["<unknown>"])[0]
-        comments = request.args.get("comments", ["<no reason specified>"])[0]
-        reason = ("The web-page 'rebuild' button was pressed by "
-                  "'%s': %s\n" % (name, comments))
-        if not self.builderControl or not self.build.isFinished():
-            log.msg("could not rebuild: bc=%s, isFinished=%s"
-                    % (self.builderControl, self.build.isFinished()))
-            # TODO: indicate an error
-        else:
-            self.builderControl.resubmitBuild(self.build, reason)
-        # we're at http://localhost:8080/svn-hello/builds/5/rebuild?[args] and
-        # we want to go to the top, at http://localhost:8080/
-        r = Redirect("../../..")
-        d = defer.Deferred()
-        reactor.callLater(1, d.callback, r)
-        return DeferredResource(d)
-
-    def getChild(self, path, request):
-        if path == "tests":
-            return StatusResourceTestResults(self.status,
-                                             self.build.getTestResults())
-        if path == "stop":
-            return self.stop(request)
-        if path == "rebuild":
-            return self.rebuild(request)
-        if path.startswith("step-"):
-            stepname = path[len("step-"):]
-            steps = self.build.getSteps()
-            for s in steps:
-                if s.getName() == stepname:
-                    return StatusResourceBuildStep(self.status, s)
-            return NoResource("No such BuildStep '%s'" % stepname)
-        return NoResource("No such resource '%s'" % path)
-
-# $builder
-class StatusResourceBuilder(HtmlResource):
-
-    def __init__(self, status, builder, control):
-        HtmlResource.__init__(self)
-        self.status = status
-        self.title = builder.getName() + " Builder"
-        self.builder = builder
-        self.control = control
-
-    def body(self, request):
-        b = self.builder
-        slaves = b.getSlaves()
-        connected_slaves = [s for s in slaves if s.isConnected()]
-
-        buildbotURL = self.status.getBuildbotURL()
-        projectName = self.status.getProjectName()
-        data = "<a href=\"%s\">%s</a>\n" % (buildbotURL, projectName)
-        data += make_row("Builder:", html.escape(b.getName()))
-        b1 = b.getBuild(-1)
-        if b1 is not None:
-            data += make_row("Current/last build:", str(b1.getNumber()))
-        data += "\n<br />BUILDSLAVES<br />\n"
-        data += "<ol>\n"
-        for slave in slaves:
-            data += "<li><b>%s</b>: " % html.escape(slave.getName())
-            if slave.isConnected():
-                data += "CONNECTED\n"
-                if slave.getAdmin():
-                    data += make_row("Admin:", html.escape(slave.getAdmin()))
-                if slave.getHost():
-                    data += "<span class='label'>Host info:</span>\n"
-                    data += html.PRE(slave.getHost())
-            else:
-                data += ("NOT CONNECTED\n")
-            data += "</li>\n"
-        data += "</ol>\n"
-
-        if self.control is not None and connected_slaves:
-            forceURL = urllib.quote(request.childLink("force"))
-            data += (
-                """
-                <form action='%(forceURL)s' class='command forcebuild'>
-                <p>To force a build, fill out the following fields and
-                push the 'Force Build' button</p>"""
-                + make_row("Your name:",
-                           "<input type='text' name='username' />")
-                + make_row("Reason for build:",
-                           "<input type='text' name='comments' />")
-                + make_row("Branch to build:",
-                           "<input type='text' name='branch' />")
-                + make_row("Revision to build:",
-                           "<input type='text' name='revision' />")
-                + """
-                <input type='submit' value='Force Build' />
-                </form>
-                """) % {"forceURL": forceURL}
-        elif self.control is not None:
-            data += """
-            <p>All buildslaves appear to be offline, so it's not possible
-            to force this build to execute at this time.</p>
-            """
-
-        if self.control is not None:
-            pingURL = urllib.quote(request.childLink("ping"))
-            data += """
-            <form action="%s" class='command pingbuilder'>
-            <p>To ping the buildslave(s), push the 'Ping' button</p>
-
-            <input type="submit" value="Ping Builder" />
-            </form>
-            """ % pingURL
-
-        return data
-
-    def force(self, request):
-        name = request.args.get("username", ["<unknown>"])[0]
-        reason = request.args.get("comments", ["<no reason specified>"])[0]
-        branch = request.args.get("branch", [""])[0]
-        revision = request.args.get("revision", [""])[0]
-
-        r = "The web-page 'force build' button was pressed by '%s': %s\n" \
-            % (name, reason)
-        log.msg("web forcebuild of builder '%s', branch='%s', revision='%s'"
-                % (self.builder.name, branch, revision))
-
-        if not self.control:
-            # TODO: tell the web user that their request was denied
-            log.msg("but builder control is disabled")
-            return Redirect("..")
-
-        # keep weird stuff out of the branch and revision strings. TODO:
-        # centralize this somewhere.
-        if not re.match(r'^[\w\.\-\/]*$', branch):
-            log.msg("bad branch '%s'" % branch)
-            return Redirect("..")
-        if not re.match(r'^[\w\.\-\/]*$', revision):
-            log.msg("bad revision '%s'" % revision)
-            return Redirect("..")
-        if branch == "":
-            branch = None
-        if revision == "":
-            revision = None
-
-        # TODO: if we can authenticate that a particular User pushed the
-        # button, use their name instead of None, so they'll be informed of
-        # the results.
-        s = SourceStamp(branch=branch, revision=revision)
-        req = BuildRequest(r, s, self.builder.getName())
-        try:
-            self.control.requestBuildSoon(req)
-        except interfaces.NoSlaveError:
-            # TODO: tell the web user that their request could not be
-            # honored
-            pass
-        return Redirect("..")
-
-    def ping(self, request):
-        log.msg("web ping of builder '%s'" % self.builder.name)
-        self.control.ping() # TODO: there ought to be an ISlaveControl
-        return Redirect("..")
-
-    def getChild(self, path, request):
-        if path == "force":
-            return self.force(request)
-        if path == "ping":
-            return self.ping(request)
-        if not path in ("events", "builds"):
-            return NoResource("Bad URL '%s'" % path)
-        num = request.postpath.pop(0)
-        request.prepath.append(num)
-        num = int(num)
-        if path == "events":
-            # TODO: is this dead code? .statusbag doesn't exist,right?
-            log.msg("getChild['path']: %s" % request.uri)
-            return NoResource("events are unavailable until code gets fixed")
-            filename = request.postpath.pop(0)
-            request.prepath.append(filename)
-            e = self.builder.statusbag.getEventNumbered(num)
-            if not e:
-                return NoResource("No such event '%d'" % num)
-            file = e.files.get(filename, None)
-            if file == None:
-                return NoResource("No such file '%s'" % filename)
-            if type(file) == type(""):
-                if file[:6] in ("<HTML>", "<html>"):
-                    return static.Data(file, "text/html")
-                return static.Data(file, "text/plain")
-            return file
-        if path == "builds":
-            build = self.builder.getBuild(num)
-            if build:
-                control = None
-                if self.control:
-                    control = self.control.getBuild(num)
-                return StatusResourceBuild(self.status, build,
-                                           self.control, control)
-            else:
-                return NoResource("No such build '%d'" % num)
-        return NoResource("really weird URL %s" % path)
-
-# $changes/NN
-class StatusResourceChanges(HtmlResource):
-    def __init__(self, status, changemaster):
-        HtmlResource.__init__(self)
-        self.status = status
-        self.changemaster = changemaster
-    def body(self, request):
-        data = ""
-        data += "Change sources:\n"
-        sources = list(self.changemaster)
-        if sources:
-            data += "<ol>\n"
-            for s in sources:
-                data += "<li>%s</li>\n" % s.describe()
-            data += "</ol>\n"
-        else:
-            data += "none (push only)\n"
-        return data
-    def getChild(self, path, request):
-        num = int(path)
-        c = self.changemaster.getChangeNumbered(num)
-        if not c:
-            return NoResource("No change number '%d'" % num)
-        return StaticHTML(c.asHTML(), "Change #%d" % num)
-
-textlog_stylesheet = """
-<style type="text/css">
- div.data {
-  font-family: "Courier New", courier, monotype;
- }
- span.stdout {
-  font-family: "Courier New", courier, monotype;
- }
- span.stderr {
-  font-family: "Courier New", courier, monotype;
-  color: red;
- }
- span.header {
-  font-family: "Courier New", courier, monotype;
-  color: blue;
- }
-</style>
-"""
-
-class ChunkConsumer:
-    implements(interfaces.IStatusLogConsumer)
-
-    def __init__(self, original, textlog):
-        self.original = original
-        self.textlog = textlog
-    def registerProducer(self, producer, streaming):
-        self.producer = producer
-        self.original.registerProducer(producer, streaming)
-    def unregisterProducer(self):
-        self.original.unregisterProducer()
-    def writeChunk(self, chunk):
-        formatted = self.textlog.content([chunk])
-        try:
-            self.original.write(formatted)
-        except pb.DeadReferenceError:
-            self.producing.stopProducing()
-    def finish(self):
-        self.textlog.finished()
-
-class TextLog(Resource):
-    # a new instance of this Resource is created for each client who views
-    # it, so we can afford to track the request in the Resource.
-    implements(IHTMLLog)
-
-    asText = False
-    subscribed = False
-
-    def __init__(self, original):
-        Resource.__init__(self)
-        self.original = original
-
-    def getChild(self, path, request):
-        if path == "text":
-            self.asText = True
-            return self
-        return NoResource("bad pathname")
-
-    def htmlHeader(self, request):
-        title = "Log File contents"
-        data = "<html>\n<head><title>" + title + "</title>\n"
-        data += textlog_stylesheet
-        data += "</head>\n"
-        data += "<body vlink=\"#800080\">\n"
-        texturl = request.childLink("text")
-        data += '<a href="%s">(view as text)</a><br />\n' % texturl
-        data += "<pre>\n"
-        return data
-
-    def content(self, entries):
-        spanfmt = '<span class="%s">%s</span>'
-        data = ""
-        for type, entry in entries:
-            if self.asText:
-                if type != builder.HEADER:
-                    data += entry
-            else:
-                data += spanfmt % (builder.ChunkTypes[type],
-                                   html.escape(entry))
-        return data
-
-    def htmlFooter(self):
-        data = "</pre>\n"
-        data += "</body></html>\n"
-        return data
-
-    def render_HEAD(self, request):
-        if self.asText:
-            request.setHeader("content-type", "text/plain")
-        else:
-            request.setHeader("content-type", "text/html")
-
-        # vague approximation, ignores markup
-        request.setHeader("content-length", self.original.length)
-        return ''
-
-    def render_GET(self, req):
-        self.req = req
-
-        if self.asText:
-            req.setHeader("content-type", "text/plain")
-        else:
-            req.setHeader("content-type", "text/html")
-
-        if not self.asText:
-            req.write(self.htmlHeader(req))
-
-        self.original.subscribeConsumer(ChunkConsumer(req, self))
-        return server.NOT_DONE_YET
-
-    def finished(self):
-        if not self.req:
-            return
-        try:
-            if not self.asText:
-                self.req.write(self.htmlFooter())
-            self.req.finish()
-        except pb.DeadReferenceError:
-            pass
-        # break the cycle, the Request's .notifications list includes the
-        # Deferred (from req.notifyFinish) that's pointing at us.
-        self.req = None
-
-components.registerAdapter(TextLog, interfaces.IStatusLog, IHTMLLog)
-
-
-class HTMLLog(Resource):
-    implements(IHTMLLog)
-
-    def __init__(self, original):
-        Resource.__init__(self)
-        self.original = original
-
-    def render(self, request):
-        request.setHeader("content-type", "text/html")
-        return self.original.html
-
-components.registerAdapter(HTMLLog, builder.HTMLLogFile, IHTMLLog)
-
-
-class CurrentBox(components.Adapter):
-    # this provides the "current activity" box, just above the builder name
-    implements(ICurrentBox)
-
-    def formatETA(self, eta):
-        if eta is None:
-            return []
-        if eta < 0:
-            return ["Soon"]
-        abstime = time.strftime("%H:%M:%S", time.localtime(util.now()+eta))
-        return ["ETA in", "%d secs" % eta, "at %s" % abstime]
-
-    def getBox(self, status):
-        # getState() returns offline, idle, or building
-        state, builds = self.original.getState()
-
-        # look for upcoming builds. We say the state is "waiting" if the
-        # builder is otherwise idle and there is a scheduler which tells us a
-        # build will be performed some time in the near future. TODO: this
-        # functionality used to be in BuilderStatus.. maybe this code should
-        # be merged back into it.
-        upcoming = []
-        builderName = self.original.getName()
-        for s in status.getSchedulers():
-            if builderName in s.listBuilderNames():
-                upcoming.extend(s.getPendingBuildTimes())
-        if state == "idle" and upcoming:
-            state = "waiting"
-
-        if state == "building":
-            color = "yellow"
-            text = ["building"]
-            if builds:
-                for b in builds:
-                    eta = b.getETA()
-                    if eta:
-                        text.extend(self.formatETA(eta))
-        elif state == "offline":
-            color = "red"
-            text = ["offline"]
-        elif state == "idle":
-            color = "white"
-            text = ["idle"]
-        elif state == "waiting":
-            color = "yellow"
-            text = ["waiting"]
-        else:
-            # just in case I add a state and forget to update this
-            color = "white"
-            text = [state]
-
-        # TODO: for now, this pending/upcoming stuff is in the "current
-        # activity" box, but really it should go into a "next activity" row
-        # instead. The only times it should show up in "current activity" is
-        # when the builder is otherwise idle.
-
-        # are any builds pending? (waiting for a slave to be free)
-        pbs = self.original.getPendingBuilds()
-        if pbs:
-            text.append("%d pending" % len(pbs))
-        for t in upcoming:
-            text.extend(["next at", 
-                         time.strftime("%H:%M:%S", time.localtime(t)),
-                         "[%d secs]" % (t - util.now()),
-                         ])
-            # TODO: the upcoming-builds box looks like:
-            #  ['waiting', 'next at', '22:14:15', '[86 secs]']
-            # while the currently-building box is reversed:
-            #  ['building', 'ETA in', '2 secs', 'at 22:12:50']
-            # consider swapping one of these to make them look the same. also
-            # consider leaving them reversed to make them look different.
-        return Box(text, color=color, class_="Activity " + state)
-
-components.registerAdapter(CurrentBox, builder.BuilderStatus, ICurrentBox)
-
-class ChangeBox(components.Adapter):
-    implements(IBox)
-
-    def getBox(self):
-        url = "changes/%d" % self.original.number
-        text = '<a href="%s">%s</a>' % (url, html.escape(self.original.who))
-        return Box([text], color="white", class_="Change")
-components.registerAdapter(ChangeBox, changes.Change, IBox)
-
-class BuildBox(components.Adapter):
-    # this provides the yellow "starting line" box for each build
-    implements(IBox)
-
-    def getBox(self):
-        b = self.original
-        name = b.getBuilder().getName()
-        number = b.getNumber()
-        url = "%s/builds/%d" % (urllib.quote(name, safe=''), number)
-        reason = b.getReason()
-        text = ('<a title="Reason: %s" href="%s">Build %d</a>'
-                % (html.escape(reason), url, number))
-        color = "yellow"
-        class_ = "start"
-        if b.isFinished() and not b.getSteps():
-            # the steps have been pruned, so there won't be any indication
-            # of whether it succeeded or failed. Color the box red or green
-            # to show its status
-            color = b.getColor()
-            class_ = build_get_class(b)
-        return Box([text], color=color, class_="BuildStep " + class_)
-components.registerAdapter(BuildBox, builder.BuildStatus, IBox)
+from buildbot.status.web.changes import StatusResourceChanges
+from buildbot.status.web.builder import StatusResourceBuilder
+from buildbot.status.web.build import StatusResourceBuild
+from buildbot.status.web.step import StatusResourceBuildStep
 
-class StepBox(components.Adapter):
-    implements(IBox)
 
-    def getBox(self):
-        b = self.original.getBuild()
-        urlbase = "%s/builds/%d/step-%s" % (
-            urllib.quote(b.getBuilder().getName(), safe=''),
-            b.getNumber(),
-            urllib.quote(self.original.getName(), safe=''))
-        text = self.original.getText()
-        if text is None:
-            log.msg("getText() gave None", urlbase)
-            text = []
-        text = text[:]
-        logs = self.original.getLogs()
-        for num in range(len(logs)):
-            name = logs[num].getName()
-            if logs[num].hasContents():
-                url = "%s/%d" % (urlbase, num)
-                text.append("<a href=\"%s\">%s</a>" % (url, html.escape(name)))
-            else:
-                text.append(html.escape(name))
-        urls = self.original.getURLs()
-        ex_url_class = "BuildStep external"
-        for name, target in urls.items():
-            text.append('[<a href="%s" class="%s">%s</a>]' %
-                        (target, ex_url_class, html.escape(name)))
-        color = self.original.getColor()
-        class_ = "BuildStep " + build_get_class(self.original)
-        return Box(text, color, class_=class_)
-components.registerAdapter(StepBox, builder.BuildStepStatus, IBox)
 
 class EventBox(components.Adapter):
     implements(IBox)
@@ -965,27 +43,6 @@
 components.registerAdapter(EventBox, builder.Event, IBox)
         
 
-class BuildTopBox(components.Adapter):
-    # this provides a per-builder box at the very top of the display,
-    # showing the results of the most recent build
-    implements(IBox)
-
-    def getBox(self):
-        assert interfaces.IBuilderStatus(self.original)
-        b = self.original.getLastFinishedBuild()
-        if not b:
-            return Box(["none"], "white", class_="LastBuild")
-        name = b.getBuilder().getName()
-        number = b.getNumber()
-        url = "%s/builds/%d" % (name, number)
-        text = b.getText()
-        # TODO: add logs?
-        # TODO: add link to the per-build page at 'url'
-        c = b.getColor()
-        class_ = build_get_class(b)
-        return Box(text, c, class_="LastBuild %s" % class_)
-components.registerAdapter(BuildTopBox, builder.BuilderStatus, ITopBox)
-
 class Spacer(builder.Event):
     def __init__(self, start, finish):
         self.started = start





More information about the Commits mailing list