diff options
author | Chris Ball <cjb@laptop.org> | 2009-07-23 17:49:13 -0400 |
---|---|---|
committer | Chris Ball <cjb@laptop.org> | 2009-07-23 17:49:13 -0400 |
commit | 6a639574fa95e50f82fa3052e5524b961295a7ab (patch) | |
tree | b498654ed1dcbdbba94605292c280c883c5e9faa /interfaces | |
parent | 5e249abfee7273c79640c4211607a6b4bf7b374c (diff) | |
parent | caf0111d9c571ac268c235880e6d18fa512e9efa (diff) | |
download | bugseverywhere-6a639574fa95e50f82fa3052e5524b961295a7ab.tar.gz |
Merge large rework from W. Trevor King.
Diffstat (limited to 'interfaces')
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', '&': '&', '\x3c': '<', + '\x3e': '>'} + for ch in to_unix(text): + if ch == ' ' and first_space is True: + yield ' ' + 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 Binary files differnew file mode 100644 index 0000000..790e438 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-b.png 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 Binary files differnew file mode 100644 index 0000000..5b43259 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-bl.png 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 Binary files differnew file mode 100644 index 0000000..6cfd62c --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-br.png 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 Binary files differnew file mode 100644 index 0000000..a6ce3ce --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-l.png 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 Binary files differnew file mode 100644 index 0000000..1ffd6f8 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-r.png 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 Binary files differnew file mode 100644 index 0000000..0129b0c --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-t.png 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 Binary files differnew file mode 100644 index 0000000..d616b77 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tl.png 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 Binary files differnew file mode 100644 index 0000000..18e542e --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds-tr.png 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 Binary files differnew file mode 100644 index 0000000..05a190e --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-b.png 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 Binary files differnew file mode 100644 index 0000000..0c3ea4c --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ds2-r.png diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.ico b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.ico Binary files differnew file mode 100644 index 0000000..339d09c --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.ico diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.png Binary files differnew file mode 100644 index 0000000..6dc53ee --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/favicon.png 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 Binary files differnew file mode 100644 index 0000000..cb4b56c --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/half-spiral.png 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 Binary files differnew file mode 100644 index 0000000..2b2d87d --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/header_inner.png diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/info.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/info.png Binary files differnew file mode 100644 index 0000000..329c523 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/info.png 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 Binary files differnew file mode 100644 index 0000000..25d3cfa --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-b.png 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 Binary files differnew file mode 100644 index 0000000..f496223 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-bl.png 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 Binary files differnew file mode 100644 index 0000000..74cbd91 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-br.png 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 Binary files differnew file mode 100644 index 0000000..dd567fa --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-l.png 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 Binary files differnew file mode 100644 index 0000000..9ac4486 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-r.png 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 Binary files differnew file mode 100644 index 0000000..fbb06c8 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-t.png 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 Binary files differnew file mode 100644 index 0000000..9336290 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tl.png 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 Binary files differnew file mode 100644 index 0000000..de74808 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/is-tr.png diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ok.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ok.png Binary files differnew file mode 100644 index 0000000..fee6751 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/ok.png diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/shadows.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/shadows.png Binary files differnew file mode 100644 index 0000000..9ddc676 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/shadows.png diff --git a/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/spiral.png b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/spiral.png Binary files differnew file mode 100644 index 0000000..b4bcb1e --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/spiral.png 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 Binary files differnew file mode 100644 index 0000000..bc9c79c --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/tg_under_the_hood.png 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 Binary files differnew file mode 100644 index 0000000..90e84b7 --- /dev/null +++ b/interfaces/web/Bugs-Everywhere-Web/beweb/static/images/under_the_hood_blue.png 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> </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"> </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) |