# HG changeset patch # User Sascha Teichmann # Date 1282320929 0 # Node ID 33323295377162d4168c545e03dbd6cb892c0f2a # Parent eacfd3744d16776e002529de393f42fa4e1dd912 Initial check-in of sawmill a simple mod_python based web application to render build reports of treepkg. diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/README --- /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. diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/details.py --- /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 +# +# 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 + }) diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/img/bottomleft-inner.png Binary file contrib/sawmill/web/img/bottomleft-inner.png has changed diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/img/bottomleft.png Binary file contrib/sawmill/web/img/bottomleft.png has changed diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/img/bottomright-inner.png Binary file contrib/sawmill/web/img/bottomright-inner.png has changed diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/img/bottomright.png Binary file contrib/sawmill/web/img/bottomright.png has changed diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/img/clear.png Binary file contrib/sawmill/web/img/clear.png has changed diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/img/logo.jpg Binary file contrib/sawmill/web/img/logo.jpg has changed diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/img/topleft-inner.png Binary file contrib/sawmill/web/img/topleft-inner.png has changed diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/img/topleft.png Binary file contrib/sawmill/web/img/topleft.png has changed diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/img/topright-inner.png Binary file contrib/sawmill/web/img/topright-inner.png has changed diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/img/topright.png Binary file contrib/sawmill/web/img/topright.png has changed diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/index.py --- /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 +# +# 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}) diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/styles/style.css --- /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; } diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/templates/details.html --- /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 @@ + +<% +from cgi import escape +from xml.sax.saxutils import quoteattr + +from datetime import date +%> + + + + + Sägewerker - Free Software forestry + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + +

<%= escape(description) %>

+<%= header %> + + + + + + + + + +<% + +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 +%> + +<% + # date changed +%> + + + + + + + + + +<% +# for all track itemes +%> + +
StatusPackageRevisionStartStopNotes
<%= pretty_time(last_date, "%Y-%m-%d") %>
<%= STATUS2MSG.get(track_item.build_status, 'error') %><%= nn(track_item.track) %><%= nn(track_item.revision) %><%= pretty_time(track_item.build_start) %><%= pretty_time(track_item.build_stop) %> +<% + for log_desc, log_path in track_item.build_logs: + # for all logs +%> +[><%= nn(log_desc) %>] + +<% + # for all logs +%> +
+ + + +
+ +
+ + + + + + +
+ + + +
+ +
+ + + + + + +
+
+
+ + This site is hosted by the Intevation GmbH + +
+ + + diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/templates/overview.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 @@ + +<% +from cgi import escape +from xml.sax.saxutils import quoteattr +%> + + + + + Sägewerker - Free Software forestry + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +<% +for treepkg, description in descriptions: + # for all descriptions +%> + +<% +# for all descriptions +%> + + + +
+ + + + + + +
+ + + +
+ +
+ + + + + + +
+
+
+ + This site is hosted by the Intevation GmbH + + +
+ + + diff -r eacfd3744d16 -r 333232953771 contrib/sawmill/web/treepkgs/demo/treepkg.xml --- /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 @@ + + + This is the status of the demo packager +
+
+

+ Put extra project information here. + Any type of HTML tags are allowed here +

+
+
+