<!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" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>SimpleExampleEgg</title>
<meta name="Generator" content="Vim reStructured Text b20 - Vim7.0" />
<meta name="Author" content="Todd Greenwood" />
<meta name="Title" content="SimpleExampleEgg" />
<meta name="Keywords" content="python, egg, setuptools, distutils, tutorial, doctests, unit tests, deploy, install, example" />
<meta name="Date" content="Dec 2005" />
<link rel="stylesheet" href="default.css" type="text/css" />
<link rel="stylesheet" href="oldstyle.css" type="text/css" />
</head>
<body>
<h1 id="lsimpleexampleegg">SimpleExampleEgg</h1>
<table class="field" summary="Field list"><tr><td class="fkey">Author:</td><td class="fval"> Todd Greenwood</td></tr>
<tr><td class="fkey">Title:</td><td class="fval"> SimpleExampleEgg</td></tr>
<tr><td class="fkey">Keywords:</td><td class="fval"> python, egg, setuptools, distutils, tutorial, doctests, unit tests, deploy, install, example</td></tr>
<tr><td class="fkey">Version:</td><td class="fval"> 0.1</td></tr>
<tr><td class="fkey">License:</td><td class="fval"> GPL v. 2</td></tr>
<tr><td class="fkey">Date:</td><td class="fval"> Dec 2005</td></tr>
</table>
<p>
This aims to be a super simple example of python, setuptools, docutils, unit-tests, and eggs.
</p>
<span id="tocheader" class="toc">Contents</span>
<ul class="toc">
<li class="h1"><a href="#lsimpleexampleegg">SimpleExampleEgg</a></li>
<li class="h2"><a href="#lintroduction">Introduction</a></li>
<li class="h2"><a href="#lbasic-source-files">Basic Source Files</a></li>
<li class="h2"><a href="#ltests-and-building">Tests and Building</a></li>
<li class="h3"><a href="#lbuilding-the-rest-docs">Building the REST docs</a></li>
<li class="h2"><a href="#lbacktracking">Backtracking</a></li>
<li class="h3"><a href="#lthe-ez-setup46py-file">The ez_setup.py file</a></li>
<li class="h3"><a href="#lcreate-a-pipy-account58">Create a PiPy account:</a></li>
<li class="h2"><a href="#lapp-deployment">App Deployment</a></li>
<li class="h3"><a href="#lscript-generation">Script Generation</a></li>
<li class="h3"><a href="#lbuilding">Building</a></li>
<li class="h3"><a href="#ldeploy-locally-40dev-mode41">Deploy Locally (Dev Mode)</a></li>
<li class="h3"><a href="#ldeploy-to-pipy">Deploy To pipy</a></li>
<li class="h2"><a href="#lend-user-app-install">End User App Install</a></li>
<li class="h3"><a href="#luser-install">User Install</a></li>
<li class="h3"><a href="#luser-run-unit-tests">User Run Unit Tests</a></li>
<li class="h2"><a href="#lconclusion">Conclusion</a></li>
</ul>
<!--.. comment:: end of toc -->
<h2 id="lintroduction">Introduction</h2>
<p>
Hopefully, this will clarify, in my own mind, at least, how to make super simple apps that have all sorts of cool extras such as working unit tests, launch scripts, deploy and install flawlessly...etc. Ideally, I'll be able to write apps that 'just work'. So my end users can just point some tool (easy_install) at a file (an egg) and voila, a working app.
</p>
<p>
Here's what I'd like to have happen for this exercise:
</p>
<ul class="circle">
<li><p class="onlyli"> user clicks a link</p></li>
<li><p class="firstli"> app downloads and installs</p></li>
<li><p class="onlyli"> user runs the app's internal test suite</p>
</li>
</ul>
<p>
Along the way, I'll play around with:
</p>
<ul class="circle">
<li><p class="firstli"> deploying the app locally, and to pipy</p></li>
<li><p class="firstli"> user may recieve the app via email, a web link, file copy, etc.</p></li>
<li><p class="onlyli"> doctests and unit tests</p>
</li>
</ul>
<p>
Note, this documentation was written using Mikolaj Machowski's
Vim reStructured Text plugin (<a href="http://skawina.eu.org/mikolaj/vst.html" title="vst">vst</a>). It is the coolest thing since the
'intraweb'. Look at the source 'readme.rest' and the generated 'readme.html'.
The code listings and sample code output were generated as part of the build,
very little 'copy and paste' here.
</p>
<h2 id="lbasic-source-files">Basic Source Files</h2>
<ol class="decimal">
<li><p class="firstli">dir listing:</p>
<blockquote>
<p>
<pre>
apple.py
docs
__init__.py
orange.py
simpletests.py
</pre>
</p>
</blockquote>
</li>
<li><p class="firstli">apple.py:</p>
<blockquote>
<p>
<pre>
import sys
def doConsole(): print make_pie('CONSOLE')
def make_pie(who):
        """
>>> import apple as a
>>> a.make_pie('Todd')
'Todd likes pie!!!'
        """
        return '%s likes pie!!!'%who
#
def _test():
        import doctest
        return doctest.testmod()
#
if __name__ == "__main__":
_test()
if len(sys.argv)>1: print make_pie(sys.argv[1])
</p>
</pre>
</blockquote>
</li>
<li><p class="firstli">Let's run this tiny app to see what we get:</p>
<blockquote>
<p>
<code>$ python apple.py Mr.Guido</code>
</p>
<p>
<pre>
Mr.Guido likes pie!!!
</pre>
</p>
</blockquote>
</li>
<li><p class="firstli">setup.py:</p>
<blockquote>
<p>
<pre>
from ez_setup import use_setuptools
use_setuptools()
from setuptools import setup, find_packages
#
setup(name = "SimpleExampleEgg",
version = "0.1",
description = "test",
author = "Todd Greenwood",
author_email = "t.greenwoodgeer@gmail.com",
        entry_points = {'console_scripts': [
                'make_apple_pie = fruit.apple:doConsole'
                ]},
packages = find_packages(exclude=['ez_setup'] ),
        package_data = {'':['docs/*.html', 'docs/*.rest','docs/*.vim']},
test_suite = 'fruit.simpletests.getTestSuite',
license = "GNU Lesser General Public License",
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
"Programming Language :: Python",
"Topic :: Utilities",
],
zip_safe=True,
)
</p>
</pre>
</blockquote>
</li>
<li><p class="firstli">simpletests.py:</p>
<blockquote>
<p>
<pre>
import apple
import unittest
import doctest
#
def getTestSuite():
        suite = unittest.TestSuite()
        for mod in apple,:
         suite.addTest(doctest.DocTestSuite(mod))
        return suite
