aboutsummaryrefslogtreecommitdiffstats
path: root/interfaces
diff options
context:
space:
mode:
authorChris Ball <cjb@laptop.org>2009-07-23 17:49:13 -0400
committerChris Ball <cjb@laptop.org>2009-07-23 17:49:13 -0400
commit6a639574fa95e50f82fa3052e5524b961295a7ab (patch)
treeb498654ed1dcbdbba94605292c280c883c5e9faa /interfaces
parent5e249abfee7273c79640c4211607a6b4bf7b374c (diff)
parentcaf0111d9c571ac268c235880e6d18fa512e9efa (diff)
downloadbugseverywhere-6a639574fa95e50f82fa3052e5524b961295a7ab.tar.gz
Merge large rework from W. Trevor King.
Diffstat (limited to 'interfaces')
-rwxr-xr-xinterfaces/email/catmutt59
-rwxr-xr-xinterfaces/gui/beg/beg12
-rw-r--r--interfaces/gui/beg/table.py97
-rwxr-xr-xinterfaces/gui/wxbe/wxbe87
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/SOURCES.txt36
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/not-zip-safe0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/requires.txt1
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/sqlobject.txt2
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/top_level.txt2
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/PKG-INFO15
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/SOURCES.txt44
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/dependency_links.txt1
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/not-zip-safe1
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/paster_plugins.txt2
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/requires.txt1
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/sqlobject.txt2
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/top_level.txt1
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/README.txt42
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/__init__.py0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/app.cfg120
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/config.py.example10
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/config/app.cfg92
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/config/log.cfg29
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/controllers.py240
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/formatting.py76
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/json.py13
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/model.py107
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/prest.py168
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/release.py14
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/css/style.css116
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-b.pngbin0 -> 213 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-bl.pngbin0 -> 327 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-br.pngbin0 -> 365 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-l.pngbin0 -> 197 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-r.pngbin0 -> 214 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-t.pngbin0 -> 200 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tl.pngbin0 -> 240 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tr.pngbin0 -> 311 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-b.pngbin0 -> 206 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-r.pngbin0 -> 204 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.icobin0 -> 318 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.pngbin0 -> 267 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/half-spiral.pngbin0 -> 1112 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/header_inner.pngbin0 -> 37537 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/info.pngbin0 -> 2889 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-b.pngbin0 -> 200 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-bl.pngbin0 -> 408 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-br.pngbin0 -> 304 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-l.pngbin0 -> 214 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-r.pngbin0 -> 197 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-t.pngbin0 -> 213 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tl.pngbin0 -> 413 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tr.pngbin0 -> 414 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ok.pngbin0 -> 25753 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/shadows.pngbin0 -> 3960 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/spiral.pngbin0 -> 2120 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/tg_under_the_hood.pngbin0 -> 4010 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/static/images/under_the_hood_blue.pngbin0 -> 2667 bytes
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/__init__.py0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/about.kid21
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/bugs.kid52
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_bug.kid52
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_comment.kid26
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/error.kid14
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/login.kid113
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/master.kid71
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/projects.kid32
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/templates/welcome.kid50
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/tests/__init__.py0
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_controllers.py16
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_model.py23
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/dev.cfg71
l---------interfaces/web/Bugs-Everywhere-Web/libbe1
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/prod.cfg41
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/sample-prod.cfg71
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/server.log26
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/setup-tables.py34
-rw-r--r--interfaces/web/Bugs-Everywhere-Web/setup.py62
-rwxr-xr-xinterfaces/web/Bugs-Everywhere-Web/start-beweb.py28
-rwxr-xr-xinterfaces/xml/be-mbox-to-xml130
-rwxr-xr-xinterfaces/xml/be-xml-to-mbox205
81 files changed, 2529 insertions, 0 deletions
diff --git a/interfaces/email/catmutt b/interfaces/email/catmutt
new file mode 100755
index 0000000..601f14f
--- /dev/null
+++ b/interfaces/email/catmutt
@@ -0,0 +1,59 @@
+#!/bin/sh
+
+# catmutt - wrap mutt allowing mboxes read from stdin.
+#
+# Copyright (C) 1998-1999 Moritz Barsnick <barsnick (at) gmx (dot) net>,
+# 2009 William Trevor King <wking (at) drexel (dot) edu>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# version 2 as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# developed from grepm-0.6
+# http://www.barsnick.net/sw/grepm.html
+
+PROGNAME=`basename "$0"`
+export TMPDIR="${TMPDIR-/tmp}" # used by mktemp
+umask 077
+
+if [ $# -gt 0 ] && [ "$1" = "--help" ]; then
+ echo 1>&2 "Usage: ${PROGNAME} [--help] mutt-arguments"
+ echo 1>&2 ""
+ echo 1>&2 "Read a mailbox file from stdin and opens it with mutt."
+ echo 1>&2 "For example: cat somefile.mbox | ${PROGNAME}"
+ exit 0
+fi
+
+# Note: the -t/-p options to mktemp are deprecated for mktemp (GNU
+# coreutils) 7.1 in favor of --tmpdir but the --tmpdir option does not
+# exist yet for my 6.10-3ubuntu2 coreutils
+TMPFILE=`mktemp -t catmutt.XXXXXX` || exit 1
+
+trap "rm -f ${TMPFILE}; exit 1" 1 2 3 13 15
+
+cat > "${TMPFILE}" || exit 1
+
+# Now that we've read in the mailbox file, reopen stdin for mutt/user
+# interaction. When in a pipe we're not technically in a tty, so use
+# a little hack from "greno" at
+# http://www.linuxforums.org/forum/linux-programming-scripting/98607-bash-stdin-problem.html
+tty="/dev/`ps -p$$ --no-heading | awk '{print $2}'`"
+exec < ${tty}
+
+if [ `wc -c "${TMPFILE}" | awk '{print $1}'` -gt 0 ]; then
+ echo 1>&2 "Calling mutt on temporary mailbox file (${TMPFILE})."
+ mutt -R -f "${TMPFILE}" "$@"
+else
+ echo 1>&2 "Empty mailbox input."
+fi
+
+rm -f "${TMPFILE}" && echo 1>&2 "Deleted temporary mailbox file (${TMPFILE})."
diff --git a/interfaces/gui/beg/beg b/interfaces/gui/beg/beg
new file mode 100755
index 0000000..55e537d
--- /dev/null
+++ b/interfaces/gui/beg/beg
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+import table
+from Tkinter import *
+from libbe import bugdir
+
+tk = Tk()
+Label(tk, text="Bug list").pack()
+mlb = table.MultiListbox(tk, (('Severity', 4), ('Creator', 8), ('Summary', 40)))
+for bug in [b for b in bugdir.tree_root(".").list() if b.active]:
+ mlb.insert(END, (bug.severity, bug.creator, bug.summary))
+mlb.pack(expand=YES,fill=BOTH)
+tk.mainloop()
diff --git a/interfaces/gui/beg/table.py b/interfaces/gui/beg/table.py
new file mode 100644
index 0000000..2865f28
--- /dev/null
+++ b/interfaces/gui/beg/table.py
@@ -0,0 +1,97 @@
+from Tkinter import *
+
+class MultiListbox(Frame):
+ def __init__(self, master, lists):
+ Frame.__init__(self, master)
+ self.lists = []
+ for l,w in lists:
+ frame = Frame(self); frame.pack(side=LEFT, expand=YES, fill=BOTH)
+ Label(frame, text=l, borderwidth=1, relief=RAISED).pack(fill=X)
+ lb = Listbox(frame, width=w, borderwidth=0, selectborderwidth=0,
+ relief=FLAT, exportselection=FALSE)
+ lb.pack(expand=YES, fill=BOTH)
+ self.lists.append(lb)
+ lb.bind('<B1-Motion>', lambda e, s=self: s._select(e.y))
+ lb.bind('<Button-1>', lambda e, s=self: s._select(e.y))
+ lb.bind('<Leave>', lambda e: 'break')
+ lb.bind('<B2-Motion>', lambda e, s=self: s._b2motion(e.x, e.y))
+ lb.bind('<Button-2>', lambda e, s=self: s._button2(e.x, e.y))
+ frame = Frame(self); frame.pack(side=LEFT, fill=Y)
+ Label(frame, borderwidth=1, relief=RAISED).pack(fill=X)
+ sb = Scrollbar(frame, orient=VERTICAL, command=self._scroll)
+ sb.pack(expand=YES, fill=Y)
+ self.lists[0]['yscrollcommand']=sb.set
+
+ def _select(self, y):
+ row = self.lists[0].nearest(y)
+ self.selection_clear(0, END)
+ self.selection_set(row)
+ return 'break'
+
+ def _button2(self, x, y):
+ for l in self.lists: l.scan_mark(x, y)
+ return 'break'
+
+ def _b2motion(self, x, y):
+ for l in self.lists: l.scan_dragto(x, y)
+ return 'break'
+
+ def _scroll(self, *args):
+ for l in self.lists:
+ apply(l.yview, args)
+
+ def curselection(self):
+ return self.lists[0].curselection()
+
+ def delete(self, first, last=None):
+ for l in self.lists:
+ l.delete(first, last)
+
+ def get(self, first, last=None):
+ result = []
+ for l in self.lists:
+ result.append(l.get(first,last))
+ if last: return apply(map, [None] + result)
+ return result
+
+ def index(self, index):
+ self.lists[0].index(index)
+
+ def insert(self, index, *elements):
+ for e in elements:
+ i = 0
+ for l in self.lists:
+ l.insert(index, e[i])
+ i = i + 1
+
+ def size(self):
+ return self.lists[0].size()
+
+ def see(self, index):
+ for l in self.lists:
+ l.see(index)
+
+ def selection_anchor(self, index):
+ for l in self.lists:
+ l.selection_anchor(index)
+
+ def selection_clear(self, first, last=None):
+ for l in self.lists:
+ l.selection_clear(first, last)
+
+ def selection_includes(self, index):
+ return self.lists[0].selection_includes(index)
+
+ def selection_set(self, first, last=None):
+ for l in self.lists:
+ l.selection_set(first, last)
+
+if __name__ == '__main__':
+ tk = Tk()
+ Label(tk, text='MultiListbox').pack()
+ mlb = MultiListbox(tk, (('Subject', 40), ('Sender', 20), ('Date', 10)))
+ for i in range(1000):
+ mlb.insert(END, ('Important Message: %d' % i, 'John Doe', '10/10/%04d' % (1900+i)))
+ mlb.pack(expand=YES,fill=BOTH)
+ tk.mainloop()
+
diff --git a/interfaces/gui/wxbe/wxbe b/interfaces/gui/wxbe/wxbe
new file mode 100755
index 0000000..e71ae0c
--- /dev/null
+++ b/interfaces/gui/wxbe/wxbe
@@ -0,0 +1,87 @@
+#!/usr/bin/env python
+import wx
+from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
+import sys, os.path
+from libbe import bugdir, names
+from libbe.bug import cmp_status, cmp_severity, cmp_time, cmp_full
+
+class MyApp(wx.App):
+ def OnInit(self):
+ frame = BugListFrame(None, title="Bug List")
+ frame.Show(True)
+ self.SetTopWindow(frame)
+ return True
+
+class BugListFrame(wx.Frame):
+ def __init__(self, *args, **kwargs):
+ wx.Frame.__init__(self, *args, **kwargs)
+ bugs = BugList(self)
+
+ # Widgets to display/sort/edit will go in this panel
+ # for now it is just a placeholder
+ panel = wx.Panel(self)
+ panel.SetBackgroundColour("RED")
+
+ vbox = wx.BoxSizer(wx.VERTICAL)
+ vbox.Add(panel, 0, wx.EXPAND)
+ vbox.Add(bugs, 1, wx.EXPAND)
+
+ self.SetAutoLayout(True)
+ self.SetSizer(vbox)
+ self.Layout()
+
+class BugList(wx.ListCtrl, ListCtrlAutoWidthMixin):
+ def __init__(self, parent):
+ wx.ListCtrl.__init__(self, parent,
+ style=wx.LC_REPORT)
+ ListCtrlAutoWidthMixin.__init__(self)
+
+ self.bugdir = bugdir.tree_root(".")
+ self.buglist = list(self.bugdir.list())
+ self.buglist.sort()
+ self.columns = ("id", "status", "severity", "summary")
+
+ dataIndex = 0
+ for x in range(len(self.columns)):
+ self.InsertColumn(x, self.columns[x].capitalize())
+ self.SetColumnWidth(x, wx.LIST_AUTOSIZE_USEHEADER)
+ for bug in [b for b in self.buglist if b.active]:
+ name = names.unique_name(bug, self.buglist)
+ id = self.InsertStringItem(self.GetItemCount(), name)
+ self.SetStringItem(id, 1, bug.status)
+ self.SetStringItem(id, 2, bug.severity)
+ self.SetStringItem(id, 3, bug.summary)
+ self.SetItemData(id, dataIndex) # set keys for each line
+ dataIndex += 1
+ self.EnsureVisible(id)
+ for x in range(len(self.columns)):
+ self.SetColumnWidth(x, wx.LIST_AUTOSIZE)
+ conts_width = self.GetColumnWidth(x)
+ self.SetColumnWidth(x, wx.LIST_AUTOSIZE_USEHEADER)
+ if conts_width > self.GetColumnWidth(x):
+ self.SetColumnWidth(x, conts_width)
+
+ self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColumnClick)
+ self.bugcmp_fn = cmp_full
+ # For reasons I don't understant, sorting is broken...
+ #self.SortItems(self.Sorter)
+ #self.Refresh()
+ def Sorter(self, key1, key2):
+ """Get bug info from the keys and pass to self.bugcmp_fn"""
+ bug1 = self.buglist[key1-1]
+ bug2 = self.buglist[key2-1]
+ # Another way of getting bug information
+ #bug1uuid = self.GetItem(key1, 0).GetText()
+ #bug2uuid = self.GetItem(key2, 0).GetText()
+ #print bug1uuid, bug2uuid
+ #bug1 = self.bugdir.get_bug(bug1uuid)
+ #bug2 = self.bugdir.get_bug(bug1uuid)
+ print self.bugcmp_fn(bug1,bug2)
+ return self.bugcmp_fn(bug1,bug2)
+ def OnColumnClick(self, event):
+ """Resort bug list depending on which column was clicked"""
+ print "TODO: sort by column %d" % event.Column
+ # change self.bugcmp_fn and resort, but I can't get it working
+
+app = MyApp()
+app.MainLoop()
diff --git a/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/SOURCES.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/SOURCES.txt
new file mode 100644
index 0000000..def18b1
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/SOURCES.txt
@@ -0,0 +1,36 @@
+README.txt
+setup.py
+start-beweb.py
+Bugs-Everywhere-Web.egg-info/PKG-INFO
+Bugs-Everywhere-Web.egg-info/SOURCES.txt
+Bugs-Everywhere-Web.egg-info/not-zip-safe
+Bugs-Everywhere-Web.egg-info/requires.txt
+Bugs-Everywhere-Web.egg-info/sqlobject.txt
+Bugs-Everywhere-Web.egg-info/top_level.txt
+beweb/__init__.py
+beweb/config.py
+beweb/controllers.py
+beweb/formatting.py
+beweb/model.py
+beweb/prest.py
+beweb/release.py
+beweb/config/__init__.py
+beweb/templates/__init__.py
+beweb/tests/__init__.py
+beweb/tests/test_controllers.py
+beweb/tests/test_model.py
+libbe/__init__.py
+libbe/arch.py
+libbe/bugdir.py
+libbe/bzr.py
+libbe/cmdutil.py
+libbe/config.py
+libbe/diff.py
+libbe/mapfile.py
+libbe/names.py
+libbe/no_rcs.py
+libbe/plugin.py
+libbe/rcs.py
+libbe/restconvert.py
+libbe/tests.py
+libbe/utility.py
diff --git a/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/not-zip-safe b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/not-zip-safe
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/not-zip-safe
diff --git a/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/requires.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/requires.txt
new file mode 100644
index 0000000..88b15cb
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/requires.txt
@@ -0,0 +1 @@
+TurboGears >= 0.9a4 \ No newline at end of file
diff --git a/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/sqlobject.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/sqlobject.txt
new file mode 100644
index 0000000..7f7cbad
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/sqlobject.txt
@@ -0,0 +1,2 @@
+db_module=beweb.model
+history_dir=$base/beweb/sqlobject-history
diff --git a/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/top_level.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/top_level.txt
new file mode 100644
index 0000000..6455be9
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/top_level.txt
@@ -0,0 +1,2 @@
+beweb
+libbe
diff --git a/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/PKG-INFO b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/PKG-INFO
new file mode 100644
index 0000000..6cb6ad2
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/PKG-INFO
@@ -0,0 +1,15 @@
+Metadata-Version: 1.0
+Name: Bugs-Everywhere-Web
+Version: 1.0
+Summary: UNKNOWN
+Home-page: UNKNOWN
+Author: UNKNOWN
+Author-email: UNKNOWN
+License: UNKNOWN
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Framework :: TurboGears
diff --git a/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/SOURCES.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/SOURCES.txt
new file mode 100644
index 0000000..ab62ee4
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/SOURCES.txt
@@ -0,0 +1,44 @@
+README.txt
+setup.py
+start-beweb.py
+Bugs_Everywhere_Web.egg-info/PKG-INFO
+Bugs_Everywhere_Web.egg-info/SOURCES.txt
+Bugs_Everywhere_Web.egg-info/dependency_links.txt
+Bugs_Everywhere_Web.egg-info/not-zip-safe
+Bugs_Everywhere_Web.egg-info/paster_plugins.txt
+Bugs_Everywhere_Web.egg-info/requires.txt
+Bugs_Everywhere_Web.egg-info/sqlobject.txt
+Bugs_Everywhere_Web.egg-info/top_level.txt
+Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/SOURCES.txt
+Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/not-zip-safe
+Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/requires.txt
+Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/sqlobject.txt
+Bugs_Everywhere_Web.egg-info/Bugs-Everywhere-Web.egg-info/top_level.txt
+beweb/__init__.py
+beweb/config.py
+beweb/controllers.py
+beweb/formatting.py
+beweb/json.py
+beweb/model.py
+beweb/prest.py
+beweb/release.py
+beweb/config/__init__.py
+beweb/templates/__init__.py
+beweb/tests/__init__.py
+beweb/tests/test_controllers.py
+beweb/tests/test_model.py
+libbe/__init__.py
+libbe/arch.py
+libbe/bugdir.py
+libbe/bzr.py
+libbe/cmdutil.py
+libbe/config.py
+libbe/diff.py
+libbe/mapfile.py
+libbe/names.py
+libbe/no_rcs.py
+libbe/plugin.py
+libbe/rcs.py
+libbe/restconvert.py
+libbe/tests.py
+libbe/utility.py
diff --git a/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/dependency_links.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/not-zip-safe b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/not-zip-safe
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/not-zip-safe
@@ -0,0 +1 @@
+
diff --git a/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/paster_plugins.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/paster_plugins.txt
new file mode 100644
index 0000000..14fec70
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/paster_plugins.txt
@@ -0,0 +1,2 @@
+TurboGears
+PasteScript
diff --git a/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/requires.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/requires.txt
new file mode 100644
index 0000000..5fd6f71
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/requires.txt
@@ -0,0 +1 @@
+TurboGears >= 1.0b1 \ No newline at end of file
diff --git a/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/sqlobject.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/sqlobject.txt
new file mode 100644
index 0000000..7f7cbad
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/sqlobject.txt
@@ -0,0 +1,2 @@
+db_module=beweb.model
+history_dir=$base/beweb/sqlobject-history
diff --git a/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/top_level.txt b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/top_level.txt
new file mode 100644
index 0000000..74a8358
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/Bugs_Everywhere_Web.egg-info/top_level.txt
@@ -0,0 +1 @@
+beweb
diff --git a/interfaces/web/Bugs-Everywhere-Web/README.txt b/interfaces/web/Bugs-Everywhere-Web/README.txt
new file mode 100644
index 0000000..10774df
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/README.txt
@@ -0,0 +1,42 @@
+Bugs-Everywhere-Web
+
+This is a TurboGears (http://www.turbogears.org) project. It can be
+started by running the start-beweb.py script.
+
+Configure by creating an appropriate beweb/config.py from
+beweb/config.py.example. The server will edit the repositories that
+it manages, so you should probably have it running on a seperate
+branch than your working repository. You can then merge/push
+as you require to keep the branches in sync.
+
+See
+ http://docs.turbogears.org/1.0/Configuration
+For standard turbogears configuration information.
+
+Currently, you need to login for any methods with a
+@identity.require() decorator. The only group in the current
+implementation is 'editbugs'. Basically, anyone can browse around,
+but only registered 'editbugs' members can change things.
+
+Anonymous actions:
+ * See project tree
+ * See buglist
+ * See comments
+Editbugs required actions:
+ * Create new comments
+ * Reply to comments
+ * Update comment info
+
+
+All login attempts will fail unless you have added some valid users. See
+ http://docs.turbogears.org/1.0/GettingStartedWithIdentity
+For a good intro. For the impatient, try something like
+ Bugs-Everywhere-Web$ tg-admin toolbox
+ browse to 'CatWalk' -> 'User' -> 'Add User+'
+or
+ Bugs-Everywhere-Web$ tg-admin sholl
+ >>> u = User(user_name=u'jdoe', email_address=u'jdoe@example.com',
+ display_name=u'Jane Doe', password=u'xxx')
+ >>> g = Group(group_name=u'editbugs', display_name=u'Edit Bugs')
+ >>> g.addUser(u) # BE-Web uses SQLObject
+Exit the tg-admin shell with Ctrl-Z on MS Windows, Ctrl-D on other systems.
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/__init__.py b/interfaces/web/Bugs-Everywhere-Web/beweb/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/__init__.py
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/app.cfg b/interfaces/web/Bugs-Everywhere-Web/beweb/app.cfg
new file mode 100644
index 0000000..024fa8a
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/app.cfg
@@ -0,0 +1,120 @@
+[global]
+# The settings in this file should not vary depending on the deployment
+# environment. devcfg.py and prodcfg.py are the locations for
+# the different deployment settings. Settings in this file will
+# be overridden by settings in those other files.
+
+# The commented out values below are the defaults
+
+# VIEW
+
+# which view (template engine) to use if one is not specified in the
+# template name
+# tg.defaultview = "kid"
+
+# kid.outputformat="html"
+# kid.encoding="utf-8"
+
+# The sitetemplate is used for overall styling of a site that
+# includes multiple TurboGears applications
+# tg.sitetemplate="<packagename.templates.templatename>"
+
+# Allow every exposed function to be called as json,
+# tg.allow_json = False
+
+# Set to True if you'd like all of your pages to include MochiKit
+# tg.mochikit_all = False
+
+# VISIT TRACKING
+# Each visit to your application will be assigned a unique visit ID tracked via
+# a cookie sent to the visitor's browser.
+# --------------
+
+# Enable Visit tracking
+visit.on=True
+
+# Number of minutes a visit may be idle before it expires.
+# visit.timeout=20
+
+# The name of the cookie to transmit to the visitor's browser.
+# visit.cookie.name="tg-visit"
+
+# Domain name to specify when setting the cookie (must begin with . according to
+# RFC 2109). The default (None) should work for most cases and will default to
+# the machine to which the request was made. NOTE: localhost is NEVER a valid
+# value and will NOT WORK.
+# visit.cookie.domain=None
+
+# Specific path for the cookie
+# visit.cookie.path="/"
+
+# The name of the VisitManager plugin to use for visitor tracking.
+# visit.manager="sqlobject"
+
+
+# IDENTITY
+# General configuration of the TurboGears Identity management module
+# --------
+
+# Switch to turn on or off the Identity management module
+identity.on=True
+
+# [REQUIRED] URL to which CherryPy will internally redirect when an access
+# control check fails. If Identity management is turned on, a value for this
+# option must be specified.
+identity.failure_url="/login"
+
+# The IdentityProvider to use -- defaults to the SqlObjectIdentityProvider which
+# pulls User, Group, and Permission data out of your model database.
+identity.provider="sqlobject"
+
+# The names of the fields on the login form containing the visitor's user ID
+# and password. In addition, the submit button is specified simply so its
+# existence may be stripped out prior to passing the form data to the target
+# controller.
+identity.form.user_name="user_name"
+identity.form.password="password"
+identity.form.submit="login"
+
+# What sources should the identity provider consider when determining the
+# identity associated with a request? Comma separated list of identity sources.
+# Valid sources: form, visit, http_auth
+identity.source="form,http_auth,visit"
+
+
+# SqlObjectIdentityProvider
+# Configuration options for the default IdentityProvider
+# -------------------------
+
+# The classes you wish to use for your Identity model. Leave these commented out
+# to use the default classes for SqlObjectIdentityProvider. Or set them to the
+# classes in your model. NOTE: These aren't TG_* because the TG prefix is
+# reserved for classes created by TurboGears.
+# identity.soprovider.model.user="beweb.model.User"
+# identity.soprovider.model.group="beweb.model.Group"
+# identity.soprovider.model.permission="beweb.model.Permission"
+
+# The password encryption algorithm used when comparing passwords against what's
+# stored in the database. Valid values are 'md5' or 'sha1'. If you do not
+# specify an encryption algorithm, passwords are expected to be clear text.
+#
+# The SqlObjectProvider *will* encrypt passwords supplied as part of your login
+# form. If you set the password through the password property, like:
+# my_user.password = 'secret'
+# the password will be encrypted in the database, provided identity is up and
+# running, or you have loaded the configuration specifying what encryption to
+# use (in situations where identity may not yet be running, like tests).
+
+# identity.soprovider.encryption_algorithm=None
+
+[/static]
+static_filter.on = True
+static_filter.dir = "."
+
+[/favicon.ico]
+static_filter.on = True
+static_filter.file = "images/favicon.ico"
+
+[/]
+decodingFilter.on = True
+static_filter.root = '%(package_dir)s/static'
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/config.py.example b/interfaces/web/Bugs-Everywhere-Web/beweb/config.py.example
new file mode 100644
index 0000000..8745c6d
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/config.py.example
@@ -0,0 +1,10 @@
+# This is an example beweb configuration file.
+
+# One thing we need is a map of projects. Projects have a beweb ID, a path,
+# and a display name.
+
+# In this example, the 'be' beweb ID is assigned the display name "Bugs
+# Everywhere" and the path "/home/abentley/be"
+
+projects = {"be": ("Bugs Everywhere","/home/abentley/be"),
+ }
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/config/app.cfg b/interfaces/web/Bugs-Everywhere-Web/beweb/config/app.cfg
new file mode 100644
index 0000000..15555b7
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/config/app.cfg
@@ -0,0 +1,92 @@
+[global]
+# The settings in this file should not vary depending on the deployment
+# environment. dev.cfg and prod.cfg are the locations for
+# the different deployment settings. Settings in this file will
+# be overridden by settings in those other files.
+
+# The commented out values below are the defaults
+
+# VIEW
+
+# which view (template engine) to use if one is not specified in the
+# template name
+# tg.defaultview = "kid"
+
+# The following kid settings determine the settings used by the kid serializer.
+
+# One of (html|xml|json)
+# kid.outputformat="html"
+
+# kid.encoding="utf-8"
+
+# The sitetemplate is used for overall styling of a site that
+# includes multiple TurboGears applications
+# tg.sitetemplate="<packagename.templates.templatename>"
+
+# Allow every exposed function to be called as json,
+# tg.allow_json = False
+
+# List of Widgets to include on every page.
+# for exemple ['turbogears.mochikit']
+# tg.include_widgets = []
+
+# Set to True if the scheduler should be started
+# tg.scheduler = False
+
+# IDENTITY
+# General configuration of the TurboGears Identity management module
+# --------
+
+# Switch to turn on or off the Identity management module
+identity.on=True
+
+# [REQUIRED] URL to which CherryPy will internally redirect when an access
+# control check fails. If Identity management is turned on, a value for this
+# option must be specified.
+identity.failure_url="/login"
+
+# identity.provider='sqlobject'
+
+# The names of the fields on the login form containing the visitor's user ID
+# and password. In addition, the submit button is specified simply so its
+# existence may be stripped out prior to passing the form data to the target
+# controller.
+# identity.form.user_name="user_name"
+# identity.form.password="password"
+# identity.form.submit="login"
+
+# What sources should the identity provider consider when determining the
+# identity associated with a request? Comma separated list of identity sources.
+# Valid sources: form, visit, http_auth
+# identity.source="form,http_auth,visit"
+
+# SqlObjectIdentityProvider
+# Configuration options for the default IdentityProvider
+# -------------------------
+
+# The classes you wish to use for your Identity model. Remember to not use reserved
+# SQL keywords for class names (at least unless you specify a different table
+# name using sqlmeta).
+identity.soprovider.model.user="stfa.model.User"
+identity.soprovider.model.group="stfa.model.Group"
+identity.soprovider.model.permission="stfa.model.Permission"
+
+# The password encryption algorithm used when comparing passwords against what's
+# stored in the database. Valid values are 'md5' or 'sha1'. If you do not
+# specify an encryption algorithm, passwords are expected to be clear text.
+# The SqlObjectProvider *will* encrypt passwords supplied as part of your login
+# form. If you set the password through the password property, like:
+# my_user.password = 'secret'
+# the password will be encrypted in the database, provided identity is up and
+# running, or you have loaded the configuration specifying what encryption to
+# use (in situations where identity may not yet be running, like tests).
+
+# identity.soprovider.encryption_algorithm=None
+
+[/static]
+static_filter.on = True
+static_filter.dir = "%(top_level_dir)s/static"
+
+[/favicon.ico]
+static_filter.on = True
+static_filter.file = "%(top_level_dir)s/static/images/favicon.ico"
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/config/log.cfg b/interfaces/web/Bugs-Everywhere-Web/beweb/config/log.cfg
new file mode 100644
index 0000000..ce776f8
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/config/log.cfg
@@ -0,0 +1,29 @@
+# LOGGING
+# Logging is often deployment specific, but some handlers and
+# formatters can be defined here.
+
+[logging]
+[[formatters]]
+[[[message_only]]]
+format='*(message)s'
+
+[[[full_content]]]
+format='*(asctime)s *(name)s *(levelname)s *(message)s'
+
+[[handlers]]
+[[[debug_out]]]
+class='StreamHandler'
+level='DEBUG'
+args='(sys.stdout,)'
+formatter='full_content'
+
+[[[access_out]]]
+class='StreamHandler'
+level='INFO'
+args='(sys.stdout,)'
+formatter='message_only'
+
+[[[error_out]]]
+class='StreamHandler'
+level='ERROR'
+args='(sys.stdout,)'
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/controllers.py b/interfaces/web/Bugs-Everywhere-Web/beweb/controllers.py
new file mode 100644
index 0000000..a0d0ff9
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/controllers.py
@@ -0,0 +1,240 @@
+import logging
+
+import cherrypy
+import turbogears
+from turbogears import controllers, expose, validate, redirect, identity
+
+from libbe.bugdir import tree_root, NoRootEntry
+from config import projects
+from prest import PrestHandler, provide_action
+
+
+from beweb import json
+
+log = logging.getLogger("beweb.controllers")
+
+def project_tree(project):
+ try:
+ return tree_root(projects[project][1])
+ except KeyError:
+ raise Exception("Unknown project %s" % project)
+
+def comment_url(project, bug, comment, **kwargs):
+ return turbogears.url("/project/%s/bug/%s/comment/%s" %
+ (project, bug, comment), kwargs)
+
+class Comment(PrestHandler):
+ @identity.require( identity.has_permission("editbugs"))
+ @provide_action("action", "New comment")
+ def new_comment(self, comment_data, comment, *args, **kwargs):
+ bug_tree = project_tree(comment_data['project'])
+ bug = bug_tree.get_bug(comment_data['bug'])
+ comment = new_comment(bug, "")
+ comment.From = identity.current.user.userId
+ comment.content_type = "text/restructured"
+ comment.save()
+ raise cherrypy.HTTPRedirect(comment_url(comment=comment.uuid,
+ **comment_data))
+
+ @identity.require( identity.has_permission("editbugs"))
+ @provide_action("action", "Reply")
+ def reply_comment(self, comment_data, comment, *args, **kwargs):
+ bug_tree = project_tree(comment_data['project'])
+ bug = bug_tree.get_bug(comment_data['bug'])
+ reply_comment = new_comment(bug, "")
+ reply_comment.From = identity.current.user.userId
+ reply_comment.in_reply_to = comment.uuid
+ reply_comment.save()
+ reply_data = dict(comment_data)
+ del reply_data["comment"]
+ raise cherrypy.HTTPRedirect(comment_url(comment=reply_comment.uuid,
+ **reply_data))
+
+ @identity.require( identity.has_permission("editbugs"))
+ @provide_action("action", "Update")
+ def update(self, comment_data, comment, comment_body, *args, **kwargs):
+ comment.body = comment_body
+ comment.save()
+ raise cherrypy.HTTPRedirect(bug_url(comment_data['project'],
+ comment_data['bug']))
+
+ def instantiate(self, project, bug, comment):
+ bug_tree = project_tree(project)
+ bug = bug_tree.get_bug(bug)
+ return bug.get_comment(comment)
+
+ def dispatch(self, comment_data, comment, *args, **kwargs):
+ return self.edit_comment(comment_data['project'], comment)
+
+ @turbogears.expose(html="beweb.templates.edit_comment")
+ def edit_comment(self, project, comment):
+ return {"comment": comment, "project_id": project}
+
+class Bug(PrestHandler):
+ comment = Comment()
+ @turbogears.expose(html="beweb.templates.edit_bug")
+ def index(self, project, bug):
+ return {"bug": bug, "project_id": project}
+
+ def dispatch(self, bug_data, bug, *args, **kwargs):
+ if bug is None:
+ return self.list(bug_data['project'], **kwargs)
+ else:
+ return self.index(bug_data['project'], bug)
+
+ @turbogears.expose(html="beweb.templates.bugs")
+ def list(self, project, sort_by=None, show_closed=False, action=None,
+ search=None):
+ if action == "New bug":
+ self.new_bug()
+ if show_closed == "False":
+ show_closed = False
+ bug_tree = project_tree(project)
+ bugs = list(bug_tree.list())
+ if sort_by is None:
+ bugs.sort()
+ return {"project_id" : project,
+ "project_name" : projects[project][0],
+ "bugs" : bugs,
+ "show_closed" : show_closed,
+ "search" : search,
+ }
+
+ @identity.require( identity.has_permission("editbugs"))
+ @provide_action("action", "New bug")
+ def new_bug(self, bug_data, bug, **kwargs):
+ bug = project_tree(bug_data['project']).new_bug()
+ bug.creator = identity.current.user.userId
+ bug.save()
+ raise cherrypy.HTTPRedirect(bug_url(bug_data['project'], bug.uuid))
+
+ @identity.require( identity.has_permission("editbugs"))
+ @provide_action("action", "Update")
+ def update(self, bug_data, bug, status, severity, summary, assigned,
+ action):
+ bug.status = status
+ bug.severity = severity
+ bug.summary = summary
+ if assigned == "":
+ assigned = None
+ bug.assigned = assigned
+ bug.save()
+# bug.rcs.precommit(bug.path)
+# bug.rcs.commit(bug.path, "Auto-commit")
+# bug.rcs.postcommit(bug.path)
+ raise cherrypy.HTTPRedirect(bug_list_url(bug_data["project"]))
+
+ def instantiate(self, project, bug):
+ return project_tree(project).get_bug(bug)
+
+ @provide_action("action", "New comment")
+ def new_comment(self, bug_data, bug, *args, **kwargs):
+ try:
+ self.update(bug_data, bug, *args, **kwargs)
+ except cherrypy.HTTPRedirect:
+ pass
+ return self.comment.new_comment(bug_data, comment=None, *args,
+ **kwargs)
+
+
+def project_url(project_id=None):
+ project_url = "/project/"
+ if project_id is not None:
+ project_url += "%s/" % project_id
+ return turbogears.url(project_url)
+
+def bug_url(project_id, bug_uuid=None):
+ bug_url = "/project/%s/bug/" % project_id
+ if bug_uuid is not None:
+ bug_url += "%s/" % bug_uuid
+ return turbogears.url(bug_url)
+
+def bug_list_url(project_id, show_closed=False, search=None):
+ bug_url = "/project/%s/bug/?show_closed=%s" % (project_id,
+ str(show_closed))
+ if search is not None:
+ bug_url = "%s&search=%s" % (bug_url, search)
+ return turbogears.url(str(bug_url))
+
+
+class Project(PrestHandler):
+ bug = Bug()
+ @turbogears.expose(html="beweb.templates.projects")
+ def dispatch(self, project_data, project, *args, **kwargs):
+ if project is not None:
+ raise cherrypy.HTTPRedirect(bug_url(project))
+ else:
+ return {"projects": projects}
+
+ def instantiate(self, project):
+ return project
+
+
+class Root(controllers.Root):
+ prest = PrestHandler()
+ prest.project = Project()
+ @turbogears.expose()
+ def index(self):
+ raise cherrypy.HTTPRedirect(project_url())
+
+ @expose(template="beweb.templates.login")
+ def login(self, forward_url=None, previous_url=None, *args, **kw):
+
+ if not identity.current.anonymous and identity.was_login_attempted():
+ raise redirect(forward_url)
+
+ forward_url=None
+ previous_url= cherrypy.request.path
+
+ if identity.was_login_attempted():
+ msg=_("The credentials you supplied were not correct or "\
+ "did not grant access to this resource.")
+ elif identity.get_identity_errors():
+ msg=_("You must provide your credentials before accessing "\
+ "this resource.")
+ else:
+ msg=_("Please log in.")
+ forward_url= cherrypy.request.headers.get("Referer", "/")
+ cherrypy.response.status=403
+ return dict(message=msg, previous_url=previous_url, logging_in=True,
+ original_parameters=cherrypy.request.params,
+ forward_url=forward_url)
+
+ @expose()
+ def logout(self):
+ identity.current.logout()
+ raise redirect("/")
+
+ @turbogears.expose('beweb.templates.about')
+ def about(self, *paths, **kwargs):
+ return {}
+
+ @turbogears.expose()
+ def default(self, *args, **kwargs):
+ return self.prest.default(*args, **kwargs)
+
+ def _cp_on_error(self):
+ import traceback, StringIO
+ bodyFile = StringIO.StringIO()
+ traceback.print_exc(file = bodyFile)
+ trace_text = bodyFile.getvalue()
+ try:
+ raise
+ except cherrypy.NotFound:
+ self.handle_error('Not Found', str(e), trace_text, '404 Not Found')
+
+ except NoRootEntry, e:
+ self.handle_error('Project Misconfiguration', str(e), trace_text)
+
+ except Exception, e:
+ self.handle_error('Internal server error', str(e), trace_text)
+
+ def handle_error(self, heading, body, traceback=None,
+ status='500 Internal Server Error'):
+ cherrypy.response.headerMap['Status'] = status
+ cherrypy.response.body = [self.errorpage(heading, body, traceback)]
+
+
+ @turbogears.expose(html='beweb.templates.error')
+ def errorpage(self, heading, body, traceback):
+ return {'heading': heading, 'body': body, 'traceback': traceback}
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/formatting.py b/interfaces/web/Bugs-Everywhere-Web/beweb/formatting.py
new file mode 100644
index 0000000..1278414
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/formatting.py
@@ -0,0 +1,76 @@
+from StringIO import StringIO
+
+try :
+ from xml.etree.ElementTree import XML # Python 2.5 (and greater?)
+except ImportError :
+ from elementtree.ElementTree import XML
+from libbe.restconvert import rest_xml
+
+def to_unix(text):
+ skip_newline = False
+ for ch in text:
+ if ch not in ('\r', '\n'):
+ yield ch
+ else:
+ if ch == '\n':
+ if skip_newline:
+ continue
+ else:
+ skip_newline = True
+ yield '\n'
+
+
+def soft_text(text):
+ first_space = False
+ translations = {'\n': '<br />\n', '&': '&amp;', '\x3c': '&lt;',
+ '\x3e': '&gt;'}
+ for ch in to_unix(text):
+ if ch == ' ' and first_space is True:
+ yield '&#160;'
+ first_space = ch in (' ')
+ try:
+ yield translations[ch]
+ except KeyError:
+ yield ch
+
+
+def soft_pre(text):
+ return XML('<div style="font-family: monospace">'+
+ ''.join(soft_text(text)).encode('utf-8')+'</div>')
+
+
+def get_rest_body(rest):
+ xml, warnings = rest_xml(StringIO(rest))
+ return xml.find('{http://www.w3.org/1999/xhtml}body'), warnings
+
+
+def comment_body_xhtml(comment):
+ if comment.content_type == "text/restructured":
+ return get_rest_body(comment.body)[0]
+ else:
+ return soft_pre(comment.body)
+
+
+def select_among(name, options, default, display_names=None):
+ output = ['<select name="%s">' % name]
+ for option in options:
+ if option == default:
+ selected = ' selected="selected"'
+ else:
+ selected = ""
+ if display_names is None:
+ display_name = None
+ else:
+ display_name = display_names.get(option)
+
+ if option is None:
+ option = ""
+ if display_name is None:
+ display_name = option
+ value = ""
+ else:
+ value = ' value="%s"' % option
+ output.append("<option%s%s>%s</option>" % (selected, value,
+ display_name))
+ output.append("</select>")
+ return XML("".join(output))
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/json.py b/interfaces/web/Bugs-Everywhere-Web/beweb/json.py
new file mode 100644
index 0000000..6e100c3
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/json.py
@@ -0,0 +1,13 @@
+# This module provides helper functions for the JSON part of your
+# view, if you are providing a JSON-based API for your app.
+
+# Here's what most rules would look like:
+# @jsonify.when("isinstance(obj, YourClass)")
+# def jsonify_yourclass(obj):
+# return [obj.val1, obj.val2]
+#
+# The goal is to break your objects down into simple values:
+# lists, dicts, numbers and strings
+
+from turbojson.jsonify import jsonify
+
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/model.py b/interfaces/web/Bugs-Everywhere-Web/beweb/model.py
new file mode 100644
index 0000000..aa4b6b6
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/model.py
@@ -0,0 +1,107 @@
+from datetime import datetime
+
+from sqlobject import *
+from turbogears.database import PackageHub
+from turbogears import identity
+
+hub = PackageHub("beweb")
+__connection__ = hub
+
+class Visit(SQLObject):
+ class sqlmeta:
+ table = "visit"
+
+ visit_key = StringCol(length=40, alternateID=True,
+ alternateMethodName="by_visit_key")
+ created = DateTimeCol(default=datetime.now)
+ expiry = DateTimeCol()
+
+ def lookup_visit(cls, visit_key):
+ try:
+ return cls.by_visit_key(visit_key)
+ except SQLObjectNotFound:
+ return None
+ lookup_visit = classmethod(lookup_visit)
+
+class VisitIdentity(SQLObject):
+ visit_key = StringCol(length=40, alternateID=True,
+ alternateMethodName="by_visit_key")
+ user_id = IntCol()
+
+
+class Group(SQLObject):
+ """
+ An ultra-simple group definition.
+ """
+
+ # names like "Group", "Order" and "User" are reserved words in SQL
+ # so we set the name to something safe for SQL
+ class sqlmeta:
+ table = "tg_group"
+
+ group_name = UnicodeCol(length=16, alternateID=True,
+ alternateMethodName="by_group_name")
+ display_name = UnicodeCol(length=255)
+ created = DateTimeCol(default=datetime.now)
+
+ # collection of all users belonging to this group
+ users = RelatedJoin("User", intermediateTable="user_group",
+ joinColumn="group_id", otherColumn="user_id")
+
+ # collection of all permissions for this group
+ permissions = RelatedJoin("Permission", joinColumn="group_id",
+ intermediateTable="group_permission",
+ otherColumn="permission_id")
+
+
+class User(SQLObject):
+ """
+ Reasonably basic User definition. Probably would want additional attributes.
+ """
+ # names like "Group", "Order" and "User" are reserved words in SQL
+ # so we set the name to something safe for SQL
+ class sqlmeta:
+ table = "tg_user"
+
+ child_name = UnicodeCol(length=255)
+ user_name = UnicodeCol(length=16, alternateID=True,
+ alternateMethodName="by_user_name")
+ email_address = UnicodeCol(length=255, alternateID=True,
+ alternateMethodName="by_email_address")
+ display_name = UnicodeCol(length=255)
+ password = UnicodeCol(length=40)
+ created = DateTimeCol(default=datetime.now)
+
+ # groups this user belongs to
+ groups = RelatedJoin("Group", intermediateTable="user_group",
+ joinColumn="user_id", otherColumn="group_id")
+
+ def _get_permissions(self):
+ perms = set()
+ for g in self.groups:
+ perms = perms | set(g.permissions)
+ return perms
+
+ def _set_password(self, cleartext_password):
+ "Runs cleartext_password through the hash algorithm before saving."
+ hash = identity.encrypt_password(cleartext_password)
+ self._SO_set_password(hash)
+
+ def set_password_raw(self, password):
+ "Saves the password as-is to the database."
+ self._SO_set_password(password)
+
+
+
+class Permission(SQLObject):
+ permission_name = UnicodeCol(length=16, alternateID=True,
+ alternateMethodName="by_permission_name")
+ description = UnicodeCol(length=255)
+
+ groups = RelatedJoin("Group",
+ intermediateTable="group_permission",
+ joinColumn="permission_id",
+ otherColumn="group_id")
+
+def people_map():
+ return dict((u.user_name, u.display_name) for u in User.select())
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/prest.py b/interfaces/web/Bugs-Everywhere-Web/beweb/prest.py
new file mode 100644
index 0000000..9a6505d
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/prest.py
@@ -0,0 +1,168 @@
+from unittest import TestCase
+import unittest
+from cherrypy import NotFound
+"""A pseudo-REST dispatching method in which only the noun comes from the path.
+The action performed will depend on kwargs.
+"""
+
+class AmbiguousAction(Exception):
+ def __init__(self, actions):
+ Exception.__init__(self, "Supplied action is ambiguous.")
+ self.actions = actions
+
+
+def provide_action(name, value):
+ def provider(func):
+ func._action_desc = (name, value)
+ return func
+ return provider
+
+class PrestHandler(object):
+ def __init__(self):
+ object.__init__(self)
+ self.actions = {}
+ for member in (getattr(self, m) for m in dir(self)):
+ if not hasattr(member, '_action_desc'):
+ continue
+ name, value = member._action_desc
+ if name not in self.actions:
+ self.actions[name] = {}
+ self.actions[name][value] = member
+
+ @classmethod
+ def add_action(klass, name, value, function):
+ if name not in klass.actions:
+ klass.actions[name] = {}
+ klass.actions[name][value] = function
+
+
+ def decode(self, path, data=None):
+ """Convert the path into a handler, a resource, data, and extra_path"""
+ if data is None:
+ data = {}
+ if len(path) < 2 or not (hasattr(self, path[1])):
+ if len(path) == 0:
+ resource = None
+ else:
+ try:
+ resource = self.instantiate(**data)
+ except NotImplementedError, e:
+ if e.args[0] is not PrestHandler.instantiate:
+ raise NotFound()
+
+ return self, resource, data, path[1:]
+ if len(path) > 2:
+ data[path[1]] = path[2]
+ return getattr(self, path[1]).decode(path[2:], data)
+
+ def instantiate(self, **date):
+ raise NotImplementedError(PrestHandler.instantiate)
+
+ def default(self, *args, **kwargs):
+ child, resource, data, extra = self.decode([None,] + list(args))
+ action = child.get_action(**kwargs)
+ new_args = ([data, resource]+extra)
+ if action is not None:
+ return action(*new_args, **kwargs)
+ else:
+ return child.dispatch(*new_args, **kwargs)
+
+ def get_action(self, **kwargs):
+ """Return the action requested by kwargs, if any.
+
+ Raises AmbiguousAction if more than one action matches.
+ """
+ actions = []
+ for key in kwargs:
+ if key in self.actions:
+ if kwargs[key] in self.actions[key]:
+ actions.append(self.actions[key][kwargs[key]])
+ if len(actions) == 0:
+ return None
+ elif len(actions) == 1:
+ return actions[0]
+ else:
+ raise AmbiguousAction(actions)
+
+
+class PrestTester(TestCase):
+ def test_decode(self):
+ class ProjectHandler(PrestHandler):
+ actions = {}
+ def dispatch(self, project_data, project, *args, **kwargs):
+ self.project_id = project_data['project']
+ self.project_data = project_data
+ self.resource = project
+ self.args = args
+ self.kwargs = kwargs
+
+ def instantiate(self, project):
+ return [project]
+
+ @provide_action('action', 'Save')
+ def save(self, project_data, project, *args, **kwargs):
+ self.action = "save"
+
+ @provide_action('behavior', 'Update')
+ def update(self, project_data, project, *args, **kwargs):
+ self.action = "update"
+
+ foo = PrestHandler()
+ foo.project = ProjectHandler()
+ handler, resource, data, extra = foo.decode([None, 'project', '83',
+ 'bloop', 'yeah'])
+ assert handler is foo.project
+ self.assertEqual({'project': '83'}, data)
+ self.assertEqual(['bloop', 'yeah'], extra)
+ foo.default(*['project', '27', 'extra'], **{'a':'b', 'b':'97'})
+ self.assertEqual(foo.project.args, ('extra',))
+ self.assertEqual(foo.project.kwargs, {'a':'b', 'b':'97'})
+ self.assertEqual(foo.project.project_data, {'project': '27'})
+ self.assertEqual(foo.project.resource, ['27'])
+ foo.default(*['project', '27', 'extra'], **{'action':'Save', 'b':'97'})
+ self.assertEqual(foo.project.action, 'save')
+ foo.default(*['project', '27', 'extra'],
+ **{'behavior':'Update', 'b':'97'})
+ self.assertEqual(foo.project.action, 'update')
+ self.assertRaises(AmbiguousAction, foo.default,
+ *['project', '27', 'extra'],
+ **{'behavior':'Update', 'action':'Save', 'b':'97'})
+
+ class BugHandler(PrestHandler):
+ actions = {}
+ def dispatch(self, bug_data, bug, *args, **kwargs):
+ self.project_id = project_data['project']
+ self.project_data = project_data
+ self.resource = project
+ self.args = args
+ self.kwargs = kwargs
+
+ def instantiate(self, project, bug):
+ return [project, bug]
+
+ @provide_action('action', 'Save')
+ def save(self, project_data, project, *args, **kwargs):
+ self.action = "save"
+
+ @provide_action('behavior', 'Update')
+ def update(self, project_data, project, *args, **kwargs):
+ self.action = "update"
+
+ foo.project.bug = BugHandler()
+ handler, resource, data, extra = foo.decode([None, 'project', '83',
+ 'bug', '92'])
+ assert handler is foo.project.bug
+ self.assertEqual(resource[0], '83')
+ self.assertEqual(resource[1], '92')
+ self.assertEqual([], extra)
+ self.assertEqual(data['project'], '83')
+ self.assertEqual(data['bug'], '92')
+
+def test():
+ patchesTestSuite = unittest.makeSuite(PrestTester,'test')
+ runner = unittest.TextTestRunner(verbosity=0)
+ return runner.run(patchesTestSuite)
+
+
+if __name__ == "__main__":
+ test()
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/release.py b/interfaces/web/Bugs-Everywhere-Web/beweb/release.py
new file mode 100644
index 0000000..9d64bf7
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/release.py
@@ -0,0 +1,14 @@
+# Release information about Bugs-Everywhere-Web
+
+version = "1.0"
+
+# description = "Your plan to rule the world"
+# long_description = "More description about your plan"
+# author = "Your Name Here"
+# email = "YourEmail@YourDomain"
+# copyright = "Vintage 2006 - a good year indeed"
+
+# if it's open source, you might want to specify these
+# url = "http://yourcool.site/"
+# download_url = "http://yourcool.site/download"
+# license = "MIT"
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/css/style.css b/interfaces/web/Bugs-Everywhere-Web/beweb/static/css/style.css
new file mode 100644
index 0000000..6fe197f
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/css/style.css
@@ -0,0 +1,116 @@
+table
+{
+ background-color: black;
+}
+td
+{
+ background-color: white;
+}
+h1
+{
+ font-family: "Verdana";
+ font-weight: bold;
+ font-size: 120%;
+ margin-bottom:0;
+ color: #990;
+}
+
+tr.closed td
+{
+ background-color: #ccc;
+}
+tr.closedeven td
+{
+ background-color: #ccc;
+}
+tr.closedodd td
+{
+ background-color: #dda;
+}
+
+a:visited, a:link
+{
+ color: #990;
+ text-decoration: None;
+}
+td a:visited, td a:link
+{
+ display: block;
+}
+a:visited:hover, a:link:hover
+{
+ text-decoration: underline;
+}
+td a:visited:hover, td a:link:hover
+{
+ color:black;
+ background-color:#dda;
+ text-decoration: None;
+ display: block;
+}
+
+body
+{
+ font-family: "Verdana";
+ font-size:11pt;
+ background-color: white;
+}
+.comment
+{
+}
+.comment table
+{
+ background-color: transparent;
+}
+.comment td
+{
+ background-color: transparent;
+}
+.comment pre
+{
+ font-family: "Verdana";
+}
+#header
+{
+ color: black;
+ font-weight: bold;
+ background-image: url(/static/images/half-spiral.png);
+ background-position: right center;
+ background-repeat: no-repeat;
+ background-color: #ff0;
+}
+#header ul.navoption
+{
+ display: block;
+ float: right;
+ margin: 0;
+ padding-right: 30px;
+}
+#header li
+{
+ display: inline;
+ margin:0;
+ padding:0;
+}
+table.insetbox
+{
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+.insetbox tr, .insetbox td
+{
+ margin: 0;
+ padding: 0;
+}
+pre.traceback
+{
+ font-family: Verdana, Ariel, Helvetica, sanserif;
+}
+tr.even td
+{
+ background-color: #eee;
+}
+tr.odd td
+{
+ background-color: #ffe;
+}
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-b.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-b.png
new file mode 100644
index 0000000..790e438
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-b.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-bl.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-bl.png
new file mode 100644
index 0000000..5b43259
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-bl.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-br.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-br.png
new file mode 100644
index 0000000..6cfd62c
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-br.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-l.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-l.png
new file mode 100644
index 0000000..a6ce3ce
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-l.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-r.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-r.png
new file mode 100644
index 0000000..1ffd6f8
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-r.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-t.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-t.png
new file mode 100644
index 0000000..0129b0c
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-t.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tl.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tl.png
new file mode 100644
index 0000000..d616b77
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tl.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tr.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tr.png
new file mode 100644
index 0000000..18e542e
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tr.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-b.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-b.png
new file mode 100644
index 0000000..05a190e
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-b.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-r.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-r.png
new file mode 100644
index 0000000..0c3ea4c
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-r.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.ico b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.ico
new file mode 100644
index 0000000..339d09c
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.ico
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.png
new file mode 100644
index 0000000..6dc53ee
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/half-spiral.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/half-spiral.png
new file mode 100644
index 0000000..cb4b56c
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/half-spiral.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/header_inner.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/header_inner.png
new file mode 100644
index 0000000..2b2d87d
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/header_inner.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/info.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/info.png
new file mode 100644
index 0000000..329c523
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/info.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-b.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-b.png
new file mode 100644
index 0000000..25d3cfa
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-b.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-bl.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-bl.png
new file mode 100644
index 0000000..f496223
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-bl.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-br.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-br.png
new file mode 100644
index 0000000..74cbd91
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-br.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-l.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-l.png
new file mode 100644
index 0000000..dd567fa
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-l.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-r.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-r.png
new file mode 100644
index 0000000..9ac4486
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-r.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-t.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-t.png
new file mode 100644
index 0000000..fbb06c8
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-t.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tl.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tl.png
new file mode 100644
index 0000000..9336290
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tl.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tr.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tr.png
new file mode 100644
index 0000000..de74808
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tr.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ok.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ok.png
new file mode 100644
index 0000000..fee6751
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ok.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/shadows.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/shadows.png
new file mode 100644
index 0000000..9ddc676
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/shadows.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/spiral.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/spiral.png
new file mode 100644
index 0000000..b4bcb1e
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/spiral.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/tg_under_the_hood.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/tg_under_the_hood.png
new file mode 100644
index 0000000..bc9c79c
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/tg_under_the_hood.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/under_the_hood_blue.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/under_the_hood_blue.png
new file mode 100644
index 0000000..90e84b7
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/under_the_hood_blue.png
Binary files differ
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/templates/__init__.py b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/__init__.py
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/templates/about.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/about.kid
new file mode 100644
index 0000000..fa3548a
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/about.kid
@@ -0,0 +1,21 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
+ py:extends="'master.kid'">
+<head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+ <title>About Bugs Everywhere</title>
+</head>
+
+<body>
+<h1>About Bugs Everywhere</h1>
+<p>Bugs Everywhere is a "distributed bugtracker", designed to complement distributed revision control systems.
+</p>
+<p>
+Bugs Everywhere was conceived and written by developers at <a href="http://panoramicfeedback.com/">Panoramic Feedback</a>, primarily Aaron Bentley. <a href="http://panoramicfeedback.com/">Panoramic Feedback</a> is no longer developing BE, and the current maintainer is <a href="http://bugseverywhere.org/be/show/ChrisBall">Chris Ball</a>.
+</p>
+<p>
+ Bugs Everywhere <a href="http://bugseverywhere.org/">web site</a>
+</p>
+<a href="/">Project List</a>
+</body>
+</html>
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/templates/bugs.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/bugs.kid
new file mode 100644
index 0000000..198aa94
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/bugs.kid
@@ -0,0 +1,52 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<?python
+from libbe.names import unique_name
+from beweb.controllers import bug_url, project_url, bug_list_url
+from beweb.model import people_map
+people = people_map()
+def row_class(bug, num):
+ if not bug.active is True:
+ extra = "closed"
+ else:
+ extra = ""
+ if num % 2 == 0:
+ return extra+"even"
+ else:
+ return extra+"odd"
+
+
+def match(bug, show_closed, search):
+ if not show_closed and not bug.active:
+ return False
+ elif search is None:
+ return True
+ else:
+ return search.lower() in bug.summary.lower()
+?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
+ py:extends="'master.kid'">
+
+<head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+ <title>Bugs for $project_name</title>
+</head>
+
+<body>
+<h1>Bug list for ${project_name}</h1>
+<table>
+<tr><td>ID</td><td>Status</td><td>Severity</td><td>Assigned To</td><td>Comments</td><td>Summary</td></tr>
+<div py:for="num, bug in enumerate([b for b in bugs if match(b, show_closed, search)])" py:strip="True"><tr class="${row_class(bug, num)}"><td><a href="${bug_url(project_id, bug.uuid)}">${unique_name(bug, bugs[:])}</a></td><td>${bug.status}</td><td>${bug.severity}</td><td>${people.get(bug.assigned, bug.assigned)}</td><td>${len(list(bug.iter_comment_ids()))}</td><td>${bug.summary}</td></tr>
+</div>
+</table>
+<a href="${project_url()}">Project list</a>
+<a href="${bug_list_url(project_id, not show_closed, search)}">Toggle closed</a>
+<form action="${bug_list_url(project_id)}" method="post">
+<input type="submit" name="action" value="New bug"/>
+</form>
+<form action="${bug_list_url(project_id)}" method="get">
+<input type="hidden" name="show_closed" value="False" />
+<input name="search" value="$search"/>
+<input type="submit" name="action" value="Search" />
+</form>
+</body>
+</html>
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_bug.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_bug.kid
new file mode 100644
index 0000000..276f610
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_bug.kid
@@ -0,0 +1,52 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<?python
+from libbe.bug import severity_values, status_values, thread_comments
+from libbe.utility import time_to_str
+from beweb.controllers import bug_list_url, comment_url
+from beweb.formatting import comment_body_xhtml, select_among
+from beweb.model import people_map
+people = people_map()
+?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
+ py:extends="'master.kid'">
+
+<head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+ <title>Edit bug</title>
+</head>
+
+<body>
+<h1>Edit bug</h1>
+<form method="post" action=".">
+<table>
+<tr><td>Status</td><td>Severity</td><td>Assigned To</td><td>Summary</td></tr>
+<tr><td>${select_among("status", status_values, bug.status)}</td><td>${select_among("severity", severity_values, bug.severity)}</td>
+<td>${select_among("assigned", people.keys()+[None], bug.assigned, people)}</td><td><input name="summary" value="${bug.summary}" size="80" /></td></tr>
+</table>
+<div py:def="show_comment(comment, children)" class="comment">
+ <insetbox>
+ <table>
+ <tr><td>From</td><td>${comment.From}</td></tr>
+ <tr><td>Date</td><td>${time_to_str(comment.time)}</td></tr>
+ </table>
+ <div py:content="comment_body_xhtml(comment)" py:strip="True"></div>
+ <a href="${comment_url(project_id, bug.uuid, comment.uuid)}">Edit</a>
+ <a href="${comment_url(project_id, bug.uuid, comment.uuid,
+ action='Reply')}">Reply</a>
+ </insetbox>
+ <div style="margin-left:20px;">
+ <div py:for="child, grandchildren in children" py:strip="True">
+ ${show_comment(child, grandchildren)}
+ </div>
+ </div>
+</div>
+<div py:for="comment, children in thread_comments(bug.list_comments())"
+ py:strip="True">
+ ${show_comment(comment, children)}
+</div>
+<p><input type="submit" name="action" value="Update"/></p>
+<p><input type="submit" name="action" value="New comment"/></p>
+</form>
+<a href="${bug_list_url(project_id)}">Bug List</a>
+</body>
+</html>
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_comment.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_comment.kid
new file mode 100644
index 0000000..2b522d4
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/edit_comment.kid
@@ -0,0 +1,26 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<?python
+from libbe.utility import time_to_str
+from beweb.controllers import bug_list_url, bug_url
+?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
+ py:extends="'master.kid'">
+
+<head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+ <title>Edit comment</title>
+</head>
+
+<body>
+<h1>Edit comment</h1>
+<form method="post">
+<table>
+ <tr><td>From</td><td>${comment.From}</td></tr>
+ <tr><td>Date</td><td>${time_to_str(comment.time)}</td></tr>
+</table>
+<insetbox><textarea rows="15" cols="80" py:content="comment.body" name="comment_body" style="border-style: none"/></insetbox>
+<p><input type="submit" name="action" value="Update"/></p>
+</form>
+<a href="${bug_url(project_id, comment.bug.uuid)}">Up to Bug</a>
+</body>
+</html>
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/templates/error.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/error.kid
new file mode 100644
index 0000000..bc55615
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/error.kid
@@ -0,0 +1,14 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
+ py:extends="'master.kid'">
+<head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+ <title>BE Error: ${heading}</title>
+</head>
+
+<body>
+<h1 py:content="heading">Error heading</h1>
+<div py:replace="body" >Error Body</div>
+<pre py:content="traceback" class="traceback">Traceback</pre>
+</body>
+</html>
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/templates/login.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/login.kid
new file mode 100644
index 0000000..e7ad852
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/login.kid
@@ -0,0 +1,113 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://purl.org/kid/ns#"
+ py:extends="'master.kid'">
+
+<head>
+ <meta content="text/html; charset=UTF-8"
+ http-equiv="content-type" py:replace="''"/>
+ <title>Login</title>
+ <style type="text/css">
+ #loginBox
+ {
+ width: 30%;
+ margin: auto;
+ margin-top: 10%;
+ padding-left: 10%;
+ padding-right: 10%;
+ padding-top: 5%;
+ padding-bottom: 5%;
+ font-family: verdana;
+ font-size: 10px;
+ background-color: #eee;
+ border: 2px solid #ccc;
+ }
+
+ #loginBox h1
+ {
+ font-size: 42px;
+ font-family: "Trebuchet MS";
+ margin: 0;
+ color: #ddd;
+ }
+
+ #loginBox p
+ {
+ position: relative;
+ top: -1.5em;
+ padding-left: 4em;
+ font-size: 12px;
+ margin: 0;
+ color: #666;
+ }
+
+ #loginBox table
+ {
+ table-layout: fixed;
+ border-spacing: 0;
+ width: 100%;
+ }
+
+ #loginBox td.label
+ {
+ width: 33%;
+ text-align: right;
+ }
+
+ #loginBox td.field
+ {
+ width: 66%;
+ }
+
+ #loginBox td.field input
+ {
+ width: 100%;
+ }
+
+ #loginBox td.buttons
+ {
+ text-align: right;
+ }
+
+ </style>
+</head>
+
+<body>
+ <div id="loginBox">
+ <h1>Login</h1>
+ <p>${message}</p>
+ <form action="${previous_url}" method="POST">
+ <table>
+ <tr>
+ <td class="label">
+ <label for="user_name">User Name:</label>
+ </td>
+ <td class="field">
+ <input type="text" id="user_name" name="user_name"/>
+ </td>
+ </tr>
+ <tr>
+ <td class="label">
+ <label for="password">Password:</label>
+ </td>
+ <td class="field">
+ <input type="password" id="password" name="password"/>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2" class="buttons">
+ <input type="submit" name="login" value="Login"/>
+ </td>
+ </tr>
+ </table>
+
+ <input py:if="forward_url" type="hidden" name="forward_url"
+ value="${forward_url}"/>
+
+ <input py:for="name,value in original_parameters.items()"
+ type="hidden" name="${name}" value="${value}"/>
+ </form>
+ </div>
+</body>
+</html>
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/templates/master.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/master.kid
new file mode 100644
index 0000000..0772524
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/master.kid
@@ -0,0 +1,71 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<?python import sitetemplate ?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#" py:extends="sitetemplate">
+
+<head py:match="item.tag=='{http://www.w3.org/1999/xhtml}head'" py:attrs="item.items()">
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+ <title py:if="False">Your title goes here</title>
+ <link rel="stylesheet" type="text/css" href="/static/css/style.css"/>
+ <meta py:replace="item[:]"/>
+ <style type="text/css">
+ #pageLogin
+ {
+ font-size: 10px;
+ font-family: verdana;
+ text-align: right;
+ }
+ </style>
+</head>
+
+<body py:match="item.tag=='{http://www.w3.org/1999/xhtml}body'" py:attrs="item.items()">
+<div id="header"><div style="float: left">b u g s e v r y w h e r e</div><ul class="navoption"><li><a href="/about/">About</a></li></ul>&#160;</div>
+ <div py:if="tg.config('identity.on',False) and not 'logging_in' in locals()"
+ id="pageLogin">
+ <span py:if="tg.identity.anonymous">
+ <a href="/login">Login</a>
+ </span>
+ <span py:if="not tg.identity.anonymous">
+ Welcome ${tg.identity.user.display_name}.
+ <a href="/logout">Logout</a>
+ </span>
+ </div>
+
+ <div py:if="tg_flash" class="flash" py:content="tg_flash"></div>
+
+ <div py:replace="[item.text]+item[:]"/>
+
+<table py:match="item.tag=='{http://www.w3.org/1999/xhtml}insetbox'" cellspacing="0" cellpadding="0" border="0" class="insetbox">
+<tr height="19"><td background="/static/images/is-tl.png" width="19"/>
+ <td background="/static/images/is-t.png" />
+ <td background="/static/images/is-tr.png" width="11"></td>
+</tr>
+<tr>
+ <td background="/static/images/is-l.png"/>
+ <td py:content="item[:]"> Hello, this is some random text</td>
+ <td background="/static/images/is-r.png"/>
+</tr>
+<tr height="11">
+ <td background="/static/images/is-bl.png"/>
+ <td background="/static/images/is-b.png" />
+ <td background="/static/images/is-br.png"/>
+</tr>
+</table>
+<table py:match="item.tag=='{http://www.w3.org/1999/xhtml}dsbox'" cellspacing="0" cellpadding="0" border="0" class="dsbox">
+<tr height="11"><td background="/static/images/ds-tl.png" width="11"/>
+ <td background="/static/images/ds-t.png" />
+ <td background="/static/images/ds-tr.png" width="19"></td>
+</tr>
+<tr>
+ <td background="/static/images/ds-l.png"/>
+ <td py:content="item[:]"> Hello, this is some random text</td>
+ <td background="/static/images/ds2-r.png"/>
+</tr>
+<tr height="19">
+ <td background="/static/images/ds-bl.png"/>
+ <td background="/static/images/ds2-b.png" />
+ <td background="/static/images/ds-br.png"/>
+</tr>
+</table>
+</body>
+
+</html>
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/templates/projects.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/projects.kid
new file mode 100644
index 0000000..d5f9fd3
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/projects.kid
@@ -0,0 +1,32 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<?python
+from libbe.bug import severity_values
+def select_among(name, options, default):
+ output = ['<select name="%s">' % name]
+ for option in options:
+ if option == default:
+ selected = ' selected="selected"'
+ else:
+ selected = ""
+ output.append("<option%s>%s</option>" % (selected, option))
+ output.append("</select>")
+ return XML("".join(output))
+?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
+ py:extends="'master.kid'">
+<?python
+project_triples = [(pn, pid, pl) for pid,(pn, pl) in projects.iteritems()]
+project_triples.sort()
+?>
+<head>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+ <title>Project List</title>
+</head>
+
+<body>
+<h1>Project List</h1>
+<table>
+<tr py:for="project_name, project_id, project_loc in project_triples"><td><a href="/project/${project_id}/">${project_name}</a></td></tr>
+</table>
+</body>
+</html>
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/templates/welcome.kid b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/welcome.kid
new file mode 100644
index 0000000..08abd21
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/templates/welcome.kid
@@ -0,0 +1,50 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
+ py:extends="'master.kid'">
+<head>
+<meta content="text/html; charset=utf-8" http-equiv="Content-Type" py:replace="''"/>
+<title>Welcome to TurboGears</title>
+</head>
+<body>
+<div id="header">&nbsp;</div>
+<div id="main_content">
+ <div id="status_block">Your TurboGears application is now running.</div>
+ <!--h1>Take steps to dive right in:</h1-->
+ <div id="sidebar">
+ <h2>Learn more</h2>
+ Learn more about TurboGears and take part in its
+ development
+ <ul class="links">
+ <li><a href="http://www.turbogears.org">Official website</a></li>
+ <li><a href="http://docs.turbogears.org">Documentation</a></li>
+ <li><a href="http://trac.turbogears.org/turbogears/">Trac
+ (bugs/suggestions)</a></li>
+ <li><a href="http://groups.google.com/group/turbogears"> Mailing list</a> </li>
+ </ul>
+ </div>
+ <div id="getting_started">
+ <ol id="getting_started_steps">
+ <li class="getting_started">
+ <h3>Model</h3>
+ <p> <a href="http://docs.turbogears.org/1.0/GettingStarted/DefineDatabase">Design models</a> in the <span class="code">model.py</span>.<br/>
+ Edit <span class="code">dev.cfg</span> to <a href="http://docs.turbogears.org/1.0/GettingStarted/UseDatabase">use a different backend</a>, or start with a pre-configured SQLite database. <br/>
+ Use script <span class="code">tg-admin sql create</span> to create the database tables.</p>
+ </li>
+ <li class="getting_started">
+ <h3>View</h3>
+ <p> Edit <a href="http://docs.turbogears.org/1.0/GettingStarted/Kid">html-like templates</a> in the <span class="code">/templates</span> folder;<br/>
+ Put all <a href="http://docs.turbogears.org/1.0/StaticFiles">static contents</a> in the <span class="code">/static</span> folder. </p>
+ </li>
+ <li class="getting_started">
+ <h3>Controller</h3>
+ <p> Edit <span class="code"> controllers.py</span> and <a href="http://docs.turbogears.org/1.0/GettingStarted/CherryPy">build your
+ website structure</a> with the simplicity of Python objects. <br/>
+ TurboGears will automatically reload itself when you modify your project. </p>
+ </li>
+ </ol>
+ <div class="notice"> If you create something cool, please <a href="http://groups.google.com/group/turbogears">let people know</a>, and consider contributing something back to the <a href="http://groups.google.com/group/turbogears">community</a>.</div>
+ </div>
+ <!-- End of getting_started -->
+</div>
+</body>
+</html>
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/tests/__init__.py b/interfaces/web/Bugs-Everywhere-Web/beweb/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/tests/__init__.py
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_controllers.py b/interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_controllers.py
new file mode 100644
index 0000000..0c77afe
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_controllers.py
@@ -0,0 +1,16 @@
+from turbogears import testutil
+from beweb.controllers import Root
+import cherrypy
+
+cherrypy.root = Root()
+
+def test_method():
+ "the index method should return a string called now"
+ import types
+ result = testutil.call(cherrypy.root.index)
+ assert type(result["now"]) == types.StringType
+
+def test_indextitle():
+ "The mainpage should have the right title"
+ testutil.createRequest("/")
+ assert "<TITLE>Welcome to TurboGears</TITLE>" in cherrypy.response.body[0]
diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_model.py b/interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_model.py
new file mode 100644
index 0000000..74c4e83
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/beweb/tests/test_model.py
@@ -0,0 +1,23 @@
+# If your project uses a database, you can set up database tests
+# similar to what you see below. Be sure to set the db_uri to
+# an appropriate uri for your testing database. sqlite is a good
+# choice for testing, because you can use an in-memory database
+# which is very fast.
+
+from turbogears import testutil, database
+# from beweb.model import YourDataClass, User
+
+# database.set_db_uri("sqlite:///:memory:")
+
+# class TestUser(testutil.DBTest):
+# def get_model(self):
+# return User
+#
+# def test_creation(self):
+# "Object creation should set the name"
+# obj = User(user_name = "creosote",
+# email_address = "spam@python.not",
+# display_name = "Mr Creosote",
+# password = "Wafer-thin Mint")
+# assert obj.display_name == "Mr Creosote"
+
diff --git a/interfaces/web/Bugs-Everywhere-Web/dev.cfg b/interfaces/web/Bugs-Everywhere-Web/dev.cfg
new file mode 100644
index 0000000..eda9e6c
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/dev.cfg
@@ -0,0 +1,71 @@
+[global]
+# This is where all of your settings go for your development environment
+# Settings that are the same for both development and production
+# (such as template engine, encodings, etc.) all go in
+# beweb/config/app.cfg
+
+# DATABASE
+
+# pick the form for your database
+# sqlobject.dburi="postgres://username@hostname/databasename"
+# sqlobject.dburi="mysql://username:password@hostname:port/databasename"
+# sqlobject.dburi="sqlite://%(package_dir)s/database.sqlite"
+
+# If you have sqlite, here's a simple default to get you started
+# in development
+sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite"
+
+
+# if you are using a database or table type without transactions
+# (MySQL default, for example), you should turn off transactions
+# by prepending notrans_ on the uri
+# sqlobject.dburi="notrans_mysql://username:password@hostname:port/databasename"
+
+# for Windows users, sqlite URIs look like:
+# sqlobject.dburi="sqlite:///drive_letter:/path/to/file"
+
+# SERVER
+
+# Some server parameters that you may want to tweak
+# server.socket_port=8080
+
+# Enable the debug output at the end on pages.
+# log_debug_info_filter.on = False
+
+server.environment="development"
+autoreload.package="beweb"
+
+# session_filter.on = True
+
+# Set to True if you'd like to abort execution if a controller gets an
+# unexpected parameter. False by default
+tg.strict_parameters = True
+identity.on = True
+visit.on = True
+identity.soprovider.model.user="beweb.model.User"
+identity.soprovider.model.group="beweb.model.Group"
+identity.soprovider.model.permission="beweb.model.Permission"
+
+
+# LOGGING
+# Logging configuration generally follows the style of the standard
+# Python logging module configuration. Note that when specifying
+# log format messages, you need to use *() for formatting variables.
+# Deployment independent log configuration is in beweb/config/log.cfg
+[logging]
+
+[[loggers]]
+[[[beweb]]]
+level='DEBUG'
+qualname='beweb'
+handlers=['debug_out']
+
+[[[allinfo]]]
+level='INFO'
+handlers=['debug_out']
+
+[[[access]]]
+level='INFO'
+qualname='turbogears.access'
+handlers=['access_out']
+propagate=0
diff --git a/interfaces/web/Bugs-Everywhere-Web/libbe b/interfaces/web/Bugs-Everywhere-Web/libbe
new file mode 120000
index 0000000..7d18612
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/libbe
@@ -0,0 +1 @@
+../../../libbe \ No newline at end of file
diff --git a/interfaces/web/Bugs-Everywhere-Web/prod.cfg b/interfaces/web/Bugs-Everywhere-Web/prod.cfg
new file mode 100644
index 0000000..c0d4aca
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/prod.cfg
@@ -0,0 +1,41 @@
+[global]
+# This is where all of your settings go for your production environment.
+# You'll copy this file over to your production server and provide it
+# as a command-line option to your start script.
+# Settings that are the same for both development and production
+# (such as template engine, encodings, etc.) all go in
+# yourpackage/config/app.cfg
+
+# DATABASE
+
+# pick the form for your database
+# sqlobject.dburi="postgres://username@hostname/databasename"
+# sqlobject.dburi="mysql://username:password@hostname:port/databasename"
+# sqlobject.dburi="sqlite:///file_name_and_path"
+
+# if you are using a database or table type without transactions
+# (MySQL default, for example), you should turn off transactions
+# by prepending notrans_ on the uri
+# sqlobject.dburi="notrans_mysql://username:password@hostname:port/databasename"
+
+# for Windows users, sqlite URIs look like:
+# sqlobject.dburi="sqlite:///drive_letter|/path/to/file"
+
+
+# SERVER
+
+server.environment="production"
+server.log_file="server.log"
+server.log_to_screen=False
+
+# Sets the number of threads the server uses
+# server.thread_pool = 1
+
+# if this is part of a larger site, you can set the path
+# to the TurboGears instance here
+# server.webpath=""
+
+# Set to True if you'd like to abort execution if a controller gets an
+# unexpected parameter. False by default
+# tg.strict_parameters = False
+
diff --git a/interfaces/web/Bugs-Everywhere-Web/sample-prod.cfg b/interfaces/web/Bugs-Everywhere-Web/sample-prod.cfg
new file mode 100644
index 0000000..d1052f8
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/sample-prod.cfg
@@ -0,0 +1,71 @@
+[global]
+# This is where all of your settings go for your production environment.
+# You'll copy this file over to your production server and provide it
+# as a command-line option to your start script.
+# Settings that are the same for both development and production
+# (such as template engine, encodings, etc.) all go in
+# beweb/config/app.cfg
+
+# pick the form for your database
+# sqlobject.dburi="postgres://username@hostname/databasename"
+# sqlobject.dburi="mysql://username:password@hostname:port/databasename"
+# sqlobject.dburi="sqlite:///file_name_and_path"
+
+# If you have sqlite, here's a simple default to get you started
+# in development
+sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite"
+
+
+# if you are using a database or table type without transactions
+# (MySQL default, for example), you should turn off transactions
+# by prepending notrans_ on the uri
+# sqlobject.dburi="notrans_mysql://username:password@hostname:port/databasename"
+
+# for Windows users, sqlite URIs look like:
+# sqlobject.dburi="sqlite:///drive_letter:/path/to/file"
+
+
+# SERVER
+
+server.environment="production"
+
+# Sets the number of threads the server uses
+# server.thread_pool = 1
+
+# if this is part of a larger site, you can set the path
+# to the TurboGears instance here
+# server.webpath=""
+
+# session_filter.on = True
+
+# Set to True if you'd like to abort execution if a controller gets an
+# unexpected parameter. False by default
+# tg.strict_parameters = False
+
+# LOGGING
+# Logging configuration generally follows the style of the standard
+# Python logging module configuration. Note that when specifying
+# log format messages, you need to use *() for formatting variables.
+# Deployment independent log configuration is in beweb/config/log.cfg
+[logging]
+
+[[handlers]]
+
+[[[access_out]]]
+# set the filename as the first argument below
+args="('server.log',)"
+class='FileHandler'
+level='INFO'
+formatter='message_only'
+
+[[loggers]]
+[[[beweb]]]
+level='ERROR'
+qualname='beweb'
+handlers=['error_out']
+
+[[[access]]]
+level='INFO'
+qualname='turbogears.access'
+handlers=['access_out']
+propagate=0
diff --git a/interfaces/web/Bugs-Everywhere-Web/server.log b/interfaces/web/Bugs-Everywhere-Web/server.log
new file mode 100644
index 0000000..fe02ade
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/server.log
@@ -0,0 +1,26 @@
+2005/12/01 15:44:05 CONFIG INFO Server parameters:
+2005/12/01 15:44:05 CONFIG INFO server.environment: production
+2005/12/01 15:44:05 CONFIG INFO server.logToScreen: False
+2005/12/01 15:44:05 CONFIG INFO server.logFile: server.log
+2005/12/01 15:44:05 CONFIG INFO server.protocolVersion: HTTP/1.0
+2005/12/01 15:44:05 CONFIG INFO server.socketHost:
+2005/12/01 15:44:05 CONFIG INFO server.socketPort: 8080
+2005/12/01 15:44:05 CONFIG INFO server.socketFile:
+2005/12/01 15:44:05 CONFIG INFO server.reverseDNS: False
+2005/12/01 15:44:05 CONFIG INFO server.socketQueueSize: 5
+2005/12/01 15:44:05 CONFIG INFO server.threadPool: 0
+2005/12/01 15:44:05 HTTP INFO Serving HTTP on http://localhost:8080/
+2005/12/01 15:44:17 HTTP INFO 127.0.0.1 - GET / HTTP/1.1
+2005/12/01 15:44:37 HTTP INFO 192.168.2.12 - GET / HTTP/1.1
+2005/12/01 15:44:42 HTTP INFO 192.168.2.12 - GET /be HTTP/1.1
+2005/12/01 15:44:43 HTTP INFO 192.168.2.12 - GET /be/301724b1-3853-4aff-8f23-44373df7cf1c HTTP/1.1
+2005/12/01 15:44:48 HTTP INFO 192.168.2.12 - GET /be/ HTTP/1.1
+2005/12/01 15:44:50 HTTP INFO 192.168.2.12 - GET / HTTP/1.1
+2005/12/01 15:44:53 HTTP INFO 192.168.2.12 - GET /devel/ HTTP/1.1
+2005/12/01 15:44:58 HTTP INFO 192.168.2.12 - GET / HTTP/1.1
+2005/12/01 15:52:57 HTTP INFO 127.0.0.1 - GET /devel HTTP/1.1
+2005/12/01 15:52:59 HTTP INFO 127.0.0.1 - GET /devel HTTP/1.1
+2005/12/01 15:53:25 HTTP INFO 127.0.0.1 - GET /devel HTTP/1.1
+2005/12/01 15:53:29 HTTP INFO <Ctrl-C> hit: shutting down server
+2005/12/01 15:53:29 HTTP INFO HTTP Server shut down
+2005/12/01 15:53:29 HTTP INFO CherryPy shut down
diff --git a/interfaces/web/Bugs-Everywhere-Web/setup-tables.py b/interfaces/web/Bugs-Everywhere-Web/setup-tables.py
new file mode 100644
index 0000000..161d7c7
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/setup-tables.py
@@ -0,0 +1,34 @@
+import pkg_resources
+pkg_resources.require("TurboGears")
+
+import turbogears
+import cherrypy
+cherrypy.lowercase_api = True
+
+from os.path import *
+import sys
+
+# first look on the command line for a desired config file,
+# if it's not on the command line, then
+# look for setup.py in this directory. If it's not there, this script is
+# probably installed
+if len(sys.argv) > 1:
+ turbogears.update_config(configfile=sys.argv[1],
+ modulename="beweb.config.app")
+elif exists(join(dirname(__file__), "setup.py")):
+ turbogears.update_config(configfile="dev.cfg",
+ modulename="beweb.config.app")
+else:
+ turbogears.update_config(configfile="prod.cfg",
+ modulename="beweb.config.app")
+
+from beweb.controllers import Root
+
+cherrypy.root = Root()
+
+
+from beweb.model import TG_Group, TG_Permission
+g = TG_Group(groupId="editors", displayName="Editors")
+p = TG_Permission(permissionId="editbugs",
+ description="Ability to create and edit bugs")
+g.addTG_Permission(p)
diff --git a/interfaces/web/Bugs-Everywhere-Web/setup.py b/interfaces/web/Bugs-Everywhere-Web/setup.py
new file mode 100644
index 0000000..8ba3da2
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/setup.py
@@ -0,0 +1,62 @@
+from setuptools import setup, find_packages
+from turbogears.finddata import find_package_data
+
+import os
+execfile(os.path.join("beweb", "release.py"))
+
+setup(
+ name="Bugs-Everywhere-Web",
+ version=version,
+
+ # uncomment the following lines if you fill them out in release.py
+ #description=description,
+ #author=author,
+ #author_email=email,
+ #url=url,
+ #download_url=download_url,
+ #license=license,
+
+ install_requires = [
+ "TurboGears >= 1.0b1",
+ ],
+ scripts = ["start-beweb.py"],
+ zip_safe=False,
+ packages=find_packages(),
+ package_data = find_package_data(where='beweb',
+ package='beweb'),
+ keywords = [
+ # Use keywords if you'll be adding your package to the
+ # Python Cheeseshop
+
+ # if this has widgets, uncomment the next line
+ # 'turbogears.widgets',
+
+ # if this has a tg-admin command, uncomment the next line
+ # 'turbogears.command',
+
+ # if this has identity providers, uncomment the next line
+ # 'turbogears.identity.provider',
+
+ # If this is a template plugin, uncomment the next line
+ # 'python.templating.engines',
+
+ # If this is a full application, uncomment the next line
+ # 'turbogears.app',
+ ],
+ classifiers = [
+ 'Development Status :: 3 - Alpha',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Framework :: TurboGears',
+ # if this is an application that you'll distribute through
+ # the Cheeseshop, uncomment the next line
+ # 'Framework :: TurboGears :: Applications',
+
+ # if this is a package that includes widgets that you'll distribute
+ # through the Cheeseshop, uncomment the next line
+ # 'Framework :: TurboGears :: Widgets',
+ ],
+ test_suite = 'nose.collector',
+ )
+
diff --git a/interfaces/web/Bugs-Everywhere-Web/start-beweb.py b/interfaces/web/Bugs-Everywhere-Web/start-beweb.py
new file mode 100755
index 0000000..4070abd
--- /dev/null
+++ b/interfaces/web/Bugs-Everywhere-Web/start-beweb.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+import pkg_resources
+pkg_resources.require("TurboGears")
+
+import turbogears
+import cherrypy
+cherrypy.lowercase_api = True
+
+from os.path import *
+import sys
+
+# first look on the command line for a desired config file,
+# if it's not on the command line, then
+# look for setup.py in this directory. If it's not there, this script is
+# probably installed
+if len(sys.argv) > 1:
+ turbogears.update_config(configfile=sys.argv[1],
+ modulename="beweb.config")
+elif exists(join(dirname(__file__), "setup.py")):
+ turbogears.update_config(configfile="dev.cfg",
+ modulename="beweb.config")
+else:
+ turbogears.update_config(configfile="prod.cfg",
+ modulename="beweb.config")
+
+from beweb.controllers import Root
+
+turbogears.start_server(Root())
diff --git a/interfaces/xml/be-mbox-to-xml b/interfaces/xml/be-mbox-to-xml
new file mode 100755
index 0000000..335f92f
--- /dev/null
+++ b/interfaces/xml/be-mbox-to-xml
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+# Copyright (C) 2009 W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+Convert an mbox into xml suitable for imput into be.
+ $ cat mbox | be-mbox-to-xml | be comment --xml <ID> -
+mbox is a flat-file format, consisting of a series of messages.
+Messages begin with a a From_ line, followed by RFC 822 email,
+followed by a blank line.
+"""
+
+import base64
+import email.utils
+from libbe.encoding import get_encoding, set_IO_stream_encodings
+from mailbox import mbox, Message # the mailbox people really want an on-disk copy
+from time import asctime, gmtime
+import types
+from xml.sax import make_parser
+from xml.sax.handler import ContentHandler
+from xml.sax.saxutils import escape
+
+DEFAULT_ENCODING = get_encoding()
+set_IO_stream_encodings(DEFAULT_ENCODING)
+
+KNOWN_IDS = []
+
+def comment_message_to_xml(message, fields=None):
+ if fields == None:
+ fields = {}
+ new_fields = {}
+ new_fields[u'alt-id'] = message[u'message-id']
+ new_fields[u'in-reply-to'] = message[u'in-reply-to']
+ new_fields[u'from'] = message[u'from']
+ new_fields[u'date'] = message[u'date']
+ new_fields[u'content-type'] = message.get_content_type()
+ for k,v in new_fields.items():
+ if v != None and type(v) != types.UnicodeType:
+ fields[k] = unicode(v, encoding=DEFAULT_ENCODING)
+ elif v == None and k in fields:
+ new_fields[k] = fields[k]
+ for k,v in fields.items():
+ if k not in new_fields:
+ new_fields.k = fields[k]
+ fields = new_fields
+
+ if fields[u'in-reply-to'] == None:
+ if message[u'references'] != None:
+ refs = message[u'references'].split()
+ for ref in refs: # search for a known reference id.
+ if ref in KNOWN_IDS:
+ fields[u'in-reply-to'] = ref
+ break
+ if fields[u'in-reply-to'] == None and len(refs) > 0:
+ fields[u'in-reply-to'] = refs[0] # default to the first
+ else: # check for mutliple in-reply-to references.
+ refs = fields[u'in-reply-to'].split()
+ for ref in refs: # search for a known reference id.
+ if ref in KNOWN_IDS:
+ fields[u'in-reply-to'] = ref
+ break
+ if fields[u'in-reply-to'] == None and len(refs) > 0:
+ fields[u'in-reply-to'] = refs[0] # default to the first
+
+ if fields['alt-id'] != None:
+ KNOWN_IDS.append(fields['alt-id'])
+
+ if message.is_multipart():
+ ret = []
+ alt_id = fields[u'alt-id']
+ from_str = fields[u'from']
+ date = fields[u'date']
+ for m in message.walk():
+ if m == message:
+ continue
+ fields[u'from'] = from_str
+ fields[u'date'] = date
+ if len(ret) > 0: # we've added one part already
+ fields.pop(u'alt-id') # don't pass alt-id to other parts
+ fields[u'in-reply-to'] = alt_id # others respond to first
+ ret.append(comment_message_to_xml(m, fields))
+ return u'\n'.join(ret)
+
+ charset = message.get_content_charset(DEFAULT_ENCODING).lower()
+ #assert charset == DEFAULT_ENCODING.lower(), \
+ # u"Unknown charset: %s" % charset
+
+ if message[u'content-transfer-encoding'] == None:
+ encoding = DEFAULT_ENCODING
+ else:
+ encoding = message[u'content-transfer-encoding'].lower()
+ body = message.get_payload(decode=True) # attempt to decode
+ assert body != None, "Unable to decode?"
+ if fields[u'content-type'].startswith(u"text/"):
+ body = unicode(body, encoding=charset).rstrip(u'\n')
+ else:
+ body = base64.encode(body)
+ fields[u'body'] = body
+ lines = [u"<comment>"]
+ for tag,body in fields.items():
+ if body != None:
+ ebody = escape(body)
+ lines.append(u" <%s>%s</%s>" % (tag, ebody, tag))
+ lines.append(u"</comment>")
+ return u'\n'.join(lines)
+
+def main(mbox_filename):
+ mb = mbox(mbox_filename)
+ print u'<?xml version="1.0" encoding="%s" ?>' % DEFAULT_ENCODING
+ print u"<comment-list>"
+ for message in mb:
+ print comment_message_to_xml(message)
+ print u"</comment-list>"
+
+
+if __name__ == "__main__":
+ import sys
+ main(sys.argv[1])
diff --git a/interfaces/xml/be-xml-to-mbox b/interfaces/xml/be-xml-to-mbox
new file mode 100755
index 0000000..ea77c34
--- /dev/null
+++ b/interfaces/xml/be-xml-to-mbox
@@ -0,0 +1,205 @@
+#!/usr/bin/env python
+# Copyright (C) 2009 Chris Ball <cjb@laptop.org>
+# W. Trevor King <wking@drexel.edu>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+"""
+Convert xml output of `be list --xml` into mbox format for browsing
+with a mail reader. For example
+ $ be list --xml --status=all | be-xml-to-mbox | catmutt
+
+mbox is a flat-file format, consisting of a series of messages.
+Messages begin with a a From_ line, followed by RFC 822 email,
+followed by a blank line.
+"""
+
+#from mailbox import mbox, Message # the mailbox people really want an on-disk copy
+import codecs
+import email.utils
+from libbe.encoding import get_encoding, set_IO_stream_encodings
+from libbe.utility import str_to_time as rfc2822_to_gmtime_integer
+from time import asctime, gmtime
+import types
+try: # import core module, Python >= 2.5
+ from xml.etree import ElementTree
+except ImportError: # look for non-core module
+ from elementtree import ElementTree
+from xml.sax.saxutils import unescape
+
+
+DEFAULT_DOMAIN = "invalid.com"
+DEFAULT_EMAIL = "dummy@" + DEFAULT_DOMAIN
+DEFAULT_ENCODING = get_encoding()
+set_IO_stream_encodings(DEFAULT_ENCODING)
+
+def rfc2822_to_asctime(rfc2822_string):
+ """Convert an RFC 2822-fomatted string into a asctime string.
+ >>> rfc2822_to_asctime("Thu, 01 Jan 1970 00:00:00 +0000")
+ "Thu Jan 01 00:00:00 1970"
+ """
+ if rfc2822_string == "":
+ return asctime(gmtime(0))
+ return asctime(gmtime(rfc2822_to_gmtime_integer(rfc2822_string)))
+
+class LimitedAttrDict (dict):
+ """
+ Dict with error checking, to avoid invalid bug/comment fields.
+ """
+ _attrs = [] # override with list of valid attribute names
+ def __init__(self, **kwargs):
+ dict.__init__(self)
+ for key,value in kwargs.items():
+ self[key] = value
+ def __setitem__(self, key, item):
+ self._validate_key(key)
+ dict.__setitem__(self, key, item)
+ def _validate_key(self, key):
+ if key in self._attrs:
+ return
+ elif type(key) not in types.StringTypes:
+ raise TypeError, "Invalid attribute type %s for '%s'" % (type(key), key)
+ else:
+ raise ValueError, "Invalid attribute name '%s'" % key
+
+class Bug (LimitedAttrDict):
+ _attrs = [u"uuid",
+ u"short-name",
+ u"severity",
+ u"status",
+ u"assigned",
+ u"target",
+ u"reporter",
+ u"creator",
+ u"created",
+ u"summary",
+ u"comments",
+ u"extra-strings"]
+ def print_to_mbox(self):
+ name,addr = email.utils.parseaddr(self["creator"])
+ print "From %s %s" % (addr, rfc2822_to_asctime(self["created"]))
+ print "Message-ID: <%s@%s>" % (self["uuid"], DEFAULT_DOMAIN)
+ print "Date: %s" % self["created"]
+ print "From: %s" % self["creator"]
+ print "Content-Type: %s; charset=%s" % ("text/plain", DEFAULT_ENCODING)
+ print "Content-Transfer-Encoding: 8bit"
+ print "Subject: %s: %s" % (self["short-name"], self["summary"])
+ print ""
+ print self["summary"]
+ print ""
+ if "extra-strings" in self:
+ print "extra strings:\n ",
+ print '\n '.join(self["extra_strings"])
+ print ""
+ if "comments" in self:
+ for comment in self["comments"]:
+ comment.print_to_mbox(self)
+ def init_from_etree(self, element):
+ assert element.tag == "bug", element.tag
+ for field in element.getchildren():
+ text = unescape(unicode(field.text).decode("unicode_escape").strip())
+ if field.tag == "comment":
+ comm = Comment()
+ comm.init_from_etree(field)
+ if "comments" in self:
+ self["comments"].append(comm)
+ else:
+ self["comments"] = [comm]
+ elif field.tag == "extra-string":
+ if "extra-strings" in self:
+ self["extra-strings"].append(text)
+ else:
+ self["extra-strings"] = [text]
+ else:
+ self[field.tag] = text
+
+class Comment (LimitedAttrDict):
+ _attrs = [u"uuid",
+ u"alt-id",
+ u"short-name",
+ u"in-reply-to",
+ u"from",
+ u"date",
+ u"content-type",
+ u"body"]
+ def print_to_mbox(self, bug=None):
+ if bug == None:
+ bug = Bug()
+ bug[u"uuid"] = u"no-uuid"
+ name,addr = email.utils.parseaddr(self["from"])
+ print "From %s %s" % (addr, rfc2822_to_asctime(self["date"]))
+ if "uuid" in self: id = self["uuid"]
+ elif "alt-id" in self: id = self["alt-id"]
+ else: id = None
+ if id != None:
+ print "Message-ID: <%s@%s>" % (id, DEFAULT_DOMAIN)
+ print "Date: %s" % self["date"]
+ print "From: %s" % self["from"]
+ subject = ""
+ if "short-name" in self:
+ subject += self["short-name"]+u": "
+ if "summary" in bug:
+ subject += bug["summary"]
+ else:
+ subject += u"no-subject"
+ print "Subject: %s" % subject
+ if "in-reply-to" not in self.keys():
+ self["in-reply-to"] = bug["uuid"]
+ print "In-Reply-To: <%s@%s>" % (self["in-reply-to"], DEFAULT_DOMAIN)
+ if self["content-type"].startswith("text/"):
+ print "Content-Transfer-Encoding: 8bit"
+ print "Content-Type: %s; charset=%s" % (self["content-type"], DEFAULT_ENCODING)
+ print ""
+ print self["body"]
+ else: # content type and transfer encoding already in XML MIME output
+ print self["body"]
+ print ""
+ def init_from_etree(self, element):
+ assert element.tag == "comment", element.tag
+ for field in element.getchildren():
+ text = unescape(unicode(field.text).decode("unicode_escape").strip())
+ if field.tag == "body":
+ text+="\n"
+ self[field.tag] = text
+
+def print_to_mbox(element):
+ if element.tag == "bug":
+ b = Bug()
+ b.init_from_etree(element)
+ b.print_to_mbox()
+ elif element.tag == "comment":
+ c = Comment()
+ c.init_from_etree(element)
+ c.print_to_mbox()
+ elif element.tag in ["bugs", "bug-list"]:
+ for b_elt in element.getchildren():
+ b = Bug()
+ b.init_from_etree(b_elt)
+ b.print_to_mbox()
+ elif element.tag in ["comments", "comment-list"]:
+ for c_elt in element.getchildren():
+ c = Comment()
+ c.init_from_etree(c_elt)
+ c.print_to_mbox()
+
+if __name__ == "__main__":
+ import sys
+
+ if len(sys.argv) == 1: # no filename given, use stdin
+ xml_unicode = sys.stdin.read()
+ else:
+ xml_unicode = codecs.open(sys.argv[1], "r", DEFAULT_ENCODING).read()
+ xml_str = xml_unicode.encode("unicode_escape").replace(r"\n", "\n")
+ elist = ElementTree.XML(xml_str)
+ print_to_mbox(elist)