--- /dev/null
+<?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://web.resource.org/cc/"
+ 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="300"
+ height="120"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.44"
+ version="1.0"
+ sodipodi:docbase="/home/blackbird/Development/jinja/trunk/artwork"
+ sodipodi:docname="jinjalogo.svg"
+ inkscape:export-filename="/home/blackbird/Development/jinja/trunk/docs/jinjalogo.png"
+ inkscape:export-xdpi="60"
+ inkscape:export-ydpi="60">
+ <defs
+ id="defs4">
+ <linearGradient
+ id="linearGradient6558">
+ <stop
+ style="stop-color:#585858;stop-opacity:1;"
+ offset="0"
+ id="stop6560" />
+ <stop
+ style="stop-color:#4a4a4a;stop-opacity:1;"
+ offset="1"
+ id="stop6562" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6558"
+ id="radialGradient6564"
+ cx="61.297766"
+ cy="60.910986"
+ fx="61.297766"
+ fy="60.910986"
+ r="44.688254"
+ gradientTransform="matrix(1,0,0,0.945104,0,3.343747)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient6558"
+ id="radialGradient6580"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.945104,0.355158,3.334402)"
+ cx="61.297766"
+ cy="60.910986"
+ fx="61.297766"
+ fy="60.910986"
+ r="44.688254" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="3.959798"
+ inkscape:cx="145.89672"
+ inkscape:cy="59.43346"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ width="300px"
+ height="120px"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-width="1400"
+ inkscape:window-height="975"
+ inkscape:window-x="0"
+ inkscape:window-y="24" />
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <path
+ style="font-size:12px;font-style:normal;font-weight:normal;fill:#dcdcdc;fill-opacity:1;stroke:#cbcbcb;stroke-width:0.3;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1;font-family:Bitstream Vera Sans;stroke-miterlimit:4;stroke-dasharray:none"
+ d="M 165.36463,80.874808 L 165.36463,80.874808 L 153.32556,80.874808 L 153.32556,81.8344 L 147.64994,81.8344 L 147.64994,36.035583 L 165.36463,36.035583 L 165.36463,20.333129 C 170.58154,21.031083 173.07533,22.077914 172.84609,23.473621 C 172.78871,24.055258 172.21545,24.549594 171.12624,24.956624 L 171.12624,36.035583 L 189.09895,36.035583 L 189.09895,82.532286 L 183.33733,82.532286 L 183.33733,80.874808 L 171.12624,80.874808 L 171.12624,102.94548 L 165.36463,102.94548 L 165.36463,80.874808 M 153.32556,55.489173 L 153.32556,55.489173 L 165.36463,55.489173 L 165.36463,41.793146 L 153.32556,41.793146 L 153.32556,55.489173 M 171.12624,55.489173 L 171.12624,55.489173 L 183.33733,55.489173 L 183.33733,41.793146 L 171.12624,41.793146 L 171.12624,55.489173 M 183.33733,61.333977 L 183.33733,61.333977 L 171.12624,61.333977 L 171.12624,75.030006 L 183.33733,75.030006 L 183.33733,61.333977 M 165.36463,61.333977 L 165.36463,61.333977 L 153.32556,61.333977 L 153.32556,75.030006 L 165.36463,75.030006 L 165.36463,61.333977 M 132.85897,59.414792 C 137.33069,63.136883 140.99969,67.934848 143.86618,73.808701 L 139.13654,77.385372 C 137.24467,72.965445 134.6362,69.12707 131.31114,65.87024 L 131.31114,102.94548 L 125.63554,102.94548 L 125.63554,68.57455 C 122.31042,71.947693 118.52671,74.913707 114.28436,77.47261 L 109.64069,73.372526 C 121.50782,67.091566 130.62312,55.489212 136.98668,38.565417 L 116.26221,38.565417 L 116.26221,32.720615 L 125.80754,32.720615 L 125.80754,20.333129 C 130.85245,21.031083 133.31761,22.048838 133.20299,23.386383 C 133.14561,24.026183 132.57235,24.549594 131.48307,24.956624 L 131.48307,32.720615 L 140.77043,32.720615 L 143.60824,36.733469 C 140.68444,45.51526 137.10137,53.075692 132.85897,59.414792 M 254.11016,49.469901 L 254.11016,49.469901 L 254.11016,20.333129 C 259.21243,21.031083 261.67755,22.048838 261.50562,23.386383 C 261.44823,23.909869 261.04699,24.346044 260.30172,24.694917 C 260.30164,24.694986 260.30164,24.694986 260.30172,24.694917 L 260.30172,24.694917 L 259.78578,24.956624 L 259.78578,49.469901 L 277.15652,49.469901 L 277.15652,55.227471 L 259.78578,55.227471 L 259.78578,93.785712 L 281.45616,93.785712 L 281.45616,99.63051 L 232.35378,99.63051 L 232.35378,93.785712 L 254.11016,93.785712 L 254.11016,55.227471 L 236.22346,55.227471 L 236.22346,49.469901 L 254.11016,49.469901 M 225.5603,59.327554 C 231.12111,63.107798 235.62145,67.876693 239.06127,73.634235 L 234.76157,77.647079 C 231.60845,72.180322 227.82475,67.934848 223.41044,64.910648 L 223.41044,102.94548 L 217.73484,102.94548 L 217.73484,67.44049 C 212.91919,71.627831 207.70222,75.030021 202.084,77.647079 L 197.87027,73.198053 C 212.66118,66.917101 224.01239,55.372897 231.92377,38.565417 L 205.35172,38.565417 L 205.35172,32.720615 L 217.99283,32.720615 L 217.99283,20.333129 C 223.03774,21.031083 225.50291,22.048838 225.38829,23.386383 C 225.33089,24.026183 224.75765,24.549594 223.66837,24.956624 L 223.66837,32.720615 L 236.22346,32.720615 L 238.80326,36.733469 C 235.13421,45.51526 230.71987,53.046611 225.5603,59.327554"
+ id="text4761" />
+ <path
+ style="font-size:44.09793472px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#b41717;fill-opacity:1;stroke:#7f2828;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Candara;stroke-miterlimit:4;stroke-dasharray:none"
+ d="M 149.14708,37.774469 C 148.97807,41.117899 148.84526,44.824225 148.74871,48.893456 C 148.67626,52.962754 148.3818,70.641328 148.38184,75.524422 C 148.3818,79.065795 148.05588,81.991266 147.40406,84.300835 C 146.75219,86.610422 145.72612,88.557071 144.32585,90.140779 C 142.94969,91.724494 141.17522,92.901283 139.00239,93.671139 C 136.82953,94.440996 134.22211,94.825935 131.18014,94.825935 C 128.83828,94.825935 126.73787,94.59498 124.87889,94.133049 L 125.4221,89.31593 C 127.13623,90.0418 128.92278,90.404734 130.78177,90.404733 C 132.85805,90.404734 134.66875,90.140782 136.2139,89.612876 C 137.78315,89.062981 139.02651,88.216133 139.94396,87.072335 C 140.8855,85.928548 141.54942,84.520804 141.93572,82.8491 C 142.34613,81.177412 142.55134,78.988811 142.55136,76.283285 C 142.55134,66.297119 142.62852,44.659257 142.26641,37.774469 L 149.14708,37.774469 M 166.38498,80.732697 L 159.83024,80.732697 C 160.16821,76.333498 160.33723,71.307412 160.33723,65.654424 C 160.33723,59.2976 159.91471,53.963567 159.06973,49.652319 L 166.31257,48.761483 C 166.02284,53.358679 165.87799,58.98965 165.87799,65.654424 C 165.87799,70.933479 166.04699,75.959565 166.38498,80.732697 M 167.90601,39.490159 C 167.90598,40.611994 167.5076,41.590815 166.7109,42.42662 C 165.91418,43.240515 164.79155,43.647442 163.343,43.647399 C 162.11172,43.647442 161.146,43.295504 160.44588,42.591595 C 159.76988,41.865769 159.43188,40.996927 159.43188,39.98507 C 159.43188,38.885304 159.84231,37.928485 160.66315,37.114591 C 161.48399,36.30078 162.61869,35.893853 164.06727,35.893811 C 165.25023,35.893853 166.17975,36.256783 166.85575,36.982609 C 167.55588,37.686526 167.90598,38.522373 167.90601,39.490159 M 206.72748,80.732697 L 200.13651,80.732697 C 200.66763,74.947749 200.93319,68.634899 200.9332,61.794122 C 200.93319,58.406756 200.1727,56.097177 198.65174,54.865371 C 197.15487,53.61163 195.00619,52.984747 192.20564,52.984714 C 188.77731,52.984747 185.61465,54.117535 182.71753,56.383099 C 182.71753,63.883761 182.76583,72.000287 182.86238,80.732697 L 176.27142,80.732697 C 176.68182,73.254058 176.88707,67.843042 176.88707,64.499632 C 176.88707,59.352589 176.3559,54.359493 175.29363,49.520339 L 181.66734,48.695493 L 182.35539,52.720761 L 182.64511,52.720761 C 186.21823,49.773323 190.04483,48.299592 194.12499,48.299567 C 198.13265,48.299592 201.23499,49.113454 203.43201,50.741118 C 205.62895,52.346863 206.72747,55.217334 206.72748,59.352563 C 206.72747,59.770507 206.70331,60.595362 206.65507,61.827118 C 206.60675,63.058915 206.5826,63.883761 206.58262,64.30167 C 206.5826,67.975018 206.63088,73.452022 206.72748,80.732697 M 222.69791,48.695493 C 222.28747,55.514282 222.08225,62.355041 222.08225,69.21778 C 222.08225,71.043461 222.14262,73.463019 222.26332,76.476468 C 222.40822,79.467925 222.4806,81.502559 222.48063,82.580363 C 222.4806,89.685068 219.51105,93.996287 213.57195,95.514024 L 211.76124,93.006484 C 213.90995,91.356766 215.2378,89.597085 215.74478,87.727431 C 216.49321,85.043912 216.86743,79.324953 216.86743,70.570535 C 216.86743,61.178248 216.3846,54.16153 215.41887,49.520339 L 222.69791,48.695493 M 224.2551,39.490159 C 224.2551,40.611994 223.85673,41.590815 223.06006,42.42662 C 222.26332,43.240515 221.14069,43.647442 219.69213,43.647399 C 218.46084,43.647442 217.49515,43.295504 216.795,42.591595 C 216.119,41.865769 215.781,40.996927 215.781,39.98507 C 215.781,38.885304 216.19144,37.928485 217.01231,37.114591 C 217.83316,36.30078 218.96785,35.893853 220.4164,35.893811 C 221.5994,35.893853 222.52889,36.256783 223.20492,36.982609 C 223.90503,37.686526 224.2551,38.522373 224.2551,39.490159 M 259.60008,80.732697 L 253.91446,80.930661 C 253.62473,79.852857 253.47987,78.830045 253.4799,77.862216 L 253.11774,77.862216 C 250.14817,80.325772 246.10427,81.557546 240.98606,81.557547 C 238.20962,81.557546 235.8195,80.820682 233.81563,79.346948 C 231.81178,77.851221 230.80988,75.728607 230.80988,72.979099 C 230.80988,69.591724 232.37914,66.875216 235.51769,64.829574 C 238.65625,62.761967 244.48667,61.67316 253.00913,61.563165 C 253.08155,61.035275 253.11772,60.430386 253.11774,59.748497 C 253.11772,57.043003 252.32104,55.239336 250.72765,54.337474 C 249.15832,53.435661 246.76819,52.984747 243.55721,52.984714 C 239.76681,52.984747 236.03678,53.413668 232.3671,54.271484 L 232.9827,49.718301 C 236.60411,48.77251 240.76873,48.299592 245.47658,48.299567 C 249.77395,48.299592 253.09359,49.113454 255.43545,50.741118 C 257.77728,52.346863 258.94819,55.096363 258.94824,58.989625 C 258.94819,60.023469 258.88785,61.904117 258.76715,64.631608 C 258.67054,67.337133 258.62228,69.140806 258.6223,70.042632 C 258.62228,74.045913 258.94819,77.609265 259.60008,80.732697 M 253.19019,74.331856 C 253.06945,70.988469 253.00909,67.986016 253.00913,65.324484 C 248.47027,65.324498 245.01786,65.632443 242.65187,66.248318 C 238.69248,67.348131 236.71278,69.448748 236.71278,72.550177 C 236.71278,75.541643 239.03044,77.037371 243.66588,77.037366 C 247.64942,77.037371 250.82416,76.135534 253.19019,74.331856"
+ id="text3736"
+ sodipodi:nodetypes="ccsscssccscccsccccsccsccsscsssccccscscccsccccscsssccscscccscccsscsssccccccscsccscsccscscsccccssc" />
+ <path
+ style="fill:url(#radialGradient6564);fill-opacity:1.0;fill-rule:evenodd;stroke:#323232;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+ d="M 105.45673,18.675923 C 105.45673,18.675923 88.211949,26.918461 74.172834,28.737898 C 60.133727,30.557333 33.360434,32.377571 28.045622,31.093256 C 22.730818,29.808941 18.915645,28.309196 18.915645,28.309196 L 20.021441,32.056583 L 16.609513,35.052471 L 17.2144,36.121726 L 18.61792,36.22764 L 22.92773,36.762252 L 23.532621,38.688909 L 25.937975,38.905784 L 27.143021,42.970927 C 27.143021,42.970927 32.254764,43.399628 33.758953,43.399628 C 35.263142,43.399628 38.271966,43.187802 38.271966,43.187802 L 38.371202,44.791657 L 39.477002,45.003495 L 39.477002,46.824227 L 37.066917,48.967759 L 37.671807,49.073671 L 37.671807,49.820127 C 37.671807,49.820127 32.255457,50.252157 30.049301,49.93109 C 27.843157,49.610006 27.440747,49.608286 27.440747,49.608286 L 27.242258,49.820127 L 27.143021,50.783455 L 27.643946,50.783455 L 27.84242,54.959544 L 38.976091,54.530844 L 38.172728,68.980747 L 38.073481,70.796442 L 28.645781,70.261816 L 28.546544,66.408513 L 30.649462,66.408513 L 30.852673,64.910557 L 32.757107,64.481857 L 33.059555,64.058192 L 25.937975,62.343374 L 20.522364,63.947229 L 21.42496,64.698732 L 22.327572,64.698732 L 22.426809,65.984848 L 24.331254,66.09076 L 24.331254,69.838147 L 22.228335,70.372777 L 22.630009,71.225146 L 23.130934,71.547931 L 23.130934,74.437917 L 24.435218,74.437917 L 24.435218,87.813529 L 22.327572,88.13632 L 22.630009,91.989617 L 23.929569,92.206492 L 23.731093,100.98236 L 29.449141,101.08826 L 28.244105,92.418334 L 36.868446,92.206492 L 36.268285,96.912181 L 35.464925,100.23086 L 44.188501,100.33677 L 44.287739,91.777793 L 50.303506,91.243181 L 50.005786,96.700351 L 49.802585,99.90807 L 54.920484,99.90807 L 54.717274,91.132217 L 55.421397,91.243181 L 55.619882,87.067076 L 54.816521,87.067076 L 54.518798,85.352258 L 54.017874,80.429702 L 54.216359,74.760706 L 55.31743,74.760706 L 55.31743,71.336105 L 53.913913,71.442015 L 54.117112,67.402096 L 55.747469,67.240708 L 55.823083,65.929374 L 56.749319,65.793192 L 57.699176,65.071956 L 51.985842,63.896802 L 46.31977,65.15265 L 46.872668,66.060507 L 47.47283,66.010066 L 48.172228,65.984848 L 48.299828,67.639144 L 49.878196,67.563497 L 49.906548,71.144447 L 43.111042,70.988097 L 43.337879,67.160002 L 43.559978,63.679927 L 43.559978,59.105378 L 43.763188,54.288748 L 57.373101,53.592733 L 73.567955,52.659674 L 73.71917,55.736265 L 73.142647,63.120082 L 72.892183,69.9945 L 66.928387,69.888585 L 66.900039,65.071956 L 69.106918,64.991267 L 69.206169,63.629486 L 70.108765,63.493308 L 70.061506,63.226006 L 70.964116,63.175568 L 71.465028,62.504773 L 64.721507,60.926122 L 58.001612,62.368592 L 58.4789,63.200785 L 59.230285,63.1453 L 59.230285,63.523577 L 60.156518,63.523577 L 60.156518,65.046738 L 62.136575,65.071956 L 62.112937,69.298485 L 60.109259,69.298485 L 60.080907,70.261816 L 60.785031,70.342507 L 60.70942,74.009202 L 62.188552,74.089909 L 62.013701,88.620507 L 60.057282,89.018952 L 60.080907,89.714967 L 60.761406,89.714967 L 60.761406,93.437137 L 61.886113,93.437137 L 61.588391,98.52109 L 61.210343,102.95945 L 68.331912,103.14605 L 68.105084,99.29275 L 67.580538,96.085028 L 67.476575,93.300955 L 73.520696,93.195041 L 73.345845,97.502272 L 73.317494,102.05159 L 76.729426,102.3189 L 81.3653,102.1323 L 82.820807,101.70358 L 82.017437,99.26753 L 81.818959,95.439438 L 81.440912,92.710853 L 87.206218,92.499027 L 86.955759,95.842931 L 86.932133,101.08826 L 89.238253,101.30009 L 91.520751,101.24965 L 92.621828,100.90165 L 91.969693,95.923633 L 91.747577,92.176239 L 92.725793,92.070324 L 92.749427,88.726422 L 93.02352,88.670945 L 92.976244,87.949712 L 91.846823,87.949712 L 91.619996,85.488427 L 91.520751,74.811143 L 92.371377,74.785924 L 92.371377,71.280616 L 92.725793,71.336105 L 92.725793,70.640088 L 91.468773,70.529127 L 91.497126,66.463987 L 93.600043,66.277382 L 93.477182,64.910557 L 94.403419,64.829863 L 94.351424,64.562549 L 95.580099,63.947229 L 89.337489,62.69138 L 82.995657,63.977495 L 83.39733,64.723951 L 84.375543,64.643256 L 84.427528,64.966046 L 85.254515,64.966046 L 85.301775,66.569901 L 87.357445,66.544681 L 87.532293,70.478688 L 80.264217,70.423216 L 79.413593,64.512124 L 78.733106,61.380041 L 78.184923,55.761484 L 78.510996,52.473053 L 92.999878,51.373557 L 93.047136,46.476221 L 93.774891,46.289613 L 93.727651,45.543159 L 93.174743,45.220372 C 93.174629,45.220372 85.252181,46.395266 82.745197,46.66284 C 82.0389,46.738209 82.09239,46.733258 81.516524,46.79397 L 81.440912,45.886118 L 78.444837,44.317564 L 78.482644,42.491786 L 79.512842,42.461518 L 79.588444,39.949808 C 79.588444,39.949808 85.728225,39.546834 88.009582,39.0117 C 90.290937,38.476559 93.524432,37.942456 93.524432,37.942456 L 95.055545,33.79662 L 98.089437,32.913987 L 98.339888,32.217972 L 105.20628,30.316548 L 105.98602,29.676006 L 103.37744,23.976741 L 103.62792,22.690624 L 104.95584,21.994611 L 105.91041,19.079404 L 105.45673,18.675923 z M 72.466874,40.403728 L 72.429067,42.476654 L 73.983813,42.542211 L 73.884576,44.509221 L 70.836515,46.506487 L 70.647496,47.081457 L 71.876167,47.091543 L 71.866712,47.575729 L 62.552432,48.029652 L 62.613863,46.652742 L 63.039175,45.966809 L 63.067524,45.528025 L 63.07698,44.579832 L 63.341609,43.949374 L 63.440849,43.439982 L 63.440849,43.076841 L 63.842533,41.47297 L 72.466874,40.403728 z M 52.987688,42.168984 L 52.760853,43.561027 L 53.488599,44.418431 L 53.441349,45.916386 L 54.117112,46.960408 L 53.942262,48.191039 L 54.443185,48.912273 L 44.939872,49.2855 L 44.916247,48.967759 L 46.017333,48.831579 L 46.069307,48.428097 L 43.66394,47.121797 L 43.536351,45.03375 L 44.689411,44.978276 L 44.788661,42.72883 L 52.987688,42.168984 z M 67.051262,74.276518 L 72.81657,74.649742 L 72.618099,82.411833 L 73.36947,88.776857 L 67.254465,88.565018 L 67.051262,74.276518 z M 28.44258,74.599304 L 37.671807,75.078442 L 36.868446,80.429702 L 36.868446,84.928593 L 37.520583,87.440302 L 28.494569,87.869006 L 28.44258,74.599304 z M 87.508658,74.649742 L 87.508658,87.924488 L 81.644113,88.353194 L 81.440912,81.342592 L 80.788764,74.811143 L 87.508658,74.649742 z M 43.087416,74.947312 L 49.906548,74.972531 L 49.977434,87.278902 L 43.611966,87.389863 L 43.285891,83.400379 L 43.262266,79.441156 L 43.087416,74.947312 z "
+ id="path4735" />
+ </g>
+</svg>
--- /dev/null
+body {
+ background-color: #333;
+ margin: 0;
+ padding: 0;
+ font-family: 'Georgia', serif;
+ font-size: 15px;
+ color: #111;
+}
+
+#content {
+ background-color: white;
+ background-image: url(watermark.png);
+ padding: 10px;
+ margin: 25px;
+ border: 4px solid #ddd;
+}
+
+h1 {
+ margin: 0;
+ padding: 0;
+ height: 80px;
+ background-image: url(jinjabanner.png);
+ background-repeat: no-repeat;
+}
+
+h1 span {
+ display: none;
+}
+
+h2.subheading {
+ margin: -55px 0 35px 200px;
+ font-weight: normal;
+ font-size: 30px;
+ color: #444;
+}
+
+h2.plain {
+ margin: 0;
+}
+
+#jinjalogo {
+ background-image: url(jinjalogo.png);
+ background-repeat: no-repeat;
+ width: 400px;
+ height: 160px;
+}
+
+#contentwrapper {
+ max-width: 700px;
+ padding: 0 0 20px 18px;
+}
+
+#contentwrapper h3,
+#contentwrapper h3 a {
+ color: #b41717;
+ font-size: 26px;
+ margin: 20px 0 0 -5px;
+}
+
+#contentwrapper h4,
+#contentwrapper h4 a {
+ color: #b41717;
+ font-size: 20px;
+ margin: 20px 0 0 0;
+}
+
+table.docutils {
+ border-collapse: collapse;
+ border: 2px solid #aaa;
+ margin: 0.5em 1.5em 0.5em 1.5em;
+}
+
+table.docutils td {
+ padding: 2px;
+ border: 1px solid #ddd;
+}
+
+p, li, dd, dt, blockquote {
+ color: #333;
+}
+
+p {
+ line-height: 150%;
+ margin-bottom: 0;
+ margin-top: 10px;
+ text-align: justify;
+}
+
+hr {
+ border-top: 1px solid #ccc;
+ border-bottom: 0;
+ border-right: 0;
+ border-left: 0;
+ margin-bottom: 10px;
+ margin-top: 20px;
+}
+
+dl {
+ margin-left: 10px;
+}
+
+li, dt {
+ margin-top: 5px;
+}
+
+dt {
+ font-weight: bold;
+}
+
+th {
+ text-align: left;
+ padding: 3px;
+ background-color: #f2f2f2;
+}
+
+a {
+ color: #b41717;
+}
+
+a:hover {
+ color: #444;
+}
+
+pre {
+ background-color: #f9f9f9;
+ border-top: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ padding: 5px;
+ font-size: 13px;
+ font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace;
+}
+
+tt {
+ font-size: 13px;
+ font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace;
+ color: black;
+ padding: 1px 2px 1px 2px;
+ background-color: #f0f0f0;
+}
+
+cite {
+ /* abusing <cite>, it's generated by ReST for `x` */
+ font-size: 13px;
+ font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace;
+ font-weight: bold;
+ font-style: normal;
+}
+
+div.admonition {
+ margin: 10px 0 10px 0;
+ padding: 10px;
+ border: 1px solid #ccc;
+ background-color: #f8f8f8;
+}
+
+div.admonition p.admonition-title {
+ margin: -3px 0 5px 0;
+ font-weight: bold;
+ color: #b41717;
+ font-size: 16px;
+}
+
+div.admonition p {
+ margin: 0 0 0 40px;
+}
+
+#toc {
+ margin: 0 -10px 10px 15px;
+ padding: 10px;
+ width: 200px;
+ float: right;
+ background-color: #f8f8f8;
+ border: 1px solid #ccc;
+ border-right: none;
+}
+
+#toc h2 {
+ font-size: 20px;
+ margin: 0 0 10px 0;
+ padding: 0;
+ color: #444;
+}
+
+#toc ul {
+ margin: 0 0 0 30px;
+ padding: 0;
+}
+
+#toc ul + h2 {
+ margin-top: 10px;
+}
+
+#toc ul li {
+ padding: 0;
+ margin: 2px 0 2px 0;
+}
--- /dev/null
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+ Generate Jinja Documentation
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ Generates a bunch of html files containing the documentation.
+
+ :copyright: 2006-2007 by Armin Ronacher, Georg Brandl.
+ :license: BSD, see LICENSE for more details.
+"""
+import os
+import sys
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
+import re
+import inspect
+from datetime import datetime
+from cgi import escape
+
+from docutils import nodes
+from docutils.parsers.rst import directives
+from docutils.core import publish_parts
+from docutils.writers import html4css1
+
+from jinja import Environment
+
+from pygments import highlight
+from pygments.lexers import get_lexer_by_name
+from pygments.formatters import HtmlFormatter
+
+def generate_list_of_filters():
+ from jinja.filters import FILTERS
+ result = []
+
+ filters = {}
+ for name, f in FILTERS.iteritems():
+ if not f in filters:
+ filters[f] = ([name], inspect.getdoc(f))
+ else:
+ filters[f][0].append(name)
+ for names, _ in filters.itervalues():
+ names.sort(key=lambda x: -len(x))
+
+ for names, doc in sorted(filters.values(), key=lambda x: x[0][0].lower()):
+ name = names[0]
+ if len(names) > 1:
+ aliases = '\n\n :Aliases: %s\n' % ', '.join(names[1:])
+ else:
+ aliases = ''
+
+ doclines = []
+ for line in doc.splitlines():
+ doclines.append(' ' + line)
+ doc = '\n'.join(doclines)
+ result.append('`%s`\n%s%s' % (name, doc, aliases))
+
+ return '\n'.join(result)
+
+def generate_list_of_tests():
+ from jinja.tests import TESTS
+ result = []
+
+ tests = {}
+ for name, f in TESTS.iteritems():
+ if not f in tests:
+ tests[f] = ([name], inspect.getdoc(f))
+ else:
+ tests[f][0].append(name)
+ for names, _ in tests.itervalues():
+ names.sort(key=lambda x: -len(x))
+
+ for names, doc in sorted(tests.values(), key=lambda x: x[0][0].lower()):
+ name = names[0]
+ if len(names) > 1:
+ aliases = '\n\n :Aliases: %s\n' % ', '.join(names[1:])
+ else:
+ aliases = ''
+
+ doclines = []
+ for line in doc.splitlines():
+ doclines.append(' ' + line)
+ doc = '\n'.join(doclines)
+ result.append('`%s`\n%s%s' % (name, doc, aliases))
+
+ return '\n'.join(result)
+
+def generate_list_of_loaders():
+ from jinja import loaders as loader_module
+
+ result = []
+ loaders = []
+ for item in loader_module.__all__:
+ loaders.append(getattr(loader_module, item))
+ loaders.sort(key=lambda x: x.__name__.lower())
+
+ for loader in loaders:
+ doclines = []
+ for line in inspect.getdoc(loader).splitlines():
+ doclines.append(' ' + line)
+ result.append('`%s`\n%s' % (loader.__name__, '\n'.join(doclines)))
+
+ return '\n\n'.join(result)
+
+e = Environment()
+
+PYGMENTS_FORMATTER = HtmlFormatter(style='pastie', cssclass='syntax')
+
+LIST_OF_FILTERS = generate_list_of_filters()
+LIST_OF_TESTS = generate_list_of_tests()
+LIST_OF_LOADERS = generate_list_of_loaders()
+
+TEMPLATE = e.from_string('''\
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
+ "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+<head>
+ <title>{{ title }} — Jinja Documentation</title>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8">
+ <link rel="stylesheet" href="style.css" type="text/css">
+ <style type="text/css">
+ {{ style|e }}
+ </style>
+</head>
+<body>
+ <div id="content">
+ {% if file_id == 'index' %}
+ <div id="jinjalogo"></div>
+ <h2 class="subheading plain">{{ title }}</h2>
+ {% else %}
+ <h1 class="heading"><span>Jinja</span></h1>
+ <h2 class="subheading">{{ title }}</h2>
+ {% endif %}
+ {% if file_id != 'index' or toc %}
+ <div id="toc">
+ <h2>Navigation</h2>
+ <ul>
+ <li><a href="index.html">back to index</a></li>
+ </ul>
+ {% if toc %}
+ <h2>Contents</h2>
+ <ul class="contents">
+ {% for key, value in toc %}
+ <li><a href="{{ key }}">{{ value }}</a></li>
+ {% endfor %}
+ </ul>
+ {% endif %}
+ </div>
+ {% endif %}
+ <div id="contentwrapper">
+ {{ body }}
+ </div>
+ </div>
+</body>
+<!-- generated on: {{ generation_date }}
+ file id: {{ file_id }} -->
+</html>\
+''')
+
+def pygments_directive(name, arguments, options, content, lineno,
+ content_offset, block_text, state, state_machine):
+ try:
+ lexer = get_lexer_by_name(arguments[0])
+ except ValueError:
+ # no lexer found
+ lexer = get_lexer_by_name('text')
+ parsed = highlight(u'\n'.join(content), lexer, PYGMENTS_FORMATTER)
+ return [nodes.raw('', parsed, format="html")]
+pygments_directive.arguments = (1, 0, 1)
+pygments_directive.content = 1
+directives.register_directive('sourcecode', pygments_directive)
+
+
+def create_translator(link_style):
+ class Translator(html4css1.HTMLTranslator):
+ def visit_reference(self, node):
+ refuri = node.get('refuri')
+ if refuri is not None and '/' not in refuri and refuri.endswith('.txt'):
+ node['refuri'] = link_style(refuri[:-4])
+ html4css1.HTMLTranslator.visit_reference(self, node)
+ return Translator
+
+
+class DocumentationWriter(html4css1.Writer):
+
+ def __init__(self, link_style):
+ html4css1.Writer.__init__(self)
+ self.translator_class = create_translator(link_style)
+
+ def translate(self):
+ html4css1.Writer.translate(self)
+ # generate table of contents
+ contents = self.build_contents(self.document)
+ contents_doc = self.document.copy()
+ contents_doc.children = contents
+ contents_visitor = self.translator_class(contents_doc)
+ contents_doc.walkabout(contents_visitor)
+ self.parts['toc'] = self._generated_toc
+
+ def build_contents(self, node, level=0):
+ sections = []
+ i = len(node) - 1
+ while i >= 0 and isinstance(node[i], nodes.section):
+ sections.append(node[i])
+ i -= 1
+ sections.reverse()
+ toc = []
+ for section in sections:
+ try:
+ reference = nodes.reference('', '', refid=section['ids'][0], *section[0])
+ except IndexError:
+ continue
+ ref_id = reference['refid']
+ text = escape(reference.astext().encode('utf-8'))
+ toc.append((ref_id, text))
+
+ self._generated_toc = [('#%s' % href, caption) for href, caption in toc]
+ # no further processing
+ return []
+
+
+def generate_documentation(data, link_style):
+ writer = DocumentationWriter(link_style)
+ data = data.replace('[[list_of_filters]]', LIST_OF_FILTERS)\
+ .replace('[[list_of_tests]]', LIST_OF_TESTS)\
+ .replace('[[list_of_loaders]]', LIST_OF_LOADERS)
+ parts = publish_parts(
+ data,
+ writer=writer,
+ settings_overrides={
+ 'initial_header_level': 3,
+ 'field_name_limit': 50,
+ }
+ )
+ return {
+ 'title': parts['title'].encode('utf-8'),
+ 'body': parts['body'].encode('utf-8'),
+ 'toc': parts['toc']
+ }
+
+
+def handle_file(filename, fp, dst):
+ now = datetime.now()
+ title = os.path.basename(filename)[:-4]
+ content = fp.read()
+ parts = generate_documentation(content, (lambda x: './%s.html' % x))
+ result = file(os.path.join(dst, title + '.html'), 'w')
+ c = dict(parts)
+ c['style'] = PYGMENTS_FORMATTER.get_style_defs('.syntax')
+ c['generation_date'] = now
+ c['file_id'] = title
+ result.write(TEMPLATE.render(c).encode('utf-8'))
+ result.close()
+
+
+def run(dst, sources=()):
+ path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'src'))
+ if not sources:
+ sources = [os.path.join(path, fn) for fn in os.listdir(path)]
+ for fn in sources:
+ if not os.path.isfile(fn):
+ continue
+ print 'Processing %s' % fn
+ f = open(fn)
+ try:
+ handle_file(fn, f, dst)
+ finally:
+ f.close()
+
+
+def main(dst='build/', *sources):
+ return run(os.path.realpath(dst), sources)
+
+
+if __name__ == '__main__':
+ main(*sys.argv[1:])
--- /dev/null
+======================
+Designer Documentation
+======================
+
+This part of the Jinja documentaton is meant for template designers.
+
+Basics
+======
+
+The Jinja template language is designed to strike a balance between content
+and application logic. Nevertheless you can use a python like statement
+language. You don't have to know how Python works to create Jinja templates,
+but if you know it you can use some additional statements you may know from
+Python.
+
+Here is a small example template:
+
+.. sourcecode:: html+jinja
+
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
+ <head>
+ <title>My Webpage</title>
+ </head>
+ <body>
+ <ul id="navigation">
+ {% for item in navigation %}
+ <li><a href="{{ item.href|e }}">{{ item.caption|e }}</a></li>
+ {% endfor %}
+ </ul>
+
+ <h1>My Webpage</h1>
+ {{ variable }}
+ </body>
+ </html>
+
+This covers the default settings. The application developer might have changed
+the syntax from ``{% foo %}`` to ``<% foo %>`` or something similar. This
+documentation just covers the default values.
+
+A variable looks like ``{{ foobar }}`` where foobar is the variable name. Inside
+of statements (``{% some content here %}``) variables are just normal names
+without the braces around it. In fact ``{{ foobar }}`` is just an alias for
+the statement ``{% print foobar %}``.
+
+Variables are coming from the context provided by the application. Normally there
+should be a documentation regarding the context contents but if you want to know
+the content of the current context you can add this to your template:
+
+.. sourcecode:: html+jinja
+
+ <pre>{{ debug()|e }}</pre>
+
+A context isn't flat which means that each variable can has subvariables, as long
+as it is representable as python data structure. You can access attributes of
+a variable by using the dot and brace operators. The following examples show
+this:
+
+.. sourcecode:: jinja
+
+ {{ user.username }}
+ is the same as
+ {{ user['username'] }}
+ you can also use a variable to access an attribute:
+ {{ users[current_user].username }}
+ If you have numerical indices you have to use the [] syntax:
+ {{ users[0].username }}
+
+Filters
+=======
+
+In the examples above you might have noticed the pipe symbols. Pipe symbols tell
+the engine that it has to apply a filter on the variable. Here a small example:
+
+.. sourcecode:: jinja
+
+ {{ variable|replace('foo', 'bar')|escape }}
+
+If you like you can also put whitespace between the filters.
+
+This will look for a variable variable, passes it to the filter replace with the
+arguments ``'foo'`` and ``'bar'``, and passes the result to the filter `escape`
+that automatically xml escapes the value. The ``e`` filter is an alias for
+``escape``. Here the complete list of supported filters:
+
+[[list_of_filters]]
+
+Tests
+=====
+
+You can use the `is`-operator to perform tests on a value:
+
+.. sourcecode:: jinja
+
+ {{ 42 is numeric }} -> true
+ {{ "foobar" is numeric }} -> false
+ {{ 'FOO' is upper }} -> true
+
+Those tests are especially useful if used in `if`-conditions.
+
+[[list_of_tests]]
+
+Loops
+=====
+
+To iterate over a sequence you can use the `for`-loop. If basically looks like a
+normal python for loop and works pretty much the same:
+
+.. sourcecode:: html+jinja
+
+ <h1>Members</h1>
+ <ul>
+ {% for user in users %}
+ <li>{{ loop.index }} / {{ loop.length }} - {{ user.username|escape }}</li>
+ {% else %}
+ <li><em>no users found</em></li>
+ {% endfor %}
+ </ul>
+
+The optional ``else`` block is only executed if the template did not iterate
+because the sequence was empty.
+
+Inside of a for loop block you can access some special variables:
+
++----------------------+----------------------------------------+
+| Variable | Description |
++======================+========================================+
+| ``loop.index`` | The current iteration of the loop. |
++----------------------+----------------------------------------+
+| ``loop.index0`` | The current iteration of the loop, |
+| | starting counting by 0. |
++----------------------+----------------------------------------+
+| ``loop.revindex`` | The number of iterations from the end |
+| | of the loop. |
++----------------------+----------------------------------------+
+| ``loop.revindex0`` | The number of iterations from the end |
+| | of the loop, starting counting by 0. |
++----------------------+----------------------------------------+
+| ``loop.first`` | True if first iteration. |
++----------------------+----------------------------------------+
+| ``loop.last`` | True if last iteration. |
++----------------------+----------------------------------------+
+| ``loop.even`` | True if current iteration is even. |
++----------------------+----------------------------------------+
+| ``loop.odd`` | True if current iteration is odd. |
++----------------------+----------------------------------------+
+| ``loop.length`` | Total number of items in the sequence. |
++----------------------+----------------------------------------+
+| ``loop.parent`` | The context of the parent loop. |
++----------------------+----------------------------------------+
+
+Loops also support recursion. For example you have a sitemap where each item
+might have a number of child items. Such a template could look like this:
+
+.. sourcecode:: html+jinja
+
+ <h1>Sitemap
+ <ul id="sitemap">
+ {% for item in sitemap recursive %}
+ <li><a href="{{ item.url|e }}">{{ item.title|e }}</a>
+ {% if item.children %}<ul>{{ loop(item.children) }}</ul>{% endif %}</li>
+ {% endfor %}
+ </ul>
+
+Now. What happens here? Basically the first thing that is different to a normal
+loop is the additional ``recursive`` modifier in the `for`-loop declaration.
+It tells the template engine that we want recursion. If recursion is enabled
+the special loop variable is callable. If you call it with a sequence it will
+automatically render that loop at that position with the new sequence as argument.
+
+Cycling
+=======
+
+Sometimes you might want to have different classes for each row in a list. For
+example to have alternating row colors. You can easily do this by using the
+``{% cycle %}`` tag:
+
+.. sourcecode:: html+jinja
+
+ <ul id="messages">
+ {% for message in messages %}
+ <li class="{% cycle 'row1', 'row2' %}">{{ message|e }}</li>
+ {% endfor %}
+ </ul>
+
+Each time Jinja encounters a cycle tag it will evaluate cycle through the list
+of given items and return the next one. If you pass it one item jinja assumes
+that this item is a sequence from the context and uses this:
+
+.. sourcecode:: html+jinja
+
+ <li style="color: {% cycle rowcolors %}">...</li>
+
+Conditions
+==========
+
+Jinja supports python like ``if`` / ``elif`` / ``else`` constructs:
+
+.. sourcecode:: jinja
+
+ {% if user.active %}
+ user {{ user.name|e }} is active.
+ {% elif user.deleted %}
+ user {{ user.name|e }} was deleted some time ago.
+ {% else %}
+ i don't know what's wrong with {{ user.username|e }}
+ {% endif %}
+
+If the user is active the first block is rendered. If not and the user was
+deleted the second one, in all other cases the third one.
+
+You can also use comparison operators:
+
+.. sourcecode:: html+jinja
+
+ {% if amount < 0 %}
+ <span style="color: red">{{ amount }}</span>
+ {% else %}
+ <span style="color: black">{{ amount }}</span>
+ {% endif %}
+
+.. admonition:: Note
+
+ Of course you can use `or` / `and` and parenthesis to create more complex
+ conditions but usually the logic is already handled in the application and
+ you don't have to create such complex constucts in the template code. However
+ in some situations it might be a good thing to have the abilities to create
+ them.
+
+Operators
+=========
+
+Inside ``{{ variable }}`` blocks, `if`-conditions and many other parts you can
+can use Expressions. In expressions you can use any of the following operators:
+
+ ======= ===================================================================
+ ``+`` add the right operand to the left one.
+ ``{{ 1 + 2 }}`` would return three.
+ ``-`` substract the right operand from the left one.
+ ``{{ 1 - 1 }}`` would return zero.
+ ``/`` divide the right from the left operand.
+ ``{{ 1 / 2 }}`` would return 0.5
+ ``*`` multiply the left operand with the right.
+ ``{{ 2 * 2}}`` would return 4
+ ``**`` raise the left operand to the power of the right
+ operand. ``{{ 2**3 }}`` would return 8
+ ``is`` perform a test on the value. See the section about
+ tests for more information.
+ ``|`` apply a filter on the value. See the section about
+ filters for more information.
+ ``and`` return true if the left and the right operand is true.
+ ``or`` return true if the left or the right operand is true.
+ ``()`` call a callable. ``{{ user.get_username() }}``. Inside of the
+ parenthesis you can use variables: ``{{ user.get('username') }}``.
+ ======= ===================================================================
+
+Note that there is no support for any bit operation or something similar.
+
+Macros
+======
+
+If you want to use a partial template on more than one place you might want to
+create a macro out of it:
+
+.. sourcecode:: html+jinja
+
+ {% macro show_user user %}
+ <h1>{{ user.name|e }}</h1>
+ <div class="test">
+ {{ user.description }}
+ </div>
+ {% endmacro %}
+
+Now you can use it from everywhere in the code by passing it an item:
+
+.. sourcecode:: jinja
+
+ {% for user in users %}
+ {{ show_user(user) }}
+ {% endfor %}
+
+You can also specify more then one value:
+
+.. sourcecode:: html+jinja
+
+ {% macro show_dialog title, text %}
+ <div class="dialog">
+ <h1>{{ title|e }}</h1>
+ <div class="test">{{ text|e }}</div>
+ </div>
+ {% endmacro %}
+
+ {{ show_dialog('Warning', 'something went wrong i guess') }}
+
+Inheritance
+===========
+
+The most powerful part of Jinja is template inheritance. Template inheritance
+allows you to build a base "skeleton" template that contains all the common
+elements of your site and defines **blocks** or **markers** that child
+templates can override.
+
+Sounds complicated but is very basic. It's easiest to understand it by starting
+with an example.
+
+Base Template
+-------------
+
+This template, which we'll call ``base.html``, defines a simple HTML skeleton
+document that you might use for a simple two-column page. It's the job of
+"child" templates to fill the empty blocks with content:
+
+.. sourcecode:: html+jinja
+
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <link rel="stylesheet" href="style.css" />
+ <title>{% block title %}{% endblock %} - My Webpage</title>
+ {% block html_head %}{% endblock %}
+ </head>
+ <body>
+ <div id="content">
+ {% block content %}{% endblock %}
+ </div>
+
+ <div id="footer">
+ {% block "footer" %}
+ © Copyright 2006 by <a href="http://mydomain.tld">myself</a>.
+ {% endblock %}
+ </div>
+ </body>
+
+In this example, the ``{% block %}`` tags define four blocks that child templates
+can fill in. All the ``block`` tag does is to tell the template engine that a
+child template may override those portions of the template.
+
+Child Template
+--------------
+
+A child template might look like this:
+
+.. sourcecode:: html+jinja
+
+ {% extends "base.html" %}
+ {% block title %}Index{% endblock %}
+
+ {% block html_head %}
+ <style type="text/css">
+ .important {
+ color: #336699;
+ }
+ </style>
+ {% endblock %}
+
+ {% block content %}
+ <h1>Index</h1>
+ <p class="important">
+ Welcome on my awsome homepage.
+ </p>
+ {% endblock %}
+
+The ``{% extends %}`` tag is the key here. It tells the template engine that
+this template "extends" another template. When the template system evaluates
+this template, first it locates the parent.
+
+The filename of the template depends on the template loader. For example the
+``FileSystemLoader`` allows you to access other templates by giving the
+filename. You can access subdirectory with an slash:
+
+.. sourcecode:: jinja
+
+ {% extends "layout/default.html" %}
+
+But this behavior can depend on the application using Jinja.
+
+Note that since the child template didn't define the ``footer`` block, the
+value from the parent template is used instead.
+
+.. admonition:: Note
+
+ You can't define multiple ``{% block %}`` tags with the same name in the
+ same template. This limitation exists because a block tag works in "both"
+ directions. That is, a block tag doesn't just provide a hole to fill - it
+ also defines the content that fills the hole in the *parent*. If there were
+ two similarly-named ``{% block %}`` tags in a template, that template's
+ parent wouldn't know which one of the blocks' content to use.
+
+Template Inclusion
+==================
+
+You can load another template at a given posiiton using ``{% include %}``.
+Usually it's a better idea to use inheritance but if you for example want to
+load macros ``include`` works better than ``extends``:
+
+.. sourcecode:: jinja
+
+ {% include "myhelpers.html" %}
+ {{ my_helper("foo") }}
+
+If you define a macro called ``my_helper`` in ``myhelpers.html`` you can now
+use it from the template as shown above.
+
+Filtering Blocks
+================
+
+Sometimes it could be a good idea to filter a complete block. For example if
+you want to escape some html code:
+
+.. sourcecode:: jinja
+
+ {% filter escape %}
+ <html>
+ <code>goes here</code>
+ </html>
+ {% endfilter %}
+
+Of course you can chain filters too.
+
+Defining Variables
+==================
+
+You can also define variables in the namespace using the ``{% set %}`` tag:
+
+.. sourcecode:: jinja
+
+ {% set foo = 'foobar' %}
+ {{ foo }}
+
+This should ouputput ``foobar``.
--- /dev/null
+====================
+Developer Quickstart
+====================
+
+This part of the documentation shows you how to embedd Jinja into your
+application.
+
+Starting Up
+===========
+
+Here the quickest way to create a template from a string and render it:
+
+.. sourcecode:: python
+
+ from jinja import Environment
+ env = Environment()
+ tmpl = env.from_string('Hello {{ name }}!')
+ print tmpl.render(name='John Doe')
+
+This example should output the following string after execution::
+
+ Hello John Doe!
+
+If you receive an error check if you have a typo in your code. If not have
+a look at the `installation`_ page for troubleshooting.
+
+The Environment
+===============
+
+The core component of Jinja is the `Environment`. It helds important shared
+variables like configuration, filters, tests, globals and other stuff.
+
+Here the possible initialisation parameters:
+
+=========================== ==================================================
+``block_start_string`` * the string marking the begin of a block. this
+ defaults to ``'{%'``.
+``block_end_string`` * the string marking the end of a block. defaults
+ to ``'%}'``.
+``variable_start_string`` * the string marking the begin of a print
+ statement. defaults to ``'{{'``.
+``comment_start_string`` * the string marking the begin of a
+ comment. defaults to ``'{#'``.
+``comment_end_string`` * the string marking the end of a comment.
+ defaults to ``'#}'``.
+``trim_blocks`` * If this is set to ``True`` the first newline
+ after a block is removed (block, not
+ variable tag!). Defaults to ``False``.
+``auto_escape`` If this is set to ``True`` Jinja will
+ automatically escape all variables using xml
+ escaping methods. If you don't want to escape a
+ string you have to wrap it in a ``Markup``
+ object from the ``jinja.datastructure`` module.
+``template_charset`` The charset of the templates. Defaults
+ to ``'utf-8'``.
+``charset`` Charset of all string input data. Defaults
+ to ``'utf-8'``.
+``namespace`` Global namespace for all templates.
+``loader`` Specify a template loader.
+``filters`` dict of filters or the default filters if not
+ defined.
+``tests`` dict of tests of the default tests if not defined.
+=========================== ==================================================
+
+All of this variables except those marked with a star(*) are modifyable after
+environment initialisation.
+
+The environment provides the following useful functions and properties
+additional to the initialisation values:
+
+=========================== ==================================================
+``parse(source, filename)`` Parse the sourcecode and return the abstract
+ syntax tree. This tree of nodes is used by the
+ `translators`_ to convert the template into
+ executable source- or bytecode.
+``from_string(source)`` Load and parse a template source and translate it
+ into evaluable python code. This code is wrapped
+ with in a `Template` class that allows you to
+ render it.
+``get_template(name)`` load a template from a loader. If the template
+ does not exist you will get a `TemplateNotFound`
+ exception.
+=========================== ==================================================
+
+There are also some internal functions on the environment used by the template
+evaluation code to keep it sandboxed.
+
+Loading Templates From Files
+============================
+
+Loading templates from a string is always a bad idea. It doesn't allow template
+inheritance and is also slow since it parses and compiles the template again
+and again whereas loaders can cache the template code.
+
+All you have to do is to define a loader and use the `get_template` function.
+
+.. sourcecode:: python
+
+ from jinja import Environment, FileSystemLoader
+ env = Environment(loader=FileSystemLoader('templates'))
+ tmpl = env.get_template('index.html')
+ print tmpl.render(name='John Doe')
+
+This tells jinja to look for templates in the ``templates`` folder. It's a
+better idea to use an absolute path here though. For a list of supported
+loaders or how to write your own, head over to the `loader`_ documentation.
+
+Adding Filters
+==============
+
+If you want to add additional filters to the environment the best way is to
+modify the ``filters`` attribute and not to pass a dict to the environment.
+If you pass it a dict it will not include the default filters!
+
+.. sourcecode:: python
+
+ from mylib import my_cool_filter
+ env.filters['mycoolfilter'] = my_cool_filter
+
+Writing filter functions is explained in the `filter development`_ section.
+
+Adding Tests
+============
+
+Adding additional tests works analog to filters:
+
+.. sourcecode:: python
+
+ from mylib import my_cool_test
+ env.tests['mycooltest'] = my_cool_test
+
+Writing tests is explained in the `test development`_ section.
+
+
+.. _installation: installation.txt
+.. _translators: translators.txt
+.. _loader: loaders.txt
+.. _filter development: filters.txt
+.. _test development: tests.txt
--- /dev/null
+===============================
+Differences To Django Templates
+===============================
+
+If you have previously worked with Django templates you should feel very
+familiar. In fact most of the syntax elements look and work the same.
+
+However Jinja provides some more syntax elements covered in the documentation
+and some work a bit different.
+
+Method Calls
+============
+
+In Django method calls work implicit. With Jinja you have to tell it that you
+want to call it. Thus this Django code:
+
+.. sourcecode:: django
+
+ {% for page in user.get_created_pages %}
+ ...
+ {% endfor %}
+
+will look like this in Jinja:
+
+.. sourcecode:: jinja
+
+ {% for page in user.get_created_pages() %}
+ ...
+ {% endfor %}
+
+This allows you to pass variables to the function which is also used for
+macros and loop recursion, both features that don't exist in Django.
+
+Conditions
+==========
+
+In Django you can use the following constructs to check for equality:
+
+.. sourcecode:: django
+
+ {% ifequals foo "bar" %}
+ ...
+ {% else %}
+ ...
+ {% endifequals %}
+
+In Jinja you can use the normal ``if`` statement in combination with
+operators:
+
+.. sourcecode:: jinja
+
+ {% if foo == 'bar' %}
+ ...
+ {% else %}
+ ...
+ {% endif %}
+
+You can also have multiple ``elif`` branches in your template:
+
+.. sourcecode:: jinja
+
+ {% if something %}
+ ...
+ {% elif otherthing %}
+ ...
+ {% elif foothing %}
+ ...
+ {% else %}
+ ...
+ {% endif %}
+
+Filter Arguments
+================
+
+Jinja provides more than one argument for a filter. Also the syntax for argument
+passing is different. A template that looks like this in Django:
+
+.. sourcecode:: django
+
+ {{ items|join:", " }}
+
+looks like this in jinja:
+
+.. sourcecode:: jinja
+
+ {{ items|join(', ') }}
+
+In fact it's a bit more to write but it allows different type of arguments including
+variables and more then one of them.
+
+Tests
+=====
+
+Additionally to filters there also exists tests you can perform using the `is`
+operator. Here some examples:
+
+.. sourcecode:: jinja
+
+ {% if user.user_id is odd %}
+ {{ user.username|e }} is odd
+ {% else %}
+ hmm. {{ user.username|e }} looks pretty normal
+ {% endif %}
+
+For a list of supported tests head over to the `syntax reference`_.
+
+
+.. _syntax reference: designerdoc.txt
--- /dev/null
+======================
+Documentation Overview
+======================
+
+Welcome in the Jinja documentation.
+
+- `Installing Jinja <installation.txt>`_
+
+- Application Developer Documentation:
+
+ - `Quickstart <devintro.txt>`_
+
+ - `Template Loaders <loaders.txt>`_
+
+ - `Filter Functions <filters.txt>`_
+
+ - `Test Functions <tests.txt>`_
+
+ - `Translators <translators.txt>`_
+
+- Template Designer Documentation:
+
+ - `Syntax Reference <designerdoc.txt>`_
+
+ - `Differences To Django <fromdjango.txt>`_
--- /dev/null
+================
+Template Loaders
+================
+
+This part of the documentation explains how to use and write a template loader.
+
+Builtin Loaders
+===============
+
+[[list_of_loaders]]
:license: BSD, see LICENSE for more details.
"""
from jinja.environment import Environment
-from jinja.loaders import FileSystemLoader
-
-__all__ = ['Environment', 'FileSystemLoader']
+from jinja.loaders import *
auto_escape option values marked as `Markup` aren't escaped.
"""
+ def __repr__(self):
+ return 'Markup(%s)' % unicode.__repr__(self)
+
safe_types = set([Markup, int, long, float])
Get or set the template loader.
"""
self._loader = LoaderWrapper(self, value)
- loader = property(lambda s: s._loader, loader, loader.__doc__)
+ loader = property(lambda s: s._loader.loader, loader, loader.__doc__)
- def parse(self, source):
+ def parse(self, source, filename=None):
"""Function that creates a new parser and parses the source."""
- parser = Parser(self, source)
+ parser = Parser(self, source, filename)
return parser.parse()
def from_string(self, source):
from random import choice
from urllib import urlencode, quote
from jinja.utils import escape
+from jinja.datastructure import Undefined
try:
nargs[idx] = env.to_unicode(var)
return f(env.to_unicode(value), *nargs)
return wrapped
+ try:
+ decorator.__doc__ = f.__doc__
+ decorator.__name__ = f.__name__
+ except:
+ pass
return decorator
def do_replace(s, old, new, count=None):
"""
- {{ s|replace(old, new, count=None) }}
+ Return a copy of the value with all occurrences of a substring
+ replaced with a new one. The first argument is the substring
+ that should be replaced, the second is the replacement string.
+ If the optional third argument ``count`` is given, only the first
+ ``count`` occurrences are replaced:
+
+ .. sourcecode:: jinja
- Return a copy of s with all occurrences of substring
- old replaced by new. If the optional argument count is
- given, only the first count occurrences are replaced.
+ {{ "Hello World"|replace("Hello", "Goodbye") }}
+ -> Goodbye World
+
+ {{ "aaaaargh"|replace("a", "d'oh, ", 2) }}
+ -> d'oh, d'oh, aaargh
"""
if count is None:
return s.replace(old, new)
def do_upper(s):
"""
- {{ s|upper }}
-
- Return a copy of s converted to uppercase.
+ Convert a value to uppercase.
"""
return s.upper()
do_upper = stringfilter(do_upper)
def do_lower(s):
"""
- {{ s|lower }}
-
- Return a copy of s converted to lowercase.
+ Convert a value to lowercase.
"""
return s.lower()
do_lower = stringfilter(do_lower)
def do_escape(s, attribute=False):
"""
- {{ s|escape(attribute) }}
+ XML escape ``&``, ``<``, and ``>`` in a string of data. If the
+ optional parameter is `true` this filter will also convert
+ ``"`` to ``"``. This filter is just used if the environment
+ was configured with disabled `auto_escape`.
- XML escape &, <, and > in a string of data. If attribute is
- True it also converts ``"`` to ``"``
+ This method will have no effect it the value is already escaped.
"""
return escape(s, attribute)
do_escape = stringfilter(do_escape)
def do_addslashes(s):
"""
- {{ s|addslashes }}
-
- Adds slashes to s.
+ Add backslashes in front of special characters to s. This method
+ might be useful if you try to fill javascript strings. Also have
+ a look at the `jsonencode` filter.
"""
return s.encode('utf-8').encode('string-escape').decode('utf-8')
do_addslashes = stringfilter(do_addslashes)
def do_capitalize(s):
"""
- {{ s|capitalize }}
-
- Return a copy of the string s with only its first character
- capitalized.
+ Capitalize a value. The first character will be uppercase, all others
+ lowercase.
"""
return s.capitalize()
do_capitalize = stringfilter(do_capitalize)
def do_title(s):
"""
- {{ s|title }}
-
- Return a titlecased version of s, i.e. words start with uppercase
- characters, all remaining cased characters have lowercase.
+ Return a titlecased version of the value. I.e. words will start with
+ uppercase letters, all remaining characters are lowercase.
"""
return s.title()
do_title = stringfilter(do_title)
-def do_default(default_value=u''):
+def do_default(default_value=u'', boolean=False):
"""
- {{ s|default(default_value='') }}
+ If the value is undefined it will return the passed default value,
+ otherwise the value of the variable:
+
+ .. sourcecode:: jinja
+
+ {{ my_variable|default('my_variable is not defined') }}
- In case of s isn't set or True default will return default_value
- which is '' per default.
+ This will output the value of ``my_variable`` if the variable was
+ defined, otherwise ``'my_variable is not defined'``. If you want
+ to use default with variables that evaluate to false you have to
+ set the second parameter to `true`:
+
+ .. sourcecode:: jinja
+
+ {{ ''|default('the string was empty', true) }}
"""
- return lambda e, c, v: v or default_value
+ def wrapped(env, context, value):
+ if (boolean and not v) or v in (Undefined, None):
+ return default_value
+ return v
+ return wrapped
do_default = stringfilter(do_default)
def do_join(d=u''):
"""
- {{ sequence|join(d='') }}
-
Return a string which is the concatenation of the strings in the
- sequence. The separator between elements is d which is an empty
- string per default.
+ sequence. The separator between elements is an empty string per
+ default, you can define ith with the optional parameter:
+
+ .. sourcecode:: jinja
+
+ {{ [1, 2, 3]|join('|') }}
+ -> 1|2|3
+
+ {{ [1, 2, 3]|join }}
+ -> 123
"""
def wrapped(env, context, value):
d = env.to_unicode(d)
def do_count():
"""
- {{ var|count }}
-
- Return the length of var. In case if getting an integer or float
+ Return the length of the value. In case if getting an integer or float
it will convert it into a string an return the length of the new
- string.
- If the object doesn't provide a __len__ function it will return zero
+ string. If the object has no length it will of corse return 0.
"""
def wrapped(env, context, value):
try:
return wrapped
-def do_odd():
- """
- {{ var|odd }}
-
- Return true if the variable is odd.
- """
- return lambda e, c, v: v % 2 == 1
-
-
-def do_even():
- """
- {{ var|even }}
-
- Return true of the variable is even.
- """
- return lambda e, c, v: v % 2 == 0
-
-
def do_reversed():
"""
- {{ var|reversed }}
+ Return a reversed list of the sequence filtered. You can use this
+ for example for reverse iteration:
+
+ .. sourcecode:: jinja
- Return a reversed list of the iterable filtered.
+ {% for item in seq|reversed %}
+ {{ item|e }}
+ {% endfor %}
"""
def wrapped(env, context, value):
try:
def do_center(value, width=80):
"""
- {{ var|center(80) }}
-
Centers the value in a field of a given width.
"""
return value.center(width)
do_center = stringfilter(do_center)
-def do_title(value):
- """
- {{ var|title }}
-
- Capitalize the first character of all words.
- """
- return value.title()
-do_title = stringfilter(do_title)
-
-
-def do_capitalize(value):
- """
- {{ var|capitalize }}
-
- Capitalize the first character of the string.
- """
- return value.capitalize()
-do_capitalize = stringfilter(do_capitalize)
-
-
def do_first():
"""
- {{ var|first }}
-
- Return the frist item of a sequence or None.
+ Return the frist item of a sequence.
"""
def wrapped(env, context, seq):
try:
return iter(seq).next()
except StopIteration:
- return
+ return Undefined
return wrapped
def do_last():
"""
- {{ var|last }}
-
Return the last item of a sequence.
"""
def wrapped(env, context, seq):
try:
return iter(_reversed(seq)).next()
except (TypeError, StopIteration):
- return
+ return Undefined
return wrapped
def do_random():
"""
- {{ var|random }}
-
Return a random item from the sequence.
"""
def wrapped(env, context, seq):
try:
return choice(seq)
except:
- return
+ return Undefined
return wrapped
def do_urlencode():
"""
- {{ var|urlencode }}
+ urlencode a string or directory.
- {{ {'foo': 'bar'}|urlencode }}
+ .. sourcecode:: jinja
- urlencode a string or directory.
+ {{ {'foo': 'bar', 'blub': 'blah'}|urlencode }}
+ -> foo=bar&blub=blah
+
+ {{ 'Hello World' }}
+ -> Hello%20World
"""
def wrapped(env, context, value):
if isinstance(value, dict):
def do_jsonencode():
"""
- {{ var|jsonencode }}
-
JSON dump a variable. just works if simplejson is installed.
+
+ .. sourcecode:: jinja
+
+ {{ 'Hello World'|jsonencode }}
+ -> "Hello World"
"""
global simplejson
try:
'default': do_default,
'join': do_join,
'count': do_count,
- 'odd': do_odd,
- 'even': do_even,
'reversed': do_reversed,
'center': do_center,
'title': do_title,
from jinja.exceptions import TemplateNotFound
+__all__ = ['FileSystemLoader']
+
+
def get_template_filename(searchpath, name):
"""
Return the filesystem filename wanted.
class FileSystemLoader(object):
"""
- Loads templates from the filesystem::
+ Loads templates from the filesystem:
+
+ .. sourcecode:: python
from jinja import Environment, FileSystemLoader
e = Environment(loader=FileSystemLoader('templates/'))
+
+ You can pass the following keyword arguments to the loader on
+ initialisation:
+
+ =================== =================================================
+ ``searchpath`` String with the path to the templates on the
+ filesystem.
+ ``use_cache`` Set this to ``True`` to enable memory caching.
+ This is usually a good idea in production mode,
+ but disable it during development since it won't
+ reload template changes automatically.
+ This only works in persistent environments like
+ FastCGI.
+ ``cache_size`` Number of template instance you want to cache.
+ Defaults to ``40``.
+ =================== =================================================
"""
def __init__(self, searchpath, use_cache=False, cache_size=40):
def get_items(self):
result = []
- for test in tests:
+ for test in self.tests:
result.extend(test)
- result.append(self._else)
+ result.append(self.else_)
return result
def __repr__(self):
def test_odd():
"""
- {{ var is odd }}
-
- Return True if the variable is odd.
+ Return true if the variable is odd.
"""
return lambda e, c, v: v % 2 == 1
def test_even():
"""
- {{ var is even }}
-
- Return True of the variable is even.
+ Return true of the variable is even.
"""
return lambda e, c, v: v % 2 == 0
def test_defined():
"""
- {{ var is defined }}
+ Return true if the variable is defined:
+
+ .. sourcecode:: jinja
- Return True if the variable is defined.
+ {% if variable is defined %}
+ value of variable: {{ variable }}
+ {% else %}
+ variable is not defined
+ {% endif %}
+
+ See also the ``default`` filter.
"""
return lambda e, c, v: v is not Undefined
def test_lower():
"""
- {{ var is lower }}
-
- Return True if the variable is lowercase.
+ Return true if the variable is lowercase.
"""
return lambda e, c, v: isinstance(v, basestring) and v.islower()
def test_upper():
"""
- {{ var is upper }}
-
- Return True if the variable is uppercase.
+ Return true if the variable is uppercase.
"""
return lambda e, c, v: isinstance(v, basestring) and v.isupper()
def test_numeric():
"""
- {{ var is numeric }}
-
- Return True if the variable is numeric.
+ Return true if the variable is numeric.
"""
return lambda e, c, v: isinstance(v, (int, long, float)) or (
isinstance(v, basestring) and
def test_sequence():
"""
- {{ var is sequence }}
-
- Return True if the variable is a sequence.
+ Return true if the variable is a sequence. Sequences are variables
+ that are iterable.
"""
def wrapped(environment, context, value):
try:
def test_matching(regex):
"""
- {{ var is matching('\d+$') }}
-
Test if the variable matches the regular expression
given. If the regular expression is a string additional
slashes are automatically added, if it's a compiled regex
- it's used without any modifications.
+ it's used without any modifications:
+
+ .. sourcecode:: jinja
+
+ {% if var is matching('\d+$') %}
+ var looks like a number
+ {% else %}
+ var doesn't really look like a number
+ {% endif %}
"""
if isinstance(regex, unicode):
regex = re.compile(regex.encode('unicode-escape'), re.U)
handle foo or bar.
"""
return ' or '.join([
- self.handle_node(n) for n in self.nodse
+ self.handle_node(n) for n in node.nodes
])
def handle_not(self, node):
:license: BSD, see LICENSE for more details.
"""
import re
-from jinja.datastructure import safe_types
+from jinja.datastructure import safe_types, Markup
_escape_pairs = {
"""
if type(x) in safe_types:
return x
- return _escape_res[not attribute].sub(lambda m: _escape_pairs[m.group()],
- unicode(x))
+ return Markup(_escape_res[not attribute].sub(lambda m:
+ _escape_pairs[m.group()], unicode(x)))