[Sugar-devel] [PATCH sugar-artwork] GTK+3 port

Manuel Quiñones manuq at laptop.org
Thu Dec 22 12:29:46 EST 2011


Work done by Benjamin Berg, Simon Schampijer,
Daniel Drake, Manuel Quiñones and Gonzalo Odiard

Signed-off-by: Manuel Quiñones <manuq at laptop.org>
---
 Makefile.am                                       |    2 +-
 configure.ac                                      |   10 +-
 gtk3/Makefile.am                                  |    1 +
 gtk3/theme/Makefile.am                            |   52 +
 gtk3/theme/assets/Makefile.am                     |   17 +
 gtk3/theme/assets/checkbox-checked-selected.svg   |   27 +
 gtk3/theme/assets/checkbox-checked.svg            |   27 +
 gtk3/theme/assets/checkbox-unchecked-selected.svg |   22 +
 gtk3/theme/assets/checkbox-unchecked.svg          |   22 +
 gtk3/theme/assets/radio-active-selected.svg       |   31 +
 gtk3/theme/assets/radio-active.svg                |   31 +
 gtk3/theme/assets/radio-selected.svg              |   26 +
 gtk3/theme/assets/radio.svg                       |   21 +
 gtk3/theme/assets/radio2.svg                      |   66 +
 gtk3/theme/assets/scale-slider-active.svg         |   29 +
 gtk3/theme/assets/scale-slider.svg                |   29 +
 gtk3/theme/assets/spinbutton-button-down.svg      |  110 +
 gtk3/theme/assets/spinbutton-button-up.svg        |  110 +
 gtk3/theme/em.py                                  | 3288 +++++++++++++++++++++
 gtk3/theme/gtk-widgets.css.em                     |  556 ++++
 gtk3/theme/gtk.css                                |  121 +
 gtk3/theme/settings.ini.em                        |   36 +
 22 files changed, 4631 insertions(+), 3 deletions(-)
 create mode 100644 gtk3/Makefile.am
 create mode 100644 gtk3/theme/Makefile.am
 create mode 100644 gtk3/theme/assets/Makefile.am
 create mode 100644 gtk3/theme/assets/checkbox-checked-selected.svg
 create mode 100644 gtk3/theme/assets/checkbox-checked.svg
 create mode 100644 gtk3/theme/assets/checkbox-unchecked-selected.svg
 create mode 100644 gtk3/theme/assets/checkbox-unchecked.svg
 create mode 100644 gtk3/theme/assets/radio-active-selected.svg
 create mode 100644 gtk3/theme/assets/radio-active.svg
 create mode 100644 gtk3/theme/assets/radio-selected.svg
 create mode 100644 gtk3/theme/assets/radio.svg
 create mode 100644 gtk3/theme/assets/radio2.svg
 create mode 100644 gtk3/theme/assets/scale-slider-active.svg
 create mode 100644 gtk3/theme/assets/scale-slider.svg
 create mode 100644 gtk3/theme/assets/spinbutton-button-down.svg
 create mode 100644 gtk3/theme/assets/spinbutton-button-up.svg
 create mode 100755 gtk3/theme/em.py
 create mode 100644 gtk3/theme/gtk-widgets.css.em
 create mode 100644 gtk3/theme/gtk.css
 create mode 100644 gtk3/theme/settings.ini.em

diff --git a/Makefile.am b/Makefile.am
index 4f78c3a..ea51bb2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1 +1 @@
-SUBDIRS = cursor icons gtk
+SUBDIRS = cursor icons gtk gtk3
diff --git a/configure.ac b/configure.ac
index a077e84..05cf525 100644
--- a/configure.ac
+++ b/configure.ac
@@ -22,10 +22,13 @@ if test -z "$ICON_SLICER"; then
     AC_MSG_ERROR([icon-slicer is required])
 fi
 
-PKG_CHECK_MODULES(GTK2, gtk+-2.0 >= 2.0.0,,
+PKG_CHECK_MODULES(GTK2, gtk+-2.0 >= 2.16.0,,
 	          AC_MSG_ERROR([GTK+-2.0 is required to compile sugar-artwork]))
 
-PKG_CHECK_MODULES(ENGINE, gtk+-2.0 >= 2.0 gobject-2.0 >= 2.0 cairo >= 0.1.1)
+PKG_CHECK_MODULES(GTK3, gtk+-3.0 >= 3.0.0,,
+	          AC_MSG_ERROR([GTK+-3.0 is required to compile sugar-artwork]))
+
+PKG_CHECK_MODULES(ENGINE, gtk+-2.0 >= 2.16 gobject-2.0 >= 2.0 cairo >= 0.1.1)
 
 GTK_VERSION=`$PKG_CONFIG --variable=gtk_binary_version gtk+-2.0`
 AC_SUBST(GTK_VERSION)
@@ -63,4 +66,7 @@ icons/scalable/status/Makefile
 gtk/Makefile
 gtk/engine/Makefile
 gtk/theme/Makefile
+gtk3/Makefile
+gtk3/theme/Makefile
+gtk3/theme/assets/Makefile
 ])