#
runner = unittest.TextTestRunner()
runner.run(getTestSuite())
</p>
</pre>
</blockquote>
</li></ol>
<h2 id="ltests-and-building">Tests and Building</h2>
<ol class="decimal">
<li><p class="firstli">Let's run the test suite:</p>
<blockquote>
<p>
<code>$ python simpletests.py</code>
</p>
<p>
<pre>
.
----------------------------------------------------------------------
Ran 1 test in 0.007s
OK
</pre>
</p>
</blockquote>
</li>
<li><p class="firstli">Now, let's run the test suite from setuptools:</p>
<blockquote>
<p>
<code>$ python setup.py test</code>
</p>
<p>
<pre>
running test
running egg_info
writing ./SimpleExampleEgg.egg-info/PKG-INFO
writing top-level names to ./SimpleExampleEgg.egg-info/top_level.txt
writing entry points to ./SimpleExampleEgg.egg-info/entry_points.txt
running build_ext
.
----------------------------------------------------------------------
Ran 1 test in 0.008s
OK
Doctest: fruit.apple.make_pie ... ok
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
</pre>
</p>
</blockquote>
</li>
<li><p class="firstli">Next, let's make sure the build system works. But first, I should mention
that this documentation is generated via a vim pluggin.</p>
</li></ol>
<h3 id="lbuilding-the-rest-docs">Building the REST docs</h3>
<ol class="decimal">
<li><p class="firstli">So, while from a command line you would execute:</p>
<blockquote>
<p>
<code>$ python setup bdist_egg</code>
</p>
<p>
Because this doc lives in ./fruit/docs/readme.rest, I will use the pbu
utility to walk the directory tree and launch setup.py. If you would like
to build the docs, here is the process:
</p>
<ol class="loweralpha">
<li><p class="firstli">install pbu:</p>
<pre>
$ easy_install buildutils
</pre>
</li>
<li><p class="firstli">build the egg using the 'pbu' utility:</p>
<pre>
$ cd ./fruit/docs
$ pbu dbist_egg
</pre>
</li></ol>
</blockquote>
</li>
<li><p class="firstli">So that's exactly what the docs do. So, to see all this in action, build
the docs :</p>
<pre>
$ vim readme.rest -s runvim.vim
BTW - runvim.vim is just a quickie script to execute :Vsti within vim,
write, save, and exit vim.
</pre>
</li>
<li><p class="firstli">Back to the build system, let's do a build :</p>
<blockquote>
<p>
<pre>
running bdist_egg
running egg_info
writing ./SimpleExampleEgg.egg-info/PKG-INFO
writing top-level names to ./SimpleExampleEgg.egg-info/top_level.txt
writing entry points to ./SimpleExampleEgg.egg-info/entry_points.txt
installing library code to build/bdist.linux-i686/egg
running install_lib
warning: install_lib: 'build/lib' does not exist -- no Python modules to install
creating build/bdist.linux-i686/egg
creating build/bdist.linux-i686/egg/EGG-INFO
copying ./SimpleExampleEgg.egg-info/PKG-INFO -> build/bdist.linux-i686/egg/EGG-INFO
copying ./SimpleExampleEgg.egg-info/top_level.txt -> build/bdist.linux-i686/egg/EGG-INFO
copying ./SimpleExampleEgg.egg-info/entry_points.txt -> build/bdist.linux-i686/egg/EGG-INFO
creating 'dist/SimpleExampleEgg-0.1-py2.4.egg' and adding 'build/bdist.linux-i686/egg' to it
removing 'build/bdist.linux-i686/egg' (and everything under it)
</pre>
</p>
</blockquote>
</li>
<li><p class="firstli">And lastly, let's check to make sure that we have what we expect in the
output:</p>
<blockquote>
<p>
``unzip -l /home/tgreenwo/active/SimpleExampleEgg/dist/SimpleExampleEgg-0.1-py2.4.egg'``
</p>
<p>
<pre>
Archive: /home/tgreenwo/active/SimpleExampleEgg/dist/SimpleExampleEgg-0.1-py2.4.egg
Length Date Time Name
-------- ---- ---- ----
290 12-14-05 12:11 fruit/orange.py
330 12-14-05 15:33 fruit/apple.py
240 12-14-05 12:26 fruit/simpletests.py
0 12-14-05 12:13 fruit/__init__.py
556 12-14-05 16:24 fruit/orange.pyc
754 12-14-05 16:24 fruit/apple.pyc
537 12-14-05 16:24 fruit/simpletests.pyc
124 12-14-05 16:24 fruit/__init__.pyc
18896 12-14-05 14:54 fruit/docs/readme.html
8768 12-14-05 16:02 fruit/docs/readme.rest
141 12-14-05 12:36 fruit/docs/vstrc.vim
14 12-14-05 14:32 fruit/docs/runvim.vim
489 12-14-05 16:24 EGG-INFO/PKG-INFO
6 12-14-05 16:24 EGG-INFO/top_level.txt
58 12-14-05 16:24 EGG-INFO/entry_points.txt
0 12-14-05 16:24 EGG-INFO/zip-safe
-------- -------
31203 16 files
</pre>
</p>
</blockquote>
</li></ol>
<h2 id="lbacktracking">Backtracking</h2>
<p>
Some things I should mention....
</p>
<ul class="circle">
<li><p class="firstli"> apply the svn 'trick' to get the ez_setup file</p></li>
<li><p class="firstli"> get a freebie ftp account to test uploading to</p></li>
<li><p class="onlyli"> create a pipy account so that you can upload there, too</p>
</li>
</ul>
<h3 id="lthe-ez-setup46py-file">The ez_setup.py file</h3>
<ol class="decimal">
<li><p class="firstli">Per the <a href="http://peak.telecommunity.com/DevCenter/setuptools" title="Peak">Peak</a> website, install ez_setup.py as an svn external thingy. Here are the steps:</p>
<pre>
$ cd SimpleExampleEgg/
$ svn propedit svn:externals .
# enter this in the editor:
ez_setup svn://svn.eby-sarna.com/svnroot/ez_setup
# slurp down the latest ez_setup file
$ svn update
# you should see the ez_setup dir now..., if you don't, try
$ svn propget svn:externals
ez_setup svn://svn.eby-sarna.com/svnroot/ez_setup
</pre>
</li>
<li><p class="firstli">It would be nice to be able to create a project that just housed the
ez_setup project, and then just reference that project in all my other
projects. However, apparently svn does not support chained external
dependencies. So each project will either have to have a copy of
ez_setup.py, or this external reference.</p>
</li></ol>
<h3 id="lcreate-a-pipy-account58">Create a PiPy account:</h3>
<ol class="decimal">
<li><p class="firstli">this part is easy:</p>
<pre>
$ python setup.py register
</pre>
</li>
<li><p class="firstli">now just follow the instructions..:</p>
<pre>
running register
We need to know who you are, so please choose either:
1. use your existing login,
2. register as a new user,
3. have the server generate a new password for you (and email it to you),
or
4. quit
Your selection [default 1]:
</pre>
</li>
<li><p class="firstli">you'll need your username and password the first time you deploy.
subsequent deploys will store your log in info somewhere in your home dir</p>
</li></ol>
<h2 id="lapp-deployment">App Deployment</h2>
<p>
Now, we want to do your basic 'app' stuff:
</p>
<ul class="circle">
<li><p class="firstli"> script generation</p></li>
<li><p class="firstli"> build</p></li>
<li><p class="onlyli"> deploy</p></li>
<li><p class="firstli"> test</p>
</li>
</ul>
<h3 id="lscript-generation">Script Generation</h3>
<p>
The idea here is that if I have some cool command line app, then how do I
access it when it's all tucked inside an egg? The generated wrapper scripts do
just that.
</p>
<ol class="decimal">
<li><p class="onlyli">add entry points to setup.py:</p>
<pre>
setup(
...
entry_points = {'console_scripts': [
'make_apple_pie = fruit.apple:make_pie'
]},
</pre>
</li>
<li><p class="firstli">note that you cannot pass command line vars to the method:</p>
<pre>
tgreenwo@luxor~/active/SimpleExampleEgg$ make_apple_pie
Traceback (most recent call last):
File "/usr/bin/make_apple_pie", line 7, in ?
sys.exit(
TypeError: make_pie() takes exactly 1 argument (0 given)
tgreenwo@luxor~/active/SimpleExampleEgg$ python make_apple_pie
python: can't open file 'make_apple_pie': [Errno 2] No such file or
directory
tgreenwo@luxor~/active/SimpleExampleEgg$ which make_apple_pie
/usr/bin/make_apple_pie
tgreenwo@luxor~/active/SimpleExampleEgg$ cat /usr/bin/make_apple_pie
#!/home/tgreenwo/bin/python
# EASY-INSTALL-ENTRY-SCRIPT:
# 'SimpleExampleEgg==0.1','console_scripts','make_apple_pie'
__requires__ = 'SimpleExampleEgg==0.1'
import sys
from pkg_resources import load_entry_point
sys.exit(
load_entry_point('SimpleExampleEgg==0.1', 'console_scripts',
'make_apple_pie')()
)
</pre>
</li>
<li><p class="firstli">add a method to apple.py w/o params:</p>
<pre>
#apple.py
def doConsole(): print make_pie('CONSOLE')
#setup.py
entry_points = {'console_scripts': [
'make_apple_pie = fruit.apple:doConsole'
]},
</pre>
</li>
<li><p class="firstli">install (as root) again</p>
<blockquote>
<p>
<code>easy_install dist/SimpleExampleEgg-0.1-py2.4.egg</code>
</p>
</blockquote>
</li>
<li><p class="firstli">and run again:</p>
<pre>
tgreenwo@luxor~/active/SimpleExampleEgg$ /usr/bin/make_apple_pie
CONSOLE likes pie!!!
</pre>
</li></ol>
<h3 id="lbuilding">Building</h3>
<p>
We did this already, but, we'll do it again here.
</p>
<ol class="decimal">
<li><p class="firstli">Build the app into an egg:</p>
<blockquote>
<p>
<code>$ python setup bdist_egg</code>
</p>
<p>
<pre>
running bdist_egg
running egg_info
writing ./SimpleExampleEgg.egg-info/PKG-INFO
writing top-level names to ./SimpleExampleEgg.egg-info/top_level.txt
writing entry points to ./SimpleExampleEgg.egg-info/entry_points.txt
installing library code to build/bdist.linux-i686/egg
running install_lib
warning: install_lib: 'build/lib' does not exist -- no Python modules to install
creating build/bdist.linux-i686/egg
creating build/bdist.linux-i686/egg/EGG-INFO
copying ./SimpleExampleEgg.egg-info/PKG-INFO -> build/bdist.linux-i686/egg/EGG-INFO
copying ./SimpleExampleEgg.egg-info/top_level.txt -> build/bdist.linux-i686/egg/EGG-INFO
copying ./SimpleExampleEgg.egg-info/entry_points.txt -> build/bdist.linux-i686/egg/EGG-INFO
creating 'dist/SimpleExampleEgg-0.1-py2.4.egg' and adding 'build/bdist.linux-i686/egg' to it
removing 'build/bdist.linux-i686/egg' (and everything under it)
</pre>
</p>
</blockquote>
</li>
<li><p class="firstli">And lastly, let's check to make sure that we have what we expect:</p>
<blockquote>
<p>
<code>$ unzip -l /home/tgreenwo/active/SimpleExampleEgg/dist/SimpleExampleEgg-0.1-py2.4.egg</code>
</p>
<p>
<pre>
Archive: /home/tgreenwo/active/SimpleExampleEgg/dist/SimpleExampleEgg-0.1-py2.4.egg
Length Date Time Name
-------- ---- ---- ----
290 12-14-05 12:11 fruit/orange.py
330 12-14-05 15:33 fruit/apple.py
240 12-14-05 12:26 fruit/simpletests.py
0 12-14-05 12:13 fruit/__init__.py
556 12-14-05 16:24 fruit/orange.pyc
754 12-14-05 16:24 fruit/apple.pyc
537 12-14-05 16:24 fruit/simpletests.pyc
124 12-14-05 16:24 fruit/__init__.pyc
18896 12-14-05 14:54 fruit/docs/readme.html
8768 12-14-05 16:02 fruit/docs/readme.rest
141 12-14-05 12:36 fruit/docs/vstrc.vim
14 12-14-05 14:32 fruit/docs/runvim.vim
489 12-14-05 16:24 EGG-INFO/PKG-INFO
6 12-14-05 16:24 EGG-INFO/top_level.txt
58 12-14-05 16:24 EGG-INFO/entry_points.txt
0 12-14-05 16:24 EGG-INFO/zip-safe
-------- -------
31203 16 files
</pre>
</p>
</blockquote>
</li></ol>
<h3 id="ldeploy-locally-40dev-mode41">Deploy Locally (Dev Mode)</h3>
<ol class="decimal">
<li><p class="firstli">create a deployment directory to house the egg links:</p>
<pre>
$ cd working
$ mkdir deploy
</pre>
</li>
<li><p class="firstli">add the 'deploy' directory to your pythonpath:</p>
<blockquote>
<p>
<pre>
                export PYTHONPATH=/home/tgreenwo/working/twisted:/home/tgreenwo/working/django:/home/tgreenwo/working/deploy
</pre>
</p>
</blockquote>
</li>
<li><p class="firstli">install the egg locally in dev mode:</p>
<blockquote>
<p>
<code>$ python setup.py develop</code>
</p>
<p>
<pre>
running develop
running egg_info
writing ./SimpleExampleEgg.egg-info/PKG-INFO
writing top-level names to ./SimpleExampleEgg.egg-info/top_level.txt
writing entry points to ./SimpleExampleEgg.egg-info/entry_points.txt
running build_ext
Creating /usr/lib/python2.4/site-packages/SimpleExampleEgg.egg-link (link to .)
error: /usr/lib/python2.4/site-packages/SimpleExampleEgg.egg-link: Permission denied
</pre>
</p>
</blockquote>
</li>
<li><p class="firstli">this time, we specify the deploy directory:</p>
<blockquote>
<p>
<code>$ python setup.py develop -d /home/tgreenwo/working/deploy</code>
</p>
<p>
<pre>
running develop
running egg_info
writing ./SimpleExampleEgg.egg-info/PKG-INFO
writing top-level names to ./SimpleExampleEgg.egg-info/top_level.txt
writing entry points to ./SimpleExampleEgg.egg-info/entry_points.txt
running build_ext
Creating /home/tgreenwo/data/working/deploy/SimpleExampleEgg.egg-link (link to .)
Installing make_apple_pie script to /home/tgreenwo/working/deploy
Installed /home/tgreenwo/active/SimpleExampleEgg/fruit/docs
Because this distribution was installed --multi-version or --install-dir,
before you can import modules from this package in an application, you
will need to 'import pkg_resources' and then use a 'require()' call
similar to one of these examples, in order to select the desired version:
pkg_resources.require("SimpleExampleEgg") # latest installed version
pkg_resources.require("SimpleExampleEgg==0.1") # this exact version
pkg_resources.require("SimpleExampleEgg>=0.1") # this version or higher
Processing dependencies for SimpleExampleEgg==0.1
</pre>
</p>
</blockquote>
</li></ol>
<h3 id="ldeploy-to-pipy">Deploy To pipy</h3>
<ol class="decimal">
<li><p class="firstli">build and deploy in one step:</p>
<pre>
$ setup.py register sdist bdist_egg upload
</pre>
</li>
<li><p class="firstli">I had problems, so I enabled the following:</p>
<pre>
$ python setup.py register sdist bdist_egg upload --show-response
</pre>
</li>
<li><p class="firstli">Once you noodle thru all the errors for your log in, password, and whether
all the meta information like url and topic are set, then the egg will
upload.</p>
</li></ol>
<h2 id="lend-user-app-install">End User App Install</h2>
<p>
General steps are:
</p>
<ul class="circle">
<li><p class="firstli"> su or sudo</p></li>
<li><p class="firstli"> easy_install [egg name] or</p></li>
<li><p class="onlyli"> easy_install [pipy project name]</p></li>
<li><p class="firstli"> run unit tests</p>
</li>
</ul>
<h3 id="luser-install">User Install</h3>
<ol class="decimal">
<li><p class="onlyli">Simple. One step:</p>
<pre>
root$ easy_install SimpleExampleEgg
Searching for SimpleExampleEgg
Reading http://www.python.org/pypi/SimpleExampleEgg/
Best match: SimpleExampleEgg 0.1
Downloading
http://cheeseshop.python.org/packages/2.4/S/SimpleExampleEgg/SimpleExampleEgg-0.1-py2.4.egg#md5=d53e38ea496e03d30b632dc08265128b
Processing SimpleExampleEgg-0.1-py2.4.egg
Moving SimpleExampleEgg-0.1-py2.4.egg to /usr/lib/python2.4/site-packages
Adding SimpleExampleEgg 0.1 to easy-install.pth file
Installing make_apple_pie script to /usr/bin
Installed /usr/lib/python2.4/site-packages/SimpleExampleEgg-0.1-py2.4.egg
Processing dependencies for SimpleExampleEgg
</pre>
</li>
<li><p class="firstli">done</p>
</li></ol>
<h3 id="luser-run-unit-tests">User Run Unit Tests</h3>
<ol class="decimal">
<li><p class="firstli">this should run the unit tests for the installed app:</p>
<pre>
$ python -c "from unittest import main; main(None)"
fruit.simpletests.getTestSuite
</pre>
</li>
<li><p class="firstli">let's try that:</p>
<blockquote>
<p>
<code>python -c 'from unittest import main; main(None)'
fruit.simpletests.getTestSuite</code>
</p>
<p>
<pre>
.
----------------------------------------------------------------------
Ran 1 test in 0.007s
OK
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
</pre>
</p>
</blockquote>
</li></ol>
<h2 id="lconclusion">Conclusion</h2>
<ol class="decimal">
<li><p class="firstli">vim + vst rocks</p></li>
<li><p class="firstli">eggs are cool</p>
</li></ol>
<!-- .. links:
-->
</body>
</html>