📄 base.py
字号:
import urlparse, urllib, timefrom zope.interface import Interfacefrom twisted.web import html, resourcefrom buildbot.status import builderfrom buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, SKIPPED, EXCEPTIONfrom buildbot import version, utilclass 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): passcss_classes = {SUCCESS: "success", WARNINGS: "warnings", FAILURE: "failure", SKIPPED: "skipped", EXCEPTION: "exception", }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_stop_form(stopURL, on_all=False, label="Build"): if on_all: data = """<form 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 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 %s" /></form>\n' % label return datadef make_force_build_form(forceURL, on_all=False): if on_all: data = """<form 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 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_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>\n')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 datadef 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 rootdef 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=''))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 '' return data def getStatus(self, request): return request.site.buildbot_service.getStatus() def getControl(self, request): return request.site.buildbot_service.getControl() def getChangemaster(self, request): return request.site.buildbot_service.getChangeSvc() def path_to_root(self, request): return path_to_root(request) def footer(self, s, req): # TODO: this stuff should be generated by a template of some sort projectURL = s.getProjectURL() projectName = s.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.bodyHTMLMINUTE = 60HOUR = 60*MINUTEDAY = 24*HOURWEEK = 7*DAYMONTH = 30*DAYdef 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 datadef 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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -