Mercurial > treepkg
changeset 452:333232953771
Initial check-in of sawmill a simple mod_python based
web application to render build reports of treepkg.
author | Sascha Teichmann <teichmann@intevation.de> |
---|---|
date | Fri, 20 Aug 2010 16:15:29 +0000 |
parents | eacfd3744d16 |
children | dde2a0b68dc7 |
files | contrib/sawmill/README contrib/sawmill/web/details.py contrib/sawmill/web/img/bottomleft-inner.png contrib/sawmill/web/img/bottomleft.png contrib/sawmill/web/img/bottomright-inner.png contrib/sawmill/web/img/bottomright.png contrib/sawmill/web/img/clear.png contrib/sawmill/web/img/logo.jpg contrib/sawmill/web/img/topleft-inner.png contrib/sawmill/web/img/topleft.png contrib/sawmill/web/img/topright-inner.png contrib/sawmill/web/img/topright.png contrib/sawmill/web/index.py contrib/sawmill/web/styles/style.css contrib/sawmill/web/templates/details.html contrib/sawmill/web/templates/overview.html contrib/sawmill/web/treepkgs/demo/treepkg.xml |
diffstat | 17 files changed, 602 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/sawmill/README Fri Aug 20 16:15:29 2010 +0000 @@ -0,0 +1,2 @@ +A simple mod_python based web application to render the +build reports of treepkg.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/sawmill/web/details.py Fri Aug 20 16:15:29 2010 +0000 @@ -0,0 +1,155 @@ +# -*- coding: UTF-8 -*- +# +# Copyright (C) 2010 by Intevation GmbH +# Authors: +# Sascha L. Teichmann <sascha.teichmann@intevation.de> +# +# This program is free software under the GPL (>=v2) +# Read the file COPYING coming with the software for details. + +from mod_python import apache, psp, util + +import os +import re +import datetime +import time + +from lxml import etree + +BASE_DIR = "treepkgs" + +TREEPKG_DIR = os.path.join(os.path.dirname(__file__), BASE_DIR) + +STATUS_LINE = re.compile(r"^([^:]+):(.+)") + +UNDER_SCORE = re.compile(r"_+(\w)") + +def _create_time(s, format="%Y-%m-%d %H:%M:%S"): + return datetime.datetime(*(time.strptime(s, format)[0:6])) + +def _pretty_log_name(log): + log = log.replace(".txt", "").replace(".gz", "").capitalize() + return UNDER_SCORE.sub(lambda x: " %s" % x.group(1).upper(), log) + +class TrackItem(object): + + def __init__(self, treepkg, track, revision, status_file): + self.treepkg = treepkg + self.track = track + self.revision = revision + self.status_file = status_file + self.loaded = False + self.status = None + self.start = None + self.stop = None + self.logs = None + + def check_loaded(self): + if not self.loaded: + f = open(self.status_file) + try: + for line in f: + m = STATUS_LINE.match(line) + if not m: continue + key, value = [x.strip() for x in m.groups()] + + if key == 'status': self.status = value + elif key == 'start': self.start = _create_time(value) + elif key == 'stop': self.stop = _create_time(value) + finally: + f.close() + self.loaded = True + + def get_build_status(self): + self.check_loaded() + return self.status + + def get_build_start(self): + self.check_loaded() + return self.start + + def get_build_stop(self): + self.check_loaded() + return self.stop + + def log_path(self, log): + return "%s/tracks/%s/pkg/%s/log/%s" % ( + self.treepkg, self.track, self.revision, log) + + def get_build_logs(self): + oj = os.path.join + if self.logs is None: + log_dir = oj(os.path.dirname(self.status_file), "log") + if not os.path.isdir(log_dir): + self.logs = [] + else: + self.logs =[(_pretty_log_name(f), self.log_path(f)) + for f in os.listdir(log_dir) + if os.path.isfile(oj(log_dir, f)) and f.find("txt") >= 0] + return self.logs + + build_status = property(get_build_status) + build_start = property(get_build_start) + build_stop = property(get_build_stop) + build_logs = property(get_build_logs) + + +def __scan_track_items(treepkg, path): + items = [] + + tracks_path = os.path.join(path, "tracks") + for track in os.listdir(tracks_path): + track_path = os.path.join(tracks_path, track) + if not os.path.isdir(track_path): continue + revisions_path = os.path.join(track_path, "pkg") + for revision in os.listdir(revisions_path): + revision_path = os.path.join(revisions_path, revision) + if not os.path.isdir(revision_path): continue + status_file = os.path.join(revision_path, "status") + if not os.path.isfile(status_file): continue + items.append(TrackItem(treepkg, track, revision, status_file)) + + return items + +def __description_header(treepkg): + treepkg_xml = os.path.join(treepkg, "treepkg.xml") + if os.path.isfile(treepkg_xml): + xml = None + try: + xml = open(treepkg_xml, "rb") + dom = etree.parse(xml) + finally: + if xml: xml.close() + + description = ''.join(dom.xpath("//description/text()")) + header = ''.join([etree.tostring(x, encoding="UTF-8", method="html") + for x in dom.xpath("//header/*")]) + return description, header + return "unknown", "" + +def index(req, treepkg=''): + if not treepkg: util.redirect(req, "index.py") + + found = None + for d in os.listdir(TREEPKG_DIR): + dp = os.path.join(TREEPKG_DIR, d) + if os.path.isdir(dp) and d == treepkg: + found = dp + break + + if not found: + req.status = apache.HTTP_NOT_FOUND + return "requested TreePkg not found" + + description, header = __description_header(found) + + track_items = __scan_track_items(treepkg, found) + + req.content_type = 'text/html;charset=utf-8' + template = psp.PSP(req, filename='templates/details.html') + template.run({ + 'base_dir': BASE_DIR, + 'description': description, + 'header': header, + 'track_items': track_items + })
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/sawmill/web/index.py Fri Aug 20 16:15:29 2010 +0000 @@ -0,0 +1,40 @@ +# -*- coding: UTF-8 -*- +# +# Copyright (C) 2010 by Intevation GmbH +# Authors: +# Sascha L. Teichmann <sascha.teichmann@intevation.de> +# +# This program is free software under the GPL (>=v2) +# Read the file COPYING coming with the software for details. + +from mod_python import psp + +import os + +from lxml import etree + +TREEPKG_DIR = os.path.join(os.path.dirname(__file__), "treepkgs") + +def index(req): + req.content_type = 'text/html;charset=utf-8' + template = psp.PSP(req, filename='templates/overview.html') + + descriptions = [] + + for f in os.listdir(TREEPKG_DIR): + d = os.path.join(TREEPKG_DIR, f) + if not os.path.isdir(d): continue + treepkg_xml = os.path.join(d, "treepkg.xml") + if not os.path.isfile(treepkg_xml): continue + xml = None + try: + xml = open(treepkg_xml, "rb") + dom = etree.parse(xml) + finally: + if xml: xml.close() + + description = ''.join(dom.xpath("//description/text()")) + + descriptions.append((os.path.basename(d), description)) + + template.run({'descriptions': descriptions})
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/sawmill/web/styles/style.css Fri Aug 20 16:15:29 2010 +0000 @@ -0,0 +1,54 @@ +body { + margin-top: 3; + margin-left: 3; + margin-right: 3; + margin-bottom: 3; + background-color:#6190c0; +} + +ol,ul,p,body,td,tr,th,form { + font-family: verdana,arial,helvetica,sans-serif; + font-size:small; + color: #333333; +} + +h1 { font-size: x-large; font-family: verdana,arial,helvetica,sans-serif; } +h2 { font-size: large; font-family: verdana,arial,helvetica,sans-serif; } +h3 { font-size: medium; font-family: verdana,arial,helvetica,sans-serif; } +h4 { font-size: small; font-family: verdana,arial,helvetica,sans-serif; } +h5 { font-size: x-small; font-family: verdana,arial,helvetica,sans-serif; } +h6 { font-size: xx-small; font-family: verdana,arial,helvetica,sans-serif; } + +pre,tt { font-family: courier,sans-serif } + +a:link { text-decoration:none; color: #0000be } +a:visited { text-decoration:none; color: #0000be } +a:active { text-decoration:none } +a:hover { text-decoration:underline; color:red } + +.titlebar { color: black; text-decoration: none; font-weight: bold; } +a.tablink { color: black; text-decoration: none; font-weight: bold; font-size: x-small; } +a.tablink:visited { color: black; text-decoration: none; font-weight: bold; font-size: x-small; } +a.tablink:hover { text-decoration: none; color: black; font-weight: bold; font-size: x-small; } +a.tabsellink { color: #0000be; text-decoration: none; font-weight: bold; font-size: x-small; } +a.tabsellink:visited { color: #0000be; text-decoration: none; font-weight: bold; font-size: x-small; } +a.tabsellink:hover { text-decoration: none; color: #0000be; font-weight: bold; font-size: x-small; } + +.css_prison {} + +.css_prison .statustable { background:#F4F4F4; width:95% } +.css_prison .statustablestatus { background:#E0E0E0; width:15% } +.css_prison .statustablepkg { background:#E0E0E0; width:45% } +.css_prison .statustablenotes { background:#E0E0E0; width:10% } +.css_prison .statustablehead { background:#E0E0E0; } +.css_prison .statusheading { font-weight:bold; } +.css_prison .finished { background:#C0FFC0; } +.css_prison .inprogress { background:#FFFFC0; } +.css_prison .error { background:#FFC0C0; } +.css_prison .date_row { + background:#F0F0F0; + font-weight:bold; font-size:smaller; + text-align:center; +} +.css_prison tr { background:#FFFFFF; } +.css_prison td { padding:5px; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/sawmill/web/templates/details.html Fri Aug 20 16:15:29 2010 +0000 @@ -0,0 +1,206 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/html4/loose.dtd"> +<% +from cgi import escape +from xml.sax.saxutils import quoteattr + +from datetime import date +%> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" href="styles/style.css" type="text/css" media="screen" /> + <title>Sägewerker - Free Software forestry</title> + </head> + + <body> + <table border="0" width="100%" cellspacing="0" cellpadding="0"> + <tr> + <td> + <a href="/"><img src="img/logo.jpg" border="0" alt="" width="533" height="94" /></a> + </td> + </tr> + </table> + <table border="0" width="100%" cellspacing="0" cellpadding="0"> + + <tr> + <td align="left" bgcolor="#E0E0E0" width="9"> + <img src="img/topleft.png" height="9" width="9" alt="" /> + </td> + <td bgcolor="#E0E0E0" width="30"> + <img src="img/clear.png" width="30" height="1" alt="" /> + </td> + <td bgcolor="#E0E0E0"> + <img src="img/clear.png" width="1" height="1" alt="" /> + + </td> + <td bgcolor="#E0E0E0" width="30"> + <img src="img/clear.png" width="30" height="1" alt="" /> + </td> + <td align="right" bgcolor="#E0E0E0" width="9"> + <img src="img/topright.png" height="9" width="9" alt="" /> + </td> + </tr> + + <tr> + <!-- Outer body row --> + <td bgcolor="#E0E0E0"> + <img src="img/clear.png" width="10" height="1" alt="" /> + </td> + <td valign="top" width="99%" bgcolor="#E0E0E0" colspan="3"> + <!-- Inner Tabs / Shell --> + <table border="0" width="100%" cellspacing="0" cellpadding="0"> + <tr> + + <td align="left" bgcolor="#ffffff" width="9"> + <img src="img/topleft-inner.png" height="9" width="9" alt="" /> + </td> + <td bgcolor="#ffffff"> + <img src="img/clear.png" width="1" height="1" alt="" /> + </td> + <td align="right" bgcolor="#ffffff" width="9"> + <img src="img/topright-inner.png" height="9" width="9" alt="" /> + </td> + + </tr> + <tr> + <td bgcolor="#ffffff"> + <img src="img/clear.png" width="10" height="1" alt="" /> + </td> + <td valign="top" width="99%" bgcolor="white" class="css_prison"> + <!-- end main body row --> +<h1><%= escape(description) %></h1> +<%= header %> +<table class="statustable"> +<tr> + <th class="statustablehead">Status</th> + <th class="statustablehead">Package</th> + <th class="statustablehead">Revision</th> + <th class="statustablehead">Start</th> + <th class="statustablehead">Stop</th> + <th class="statustablehead">Notes</th> +</tr> +<% + +def nn(s, d=""): + if not s: return d + return escape(s) + +def pretty_time(t, format="%Y-%m-%d %H:%M:%S"): + if not t: return "<unknown>" + return t.strftime(format) + + +def date_from_datetime(x): + if not x: return None + return date(x.year, x.month, x.day) + +def sort_by_start(a, b): + a_start = a.build_start + b_start = b.build_start + if not a_start and not b_start: return 0 + if not a_start: return 1 + if not b_start: return -1 + return cmp(a_start, b_start) + +track_items = sorted(track_items, cmp=sort_by_start, reverse=True) + +last_date = None + +STATUS2CLASS = { + 'creating_binary_package': 'inprogress', + 'binary_package_created': 'finished' +} + +STATUS2MSG = { + 'creating_binary_package': 'building binary packages', + 'binary_package_created': 'build successful' +} + +for track_item in track_items: + # for all track items + curr_date = date_from_datetime(track_item.build_start) + if curr_date != last_date: + last_date = curr_date +%> +<tr class="date_row"><td colspan="6"><%= pretty_time(last_date, "%Y-%m-%d") %></td></tr> +<% + # date changed +%> +<tr class="<%= STATUS2CLASS.get(track_item.build_status, 'error') %>"> + <td><%= STATUS2MSG.get(track_item.build_status, 'error') %></td> + <td style="font-weight:bold;"><%= nn(track_item.track) %></td> + <td align="right"><%= nn(track_item.revision) %></td> + <td><%= pretty_time(track_item.build_start) %></td> + <td><%= pretty_time(track_item.build_stop) %></td> + <td> +<% + for log_desc, log_path in track_item.build_logs: + # for all logs +%> +[<a href=<%= quoteattr("%s/%s" % (base_dir, log_path)) %>><%= nn(log_desc) %></a>] + +<% + # for all logs +%> + </td> +</tr> + +<% +# for all track itemes +%> + +</table> + + + <!-- end main body row --> + </td> + <td width="10" bgcolor="#ffffff"> + <img src="img/clear.png" width="2" height="1" alt="" /> + </td> + </tr> + + <tr> + <td align="left" bgcolor="#E0E0E0" width="9"> + <img src="img/bottomleft-inner.png" height="11" width="11" alt="" /> + </td> + <td bgcolor="#ffffff"> + <img src="img/clear.png" width="1" height="1" alt="" /> + </td> + <td align="right" bgcolor="#E0E0E0" width="9"> + <img src="img/bottomright-inner.png" height="11" width="11" alt="" /> + + </td> + </tr> + </table> + + <!-- end inner body row --> + + </td> + <td width="10" bgcolor="#E0E0E0"> + <img src="img/clear.png" width="2" height="1" alt="" /> + </td> + + </tr> + <tr> + <td align="left" bgcolor="#E0E0E0" width="9"> + <img src="img/bottomleft.png" height="9" width="9" alt="" /> + </td> + <td bgcolor="#E0E0E0" colspan="3"> + <img src="img/clear.png" width="1" height="1" alt="" /> + </td> + <td align="right" bgcolor="#E0E0E0" width="9"> + + <img src="img/bottomright.png" height="9" width="9" alt="" /> + </td> + </tr> + </table> + <br /> + <center> + <b style="color:white; font-size:13px;"> + This site is hosted by the <a href="http://www.intevation.de">Intevation GmbH</a> + </b> + </center> + + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/sawmill/web/templates/overview.html Fri Aug 20 16:15:29 2010 +0000 @@ -0,0 +1,133 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/html4/loose.dtd"> +<% +from cgi import escape +from xml.sax.saxutils import quoteattr +%> +<html> + <head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" href="styles/style.css" type="text/css" media="screen" /> + <title>Sägewerker - Free Software forestry</title> + </head> + + <body> + <table border="0" width="100%" cellspacing="0" cellpadding="0"> + <tr> + <td> + <a href="/"><img src="img/logo.jpg" border="0" alt="" width="533" height="94" /></a> + </td> + </tr> + </table> + <table border="0" width="100%" cellspacing="0" cellpadding="0"> + + <tr> + <td align="left" bgcolor="#E0E0E0" width="9"> + <img src="img/topleft.png" height="9" width="9" alt="" /> + </td> + <td bgcolor="#E0E0E0" width="30"> + <img src="img/clear.png" width="30" height="1" alt="" /> + </td> + <td bgcolor="#E0E0E0"> + <img src="img/clear.png" width="1" height="1" alt="" /> + + </td> + <td bgcolor="#E0E0E0" width="30"> + <img src="img/clear.png" width="30" height="1" alt="" /> + </td> + <td align="right" bgcolor="#E0E0E0" width="9"> + <img src="img/topright.png" height="9" width="9" alt="" /> + </td> + </tr> + + <tr> + <!-- Outer body row --> + <td bgcolor="#E0E0E0"> + <img src="img/clear.png" width="10" height="1" alt="" /> + </td> + <td valign="top" width="99%" bgcolor="#E0E0E0" colspan="3"> + <!-- Inner Tabs / Shell --> + <table border="0" width="100%" cellspacing="0" cellpadding="0"> + <tr> + + <td align="left" bgcolor="#ffffff" width="9"> + <img src="img/topleft-inner.png" height="9" width="9" alt="" /> + </td> + <td bgcolor="#ffffff"> + <img src="img/clear.png" width="1" height="1" alt="" /> + </td> + <td align="right" bgcolor="#ffffff" width="9"> + <img src="img/topright-inner.png" height="9" width="9" alt="" /> + </td> + + </tr> + <tr> + <td bgcolor="#ffffff"> + <img src="img/clear.png" width="10" height="1" alt="" /> + </td> + <td valign="top" width="99%" bgcolor="white"> + <!-- end main body row --> + <div style="text-align: right"><a href="http://wald.intevation.de/">...to WALD source code repository</a></div> + +<% +for treepkg, description in descriptions: + # for all descriptions +%> +<div style="magin:5px;border-bottom:1px solid #DDDDDD;padding:5px;font-weight:bold; font-size: 16pt"> +<a href=<%= quoteattr("details.py?treepkg=%s" % treepkg) %>><%= escape(description) %></a> +</div> +<% +# for all descriptions +%> + <!-- end main body row --> + </td> + <td width="10" bgcolor="#ffffff"> + <img src="img/clear.png" width="2" height="1" alt="" /> + </td> + </tr> + + <tr> + <td align="left" bgcolor="#E0E0E0" width="9"> + <img src="img/bottomleft-inner.png" height="11" width="11" alt="" /> + </td> + <td bgcolor="#ffffff"> + <img src="img/clear.png" width="1" height="1" alt="" /> + </td> + <td align="right" bgcolor="#E0E0E0" width="9"> + <img src="img/bottomright-inner.png" height="11" width="11" alt="" /> + + </td> + </tr> + </table> + + <!-- end inner body row --> + + </td> + <td width="10" bgcolor="#E0E0E0"> + <img src="img/clear.png" width="2" height="1" alt="" /> + </td> + + </tr> + <tr> + <td align="left" bgcolor="#E0E0E0" width="9"> + <img src="img/bottomleft.png" height="9" width="9" alt="" /> + </td> + <td bgcolor="#E0E0E0" colspan="3"> + <img src="img/clear.png" width="1" height="1" alt="" /> + </td> + <td align="right" bgcolor="#E0E0E0" width="9"> + + <img src="img/bottomright.png" height="9" width="9" alt="" /> + </td> + </tr> + </table> + <br /> + <center> + <b style="color:white; font-size:13px;"> + This site is hosted by the <a href="http://www.intevation.de">Intevation GmbH</a> + + </b> + </center> + + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/sawmill/web/treepkgs/demo/treepkg.xml Fri Aug 20 16:15:29 2010 +0000 @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<treepkg> + <description>This is the status of the demo packager</description> + <header> + <div xmlns="http://www.w3.org/1999/xhtml"> + <p> + Put extra project information here. + <strong>Any type of HTML tags are allowed here</strong> + </p> + </div> + </header> +</treepkg>