| Index: third_party/twisted_8_1/twisted/manhole/ui/spelunk_gnome.py
|
| diff --git a/third_party/twisted_8_1/twisted/manhole/ui/spelunk_gnome.py b/third_party/twisted_8_1/twisted/manhole/ui/spelunk_gnome.py
|
| deleted file mode 100644
|
| index 72a509d6ea665d138fdada576220259d0c2551d0..0000000000000000000000000000000000000000
|
| --- a/third_party/twisted_8_1/twisted/manhole/ui/spelunk_gnome.py
|
| +++ /dev/null
|
| @@ -1,665 +0,0 @@
|
| -# -*- Python -*-
|
| -# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
|
| -# See LICENSE for details.
|
| -
|
| -
|
| -"""Object browser GUI, GnomeCanvas implementation.
|
| -"""
|
| -
|
| -from twisted.python import log
|
| -
|
| -# TODO:
|
| -# gzigzag-style navigation
|
| -
|
| -class SillyModule:
|
| - def __init__(self, module, prefix):
|
| - self.__module = module
|
| - self.__prefix = prefix
|
| -
|
| - def __getattr__(self, attr):
|
| - try:
|
| - return getattr(self.__module, self.__prefix + attr)
|
| - except AttributeError:
|
| - return getattr(self.__module, attr)
|
| -
|
| -
|
| -# We use gnome.ui because that's what happens to have Python bindings
|
| -# for the Canvas. I think this canvas widget is available seperately
|
| -# in "libart", but nobody's given me Python bindings for just that.
|
| -
|
| -# The Gnome canvas is said to be modeled after the Tk canvas, so we
|
| -# could probably write this in Tk too. But my experience is with GTK,
|
| -# not with Tk, so this is what I use.
|
| -
|
| -import gnome.ui
|
| -gnome = SillyModule(gnome.ui, 'Gnome')
|
| -
|
| -import gtk
|
| -(True, False) = (gtk.TRUE, gtk.FALSE)
|
| -gtk = SillyModule(gtk, 'Gtk')
|
| -
|
| -import GDK
|
| -
|
| -from twisted.python import reflect, text
|
| -from twisted.spread import pb
|
| -from twisted.manhole import explorer
|
| -
|
| -import string, sys, types
|
| -import UserList
|
| -_PIXELS_PER_UNIT=10
|
| -
|
| -#### Support class.
|
| -
|
| -class PairList(UserList.UserList):
|
| - """An ordered list of key, value pairs.
|
| -
|
| - Kinda like an ordered dictionary. Made with small data sets
|
| - in mind, as get() does a linear search, not hashing.
|
| - """
|
| - def get(self, key):
|
| - i = 0
|
| - for k, v in self.data:
|
| - if key == k:
|
| - return (i, v)
|
| - i = i + 1
|
| - else:
|
| - return (None, None)
|
| -
|
| - def keys(self):
|
| - return map(lambda x: x[0], self.data)
|
| -
|
| -
|
| -#### Public
|
| -
|
| -class SpelunkDisplay(gnome.Canvas):
|
| - """Spelunk widget.
|
| -
|
| - The top-level widget for this module. This gtk.Widget is where the
|
| - explorer display will be, and this object is also your interface to
|
| - creating new visages.
|
| - """
|
| - def __init__(self, aa=False):
|
| - gnome.Canvas.__init__(self, aa)
|
| - self.set_pixels_per_unit(_PIXELS_PER_UNIT)
|
| - self.visages = {}
|
| -
|
| - def makeDefaultCanvas(self):
|
| - """Make myself the default canvas which new visages are created on.
|
| - """
|
| - # XXX: For some reason, the 'canvas' and 'parent' properties
|
| - # of CanvasItems aren't accessible thorugh pygnome.
|
| - Explorer.canvas = self
|
| -
|
| - def receiveExplorer(self, xplorer):
|
| - if self.visages.has_key(xplorer.id):
|
| - log.msg("Using cached visage for %d" % (xplorer.id, ))
|
| - # Ikk. Just because we just received this explorer, that
|
| - # doesn't necessarily mean its attributes are fresh. Fix
|
| - # that, either by having this side pull or the server
|
| - # side push.
|
| - visage = self.visages[xplorer.id]
|
| - #xplorer.give_properties(visage)
|
| - #xplorer.give_attributes(visage)
|
| - else:
|
| - log.msg("Making new visage for %d" % (xplorer.id, ))
|
| - self.visages[xplorer.id] = xplorer.newVisage(self.root(),
|
| - self)
|
| -
|
| -#### Base classes
|
| -
|
| -class Explorer(pb.RemoteCache):
|
| - """Base class for all RemoteCaches of explorer.Explorer cachables.
|
| -
|
| - Meaning that when an Explorer comes back over the wire, one of
|
| - these is created. From this, you can make a Visage for the
|
| - SpelunkDisplay, or a widget to display as an Attribute.
|
| - """
|
| - canvas = None
|
| - # From our cache:
|
| - id = None
|
| - identifier = None
|
| - explorerClass = None
|
| - attributeGroups = None
|
| -
|
| - def newVisage(self, group, canvas=None):
|
| - """Make a new visage for the object I explore.
|
| -
|
| - Returns a Visage.
|
| - """
|
| - canvas = canvas or self.canvas
|
| - klass = spelunkerClassTable.get(self.explorerClass, None)
|
| - if (not klass) or (klass[0] is None):
|
| - log.msg("%s not in table, using generic" % self.explorerClass)
|
| - klass = GenericVisage
|
| - else:
|
| - klass = klass[0]
|
| - spelunker = klass(self, group, canvas)
|
| - if hasattr(canvas, "visages") \
|
| - and not canvas.visages.has_key(self.id):
|
| - canvas.visages[self.id] = spelunker
|
| -
|
| - self.give_properties(spelunker)
|
| -
|
| - self.give_attributes(spelunker)
|
| -
|
| - return spelunker
|
| -
|
| - def newAttributeWidget(self, group):
|
| - """Make a new attribute item for my object.
|
| -
|
| - Returns a gtk.Widget.
|
| - """
|
| - klass = spelunkerClassTable.get(self.explorerClass, None)
|
| - if (not klass) or (klass[1] is None):
|
| - log.msg("%s not in table, using generic" % self.explorerClass)
|
| - klass = GenericAttributeWidget
|
| - else:
|
| - klass = klass[1]
|
| -
|
| - return klass(self, group)
|
| -
|
| - def give_properties(self, spelunker):
|
| - """Give a spelunker my properties in an ordered list.
|
| - """
|
| - valuelist = PairList()
|
| - for p in spelunker.propertyLabels.keys():
|
| - value = getattr(self, p, None)
|
| - valuelist.append((p,value))
|
| - spelunker.fill_properties(valuelist)
|
| -
|
| - def give_attributes(self, spelunker):
|
| - for a in spelunker.groupLabels.keys():
|
| - things = getattr(self, a)
|
| - spelunker.fill_attributeGroup(a, things)
|
| -
|
| -class _LooseBoxBorder:
|
| - box = None
|
| - color = 'black'
|
| - width = 1
|
| - def __init__(self, box):
|
| - self.box = box
|
| -
|
| -class LooseBox(gnome.CanvasGroup):
|
| - def __init__(self):
|
| - self.border = _LooseBoxBorder(self)
|
| -
|
| -class Visage(gnome.CanvasGroup):
|
| - """A \"face\" of an object under exploration.
|
| -
|
| - A Visage is a representation of an object presented to the user.
|
| - The \"face\" in \"interface\".
|
| -
|
| - 'propertyLabels' and 'groupLabels' are lists of (key, name)
|
| - 2-ples, with 'key' being the string the property or group is
|
| - denoted by in the code, and 'name' being the pretty human-readable
|
| - string you want me to show on the Visage. These attributes are
|
| - accumulated from base classes as well.
|
| -
|
| - I am a gnome.CanvasItem (more specifically, CanvasGroup).
|
| - """
|
| - color = {'border': '#006644'}
|
| - border_width = 8
|
| - detail_level = 0
|
| - # These are mappings from the strings the code calls these by
|
| - # and the pretty names you want to see on the screen.
|
| - # (e.g. Capitalized or localized)
|
| - propertyLabels = []
|
| - groupLabels = []
|
| -
|
| - drag_x0 = 0
|
| - drag_y0 = 0
|
| -
|
| - def __init__(self, explorer, rootGroup, canvas):
|
| - """Place a new Visage of an explorer in a canvas group.
|
| -
|
| - I also need a 'canvas' reference is for certain coordinate
|
| - conversions, and pygnome doesn't give access to my GtkObject's
|
| - .canvas attribute. :(
|
| - """
|
| - # Ugh. PyGtk/GtkObject/GnomeCanvas interfacing grits.
|
| - gnome.CanvasGroup.__init__(self,
|
| - _obj = rootGroup.add('group')._o)
|
| -
|
| - self.propertyLabels = PairList()
|
| - reflect.accumulateClassList(self.__class__, 'propertyLabels',
|
| - self.propertyLabels)
|
| - self.groupLabels = PairList()
|
| - reflect.accumulateClassList(self.__class__, 'groupLabels',
|
| - self.groupLabels)
|
| -
|
| - self.explorer = explorer
|
| - self.identifier = explorer.identifier
|
| - self.objectId = explorer.id
|
| -
|
| - self.canvas = canvas
|
| - self.rootGroup = rootGroup
|
| -
|
| - self.ebox = gtk.EventBox()
|
| - self.ebox.set_name("Visage")
|
| - self.frame = gtk.Frame(self.identifier)
|
| - self.container = gtk.VBox()
|
| - self.ebox.add(self.frame)
|
| - self.frame.add(self.container)
|
| -
|
| - self.canvasWidget = self.add('widget', widget=self.ebox,
|
| - x=0, y=0, anchor=gtk.ANCHOR_NW,
|
| - size_pixels=0)
|
| -
|
| - self.border = self.add('rect', x1=0, y1=0,
|
| - x2=1, y2=1,
|
| - fill_color=None,
|
| - outline_color=self.color['border'],
|
| - width_pixels=self.border_width)
|
| -
|
| - self.subtable = {}
|
| -
|
| - self._setup_table()
|
| -
|
| - # TODO:
|
| - # Collapse me
|
| - # Movable/resizeable me
|
| - # Destroy me
|
| - # Set my detail level
|
| -
|
| - self.frame.connect("size_allocate", self.signal_size_allocate,
|
| - None)
|
| - self.connect("destroy", self.signal_destroy, None)
|
| - self.connect("event", self.signal_event)
|
| -
|
| - self.ebox.show_all()
|
| -
|
| - # Our creator will call our fill_ methods when she has the goods.
|
| -
|
| - def _setup_table(self):
|
| - """Called by __init__ to set up my main table.
|
| -
|
| - You can easily override me instead of clobbering __init__.
|
| - """
|
| -
|
| - table = gtk.Table(len(self.propertyLabels), 2)
|
| - self.container.add(table)
|
| - table.set_name("PropertyTable")
|
| - self.subtable['properties'] = table
|
| - row = 0
|
| -
|
| - for p, name in self.propertyLabels:
|
| - label = gtk.Label(name)
|
| - label.set_name("PropertyName")
|
| - label.set_data("property", p)
|
| - table.attach(label, 0, 1, row, row + 1)
|
| - label.set_alignment(0, 0)
|
| - row = row + 1
|
| -
|
| - # XXX: make these guys collapsable
|
| - for g, name in self.groupLabels:
|
| - table = gtk.Table(1, 2)
|
| - self.container.add(table)
|
| - table.set_name("AttributeGroupTable")
|
| - self.subtable[g] = table
|
| - label = gtk.Label(name)
|
| - label.set_name("AttributeGroupTitle")
|
| - table.attach(label, 0, 2, 0, 1)
|
| -
|
| - def fill_properties(self, propValues):
|
| - """Fill in values for my properites.
|
| -
|
| - Takes a list of (name, value) pairs. 'name' should be one of
|
| - the keys in my propertyLabels, and 'value' either an Explorer
|
| - or a string.
|
| - """
|
| - table = self.subtable['properties']
|
| -
|
| - table.resize(len(propValues), 2)
|
| -
|
| - # XXX: Do I need to destroy previously attached children?
|
| -
|
| - for name, value in propValues:
|
| - self.fill_property(name, value)
|
| -
|
| - table.show_all()
|
| -
|
| - def fill_property(self, property, value):
|
| - """Set a value for a particular property.
|
| -
|
| - 'property' should be one of the keys in my propertyLabels.
|
| - """
|
| - row, name = self.propertyLabels.get(property)
|
| - if type(value) is not types.InstanceType:
|
| - widget = gtk.Label(str(value))
|
| - widget.set_alignment(0, 0)
|
| - else:
|
| - widget = value.newAttributeWidget(self)
|
| - widget.set_name("PropertyValue")
|
| -
|
| - self.subtable['properties'].attach(widget, 1, 2, row, row+1)
|
| -
|
| - def fill_attributeGroup(self, group, attributes):
|
| - """Provide members of an attribute group.
|
| -
|
| - 'group' should be one of the keys in my groupLabels, and
|
| - 'attributes' a list of (name, value) pairs, with each value as
|
| - either an Explorer or string.
|
| - """
|
| -
|
| - # XXX: How to indicate detail level of members?
|
| -
|
| - table = self.subtable[group]
|
| - if not attributes:
|
| - table.hide()
|
| - return
|
| -
|
| - table.resize(len(attributes)+1, 2)
|
| -
|
| - # XXX: Do I need to destroy previously attached children?
|
| -
|
| - row = 1 # 0 is title
|
| -
|
| - for name, value in attributes.items():
|
| - label = gtk.Label(name)
|
| - label.set_name("AttributeName")
|
| - label.set_alignment(0, 0)
|
| -
|
| - if type(value) is types.StringType:
|
| - widget = gtk.Label(value)
|
| - widget.set_alignment(0, 0)
|
| - else:
|
| - widget = value.newAttributeWidget(self)
|
| -
|
| - table.attach(label, 0, 1, row, row + 1)
|
| - table.attach(widget, 1, 2, row, row + 1)
|
| - row = row + 1
|
| -
|
| - table.show_all()
|
| -
|
| - def signal_event(self, widget, event=None):
|
| - if not event:
|
| - log.msg("Huh? got event signal with no event.")
|
| - return
|
| - if event.type == GDK.BUTTON_PRESS:
|
| - if event.button == 1:
|
| - self.drag_x0, self.drag_y0 = event.x, event.y
|
| - return True
|
| - elif event.type == GDK.MOTION_NOTIFY:
|
| - if event.state & GDK.BUTTON1_MASK:
|
| - self.move(event.x - self.drag_x0, event.y - self.drag_y0)
|
| - self.drag_x0, self.drag_y0 = event.x, event.y
|
| - return True
|
| - return False
|
| -
|
| - def signal_size_allocate(self, frame_widget,
|
| - unusable_allocation, unused_data):
|
| - (x, y, w, h) = frame_widget.get_allocation()
|
| -
|
| - # XXX: allocation PyCObject is apparently unusable!
|
| - # (w, h) = allocation.width, allocation.height
|
| -
|
| - w, h = (float(w)/_PIXELS_PER_UNIT, float(h)/_PIXELS_PER_UNIT)
|
| -
|
| - x1, y1 = (self.canvasWidget['x'], self.canvasWidget['y'])
|
| -
|
| - b = self.border
|
| - (b['x1'], b['y1'], b['x2'], b['y2']) = (x1, y1, x1+w, y1+h)
|
| -
|
| - def signal_destroy(self, unused_object, unused_data):
|
| - del self.explorer
|
| -
|
| - del self.canvasWidget
|
| - del self.border
|
| -
|
| - del self.ebox
|
| - del self.frame
|
| - del self.container
|
| -
|
| - self.subtable.clear()
|
| -
|
| -
|
| -class AttributeWidget(gtk.Widget):
|
| - """A widget briefly describing an object.
|
| -
|
| - This is similar to a Visage, but has far less detail. This should
|
| - display only essential identifiying information, a gtk.Widget
|
| - suitable for including in a single table cell.
|
| -
|
| - (gtk.Widgets are used here instead of the more graphically
|
| - pleasing gnome.CanvasItems because I was too lazy to re-write
|
| - gtk.table for the canvas. A new table widget/item would be great
|
| - though, not only for canvas prettiness, but also because we could
|
| - use one with a mone pythonic API.)
|
| -
|
| - """
|
| - def __init__(self, explorer, parent):
|
| - """A new AttributeWidget describing an explorer.
|
| - """
|
| - self.parent = parent
|
| -
|
| - self.explorer = explorer
|
| - self.identifier = explorer.identifier
|
| - self.id = explorer.id
|
| -
|
| - widgetObj = self._makeWidgetObject()
|
| - gtk.Widget.__init__(self, _obj=widgetObj)
|
| - self.set_name("AttributeValue")
|
| - self.connect("destroy", self.signal_destroy, None)
|
| - self.connect("button-press-event", self.signal_buttonPressEvent,
|
| - None)
|
| -
|
| - def getTextForLabel(self):
|
| - """Returns text for my label.
|
| -
|
| - The default implementation of AttributeWidget is a gtk.Label
|
| - widget. You may override this method to change the text which
|
| - appears in the label. However, if you don't want to be a
|
| - label, override _makeWidgetObject instead.
|
| - """
|
| - return self.identifier
|
| -
|
| - def _makeWidgetObject(self):
|
| - """Make the GTK widget object that is me.
|
| -
|
| - Called by __init__ to construct the GtkObject I wrap-- the ._o
|
| - member of a pygtk GtkObject. Isn't subclassing GtkObjects in
|
| - Python fun?
|
| - """
|
| - ebox = gtk.EventBox()
|
| - label = gtk.Label(self.getTextForLabel())
|
| - label.set_alignment(0,0)
|
| - ebox.add(label)
|
| - return ebox._o
|
| -
|
| - def signal_destroy(self, unused_object, unused_data):
|
| - del self.explorer
|
| -
|
| - def signal_buttonPressEvent(self, widget, eventButton, unused_data):
|
| - if eventButton.type == GDK._2BUTTON_PRESS:
|
| - if self.parent.canvas.visages.has_key(self.explorer.id):
|
| - visage = self.parent.canvas.visages[self.explorer.id]
|
| - else:
|
| - visage = self.explorer.newVisage(self.parent.rootGroup,
|
| - self.parent.canvas)
|
| - (x, y, w, h) = self.get_allocation()
|
| - wx, wy = self.parent.canvas.c2w(x, y)
|
| -
|
| - x1, y1, x2, y2 = self.parent.get_bounds()
|
| -
|
| - v_x1, v_y1, v_x2, v_y2 = visage.get_bounds()
|
| -
|
| - visage.move(x2 - v_x1, wy + y1 - v_y1)
|
| -
|
| -
|
| -#### Widget-specific subclasses of Explorer, Visage, and Attribute
|
| -
|
| -# Instance
|
| -
|
| -class ExplorerInstance(Explorer):
|
| - pass
|
| -
|
| -class InstanceVisage(Visage):
|
| - # Detail levels:
|
| - # Just me
|
| - # me and my class
|
| - # me and my whole class heirarchy
|
| -
|
| - propertyLabels = [('klass', "Class")]
|
| - groupLabels = [('data', "Data"),
|
| - ('methods', "Methods")]
|
| -
|
| - detail = 0
|
| -
|
| - def __init__(self, explorer, group, canvas):
|
| - Visage.__init__(self, explorer, group, canvas)
|
| -
|
| - class_identifier = self.explorer.klass.name
|
| - # XXX: include partial module name in class?
|
| - self.frame.set_label("%s (%s)" % (self.identifier,
|
| - class_identifier))
|
| -
|
| -class InstanceAttributeWidget(AttributeWidget):
|
| - def getTextForLabel(self):
|
| - return "%s instance" % (self.explorer.klass.name,)
|
| -
|
| -
|
| -# Class
|
| -
|
| -class ExplorerClass(Explorer):
|
| - pass
|
| -
|
| -class ClassVisage(Visage):
|
| - propertyLabels = [("name", "Name"),
|
| - ("module", "Module"),
|
| - ("bases", "Bases")]
|
| - groupLabels = [('data', "Data"),
|
| - ('methods', "Methods")]
|
| -
|
| - def fill_properties(self, propValues):
|
| - Visage.fill_properties(self, propValues)
|
| - basesExplorer = propValues.get('bases')[1]
|
| - basesExplorer.view.callRemote("get_elements").addCallback(self.fill_bases)
|
| -
|
| - def fill_bases(self, baseExplorers):
|
| - box = gtk.HBox()
|
| - for b in baseExplorers:
|
| - box.add(b.newAttributeWidget(self))
|
| - row = self.propertyLabels.get('bases')[0]
|
| - self.subtable["properties"].attach(box, 1, 2, row, row+1)
|
| - box.show_all()
|
| -
|
| -class ClassAttributeWidget(AttributeWidget):
|
| - def getTextForLabel(self):
|
| - return self.explorer.name
|
| -
|
| -
|
| -# Function
|
| -
|
| -class ExplorerFunction(Explorer):
|
| - pass
|
| -
|
| -class FunctionAttributeWidget(AttributeWidget):
|
| - def getTextForLabel(self):
|
| - signature = self.explorer.signature
|
| - arglist = []
|
| - for arg in xrange(len(signature)):
|
| - name = signature.name[arg]
|
| - hasDefault, default = signature.get_default(arg)
|
| - if hasDefault:
|
| - if default.explorerClass == "ExplorerImmutable":
|
| - default = default.value
|
| - else:
|
| - # XXX
|
| - pass
|
| - a = "%s=%s" % (name, default)
|
| - elif signature.is_varlist(arg):
|
| - a = "*%s" % (name,)
|
| - elif signature.is_keyword(arg):
|
| - a = "**%s" % (name,)
|
| - else:
|
| - a = name
|
| - arglist.append(a)
|
| -
|
| - return string.join(arglist, ", ")
|
| -
|
| -
|
| -# Method
|
| -
|
| -class ExplorerMethod(ExplorerFunction):
|
| - pass
|
| -
|
| -class MethodAttributeWidget(FunctionAttributeWidget):
|
| - pass
|
| -
|
| -class ExplorerBulitin(Explorer):
|
| - pass
|
| -
|
| -class ExplorerModule(Explorer):
|
| - pass
|
| -
|
| -class ExplorerSequence(Explorer):
|
| - pass
|
| -
|
| -
|
| -# Sequence
|
| -
|
| -class SequenceVisage(Visage):
|
| - propertyLabels = [('len', 'length')]
|
| - # XXX: add elements group
|
| -
|
| -class SequenceAttributeWidget(AttributeWidget):
|
| - def getTextForLabel(self):
|
| - # XXX: Differentiate between lists and tuples.
|
| - if self.explorer.len:
|
| - txt = "list of length %d" % (self.explorer.len,)
|
| - else:
|
| - txt = "[]"
|
| - return txt
|
| -
|
| -
|
| -# Mapping
|
| -
|
| -class ExplorerMapping(Explorer):
|
| - pass
|
| -
|
| -class MappingVisage(Visage):
|
| - propertyLabels = [('len', 'length')]
|
| - # XXX: add items group
|
| -
|
| -class MappingAttributeWidget(AttributeWidget):
|
| - def getTextForLabel(self):
|
| - if self.explorer.len:
|
| - txt = "dict with %d elements" % (self.explorer.len,)
|
| - else:
|
| - txt = "{}"
|
| - return txt
|
| -
|
| -class ExplorerImmutable(Explorer):
|
| - pass
|
| -
|
| -
|
| -# Immutable
|
| -
|
| -class ImmutableVisage(Visage):
|
| - def __init__(self, explorer, rootGroup, canvas):
|
| - Visage.__init__(self, explorer, rootGroup, canvas)
|
| - widget = explorer.newAttributeWidget(self)
|
| - self.container.add(widget)
|
| - self.container.show_all()
|
| -
|
| -class ImmutableAttributeWidget(AttributeWidget):
|
| - def getTextForLabel(self):
|
| - return repr(self.explorer.value)
|
| -
|
| -
|
| -#### misc. module definitions
|
| -
|
| -spelunkerClassTable = {
|
| - "ExplorerInstance": (InstanceVisage, InstanceAttributeWidget),
|
| - "ExplorerFunction": (None, FunctionAttributeWidget),
|
| - "ExplorerMethod": (None, MethodAttributeWidget),
|
| - "ExplorerImmutable": (ImmutableVisage, ImmutableAttributeWidget),
|
| - "ExplorerClass": (ClassVisage, ClassAttributeWidget),
|
| - "ExplorerSequence": (SequenceVisage, SequenceAttributeWidget),
|
| - "ExplorerMapping": (MappingVisage, MappingAttributeWidget),
|
| - }
|
| -GenericVisage = Visage
|
| -GenericAttributeWidget = AttributeWidget
|
| -
|
| -pb.setCopierForClassTree(sys.modules[__name__],
|
| - Explorer, 'twisted.manhole.explorer')
|
|
|