Refreshing Documentation
 An Introduction to Dexy

       Ana Nelson

           dexy.it


       July 12, 2011
Dexy for Web Apps




• Install Guide
Dexy for Web Apps




• Install Guide
• User Guide
Dexy for Web Apps




• Install Guide
• User Guide
• Developer Docs
The Big Idea




No Dead Code
  • Any code you show comes from a live, runnable file.
  • Any images or output you show comes from running live code.
Benefits




• Correctness
• Maintainability
• Workflow
Tool for the job



Dexy
 • Open Source (mostly MIT, some AGPL)
 • Written in Python
 • Command Line, Text Based
 • * Agnostic
 • My Day Job and my Mission in Life
Demo


What we want to create:
  • Install Guide
  • User Guide
  • Developer Docs
What we need:
  • An App!
  • Install Script
  • Watir Script
An App




web.py todo list app http://webpy.org/src/todo-list/0.3
DB Schema




CREATE TABLE todo (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT
);
model.py


import web

db = web.database(dbn= ’ sqlite ’ , db= ’ todo.sqlite3 ’ )

def get_todos():
    return db.select( ’ todo ’ , order= ’ id ’ )

def new_todo(text):
    db.insert( ’ todo ’ , title=text)

def del_todo(id):
    db.delete( ’ todo ’ , where= " id=$id " , vars=locals())
base.html


$def with (page)

<html>
<head>
    <title>Todo list</title>
</head>
<body>

$:page

</body>
</html>
index.html
$def with (todos, form)

<table>
    <tr>
        <th>What to do ?</th>
        <th></th>
    </tr>
$for todo in todos:
    <tr>
        <td>$todo.title</td>
        <td>
            <form action= "/del/$todo.id" method= "post" >
                <input type= "submit" value= "Delete" />
            </form>
        </td>
    </tr>
</table>

<form action= "" method= "post" >
$:form.render()
</form>
todo.py



 """ Basic todo list using webpy 0.3 """
import web
import model

urls = (
     ’ / ’ , ’ Index ’ ,
    ’ /del/(  d+) ’ , ’ Delete ’
)

render = web.template.render( ’ templates ’ , base= ’ base ’ )
todo.py




class Index:

    form = web.form.Form(
        web.form.Textbox( ’ title ’ , web.form.notnull,
            description= " I need to: " , size=75),
        web.form.Button( ’ Add todo ’ ),
    )
todo.py




def GET(self):
     """ Show page """
    todos = model.get_todos()
    form = self.form()
    return render.index(todos, form)
todo.py



def POST(self):
     """ Add new entry """
    form = self.form()
    if not form.validates():
        todos = model.get_todos()
        return render.index(todos, form)
    model.new_todo(form.d.title)
    raise web.seeother( ’ / ’ )
todo.py




class Delete:

    def POST(self, id):
         """ Delete based on ID """
        id = int(id)
        model.del_todo(id)
        raise web.seeother( ’ / ’ )
todo.py




app = web.application(urls, globals())

if __name__ == ’ __main__ ’ :
    app.run()
install script




Install Script
install script




apt-get update
apt-get upgrade -y --force-yes

apt-get install -y python-webpy
apt-get install -y mercurial
apt-get install -y sqlite3
install script




hg clone https://bitbucket.org/ananelson/dexy-examples
cd dexy-examples
cd webpy

sqlite3 todo.sqlite3 < schema.sql

python todo.py
install script




export UBUNTU_AMI= "ami-06ad526f" # natty
cd ~/.ec2
ec2run $UBUNTU_AMI -k $EC2_KEYPAIR 
    -t t1.micro -f ~/dev/dexy-examples/webpy/ubuntu-install.sh
(Make sure to allow access to port 8080 in security group.)
Now What




• We have an app and we have it running.
• We have an install script which we can use to create an install guide.
• Now we need a script to show how the app works.
Watir


• Watir lets us automate the web browser
• Can be integrated with functional tests
• For extra awesomeness, let’s use Watir to take screenshots
watir




require ’rubygems’
require ’safariwatir’

IP_ADDRESS = ENV[ ’EC2_INSTANCE_IP’ ]
PORT = ’8080’
BASE = " http:// #{ IP_ADDRESS } : #{ PORT } / "
watir



We create a reference to the browser:
browser = Watir::Safari.new
And define a helper method to take screenshots:
def take_screenshot(filename)
    sleep(1) # Make sure page is finished loading.
     ‘ screencapture #{ filename } ‘
      ‘ convert -crop 800x500+0+0   #{ filename }    #{ filename } ‘
end
watir

Now we’re ready to go!
browser.goto(BASE)
take_screenshot( " dexy--index.png " )
watir

Let’s enter a TODO:
browser.text_field( :name , " title " ).set( " Prepare Refresh Austin Talk Demo
take_screenshot( " dexy--enter.png " )
watir
Click the ”Add todo” button to add it. We’ll verify that it was actually added.
browser.button( :name , " Add todo " ).click
raise unless browser.html.include?( " <td>Prepare Refresh Austin Talk Demo</td>
take_screenshot( " dexy--add.png " )
watir

And delete it again:
browser.form( :index , 1).submit
take_screenshot( " dexy--delete.png " )
• Now we have screenshots we can use in our documentation, and which
  we can update any time.
• We also know that the steps described in our screenshots WORK.
• Note that we are also validating our install script.
• You will want to return your DB to its original state within your script, or
  have a reset method in your app.

Refresh Austin - Intro to Dexy

  • 1.
    Refreshing Documentation AnIntroduction to Dexy Ana Nelson dexy.it July 12, 2011
  • 2.
    Dexy for WebApps • Install Guide
  • 3.
    Dexy for WebApps • Install Guide • User Guide
  • 4.
    Dexy for WebApps • Install Guide • User Guide • Developer Docs
  • 5.
    The Big Idea NoDead Code • Any code you show comes from a live, runnable file. • Any images or output you show comes from running live code.
  • 6.
  • 7.
    Tool for thejob Dexy • Open Source (mostly MIT, some AGPL) • Written in Python • Command Line, Text Based • * Agnostic • My Day Job and my Mission in Life
  • 8.
    Demo What we wantto create: • Install Guide • User Guide • Developer Docs What we need: • An App! • Install Script • Watir Script
  • 9.
    An App web.py todolist app http://webpy.org/src/todo-list/0.3
  • 10.
    DB Schema CREATE TABLEtodo ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT );
  • 11.
    model.py import web db =web.database(dbn= ’ sqlite ’ , db= ’ todo.sqlite3 ’ ) def get_todos(): return db.select( ’ todo ’ , order= ’ id ’ ) def new_todo(text): db.insert( ’ todo ’ , title=text) def del_todo(id): db.delete( ’ todo ’ , where= " id=$id " , vars=locals())
  • 12.
    base.html $def with (page) <html> <head> <title>Todo list</title> </head> <body> $:page </body> </html>
  • 13.
    index.html $def with (todos,form) <table> <tr> <th>What to do ?</th> <th></th> </tr> $for todo in todos: <tr> <td>$todo.title</td> <td> <form action= "/del/$todo.id" method= "post" > <input type= "submit" value= "Delete" /> </form> </td> </tr> </table> <form action= "" method= "post" > $:form.render() </form>
  • 14.
    todo.py """ Basictodo list using webpy 0.3 """ import web import model urls = ( ’ / ’ , ’ Index ’ , ’ /del/( d+) ’ , ’ Delete ’ ) render = web.template.render( ’ templates ’ , base= ’ base ’ )
  • 15.
    todo.py class Index: form = web.form.Form( web.form.Textbox( ’ title ’ , web.form.notnull, description= " I need to: " , size=75), web.form.Button( ’ Add todo ’ ), )
  • 16.
    todo.py def GET(self): """ Show page """ todos = model.get_todos() form = self.form() return render.index(todos, form)
  • 17.
    todo.py def POST(self): """ Add new entry """ form = self.form() if not form.validates(): todos = model.get_todos() return render.index(todos, form) model.new_todo(form.d.title) raise web.seeother( ’ / ’ )
  • 18.
    todo.py class Delete: def POST(self, id): """ Delete based on ID """ id = int(id) model.del_todo(id) raise web.seeother( ’ / ’ )
  • 19.
    todo.py app = web.application(urls,globals()) if __name__ == ’ __main__ ’ : app.run()
  • 20.
  • 21.
    install script apt-get update apt-getupgrade -y --force-yes apt-get install -y python-webpy apt-get install -y mercurial apt-get install -y sqlite3
  • 22.
    install script hg clonehttps://bitbucket.org/ananelson/dexy-examples cd dexy-examples cd webpy sqlite3 todo.sqlite3 < schema.sql python todo.py
  • 23.
    install script export UBUNTU_AMI="ami-06ad526f" # natty cd ~/.ec2 ec2run $UBUNTU_AMI -k $EC2_KEYPAIR -t t1.micro -f ~/dev/dexy-examples/webpy/ubuntu-install.sh (Make sure to allow access to port 8080 in security group.)
  • 24.
    Now What • Wehave an app and we have it running. • We have an install script which we can use to create an install guide. • Now we need a script to show how the app works.
  • 25.
    Watir • Watir letsus automate the web browser • Can be integrated with functional tests • For extra awesomeness, let’s use Watir to take screenshots
  • 26.
    watir require ’rubygems’ require ’safariwatir’ IP_ADDRESS= ENV[ ’EC2_INSTANCE_IP’ ] PORT = ’8080’ BASE = " http:// #{ IP_ADDRESS } : #{ PORT } / "
  • 27.
    watir We create areference to the browser: browser = Watir::Safari.new And define a helper method to take screenshots: def take_screenshot(filename) sleep(1) # Make sure page is finished loading. ‘ screencapture #{ filename } ‘ ‘ convert -crop 800x500+0+0 #{ filename } #{ filename } ‘ end
  • 28.
    watir Now we’re readyto go! browser.goto(BASE) take_screenshot( " dexy--index.png " )
  • 29.
    watir Let’s enter aTODO: browser.text_field( :name , " title " ).set( " Prepare Refresh Austin Talk Demo take_screenshot( " dexy--enter.png " )
  • 30.
    watir Click the ”Addtodo” button to add it. We’ll verify that it was actually added. browser.button( :name , " Add todo " ).click raise unless browser.html.include?( " <td>Prepare Refresh Austin Talk Demo</td> take_screenshot( " dexy--add.png " )
  • 31.
    watir And delete itagain: browser.form( :index , 1).submit take_screenshot( " dexy--delete.png " )
  • 32.
    • Now wehave screenshots we can use in our documentation, and which we can update any time. • We also know that the steps described in our screenshots WORK. • Note that we are also validating our install script. • You will want to return your DB to its original state within your script, or have a reset method in your app.