| Index: third_party/buildbot_7_12/buildbot/status/web/base.py
|
| diff --git a/third_party/buildbot_7_12/buildbot/status/web/base.py b/third_party/buildbot_7_12/buildbot/status/web/base.py
|
| deleted file mode 100644
|
| index 4b5dfe5ade0513c2397b90c6895c5abb46d1bd44..0000000000000000000000000000000000000000
|
| --- a/third_party/buildbot_7_12/buildbot/status/web/base.py
|
| +++ /dev/null
|
| @@ -1,499 +0,0 @@
|
| -
|
| -import urlparse, urllib, time, re
|
| -from zope.interface import Interface
|
| -from twisted.python import log
|
| -from twisted.web import html, resource
|
| -from buildbot.status import builder
|
| -from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTION
|
| -from buildbot import version, util
|
| -from buildbot.process.properties import Properties
|
| -
|
| -import datetime
|
| -
|
| -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."""
|
| - def getBox(self, request):
|
| - """Return a Box instance, which can produce a <td> cell.
|
| - """
|
| -
|
| -class ICurrentBox(Interface):
|
| - """I represent the 'current activity' box, just above the builder name."""
|
| - def getBox(self, status):
|
| - """Return a Box instance, which can produce a <td> cell.
|
| - """
|
| -
|
| -class IBox(Interface):
|
| - """I represent a box in the waterfall display."""
|
| - def getBox(self, request):
|
| - """Return a Box instance, which wraps an Event and can produce a <td>
|
| - cell.
|
| - """
|
| -
|
| -class IHTMLLog(Interface):
|
| - pass
|
| -
|
| -css_classes = {SUCCESS: "success",
|
| - WARNINGS: "warnings",
|
| - FAILURE: "failure",
|
| - SKIPPED: "skipped",
|
| - EXCEPTION: "exception",
|
| - None: "",
|
| - }
|
| -
|
| -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}
|
| -
|
| -def make_name_user_passwd_form(useUserPasswd):
|
| - """helper function to create HTML prompt for 'name' when
|
| - C{useUserPasswd} is C{False} or 'username' / 'password' prompt
|
| - when C{True}."""
|
| -
|
| - if useUserPasswd:
|
| - label = "Your username:"
|
| - else:
|
| - label = "Your name:"
|
| - data = make_row(label, '<input type="text" name="username" />')
|
| - if useUserPasswd:
|
| - data += make_row("Your password:",
|
| - '<input type="password" name="passwd" />')
|
| - return data
|
| -
|
| -def make_stop_form(stopURL, useUserPasswd, on_all=False, label="Build"):
|
| - if on_all:
|
| - data = """<form method="post" action="%s" class='command stopbuild'>
|
| - <p>To stop all builds, fill out the following fields and
|
| - push the 'Stop' button</p>\n""" % stopURL
|
| - else:
|
| - data = """<form method="post" 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_name_user_passwd_form(useUserPasswd)
|
| - data += make_row("Reason for stopping build:",
|
| - "<input type='text' name='comments' />")
|
| - data += '<input type="submit" value="Stop %s" /></form>\n' % label
|
| - return data
|
| -
|
| -def make_extra_property_row(N):
|
| - """helper function to create the html for adding extra build
|
| - properties to a forced (or resubmitted) build. "N" is an integer
|
| - inserted into the form names so that more than one property can be
|
| - used in the form.
|
| - """
|
| - prop_html = '''
|
| - <div class="row">Property %(N)i
|
| - <span class="label">Name:</span>
|
| - <span class="field"><input type="text" name="property%(N)iname" /></span>
|
| - <span class="label">Value:</span>
|
| - <span class="field"><input type="text" name="property%(N)ivalue" /></span>
|
| - </div>
|
| - ''' % {"N": N}
|
| - return prop_html
|
| -
|
| -def make_force_build_form(forceURL, useUserPasswd, on_all=False):
|
| - if on_all:
|
| - data = """<form method="post" action="%s" class="command forcebuild">
|
| - <p>To force a build on all Builders, fill out the following fields
|
| - and push the 'Force Build' button</p>""" % forceURL
|
| - else:
|
| - data = """<form method="post" action="%s" class="command forcebuild">
|
| - <p>To force a build, fill out the following fields and
|
| - push the 'Force Build' button</p>""" % forceURL
|
| - return (data
|
| - + make_name_user_passwd_form(useUserPasswd)
|
| - + 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' />")
|
| - + make_extra_property_row(1)
|
| - + make_extra_property_row(2)
|
| - + make_extra_property_row(3)
|
| - + '<input type="submit" value="Force Build" /></form>\n')
|
| -
|
| -def getAndCheckProperties(req):
|
| - """
|
| -Fetch custom build properties from the HTTP request of a "Force build" or
|
| -"Resubmit build" HTML form.
|
| -Check the names for valid strings, and return None if a problem is found.
|
| -Return a new Properties object containing each property found in req.
|
| -"""
|
| - properties = Properties()
|
| - for i in (1,2,3):
|
| - pname = req.args.get("property%dname" % i, [""])[0]
|
| - pvalue = req.args.get("property%dvalue" % i, [""])[0]
|
| - if pname and pvalue:
|
| - if not re.match(r'^[\w\.\-\/\~:]*$', pname) \
|
| - or not re.match(r'^[\w\.\-\/\~:]*$', pvalue):
|
| - log.msg("bad property name='%s', value='%s'" % (pname, pvalue))
|
| - return None
|
| - properties.setProperty(pname, pvalue, "Force Build Form")
|
| - return properties
|
| -
|
| -def td(text="", parms={}, **props):
|
| - data = ""
|
| - data += " "
|
| - #if not props.has_key("border"):
|
| - # props["border"] = 1
|
| - props.update(parms)
|
| - 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", "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 isinstance(text, list):
|
| - data += "<br />".join(text)
|
| - 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]
|
| -
|
| -def path_to_root(request):
|
| - # /waterfall : ['waterfall'] -> ''
|
| - # /somewhere/lower : ['somewhere', 'lower'] -> '../'
|
| - # /somewhere/indexy/ : ['somewhere', 'indexy', ''] -> '../../'
|
| - # / : [] -> ''
|
| - if request.prepath:
|
| - segs = len(request.prepath) - 1
|
| - else:
|
| - segs = 0
|
| - root = "../" * segs
|
| - return root
|
| -
|
| -def path_to_builder(request, builderstatus):
|
| - return (path_to_root(request) +
|
| - "builders/" +
|
| - urllib.quote(builderstatus.getName(), safe=''))
|
| -
|
| -def path_to_build(request, buildstatus):
|
| - return (path_to_builder(request, buildstatus.getBuilder()) +
|
| - "/builds/%d" % buildstatus.getNumber())
|
| -
|
| -def path_to_step(request, stepstatus):
|
| - return (path_to_build(request, stepstatus.getBuild()) +
|
| - "/steps/%s" % urllib.quote(stepstatus.getName(), safe=''))
|
| -
|
| -def path_to_slave(request, slave):
|
| - return (path_to_root(request) +
|
| - "buildslaves/" +
|
| - urllib.quote(slave.getName(), safe=''))
|
| -
|
| -def path_to_change(request, change):
|
| - return (path_to_root(request) +
|
| - "changes/%s" % change.number)
|
| -
|
| -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=[], class_=None, urlbase=None,
|
| - **parms):
|
| - self.text = text
|
| - 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, class_=self.class_)
|
| -
|
| -
|
| -class HtmlResource(resource.Resource):
|
| - # this is a cheap sort of template thingy
|
| - contentType = "text/html; charset=UTF-8"
|
| - title = "Buildbot"
|
| - addSlash = False # adapted from Nevow
|
| -
|
| - def getChild(self, path, request):
|
| - if self.addSlash and path == "" and len(request.postpath) == 0:
|
| - return self
|
| - return resource.Resource.getChild(self, path, request)
|
| -
|
| - def render(self, request):
|
| - # tell the WebStatus about the HTTPChannel that got opened, so they
|
| - # can close it if we get reconfigured and the WebStatus goes away.
|
| - # They keep a weakref to this, since chances are good that it will be
|
| - # closed by the browser or by us before we get reconfigured. See
|
| - # ticket #102 for details.
|
| - if hasattr(request, "channel"):
|
| - # web.distrib.Request has no .channel
|
| - request.site.buildbot_service.registerChannel(request.channel)
|
| -
|
| - # Our pages no longer require that their URL end in a slash. Instead,
|
| - # they all use request.childLink() or some equivalent which takes the
|
| - # last path component into account. This clause is left here for
|
| - # historical and educational purposes.
|
| - if False and self.addSlash and request.prepath[-1] != '':
|
| - # this is intended to behave like request.URLPath().child('')
|
| - # but we need a relative URL, since we might be living behind a
|
| - # reverse proxy
|
| - #
|
| - # note that the Location: header (as used in redirects) are
|
| - # required to have absolute URIs, and my attempt to handle
|
| - # reverse-proxies gracefully violates rfc2616. This frequently
|
| - # works, but single-component paths sometimes break. The best
|
| - # strategy is to avoid these redirects whenever possible by using
|
| - # HREFs with trailing slashes, and only use the redirects for
|
| - # manually entered URLs.
|
| - url = request.prePathURL()
|
| - scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
|
| - new_url = request.prepath[-1] + "/"
|
| - if query:
|
| - new_url += "?" + query
|
| - request.redirect(new_url)
|
| - return ''
|
| -
|
| - 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 ''
|
| -
|
| - # Make sure we get fresh pages.
|
| - now = datetime.datetime.utcnow()
|
| - expires = now + datetime.timedelta(seconds=60)
|
| - request.setHeader("Expires", expires.strftime("%a, %d %b %Y %H:%M:%S GMT"))
|
| - request.setHeader("Pragma", "no-cache")
|
| -
|
| - return data
|
| -
|
| - def getStatus(self, request):
|
| - return request.site.buildbot_service.getStatus()
|
| -
|
| - def getControl(self, request):
|
| - return request.site.buildbot_service.getControl()
|
| -
|
| - def isUsingUserPasswd(self, request):
|
| - return request.site.buildbot_service.isUsingUserPasswd()
|
| -
|
| - def authUser(self, request):
|
| - user = request.args.get("username", ["<unknown>"])[0]
|
| - passwd = request.args.get("passwd", ["<no-password>"])[0]
|
| - if user == "<unknown>" or passwd == "<no-password>":
|
| - return False
|
| - return request.site.buildbot_service.authUser(user, passwd)
|
| -
|
| - def getChangemaster(self, request):
|
| - return request.site.buildbot_service.getChangeSvc()
|
| -
|
| - def path_to_root(self, request):
|
| - return path_to_root(request)
|
| -
|
| - def footer(self, status, req):
|
| - # TODO: this stuff should be generated by a template of some sort
|
| - projectURL = status.getProjectURL()
|
| - projectName = status.getProjectName()
|
| - data = '<hr /><div class="footer">\n'
|
| -
|
| - welcomeurl = self.path_to_root(req) + "index.html"
|
| - data += '[<a href="%s">welcome</a>]\n' % welcomeurl
|
| - data += "<br />\n"
|
| -
|
| - data += '<a href="http://buildbot.sourceforge.net/">Buildbot</a>'
|
| - data += "-%s " % version
|
| - if projectName:
|
| - data += "working for the "
|
| - if projectURL:
|
| - data += "<a href=\"%s\">%s</a> project." % (projectURL,
|
| - projectName)
|
| - else:
|
| - data += "%s project." % projectName
|
| - data += "<br />\n"
|
| - data += ("Page built: " +
|
| - time.strftime("%a %d %b %Y %H:%M:%S",
|
| - time.localtime(util.now()))
|
| - + "\n")
|
| - data += '</div>\n'
|
| -
|
| - return data
|
| -
|
| - def getTitle(self, request):
|
| - return self.title
|
| -
|
| - def fillTemplate(self, template, request):
|
| - s = request.site.buildbot_service
|
| - values = s.template_values.copy()
|
| - values['root'] = self.path_to_root(request)
|
| - # e.g. to reference the top-level 'buildbot.css' page, use
|
| - # "%(root)sbuildbot.css"
|
| - values['title'] = self.getTitle(request)
|
| - return template % values
|
| -
|
| - def content(self, request):
|
| - s = request.site.buildbot_service
|
| - data = ""
|
| - data += self.fillTemplate(s.header, request)
|
| - data += "<head>\n"
|
| - for he in s.head_elements:
|
| - data += " " + self.fillTemplate(he, request) + "\n"
|
| - data += self.head(request)
|
| - data += "</head>\n\n"
|
| -
|
| - data += '<body %s>\n' % " ".join(['%s="%s"' % (k,v)
|
| - for (k,v) in s.body_attrs.items()])
|
| - data += self.body(request)
|
| - data += "</body>\n"
|
| - data += self.fillTemplate(s.footer, request)
|
| - return data
|
| -
|
| - def head(self, request):
|
| - return ""
|
| -
|
| - 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
|
| -
|
| -MINUTE = 60
|
| -HOUR = 60*MINUTE
|
| -DAY = 24*HOUR
|
| -WEEK = 7*DAY
|
| -MONTH = 30*DAY
|
| -
|
| -def plural(word, words, num):
|
| - if int(num) == 1:
|
| - return "%d %s" % (num, word)
|
| - else:
|
| - return "%d %s" % (num, words)
|
| -
|
| -def abbreviate_age(age):
|
| - if age <= 90:
|
| - return "%s ago" % plural("second", "seconds", age)
|
| - if age < 90*MINUTE:
|
| - return "about %s ago" % plural("minute", "minutes", age / MINUTE)
|
| - if age < DAY:
|
| - return "about %s ago" % plural("hour", "hours", age / HOUR)
|
| - if age < 2*WEEK:
|
| - return "about %s ago" % plural("day", "days", age / DAY)
|
| - if age < 2*MONTH:
|
| - return "about %s ago" % plural("week", "weeks", age / WEEK)
|
| - return "a long time ago"
|
| -
|
| -
|
| -class OneLineMixin:
|
| - LINE_TIME_FORMAT = "%b %d %H:%M"
|
| -
|
| - def get_line_values(self, req, build):
|
| - '''
|
| - Collect the data needed for each line display
|
| - '''
|
| - builder_name = build.getBuilder().getName()
|
| - results = build.getResults()
|
| - text = build.getText()
|
| - try:
|
| - rev = build.getProperty("got_revision")
|
| - if rev is None:
|
| - rev = "??"
|
| - except KeyError:
|
| - rev = "??"
|
| - rev = str(rev)
|
| - if len(rev) > 40:
|
| - rev = "version is too-long"
|
| - root = self.path_to_root(req)
|
| - css_class = css_classes.get(results, "")
|
| - values = {'class': css_class,
|
| - 'builder_name': builder_name,
|
| - 'buildnum': build.getNumber(),
|
| - 'results': css_class,
|
| - 'text': " ".join(build.getText()),
|
| - 'buildurl': path_to_build(req, build),
|
| - 'builderurl': path_to_builder(req, build.getBuilder()),
|
| - 'rev': rev,
|
| - 'time': time.strftime(self.LINE_TIME_FORMAT,
|
| - time.localtime(build.getTimes()[0])),
|
| - }
|
| - return values
|
| -
|
| - def make_line(self, req, build, include_builder=True):
|
| - '''
|
| - Format and render a single line into HTML
|
| - '''
|
| - values = self.get_line_values(req, build)
|
| - fmt_pieces = ['<font size="-1">(%(time)s)</font>',
|
| - 'rev=[%(rev)s]',
|
| - '<span class="%(class)s">%(results)s</span>',
|
| - ]
|
| - if include_builder:
|
| - fmt_pieces.append('<a href="%(builderurl)s">%(builder_name)s</a>')
|
| - fmt_pieces.append('<a href="%(buildurl)s">#%(buildnum)d</a>:')
|
| - fmt_pieces.append('%(text)s')
|
| - data = " ".join(fmt_pieces) % values
|
| - return data
|
| -
|
| -def map_branches(branches):
|
| - # when the query args say "trunk", present that to things like
|
| - # IBuilderStatus.generateFinishedBuilds as None, since that's the
|
| - # convention in use. But also include 'trunk', because some VC systems
|
| - # refer to it that way. In the long run we should clean this up better,
|
| - # maybe with Branch objects or something.
|
| - if "trunk" in branches:
|
| - return branches + [None]
|
| - return branches
|
|
|