changeset 4:f8612f05af99

Added first stab of an implementation of the !TurboGears [http://www.turbogears.org/docs/plugins/template.html plugin API for template engines], and also a !TurboGears-based example using this plugin. Both written by Matt Good.
author cmlenz
date Sat, 03 Jun 2006 12:31:58 +0000
parents e86dcec16d55
children 1add946decb8
files examples/turbogears/README.txt examples/turbogears/dev.cfg examples/turbogears/markuptest/__init__.py examples/turbogears/markuptest/config/__init__.py examples/turbogears/markuptest/config/app.cfg examples/turbogears/markuptest/config/log.cfg examples/turbogears/markuptest/controllers.py examples/turbogears/markuptest/json.py examples/turbogears/markuptest/model.py examples/turbogears/markuptest/release.py examples/turbogears/markuptest/static/images/favicon.ico examples/turbogears/markuptest/static/images/tg_under_the_hood.png examples/turbogears/markuptest/templates/__init__.py examples/turbogears/markuptest/templates/login.html examples/turbogears/markuptest/templates/master.html examples/turbogears/markuptest/templates/welcome.html examples/turbogears/markuptest/tests/__init__.py examples/turbogears/markuptest/tests/test_controllers.py examples/turbogears/markuptest/tests/test_model.py examples/turbogears/sample-prod.cfg examples/turbogears/setup.py examples/turbogears/start-markuptest.py markup/plugin.py setup.py
diffstat 24 files changed, 633 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/README.txt
@@ -0,0 +1,4 @@
+MarkupTest
+
+This is a TurboGears (http://www.turbogears.org) project. It can be
+started by running the start-markuptest.py script.
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/dev.cfg
@@ -0,0 +1,64 @@
+[global]
+# This is where all of your settings go for your development environment
+# Settings that are the same for both development and production
+# (such as template engine, encodings, etc.) all go in 
+# markuptest/config/app.cfg
+
+# DATABASE
+
+# pick the form for your database
+# sqlobject.dburi="postgres://username@hostname/databasename"
+# sqlobject.dburi="mysql://username:password@hostname:port/databasename"
+# sqlobject.dburi="sqlite:///file_name_and_path"
+
+# If you have sqlite, here's a simple default to get you started
+# in development
+sqlobject.dburi="sqlite://%(current_dir_uri)s/devdata.sqlite"
+
+
+# if you are using a database or table type without transactions
+# (MySQL default, for example), you should turn off transactions
+# by prepending notrans_ on the uri
+# sqlobject.dburi="notrans_mysql://username:password@hostname:port/databasename"
+
+# for Windows users, sqlite URIs look like:
+# sqlobject.dburi="sqlite:///drive_letter:/path/to/file"
+
+
+# SERVER
+
+# Some server parameters that you may want to tweak
+# server.socket_port=8080
+
+# Enable the debug output at the end on pages.
+# log_debug_info_filter.on = False
+
+server.environment="development"
+autoreload.package="markuptest"
+
+# Set to True if you'd like to abort execution if a controller gets an
+# unexpected parameter. False by default
+tg.strict_parameters = True
+
+# LOGGING
+# Logging configuration generally follows the style of the standard
+# Python logging module configuration. Note that when specifying
+# log format messages, you need to use *() for formatting variables.
+# Deployment independent log configuration is in markuptest/config/log.cfg
+[logging]
+
+[[loggers]]
+[[[markuptest]]]
+level='DEBUG'
+qualname='markuptest'
+handlers=['debug_out']
+
+[[[allinfo]]]
+level='INFO'
+handlers=['debug_out']
+
+[[[access]]]
+level='INFO'
+qualname='turbogears.access'
+handlers=['access_out']
+propagate=0
new file mode 100644
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/markuptest/config/app.cfg
@@ -0,0 +1,44 @@
+[global]
+# The settings in this file should not vary depending on the deployment
+# environment. dev.cfg and prod.cfg are the locations for
+# the different deployment settings. Settings in this file will
+# be overridden by settings in those other files.
+
+# The commented out values below are the defaults
+
+# VIEW
+
+# which view (template engine) to use if one is not specified in the
+# template name
+# tg.defaultview = "kid"
+tg.defaultview = "markup"
+markup.search_path = ["%(current_dir_uri)s"]
+markup.outputformat = "xml"
+
+# The following kid settings determine the settings used by the kid serializer.
+
+# One of (html|xml|json)
+# kid.outputformat="html"
+
+# kid.encoding="utf-8"
+
+# The sitetemplate is used for overall styling of a site that
+# includes multiple TurboGears applications
+# tg.sitetemplate="<packagename.templates.templatename>"
+
+# Allow every exposed function to be called as json,
+# tg.allow_json = False
+
+# Set to True if you'd like all of your pages to include MochiKit
+# tg.mochikit_all = False
+
+# Set to True if the scheduler should be started
+# tg.scheduler = False
+
+[/static]
+static_filter.on = True
+static_filter.dir = "%(top_level_dir)s/static"
+
+[/favicon.ico]
+static_filter.on = True
+static_filter.file = "%(top_level_dir)s/static/images/favicon.ico"
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/markuptest/config/log.cfg
@@ -0,0 +1,29 @@
+# LOGGING
+# Logging is often deployment specific, but some handlers and
+# formatters can be defined here.
+
+[logging]
+[[formatters]]
+[[[message_only]]]
+format='*(message)s'
+
+[[[full_content]]]
+format='*(asctime)s *(name)s *(levelname)s *(message)s'
+
+[[handlers]]
+[[[debug_out]]]
+class='StreamHandler'
+level='DEBUG'
+args='(sys.stdout,)'
+formatter='full_content'
+
+[[[access_out]]]
+class='StreamHandler'
+level='INFO'
+args='(sys.stdout,)'
+formatter='message_only'
+
+[[[error_out]]]
+class='StreamHandler'
+level='ERROR'
+args='(sys.stdout,)'
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/markuptest/controllers.py
@@ -0,0 +1,17 @@
+import logging
+
+import cherrypy
+
+import turbogears
+from turbogears import controllers, expose, validate, redirect
+
+from markuptest import json
+
+log = logging.getLogger("markuptest.controllers")
+
+class Root(controllers.RootController):
+    @expose(template="markuptest.templates.welcome")
+    def index(self):
+        import time
+        log.debug("Happy TurboGears Controller Responding For Duty")
+        return dict(now=time.ctime())
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/markuptest/json.py
@@ -0,0 +1,13 @@
+# This module provides helper functions for the JSON part of your
+# view, if you are providing a JSON-based API for your app.
+
+# Here's what most rules would look like:
+# @jsonify.when("isinstance(obj, YourClass)")
+# def jsonify_yourclass(obj):
+#     return [obj.val1, obj.val2]
+#
+# The goal is to break your objects down into simple values:
+# lists, dicts, numbers and strings
+
+from turbojson.jsonify import jsonify
+
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/markuptest/model.py
@@ -0,0 +1,1 @@
+
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/markuptest/release.py
@@ -0,0 +1,13 @@
+# Release information about MarkupTest
+
+version = "1.0"
+
+# description = "Your plan to rule the world"
+# author = "Your Name Here"
+# email = "YourEmail@YourDomain"
+# copyright = "Vintage 2006 - a good year indeed"
+
+# if it's open source, you might want to specify these
+# url = "http://yourcool.site/"
+# download_url = "http://yourcool.site/download"
+# license = "MIT"
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..332557bc307647601389c14939be0671c62efcd7
GIT binary patch
literal 1081
zc$@(_1jhSENk%w1VGsZi0Ow5rU0q$8Llvf`rg3p`iHV7;tE=$v@Z{v=e0+SkxVY1i
zVbjyo_xJa<wzj;CZl9l@FD)+J-QCTjjjXJ!)3l`g{QT+H&GPc{larIe!omUq0>s3`
z%*@QbzP_%muFlTRuXR$eu&|(@pvlR}#>U39w6vL-ncLgj_sv?eva+bCsJgnky}iAU
zFbC)7=g`p5RFNg6++fty)Q5+M+-+j?^z?2p6YHcgZ*Feo?Dc7BY3=j=*}=5d*4FCk
z>e$%WkZD%6mVNm6_)$?&^5)=2M@NQ3Cg0!R!<&WI$-#kvfuvL_*WKs;|Nrdl>}F<W
zU|?W#K`zUCEy(zUpiLrIS68xvQ~L0KtY$%`S~AAV)!X3f;_2^~cW1SLV})BtxxUBm
z;@k83{l24+&C=ZVqLJCp%W$nEK5Q|VY&!Lhf^bkt<jugQs<QX}|J>Ek`SkLhzMN`o
zagv9Jfw4^C=I!{dq?(?k?eOvV@$SCE%$}mDz_6m><mR%rzv=J#-{Rzqjg9Q(;$mfK
z^Y-}Y?C$*k|I^pog~2`A-QSOokLc^{jgW+Hadvllfcy9L&cwdJuSos-`tbDjtFO0p
zb#|ept&WkH?)CZH*wuoCi}?Hf=HJ}X*y2o0O;c4_wyC4p+ueGDL!^du9X1y8^YdI^
zWBvU4`}+Ct^Y-1};1d%RczAgF`1t4V^M#3z<KyG;@$v2L?M_frU0`IhGA?h2ZaO+T
zl9rwI?die6!K0(2QBzmI*0#&b%chcw%1}t}@9*yJ?)&@u%F4>Xz`*|g{`&g*;^N}L
z#?NMGZT|oN|Nj2#@%%wTLjL~$`T6<%{r&p;`}Xzq`~Cj#^77Hr)zs3`*VNL(wyegx
zwdmZ{ok<+e(bVDL;o;)r_4W1k_V)Mo_07%AQ&Us!?(OO6>Few3%*VrGV`a<B%z<-r
z>+9-gXJ=SgSXx?J$iKM${{4J^g{GvV9UL4aBO}Yu&APj}r>CXM&&=!Z@#5p;>gwsi
zzrDi2z?Pb!A^sIZa%Ew3Wn>_CX>@2HRA^-&M@dak04x9i000mG5C8xO{s5aaVb<i0
z86g#fn0Yf$pu=TIf&{^44x2<I@f->~md%O-2S^lHv64-i!($A}fp9Pe9y&;49CUHQ
z;Fz%>4uYhycVZEqMJMv5!5PGul{9Hmczdy=sTptC(pZr~t<X_bM_SNOBc;U=RZ?9E
z0oUaVAapIHDCi2Q&zCMWqEU;orVlzBRIpHKfzHL6DA9}ovGL`{i7_^8v>0<D!WSDr
zniz=^rOUZ%@Umriv8YQFBSs9N8<nOqtUi5Q!0KW|DkNw%%=G~h<;xc!x1#{r=c0x&
zV->;$iDIKRaU;LNRWRnT0wg0}PMk>e#R#Md8k<DBu;`nt8nR#pExHiRVL$*o*j18l
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..bc9c79cc6141a67a21edaffc9a068ebd0899eb46
GIT binary patch
literal 4010
zc$@*M4^{AqP)<h;3K|Lk000e1NJLTq006)M002Y?0{{R3;3LZW00004XF*Lt007q5
z)K6G40000PbVXQnQ*UN;cVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#08mU+Ma8Cw
z#Jjb~$jJKo`nj5ib2u0H`1qH4X~&#%rH5>*YDvbIYV`E<X)_hsrE}TY+3e%qs$@W>
zcS*)_N8;Vt-{0T4i*l)rclP%7^78WR?CkR9;P$YDnnM-rm~X_Hf?6gCgI!D3*xIvn
zQ{CO&=H}+Sp@`Jf)V8Uj(#^}_=IZF^=*z;rmu*C&dQ{-x;HiUM_4)eK$-?jO@apR7
z<mBY{qkG-JvF`5f`10?CI~CW_&C9c*gkn+KzOIE;I)`9P+TP*8i&nFebA%}X)YjPU
z@$=c$(Yvv%yMs#N;^K%#A=iRgifvuIyu9Y<>4`B0d{{=xteoB8;=Pe*n1FP(i(`00
zCd0L>(Y~|l?(u6oA$UtP`27CBuA8AxB)zPixrJ$+S~$(6kGzasu$G0tl6vUv@2XQK
zf<Yddc2u&Ke3525zqYcTaYMR>Q>0ljtdM-Vqm<d)-psL?&&tP?X;JIu<F})cyMbcz
z^!4N9<)us>#KFMX+T796(dp~%*3;2vD+#Qelyox?%EiL-_V~t;T(y5=&(hTV{{OkB
zo72X-v!k2Uw4&C#tI^Wbv{xzO<KwuMbg+e7v!9Tzjc&Y{b);`p#h`tyqn+v4&xvMK
z!iiAi=I7YUzk5<WxQ=Dos(rA5QG{t*U@jAjXji_TfBF0Uzp0e}|Nqj|)#A#&wV8jh
zeog4<>dCvVzK(S3?e5yiz@JVdkvR$b{r=6)&)weN*q>+N;^X!7@z~DD_xk&9Kqikt
z9OvrnwqG-EM=`>&pm;6@_4fGm_4e=c^|FXxdp;a~AOZgV{{8*^qobqz{QR%4ueG(c
zsi~>Zr;`2u|NQ;^>+9>$z_#=A^S_I5<mBVz;oZ%)ss8=_`1$(z`T5@0)7sqI^6l#2
zi(<cOK7&jyo{NF|`}?GhewtD*&z)}Z@$vHU^7r@m<KyGUuA=Mf?CtIC+QYY?OCFs@
z7~9+1<>lq(=H=11oP=gq$D)u}Aq3>j!T$gL;o;%p;o-fAQ7nYh%>V!lFiAu~RCwCd
znhRJHR~CSQX=1^N5$r&MP6IdtXpo=@HG-^Ij3^2sh>t}@s$wmIR2F=oSQXG}tt}#I
zeH0&H9c!Z&O6pn_wW*jfP&LNBroNh18rHOut-3^eXM)LqRKI>3zAxPGOPI{ud(Qdq
zIrpA3Gq@1lyt#CZSPiKmTET}oCJ2($Xu1;sNs_IXAqp}8yEWfmF|2RB0vWWVrken0
zNRs$y@sO$C8jQedl7x;-{ZT&S3$CF{+~Cp$_FrEA?UBGtpCcuDH3d<`Blj?$B|}Vu
zjZjxOp$h<7|NXDYn-2sYo*Ow@s#kxeUlQh1o|(BNRsbB*9Xvp;7rN}*vo#P9gqV*>
zUs@98ULF&8*k^!L&+Q%n)Qoq|pD!yv9GIDT@a#dKu&~h3^1zrSm{KY*bPE9Pj1ysH
zWnpD8F`Ks^*b}zTD?2-EbIh#VaFg254c35$6wG)hwCw!(^LysZ>3QeXg$oz%+c$a7
z<_(Hc9Sk`+faZI!Yf?XyEDAk8dD5hwYajQ#^LXs8U9a}MGihsJZGl+G)zT?V<D>v|
z_PtC|Kdc|}Mi{Vv&ZO-wXNS*pIWT8euU>avJaBg8TXGm8u0S+|GXiMHR^f-=mcX(Q
z_fQZ%dt6-IYTrJjIJB~O>fT;^du`9$uu3WQ9r6;F3lN+UfV^_~L-)foXKis04V_#T
z;|i<~C~U#7+`@p`KYI1*xhLj@Su>aT%re0O$Qc1N<oX*iWt(F%4<8Q84$X9XJBW`-
z3UITzIS$EHOuMt|)$J|^0zq~-Aq7i8<Aea(&p!0}WO8|VOnI1>yW63lmPg`aqd_w%
z@i=7U@U>taY~SwU5;$|W33i|h90oudj4oprdU=Ho%6=nkg+ie+ADh4rIT8{(Pk2}P
z-{T|y=(YDw&z>)Com3tvlNy~5K%@Pr_rhIk_jwH(v|zy&MSu#wyK2L>Uw+B)n7eAU
zNrw+ywYOity?53=ew=+!X;N#Q&jZ>odT)QcYvCte*$dt%F2ur>#c!UfS^j?A`^&e5
z9J?#S2OjzS^R>IijvZTew!jxQI3a+sX~H{`KA{1A4k(l;2h2T{_<Eo5<Hz4xzAf*r
zO!rpofvsCNZ;m-QprsVl6wVhyBv<&>>EeAfKyWT*mIQl@x>q;;<yT%FU-$c5i%ciO
zk$^+*tz4<dmEcmNGXg*YIX-ZSmsj?ngk(gSx9HTU<+pD3>(}pQpL^T#lrmqP_&9RC
z<+{p(n;cnJI}Sh#!Mb@z24yekk*BgKU0)cL`1`G!uk?H6=B<0LWtED3O}<Jz{2N@X
z1NQ|6C)`E>0k>&z)=AEk_`Ktm-82AN*XQQTH~ZApyq2~5*s{Tczbq+Hn@mDAs3n{*
zk<me<|7^{}%IWuuOCRA`|4AHG^LyQ`K7HzzkJ=XOTAVikw7gYvz22erJJ17Q8EbSE
z56}4fXAP80aGmCPA+hFO+Usfe5))rY*&ole{rih&2tg_2oVCyuLfYeDWrhF5H3b%P
zWY+M+QHhB)H8qJBwr!}20?j`v>b`%E0VQ9moiT*K&A^RCBUbY&r$<dp321IEof`5W
z@xmz3E_fX2Q8CfqKRPNpYG-^@{F+FizzJ(14N3gSE2`)4Qo%%L?S51c8=N=I<5Wb%
zFBwC6@G5zgsi`MVo~ekgs;XEsTIh`1sP>~@^NZ$lIJ}e5{we(3meQc$sgcvZ8+yVu
zpU2}>R#y6f4=;84h>8%IT;PnQ1ow+Y=f=$kKB$k5T2oM9Da~~a&I(S6<~;QCqm8cw
z92^d3bw!Zb1UqFZG5E*Z5rCkcGbOqzcp#2Qk`*h*^yrZS+E4pw`%{ZJ_48N%oZRe`
zwUCxt_SKu`BF2s56h#1nH7%#(5Y_d<!hirp?#K_$0Q)OfbEa_4-7Zf4Ms9S%a#{ny
zB`5NIT(dUj7jbUy+?kSPIX&UH1W~DS6ZX?F<ahF?F<Bd<=Z=(!opBKZ7Z`;~Npdcj
z@sy&X=~X-Lr)+f18<4jmIAvlgy@t;;D&9N958@+clNxf$S_o20b!LmIB{^W{YR>ek
z_?=Pr<KwHM=nOD1)vxk{!j*~^%+kyhqfS{1X(@G++*gK|&fCH%;!OvME82eqXcZM_
z&ZIt^SR25Xn8iAi6gE2LV--*gX(6K;);H<Cnp$53_MDTc^}Hf_=cLnsUwlBJMJ#WE
z)kezToDa5W0)qjfjK2<^TF(LbfX7?Sn^J!c9D?Ro6#(wUKqYN(@XIpCKAqNpniTqB
znY%R~@OEw-Gv?>p5#YGKXo~9unVzB_?K<`ewc{VJ8!j)-KX+=}xcQuvT7^PUm^A_<
z2M|*mPlE*99o~_VT(xBGh=_=y{6mT$KEGw)hWQZ@^YgP>%-!P!2SHLo>Ac7-`L{PF
z^Ht5}Zzk~bN4Sp($VJ3bLAQ8Erhx=fv3X!_-b&0OHc90=JTf*pmyem{Fx5Q(Kt7P_
znhSOpgzK8rMuE`u&k2Zx$zKL-cL*Us011S0olGa!8^Ny0g{8haI)8B?vRk~Vr*H2e
zt^kH%HEl9*h(HZ9`Kycm)nF+4qz!c-f&qdc1M@Nrq&MdHk_S-HU(|P4*8yyD+*?ph
zTeBSx^Jr62-Ph5HthX>6@kYH7B=&^Rwo6<>`_q4xqL)~GFIi1k%AUicZRgqv9onCs
z>~;=ail&Lo5+unZUC)}PHi;gs?5^*2jD^nh4qHl}*+Bznn-~B-hBP<X0Cu1vE=dn%
zd*z9*mXc2iM`w3-&OWp+icV8uDnxR12rhU^q9>PK-7Q_3bFE!0bfUMl&7u7@E+Fx!
z;SAI}Tneix(hhLc7b1w+=y0ih@BRmR6hV-ys3&ZfG#eFxb0Sn^w5c4HhNj~ZWF1<4
z!*=d}Xif{F{0-Lo;yNzTUSe$QcQT4#vHm8iUd+B}?o4l5TI3_-foGD@h545-5$dg^
z2-YUC1N9bDw0TLs29_Xeqx;$ctaBJEH`pgE9RZWAT8F+1JvsL=nuF;m5#99~Npz5S
z6%n(bM4qzP>I^PHJX7x`&voA3NNk+Rx{Zq3U1R{#+W<@*0dhpw&Bl%ZIalrd-bK?f
zya{efN0XL4nUdZp`W>uc05-AW#VYYrIqNYC@E^U@7e9sBUhgKe3eQN-87}=xfa>8C
zu_LDgfHz;|fhHlo5DOp!te$D8N0Ut>Fg6`H`x;suYl}Yv2&e~|k4+X4nj~#65kt(s
zX`fox85HqMy`JknmH(QAdJ6smfb?veSxE-~EVer5B{kX61E`06t!N*#of9FpljLI0
zC!6)Ur-L$$1(4$z$H%aooF?*Fdi$oe36om^`lB8l0EFJv^gP5OtVKK<?H?|crlb9D
z*gIZpvww)#HeTEGfXP`8usGOe7+0LZAfzW@EPyurYI_KY&<quZNIcLC=$U%CB3j&0
z47UPMVocl)Kx4!)Or^pw_NHg>MZ^@@6c=aTZV5=)0)hZ^WR0hE%p%6vE+=6i5Qs!D
zi+#BRi5M7-Ah<2KzlXz{<uEL_H0#M{=+y|s2vE_2$*cg7Z#YXCqfjl2LlBD+W>vsi
zDQ<!Y$`_Z~`$BTi4M8Nh-riBeHG!NZF`Kxo-CV2Ig-d`$T+Vt;ua!2_qY<$UKr0jj
z9bgV{dsgjCuNJ1oS!8W%faD6e9i9}B5U9rl6xpuQH-g_J#qLXI5vFd_w!5t~)`IfQ
zpb2U}TFrFSw7YkpN5l3<jU+|upbfIC?o6*$9O(3%YLxoA7JN6j{p}JZ;UeAKo+F$F
zOla)Va=L*}>tJTG;UYos8|b4T+Bo_G;YJ@NSg&5Z__f|P&P3oB>2HFNG*AT9AOS~u
z1`6*W+>N260C6L8$<zoW(t=FSpMt*!kdTl-;Eb{KH-3>;5(E^OOw3iBXkEb$85)3Y
zdU^<B5dnti^?H_YyPJS)f=`QN+~UT}6A++h^g9Xw4_dL?20&YE^@W=)W>_9z0Welt
z0iGN@?E&U|0{D$L*nZLif`a}}fJR`e{_8dX!maTc9%x-5A)fC6%G4=9mwAAWNNbQY
z>KZ#+1I)RGAfTJAjepT<YR5Ie?{pFCfd?CyHGl(+jgDIbPT#scz@GvP0O}0)lmmXS
QYybcN07*qoM6N<$f~epMssI20
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/markuptest/templates/login.html
@@ -0,0 +1,115 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://purl.org/kid/ns#"
+      xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<xi:include href="master.html" />
+
+<head>
+    <meta content="text/html; charset=UTF-8"
+        http-equiv="content-type" py:replace="''"/>
+    <title>Login</title>
+    <style type="text/css">
+        #loginBox
+        {
+            width: 30%;
+            margin: auto;
+            margin-top: 10%;
+            padding-left: 10%;
+            padding-right: 10%;
+            padding-top: 5%;
+            padding-bottom: 5%;
+            font-family: verdana;
+            font-size: 10px;
+            background-color: #eee;
+            border: 2px solid #ccc;
+        }
+
+        #loginBox h1
+        {
+            font-size: 42px;
+            font-family: "Trebuchet MS";
+            margin: 0;
+            color: #ddd;
+        }
+
+        #loginBox p
+        {
+            position: relative;
+            top: -1.5em;
+            padding-left: 4em;
+            font-size: 12px;
+            margin: 0;
+            color: #666;
+        }
+
+        #loginBox table
+        {
+            table-layout: fixed;
+            border-spacing: 0;
+            width: 100%;
+        }
+
+        #loginBox td.label
+        {
+            width: 33%;
+            text-align: right;
+        }
+
+        #loginBox td.field
+        {
+            width: 66%;
+        }
+
+        #loginBox td.field input
+        {
+            width: 100%;
+        }
+
+        #loginBox td.buttons
+        {
+            text-align: right;
+        }
+
+    </style>
+</head>
+
+<body>
+    <div id="loginBox">
+        <h1>Login</h1>
+        <p>$message</p>
+        <form action="$previous_url" method="POST">
+            <table>
+                <tr>
+                    <td class="label">
+                        <label for="user_name">User Name:</label>
+                    </td>
+                    <td class="field">
+                        <input type="text" id="user_name" name="user_name"/>
+                    </td>
+                </tr>
+                <tr>
+                    <td class="label">
+                        <label for="password">Password:</label>
+                    </td>
+                    <td class="field">
+                        <input type="password" id="password" name="password"/>
+                    </td>
+                </tr>
+                <tr>
+                    <td colspan="2" class="buttons">
+                        <input type="submit" name="login" value="Login"/>
+                    </td>
+                </tr>
+            </table>
+
+            <input py:if="forward_url" type="hidden" name="forward_url"
+                value="$forward_url"/>
+                
+            <input py:for="name,value in original_parameters.items()"
+                type="hidden" name="$name" value="$value"/>
+        </form>
+    </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/markuptest/templates/master.html
@@ -0,0 +1,40 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://purl.org/kid/ns#"
+      xmlns:xi="http://www.w3.org/2001/XInclude"
+      py:strip="">
+<xi:include href="sitetemplate.html"><xi:fallback/></xi:include>
+
+<head py:match="head">
+    <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+    <title py:replace="''">Your title goes here</title>
+    ${select('*/*')}
+    <style type="text/css">
+        #pageLogin
+        {
+            font-size: 10px;
+            font-family: verdana;
+            text-align: right;
+        }
+    </style>
+</head>
+
+<body py:match="body">
+    <div py:if="tg.config('identity.on', False) and not logging_in"
+        id="pageLogin">
+        <span py:if="tg.identity.anonymous">
+            <a href="/login">Login</a>
+        </span>
+        <span py:if="not tg.identity.anonymous">
+            Welcome ${tg.identity.user.display_name}.
+            <a href="/logout">Logout</a>
+        </span>
+    </div>
+
+    <div py:if="tg_flash" class="flash" py:content="tg_flash"></div>
+
+    ${select('*')}
+
+    <p align="center"><img src="/static/images/tg_under_the_hood.png" alt="TurboGears under the hood"/></p>
+</body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/markuptest/templates/welcome.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"
+      xmlns:py="http://purl.org/kid/ns#"
+      xmlns:xi="http://www.w3.org/2001/XInclude">
+<xi:include href="master.html" />
+
+<head>
+    <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
+    <title>Welcome to TurboGears</title>
+</head>
+
+<body>
+    <p>Congratulations, your TurboGears application is running as of <span py:replace="now">now</span>.</p>
+
+    <h2>Are you ready to Gear Up?</h2>
+
+    <p>Take the following steps to dive right in:</p>
+
+    <ol>
+        <li>Edit your project's model.py to create SQLObjects representing the data you're working with</li>
+        <li>Edit your dev.cfg file to point to the database you'll be using</li>
+        <li>Run "<code>tg-admin sql create</code>" to create the tables in the database</li>
+        <li>Edit controllers.py to add the functionality to your webapp</li>
+        <li>Change the master.kid template to have the headers and footers for your application.</li>
+        <li>Change welcome.kid (this template) or create a new one to display your data</li>
+        <li>Repeat steps 4-6 until done.</li>
+        <li><b>Profit!</b></li>
+    </ol>
+
+    <p>If you haven't already, you might check out some of the <a href="http://www.turbogears.org/docs/" >documentation</a>.</p>
+
+    <p>Thanks for using TurboGears! See you on the <a href="http://groups.google.com/group/turbogears" >mailing list</a> and the "turbogears" channel on irc.freenode.org!</p>
+
+</body>
+</html>
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/markuptest/tests/test_controllers.py
@@ -0,0 +1,16 @@
+from turbogears import testutil
+from markuptest.controllers import Root
+import cherrypy
+
+cherrypy.root = Root()
+
+def test_method():
+    "the index method should return a string called now"
+    import types
+    result = testutil.call(cherrypy.root.index)
+    assert type(result["now"]) == types.StringType
+
+def test_indextitle():
+    "The mainpage should have the right title"
+    testutil.createRequest("/")
+    assert "<TITLE>Welcome to TurboGears</TITLE>" in cherrypy.response.body[0]
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/markuptest/tests/test_model.py
@@ -0,0 +1,23 @@
+# If your project uses a database, you can set up database tests
+# similar to what you see below. Be sure to set the db_uri to
+# an appropriate uri for your testing database. sqlite is a good
+# choice for testing, because you can use an in-memory database
+# which is very fast.
+
+from turbogears import testutil
+# from markuptest.model import YourDataClass, User
+
+# database.set_db_uri("sqlite:///:memory:")
+
+# class TestUser(testutil.DBTest):
+#     def get_model(self):
+#         return User
+#
+#     def test_creation(self):
+#         "Object creation should set the name"
+#         obj = User(user_name = "creosote",
+#                       email_address = "spam@python.not",
+#                       display_name = "Mr Creosote",
+#                       password = "Wafer-thin Mint")
+#         assert obj.display_name == "Mr Creosote"
+
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/sample-prod.cfg
@@ -0,0 +1,66 @@
+[global]
+# This is where all of your settings go for your production environment.
+# You'll copy this file over to your production server and provide it
+# as a command-line option to your start script.
+# Settings that are the same for both development and production
+# (such as template engine, encodings, etc.) all go in 
+# markuptest/config/app.cfg
+
+# DATABASE
+
+# pick the form for your database
+# sqlobject.dburi="postgres://username@hostname/databasename"
+# sqlobject.dburi="mysql://username:password@hostname:port/databasename"
+# sqlobject.dburi="sqlite:///file_name_and_path"
+
+# if you are using a database or table type without transactions
+# (MySQL default, for example), you should turn off transactions
+# by prepending notrans_ on the uri
+# sqlobject.dburi="notrans_mysql://username:password@hostname:port/databasename"
+
+# for Windows users, sqlite URIs look like:
+# sqlobject.dburi="sqlite:///drive_letter|/path/to/file"
+
+
+# SERVER
+
+server.environment="production"
+
+# Sets the number of threads the server uses
+# server.thread_pool = 1
+
+# if this is part of a larger site, you can set the path
+# to the TurboGears instance here
+# server.webpath=""
+
+# Set to True if you'd like to abort execution if a controller gets an
+# unexpected parameter. False by default
+# tg.strict_parameters = False
+
+# LOGGING
+# Logging configuration generally follows the style of the standard
+# Python logging module configuration. Note that when specifying
+# log format messages, you need to use *() for formatting variables.
+# Deployment independent log configuration is in markuptest/config/log.cfg
+[logging]
+
+[[handlers]]
+
+[[[access_out]]]
+# set the filename as the first argument below
+args="('server.log',)"
+class='FileHandler'
+level='INFO'
+formatter='message_only'
+
+[[loggers]]
+[[[markuptest]]]
+level='ERROR'
+qualname='markuptest'
+handlers=['error_out']
+
+[[[access]]]
+level='INFO'
+qualname='turbogears.access'
+handlers=['access_out']
+propagate=0
new file mode 100644
--- /dev/null
+++ b/examples/turbogears/setup.py
@@ -0,0 +1,62 @@
+from setuptools import setup, find_packages
+from turbogears.finddata import find_package_data
+
+import os
+execfile(os.path.join("markuptest", "release.py"))
+
+setup(
+    name="MarkupTest",
+    version=version,
+    
+    # uncomment the following lines if you fill them out in release.py
+    #description=description,
+    #author=author,
+    #author_email=email,
+    #url=url,
+    #download_url=download_url,
+    #license=license,
+    
+    install_requires = [
+        "TurboGears >= 0.9a7dev-r1517",
+    ],
+    scripts = ["start-markuptest.py"],
+    zip_safe=False,
+    packages=find_packages(),
+    package_data = find_package_data(where='markuptest',
+                                     package='markuptest'),
+    keywords = [
+        # Use keywords if you'll be adding your package to the
+        # Python Cheeseshop
+        
+        # if this has widgets, uncomment the next line
+        # 'turbogears.widgets',
+        
+        # if this has a tg-admin command, uncomment the next line
+        # 'turbogears.command',
+        
+        # if this has identity providers, uncomment the next line
+        # 'turbogears.identity.provider',
+    
+        # If this is a template plugin, uncomment the next line
+        # 'python.templating.engines',
+        
+        # If this is a full application, uncomment the next line
+        # 'turbogears.app',
+    ],
+    classifiers = [
+        'Development Status :: 3 - Alpha',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+        'Topic :: Software Development :: Libraries :: Python Modules',
+        'Framework :: TurboGears',
+        # if this is an application that you'll distribute through
+        # the Cheeseshop, uncomment the next line
+        # 'Framework :: TurboGears :: Applications',
+        
+        # if this is a package that includes widgets that you'll distribute
+        # through the Cheeseshop, uncomment the next line
+        # 'Framework :: TurboGears :: Widgets',
+    ],
+    test_suite = 'nose.collector',
+    )
+    
new file mode 100755
--- /dev/null
+++ b/examples/turbogears/start-markuptest.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+import pkg_resources
+pkg_resources.require("TurboGears")
+
+import turbogears
+import cherrypy
+cherrypy.lowercase_api = True
+
+from os.path import *
+import sys
+
+# first look on the command line for a desired config file,
+# if it's not on the command line, then
+# look for setup.py in this directory. If it's not there, this script is
+# probably installed
+if len(sys.argv) > 1:
+    turbogears.update_config(configfile=sys.argv[1], 
+        modulename="markuptest.config")
+elif exists(join(dirname(__file__), "setup.py")):
+    turbogears.update_config(configfile="dev.cfg",
+        modulename="markuptest.config")
+else:
+    turbogears.update_config(configfile="prod.cfg",
+        modulename="markuptest.config")
+
+from markuptest.controllers import Root
+
+turbogears.start_server(Root())
new file mode 100644
--- /dev/null
+++ b/markup/plugin.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2006 Mattew Good
+# All rights reserved.
+#
+# This software is licensed as described in the file COPYING, which
+# you should have received as part of this distribution. The terms
+# are also available at http://trac.edgewall.com/license.html.
+#
+# This software consists of voluntary contributions made by many
+# individuals. For the exact contribution history, see the revision
+# history and logs, available at http://projects.edgewall.com/trac/.
+
+import os
+from pkg_resources import resource_filename
+
+from markup.template import Context, Template, TemplateLoader
+
+
+class TemplateEnginePlugin(object):
+
+    def __init__(self, extra_vars_func=None, options=None):
+        if options is None:
+            options = {}
+        # TODO get loader_args from the options dict
+
+        self.loader = TemplateLoader(auto_reload=True)
+        self.options = options
+        self.get_extra_vars = extra_vars_func
+
+    def load_template(self, templatename):
+        """Find a template specified in python 'dot' notation."""
+        divider = templatename.rfind('.')
+        if divider >= 0:
+            package = templatename[:divider]
+            basename = templatename[divider + 1:] + '.html'
+            fullpath = resource_filename(package, basename)
+            dirname, templatename = os.path.split(fullpath)
+            self.loader.search_path.append(dirname) # Kludge
+
+        return self.loader.load(templatename)
+
+    def render(self, info, format='html', fragment=False, template=None):
+        """Renders the template to a string using the provided info."""
+        return self.transform(info, template).render(method=format)
+
+    def transform(self, info, template):
+        "Render the output to Elements"
+        if not isinstance(template, Template):
+            template = self.load_template(template)
+
+        data = {}
+        if self.get_extra_vars:
+            data.update(self.get_extra_vars())
+        data.update(info)
+
+        ctxt = Context(**data)
+        return template.generate(ctxt)
--- a/setup.py
+++ b/setup.py
@@ -21,5 +21,9 @@
     license='BSD', url='http://markup.cmlenz.net/',
     packages=find_packages(exclude=['*.tests*']),
     test_suite = 'markup.tests.suite',
-    zip_safe = True
+    zip_safe = True,
+    entry_points = """
+    [python.templating.engines]
+    markup = markup.plugin:TemplateEnginePlugin
+    """,
 )
Copyright (C) 2012-2017 Edgewall Software