diff --git a/gtk3/Makefile.am b/gtk3/Makefile.am
new file mode 100644
index 0000000..aeb15cf
--- /dev/null
+++ b/gtk3/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = theme
diff --git a/gtk3/theme/Makefile.am b/gtk3/theme/Makefile.am
new file mode 100644
index 0000000..7ffed4f
--- /dev/null
+++ b/gtk3/theme/Makefile.am
@@ -0,0 +1,52 @@
+SUBDIRS = assets
+
+gtk-widgets-72.css: gtk-widgets.css.em
+	$(srcdir)/em.py -p $$ -D scaling=\'72\' $(srcdir)/gtk-widgets.css.em > \
+		$(top_builddir)/gtk3/theme/gtk-widgets-72.css
+
+gtk-widgets-100.css: gtk-widgets.css.em
+	$(srcdir)/em.py -p $$ -D scaling=\'100\' $(srcdir)/gtk-widgets.css.em > \
+		$(top_builddir)/gtk3/theme/gtk-widgets-100.css
+
+settings-72.ini: settings.ini.em
+	$(srcdir)/em.py -p $$ -D scaling=\'72\' $(srcdir)/settings.ini.em > \
+		$(top_builddir)/gtk3/theme/settings-72.ini
+
+settings-100.ini: settings.ini.em
+	$(srcdir)/em.py -p $$ -D scaling=\'100\' $(srcdir)/settings.ini.em > \
+		$(top_builddir)/gtk3/theme/settings-100.ini
+
+clean:
+	$(RM) gtk-widgets-100.css
+	$(RM) gtk-widgets-72.css
+	$(RM) settings-100.ini
+	$(RM) settings-72.ini
+
+GEN_FILES = \
+       gtk-widgets-72.css \
+       gtk-widgets-100.css \
+       settings-72.ini \
+       settings-100.ini
+
+install-data-local: $(GEN_FILES)
+	$(mkinstalldirs) $(DESTDIR)$(datadir)/themes/sugar-72/gtk-3.0
+	$(INSTALL_DATA) $(top_builddir)/gtk3/theme/gtk-widgets-72.css \
+		$(DESTDIR)$(datadir)/themes/sugar-72/gtk-3.0/gtk-widgets.css
+	$(INSTALL_DATA) $(top_builddir)/gtk3/theme/settings-72.ini \
+		$(DESTDIR)$(datadir)/themes/sugar-72/gtk-3.0/settings.ini
+	$(INSTALL_DATA) $(srcdir)/gtk.css \
+		$(DESTDIR)$(datadir)/themes/sugar-72/gtk-3.0/gtk.css
+	$(mkinstalldirs) $(DESTDIR)$(datadir)/themes/sugar-100/gtk-3.0
+	$(INSTALL_DATA) $(top_builddir)/gtk3/theme/gtk-widgets-72.css \
+		$(DESTDIR)$(datadir)/themes/sugar-100/gtk-3.0/gtk-widgets.css
+	$(INSTALL_DATA) $(top_builddir)/gtk3/theme/settings-100.ini \
+		$(DESTDIR)$(datadir)/themes/sugar-100/gtk-3.0/settings.ini
+	$(INSTALL_DATA) $(srcdir)/gtk.css \
+		$(DESTDIR)$(datadir)/themes/sugar-100/gtk-3.0/gtk.css
+
+uninstall-local:
+	rm -rf $(DESTDIR)$(datadir)/themes/sugar-72/gtk-3.0
+	rm -rf $(DESTDIR)$(datadir)/themes/sugar-100/gtk-3.0
+
+EXTRA_DIST = gtk-widgets.css.em settings.ini.em em.py gtk.css
+CLEANFILES = $(GEN_FILES)
diff --git a/gtk3/theme/assets/Makefile.am b/gtk3/theme/assets/Makefile.am
new file mode 100644
index 0000000..9691b72
--- /dev/null
+++ b/gtk3/theme/assets/Makefile.am
@@ -0,0 +1,17 @@
+assets =			\
+	scale-slider.svg	\
+	scale-slider-active.svg \
+	radio.svg		\
+	radio-selected.svg	\
+	radio-active.svg	\
+	radio-active-selected.svg	\
+	checkbox-unchecked.svg		\
+	checkbox-unchecked-selected.svg	\
+	checkbox-checked.svg		\
+	checkbox-checked-selected.svg
+
+sugar72dir = $(datadir)/themes/sugar-72/gtk-3.0/assets
+sugar100dir = $(datadir)/themes/sugar-100/gtk-3.0/assets
+
+dist_sugar72_DATA = $(assets)
+dist_sugar100_DATA = $(assets)
diff --git a/gtk3/theme/assets/checkbox-checked-selected.svg b/gtk3/theme/assets/checkbox-checked-selected.svg
new file mode 100644
index 0000000..8ec1223
--- /dev/null
+++ b/gtk3/theme/assets/checkbox-checked-selected.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+  <!ENTITY fill_color "#E5E5E5">
+  <!ENTITY stroke_color "#010101">
+]>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   version="1.1"
+   width="26"
+   height="26"
+   id="svg814">
+  <g
+     transform="translate(0,-1026.3622)"
+     id="layer1">
+    <rect
+       width="23.999523"
+       height="23.999525"
+       x="1"
+       y="1027.3627"
+       id="rect3268"
+       style="color:&stroke_color;;fill:&fill_color;;fill-opacity:1;stroke:#808080;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    <path
+       d="m 5.6810941,1039.239 4.7885489,4.7885 9.330512,-9.3305"
+       id="path3438"
+       style="fill:none;stroke:&stroke_color;;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+  </g>
+</svg>
diff --git a/gtk3/theme/assets/checkbox-checked.svg b/gtk3/theme/assets/checkbox-checked.svg
new file mode 100644
index 0000000..3cfce18
--- /dev/null
+++ b/gtk3/theme/assets/checkbox-checked.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+  <!ENTITY fill_color "#FFFFFF">
+  <!ENTITY stroke_color "#010101">
+]>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   version="1.1"
+   width="26"
+   height="26"
+   id="svg814">
+  <g
+     transform="translate(0,-1026.3622)"
+     id="layer1">
+    <rect
+       width="23.999523"
+       height="23.999525"
+       x="1"
+       y="1027.3627"
+       id="rect3268"
+       style="color:&stroke_color;;fill:&fill_color;;fill-opacity:1;stroke:#808080;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    <path
+       d="m 5.6810941,1039.239 4.7885489,4.7885 9.330512,-9.3305"
+       id="path3438"
+       style="fill:none;stroke:&stroke_color;;stroke-width:3;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+  </g>
+</svg>
diff --git a/gtk3/theme/assets/checkbox-unchecked-selected.svg b/gtk3/theme/assets/checkbox-unchecked-selected.svg
new file mode 100644
index 0000000..2263279
--- /dev/null
+++ b/gtk3/theme/assets/checkbox-unchecked-selected.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+  <!ENTITY fill_color "#E5E5E5">
+]>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   version="1.1"
+   width="26"
+   height="26"
+   id="svg814">
+  <g
+     transform="translate(0,-1026.3622)"
+     id="layer1">
+    <rect
+       width="23.999523"
+       height="23.999525"
+       x="1"
+       y="1027.3627"
+       id="rect3268"
+       style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:#808080;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+  </g>
+</svg>
diff --git a/gtk3/theme/assets/checkbox-unchecked.svg b/gtk3/theme/assets/checkbox-unchecked.svg
new file mode 100644
index 0000000..20c198e
--- /dev/null
+++ b/gtk3/theme/assets/checkbox-unchecked.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+  <!ENTITY fill_color "#FFFFFF">
+]>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   version="1.1"
+   width="26"
+   height="26"
+   id="svg814">
+  <g
+     transform="translate(0,-1026.3622)"
+     id="layer1">
+    <rect
+       width="23.999523"
+       height="23.999525"
+       x="1"
+       y="1027.3627"
+       id="rect3268"
+       style="color:#000000;fill:none;stroke:#808080;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+  </g>
+</svg>
diff --git a/gtk3/theme/assets/radio-active-selected.svg b/gtk3/theme/assets/radio-active-selected.svg
new file mode 100644
index 0000000..c1d1085
--- /dev/null
+++ b/gtk3/theme/assets/radio-active-selected.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+  <!ENTITY fill_color "#E5E5E5">
+  <!ENTITY stroke_color "#808080">
+]>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   version="1.1"
+   width="26"
+   height="26"
+   id="svg27352">
+  <g
+     transform="translate(0,10)"
+     id="layer1">
+    <path
+       d="M 13,0 C 5.8202983,0 0,5.8202983 0,13 0,20.179702 5.8202983,26 13,26 20.179702,26 26,20.179702 26,13 26,5.8202983 20.179702,0 13,0 z m 0,1 C 19.627417,1 25,6.372583 25,13 25,19.627417 19.627417,25 13,25 6.372583,25 1,19.627417 1,13 1,6.372583 6.372583,1 13,1 z"
+       transform="translate(0,-10)"
+       id="path3002"
+       style="color:#000000;fill:&stroke_color;;fill-opacity:1;stroke:none;stroke-width:0.82332432;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    <path
+       d="m 11.276603,5.8201666 a 5.3516083,5.3516083 0 1 1 -10.70321633,0 5.3516083,5.3516083 0 1 1 10.70321633,0 z"
+       transform="matrix(2.2423166,0,0,2.2423166,-0.28571452,-10.050656)"
+       id="path4079"
+       style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:none;stroke-width:0.82325572;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    <path
+       d="m 14.100023,7.3515821 a 5.9129128,5.9129128 0 1 1 -11.8258259,0 5.9129128,5.9129128 0 1 1 11.8258259,0 z"
+       transform="matrix(0.84560692,0,0,0.84560692,6.0769232,-3.2165487)"
+       id="path4754"
+       style="color:#000000;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.84599996;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+  </g>
+</svg>
diff --git a/gtk3/theme/assets/radio-active.svg b/gtk3/theme/assets/radio-active.svg
new file mode 100644
index 0000000..a5fe591
--- /dev/null
+++ b/gtk3/theme/assets/radio-active.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+  <!ENTITY fill_color "#FFFFFF">
+  <!ENTITY stroke_color "#808080">
+]>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   version="1.1"
+   width="26"
+   height="26"
+   id="svg27352">
+  <g
+     transform="translate(0,10)"
+     id="layer1">
+    <path
+       d="M 13,0 C 5.8202983,0 0,5.8202983 0,13 0,20.179702 5.8202983,26 13,26 20.179702,26 26,20.179702 26,13 26,5.8202983 20.179702,0 13,0 z m 0,1 C 19.627417,1 25,6.372583 25,13 25,19.627417 19.627417,25 13,25 6.372583,25 1,19.627417 1,13 1,6.372583 6.372583,1 13,1 z"
+       transform="translate(0,-10)"
+       id="path3002"
+       style="color:#000000;fill:&stroke_color;;fill-opacity:1;stroke:none;stroke-width:0.82332432;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    <path
+       d="m 11.276603,5.8201666 a 5.3516083,5.3516083 0 1 1 -10.70321633,0 5.3516083,5.3516083 0 1 1 10.70321633,0 z"
+       transform="matrix(2.2423166,0,0,2.2423166,-0.28571452,-10.050656)"
+       id="path4079"
+       style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:none;stroke-width:0.82325572;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    <path
+       d="m 14.100023,7.3515821 a 5.9129128,5.9129128 0 1 1 -11.8258259,0 5.9129128,5.9129128 0 1 1 11.8258259,0 z"
+       transform="matrix(0.84560692,0,0,0.84560692,6.0769232,-3.2165487)"
+       id="path4754"
+       style="color:#000000;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.84599996;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+  </g>
+</svg>
diff --git a/gtk3/theme/assets/radio-selected.svg b/gtk3/theme/assets/radio-selected.svg
new file mode 100644
index 0000000..a24b97e
--- /dev/null
+++ b/gtk3/theme/assets/radio-selected.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+  <!ENTITY fill_color "#E5E5E5">
+  <!ENTITY stroke_color "#808080">
+]>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   version="1.1"
+   width="26"
+   height="26"
+   id="svg27352">
+  <g
+     transform="translate(0,10)"
+     id="layer1">
+    <path
+       d="M 13,0 C 5.8202983,0 0,5.8202983 0,13 0,20.179702 5.8202983,26 13,26 20.179702,26 26,20.179702 26,13 26,5.8202983 20.179702,0 13,0 z m 0,1 C 19.627417,1 25,6.372583 25,13 25,19.627417 19.627417,25 13,25 6.372583,25 1,19.627417 1,13 1,6.372583 6.372583,1 13,1 z"
+       transform="translate(0,-10)"
+       id="path3002"
+       style="color:#000000;fill:&stroke_color;;fill-opacity:1;stroke:none;stroke-width:0.82332432;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    <path
+       d="m 11.276603,5.8201666 a 5.3516083,5.3516083 0 1 1 -10.70321633,0 5.3516083,5.3516083 0 1 1 10.70321633,0 z"
+       transform="matrix(2.2423166,0,0,2.2423166,-0.28571452,-10.050656)"
+       id="path4079"
+       style="color:#000000;fill:&fill_color;;fill-opacity:1;stroke:none;stroke-width:0.82325572;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+  </g>
+</svg>
diff --git a/gtk3/theme/assets/radio.svg b/gtk3/theme/assets/radio.svg
new file mode 100644
index 0000000..25f104c
--- /dev/null
+++ b/gtk3/theme/assets/radio.svg
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+  <!ENTITY fill_color "#FFFFFF">
+  <!ENTITY stroke_color "#808080">
+]>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   version="1.1"
+   width="26"
+   height="26"
+   id="svg27352">
+  <g
+     transform="translate(0,10)"
+     id="layer1">
+    <path
+       d="M 13,0 C 5.8202983,0 0,5.8202983 0,13 0,20.179702 5.8202983,26 13,26 20.179702,26 26,20.179702 26,13 26,5.8202983 20.179702,0 13,0 z m 0,1 C 19.627417,1 25,6.372583 25,13 25,19.627417 19.627417,25 13,25 6.372583,25 1,19.627417 1,13 1,6.372583 6.372583,1 13,1 z"
+       transform="translate(0,-10)"
+       id="path3002"
+       style="color:#000000;fill:&stroke_color;;fill-opacity:1;stroke:none;stroke-width:0.82332432;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+  </g>
+</svg>
diff --git a/gtk3/theme/assets/radio2.svg b/gtk3/theme/assets/radio2.svg
new file mode 100644
index 0000000..d54da65
--- /dev/null
+++ b/gtk3/theme/assets/radio2.svg
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16"
+   height="16"
+   id="svg27352"
+   version="1.1"
+   inkscape:version="0.48.1 r9760"
+   sodipodi:docname="radio2.svg">
+  <defs
+     id="defs27354" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ff00ff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="1"
+     inkscape:pageshadow="2"
+     inkscape:zoom="21.1875"
+     inkscape:cx="7.9955005"
+     inkscape:cy="7.9955014"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1024"
+     inkscape:window-height="542"
+     inkscape:window-x="0"
+     inkscape:window-y="26"
+     inkscape:window-maximized="1" />
+  <metadata
+     id="metadata27357">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer">
+    <path
+       sodipodi:type="arc"
+       style="color:#000000;fill:none;stroke:#808080;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="path3002"
+       sodipodi:cx="5.9249949"
+       sodipodi:cy="5.8201666"
+       sodipodi:rx="5.3516083"
+       sodipodi:ry="5.3516083"
+       d="m 11.276603,5.8201666 a 5.3516083,5.3516083 0 1 1 -10.70321633,0 5.3516083,5.3516083 0 1 1 10.70321633,0 z"
+       transform="matrix(1.3671455,0,0,1.3671455,-0.10482967,0.04748408)" />
+  </g>
+</svg>
diff --git a/gtk3/theme/assets/scale-slider-active.svg b/gtk3/theme/assets/scale-slider-active.svg
new file mode 100644
index 0000000..63fcb51
--- /dev/null
+++ b/gtk3/theme/assets/scale-slider-active.svg
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+  <!ENTITY fill_color "#FFFFFF">
+  <!ENTITY stroke_color "#808080">
+]>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   version="1.1"
+   width="32"
+   height="32"
+   id="svg5980">
+  <g
+     id="layer1">
+    <g
+       transform="matrix(1.9661528,0,0,1.9661528,0.27077716,-27.255355)"
+       id="g5104">
+      <path
+         d="m 319.25,290.25 a 6.875,6.875 0 1 1 -13.75,0 6.875,6.875 0 1 1 13.75,0 z"
+         transform="matrix(1.0909091,0,0,1.0909091,-332.77273,-294.63637)"
+         id="path5387"
+         style="color:#000000;fill:&fill_color;;fill-opacity:1;fill-rule:nonzero;stroke:&stroke_color;;stroke-width:0.91666669;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+      <path
+         d="m 319.25,290.25 a 6.875,6.875 0 1 1 -13.75,0 6.875,6.875 0 1 1 13.75,0 z"
+         transform="matrix(0.6088795,0,0,0.6088795,-182.19873,-154.72728)"
+         id="path5102"
+         style="color:#000000;fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.91666669;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    </g>
+  </g>
+</svg>
diff --git a/gtk3/theme/assets/scale-slider.svg b/gtk3/theme/assets/scale-slider.svg
new file mode 100644
index 0000000..9dbcacd
--- /dev/null
+++ b/gtk3/theme/assets/scale-slider.svg
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+  <!ENTITY fill_color "#e5e5e5">
+  <!ENTITY stroke_color "#808080">
+]>
+<svg
+   xmlns="http://www.w3.org/2000/svg"
+   version="1.1"
+   width="32"
+   height="32"
+   id="svg5980">
+  <g
+     id="layer1">
+    <g
+       transform="matrix(1.9661528,0,0,1.9661528,0.27077716,-27.255355)"
+       id="g5104">
+      <path
+         d="m 319.25,290.25 a 6.875,6.875 0 1 1 -13.75,0 6.875,6.875 0 1 1 13.75,0 z"
+         transform="matrix(1.0909091,0,0,1.0909091,-332.77273,-294.63637)"
+         id="path5387"
+         style="color:#000000;fill:&fill_color;;fill-opacity:1;fill-rule:nonzero;stroke:&stroke_color;;stroke-width:0.91666669;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+      <path
+         d="m 319.25,290.25 a 6.875,6.875 0 1 1 -13.75,0 6.875,6.875 0 1 1 13.75,0 z"
+         transform="matrix(0.6088795,0,0,0.6088795,-182.19873,-154.72728)"
+         id="path5102"
+         style="color:#000000;fill:&stroke_color;;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.91666669;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
+    </g>
+  </g>
+</svg>
diff --git a/gtk3/theme/assets/spinbutton-button-down.svg b/gtk3/theme/assets/spinbutton-button-down.svg
new file mode 100644
index 0000000..bc11819
--- /dev/null
+++ b/gtk3/theme/assets/spinbutton-button-down.svg
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16"
+   height="16"
+   id="svg5980"
+   version="1.1"
+   inkscape:version="0.48.1 r9760"
+   sodipodi:docname="spinbutton-button-up.svg">
+  <defs
+     id="defs5982">
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4714-2"
+       id="linearGradient5304-8"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,-1,1,0,-167.30724,401.40146)"
+       x1="108.59611"
+       y1="477.02258"
+       x2="113.8317"
+       y2="477.02258" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4714-2">
+      <stop
+         style="stop-color:#eeeeec;stop-opacity:1;"
+         offset="0"
+         id="stop4716-4" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="1"
+         id="stop4718-5" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4722-3"
+       id="linearGradient5306-4"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,-1,1,0,-166.30724,401.40146)"
+       x1="116.15861"
+       y1="479.89758"
+       x2="107.97111"
+       y2="479.89758" />
+    <linearGradient
+       id="linearGradient4722-3">
+      <stop
+         style="stop-color:#babdb6;stop-opacity:1;"
+         offset="0"
+         id="stop4724-2" />
+      <stop
+         style="stop-color:#7b8073;stop-opacity:1"
+         offset="1"
+         id="stop4726-2" />
+    </linearGradient>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ff06ff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="1"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.313708"
+     inkscape:cx="8.4899306"
+     inkscape:cy="6.0708311"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1024"
+     inkscape:window-height="542"
+     inkscape:window-x="0"
+     inkscape:window-y="26"
+     inkscape:window-maximized="1"
+     showguides="true"
+     inkscape:guide-bbox="true" />
+  <metadata
+     id="metadata5985">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,-16)">
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:2.142;stroke-opacity:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;stroke-miterlimit:4;stroke-dasharray:none;stroke-linejoin:round;stroke-linecap:round"
+       d="M 13.065472,21.37973 7.6897859,26.755416 2.3141,21.37973"
+       id="rect5213"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccc" />
+  </g>
+</svg>
diff --git a/gtk3/theme/assets/spinbutton-button-up.svg b/gtk3/theme/assets/spinbutton-button-up.svg
new file mode 100644
index 0000000..4690037
--- /dev/null
+++ b/gtk3/theme/assets/spinbutton-button-up.svg
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16"
+   height="16"
+   id="svg5980"
+   version="1.1"
+   inkscape:version="0.48.1 r9760"
+   sodipodi:docname="spinbutton-button-down.svg">
+  <defs
+     id="defs5982">
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4714-2"
+       id="linearGradient5304-8"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,-1,1,0,-167.30724,401.40146)"
+       x1="108.59611"
+       y1="477.02258"
+       x2="113.8317"
+       y2="477.02258" />
+    <linearGradient
+       inkscape:collect="always"
+       id="linearGradient4714-2">
+      <stop
+         style="stop-color:#eeeeec;stop-opacity:1;"
+         offset="0"
+         id="stop4716-4" />
+      <stop
+         style="stop-color:#ffffff;stop-opacity:1"
+         offset="1"
+         id="stop4718-5" />
+    </linearGradient>
+    <linearGradient
+       inkscape:collect="always"
+       xlink:href="#linearGradient4722-3"
+       id="linearGradient5306-4"
+       gradientUnits="userSpaceOnUse"
+       gradientTransform="matrix(0,-1,1,0,-166.30724,401.40146)"
+       x1="116.15861"
+       y1="479.89758"
+       x2="107.97111"
+       y2="479.89758" />
+    <linearGradient
+       id="linearGradient4722-3">
+      <stop
+         style="stop-color:#babdb6;stop-opacity:1;"
+         offset="0"
+         id="stop4724-2" />
+      <stop
+         style="stop-color:#7b8073;stop-opacity:1"
+         offset="1"
+         id="stop4726-2" />
+    </linearGradient>
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ff06ff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="1"
+     inkscape:pageshadow="2"
+     inkscape:zoom="11.313708"
+     inkscape:cx="8.4899306"
+     inkscape:cy="6.0708311"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:grid-bbox="true"
+     inkscape:document-units="px"
+     inkscape:window-width="1024"
+     inkscape:window-height="542"
+     inkscape:window-x="0"
+     inkscape:window-y="26"
+     inkscape:window-maximized="1"
+     showguides="true"
+     inkscape:guide-bbox="true" />
+  <metadata
+     id="metadata5985">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     id="layer1"
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     transform="translate(0,-16)">
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:2.14199996;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="M 13.065472,26.755416 7.6897859,21.37973 2.3141,26.755416"
+       id="rect5213"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccc" />
+  </g>
+</svg>
diff --git a/gtk3/theme/em.py b/gtk3/theme/em.py
new file mode 100755
index 0000000..9f92124
--- /dev/null
+++ b/gtk3/theme/em.py
@@ -0,0 +1,3288 @@
+#!/usr/bin/env python
+#
+# $Id: //projects/empy/em.py#146 $ $Date: 2003/10/27 $
+
+"""
+A system for processing Python as markup embedded in text.
+"""
+
+
+__program__ = 'empy'
+__version__ = '3.3'
+__url__ = 'http://www.alcyone.com/software/empy/'
+__author__ = 'Erik Max Francis <max at alcyone.com>'
+__copyright__ = 'Copyright (C) 2002-2003 Erik Max Francis'
+__license__ = 'LGPL'
+
+
+import copy
+import getopt
+import os
+import re
+import string
+import sys
+import types
+
+try:
+    # The equivalent of import cStringIO as StringIO.
+    import cStringIO
+    StringIO = cStringIO
+    del cStringIO
+except ImportError:
+    import StringIO
+
+# For backward compatibility, we can't assume these are defined.
+False, True = 0, 1
+
+# Some basic defaults.
+FAILURE_CODE = 1
+DEFAULT_PREFIX = '@'
+DEFAULT_PSEUDOMODULE_NAME = 'empy'
+DEFAULT_SCRIPT_NAME = '?'
+SIGNIFICATOR_RE_SUFFIX = r"%(\S+)\s*(.*)\s*$"
+SIGNIFICATOR_RE_STRING = DEFAULT_PREFIX + SIGNIFICATOR_RE_SUFFIX
+BANGPATH = '#!'
+DEFAULT_CHUNK_SIZE = 8192
+DEFAULT_ERRORS = 'strict'
+
+# Character information.
+IDENTIFIER_FIRST_CHARS = '_abcdefghijklmnopqrstuvwxyz' \
+                         'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+IDENTIFIER_CHARS = IDENTIFIER_FIRST_CHARS + '0123456789.'
+ENDING_CHARS = {'(': ')', '[': ']', '{': '}'}
+
+# Environment variable names.
+OPTIONS_ENV = 'EMPY_OPTIONS'
+PREFIX_ENV = 'EMPY_PREFIX'
+PSEUDO_ENV = 'EMPY_PSEUDO'
+FLATTEN_ENV = 'EMPY_FLATTEN'
+RAW_ENV = 'EMPY_RAW_ERRORS'
+INTERACTIVE_ENV = 'EMPY_INTERACTIVE'
+BUFFERED_ENV = 'EMPY_BUFFERED_OUTPUT'
+NO_OVERRIDE_ENV = 'EMPY_NO_OVERRIDE'
+UNICODE_ENV = 'EMPY_UNICODE'
+INPUT_ENCODING_ENV = 'EMPY_UNICODE_INPUT_ENCODING'
+OUTPUT_ENCODING_ENV = 'EMPY_UNICODE_OUTPUT_ENCODING'
+INPUT_ERRORS_ENV = 'EMPY_UNICODE_INPUT_ERRORS'
+OUTPUT_ERRORS_ENV = 'EMPY_UNICODE_OUTPUT_ERRORS'
+
+# Interpreter options.
+BANGPATH_OPT = 'processBangpaths' # process bangpaths as comments?
+BUFFERED_OPT = 'bufferedOutput' # fully buffered output?
+RAW_OPT = 'rawErrors' # raw errors?
+EXIT_OPT = 'exitOnError' # exit on error?
+FLATTEN_OPT = 'flatten' # flatten pseudomodule namespace?
+OVERRIDE_OPT = 'override' # override sys.stdout with proxy?
+CALLBACK_OPT = 'noCallbackError' # is no custom callback an error?
+
+# Usage info.
+OPTION_INFO = [
+("-V --version", "Print version and exit"),
+("-h --help", "Print usage and exit"),
+("-H --extended-help", "Print extended usage and exit"),
+("-k --suppress-errors", "Do not exit on errors; go interactive"),
+("-p --prefix=<char>", "Change prefix to something other than @"),
+("   --no-prefix", "Do not do any markup processing at all"),
+("-m --module=<name>", "Change the internal pseudomodule name"),
+("-f --flatten", "Flatten the members of pseudmodule to start"),
+("-r --raw-errors", "Show raw Python errors"),
+("-i --interactive", "Go into interactive mode after processing"),
+("-n --no-override-stdout", "Do not override sys.stdout with proxy"),
+("-o --output=<filename>", "Specify file for output as write"),
+("-a --append=<filename>", "Specify file for output as append"),
+("-b --buffered-output", "Fully buffer output including open"),
+("   --binary", "Treat the file as a binary"),
+("   --chunk-size=<chunk>", "Use this chunk size for reading binaries"),
+("-P --preprocess=<filename>", "Interpret EmPy file before main processing"),
+("-I --import=<modules>", "Import Python modules before processing"),
+("-D --define=<definition>", "Execute Python assignment statement"),
+("-E --execute=<statement>", "Execute Python statement before processing"),
+("-F --execute-file=<filename>", "Execute Python file before processing"),
+("   --pause-at-end", "Prompt at the ending of processing"),
+("   --relative-path", "Add path of EmPy script to sys.path"),
+("   --no-callback-error", "Custom markup without callback is error"),
+("   --no-bangpath-processing", "Suppress bangpaths as comments"),
+("-u --unicode", "Enable Unicode subsystem (Python 2+ only)"),
+("   --unicode-encoding=<e>", "Set both input and output encodings"),
+("   --unicode-input-encoding=<e>", "Set input encoding"),
+("   --unicode-output-encoding=<e>", "Set output encoding"),
+("   --unicode-errors=<E>", "Set both input and output error handler"),
+("   --unicode-input-errors=<E>", "Set input error handler"),
+("   --unicode-output-errors=<E>", "Set output error handler"),
+]
+
+USAGE_NOTES = """\
+Notes: Whitespace immediately inside parentheses of @(...) are
+ignored.  Whitespace immediately inside braces of @{...} are ignored,
+unless ... spans multiple lines.  Use @{ ... }@ to suppress newline
+following expansion.  Simple expressions ignore trailing dots; `@x.'
+means `@(x).'.  A #! at the start of a file is treated as a @#
+comment."""
+
+MARKUP_INFO = [
+("@# ... NL", "Comment; remove everything up to newline"),
+("@? NAME NL", "Set the current context name"),
+("@! INTEGER NL", "Set the current context line number"),
+("@ WHITESPACE", "Remove following whitespace; line continuation"),
+("@\\ ESCAPE_CODE", "A C-style escape sequence"),
+("@@", "Literal @; @ is escaped (duplicated prefix)"),
+("@), @], @}", "Literal close parenthesis, bracket, brace"),
+("@ STRING_LITERAL", "Replace with string literal contents"),
+("@( EXPRESSION )", "Evaluate expression and substitute with str"),
+("@( TEST [? THEN [! ELSE]] )", "If test is true, evaluate then, otherwise else"),
+("@( TRY $ CATCH )", "Expand try expression, or catch if it raises"),
+("@ SIMPLE_EXPRESSION", "Evaluate simple expression and substitute;\n"
+                        "e.g., @x, @x.y, @f(a, b), @l[i], etc."),
+("@` EXPRESSION `", "Evaluate expression and substitute with repr"),
+("@: EXPRESSION : [DUMMY] :", "Evaluates to @:...:expansion:"),
+("@{ STATEMENTS }", "Statements are executed for side effects"),
+("@[ CONTROL ]", "Control markups: if E; elif E; for N in E;\n"
+                 "while E; try; except E, N; finally; continue;\n"
+                 "break; end X"),
+("@%% KEY WHITESPACE VALUE NL", "Significator form of __KEY__ = VALUE"),
+("@< CONTENTS >", "Custom markup; meaning provided by user"),
+]
+
+ESCAPE_INFO = [
+("@\\0", "NUL, null"),
+("@\\a", "BEL, bell"),
+("@\\b", "BS, backspace"),
+("@\\dDDD", "three-digit decimal code DDD"),
+("@\\e", "ESC, escape"),
+("@\\f", "FF, form feed"),
+("@\\h", "DEL, delete"),
+("@\\n", "LF, linefeed, newline"),
+("@\\N{NAME}", "Unicode character named NAME"),
+("@\\oOOO", "three-digit octal code OOO"),
+("@\\qQQQQ", "four-digit quaternary code QQQQ"),
+("@\\r", "CR, carriage return"),
+("@\\s", "SP, space"),
+("@\\t", "HT, horizontal tab"),
+("@\\uHHHH", "16-bit hexadecimal Unicode HHHH"),
+("@\\UHHHHHHHH", "32-bit hexadecimal Unicode HHHHHHHH"),
+("@\\v", "VT, vertical tab"),
+("@\\xHH", "two-digit hexadecimal code HH"),
+("@\\z", "EOT, end of transmission"),
+]
+
+PSEUDOMODULE_INFO = [
+("VERSION", "String representing EmPy version"),
+("SIGNIFICATOR_RE_STRING", "Regular expression matching significators"),
+("SIGNIFICATOR_RE_SUFFIX", "The above stub, lacking the prefix"),
+("interpreter", "Currently-executing interpreter instance"),
+("argv", "The EmPy script name and command line arguments"),
+("args", "The command line arguments only"),
+("identify()", "Identify top context as name, line"),
+("setContextName(name)", "Set the name of the current context"),
+("setContextLine(line)", "Set the line number of the current context"),
+("atExit(callable)", "Invoke no-argument function at shutdown"),
+("getGlobals()", "Retrieve this interpreter's globals"),
+("setGlobals(dict)", "Set this interpreter's globals"),
+("updateGlobals(dict)", "Merge dictionary into interpreter's globals"),
+("clearGlobals()", "Start globals over anew"),
+("saveGlobals([deep])", "Save a copy of the globals"),
+("restoreGlobals([pop])", "Restore the most recently saved globals"),
+("defined(name, [loc])", "Find if the name is defined"),
+("evaluate(expression, [loc])", "Evaluate the expression"),
+("serialize(expression, [loc])", "Evaluate and serialize the expression"),
+("execute(statements, [loc])", "Execute the statements"),
+("single(source, [loc])", "Execute the 'single' object"),
+("atomic(name, value, [loc])", "Perform an atomic assignment"),
+("assign(name, value, [loc])", "Perform an arbitrary assignment"),
+("significate(key, [value])", "Significate the given key, value pair"),
+("include(file, [loc])", "Include filename or file-like object"),
+("expand(string, [loc])", "Explicitly expand string and return"),
+("string(data, [name], [loc])", "Process string-like object"),
+("quote(string)", "Quote prefixes in provided string and return"),
+("flatten([keys])", "Flatten module contents into globals namespace"),
+("getPrefix()", "Get current prefix"),
+("setPrefix(char)", "Set new prefix"),
+("stopDiverting()", "Stop diverting; data sent directly to output"),
+("createDiversion(name)", "Create a diversion but do not divert to it"),
+("retrieveDiversion(name)", "Retrieve the actual named diversion object"),
+("startDiversion(name)", "Start diverting to given diversion"),
+("playDiversion(name)", "Recall diversion and then eliminate it"),
+("replayDiversion(name)", "Recall diversion but retain it"),
+("purgeDiversion(name)", "Erase diversion"),
+("playAllDiversions()", "Stop diverting and play all diversions in order"),
+("replayAllDiversions()", "Stop diverting and replay all diversions"),
+("purgeAllDiversions()", "Stop diverting and purge all diversions"),
+("getFilter()", "Get current filter"),
+("resetFilter()", "Reset filter; no filtering"),
+("nullFilter()", "Install null filter"),
+("setFilter(shortcut)", "Install new filter or filter chain"),
+("attachFilter(shortcut)", "Attach single filter to end of current chain"),
+("areHooksEnabled()", "Return whether or not hooks are enabled"),
+("enableHooks()", "Enable hooks (default)"),
+("disableHooks()", "Disable hook invocation"),
+("getHooks()", "Get all the hooks"),
+("clearHooks()", "Clear all hooks"),
+("addHook(hook, [i])", "Register the hook (optionally insert)"),
+("removeHook(hook)", "Remove an already-registered hook from name"),
+("invokeHook(name_, ...)", "Manually invoke hook"),
+("getCallback()", "Get interpreter callback"),
+("registerCallback(callback)", "Register callback with interpreter"),
+("deregisterCallback()", "Deregister callback from interpreter"),
+("invokeCallback(contents)", "Invoke the callback directly"),
+("Interpreter", "The interpreter class"),
+]
+
+ENVIRONMENT_INFO = [
+(OPTIONS_ENV, "Specified options will be included"),
+(PREFIX_ENV, "Specify the default prefix: -p <value>"),
+(PSEUDO_ENV, "Specify name of pseudomodule: -m <value>"),
+(FLATTEN_ENV, "Flatten empy pseudomodule if defined: -f"),
+(RAW_ENV, "Show raw errors if defined: -r"),
+(INTERACTIVE_ENV, "Enter interactive mode if defined: -i"),
+(BUFFERED_ENV, "Fully buffered output if defined: -b"),
+(NO_OVERRIDE_ENV, "Do not override sys.stdout if defined: -n"),
+(UNICODE_ENV, "Enable Unicode subsystem: -n"),
+(INPUT_ENCODING_ENV, "Unicode input encoding"),
+(OUTPUT_ENCODING_ENV, "Unicode output encoding"),
+(INPUT_ERRORS_ENV, "Unicode input error handler"),
+(OUTPUT_ERRORS_ENV, "Unicode output error handler"),
+]
+
+class Error(Exception):
+    """The base class for all EmPy errors."""
+    pass
+
+EmpyError = EmPyError = Error # DEPRECATED
+
+class DiversionError(Error):
+    """An error related to diversions."""
+    pass
+
+class FilterError(Error):
+    """An error related to filters."""
+    pass
+
+class StackUnderflowError(Error):
+    """A stack underflow."""
+    pass
+
+class SubsystemError(Error):
+    """An error associated with the Unicode subsystem."""
+    pass
+
+class FlowError(Error):
+    """An exception related to control flow."""
+    pass
+
+class ContinueFlow(FlowError):
+    """A continue control flow."""
+    pass
+
+class BreakFlow(FlowError):
+    """A break control flow."""
+    pass
+
+class ParseError(Error):
+    """A parse error occurred."""
+    pass
+
+class TransientParseError(ParseError):
+    """A parse error occurred which may be resolved by feeding more data.
+    Such an error reaching the toplevel is an unexpected EOF error."""
+    pass
+
+
+class MetaError(Exception):
+
+    """A wrapper around a real Python exception for including a copy of
+    the context."""
+    
+    def __init__(self, contexts, exc):
+        Exception.__init__(self, exc)
+        self.contexts = contexts
+        self.exc = exc
+
+    def __str__(self):
+        backtrace = map(lambda x: str(x), self.contexts)
+        return "%s: %s (%s)" % (self.exc.__class__, self.exc, \
+                                (string.join(backtrace, ', ')))
+
+
+class Subsystem:
+
+    """The subsystem class defers file creation so that it can create
+    Unicode-wrapped files if desired (and possible)."""
+
+    def __init__(self):
+        self.useUnicode = False
+        self.inputEncoding = None
+        self.outputEncoding = None
+        self.errors = None
+
+    def initialize(self, inputEncoding=None, outputEncoding=None, \
+                   inputErrors=None, outputErrors=None):
+        self.useUnicode = True
+        try:
+            unicode
+            import codecs
+        except (NameError, ImportError):
+            raise SubsystemError, "Unicode subsystem unavailable"
+        defaultEncoding = sys.getdefaultencoding()
+        if inputEncoding is None:
+            inputEncoding = defaultEncoding
+        self.inputEncoding = inputEncoding
+        if outputEncoding is None:
+            outputEncoding = defaultEncoding
+        self.outputEncoding = outputEncoding
+        if inputErrors is None:
+            inputErrors = DEFAULT_ERRORS
+        self.inputErrors = inputErrors
+        if outputErrors is None:
+            outputErrors = DEFAULT_ERRORS
+        self.outputErrors = outputErrors
+
+    def assertUnicode(self):
+        if not self.useUnicode:
+            raise SubsystemError, "Unicode subsystem unavailable"
+
+    def open(self, name, mode=None):
+        if self.useUnicode:
+            return self.unicodeOpen(name, mode)
+        else:
+            return self.defaultOpen(name, mode)
+
+    def defaultOpen(self, name, mode=None):
+        if mode is None:
+            mode = 'r'
+        return open(name, mode)
+
+    def unicodeOpen(self, name, mode=None):
+        import codecs
+        if mode is None:
+            mode = 'rb'
+        if mode.find('w') >= 0 or mode.find('a') >= 0:
+            encoding = self.outputEncoding
+            errors = self.outputErrors
+        else:
+            encoding = self.inputEncoding
+            errors = self.inputErrors
+        return codecs.open(name, mode, encoding, errors)
+
+theSubsystem = Subsystem()
+
+
+class Stack:
+    
+    """A simple stack that behaves as a sequence (with 0 being the top
+    of the stack, not the bottom)."""
+
+    def __init__(self, seq=None):
+        if seq is None:
+            seq = []
+        self.data = seq
+
+    def top(self):
+        """Access the top element on the stack."""
+        try:
+            return self.data[-1]
+        except IndexError:
+            raise StackUnderflowError, "stack is empty for top"
+        
+    def pop(self):
+        """Pop the top element off the stack and return it."""
+        try:
+            return self.data.pop()
+        except IndexError:
+            raise StackUnderflowError, "stack is empty for pop"
+        
+    def push(self, object):
+        """Push an element onto the top of the stack."""
+        self.data.append(object)
+
+    def filter(self, function):
+        """Filter the elements of the stack through the function."""
+        self.data = filter(function, self.data)
+
+    def purge(self):
+        """Purge the stack."""
+        self.data = []
+
+    def clone(self):
+        """Create a duplicate of this stack."""
+        return self.__class__(self.data[:])
+
+    def __nonzero__(self): return len(self.data) != 0
+    def __len__(self): return len(self.data)
+    def __getitem__(self, index): return self.data[-(index + 1)]
+
+    def __repr__(self):
+        return '<%s instance at 0x%x [%s]>' % \
+               (self.__class__, id(self), \
+                string.join(map(repr, self.data), ', '))
+
+
+class AbstractFile:
+    
+    """An abstracted file that, when buffered, will totally buffer the
+    file, including even the file open."""
+
+    def __init__(self, filename, mode='w', buffered=False):
+        # The calls below might throw, so start off by marking this
+        # file as "done."  This way destruction of a not-completely-
+        # initialized AbstractFile will generate no further errors.
+        self.done = True
+        self.filename = filename
+        self.mode = mode
+        self.buffered = buffered
+        if buffered:
+            self.bufferFile = StringIO.StringIO()
+        else:
+            self.bufferFile = theSubsystem.open(filename, mode)
+        # Okay, we got this far, so the AbstractFile is initialized.
+        # Flag it as "not done."
+        self.done = False
+
+    def __del__(self):
+        self.close()
+
+    def write(self, data):
+        self.bufferFile.write(data)
+
+    def writelines(self, data):
+        self.bufferFile.writelines(data)
+
+    def flush(self):
+        self.bufferFile.flush()
+
+    def close(self):
+        if not self.done:
+            self.commit()
+            self.done = True
+
+    def commit(self):
+        if self.buffered:
+            file = theSubsystem.open(self.filename, self.mode)
+            file.write(self.bufferFile.getvalue())
+            file.close()
+        else:
+            self.bufferFile.close()
+
+    def abort(self):
+        if self.buffered:
+            self.bufferFile = None
+        else:
+            self.bufferFile.close()
+            self.bufferFile = None
+        self.done = True
+
+
+class Diversion:
+
+    """The representation of an active diversion.  Diversions act as
+    (writable) file objects, and then can be recalled either as pure
+    strings or (readable) file objects."""
+
+    def __init__(self):
+        self.file = StringIO.StringIO()
+
+    # These methods define the writable file-like interface for the
+    # diversion.
+
+    def write(self, data):
+        self.file.write(data)
+
+    def writelines(self, lines):
+        for line in lines:
+            self.write(line)
+
+    def flush(self):
+        self.file.flush()
+
+    def close(self):
+        self.file.close()
+
+    # These methods are specific to diversions.
+
+    def asString(self):
+        """Return the diversion as a string."""
+        return self.file.getvalue()
+
+    def asFile(self):
+        """Return the diversion as a file."""
+        return StringIO.StringIO(self.file.getvalue())
+
+
+class Stream:
+    
+    """A wrapper around an (output) file object which supports
+    diversions and filtering."""
+    
+    def __init__(self, file):
+        self.file = file
+        self.currentDiversion = None
+        self.diversions = {}
+        self.filter = file
+        self.done = False
+
+    def write(self, data):
+        if self.currentDiversion is None:
+            self.filter.write(data)
+        else:
+            self.diversions[self.currentDiversion].write(data)
+    
+    def writelines(self, lines):
+        for line in lines:
+            self.write(line)
+
+    def flush(self):
+        self.filter.flush()
+
+    def close(self):
+        if not self.done:
+            self.undivertAll(True)
+            self.filter.close()
+            self.done = True
+
+    def shortcut(self, shortcut):
+        """Take a filter shortcut and translate it into a filter, returning
+        it.  Sequences don't count here; these should be detected
+        independently."""
+        if shortcut == 0:
+            return NullFilter()
+        elif type(shortcut) is types.FunctionType or \
+             type(shortcut) is types.BuiltinFunctionType or \
+             type(shortcut) is types.BuiltinMethodType or \
+             type(shortcut) is types.LambdaType:
+            return FunctionFilter(shortcut)
+        elif type(shortcut) is types.StringType:
+            return StringFilter(filter)
+        elif type(shortcut) is types.DictType:
+            raise NotImplementedError, "mapping filters not yet supported"
+        else:
+            # Presume it's a plain old filter.
+            return shortcut
+
+    def last(self):
+        """Find the last filter in the current filter chain, or None if
+        there are no filters installed."""
+        if self.filter is None:
+            return None
+        thisFilter, lastFilter = self.filter, None
+        while thisFilter is not None and thisFilter is not self.file:
+            lastFilter = thisFilter
+            thisFilter = thisFilter.next()
+        return lastFilter
+
+    def install(self, shortcut=None):
+        """Install a new filter; None means no filter.  Handle all the
+        special shortcuts for filters here."""
+        # Before starting, execute a flush.
+        self.filter.flush()
+        if shortcut is None or shortcut == [] or shortcut == ():
+            # Shortcuts for "no filter."
+            self.filter = self.file
+        else:
+            if type(shortcut) in (types.ListType, types.TupleType):
+                shortcuts = list(shortcut)
+            else:
+                shortcuts = [shortcut]
+            # Run through the shortcut filter names, replacing them with
+            # full-fledged instances of Filter.
+            filters = []
+            for shortcut in shortcuts:
+                filters.append(self.shortcut(shortcut))
+            if len(filters) > 1:
+                # If there's more than one filter provided, chain them
+                # together.
+                lastFilter = None
+                for filter in filters:
+                    if lastFilter is not None:
+                        lastFilter.attach(filter)
+                    lastFilter = filter
+                lastFilter.attach(self.file)
+                self.filter = filters[0]
+            else:
+                # If there's only one filter, assume that it's alone or it's
+                # part of a chain that has already been manually chained;
+                # just find the end.
+                filter = filters[0]
+                lastFilter = filter.last()
+                lastFilter.attach(self.file)
+                self.filter = filter
+
+    def attach(self, shortcut):
+        """Attached a solitary filter (no sequences allowed here) at the
+        end of the current filter chain."""
+        lastFilter = self.last()
+        if lastFilter is None:
+            # Just install it from scratch if there is no active filter.
+            self.install(shortcut)
+        else:
+            # Attach the last filter to this one, and this one to the file.
+            filter = self.shortcut(shortcut)
+            lastFilter.attach(filter)
+            filter.attach(self.file)
+
+    def revert(self):
+        """Reset any current diversions."""
+        self.currentDiversion = None
+
+    def create(self, name):
+        """Create a diversion if one does not already exist, but do not
+        divert to it yet."""
+        if name is None:
+            raise DiversionError, "diversion name must be non-None"
+        if not self.diversions.has_key(name):
+            self.diversions[name] = Diversion()
+
+    def retrieve(self, name):
+        """Retrieve the given diversion."""
+        if name is None:
+            raise DiversionError, "diversion name must be non-None"
+        if self.diversions.has_key(name):
+            return self.diversions[name]
+        else:
+            raise DiversionError, "nonexistent diversion: %s" % name
+
+    def divert(self, name):
+        """Start diverting."""
+        if name is None:
+            raise DiversionError, "diversion name must be non-None"
+        self.create(name)
+        self.currentDiversion = name
+
+    def undivert(self, name, purgeAfterwards=False):
+        """Undivert a particular diversion."""
+        if name is None:
+            raise DiversionError, "diversion name must be non-None"
+        if self.diversions.has_key(name):
+            diversion = self.diversions[name]
+            self.filter.write(diversion.asString())
+            if purgeAfterwards:
+                self.purge(name)
+        else:
+            raise DiversionError, "nonexistent diversion: %s" % name
+
+    def purge(self, name):
+        """Purge the specified diversion."""
+        if name is None:
+            raise DiversionError, "diversion name must be non-None"
+        if self.diversions.has_key(name):
+            del self.diversions[name]
+            if self.currentDiversion == name:
+                self.currentDiversion = None
+
+    def undivertAll(self, purgeAfterwards=True):
+        """Undivert all pending diversions."""
+        if self.diversions:
+            self.revert() # revert before undiverting!
+            names = self.diversions.keys()
+            names.sort()
+            for name in names:
+                self.undivert(name)
+                if purgeAfterwards:
+                    self.purge(name)
+            
+    def purgeAll(self):
+        """Eliminate all existing diversions."""
+        if self.diversions:
+            self.diversions = {}
+        self.currentDiversion = None
+
+
+class NullFile:
+
+    """A simple class that supports all the file-like object methods
+    but simply does nothing at all."""
+
+    def __init__(self): pass
+    def write(self, data): pass
+    def writelines(self, lines): pass
+    def flush(self): pass
+    def close(self): pass
+
+
+class UncloseableFile:
+
+    """A simple class which wraps around a delegate file-like object
+    and lets everything through except close calls."""
+
+    def __init__(self, delegate):
+        self.delegate = delegate
+
+    def write(self, data):
+        self.delegate.write(data)
+
+    def writelines(self, lines):
+        self.delegate.writelines(data)
+
+    def flush(self):
+        self.delegate.flush()
+
+    def close(self):
+        """Eat this one."""
+        pass
+
+
+class ProxyFile:
+
+    """The proxy file object that is intended to take the place of
+    sys.stdout.  The proxy can manage a stack of file objects it is
+    writing to, and an underlying raw file object."""
+
+    def __init__(self, bottom):
+        self.stack = Stack()
+        self.bottom = bottom
+
+    def current(self):
+        """Get the current stream to write to."""
+        if self.stack:
+            return self.stack[-1][1]
+        else:
+            return self.bottom
+
+    def push(self, interpreter):
+        self.stack.push((interpreter, interpreter.stream()))
+
+    def pop(self, interpreter):
+        result = self.stack.pop()
+        assert interpreter is result[0]
+
+    def clear(self, interpreter):
+        self.stack.filter(lambda x, i=interpreter: x[0] is not i)
+
+    def write(self, data):
+        self.current().write(data)
+
+    def writelines(self, lines):
+        self.current().writelines(lines)
+
+    def flush(self):
+        self.current().flush()
+
+    def close(self):
+        """Close the current file.  If the current file is the bottom, then
+        close it and dispose of it."""
+        current = self.current()
+        if current is self.bottom:
+            self.bottom = None
+        current.close()
+
+    def _testProxy(self): pass
+
+
+class Filter:
+
+    """An abstract filter."""
+
+    def __init__(self):
+        if self.__class__ is Filter:
+            raise NotImplementedError
+        self.sink = None
+
+    def next(self):
+        """Return the next filter/file-like object in the sequence, or None."""
+        return self.sink
+
+    def write(self, data):
+        """The standard write method; this must be overridden in subclasses."""
+        raise NotImplementedError
+
+    def writelines(self, lines):
+        """Standard writelines wrapper."""
+        for line in lines:
+            self.write(line)
+
+    def _flush(self):
+        """The _flush method should always flush the sink and should not
+        be overridden."""
+        self.sink.flush()
+
+    def flush(self):
+        """The flush method can be overridden."""
+        self._flush()
+
+    def close(self):
+        """Close the filter.  Do an explicit flush first, then close the
+        sink."""
+        self.flush()
+        self.sink.close()
+
+    def attach(self, filter):
+        """Attach a filter to this one."""
+        if self.sink is not None:
+            # If it's already attached, detach it first.
+            self.detach()
+        self.sink = filter
+
+    def detach(self):
+        """Detach a filter from its sink."""
+        self.flush()
+        self._flush() # do a guaranteed flush to just to be safe
+        self.sink = None
+
+    def last(self):
+        """Find the last filter in this chain."""
+        this, last = self, self
+        while this is not None:
+            last = this
+            this = this.next()
+        return last
+
+class NullFilter(Filter):
+
+    """A filter that never sends any output to its sink."""
+
+    def write(self, data): pass
+
+class FunctionFilter(Filter):
+
+    """A filter that works simply by pumping its input through a
+    function which maps strings into strings."""
+    
+    def __init__(self, function):
+        Filter.__init__(self)
+        self.function = function
+
+    def write(self, data):
+        self.sink.write(self.function(data))
+
+class StringFilter(Filter):
+
+    """A filter that takes a translation string (256 characters) and
+    filters any incoming data through it."""
+
+    def __init__(self, table):
+        if not (type(table) == types.StringType and len(table) == 256):
+            raise FilterError, "table must be 256-character string"
+        Filter.__init__(self)
+        self.table = table
+
+    def write(self, data):
+        self.sink.write(string.translate(data, self.table))
+
+class BufferedFilter(Filter):
+
+    """A buffered filter is one that doesn't modify the source data
+    sent to the sink, but instead holds it for a time.  The standard
+    variety only sends the data along when it receives a flush
+    command."""
+
+    def __init__(self):
+        Filter.__init__(self)
+        self.buffer = ''
+
+    def write(self, data):
+        self.buffer = self.buffer + data
+
+    def flush(self):
+        if self.buffer:
+            self.sink.write(self.buffer)
+        self._flush()
+
+class SizeBufferedFilter(BufferedFilter):
+
+    """A size-buffered filter only in fixed size chunks (excepting the
+    final chunk)."""
+
+    def __init__(self, bufferSize):
+        BufferedFilter.__init__(self)
+        self.bufferSize = bufferSize
+
+    def write(self, data):
+        BufferedFilter.write(self, data)
+        while len(self.buffer) > self.bufferSize:
+            chunk, self.buffer = \
+                self.buffer[:self.bufferSize], self.buffer[self.bufferSize:]
+            self.sink.write(chunk)
+
+class LineBufferedFilter(BufferedFilter):
+
+    """A line-buffered filter only lets data through when it sees
+    whole lines."""
+
+    def __init__(self):
+        BufferedFilter.__init__(self)
+
+    def write(self, data):
+        BufferedFilter.write(self, data)
+        chunks = string.split(self.buffer, '\n')
+        for chunk in chunks[:-1]:
+            self.sink.write(chunk + '\n')
+        self.buffer = chunks[-1]
+
+class MaximallyBufferedFilter(BufferedFilter):
+
+    """A maximally-buffered filter only lets its data through on the final
+    close.  It ignores flushes."""
+
+    def __init__(self):
+        BufferedFilter.__init__(self)
+
+    def flush(self): pass
+
+    def close(self):
+        if self.buffer:
+            BufferedFilter.flush(self)
+            self.sink.close()
+
+
+class Context:
+    
+    """An interpreter context, which encapsulates a name, an input
+    file object, and a parser object."""
+
+    DEFAULT_UNIT = 'lines'
+
+    def __init__(self, name, line=0, units=DEFAULT_UNIT):
+        self.name = name
+        self.line = line
+        self.units = units
+        self.pause = False
+
+    def bump(self, quantity=1):
+        if self.pause:
+            self.pause = False
+        else:
+            self.line = self.line + quantity
+
+    def identify(self):
+        return self.name, self.line
+
+    def __str__(self):
+        if self.units == self.DEFAULT_UNIT:
+            return "%s:%s" % (self.name, self.line)
+        else:
+            return "%s:%s[%s]" % (self.name, self.line, self.units)
+
+
+class Hook:
+
+    """The base class for implementing hooks."""
+
+    def __init__(self):
+        self.interpreter = None
+
+    def register(self, interpreter):
+        self.interpreter = interpreter
+
+    def deregister(self, interpreter):
+        if interpreter is not self.interpreter:
+            raise Error, "hook not associated with this interpreter"
+        self.interpreter = None
+
+    def push(self):
+        self.interpreter.push()
+
+    def pop(self):
+        self.interpreter.pop()
+
+    def null(self): pass
+
+    def atStartup(self): pass
+    def atReady(self): pass
+    def atFinalize(self): pass
+    def atShutdown(self): pass
+    def atParse(self, scanner, locals): pass
+    def atToken(self, token): pass
+    def atHandle(self, meta): pass
+    def atInteract(self): pass
+
+    def beforeInclude(self, name, file, locals): pass
+    def afterInclude(self): pass
+
+    def beforeExpand(self, string, locals): pass
+    def afterExpand(self, result): pass
+
+    def beforeFile(self, name, file, locals): pass
+    def afterFile(self): pass
+
+    def beforeBinary(self, name, file, chunkSize, locals): pass
+    def afterBinary(self): pass
+
+    def beforeString(self, name, string, locals): pass
+    def afterString(self): pass
+
+    def beforeQuote(self, string): pass
+    def afterQuote(self, result): pass
+
+    def beforeEscape(self, string, more): pass
+    def afterEscape(self, result): pass
+
+    def beforeControl(self, type, rest, locals): pass
+    def afterControl(self): pass
+
+    def beforeSignificate(self, key, value, locals): pass
+    def afterSignificate(self): pass
+
+    def beforeAtomic(self, name, value, locals): pass
+    def afterAtomic(self): pass
+
+    def beforeMulti(self, name, values, locals): pass
+    def afterMulti(self): pass
+
+    def beforeImport(self, name, locals): pass
+    def afterImport(self): pass
+
+    def beforeClause(self, catch, locals): pass
+    def afterClause(self, exception, variable): pass
+
+    def beforeSerialize(self, expression, locals): pass
+    def afterSerialize(self): pass
+
+    def beforeDefined(self, name, locals): pass
+    def afterDefined(self, result): pass
+
+    def beforeLiteral(self, text): pass
+    def afterLiteral(self): pass
+
+    def beforeEvaluate(self, expression, locals): pass
+    def afterEvaluate(self, result): pass
+
+    def beforeExecute(self, statements, locals): pass
+    def afterExecute(self): pass
+
+    def beforeSingle(self, source, locals): pass
+    def afterSingle(self): pass
+
+class VerboseHook(Hook):
+
+    """A verbose hook that reports all information received by the
+    hook interface.  This class dynamically scans the Hook base class
+    to ensure that all hook methods are properly represented."""
+
+    EXEMPT_ATTRIBUTES = ['register', 'deregister', 'push', 'pop']
+
+    def __init__(self, output=sys.stderr):
+        Hook.__init__(self)
+        self.output = output
+        self.indent = 0
+
+        class FakeMethod:
+            """This is a proxy method-like object."""
+            def __init__(self, hook, name):
+                self.hook = hook
+                self.name = name
+
+            def __call__(self, **keywords):
+                self.hook.output.write("%s%s: %s\n" % \
+                                       (' ' * self.hook.indent, \
+                                        self.name, repr(keywords)))
+
+        for attribute in dir(Hook):
+            if attribute[:1] != '_' and \
+                   attribute not in self.EXEMPT_ATTRIBUTES:
+                self.__dict__[attribute] = FakeMethod(self, attribute)
+        
+
+class Token:
+
+    """An element of expansion."""
+
+    def run(self, interpreter, locals):
+        raise NotImplementedError
+
+    def string(self):
+        raise NotImplementedError
+
+    def __str__(self): return self.string()
+
+class NullToken(Token):
+    """A chunk of data not containing markups."""
+    def __init__(self, data):
+        self.data = data
+
+    def run(self, interpreter, locals):
+        interpreter.write(self.data)
+
+    def string(self):
+        return self.data
+
+class ExpansionToken(Token):
+    """A token that involves an expansion."""
+    def __init__(self, prefix, first):
+        self.prefix = prefix
+        self.first = first
+
+    def scan(self, scanner):
+        pass
+
+    def run(self, interpreter, locals):
+        pass
+
+class WhitespaceToken(ExpansionToken):
+    """A whitespace markup."""
+    def string(self):
+        return '%s%s' % (self.prefix, self.first)
+
+class LiteralToken(ExpansionToken):
+    """A literal markup."""
+    def run(self, interpreter, locals):
+        interpreter.write(self.first)
+
+    def string(self):
+        return '%s%s' % (self.prefix, self.first)
+
+class PrefixToken(ExpansionToken):
+    """A prefix markup."""
+    def run(self, interpreter, locals):
+        interpreter.write(interpreter.prefix)
+
+    def string(self):
+        return self.prefix * 2
+        
+class CommentToken(ExpansionToken):
+    """A comment markup."""
+    def scan(self, scanner):
+        loc = scanner.find('\n')
+        if loc >= 0:
+            self.comment = scanner.chop(loc, 1)
+        else:
+            raise TransientParseError, "comment expects newline"
+
+    def string(self):
+        return '%s#%s\n' % (self.prefix, self.comment)
+
+class ContextNameToken(ExpansionToken):
+    """A context name change markup."""
+    def scan(self, scanner):
+        loc = scanner.find('\n')
+        if loc >= 0:
+            self.name = string.strip(scanner.chop(loc, 1))
+        else:
+            raise TransientParseError, "context name expects newline"
+
+    def run(self, interpreter, locals):
+        context = interpreter.context()
+        context.name = self.name
+
+class ContextLineToken(ExpansionToken):
+    """A context line change markup."""
+    def scan(self, scanner):
+        loc = scanner.find('\n')
+        if loc >= 0:
+            try:
+                self.line = int(scanner.chop(loc, 1))
+            except ValueError:
+                raise ParseError, "context line requires integer"
+        else:
+            raise TransientParseError, "context line expects newline"
+
+    def run(self, interpreter, locals):
+        context = interpreter.context()
+        context.line = self.line
+        context.pause = True
+
+class EscapeToken(ExpansionToken):
+    """An escape markup."""
+    def scan(self, scanner):
+        try:
+            code = scanner.chop(1)
+            result = None
+            if code in '()[]{}\'\"\\': # literals
+                result = code
+            elif code == '0': # NUL
+                result = '\x00'
+            elif code == 'a': # BEL
+                result = '\x07'
+            elif code == 'b': # BS
+                result = '\x08'
+            elif code == 'd': # decimal code
+                decimalCode = scanner.chop(3)
+                result = chr(string.atoi(decimalCode, 10))
+            elif code == 'e': # ESC
+                result = '\x1b'
+            elif code == 'f': # FF
+                result = '\x0c'
+            elif code == 'h': # DEL
+                result = '\x7f'
+            elif code == 'n': # LF (newline)
+                result = '\x0a'
+            elif code == 'N': # Unicode character name
+                theSubsystem.assertUnicode()
+                import unicodedata
+                if scanner.chop(1) != '{':
+                    raise ParseError, r"Unicode name escape should be \N{...}"
+                i = scanner.find('}')
+                name = scanner.chop(i, 1)
+                try:
+                    result = unicodedata.lookup(name)
+                except KeyError:
+                    raise SubsystemError, \
+                          "unknown Unicode character name: %s" % name
+            elif code == 'o': # octal code
+                octalCode = scanner.chop(3)
+                result = chr(string.atoi(octalCode, 8))
+            elif code == 'q': # quaternary code
+                quaternaryCode = scanner.chop(4)
+                result = chr(string.atoi(quaternaryCode, 4))
+            elif code == 'r': # CR
+                result = '\x0d'
+            elif code in 's ': # SP
+                result = ' '
+            elif code == 't': # HT
+                result = '\x09'
+            elif code in 'u': # Unicode 16-bit hex literal
+                theSubsystem.assertUnicode()
+                hexCode = scanner.chop(4)
+                result = unichr(string.atoi(hexCode, 16))
+            elif code in 'U': # Unicode 32-bit hex literal
+                theSubsystem.assertUnicode()
+                hexCode = scanner.chop(8)
+                result = unichr(string.atoi(hexCode, 16))
+            elif code == 'v': # VT
+                result = '\x0b'
+            elif code == 'x': # hexadecimal code
+                hexCode = scanner.chop(2)
+                result = chr(string.atoi(hexCode, 16))
+            elif code == 'z': # EOT
+                result = '\x04'
+            elif code == '^': # control character
+                controlCode = string.upper(scanner.chop(1))
+                if controlCode >= '@' and controlCode <= '`':
+                    result = chr(ord(controlCode) - ord('@'))
+                elif controlCode == '?':
+                    result = '\x7f'
+                else:
+                    raise ParseError, "invalid escape control code"
+            else:
+                raise ParseError, "unrecognized escape code"
+            assert result is not None
+            self.code = result
+        except ValueError:
+            raise ParseError, "invalid numeric escape code"
+
+    def run(self, interpreter, locals):
+        interpreter.write(self.code)
+
+    def string(self):
+        return '%s\\x%02x' % (self.prefix, ord(self.code))
+
+class SignificatorToken(ExpansionToken):
+    """A significator markup."""
+    def scan(self, scanner):
+        loc = scanner.find('\n')
+        if loc >= 0:
+            line = scanner.chop(loc, 1)
+            if not line:
+                raise ParseError, "significator must have nonblank key"
+            if line[0] in ' \t\v\n':
+                raise ParseError, "no whitespace between % and key"
+            # Work around a subtle CPython-Jython difference by stripping
+            # the string before splitting it: 'a '.split(None, 1) has two
+            # elements in Jython 2.1).
+            fields = string.split(string.strip(line), None, 1)
+            if len(fields) == 2 and fields[1] == '':
+                fields.pop()
+            self.key = fields[0]
+            if len(fields) < 2:
+                fields.append(None)
+            self.key, self.valueCode = fields
+        else:
+            raise TransientParseError, "significator expects newline"
+
+    def run(self, interpreter, locals):
+        value = self.valueCode
+        if value is not None:
+            value = interpreter.evaluate(string.strip(value), locals)
+        interpreter.significate(self.key, value)
+
+    def string(self):
+        if self.valueCode is None:
+            return '%s%%%s\n' % (self.prefix, self.key)
+        else:
+            return '%s%%%s %s\n' % (self.prefix, self.key, self.valueCode)
+
+class ExpressionToken(ExpansionToken):
+    """An expression markup."""
+    def scan(self, scanner):
+        z = scanner.complex('(', ')', 0)
+        try:
+            q = scanner.next('$', 0, z, True)
+        except ParseError:
+            q = z
+        try:
+            i = scanner.next('?', 0, q, True)
+            try:
+                j = scanner.next('!', i, q, True)
+            except ParseError:
+                try:
+                    j = scanner.next(':', i, q, True) # DEPRECATED
+                except ParseError:
+                    j = q
+        except ParseError:
+            i = j = q
+        code = scanner.chop(z, 1)
+        self.testCode = code[:i]
+        self.thenCode = code[i + 1:j]
+        self.elseCode = code[j + 1:q]
+        self.exceptCode = code[q + 1:z]
+
+    def run(self, interpreter, locals):
+        try:
+            result = interpreter.evaluate(self.testCode, locals)
+            if self.thenCode:
+                if result:
+                    result = interpreter.evaluate(self.thenCode, locals)
+                else:
+                    if self.elseCode:
+                        result = interpreter.evaluate(self.elseCode, locals)
+                    else:
+                        result = None
+        except SyntaxError:
+            # Don't catch syntax errors; let them through.
+            raise
+        except:
+            if self.exceptCode:
+                result = interpreter.evaluate(self.exceptCode, locals)
+            else:
+                raise
+        if result is not None:
+            interpreter.write(str(result))
+
+    def string(self):
+        result = self.testCode
+        if self.thenCode:
+            result = result + '?' + self.thenCode
+        if self.elseCode:
+            result = result + '!' + self.elseCode
+        if self.exceptCode:
+            result = result + '$' + self.exceptCode
+        return '%s(%s)' % (self.prefix, result)
+
+class StringLiteralToken(ExpansionToken):
+    """A string token markup."""
+    def scan(self, scanner):
+        scanner.retreat()
+        assert scanner[0] == self.first
+        i = scanner.quote()
+        self.literal = scanner.chop(i)
+
+    def run(self, interpreter, locals):
+        interpreter.literal(self.literal)
+
+    def string(self):
+        return '%s%s' % (self.prefix, self.literal)
+
+class SimpleExpressionToken(ExpansionToken):
+    """A simple expression markup."""
+    def scan(self, scanner):
+        i = scanner.simple()
+        self.code = self.first + scanner.chop(i)
+
+    def run(self, interpreter, locals):
+        interpreter.serialize(self.code, locals)
+
+    def string(self):
+        return '%s%s' % (self.prefix, self.code)
+
+class ReprToken(ExpansionToken):
+    """A repr markup."""
+    def scan(self, scanner):
+        i = scanner.next('`', 0)
+        self.code = scanner.chop(i, 1)
+
+    def run(self, interpreter, locals):
+        interpreter.write(repr(interpreter.evaluate(self.code, locals)))
+
+    def string(self):
+        return '%s`%s`' % (self.prefix, self.code)
+    
+class InPlaceToken(ExpansionToken):
+    """An in-place markup."""
+    def scan(self, scanner):
+        i = scanner.next(':', 0)
+        j = scanner.next(':', i + 1)
+        self.code = scanner.chop(i, j - i + 1)
+
+    def run(self, interpreter, locals):
+        interpreter.write("%s:%s:" % (interpreter.prefix, self.code))
+        try:
+            interpreter.serialize(self.code, locals)
+        finally:
+            interpreter.write(":")
+
+    def string(self):
+        return '%s:%s::' % (self.prefix, self.code)
+
+class StatementToken(ExpansionToken):
+    """A statement markup."""
+    def scan(self, scanner):
+        i = scanner.complex('{', '}', 0)
+        self.code = scanner.chop(i, 1)
+
+    def run(self, interpreter, locals):
+        interpreter.execute(self.code, locals)
+
+    def string(self):
+        return '%s{%s}' % (self.prefix, self.code)
+
+class CustomToken(ExpansionToken):
+    """A custom markup."""
+    def scan(self, scanner):
+        i = scanner.complex('<', '>', 0)
+        self.contents = scanner.chop(i, 1)
+
+    def run(self, interpreter, locals):
+        interpreter.invokeCallback(self.contents)
+
+    def string(self):
+        return '%s<%s>' % (self.prefix, self.contents)
+
+class ControlToken(ExpansionToken):
+
+    """A control token."""
+
+    PRIMARY_TYPES = ['if', 'for', 'while', 'try', 'def']
+    SECONDARY_TYPES = ['elif', 'else', 'except', 'finally']
+    TERTIARY_TYPES = ['continue', 'break']
+    GREEDY_TYPES = ['if', 'elif', 'for', 'while', 'def', 'end']
+    END_TYPES = ['end']
+
+    IN_RE = re.compile(r"\bin\b")
+    
+    def scan(self, scanner):
+        scanner.acquire()
+        i = scanner.complex('[', ']', 0)
+        self.contents = scanner.chop(i, 1)
+        fields = string.split(string.strip(self.contents), ' ', 1)
+        if len(fields) > 1:
+            self.type, self.rest = fields
+        else:
+            self.type = fields[0]
+            self.rest = None
+        self.subtokens = []
+        if self.type in self.GREEDY_TYPES and self.rest is None:
+            raise ParseError, "control '%s' needs arguments" % self.type
+        if self.type in self.PRIMARY_TYPES:
+            self.subscan(scanner, self.type)
+            self.kind = 'primary'
+        elif self.type in self.SECONDARY_TYPES:
+            self.kind = 'secondary'
+        elif self.type in self.TERTIARY_TYPES:
+            self.kind = 'tertiary'
+        elif self.type in self.END_TYPES:
+            self.kind = 'end'
+        else:
+            raise ParseError, "unknown control markup: '%s'" % self.type
+        scanner.release()
+
+    def subscan(self, scanner, primary):
+        """Do a subscan for contained tokens."""
+        while True:
+            token = scanner.one()
+            if token is None:
+                raise TransientParseError, \
+                      "control '%s' needs more tokens" % primary
+            if isinstance(token, ControlToken) and \
+                   token.type in self.END_TYPES:
+                if token.rest != primary:
+                    raise ParseError, \
+                          "control must end with 'end %s'" % primary
+                break
+            self.subtokens.append(token)
+
+    def build(self, allowed=None):
+        """Process the list of subtokens and divide it into a list of
+        2-tuples, consisting of the dividing tokens and the list of
+        subtokens that follow them.  If allowed is specified, it will
+        represent the list of the only secondary markup types which
+        are allowed."""
+        if allowed is None:
+            allowed = SECONDARY_TYPES
+        result = []
+        latest = []
+        result.append((self, latest))
+        for subtoken in self.subtokens:
+            if isinstance(subtoken, ControlToken) and \
+               subtoken.kind == 'secondary':
+                if subtoken.type not in allowed:
+                    raise ParseError, \
+                          "control unexpected secondary: '%s'" % subtoken.type
+                latest = []
+                result.append((subtoken, latest))
+            else:
+                latest.append(subtoken)
+        return result
+
+    def run(self, interpreter, locals):
+        interpreter.invoke('beforeControl', type=self.type, rest=self.rest, \
+                           locals=locals)
+        if self.type == 'if':
+            info = self.build(['elif', 'else'])
+            elseTokens = None
+            if info[-1][0].type == 'else':
+                elseTokens = info.pop()[1]
+            for secondary, subtokens in info:
+                if secondary.type not in ('if', 'elif'):
+                    raise ParseError, \
+                          "control 'if' unexpected secondary: '%s'" % secondary.type
+                if interpreter.evaluate(secondary.rest, locals):
+                    self.subrun(subtokens, interpreter, locals)
+                    break
+            else:
+                if elseTokens:
+                    self.subrun(elseTokens, interpreter, locals)
+        elif self.type == 'for':
+            sides = self.IN_RE.split(self.rest, 1)
+            if len(sides) != 2:
+                raise ParseError, "control expected 'for x in seq'"
+            iterator, sequenceCode = sides
+            info = self.build(['else'])
+            elseTokens = None
+            if info[-1][0].type == 'else':
+                elseTokens = info.pop()[1]
+            if len(info) != 1:
+                raise ParseError, "control 'for' expects at most one 'else'"
+            sequence = interpreter.evaluate(sequenceCode, locals)
+            for element in sequence:
+                try:
+                    interpreter.assign(iterator, element, locals)
+                    self.subrun(info[0][1], interpreter, locals)
+                except ContinueFlow:
+                    continue
+                except BreakFlow:
+                    break
+            else:
+                if elseTokens:
+                    self.subrun(elseTokens, interpreter, locals)
+        elif self.type == 'while':
+            testCode = self.rest
+            info = self.build(['else'])
+            elseTokens = None
+            if info[-1][0].type == 'else':
+                elseTokens = info.pop()[1]
+            if len(info) != 1:
+                raise ParseError, "control 'while' expects at most one 'else'"
+            atLeastOnce = False
+            while True:
+                try:
+                    if not interpreter.evaluate(testCode, locals):
+                        break
+                    atLeastOnce = True
+                    self.subrun(info[0][1], interpreter, locals)
+                except ContinueFlow:
+                    continue
+                except BreakFlow:
+                    break
+            if not atLeastOnce and elseTokens:
+                self.subrun(elseTokens, interpreter, locals)
+        elif self.type == 'try':
+            info = self.build(['except', 'finally'])
+            if len(info) == 1:
+                raise ParseError, "control 'try' needs 'except' or 'finally'"
+            type = info[-1][0].type
+            if type == 'except':
+                for secondary, _tokens in info[1:]:
+                    if secondary.type != 'except':
+                        raise ParseError, \
+                              "control 'try' cannot have 'except' and 'finally'"
+            else:
+                assert type == 'finally'
+                if len(info) != 2:
+                    raise ParseError, \
+                          "control 'try' can only have one 'finally'"
+            if type == 'except':
+                try:
+                    self.subrun(info[0][1], interpreter, locals)
+                except FlowError:
+                    raise
+                except Exception, e:
+                    for secondary, tokens in info[1:]:
+                        exception, variable = interpreter.clause(secondary.rest)
+                        if variable is not None:
+                            interpreter.assign(variable, e)
+                        if isinstance(e, exception):
+                            self.subrun(tokens, interpreter, locals)
+                            break
+                    else:
+                        raise
+            else:
+                try:
+                    self.subrun(info[0][1], interpreter, locals)
+                finally:
+                    self.subrun(info[1][1], interpreter, locals)
+        elif self.type == 'continue':
+            raise ContinueFlow, "control 'continue' without 'for', 'while'"
+        elif self.type == 'break':
+            raise BreakFlow, "control 'break' without 'for', 'while'"
+        elif self.type == 'def':
+            signature = self.rest
+            definition = self.substring()
+            code = 'def %s:\n' \
+                   ' r"""%s"""\n' \
+                   ' return %s.expand(r"""%s""", locals())\n' % \
+                   (signature, definition, interpreter.pseudo, definition)
+            interpreter.execute(code, locals)
+        elif self.type == 'end':
+            raise ParseError, "control 'end' requires primary markup"
+        else:
+            raise ParseError, \
+                  "control '%s' cannot be at this level" % self.type
+        interpreter.invoke('afterControl')
+
+    def subrun(self, tokens, interpreter, locals):
+        """Execute a sequence of tokens."""
+        for token in tokens:
+            token.run(interpreter, locals)
+
+    def substring(self):
+        return string.join(map(str, self.subtokens), '')
+
+    def string(self):
+        if self.kind == 'primary':
+            return '%s[%s]%s%s[end %s]' % \
+                   (self.prefix, self.contents, self.substring(), \
+                    self.prefix, self.type)
+        else:
+            return '%s[%s]' % (self.prefix, self.contents)
+
+
+class Scanner:
+
+    """A scanner holds a buffer for lookahead parsing and has the
+    ability to scan for special symbols and indicators in that
+    buffer."""
+
+    # This is the token mapping table that maps first characters to
+    # token classes.
+    TOKEN_MAP = [
+        (None,                   PrefixToken),
+        (' \t\v\r\n',            WhitespaceToken),
+        (')]}',                  LiteralToken),
+        ('\\',                   EscapeToken),
+        ('#',                    CommentToken),
+        ('?',                    ContextNameToken),
+        ('!',                    ContextLineToken),
+        ('%',                    SignificatorToken),
+        ('(',                    ExpressionToken),
+        (IDENTIFIER_FIRST_CHARS, SimpleExpressionToken),
+        ('\'\"',                 StringLiteralToken),
+        ('`',                    ReprToken),
+        (':',                    InPlaceToken),
+        ('[',                    ControlToken),
+        ('{',                    StatementToken),
+        ('<',                    CustomToken),
+    ]
+
+    def __init__(self, prefix, data=''):
+        self.prefix = prefix
+        self.pointer = 0
+        self.buffer = data
+        self.lock = 0
+
+    def __nonzero__(self): return self.pointer < len(self.buffer)
+    def __len__(self): return len(self.buffer) - self.pointer
+    def __getitem__(self, index): return self.buffer[self.pointer + index]
+
+    def __getslice__(self, start, stop):
+        if stop > len(self):
+            stop = len(self)
+        return self.buffer[self.pointer + start:self.pointer + stop]
+
+    def advance(self, count=1):
+        """Advance the pointer count characters."""
+        self.pointer = self.pointer + count
+
+    def retreat(self, count=1):
+        self.pointer = self.pointer - count
+        if self.pointer < 0:
+            raise ParseError, "can't retreat back over synced out chars"
+
+    def set(self, data):
+        """Start the scanner digesting a new batch of data; start the pointer
+        over from scratch."""
+        self.pointer = 0
+        self.buffer = data
+
+    def feed(self, data):
+        """Feed some more data to the scanner."""
+        self.buffer = self.buffer + data
+
+    def chop(self, count=None, slop=0):
+        """Chop the first count + slop characters off the front, and return
+        the first count.  If count is not specified, then return
+        everything."""
+        if count is None:
+            assert slop == 0
+            count = len(self)
+        if count > len(self):
+            raise TransientParseError, "not enough data to read"
+        result = self[:count]
+        self.advance(count + slop)
+        return result
+
+    def acquire(self):
+        """Lock the scanner so it doesn't destroy data on sync."""
+        self.lock = self.lock + 1
+
+    def release(self):
+        """Unlock the scanner."""
+        self.lock = self.lock - 1
+
+    def sync(self):
+        """Sync up the buffer with the read head."""
+        if self.lock == 0 and self.pointer != 0:
+            self.buffer = self.buffer[self.pointer:]
+            self.pointer = 0
+
+    def unsync(self):
+        """Undo changes; reset the read head."""
+        if self.pointer != 0:
+            self.lock = 0
+            self.pointer = 0
+
+    def rest(self):
+        """Get the remainder of the buffer."""
+        return self[:]
+
+    def read(self, i=0, count=1):
+        """Read count chars starting from i; raise a transient error if
+        there aren't enough characters remaining."""
+        if len(self) < i + count:
+            raise TransientParseError, "need more data to read"
+        else:
+            return self[i:i + count]
+
+    def check(self, i, archetype=None):
+        """Scan for the next single or triple quote, with the specified
+        archetype.  Return the found quote or None."""
+        quote = None
+        if self[i] in '\'\"':
+            quote = self[i]
+            if len(self) - i < 3:
+                for j in range(i, len(self)):
+                    if self[i] == quote:
+                        return quote
+                else:
+                    raise TransientParseError, "need to scan for rest of quote"
+            if self[i + 1] == self[i + 2] == quote:
+                quote = quote * 3
+        if quote is not None:
+            if archetype is None:
+                return quote
+            else:
+                if archetype == quote:
+                    return quote
+                elif len(archetype) < len(quote) and archetype[0] == quote[0]:
+                    return archetype
+                else:
+                    return None
+        else:
+            return None
+
+    def find(self, sub, start=0, end=None):
+        """Find the next occurrence of the character, or return -1."""
+        if end is not None:
+            return string.find(self.rest(), sub, start, end)
+        else:
+            return string.find(self.rest(), sub, start)
+
+    def last(self, char, start=0, end=None):
+        """Find the first character that is _not_ the specified character."""
+        if end is None:
+            end = len(self)
+        i = start
+        while i < end:
+            if self[i] != char:
+                return i
+            i = i + 1
+        else:
+            raise TransientParseError, "expecting other than %s" % char
+
+    def next(self, target, start=0, end=None, mandatory=False):
+        """Scan for the next occurrence of one of the characters in
+        the target string; optionally, make the scan mandatory."""
+        if mandatory:
+            assert end is not None
+        quote = None
+        if end is None:
+            end = len(self)
+        i = start
+        while i < end:
+            newQuote = self.check(i, quote)
+            if newQuote:
+                if newQuote == quote:
+                    quote = None
+                else:
+                    quote = newQuote
+                i = i + len(newQuote)
+            else:
+                c = self[i]
+                if quote:
+                    if c == '\\':
+                        i = i + 1
+                else:
+                    if c in target:
+                        return i
+                i = i + 1
+        else:
+            if mandatory:
+                raise ParseError, "expecting %s, not found" % target
+            else:
+                raise TransientParseError, "expecting ending character"
+
+    def quote(self, start=0, end=None, mandatory=False):
+        """Scan for the end of the next quote."""
+        assert self[start] in '\'\"'
+        quote = self.check(start)
+        if end is None:
+            end = len(self)
+        i = start + len(quote)
+        while i < end:
+            newQuote = self.check(i, quote)
+            if newQuote:
+                i = i + len(newQuote)
+                if newQuote == quote:
+                    return i
+            else:
+                c = self[i]
+                if c == '\\':
+                    i = i + 1
+                i = i + 1
+        else:
+            if mandatory:
+                raise ParseError, "expecting end of string literal"
+            else:
+                raise TransientParseError, "expecting end of string literal"
+
+    def nested(self, enter, exit, start=0, end=None):
+        """Scan from i for an ending sequence, respecting entries and exits
+        only."""
+        depth = 0
+        if end is None:
+            end = len(self)
+        i = start
+        while i < end:
+            c = self[i]
+            if c == enter:
+                depth = depth + 1
+            elif c == exit:
+                depth = depth - 1
+                if depth < 0:
+                    return i
+            i = i + 1
+        else:
+            raise TransientParseError, "expecting end of complex expression"
+
+    def complex(self, enter, exit, start=0, end=None, skip=None):
+        """Scan from i for an ending sequence, respecting quotes,
+        entries and exits."""
+        quote = None
+        depth = 0
+        if end is None:
+            end = len(self)
+        last = None
+        i = start
+        while i < end:
+            newQuote = self.check(i, quote)
+            if newQuote:
+                if newQuote == quote:
+                    quote = None
+                else:
+                    quote = newQuote
+                i = i + len(newQuote)
+            else:
+                c = self[i]
+                if quote:
+                    if c == '\\':
+                        i = i + 1
+                else:
+                    if skip is None or last != skip:
+                        if c == enter:
+                            depth = depth + 1
+                        elif c == exit:
+                            depth = depth - 1
+                            if depth < 0:
+                                return i
+                last = c
+                i = i + 1
+        else:
+            raise TransientParseError, "expecting end of complex expression"
+
+    def word(self, start=0):
+        """Scan from i for a simple word."""
+        length = len(self)
+        i = start
+        while i < length:
+            if not self[i] in IDENTIFIER_CHARS:
+                return i
+            i = i + 1
+        else:
+            raise TransientParseError, "expecting end of word"
+
+    def phrase(self, start=0):
+        """Scan from i for a phrase (e.g., 'word', 'f(a, b, c)', 'a[i]', or
+        combinations like 'x[i](a)'."""
+        # Find the word.
+        i = self.word(start)
+        while i < len(self) and self[i] in '([{':
+            enter = self[i]
+            if enter == '{':
+                raise ParseError, "curly braces can't open simple expressions"
+            exit = ENDING_CHARS[enter]
+            i = self.complex(enter, exit, i + 1) + 1
+        return i
+    
+    def simple(self, start=0):
+        """Scan from i for a simple expression, which consists of one 
+        more phrases separated by dots."""
+        i = self.phrase(start)
+        length = len(self)
+        while i < length and self[i] == '.':
+            i = self.phrase(i)
+        # Make sure we don't end with a trailing dot.
+        while i > 0 and self[i - 1] == '.':
+            i = i - 1
+        return i
+
+    def one(self):
+        """Parse and return one token, or None if the scanner is empty."""
+        if not self:
+            return None
+        if not self.prefix:
+            loc = -1
+        else:
+            loc = self.find(self.prefix)
+        if loc < 0:
+            # If there's no prefix in the buffer, then set the location to
+            # the end so the whole thing gets processed.
+            loc = len(self)
+        if loc == 0:
+            # If there's a prefix at the beginning of the buffer, process
+            # an expansion.
+            prefix = self.chop(1)
+            assert prefix == self.prefix
+            first = self.chop(1)
+            if first == self.prefix:
+                first = None
+            for firsts, factory in self.TOKEN_MAP:
+                if firsts is None:
+                    if first is None:
+                        break
+                elif first in firsts:
+                    break
+            else:
+                raise ParseError, "unknown markup: %s%s" % (self.prefix, first)
+            token = factory(self.prefix, first)
+            try:
+                token.scan(self)
+            except TransientParseError:
+                # If a transient parse error occurs, reset the buffer pointer
+                # so we can (conceivably) try again later.
+                self.unsync()
+                raise
+        else:
+            # Process everything up to loc as a null token.
+            data = self.chop(loc)
+            token = NullToken(data)
+        self.sync()
+        return token
+
+
+class Interpreter:
+    
+    """An interpreter can process chunks of EmPy code."""
+
+    # Constants.
+
+    VERSION = __version__
+    SIGNIFICATOR_RE_SUFFIX = SIGNIFICATOR_RE_SUFFIX
+    SIGNIFICATOR_RE_STRING = None
+
+    # Types.
+
+    Interpreter = None # define this below to prevent a circular reference
+    Hook = Hook # DEPRECATED
+    Filter = Filter # DEPRECATED
+    NullFilter = NullFilter # DEPRECATED
+    FunctionFilter = FunctionFilter # DEPRECATED
+    StringFilter = StringFilter # DEPRECATED
+    BufferedFilter = BufferedFilter # DEPRECATED
+    SizeBufferedFilter = SizeBufferedFilter # DEPRECATED
+    LineBufferedFilter = LineBufferedFilter # DEPRECATED
+    MaximallyBufferedFilter = MaximallyBufferedFilter # DEPRECATED
+
+    # Tables.
+
+    ESCAPE_CODES = {0x00: '0', 0x07: 'a', 0x08: 'b', 0x1b: 'e', 0x0c: 'f', \
+                    0x7f: 'h', 0x0a: 'n', 0x0d: 'r', 0x09: 't', 0x0b: 'v', \
+                    0x04: 'z'}
+
+    ASSIGN_TOKEN_RE = re.compile(r"[_a-zA-Z][_a-zA-Z0-9]*|\(|\)|,")
+
+    DEFAULT_OPTIONS = {BANGPATH_OPT: True,
+                       BUFFERED_OPT: False,
+                       RAW_OPT: False,
+                       EXIT_OPT: True,
+                       FLATTEN_OPT: False,
+                       OVERRIDE_OPT: True,
+                       CALLBACK_OPT: False}
+
+    _wasProxyInstalled = False # was a proxy installed?
+
+    # Construction, initialization, destruction.
+
+    def __init__(self, output=None, argv=None, prefix=DEFAULT_PREFIX, \
+                 pseudo=None, options=None, globals=None, hooks=None):
+        self.interpreter = self # DEPRECATED
+        # Set up the stream.
+        if output is None:
+            output = UncloseableFile(sys.__stdout__)
+        self.output = output
+        self.prefix = prefix
+        if pseudo is None:
+            pseudo = DEFAULT_PSEUDOMODULE_NAME
+        self.pseudo = pseudo
+        if argv is None:
+            argv = [DEFAULT_SCRIPT_NAME]
+        self.argv = argv
+        self.args = argv[1:]
+        if options is None:
+            options = {}
+        self.options = options
+        # Initialize any hooks.
+        self.hooksEnabled = None # special sentinel meaning "false until added"
+        self.hooks = []
+        if hooks is None:
+            hooks = []
+        for hook in hooks:
+            self.register(hook)
+        # Initialize callback.
+        self.callback = None
+        # Finalizers.
+        self.finals = []
+        # The interpreter stacks.
+        self.contexts = Stack()
+        self.streams = Stack()
+        # Now set up the globals.
+        self.globals = globals
+        self.fix()
+        self.history = Stack()
+        # Install a proxy stdout if one hasn't been already.
+        self.installProxy()
+        # Finally, reset the state of all the stacks.
+        self.reset()
+        # Okay, now flatten the namespaces if that option has been set.
+        if self.options.get(FLATTEN_OPT, False):
+            self.flatten()
+        # Set up old pseudomodule attributes.
+        if prefix is None:
+            self.SIGNIFICATOR_RE_STRING = None
+        else:
+            self.SIGNIFICATOR_RE_STRING = prefix + self.SIGNIFICATOR_RE_SUFFIX
+        self.Interpreter = self.__class__
+        # Done.  Now declare that we've started up.
+        self.invoke('atStartup')
+
+    def __del__(self):
+        self.shutdown()
+
+    def __repr__(self):
+        return '<%s pseudomodule/interpreter at 0x%x>' % \
+               (self.pseudo, id(self))
+
+    def ready(self):
+        """Declare the interpreter ready for normal operations."""
+        self.invoke('atReady')
+
+    def fix(self):
+        """Reset the globals, stamping in the pseudomodule."""
+        if self.globals is None:
+            self.globals = {}
+        # Make sure that there is no collision between two interpreters'
+        # globals.
+        if self.globals.has_key(self.pseudo):
+            if self.globals[self.pseudo] is not self:
+                raise Error, "interpreter globals collision"
+        self.globals[self.pseudo] = self
+
+    def unfix(self):
+        """Remove the pseudomodule (if present) from the globals."""
+        UNWANTED_KEYS = [self.pseudo, '__builtins__']
+        for unwantedKey in UNWANTED_KEYS:
+            if self.globals.has_key(unwantedKey):
+                del self.globals[unwantedKey]
+
+    def update(self, other):
+        """Update the current globals dictionary with another dictionary."""
+        self.globals.update(other)
+        self.fix()
+
+    def clear(self):
+        """Clear out the globals dictionary with a brand new one."""
+        self.globals = {}
+        self.fix()
+
+    def save(self, deep=True):
+        if deep:
+            copyMethod = copy.deepcopy
+        else:
+            copyMethod = copy.copy
+        """Save a copy of the current globals on the history stack."""
+        self.unfix()
+        self.history.push(copyMethod(self.globals))
+        self.fix()
+
+    def restore(self, destructive=True):
+        """Restore the topmost historic globals."""
+        if destructive:
+            fetchMethod = self.history.pop
+        else:
+            fetchMethod = self.history.top
+        self.unfix()
+        self.globals = fetchMethod()
+        self.fix()
+
+    def shutdown(self):
+        """Declare this interpreting session over; close the stream file
+        object.  This method is idempotent."""
+        if self.streams is not None:
+            try:
+                self.finalize()
+                self.invoke('atShutdown')
+                while self.streams:
+                    stream = self.streams.pop()
+                    stream.close()
+            finally:
+                self.streams = None
+
+    def ok(self):
+        """Is the interpreter still active?"""
+        return self.streams is not None
+
+    # Writeable file-like methods.
+
+    def write(self, data):
+        self.stream().write(data)
+
+    def writelines(self, stuff):
+        self.stream().writelines(stuff)
+
+    def flush(self):
+        self.stream().flush()
+
+    def close(self):
+        self.shutdown()
+
+    # Stack-related activity.
+
+    def context(self):
+        return self.contexts.top()
+
+    def stream(self):
+        return self.streams.top()
+
+    def reset(self):
+        self.contexts.purge()
+        self.streams.purge()
+        self.streams.push(Stream(self.output))
+        if self.options.get(OVERRIDE_OPT, True):
+            sys.stdout.clear(self)
+
+    def push(self):
+        if self.options.get(OVERRIDE_OPT, True):
+            sys.stdout.push(self)
+
+    def pop(self):
+        if self.options.get(OVERRIDE_OPT, True):
+            sys.stdout.pop(self)
+
+    # Higher-level operations.
+
+    def include(self, fileOrFilename, locals=None):
+        """Do an include pass on a file or filename."""
+        if type(fileOrFilename) is types.StringType:
+            # Either it's a string representing a filename ...
+            filename = fileOrFilename
+            name = filename
+            file = theSubsystem.open(filename, 'r')
+        else:
+            # ... or a file object.
+            file = fileOrFilename
+            name = "<%s>" % str(file.__class__)
+        self.invoke('beforeInclude', name=name, file=file, locals=locals)
+        self.file(file, name, locals)
+        self.invoke('afterInclude')
+
+    def expand(self, data, locals=None):
+        """Do an explicit expansion on a subordinate stream."""
+        outFile = StringIO.StringIO()
+        stream = Stream(outFile)
+        self.invoke('beforeExpand', string=data, locals=locals)
+        self.streams.push(stream)
+        try:
+            self.string(data, '<expand>', locals)
+            stream.flush()
+            expansion = outFile.getvalue()
+            self.invoke('afterExpand', result=expansion)
+            return expansion
+        finally:
+            self.streams.pop()
+
+    def quote(self, data):
+        """Quote the given string so that if it were expanded it would
+        evaluate to the original."""
+        self.invoke('beforeQuote', string=data)
+        scanner = Scanner(self.prefix, data)
+        result = []
+        i = 0
+        try:
+            j = scanner.next(self.prefix, i)
+            result.append(data[i:j])
+            result.append(self.prefix * 2)
+            i = j + 1
+        except TransientParseError:
+            pass
+        result.append(data[i:])
+        result = string.join(result, '')
+        self.invoke('afterQuote', result=result)
+        return result
+
+    def escape(self, data, more=''):
+        """Escape a string so that nonprintable characters are replaced
+        with compatible EmPy expansions."""
+        self.invoke('beforeEscape', string=data, more=more)
+        result = []
+        for char in data:
+            if char < ' ' or char > '~':
+                charOrd = ord(char)
+                if Interpreter.ESCAPE_CODES.has_key(charOrd):
+                    result.append(self.prefix + '\\' + \
+                                  Interpreter.ESCAPE_CODES[charOrd])
+                else:
+                    result.append(self.prefix + '\\x%02x' % charOrd)
+            elif char in more:
+                result.append(self.prefix + '\\' + char)
+            else:
+                result.append(char)
+        result = string.join(result, '')
+        self.invoke('afterEscape', result=result)
+        return result
+
+    # Processing.
+
+    def wrap(self, callable, args):
+        """Wrap around an application of a callable and handle errors.
+        Return whether no error occurred."""
+        try:
+            apply(callable, args)
+            self.reset()
+            return True
+        except KeyboardInterrupt, e:
+            # Handle keyboard interrupts specially: we should always exit
+            # from these.
+            self.fail(e, True)
+        except Exception, e:
+            # A standard exception (other than a keyboard interrupt).
+            self.fail(e)
+        except:
+            # If we get here, then either it's an exception not derived from
+            # Exception or it's a string exception, so get the error type
+            # from the sys module.
+            e = sys.exc_type
+            self.fail(e)
+        # An error occurred if we leak through to here, so do cleanup.
+        self.reset()
+        return False
+
+    def interact(self):
+        """Perform interaction."""
+        self.invoke('atInteract')
+        done = False
+        while not done:
+            result = self.wrap(self.file, (sys.stdin, '<interact>'))
+            if self.options.get(EXIT_OPT, True):
+                done = True
+            else:
+                if result:
+                    done = True
+                else:
+                    self.reset()
+
+    def fail(self, error, fatal=False):
+        """Handle an actual error that occurred."""
+        if self.options.get(BUFFERED_OPT, False):
+            try:
+                self.output.abort()
+            except AttributeError:
+                # If the output file object doesn't have an abort method,
+                # something got mismatched, but it's too late to do
+                # anything about it now anyway, so just ignore it.
+                pass
+        meta = self.meta(error)
+        self.handle(meta)
+        if self.options.get(RAW_OPT, False):
+            raise
+        if fatal or self.options.get(EXIT_OPT, True):
+            sys.exit(FAILURE_CODE)
+
+    def file(self, file, name='<file>', locals=None):
+        """Parse the entire contents of a file-like object, line by line."""
+        context = Context(name)
+        self.contexts.push(context)
+        self.invoke('beforeFile', name=name, file=file, locals=locals)
+        scanner = Scanner(self.prefix)
+        first = True
+        done = False
+        while not done:
+            self.context().bump()
+            line = file.readline()
+            if first:
+                if self.options.get(BANGPATH_OPT, True) and self.prefix:
+                    # Replace a bangpath at the beginning of the first line
+                    # with an EmPy comment.
+                    if string.find(line, BANGPATH) == 0:
+                        line = self.prefix + '#' + line[2:]
+                first = False
+            if line:
+                scanner.feed(line)
+            else:
+                done = True
+            self.safe(scanner, done, locals)
+        self.invoke('afterFile')
+        self.contexts.pop()
+
+    def binary(self, file, name='<binary>', chunkSize=0, locals=None):
+        """Parse the entire contents of a file-like object, in chunks."""
+        if chunkSize <= 0:
+            chunkSize = DEFAULT_CHUNK_SIZE
+        context = Context(name, units='bytes')
+        self.contexts.push(context)
+        self.invoke('beforeBinary', name=name, file=file, \
+                    chunkSize=chunkSize, locals=locals)
+        scanner = Scanner(self.prefix)
+        done = False
+        while not done:
+            chunk = file.read(chunkSize)
+            if chunk:
+                scanner.feed(chunk)
+            else:
+                done = True
+            self.safe(scanner, done, locals)
+            self.context().bump(len(chunk))
+        self.invoke('afterBinary')
+        self.contexts.pop()
+
+    def string(self, data, name='<string>', locals=None):
+        """Parse a string."""
+        context = Context(name)
+        self.contexts.push(context)
+        self.invoke('beforeString', name=name, string=data, locals=locals)
+        context.bump()
+        scanner = Scanner(self.prefix, data)
+        self.safe(scanner, True, locals)
+        self.invoke('afterString')
+        self.contexts.pop()
+
+    def safe(self, scanner, final=False, locals=None):
+        """Do a protected parse.  Catch transient parse errors; if
+        final is true, then make a final pass with a terminator,
+        otherwise ignore the transient parse error (more data is
+        pending)."""
+        try:
+            self.parse(scanner, locals)
+        except TransientParseError:
+            if final:
+                # If the buffer doesn't end with a newline, try tacking on
+                # a dummy terminator.
+                buffer = scanner.rest()
+                if buffer and buffer[-1] != '\n':
+                    scanner.feed(self.prefix + '\n')
+                # A TransientParseError thrown from here is a real parse
+                # error.
+                self.parse(scanner, locals)
+
+    def parse(self, scanner, locals=None):
+        """Parse and run as much from this scanner as possible."""
+        self.invoke('atParse', scanner=scanner, locals=locals)
+        while True:
+            token = scanner.one()
+            if token is None:
+                break
+            self.invoke('atToken', token=token)
+            token.run(self, locals)
+
+    # Medium-level evaluation and execution.
+
+    def tokenize(self, name):
+        """Take an lvalue string and return a name or a (possibly recursive)
+        list of names."""
+        result = []
+        stack = [result]
+        for garbage in self.ASSIGN_TOKEN_RE.split(name):
+            garbage = string.strip(garbage)
+            if garbage:
+                raise ParseError, "unexpected assignment token: '%s'" % garbage
+        tokens = self.ASSIGN_TOKEN_RE.findall(name)
+        # While processing, put a None token at the start of any list in which
+        # commas actually appear.
+        for token in tokens:
+            if token == '(':
+                stack.append([])
+            elif token == ')':
+                top = stack.pop()
+                if len(top) == 1:
+                    top = top[0] # no None token means that it's not a 1-tuple
+                elif top[0] is None:
+                    del top[0] # remove the None token for real tuples
+                stack[-1].append(top)
+            elif token == ',':
+                if len(stack[-1]) == 1:
+                    stack[-1].insert(0, None)
+            else:
+                stack[-1].append(token)
+        # If it's a 1-tuple at the top level, turn it into a real subsequence.
+        if result and result[0] is None:
+            result = [result[1:]]
+        if len(result) == 1:
+            return result[0]
+        else:
+            return result
+
+    def significate(self, key, value=None, locals=None):
+        """Declare a significator."""
+        self.invoke('beforeSignificate', key=key, value=value, locals=locals)
+        name = '__%s__' % key
+        self.atomic(name, value, locals)
+        self.invoke('afterSignificate')
+
+    def atomic(self, name, value, locals=None):
+        """Do an atomic assignment."""
+        self.invoke('beforeAtomic', name=name, value=value, locals=locals)
+        if locals is None:
+            self.globals[name] = value
+        else:
+            locals[name] = value
+        self.invoke('afterAtomic')
+
+    def multi(self, names, values, locals=None):
+        """Do a (potentially recursive) assignment."""
+        self.invoke('beforeMulti', names=names, values=values, locals=locals)
+        # No zip in 1.5, so we have to do it manually.
+        i = 0
+        try:
+            values = tuple(values)
+        except TypeError:
+            raise TypeError, "unpack non-sequence"
+        if len(names) != len(values):
+            raise ValueError, "unpack tuple of wrong size"
+        for i in range(len(names)):
+            name = names[i]
+            if type(name) is types.StringType:
+                self.atomic(name, values[i], locals)
+            else:
+                self.multi(name, values[i], locals)
+        self.invoke('afterMulti')
+
+    def assign(self, name, value, locals=None):
+        """Do a potentially complex (including tuple unpacking) assignment."""
+        left = self.tokenize(name)
+        # The return value of tokenize can either be a string or a list of
+        # (lists of) strings.
+        if type(left) is types.StringType:
+            self.atomic(left, value, locals)
+        else:
+            self.multi(left, value, locals)
+
+    def import_(self, name, locals=None):
+        """Do an import."""
+        self.invoke('beforeImport', name=name, locals=locals)
+        self.execute('import %s' % name, locals)
+        self.invoke('afterImport')
+
+    def clause(self, catch, locals=None):
+        """Given the string representation of an except clause, turn it into
+        a 2-tuple consisting of the class name, and either a variable name
+        or None."""
+        self.invoke('beforeClause', catch=catch, locals=locals)
+        if catch is None:
+            exceptionCode, variable = None, None
+        elif string.find(catch, ',') >= 0:
+            exceptionCode, variable = string.split(string.strip(catch), ',', 1)
+            variable = string.strip(variable)
+        else:
+            exceptionCode, variable = string.strip(catch), None
+        if not exceptionCode:
+            exception = Exception
+        else:
+            exception = self.evaluate(exceptionCode, locals)
+        self.invoke('afterClause', exception=exception, variable=variable)
+        return exception, variable
+
+    def serialize(self, expression, locals=None):
+        """Do an expansion, involving evaluating an expression, then
+        converting it to a string and writing that string to the
+        output if the evaluation is not None."""
+        self.invoke('beforeSerialize', expression=expression, locals=locals)
+        result = self.evaluate(expression, locals)
+        if result is not None:
+            self.write(str(result))
+        self.invoke('afterSerialize')
+
+    def defined(self, name, locals=None):
+        """Return a Boolean indicating whether or not the name is
+        defined either in the locals or the globals."""
+        self.invoke('beforeDefined', name=name, local=local)
+        if locals is not None:
+            if locals.has_key(name):
+                result = True
+            else:
+                result = False
+        elif self.globals.has_key(name):
+            result = True
+        else:
+            result = False
+        self.invoke('afterDefined', result=result)
+
+    def literal(self, text):
+        """Process a string literal."""
+        self.invoke('beforeLiteral', text=text)
+        self.serialize(text)
+        self.invoke('afterLiteral')
+
+    # Low-level evaluation and execution.
+
+    def evaluate(self, expression, locals=None):
+        """Evaluate an expression."""
+        if expression in ('1', 'True'): return True
+        if expression in ('0', 'False'): return False
+        self.push()
+        try:
+            self.invoke('beforeEvaluate', \
+                        expression=expression, locals=locals)
+            if locals is not None:
+                result = eval(expression, self.globals, locals)
+            else:
+                result = eval(expression, self.globals)
+            self.invoke('afterEvaluate', result=result)
+            return result
+        finally:
+            self.pop()
+
+    def execute(self, statements, locals=None):
+        """Execute a statement."""
+        # If there are any carriage returns (as opposed to linefeeds/newlines)
+        # in the statements code, then remove them.  Even on DOS/Windows
+        # platforms, 
+        if string.find(statements, '\r') >= 0:
+            statements = string.replace(statements, '\r', '')
+        # If there are no newlines in the statements code, then strip any
+        # leading or trailing whitespace.
+        if string.find(statements, '\n') < 0:
+            statements = string.strip(statements)
+        self.push()
+        try:
+            self.invoke('beforeExecute', \
+                        statements=statements, locals=locals)
+            if locals is not None:
+                exec statements in self.globals, locals
+            else:
+                exec statements in self.globals
+            self.invoke('afterExecute')
+        finally:
+            self.pop()
+
+    def single(self, source, locals=None):
+        """Execute an expression or statement, just as if it were
+        entered into the Python interactive interpreter."""
+        self.push()
+        try:
+            self.invoke('beforeSingle', \
+                        source=source, locals=locals)
+            code = compile(source, '<single>', 'single')
+            if locals is not None:
+                exec code in self.globals, locals
+            else:
+                exec code in self.globals
+            self.invoke('afterSingle')
+        finally:
+            self.pop()
+
+    # Hooks.
+
+    def register(self, hook, prepend=False):
+        """Register the provided hook."""
+        hook.register(self)
+        if self.hooksEnabled is None:
+            # A special optimization so that hooks can be effectively
+            # disabled until one is added or they are explicitly turned on.
+            self.hooksEnabled = True
+        if prepend:
+            self.hooks.insert(0, hook)
+        else:
+            self.hooks.append(hook)
+
+    def deregister(self, hook):
+        """Remove an already registered hook."""
+        hook.deregister(self)
+        self.hooks.remove(hook)
+
+    def invoke(self, _name, **keywords):
+        """Invoke the hook(s) associated with the hook name, should they
+        exist."""
+        if self.hooksEnabled:
+            for hook in self.hooks:
+                hook.push()
+                try:
+                    method = getattr(hook, _name)
+                    apply(method, (), keywords)
+                finally:
+                    hook.pop()
+
+    def finalize(self):
+        """Execute any remaining final routines."""
+        self.push()
+        self.invoke('atFinalize')
+        try:
+            # Pop them off one at a time so they get executed in reverse
+            # order and we remove them as they're executed in case something
+            # bad happens.
+            while self.finals:
+                final = self.finals.pop()
+                final()
+        finally:
+            self.pop()
+
+    # Error handling.
+
+    def meta(self, exc=None):
+        """Construct a MetaError for the interpreter's current state."""
+        return MetaError(self.contexts.clone(), exc)
+
+    def handle(self, meta):
+        """Handle a MetaError."""
+        first = True
+        self.invoke('atHandle', meta=meta)
+        for context in meta.contexts:
+            if first:
+                if meta.exc is not None:
+                    desc = "error: %s: %s" % (meta.exc.__class__, meta.exc)
+                else:
+                    desc = "error"
+            else:
+                desc = "from this context"
+            first = False
+            sys.stderr.write('%s: %s\n' % (context, desc))
+
+    def installProxy(self):
+        """Install a proxy if necessary."""
+        # Unfortunately, there's no surefire way to make sure that installing
+        # a sys.stdout proxy is idempotent, what with different interpreters
+        # running from different modules.  The best we can do here is to try
+        # manipulating the proxy's test function ...
+        try:
+            sys.stdout._testProxy()
+        except AttributeError:
+            # ... if the current stdout object doesn't have one, then check
+            # to see if we think _this_ particularly Interpreter class has
+            # installed it before ...
+            if Interpreter._wasProxyInstalled:
+                # ... and if so, we have a proxy problem.
+                raise Error, "interpreter stdout proxy lost"
+            else:
+                # Otherwise, install the proxy and set the flag.
+                sys.stdout = ProxyFile(sys.stdout)
+                Interpreter._wasProxyInstalled = True
+
+    #
+    # Pseudomodule routines.
+    #
+
+    # Identification.
+
+    def identify(self):
+        """Identify the topmost context with a 2-tuple of the name and
+        line number."""
+        return self.context().identify()
+
+    def atExit(self, callable):
+        """Register a function to be called at exit."""
+        self.finals.append(callable)
+
+    # Context manipulation.
+
+    def pushContext(self, name='<unnamed>', line=0):
+        """Create a new context and push it."""
+        self.contexts.push(Context(name, line))
+
+    def popContext(self):
+        """Pop the top context."""
+        self.contexts.pop()
+
+    def setContextName(self, name):
+        """Set the name of the topmost context."""
+        context = self.context()
+        context.name = name
+        
+    def setContextLine(self, line):
+        """Set the name of the topmost context."""
+        context = self.context()
+        context.line = line
+
+    setName = setContextName # DEPRECATED
+    setLine = setContextLine # DEPRECATED
+
+    # Globals manipulation.
+
+    def getGlobals(self):
+        """Retrieve the globals."""
+        return self.globals
+
+    def setGlobals(self, globals):
+        """Set the globals to the specified dictionary."""
+        self.globals = globals
+        self.fix()
+
+    def updateGlobals(self, otherGlobals):
+        """Merge another mapping object into this interpreter's globals."""
+        self.update(otherGlobals)
+
+    def clearGlobals(self):
+        """Clear out the globals with a brand new dictionary."""
+        self.clear()
+
+    def saveGlobals(self, deep=True):
+        """Save a copy of the globals off onto the history stack."""
+        self.save(deep)
+
+    def restoreGlobals(self, destructive=True):
+        """Restore the most recently saved copy of the globals."""
+        self.restore(destructive)
+        
+    # Hook support.
+
+    def areHooksEnabled(self):
+        """Return whether or not hooks are presently enabled."""
+        if self.hooksEnabled is None:
+            return True
+        else:
+            return self.hooksEnabled
+
+    def enableHooks(self):
+        """Enable hooks."""
+        self.hooksEnabled = True
+
+    def disableHooks(self):
+        """Disable hooks."""
+        self.hooksEnabled = False
+
+    def getHooks(self):
+        """Get the current hooks."""
+        return self.hooks[:]
+
+    def clearHooks(self):
+        """Clear all hooks."""
+        self.hooks = []
+
+    def addHook(self, hook, prepend=False):
+        """Add a new hook; optionally insert it rather than appending it."""
+        self.register(hook, prepend)
+
+    def removeHook(self, hook):
+        """Remove a preexisting hook."""
+        self.deregister(hook)
+
+    def invokeHook(self, _name, **keywords):
+        """Manually invoke a hook."""
+        apply(self.invoke, (_name,), keywords)
+
+    # Callbacks.
+
+    def getCallback(self):
+        """Get the callback registered with this interpreter, or None."""
+        return self.callback
+
+    def registerCallback(self, callback):
+        """Register a custom markup callback with this interpreter."""
+        self.callback = callback
+
+    def deregisterCallback(self):
+        """Remove any previously registered callback with this interpreter."""
+        self.callback = None
+
+    def invokeCallback(self, contents):
+        """Invoke the callback."""
+        if self.callback is None:
+            if self.options.get(CALLBACK_OPT, False):
+                raise Error, "custom markup invoked with no defined callback"
+        else:
+            self.callback(contents)
+
+    # Pseudomodule manipulation.
+
+    def flatten(self, keys=None):
+        """Flatten the contents of the pseudo-module into the globals
+        namespace."""
+        if keys is None:
+            keys = self.__dict__.keys() + self.__class__.__dict__.keys()
+        dict = {}
+        for key in keys:
+            # The pseudomodule is really a class instance, so we need to
+            # fumble use getattr instead of simply fumbling through the
+            # instance's __dict__.
+            dict[key] = getattr(self, key)
+        # Stomp everything into the globals namespace.
+        self.globals.update(dict)
+
+    # Prefix.
+
+    def getPrefix(self):
+        """Get the current prefix."""
+        return self.prefix
+
+    def setPrefix(self, prefix):
+        """Set the prefix."""
+        self.prefix = prefix
+
+    # Diversions.
+
+    def stopDiverting(self):
+        """Stop any diverting."""
+        self.stream().revert()
+
+    def createDiversion(self, name):
+        """Create a diversion (but do not divert to it) if it does not
+        already exist."""
+        self.stream().create(name)
+
+    def retrieveDiversion(self, name):
+        """Retrieve the diversion object associated with the name."""
+        return self.stream().retrieve(name)
+
+    def startDiversion(self, name):
+        """Start diverting to the given diversion name."""
+        self.stream().divert(name)
+
+    def playDiversion(self, name):
+        """Play the given diversion and then purge it."""
+        self.stream().undivert(name, True)
+
+    def replayDiversion(self, name):
+        """Replay the diversion without purging it."""
+        self.stream().undivert(name, False)
+
+    def purgeDiversion(self, name):
+        """Eliminate the given diversion."""
+        self.stream().purge(name)
+
+    def playAllDiversions(self):
+        """Play all existing diversions and then purge them."""
+        self.stream().undivertAll(True)
+
+    def replayAllDiversions(self):
+        """Replay all existing diversions without purging them."""
+        self.stream().undivertAll(False)
+
+    def purgeAllDiversions(self):
+        """Purge all existing diversions."""
+        self.stream().purgeAll()
+
+    def getCurrentDiversion(self):
+        """Get the name of the current diversion."""
+        return self.stream().currentDiversion
+
+    def getAllDiversions(self):
+        """Get the names of all existing diversions."""
+        names = self.stream().diversions.keys()
+        names.sort()
+        return names
+    
+    # Filter.
+
+    def resetFilter(self):
+        """Reset the filter so that it does no filtering."""
+        self.stream().install(None)
+
+    def nullFilter(self):
+        """Install a filter that will consume all text."""
+        self.stream().install(0)
+
+    def getFilter(self):
+        """Get the current filter."""
+        filter = self.stream().filter
+        if filter is self.stream().file:
+            return None
+        else:
+            return filter
+
+    def setFilter(self, shortcut):
+        """Set the filter."""
+        self.stream().install(shortcut)
+
+    def attachFilter(self, shortcut):
+        """Attach a single filter to the end of the current filter chain."""
+        self.stream().attach(shortcut)
+
+
+class Document:
+
+    """A representation of an individual EmPy document, as used by a
+    processor."""
+
+    def __init__(self, ID, filename):
+        self.ID = ID
+        self.filename = filename
+        self.significators = {}
+
+
+class Processor:
+
+    """An entity which is capable of processing a hierarchy of EmPy
+    files and building a dictionary of document objects associated
+    with them describing their significator contents."""
+
+    DEFAULT_EMPY_EXTENSIONS = ('.em',)
+    SIGNIFICATOR_RE = re.compile(SIGNIFICATOR_RE_STRING)
+
+    def __init__(self, factory=Document):
+        self.factory = factory
+        self.documents = {}
+
+    def identifier(self, pathname, filename): return filename
+
+    def clear(self):
+        self.documents = {}
+
+    def scan(self, basename, extensions=DEFAULT_EMPY_EXTENSIONS):
+        if type(extensions) is types.StringType:
+            extensions = (extensions,)
+        def _noCriteria(x):
+            return True
+        def _extensionsCriteria(pathname, extensions=extensions):
+            if extensions:
+                for extension in extensions:
+                    if pathname[-len(extension):] == extension:
+                        return True
+                return False
+            else:
+                return True
+        self.directory(basename, _noCriteria, _extensionsCriteria, None)
+        self.postprocess()
+
+    def postprocess(self):
+        pass
+
+    def directory(self, basename, dirCriteria, fileCriteria, depth=None):
+        if depth is not None:
+            if depth <= 0:
+                return
+            else:
+                depth = depth - 1
+        filenames = os.listdir(basename)
+        for filename in filenames:
+            pathname = os.path.join(basename, filename)
+            if os.path.isdir(pathname):
+                if dirCriteria(pathname):
+                    self.directory(pathname, dirCriteria, fileCriteria, depth)
+            elif os.path.isfile(pathname):
+                if fileCriteria(pathname):
+                    documentID = self.identifier(pathname, filename)
+                    document = self.factory(documentID, pathname)
+                    self.file(document, open(pathname))
+                    self.documents[documentID] = document
+
+    def file(self, document, file):
+        while True:
+            line = file.readline()
+            if not line:
+                break
+            self.line(document, line)
+
+    def line(self, document, line):
+        match = self.SIGNIFICATOR_RE.search(line)
+        if match:
+            key, valueS = match.groups()
+            valueS = string.strip(valueS)
+            if valueS:
+                value = eval(valueS)
+            else:
+                value = None
+            document.significators[key] = value
+
+
+def expand(_data, _globals=None, \
+           _argv=None, _prefix=DEFAULT_PREFIX, _pseudo=None, _options=None, \
+           **_locals):
+    """Do an atomic expansion of the given source data, creating and
+    shutting down an interpreter dedicated to the task.  The sys.stdout
+    object is saved off and then replaced before this function
+    returns."""
+    if len(_locals) == 0:
+        # If there were no keyword arguments specified, don't use a locals
+        # dictionary at all.
+        _locals = None
+    output = NullFile()
+    interpreter = Interpreter(output, argv=_argv, prefix=_prefix, \
+                              pseudo=_pseudo, options=_options, \
+                              globals=_globals)
+    if interpreter.options.get(OVERRIDE_OPT, True):
+        oldStdout = sys.stdout
+    try:
+        result = interpreter.expand(_data, _locals)
+    finally:
+        interpreter.shutdown()
+        if _globals is not None:
+            interpreter.unfix() # remove pseudomodule to prevent clashes
+        if interpreter.options.get(OVERRIDE_OPT, True):
+            sys.stdout = oldStdout
+    return result
+
+def environment(name, default=None):
+    """Get data from the current environment.  If the default is True
+    or False, then presume that we're only interested in the existence
+    or non-existence of the environment variable."""
+    if os.environ.has_key(name):
+        # Do the True/False test by value for future compatibility.
+        if default == False or default == True:
+            return True
+        else:
+            return os.environ[name]
+    else:
+        return default
+
+def info(table):
+    DEFAULT_LEFT = 28
+    maxLeft = 0
+    maxRight = 0
+    for left, right in table:
+        if len(left) > maxLeft:
+            maxLeft = len(left)
+        if len(right) > maxRight:
+            maxRight = len(right)
+    FORMAT = '  %%-%ds  %%s\n' % max(maxLeft, DEFAULT_LEFT)
+    for left, right in table:
+        if right.find('\n') >= 0:
+            for right in right.split('\n'):
+                sys.stderr.write(FORMAT % (left, right))
+                left = ''
+        else:
+            sys.stderr.write(FORMAT % (left, right))
+
+def usage(verbose=True):
+    """Print usage information."""
+    programName = sys.argv[0]
+    def warn(line=''):
+        sys.stderr.write("%s\n" % line)
+    warn("""\
+Usage: %s [options] [<filename, or '-' for stdin> [<argument>...]]
+Welcome to EmPy version %s.""" % (programName, __version__))
+    warn()
+    warn("Valid options:")
+    info(OPTION_INFO)
+    if verbose:
+        warn()
+        warn("The following markups are supported:")
+        info(MARKUP_INFO)
+        warn()
+        warn("Valid escape sequences are:")
+        info(ESCAPE_INFO)
+        warn()
+        warn("The %s pseudomodule contains the following attributes:" % \
+             DEFAULT_PSEUDOMODULE_NAME)
+        info(PSEUDOMODULE_INFO)
+        warn()
+        warn("The following environment variables are recognized:")
+        info(ENVIRONMENT_INFO)
+        warn()
+        warn(USAGE_NOTES)
+    else:
+        warn()
+        warn("Type %s -H for more extensive help." % programName)
+
+def invoke(args):
+    """Run a standalone instance of an EmPy interpeter."""
+    # Initialize the options.
+    _output = None
+    _options = {BUFFERED_OPT: environment(BUFFERED_ENV, False),
+                RAW_OPT: environment(RAW_ENV, False),
+                EXIT_OPT: True,
+                FLATTEN_OPT: environment(FLATTEN_ENV, False),
+                OVERRIDE_OPT: not environment(NO_OVERRIDE_ENV, False),
+                CALLBACK_OPT: False}
+    _preprocessing = []
+    _prefix = environment(PREFIX_ENV, DEFAULT_PREFIX)
+    _pseudo = environment(PSEUDO_ENV, None)
+    _interactive = environment(INTERACTIVE_ENV, False)
+    _extraArguments = environment(OPTIONS_ENV)
+    _binary = -1 # negative for not, 0 for default size, positive for size
+    _unicode = environment(UNICODE_ENV, False)
+    _unicodeInputEncoding = environment(INPUT_ENCODING_ENV, None)
+    _unicodeOutputEncoding = environment(OUTPUT_ENCODING_ENV, None)
+    _unicodeInputErrors = environment(INPUT_ERRORS_ENV, None)
+    _unicodeOutputErrors = environment(OUTPUT_ERRORS_ENV, None)
+    _hooks = []
+    _pauseAtEnd = False
+    _relativePath = False
+    if _extraArguments is not None:
+        _extraArguments = string.split(_extraArguments)
+        args = _extraArguments + args
+    # Parse the arguments.
+    pairs, remainder = getopt.getopt(args, 'VhHvkp:m:frino:a:buBP:I:D:E:F:', ['version', 'help', 'extended-help', 'verbose', 'null-hook', 'suppress-errors', 'prefix=', 'no-prefix', 'module=', 'flatten', 'raw-errors', 'interactive', 'no-override-stdout', 'binary', 'chunk-size=', 'output=' 'append=', 'preprocess=', 'import=', 'define=', 'execute=', 'execute-file=', 'buffered-output', 'pause-at-end', 'relative-path', 'no-callback-error', 'no-bangpath-processing', 'unicode', 'unicode-encoding=', 'unicode-input-encoding=', 'unicode-output-encoding=', 'unicode-errors=', 'unicode-input-errors=', 'unicode-output-errors='])
+    for option, argument in pairs:
+        if option in ('-V', '--version'):
+            sys.stderr.write("%s version %s\n" % (__program__, __version__))
+            return
+        elif option in ('-h', '--help'):
+            usage(False)
+            return
+        elif option in ('-H', '--extended-help'):
+            usage(True)
+            return
+        elif option in ('-v', '--verbose'):
+            _hooks.append(VerboseHook())
+        elif option in ('--null-hook',):
+            _hooks.append(Hook())
+        elif option in ('-k', '--suppress-errors'):
+            _options[EXIT_OPT] = False
+            _interactive = True # suppress errors implies interactive mode
+        elif option in ('-m', '--module'):
+            _pseudo = argument
+        elif option in ('-f', '--flatten'):
+            _options[FLATTEN_OPT] = True
+        elif option in ('-p', '--prefix'):
+            _prefix = argument
+        elif option in ('--no-prefix',):
+            _prefix = None
+        elif option in ('-r', '--raw-errors'):
+            _options[RAW_OPT] = True
+        elif option in ('-i', '--interactive'):
+            _interactive = True
+        elif option in ('-n', '--no-override-stdout'):
+            _options[OVERRIDE_OPT] = False
+        elif option in ('-o', '--output'):
+            _output = argument, 'w', _options[BUFFERED_OPT]
+        elif option in ('-a', '--append'):
+            _output = argument, 'a', _options[BUFFERED_OPT]
+        elif option in ('-b', '--buffered-output'):
+            _options[BUFFERED_OPT] = True
+        elif option in ('-B',): # DEPRECATED
+            _options[BUFFERED_OPT] = True
+        elif option in ('--binary',):
+            _binary = 0
+        elif option in ('--chunk-size',):
+            _binary = int(argument)
+        elif option in ('-P', '--preprocess'):
+            _preprocessing.append(('pre', argument))
+        elif option in ('-I', '--import'):
+            for module in string.split(argument, ','):
+                module = string.strip(module)
+                _preprocessing.append(('import', module))
+        elif option in ('-D', '--define'):
+            _preprocessing.append(('define', argument))
+        elif option in ('-E', '--execute'):
+            _preprocessing.append(('exec', argument))
+        elif option in ('-F', '--execute-file'):
+            _preprocessing.append(('file', argument))
+        elif option in ('-u', '--unicode'):
+            _unicode = True
+        elif option in ('--pause-at-end',):
+            _pauseAtEnd = True
+        elif option in ('--relative-path',):
+            _relativePath = True
+        elif option in ('--no-callback-error',):
+            _options[CALLBACK_OPT] = True
+        elif option in ('--no-bangpath-processing',):
+            _options[BANGPATH_OPT] = False
+        elif option in ('--unicode-encoding',):
+            _unicodeInputEncoding = _unicodeOutputEncoding = argument
+        elif option in ('--unicode-input-encoding',):
+            _unicodeInputEncoding = argument
+        elif option in ('--unicode-output-encoding',):
+            _unicodeOutputEncoding = argument
+        elif option in ('--unicode-errors',):
+            _unicodeInputErrors = _unicodeOutputErrors = argument
+        elif option in ('--unicode-input-errors',):
+            _unicodeInputErrors = argument
+        elif option in ('--unicode-output-errors',):
+            _unicodeOutputErrors = argument
+    # Set up the Unicode subsystem if required.
+    if _unicode or \
+       _unicodeInputEncoding or _unicodeOutputEncoding or \
+       _unicodeInputErrors or _unicodeOutputErrors:
+        theSubsystem.initialize(_unicodeInputEncoding, \
+                                _unicodeOutputEncoding, \
+                                _unicodeInputErrors, _unicodeOutputErrors)
+    # Now initialize the output file if something has already been selected.
+    if _output is not None:
+        _output = apply(AbstractFile, _output)
+    # Set up the main filename and the argument.
+    if not remainder:
+        remainder.append('-')
+    filename, arguments = remainder[0], remainder[1:]
+    # Set up the interpreter.
+    if _options[BUFFERED_OPT] and _output is None:
+        raise ValueError, "-b only makes sense with -o or -a arguments"
+    if _prefix == 'None':
+        _prefix = None
+    if _prefix and type(_prefix) is types.StringType and len(_prefix) != 1:
+        raise Error, "prefix must be single-character string"
+    interpreter = Interpreter(output=_output, \
+                              argv=remainder, \
+                              prefix=_prefix, \
+                              pseudo=_pseudo, \
+                              options=_options, \
+                              hooks=_hooks)
+    try:
+        # Execute command-line statements.
+        i = 0
+        for which, thing in _preprocessing:
+            if which == 'pre':
+                command = interpreter.file
+                target = theSubsystem.open(thing, 'r')
+                name = thing
+            elif which == 'define':
+                command = interpreter.string
+                if string.find(thing, '=') >= 0:
+                    target = '%s{%s}' % (_prefix, thing)
+                else:
+                    target = '%s{%s = None}' % (_prefix, thing)
+                name = '<define:%d>' % i
+            elif which == 'exec':
+                command = interpreter.string
+                target = '%s{%s}' % (_prefix, thing)
+                name = '<exec:%d>' % i
+            elif which == 'file':
+                command = interpreter.string
+                name = '<file:%d (%s)>' % (i, thing)
+                target = '%s{execfile("""%s""")}' % (_prefix, thing)
+            elif which == 'import':
+                command = interpreter.string
+                name = '<import:%d>' % i
+                target = '%s{import %s}' % (_prefix, thing)
+            else:
+                assert 0
+            interpreter.wrap(command, (target, name))
+            i = i + 1
+        # Now process the primary file.
+        interpreter.ready()
+        if filename == '-':
+            if not _interactive:
+                name = '<stdin>'
+                path = ''
+                file = sys.stdin
+            else:
+                name, file = None, None
+        else:
+            name = filename
+            file = theSubsystem.open(filename, 'r')
+            path = os.path.split(filename)[0]
+            if _relativePath:
+                sys.path.insert(0, path)
+        if file is not None:
+            if _binary < 0:
+                interpreter.wrap(interpreter.file, (file, name))
+            else:
+                chunkSize = _binary
+                interpreter.wrap(interpreter.binary, (file, name, chunkSize))
+        # If we're supposed to go interactive afterwards, do it.
+        if _interactive:
+            interpreter.interact()
+    finally:
+        interpreter.shutdown()
+    # Finally, if we should pause at the end, do it.
+    if _pauseAtEnd:
+        try:
+            raw_input()
+        except EOFError:
+            pass
+
+def main():
+    invoke(sys.argv[1:])
+
+if __name__ == '__main__': main()
diff --git a/gtk3/theme/gtk-widgets.css.em b/gtk3/theme/gtk-widgets.css.em
new file mode 100644
index 0000000..ca75450
--- /dev/null
+++ b/gtk3/theme/gtk-widgets.css.em
@@ -0,0 +1,556 @@
+${
+import math
+
+def my_floor(num):
+    return int(math.floor(num))
+
+def my_ceil(num):
+    return int(math.ceil(num))
+
+
+# Should we set the line width in the engine to 2.25, and draw non pixel aligned lines?
+# Are these already the correct sizes for the XO?
+
+# These sizes need to be sanity checked ...
+if scaling == "100":
+    xo = True
+    line_width = 2.0        # 2.25px, rounded down
+    thick_line_width = 3.5  # 3.5
+    subcell_size = 15
+    icon_base = 11
+    bullet_size = 9.5
+    font_height = 24
+    default_padding = 6
+    toolbutton_padding = 9
+else: # About 72% of the XO size, adjusted so that eg. toolbuttons work
+    xo = False
+    line_width = 2.0            # 1.62 rounded up
+    thick_line_width = 3.0      # 2.52
+    subcell_size = 11           # 10.8
+    icon_base = 8               # 7.92
+    bullet_size = 6.5           # 6.84
+    # This is a guess on the font size (Sans 10 at 96 DPI)
+    font_height = 17
+    default_padding = 4         # 4.32
+    toolbutton_padding = 6      # 7.68
+
+
+# Radio size used to be:
+#
+# radio_size = my_floor(subcell_size + bullet_size + line_width)
+#
+# But a screenshot shows that the graphic was actually rendered at 26px
+# so the SVG displays at the correct size.
+radio_size = 26
+scale_slider_width = my_floor(2 * subcell_size + line_width)
+thickness = my_ceil(line_width)
+
+icon_small = icon_base * 3
+icon_large = icon_base * 5
+
+}
+
+* {
+    -sugar-focus-line: @white;
+
+    background-color: @button_grey;
+    color: @black;
+
+    border-color: none;
+    border-radius: 0;
+    border-style: none;
+
+    padding: $thickness;
+
+    /* A lot of these will probably need to be changed, but this has to
+       be done when the exact sizes are known */
+    -GtkWidget-interior-focus: 0;
+
+    /* we have to disable focus border for GtkTreeView, see #1261 */
+    -GtkTreeView-interior-focus: 1;
+    -GtkTreeView-focus-line-width: 0;
+
+    -GtkTextView-interior-focus: 1;
+
+    -GtkWidget-focus-line-width: 0;  /* Prevents some drawing glitches */
+    -GtkEntry-focus-line-width: 0;
+    -GtkScale-focus-line-width: 0;
+    -GtkScale-focus-line-width: 0;
+    -GtkWidget-focus-padding: 3;
+    /* 0.05 works good for both the sugar and sugar-xo themes */
+    -GtkWidget-cursor-aspect-ratio: 0.05;
+
+    -GtkWidget-wide-separators: false;
+    -GtkWidget-separator-height: $thickness;
+    -GtkWidget-separator-width: $thickness;
+
+    -GtkWidget-scroll-arrow-hlength: $subcell_size;
+    -GtkWidget-scroll-arrow-vlength: $subcell_size;
+
+    -GtkRange-activate-slider: 1;
+
+    /* We fake the default border in the theme */
+    -GtkButton-default-border: 0 0 0 0;
+    -GtkButton-default-outside-border: 0 0 0 0;
+    -GtkButton-image-spacing: $subcell_size;
+
+    -GtkEntry-progress-border: $thickness $thickness $thickness $thickness;
+
+    -GtkScrolledWindow-scrollbar-spacing: 0;
+
+    -GtkExpander-expander-size: $font_height;
+    -GtkExpander-expander-spacing: 2;
+
+    -GtkTreeView-expander-size: $font_height;
+
+    -GtkArrow-arrow-size: 1.0;
+
+    -GtkToolbar-space-size: $(2*subcell_size);
+
+    -GtkProgressBar-min-horizontal-bar-height: $subcell_size;
+    -GtkProgressBar-min-vertical-bar-width: $subcell_size;
+
+    -GtkButtonBox-child-min-height: $(3*subcell_size);
+    -GtkButtonBox-child-min-width: $(3*subcell_size);
+    -GtkButtonBox-child-internal-pad-x: 0;
+    -GtkButtonBox-child-internal-pad-y: 0;
+
+    -GtkCheckButton-indicator-size: $radio_size;
+    -GtkCheckButton-indicator-spacing: 3;
+}
+
+*:active {
+    background-color: @white;
+    color: @black;
+}
+
+/* Backgrounds and windows */
+
+.background {
+    padding: 0px;
+    border-width: 0px;
+    background-color: @panel_grey;
+}
+
+.window {
+    background-color: @panel_grey;
+    color: @black;
+}
+
+/* Handle this differently? */
+.window *:insensitive {
+    background-color: @panel_grey;
+}
+
+/* Buttons */
+
+.button {
+    ${ border = max(0, my_ceil((3*subcell_size/2.0 - icon_small / 2.0))) }
+    /* It would be nicer to just set the inner-border, but that does not work
+       for combo boxes ... The combobox ignores it, so set it to 0px
+       See http://bugzilla.gnome.org/show_bug.cgi?id=485762 */
+
+    -GtkButton-inner-border: 0 0 0 0;
+    padding: $(border)px $(border)px $(border)px $(border)px;
+
+    border-radius: $(2*subcell_size);
+    background-color: @button_grey;
+    color: @white;
+}
+
+.button * {
+    color: @white;
+}
+
+.button:focused {
+    border-width: $(thickness);
+    border-color: @white;
+    border-style: solid;
+}
+
+/* Spin buttons */
+
+.spinbutton {
+    ${ spin_ythickness = my_ceil(3*subcell_size - font_height) }
+    ${ spin_xthickness = subcell_size }
+    ${ spin_btn_ythickness = spin_ythickness }
+    ${ spin_btn_xthickness = subcell_size * 2 }
+
+    /* small inner border and a lage x/ythickness for entries
+       to reduce the number of hacks needed :-) */
+    # FIXME this is setting the wrong padding
+    /* padding: $(spin_xthickness)px $(spin_ythickness)px; */
+}
+
+.spinbutton.button,
+.spinbutton.button:focused,
+.spinbutton.button:active {
+    padding: $(spin_ythickness)px $(spin_xthickness)px;
+    border-color: @button_grey;
+    border-width: $(thickness)px;
+    background-color: @button_grey;
+    color: @white;
+}
+
+.spinbutton.button:active {
+    background-color: @white;
+    color: @black;
+    border-color: @white;
+}
+
+.spinbutton.button:insensitive {
+    background-color: @selection_grey;
+}
+
+.spinbutton.button:focused,
+.spinbutton.button:focused:prelight,
+.spinbutton.button:focused:insensitive {
+    border-color: @white;
+}
+
+/* Toggle buttons */
+
+GtkToggleButton.button:active {
+    background-color: @white;
+}
+
+GtkToggleButton.button:active GtkLabel {
+    color: @black;
+}
+
+/* Entries and views */
+
+.view {
+    border-width: 0px;
+    border-style: none;
+    border-radius: 0;
+    padding: 0px;
+    background-color: @text_field_grey;
+}
+
+.entry {
+    border-radius: $(2*subcell_size);
+    border-width: $(thickness);
+    border-color: @selection_grey;
+    border-style: solid;
+    background-color: @text_field_grey;
+    color: @black;
+    ${ entry_ythickness = my_ceil(0.2 * (subcell_size*3.0/2.0 - thickness) + thickness) }
+    ${ entry_xthickness = my_ceil(0.32 * (subcell_size*3.0/2.0 - thickness) + thickness) }
+    -GtkEntry-inner-border: $(2 * max(subcell_size - entry_xthickness, 0)), $(2 * max(subcell_size - entry_xthickness, 0)), $(2 * max(my_ceil((3*subcell_size - font_height - entry_ythickness*2)/2.0),0)), $(2 * max(my_floor((3*subcell_size - font_height - entry_ythickness*2)/2.0), 0))
+}
+
+.entry.progressbar {
+    border-radius: $(2 * subcell_size);
+    border-width: $(thickness);
+    background-color: @selection_grey;
+}
+
+.entry:focused {
+    background-color: @white;
+}
+
+.entry:insensitive, .view:insensitive {
+    background-color: @button_grey;
+}
+
+.view:selected {
+    background-color: @panel_grey;
+    color: @black;
+}
+
+.entry:selected, .entry:selected:focused, 
+.view:selected:focused {
+    background-color: @selection_grey;
+    color: @black;
+}
+
+/* Combo boxes */
+
+GtkComboBox * {
+    color: @white;
+}
+
+GtkComboBox .separator {
+    /* Remove the separator turning it transparent */
+    color: alpha(@theme_base_color, 0.0);
+}
+
+.toolbar GtkToggleButton.button:active,
+SugarPaletteWindowWidget GtkToggleButton.button:active {
+    background-color: @button_grey;
+}
+
+/* Notebooks */
+
+.notebook {
+    background-color: @selection_grey;
+    border-width: 0;
+    -GtkNotebook-tab-overlap: -2;
+    -GtkNotebook-tab-curvature: $default_padding;
+}
+
+.notebook tab,
+.notebook tab GtkLabel {
+    background-color: @button_grey;
+    color: @white;
+}
+
+.notebook tab:active {
+    background-color: @selection_grey;
+}
+
+/* Control panel */
+
+SugarSectionView {
+    background-color: @white;
+    color: @black;
+}
+
+SugarSectionView *:insensitive {
+    background-color: @white;
+}
+
+/* Alert */
+
+SugarAlert {
+    background-color: @black;
+    color: @white;
+}
+
+SugarAlert *:insensitive {
+    background-color: @black;
+}
+
+/* Menus and palettes */
+
+SugarPaletteWindowWidget.background {
+    background-color: @black;
+}
+
+SugarPaletteWindowWidget * {
+    color: @white;
+}
+
+SugarPaletteMenuWidget.background {
+    background-color: @black;
+}
+
+SugarPaletteMenuWidget * {
+    color: @white;
+}
+
+SugarPaletteWindow SugarGroupBox {
+    background-color: @toolbar_grey;
+    color: @white;
+}
+
+SugarPaletteWindow SugarGroupBox *:insensitive {
+    background-color: @toolbar_grey;
+}
+
+.menu,
+.palette {
+    background-color: @black;
+    color: @white;
+
+    -GtkMenu-scroll-arrow-vlength: $(my_floor(subcell_size/0.7 + 2*thickness));
+    -GtkMenu-horizontal-padding: $thickness;
+    -GtkMenu-vertical-padding  : $thickness;
+    /* This means the outline of the submenu overlaps with a palette.
+     * However in the case of two normal menus, they are next to each other.
+     * It is not possible to be smarter about this, because the style comes from
+     * the submenu. */
+    -GtkMenu-horizontal-offset : 0;
+    -GtkMenu-vertical-offset   : 0;
+
+    padding: 0;
+    border-width: 2;
+    border-color: @button_grey;
+    border-style: solid;
+}
+
+.menu :prelight, palette :prelight {
+    color: @white
+}
+
+.menu :active, palette :active {
+    background-color: @button_grey;
+}
+
+.palette {
+    padding: $(thickness)px;
+}
+
+.palette .menu {
+    -GtkMenu-horizontal-padding: 0;
+    -GtkMenu-vertical-padding: 0;
+
+    padding: 0px $(subcell_size)px;
+}
+
+.menu * {
+    color: @white;
+}
+
+GtkMenuItem {
+    padding: $subcell_size $((subcell_size * 3 - font_height) / 2);
+}
+
+/* Scrollbars */
+
+.scrollbar {
+    -GtkRange-slider-width: $subcell_size;
+    -GtkRange-trough-border: 0;
+    -GtkRange-stepper-size: 0;
+    -GtkScrollbar-min-slider-length: $(3*subcell_size);
+    -GtkScrollbar-has-forward-stepper: 0;
+    -GtkScrollbar-has-backward-stepper: 0;
+    -GtkScrollbar-has-secondary-forward-stepper: 0;
+    -GtkScrollbar-has-secondary-backward-stepper: 0;
+}
+
+.scrollbar.trough {
+    background-color: @button_grey;
+    border-width: 0;
+}
+
+.scrollbar.slider {
+    background-color: @white;
+    border-radius: $(2*subcell_size);
+    border-width: 0;
+}
+
+.scrollbar.slider:active {
+    background-color: @text_field_grey;
+}
+
+/* Progress bars */
+
+/* Scrollbar padding hack? What was that about? */
+
+GtkProgressBar.progressbar {
+    background-color: @white;
+    border-color: @white;
+    border-radius: 10;
+    border-style: solid;
+    border-width: 0;
+}
+
+GtkProgressBar.trough {
+    background-color: alpha (@black, 0.0);
+    border-style: solid;
+    border-radius: 10;
+    border-color: @button_grey;
+    border-width: 2;
+}
+
+/* Separators */
+
+GtkVSeparator, GtkHSeparator,
+.toolbar GtkSeparatorToolItem {
+    color: @button_grey;
+}
+
+/* Tool buttons */
+
+.toolbar GtkToolButton .button,
+SugarPaletteWindowWidget GtkToolButton .button {
+    border-radius: $toolbutton_padding;
+}
+
+.toolbar GtkToolButton .button:prelight,
+SugarPaletteWindowWidget GtkToolButton .button:prelight {
+    background-color: @black;
+}
+
+.toolbar GtkToolButton .button:active:prelight,
+SugarPaletteWindowWidget GtkToolButton .button:active:prelight {
+    background-color: @button_grey;
+}
+
+/* Scales */
+
+GtkScale {
+    -GtkScale-slider-length: $scale_slider_width;
+    -GtkRange-slider-width: $scale_slider_width;
+}
+
+GtkScale.trough {
+    border-radius: 30;
+}
+
+GtkScale.slider {
+    color: alpha(@theme_base_color, 0.0);
+    background-image: url("assets/scale-slider.svg");
+}
+
+GtkScale.slider:active {
+    color: alpha(@theme_base_color, 0.0);
+    background-image: url("assets/scale-slider-active.svg");
+}
+
+/* Radio and check buttons */
+
+GtkCheckButton:prelight {
+    background-color: alpha(@theme_base_color, 0.0);
+}
+
+.radio,
+.radio row:selected,
+.radio row:selected:focused {
+    background-image: url("assets/radio.svg");
+}
+
+.radio:selected,
+.radio:selected row:selected,
+.radio:selected row:selected:focused {
+    background-image: url("assets/radio-selected.svg");
+}
+
+.radio:active,
+.radio row:selected:active,
+.radio row:selected:focused:active {
+    background-image: url("assets/radio-active.svg");
+}
+
+.radio:active:selected,
+.radio:selected row:selected:active,
+.radio:selected row:selected:focused:active {
+    background-image: url("assets/radio-active-selected.svg");
+}
+
+.check,
+.check row:selected,
+.check row:selected:focused {
+    background-image: url("assets/checkbox-unchecked.svg");
+}
+
+.check:selected,
+.check:selected row:selected,
+.check:selected row:selected:focused {
+    background-image: url("assets/checkbox-unchecked-selected.svg");
+}
+
+.check:active,
+.check row:selected:active,
+.check row:selected:focused:active {
+    background-image: url("assets/checkbox-checked.svg");
+}
+
+.check:active:selected,
+.check:selected row:selected:active,
+.check:selected row:selected:focused:active {
+    background-image: url("assets/checkbox-checked-selected.svg");
+}
+
+/* Tool items */
+
+.toolitem {
+    /* arrow in the toolbarbox */
+    color: @white;
+    /* outline of the toolbarbutton when palette is expanded */
+    border-style: solid;
+    border-width: 2;
+    border-color: @button_grey;
+}
diff --git a/gtk3/theme/gtk.css b/gtk3/theme/gtk.css
new file mode 100644
index 0000000..0c51959
--- /dev/null
+++ b/gtk3/theme/gtk.css
@@ -0,0 +1,121 @@
+
+/* Sugar colors */
+ at define-color black #000000;
+ at define-color toolbar_grey #282828;
+ at define-color button_grey /*#ff0000*/ #808080 ;
+ at define-color selection_grey #A6A6A6;
+ at define-color panel_grey #C0C0C0;
+ at define-color text_field_grey #E5E5E5;
+ at define-color white #FFFFFF;
+
+/* Default color scheme */
+ at define-color base_color #ffffff;
+ at define-color bg_color #ededed;
+ at define-color tooltip_bg_color #343434;
+ at define-color selected_bg_color #4a90d9;
+ at define-color text_color #2e3436;
+ at define-color fg_color #2e3436;
+ at define-color tooltip_fg_color #ffffff;
+ at define-color selected_fg_color #ffffff;
+
+/* Colormap actually used by the theme, to be overridden in other css files */
+ at define-color theme_base_color @base_color;
+ at define-color theme_text_color @text_color;
+ at define-color theme_bg_color @bg_color;
+ at define-color theme_fg_color @fg_color;
+ at define-color theme_tooltip_bg_color @tooltip_bg_color;
+ at define-color theme_tooltip_fg_color @tooltip_fg_color;
+ at define-color theme_selected_bg_color @selected_bg_color;
+ at define-color theme_selected_fg_color @selected_fg_color;
+
+ at define-color menu_bg_color shade (@theme_bg_color, 0.45);
+ at define-color menu_fg_color #ffffff;
+ at define-color menu_controls_color #aaaaaa;
+
+ at define-color link_color #4a90d9;
+ at define-color frame_color #8a9580;
+ at define-color inactive_frame_color #c7ccc1;
+ at define-color warning_color #f57900;
+ at define-color error_color #cc0000;
+ at define-color success_color #4e9a06;
+
+ at define-color info_fg_color rgb (181, 171, 156);
+ at define-color info_bg_color rgb (252, 252, 189);
+ at define-color warning_fg_color rgb (173, 120, 41);
+ at define-color warning_bg_color rgb (250, 173, 61);
+ at define-color question_fg_color rgb (97, 122, 214);
+ at define-color question_bg_color rgb (138, 173, 212);
+ at define-color error_fg_color rgb (166, 38, 38);
+ at define-color error_bg_color rgb (237, 54, 54);
+
+ at define-color keyboard_focus_border_a #a2c9f1;
+ at define-color keyboard_focus_border_b #6794cf;
+
+ at define-color os_chrome_bg_color black;
+ at define-color os_chrome_fg_color #ccc;
+ at define-color os_chrome_selected_bg_color #333;
+ at define-color os_chrome_selected_fg_color white;
+
+ at define-color chrome_bg_color #1e1a17;
+ at define-color chrome_fg_color #fff;
+
+ at define-color focused_entry_border #579eea;
+ at define-color focused_entry_inset alpha (#d7e4f1, 0.50);
+
+ at define-color button_gradient_color_a #f4f6f4;
+ at define-color button_gradient_color_b #d7dad7;
+ at define-color button_border #a7aba7;
+
+ at define-color button_hover_gradient_color_a @theme_base_color;
+ at define-color button_hover_gradient_color_b shade (@button_gradient_color_a, 0.94);
+
+ at define-color button_active_gradient_color_a #a2a9a2;
+ at define-color button_active_gradient_color_b shade (@button_active_gradient_color_a, 0.83);
+
+ at define-color insensitive_bg_color #f4f4f2;
+ at define-color insensitive_fg_color #a7aba7;
+ at define-color insensitive_border_color shade (@internal_element_color, 1.37);
+
+ at define-color trough_bg_color_a #ccccc7;
+ at define-color trough_bg_color_b #e4e4e1;
+
+ at define-color active_switch_bg_color_a #509ae7;
+ at define-color active_switch_bg_color_b #84b8ee;
+
+ at define-color switch_slider_color #eeeeec;
+ at define-color switch_slider_border #2a79cb;
+
+ at define-color progressbar_background_a #76b0ec;
+ at define-color progressbar_background_b #1f72c6;
+ at define-color progressbar_border #3277bf;
+ at define-color progressbar_pattern #000000;
+
+ at define-color internal_element_color #888a85;
+
+ at define-color scale_fill @insensitive_border_color;
+ at define-color scale_border_a @internal_element_color;
+ at define-color scale_border_b shade (@internal_element_color, 1.25);
+ at define-color scale_progress_fill #2c85e2;
+ at define-color scale_progress_border_a #1864b2;
+ at define-color scale_progress_border_b #3e90e5;
+
+ at define-color highlighted_border #8a8f8a;
+
+ at define-color notebook_border #a6a6a6;
+ at define-color notebook_active_tab_border #1372d3;
+ at define-color notebook_selected_tab_color #8dc0f3;
+
+ at define-color notebook_tab_gradient_a @theme_base_color;
+ at define-color notebook_tab_gradient_b @switch_slider_color;
+
+ at define-color toolbar_gradient_base #aaaa9e;
+ at define-color toolbar_gradient_step1 #bcbcb4;
+ at define-color toolbar_gradient_step2 #d9d9d7;
+ at define-color toolbar_gradient_final #e5e5e2;
+
+ at define-color toolbar_active_button_color #909081;
+
+ at define-color expander_row_selected_color #acccee;
+
+ at import url("gtk-widgets.css");
+
diff --git a/gtk3/theme/settings.ini.em b/gtk3/theme/settings.ini.em
new file mode 100644
index 0000000..f4d6369
--- /dev/null
+++ b/gtk3/theme/settings.ini.em
@@ -0,0 +1,36 @@
+${
+
+# These sizes are copied from gtk2 rc files ...
+if scaling == "100":
+    icon_base = 11
+else: # About 72% of the XO size, adjusted so that eg. toolbuttons work
+    icon_base = 8               # 7.92
+
+icon_small = icon_base * 3
+icon_large = icon_base * 5
+
+}
+
+[Settings]
+#gtk-color-scheme = "base_color:#ffffff\nbg_color:#ededed\ntooltip_bg_color:#343434\nselected_bg_color:#4a90d9\ntext_color:#2e3436\nfg_color:#2e3436;\ntooltip_fg_color:#ffffff\nselected_fg_color:#ffffff"
+gtk-auto-mnemonics = 1
+gtk-toolbar-style = GTK_TOOLBAR_ICONS
+
+${
+icon_small = icon_base * 3
+icon_large = icon_base * 5
+
+small_icons = [ "gtk-menu", "gtk-dnd", "gtk-small-toolbar", "gtk-button" ]
+large_icons = [ "gtk-large-toolbar" ]
+
+icon_sizes = []
+for icon in small_icons:
+    icon_sizes += [icon + "=" + str(icon_small) + ',' + str(icon_small)]
+for icon in large_icons:
+    icon_sizes += [icon + "=" + str(icon_large) + ',' + str(icon_large)]
+
+icon_sizes = ":".join(icon_sizes)
+
+}
+gtk-icon-sizes=$icon_sizes
+
-- 
1.7.7.4



More information about the Sugar-devel mailing list