buildbot.status.web.base: add comments to path_to_root
[buildbot.git] / buildbot / status / web / base.py
blobe00aac33c4a59bcc5cd7f6fbddc67ae285bb258a
2 from zope.interface import Interface
3 from twisted.web import html, resource
4 from buildbot.status import builder
5 from buildbot.status.builder import SUCCESS, WARNINGS, FAILURE, EXCEPTION
8 class ITopBox(Interface):
9 """I represent a box in the top row of the waterfall display: the one
10 which shows the status of the last build for each builder."""
11 pass
13 class ICurrentBox(Interface):
14 """I represent the 'current activity' box, just above the builder name."""
15 pass
17 class IBox(Interface):
18 """I represent a box in the waterfall display."""
19 pass
21 class IHTMLLog(Interface):
22 pass
24 css_classes = {SUCCESS: "success",
25 WARNINGS: "warnings",
26 FAILURE: "failure",
27 EXCEPTION: "exception",
30 ROW_TEMPLATE = '''
31 <div class="row">
32 <span class="label">%(label)s</span>
33 <span class="field">%(field)s</span>
34 </div>
35 '''
37 def make_row(label, field):
38 """Create a name/value row for the HTML.
40 `label` is plain text; it will be HTML-encoded.
42 `field` is a bit of HTML structure; it will not be encoded in
43 any way.
44 """
45 label = html.escape(label)
46 return ROW_TEMPLATE % {"label": label, "field": field}
48 colormap = {
49 'green': '#72ff75',
51 def td(text="", parms={}, **props):
52 data = ""
53 data += " "
54 #if not props.has_key("border"):
55 # props["border"] = 1
56 props.update(parms)
57 if props.has_key("bgcolor"):
58 props["bgcolor"] = colormap.get(props["bgcolor"], props["bgcolor"])
59 comment = props.get("comment", None)
60 if comment:
61 data += "<!-- %s -->" % comment
62 data += "<td"
63 class_ = props.get('class_', None)
64 if class_:
65 props["class"] = class_
66 for prop in ("align", "bgcolor", "colspan", "rowspan", "border",
67 "valign", "halign", "class"):
68 p = props.get(prop, None)
69 if p != None:
70 data += " %s=\"%s\"" % (prop, p)
71 data += ">"
72 if not text:
73 text = "&nbsp;"
74 if isinstance(text, list):
75 data += "<br />".join(text)
76 else:
77 data += text
78 data += "</td>\n"
79 return data
81 def build_get_class(b):
82 """
83 Return the class to use for a finished build or buildstep,
84 based on the result.
85 """
86 # FIXME: this getResults duplicity might need to be fixed
87 result = b.getResults()
88 #print "THOMAS: result for b %r: %r" % (b, result)
89 if isinstance(b, builder.BuildStatus):
90 result = b.getResults()
91 elif isinstance(b, builder.BuildStepStatus):
92 result = b.getResults()[0]
93 # after forcing a build, b.getResults() returns ((None, []), []), ugh
94 if isinstance(result, tuple):
95 result = result[0]
96 else:
97 raise TypeError, "%r is not a BuildStatus or BuildStepStatus" % b
99 if result == None:
100 # FIXME: this happens when a buildstep is running ?
101 return "running"
102 return builder.Results[result]
104 class Box:
105 # a Box wraps an Event. The Box has HTML <td> parameters that Events
106 # lack, and it has a base URL to which each File's name is relative.
107 # Events don't know about HTML.
108 spacer = False
109 def __init__(self, text=[], color=None, class_=None, urlbase=None,
110 **parms):
111 self.text = text
112 self.color = color
113 self.class_ = class_
114 self.urlbase = urlbase
115 self.show_idle = 0
116 if parms.has_key('show_idle'):
117 del parms['show_idle']
118 self.show_idle = 1
120 self.parms = parms
121 # parms is a dict of HTML parameters for the <td> element that will
122 # represent this Event in the waterfall display.
124 def td(self, **props):
125 props.update(self.parms)
126 text = self.text
127 if not text and self.show_idle:
128 text = ["[idle]"]
129 return td(text, props, bgcolor=self.color, class_=self.class_)
132 class HtmlResource(resource.Resource):
133 # this is a cheap sort of template thingy
134 contentType = "text/html; charset=UTF-8"
135 title = "Dummy"
136 addSlash = False # adapted from Nevow
138 def getChild(self, path, request):
139 if self.addSlash and path == "" and len(request.postpath) == 0:
140 return self
141 return resource.Resource.getChild(self, path, request)
143 def render(self, request):
144 if self.addSlash and request.prepath[-1] != '':
145 request.redirect(request.URLPath().child(''))
146 return ''
148 data = self.content(request)
149 if isinstance(data, unicode):
150 data = data.encode("utf-8")
151 request.setHeader("content-type", self.contentType)
152 if request.method == "HEAD":
153 request.setHeader("content-length", len(data))
154 return ''
155 return data
157 def getStatus(self, request):
158 return request.site.buildbot_service.getStatus()
159 def getControl(self, request):
160 return request.site.buildbot_service.getControl()
162 def getChangemaster(self, request):
163 return request.site.buildbot_service.parent.change_svc
165 def path_to_root(self, request):
166 # /waterfall : ['waterfall'] -> ''
167 # /somewhere/lower : ['somewhere', 'lower'] -> '../'
168 # /somewhere/indexy/ : ['somewhere', 'indexy', ''] -> '../../'
169 # / : [] -> ''
170 if request.prepath:
171 segs = len(request.prepath) - 1
172 else:
173 segs = 0
174 root = "../" * segs
175 return root
177 def getTitle(self, request):
178 return self.title
180 def fillTemplate(self, template, request):
181 s = request.site.buildbot_service
182 values = s.template_values.copy()
183 values['root'] = self.path_to_root(request)
184 # e.g. to reference the top-level 'buildbot.css' page, use
185 # "%(root)sbuildbot.css"
186 values['title'] = self.getTitle(request)
187 return template % values
189 def content(self, request):
190 s = request.site.buildbot_service
191 data = ""
192 data += self.fillTemplate(s.header, request)
193 data += "<head>\n"
194 for he in s.head_elements:
195 data += " " + self.fillTemplate(he, request) + "\n"
196 data += self.head(request)
197 data += "</head>\n\n"
199 data += '<body %s>\n' % " ".join(['%s="%s"' % (k,v)
200 for (k,v) in s.body_attrs.items()])
201 data += self.body(request)
202 data += "</body>\n"
203 data += self.fillTemplate(s.footer, request)
204 return data
206 def head(self, request):
207 return ""
209 def body(self, request):
210 return "Dummy\n"
212 class StaticHTML(HtmlResource):
213 def __init__(self, body, title):
214 HtmlResource.__init__(self)
215 self.bodyHTML = body
216 self.title = title
217 def body(self, request):
218 return self.bodyHTML
220 MINUTE = 60
221 HOUR = 60*MINUTE
222 DAY = 24*HOUR
223 WEEK = 7*DAY
224 MONTH = 30*DAY
226 def plural(word, words, num):
227 if int(num) == 1:
228 return "%d %s" % (num, word)
229 else:
230 return "%d %s" % (num, words)
232 def abbreviate_age(age):
233 if age <= 90:
234 return "%s ago" % plural("second", "seconds", age)
235 if age < 90*MINUTE:
236 return "about %s ago" % plural("minute", "minutes", age / MINUTE)
237 if age < DAY:
238 return "about %s ago" % plural("hour", "hours", age / HOUR)
239 if age < 2*WEEK:
240 return "about %s ago" % plural("day", "days", age / DAY)
241 if age < 2*MONTH:
242 return "about %s ago" % plural("week", "weeks", age / WEEK)
243 return "a long time ago"