diff -Nru nevow-0.9.31/debian/changelog nevow-0.10.0/debian/changelog --- nevow-0.9.31/debian/changelog 2010-04-12 12:25:16.000000000 +0100 +++ nevow-0.10.0/debian/changelog 2010-03-02 20:34:20.000000000 +0000 @@ -1,18 +1,76 @@ -nevow (0.9.31-4ubuntu2) jaunty; urgency=low +nevow (0.10.0-1) unstable; urgency=low - * Fix FTBFS with python2.6 (lp: #345860): - + Added debian/patches/python_libdir.diff - Use the correct *-packages directory for python2.6 + * New upstream release. + * Bump Standards-Version to 3.8.4. + * Switch to 3.0 (quilt) format. + * Remove dependency on python-all-dev and depends on python instead. + * Add a gigantic patch to add missing doc (svn-buildpackage does not + support multiple orig tarballs yet). Missing doc is grabbed from SVN + (everything matching **/*.{tpl,xhtml}). + * In the above patch, alter glossary.xhtml to use H2 tag instead of + H3. This closes: #564375. + * Fix an FTBFS with Python 2.6 because of the way unittests are + run. Also fix a bad interaction between CDBS, setuptools and Python + 2.6 in debian/rules. + * Remove lintian overrides: not needed anymore. + + -- Vincent Bernat Tue, 02 Mar 2010 20:37:21 +0100 + +nevow (0.9.33-5) unstable; urgency=low + + * Also regenerate dropin.cache for nevow specific plugins on postinst. + + -- Vincent Bernat Fri, 24 Apr 2009 22:24:05 +0200 + +nevow (0.9.33-4) unstable; urgency=low + + * Don't ship dropin.cache. Closes: #525034. + + -- Vincent Bernat Tue, 21 Apr 2009 21:14:21 +0200 + +nevow (0.9.33-3) unstable; urgency=low + + * Regenerate cache after python-central pkginstall has been + invoked. Closes: #521663. + + -- Vincent Bernat Tue, 31 Mar 2009 07:38:38 +0200 - -- Michael Bienia Fri, 20 Mar 2009 12:46:22 +0100 +nevow (0.9.33-2) unstable; urgency=low -nevow (0.9.31-4ubuntu1) jaunty; urgency=low + * python-central does not like file that are moving out; move them + before invoking it. - * debian/patches/remove_deprecated_with: Added. Remove deprecated function - with (no callees) - "with" has become a reserved keyword in python 2.6, - which caused this package to fail to install. (LP: #332053) + -- Vincent Bernat Fri, 20 Mar 2009 23:21:41 +0100 + +nevow (0.9.33-1) unstable; urgency=low + + * New upstream release. + * Change the way we run tests to use an helper that will ensure that we + use nevow as it will be installed. + * Compile documentation with lore. + + -- Vincent Bernat Fri, 20 Mar 2009 21:52:53 +0100 + +nevow (0.9.32-2) unstable; urgency=low + + * Don't try to byte-compile modules with python << 2.4. Closes: #519750. + * Bump Standard Version To 3.8.1. No changes. + + -- Vincent Bernat Sat, 14 Mar 2009 22:24:59 +0100 + +nevow (0.9.32-1) unstable; urgency=low + + [ Vincent Bernat ] + * New upstream version. + - Remove manual page fix for nevow-xmlgettext, applied upstream. + - Remove widget plugin fix, applied upstream. + - Remove dependency on quilt which is not needed anymore. + + [ Sandro Tosi ] + * debian/control + - switch Vcs-Browser field to viewsvn - -- Iain Lane Mon, 23 Feb 2009 18:56:15 +0000 + -- Vincent Bernat Sun, 15 Feb 2009 21:02:09 +0100 nevow (0.9.31-4) unstable; urgency=low diff -Nru nevow-0.9.31/debian/control nevow-0.10.0/debian/control --- nevow-0.9.31/debian/control 2010-04-12 12:25:16.000000000 +0100 +++ nevow-0.10.0/debian/control 2010-03-02 19:38:33.000000000 +0000 @@ -1,16 +1,15 @@ Source: nevow Section: python Priority: optional -Maintainer: Ubuntu Core Developers -XSBC-Original-Maintainer: Vincent Bernat +Maintainer: Vincent Bernat Uploaders: Debian Python Modules Team -Standards-Version: 3.8.0 -Build-Depends: debhelper (>= 5.0.38), cdbs (>= 0.4.49), python-all-dev (>= 2.3.5-11), python-central (>= 0.5.6), python-setuptools (>= 0.6b3), quilt, patchutils (>= 0.2.25) -Build-Depends-Indep: python-twisted-core (>= 2.4), python-twisted-web (>= 0.6) +Standards-Version: 3.8.4 +Build-Depends: debhelper (>= 5.0.38), cdbs (>= 0.4.49), python, python-central (>= 0.5.6), python-setuptools (>= 0.6b3) +Build-Depends-Indep: python-twisted-core (>= 2.4), python-twisted-web (>= 0.6), python-twisted-lore Vcs-Svn: svn://svn.debian.org/python-modules/packages/nevow/trunk -Vcs-Browser: http://svn.debian.org/wsvn/python-modules/packages/nevow/trunk/?op=log +Vcs-Browser: http://svn.debian.org/viewsvn/python-modules/packages/nevow/trunk/ Homepage: http://divmod.org/trac/wiki/DivmodNevow -XS-Python-Version: all +XS-Python-Version: >= 2.4 Package: python-nevow Architecture: all diff -Nru nevow-0.9.31/debian/patches/01-add-doc-from-svn.patch nevow-0.10.0/debian/patches/01-add-doc-from-svn.patch --- nevow-0.9.31/debian/patches/01-add-doc-from-svn.patch 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/debian/patches/01-add-doc-from-svn.patch 2010-03-02 20:03:20.000000000 +0000 @@ -0,0 +1,3326 @@ +This is the complete documentation from SVN. + +To get it: + $ svn co http://divmod.org/svn/Divmod/trunk/Nevow/ Nevow-svn + $ cp -a Nevow-svn Nevow-svn-withoutdoc + $ rm Nevow-svn-withoutdoc/**/*.{xhtml,tpl} + $ diff -Naur Nevow-svn-withoutdoc Nevow-svn + +diff -Naur Nevow-0.10.0/doc/howto/chattutorial/concepts.xhtml Nevow-0.10.0-new/doc/howto/chattutorial/concepts.xhtml +--- Nevow-0.10.0/doc/howto/chattutorial/concepts.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/chattutorial/concepts.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,136 @@ ++ ++ ++ ++ ++ Concepts ++ ++ ++ ++

Concepts

++ ++

Servers and Clients

++ ++

COMET applications can seem an almost impenetrable mess when one is first ++learning about them, much like when writing event-based desktop ++applications. However, there are some basic concepts that we can emphasize now ++to circumvent or alleviate most of the confusion.

++ ++

In principle, the problem is very simple:

++
    ++
  • We want out users to interact with a web page with out having to refresh ++the page, and we want new data and/or views to be rendered in response to our ++users' actions;
  • ++
  • We want the ability to push updates to user pages from the server to the ++browser, when the server has new data or views that are ready.
  • ++
++ ++

As usual, the implementation of the solution is much more complicated than ++the statement of the problem, but hopefully the way that we have designed ++Athena will hide those implementation details while providing powerful tools to ++build the applications you envision. So, let's take a look at what you need to ++know about servers and clients when building Athena web applications.

++ ++

It is crucial that one understands that when we write Athena applications, ++we are doing a few different things:

++
    ++
  • Writing server code in Python that performs server actions
  • ++
  • Writing server code in Python that makes remote calls to the browser
  • ++
  • Writing browser code in JavaScript that performs browser actions
  • ++
  • Writing browser code in JavaScript that makes remote calls to the server
  • ++
++ ++

Since server-on-server and client-on-client are rather common place and ++generally well understood, we will ignore those for now. As the other two ++are the focus of AJAX/COMET and thus also the primary domain of Athena, that is ++what we will discuss below.

++ ++

Browser-to-server calls are made by Athena via the now-famous ++ XMLHttpRequest. Server-to-browser calls are opened by the browser ahead of ++time, and when the server is ready, the data is sent to the browser via that ++connection.

++ ++

JavaScript: Making Calls to the Server

++ ++

When creating the JavaScript portion of our applications, we subclass ++an Athena JavaScript widget, which has a method named ++callRemote(). By utilizing this method, we can send messages from ++our JavaScript client to the server (as long as the method we call exists in ++the server code).

++ ++

For example, in the chat application we will be building in this series ++of tutorials, we will have a JavaScript class called ChatterBox with a ++say() method, like the following:

++
++function say(self, msg) {
++    self.callRemote("say", msg);
++    // Now show the text to the user somehow...
++}
++
++

This will make a remote call to the Python server code, executing the ++say() method and passing the msg variable as a ++parameter.

++ ++

In Athena, the relationship between the browser code and the server code is ++established by declaring the JavaScript module in the Python server code, in ++the following manner:

++
++class ChatterBox(LiveElement):
++    jsClass = u'ChatThing.ChatterBox'
++
++

Additionally, in order for the JS to be able to make a call to remote Python ++code, the Python method has to be exposed. This is a security feature, ++implemented to ensure the JavaScript code can only call Python methods that ++have been specifically marked as safe. Appropriately enough, this is done in ++your Python class with the expose decorator:

++ ++
++def say(self, text):
++    for chatter in chatRoom:
++        chatter.youHeardSomething(text)
++say = expose(say)
++
++ ++

Python: Making Calls to the Browser

++ ++

Now what about the COMET side of the equation? If we want our server to ++update data in the browser, we need to be able to call JavaScript code from our ++Python server. We use a similar Python method as the JavaScript one (when ++making calls from the browser to the server), acquired when our Python class ++inherited from nevow.athena.LiveElement:

++ ++
++def hear(self, sayer, text):
++    self.callRemote("hear", sayer, text)
++
++ ++

In order for this call to work, we need to have the hear() ++method defined in our ChatterBox JavaScript class, and that will ++look like this:

++
++function hear(self, avatarName, text) {
++    // Here, you'd show the user some text.
++}
++
++ ++

Unlike on our Python classes, no special annotations need to be made on the ++JavaScript side: all JavaScript methods on browser-side Widget objects are ++allowed to be called by the server. If you've sent code to the browser, you've ++already forfeited the ability to control when it's called. There wouldn't be a ++point to limiting the server's rights to run its code when the user can freely ++run it herself.

++ ++

Summary

++ ++

With the samples above, you should have a growing sense of how Python and ++JavaScript interact as servers and clients in the world of Athena. In ++particular, you should be getting a sense of how JavaScript and Python will be ++interacting in your Athena applications.

++ ++

This has just been a taste of Athena with a few peeks into the code we ++will be writing. We will cover these topics in greater detail in the following ++pages, within the context of creating a functional Athena application, ++complete with step-by-step instructions and rationale.

++ ++ ++ +diff -Naur Nevow-0.10.0/doc/howto/chattutorial/env.xhtml Nevow-0.10.0-new/doc/howto/chattutorial/env.xhtml +--- Nevow-0.10.0/doc/howto/chattutorial/env.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/chattutorial/env.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,122 @@ ++ ++ ++ ++ ++ Setting up an Environment ++ ++ ++ ++

Setting up an Environment

++ ++

Install

++ ++To run this tutorial, you need to have nevow available to python and ++you'll need the files in the doc/howto tree. You don't even have to ++install nevow; the examples will run within the source tree. ++ ++ ++

Combinator: The Divmod Way

++ ++

Using SVN ++with Combinator ++is the best way to try out the example code in-place (and hop between ++other SVN branches in the future). This is how we develop and test our ++applications at Divmod. If you have a system installation of Twisted ++that you don't want to update or interfere with, you can use this ++method without installing anything. ++

++ ++
    ++ ++
  1. Create a projects directory or change to some other test directory ++of your choice: ++
    ++$ mkdir ~/Projects
    ++$ cd ~/Projects
    ++
    ++
  2. ++ ++ ++
  3. If you don't have the ++twisted library, check it out now: ++
    ++$ svn co svn://svn.twistedmatrix.com/svn/Twisted/trunk Twisted/trunk
    ++
    ++
  4. ++ ++
  5. Then get Combinator and Nevow (and the rest of Divmod). See the ++Combinator ++Tutorial for more about these special checkout paths. ++
    ++$ svn co http://divmod.org/svn/Divmod/trunk Divmod/trunk
    ++
    ++
  6. ++ ++
  7. Set up the Combinator environment in this shell. You'll need this ++step in any future test shells since it adjusts PATH and PYTHONPATH: ++
    ++$ eval `python Divmod/trunk/Combinator/environment.py`
    ++(some "link:" lines are normal)
    ++
    ++
  8. ++ ++
  9. Register both the Twisted and Divmod (and thus Nevow+Athena) codebases with ++Combinator: ++
    ++$ chbranch Twisted trunk
    ++$ chbranch Divmod trunk
    ++
    ++
  10. ++ ++
  11. You can check to see if your environment is ready to go by running the ++tutorial tests (from any directory, after executing the previous command): ++
    ++$ trial nevow.test.test_howtolistings
    ++
    ++If they all pass, you're ready to begin the tutorial. ++
  12. ++
++ ++ ++ ++

Standard distutils Installation

++ ++

If you don't want to manage branches and environments with ++Combinator, you can install our code in the ++standard site-packages directory. You'll still need the ++source tree so you can use the files in doc/howto.

++ ++

For those that would prefer the old way, here's how you do it:

++ ++
    ++ ++
  1. Create a projects directory: ++
    ++$ mkdir ~/Projects
    ++$ cd ~/Projects
    ++
    ++
  2. ++ ++
  3. Checkout and install the latest Twisted: ++
    ++$ svn co svn://svn.twistedmatrix.com/svn/Twisted/trunk Twisted
    ++$ cd Twisted
    ++$ sudo python setup.py install
    ++$ cd ../
    ++
    ++
  4. ++ ++
  5. Checkout and install Nevow: ++
    ++$ svn co http://divmod.org/svn/Divmod/trunk/Nevow Nevow
    ++$ cd Nevow
    ++$ sudo python setup.py install
    ++$ cd ../
    ++
    ++
  6. ++ ++
++ ++ ++ +diff -Naur Nevow-0.10.0/doc/howto/chattutorial/index.xhtml Nevow-0.10.0-new/doc/howto/chattutorial/index.xhtml +--- Nevow-0.10.0/doc/howto/chattutorial/index.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/chattutorial/index.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,56 @@ ++ ++ ++ ++ ++ Nevow Athena from Scratch, or The Evolution of a Chat Application ++ ++ ++ ++

Nevow Athena from Scratch, or The Evolution of a Chat Application

++ ++

The Chat Tutorial Series

++ ++

++Athena is the JavaScript engine behind Nevow, providing a great deal of ++resources and power to the developer of asynchronous web applications. To ++demonstrate this, we are using a web chat application as our primary example ++in this tutorial. The tutorial is split into several parts: a few introductory ++pages and then independent (but related) tutorials of increasing complexity. ++

++ ++
    ++
  1. Basics ++ ++
  2. ++
  3. Toy Echo Application
  4. ++
  5. Simple Chat and Two-Way Communications
  6. ++
++ ++

History

++

++Nevow's predecessor was Woven (and prior to that, WebMVC). Woven had something ++called LivePage that was doing DOM manipulation as far back as ++2002. In early 2003, Woven event handlers supported sending JavaScript back to ++the user's browser, allowing pages to be updated in response to user-generated ++events. The earliest publicly visible revisions of Nevow made use of XHR ++(XMLHttpRequest) in early 2004. These facts are notable because Nevow was using ++AJAX a year before the term was coined in 2005 and had working code in 2002 and ++2003 that predated Netscape publishing articles on what they called Inner ++Browsing where all navigation takes place withing a single page. ++

++ ++

++Again taking the lead, Athena offers features which developers cannot find ++elsewhere. In this series, we attempt to expose these excellent qualities ++to the world of application ++developers. ++

++ ++ ++ ++ +diff -Naur Nevow-0.10.0/doc/howto/chattutorial/intro.xhtml Nevow-0.10.0-new/doc/howto/chattutorial/intro.xhtml +--- Nevow-0.10.0/doc/howto/chattutorial/intro.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/chattutorial/intro.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,106 @@ ++ ++ ++ ++ ++ Introduction ++ ++ ++ ++

Introduction

++ ++

Who is this tutorial for?

++ ++

This tutorial is for people who want to build interactive client-server ++functionality where a web-browser is the client. It will show you how to build ++a live, interactive chat application that requires nothing more than a web ++browser that supports JavaScript.

++ ++

The interesting thing about a chat application, which shows why Nevow Athena ++is special, is that it involves two-way communication. In other words, it ++involves not only the recently-popular AJAX (the web browser sending commands ++to the server without loading a new page) but also the trickier and, in our ++opinion, somewhat cooler technique known as COMET (the web server ++sending commands to the browser).

++ ++

Who is this tutorial not for?

++ ++

Nevow Athena is not for people who want a normal web application ++framework. If you want one of those, you should use ++non-Athena-Nevow, ++Django, ++TurboGears, or maybe even ++Ruby On Rails. Athena doesn't work in terms ++of pages, links, or HTTP requests and responses; it is a client-server ++framework that works in terms of widgets, JavaScript objects, and symmetric ++asynchronous message queues.

++ ++

However, as alluded to above, Athena is part of a larger framework, Nevow, ++which can be used to build more general-purpose and traditional ++web applications.

++ ++

AJAX

++ ++

AJAX isn't a technology in and of itself, bur rather an amalgam of ++technologies used together in order to accomplish the goal of making web ++applications more responsive than traditional delivery and interactive ++mechanisms, such as HTML forms submitted to a server.

++ ++

In particular, AJAX consists of the following:

++
    ++
  • Asynchronous communications from a user's browser to a server
  • ++
  • JavaScript
  • ++
  • Exchanged data (usually XML or JSON)
  • ++
++ ++

COMET

++ ++

Historically, the focus of AJAX technologies was user-event driven. However, ++with the need to update the user's browser with events generated at the server, ++a solution more sophisticated than AJAX was needed; this has been dubbed COMET. ++Athena is implemented using both AJAX and COMET techniques, and therefore ++allows two-way browser <-> server communications.

++ ++

Athena Basics

++ ++

We've provided brief background information on AJAX/COMET, but what is the ++purpose of Athena? What makes Athena different than other solutions? Here are a ++few key points that should help with these questions::

++
    ++
  • Athena exists to make writing COMET web applications easy.
  • ++
  • Athena is written in Python and JavaScript
  • ++
  • It is written to be used with Nevow, a Twisted-based web framework.
  • ++
  • Similar to Twisted's Perspective ++Broker, Athena employs remote calls.
  • ++
++ ++

Athena was written by Twisted and Divmod developers (in addition to ++contributing members of the community) in order to bring the outdated and ++Nevow-incompatible Woven LivePage technology to Nevow. In addition, it was an ++opportunity to improve upon the original design and incorporate new features to ++address the growing needs of developers.

++ ++

Target Applications

++ ++

Good candidates for Athena web applications would include those where the ++application needs to respond to user input and/or updates from servers, such ++as the following:

++
    ++
  • conference software (e.g. whiteboard, shared text, chat, etc.)
  • ++
  • mail clients
  • ++
  • interactive, multi-player games
  • ++
  • social networking tools
  • ++
  • office applications (e.g., spreadsheets, word processors, etc.)
  • ++
++ ++

Target Developers

++ ++

Anyone who wants to create interactive, web-based applications is a ++potential Nevow/Athena user. It's best to have some background in writing web ++applications, and in addition, to know how to use Nevow. However, we hope that ++this tutorial will be just as useful for beginners as experienced ++developers.

++ ++ ++ +diff -Naur Nevow-0.10.0/doc/howto/chattutorial/part00/index.xhtml Nevow-0.10.0-new/doc/howto/chattutorial/part00/index.xhtml +--- Nevow-0.10.0/doc/howto/chattutorial/part00/index.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/chattutorial/part00/index.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,230 @@ ++ ++ ++ ++ ++ Nevow Athena from Scratch: Echo Application ++ ++ ++ ++

What is an "Echo Application?"

++ ++

++Our first foray into building an Athena application will be an easy venture: ++we want to type something in an input box and have it echoed back to us on ++the same page, without having to reload anything. Why? Well, our eventual ++goal is to have a working chat server, with all sorts of technical bells ++and whistles (persistent storage, authentication, ++etc.), but that's a bit heady for right now. Many of the same principles ++which we will eventually employ in our chat application exist for a simple ++case of sending textual messages between a web browser and a server. This ++is the essence of our "Echo" application. ++

++ ++

Mental Preparation

++ ++

In the ++Introduction and the ++Concepts pages, we had a refresher on AJAX and ++COMET and we learned a little bit about what that looks like for Athena. But ++as we sit down to actually write an Athena application, what do we need to ++wrap our heads around? ++

++ ++

Given the introductory knowledge we have, we know that we will need to ++write some JavaScript, some Python, and if our past experience in developing ++web applications is any guide, some form of template. This indeed is the ++case, but here's something big: we're not working with pages and page ++templates; we're working with "elements", or parts of the DOM tree. We will ++not be creating page resources; we will be creating just the parts of a ++"traditional" page that will be dynamic and interactive. ++

++ ++

Architecture

++ ++

Now that we've pumped ourselves up and before we start clacking away at the ++keyboard, we need to get pointed in the right direction. We need a ++plan. Here's what we know:

++ ++
    ++
  1. We will have a server that: ++
      ++
    • serves dynamic elements in a resource accessible via a URL;
    • ++
    • communicates with a client.
    • ++
    ++
  2. ++
  3. We will have a client that: ++
      ++
    • communicates with the server;
    • ++
    • updates its DOM tree.
    • ++
    ++
  4. ++
++ ++

The user experience of this application will be the following:

++
    ++
  1. they will type text in an input on a form; and
  2. ++
  3. the typed text will be rendered to a different part of the page upon ++hitting a submit button.
  4. ++
++ ++

We will not simply write user input to a div with JavaScript ++DOM manipulation, but will instead pass data like we expect will be necessary ++when we write our chat application. After all, it's probably best to build ++towards our goal. In order to accomplish this, the application will do ++something like the following:

++ ++
    ++
  1. JavaScript client code will extract user input and send ++it to our server;
  2. ++
  3. Python code will receive messages from the client;
  4. ++
  5. Python code will send messages to the client; and
  6. ++
  7. a template file (or stan code) will be used for ++presentation.
  8. ++
++ ++

++ ++

Let the Coding Begin

++ ++

In a future installment, we will outline the development process from ++the perspective of test-driven development, in order to not only show how ++to write unit tests for Athena (Python and JavaScript), but to encourage ++good programming practices while working with Athena. For now, though, we will ++just dive right in.

++ ++

Presentation

++ ++

Let's start with the easy bit: what our app will look like. Here is the ++template for our echo application:

++ ++ ++ ++

Things to note:

++
    ++
  • This is not a complete HTML document, but is an XHTML template for an ++"element".
  • ++
  • The name space declarations in the top div tag are necessary ++for the operation of Athena.
  • ++
  • When we hit the "Send" button, our JavaScript class will call the ++doSay() method.
  • ++
++ ++

Writing the Client

++ ++

Next up is the JavaScript. We need to send our data to the server. In a ++full chat application, it would be necessary to send the data to the server ++so that we could propagate the message to all connected clients. In this ++case, with the simple echo, we're not going to do anything with the data ++that gets sent to the server, except send it back, of course.

++ ++

Our JavaScript will need to do several things:

++
    ++
  1. import required modules;
  2. ++
  3. inherit callRemote functionality from the base ++Widget class;
  4. ++
  5. setup convenience attributes;
  6. ++
  7. implement the doSay() method we put in our template above; ++and
  8. ++
  9. implement a method for updating the DOM with data it receives from ++the server
  10. ++
++ ++
++ ++

Points to note:

++
++ ++

There's not much to say about the next one. This is what sets up the ++relationship between our module name and the actual file itself (so that ++the JavaScript can be loaded):

++ ++ ++ ++

Writing the Server

++ ++

Despite what one might think, writing the server may be the easiest ++part! If you've created Nevow applications before, then this will look ++very familiar. The only method we need is one that will send data back to ++the client. Besides importing the necessary modules and creating a class ++with some boilerplate, that's about it. ++

++ ++

Let's take a look at the code:

++ ++
++ ++

As promised, simple as can be. We do make use of a Twisted utility that ++simplifies typing the path to our template. Some very important points:

++
++ ++

Putting it All Together

++ ++

Now that we've got all the code in front of us, we can trace out exactly ++what happens:

++
    ++
  1. the user loads the resource in their browser, and the template is ++rendered;
  2. ++
  3. after typing a message in the input box, the user hits submit;
  4. ++
  5. upon hitting submit, the client code doSay() method is ++called;
  6. ++
  7. doSay() makes a remote call to the Python server method ++say();
  8. ++
  9. the Python server receives the data when say() is called, and ++then it passes that data to the client code's addText() method;
  10. ++
  11. with control back in the client code and data fresh from the server, ++JavaScript can now update the page's DOM with the new data, and this is ++what the addText() method does;
  12. ++
  13. when addText() finishes, the cycle has completed and the ++browser now displays the latest data input by the user.
  14. ++
++ ++

The Fruits of Our Labor

++ ++

Now we get to run it! This is a little different than what you may be ++used to, if you have written Twisted applications in the past. We are using ++the plugin architecture of Twisted and Nevow such that twistd ++will publish our element in an HTTP service. To do this, we will use ++twistd's athena-widget command:

++ ++
++cd Nevow/doc/howto/chattutorial/part00/listings
++twistd -n athena-widget --element=echothing.echobox.EchoElement
++
++ ++

If you executed this against the tutorial code on your local machine, ++you can now visit localhost:8080 and start ++echoing to your heart's content.

++ ++

Summary

++ ++

As you can see, our echo application is a toy app that doesn't do ++anything very useful. However, it has provided us with a basis for learning how ++to write working Athena code that lets a browser and server communicate ++with each other, both sending and receiving data. As such, we now have a ++solid foundation upon which we can build a functional, useful and ++instructional chat application.

++ ++ ++ ++ +diff -Naur Nevow-0.10.0/doc/howto/chattutorial/part01/index.xhtml Nevow-0.10.0-new/doc/howto/chattutorial/part01/index.xhtml +--- Nevow-0.10.0/doc/howto/chattutorial/part01/index.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/chattutorial/part01/index.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,236 @@ ++ ++ ++ ++ ++ Nevow Athena from Scratch: Chat Application ++ ++ ++ ++

Architecture

++ ++

We'll assume that you've read all the preceding sections of this tutorial ++and have just finished the "Echo" application example. As such, we don't need ++to do any more "mental preparation" and can skip straight to a description of ++the architecture.

++ ++

Fundamentally, this is no different than our echo application: there is ++a little more chatter that takes place between the client and server; ++there's another object involved (a ChatRoom); and we'll have ++to run the server a little differently. ++

++ ++

Here are the new features we want to support:

++
    ++
  • login form;
  • ++
  • in-memory user storage;
  • ++
  • the ability to send global alerts to all users; and
  • ++
  • the ability for all users to "hear" when another user speaks in the ++chat room;
  • ++
++ ++

A general rule we can establish about our architecture is that if something ++has to happen for everyone, that code needs to appear on the server side, ++since it's the server that is keeping track of all users. If something is ++going to happen irrespective of other users or if browser DOM manipulation ++is required, then we know the client will be the recipient of the code.

++ ++

As such, in the features above, the login form will be client code. The ++user storage, global alerts, and "hearing" will be implemented in server ++code for the data; updating the DOM with that data will be implemented in ++client code.

++ ++

The user experience of this application will be the following:

++
    ++
  1. they will be presented with a login box (no password, only username);
  2. ++
  3. upon logging in, a message will be sent to all logged in users that ++this person has joined, they will see a message at the bottom of the ++chat that states their login name, and the login form will be replaced with ++a chat area and a text input field;
  4. ++
  5. they will type text in the input field; and
  6. ++
  7. the typed text will appear in the browser of every person who is ++logged in.
  8. ++
++ ++

Building upon our previous example, our application will do the ++following:

++ ++
    ++
  1. JavaScript client code will extract user input and send ++it to our server;
  2. ++
  3. Python code will receive messages from the client;
  4. ++
  5. Python code will process these messages;
  6. ++
  7. Python code will send messages to the all clients; and
  8. ++
  9. a template file (or stan code) will be used for ++presentation.
  10. ++
++ ++

More Coding

++ ++

Presentation

++ ++

The template is very similar as it was in the previous example, with ++the differences being a new login box, a "logged in as" ++area, and some name changes:

++ ++ ++ ++

We've now got two JavaScript methods that need to be defined: ++doSetUsername() and doSay(). We can also infer ++from this template that elements will be hidden and shown after login ++(note the presence of style="display:none" in two places). With ++these observations in hand, let's proceed to the JavaScript code.

++ ++

Writing the Client

++ ++

Referring back to our thoughts in the "Architecture" section above, we ++can establish that the JavaScript code needs the following:

++ ++
    ++
  • have the same basic boilerplate as in the "echo" example (imports, ++inheritance, attribute-setting in the constructor);
  • ++
  • implement the doSetUsername() and doSay() ++methods;
  • ++
  • create a method that will send a message to all users; and
  • ++
  • create a method that will let everyone know when someone says ++something. Let's see how this is done.
  • ++
++ ++
++ ++

There is a little abstraction here:

++
    ++
  • we need a general message-sending method (displayMessage()) for any ++message that gets sent to all users;
  • ++
  • for user chat messages, we need something that will prepend the username so ++that everyone knows who said what (displayUserMessage()), and once this method ++does its thing, it passes the adjusted message on to displayMessage().
  • ++
++ ++

Other than that, this is very straight-forward code; it's pretty much ++the same as the "Echo" tutorial. The display*() methods ++are only responsible for updating the UI, just as we would expect.

++ ++We also need the same glue that we demonstrated in the "Echo" example: ++ ++
++ ++

Writing the Server

++ ++

The server code is a bit more complicated. We ++anticipated this above in the "Architecture" section where we noted that ++the Python code needs to receive, process and send messages.

++ ++
++ ++

There is something in our "Chat" code that is not at all present in the ++"Echo" application: the ChatRoom object. We need this object for the ++following functionality:

++
    ++
  • a means of instantiating new ChatterElement clients;
  • ++
  • a "singleton" instance for keeping track of all ChatterElement clients;
  • ++
  • a means sending messages to all clients;
  • ++
++ ++

Let's look at the second two reasons first. In our "Chat" application, ++a new ChatterElement is created whenever a user connects, ++so we will have potentially many of these instances. In order ++for our chat server to function as designed, it will need a way to ++communicate with each of these. If we create an object that can keep the ++ChatterElementes in a list, then it will be able to iterate that ++list and call methods that, in turn, make remote calls to the JavaScript. ++

++ ++

Because we need the chat room to be a singleton object, it ++can only be instantiated once. But we need many instantiations of ++ChatterElement -- one for each connection, in fact. So what do ++we do? Well, in this case, we make one of the methods ++of ChatRoom a factory for instantiating a ++ChatterElement. Before we return the instance, though, we ++append it to the list of instances that the ChatRoom ++is keeping track of. ++

++ ++

Putting it All Together

++ ++ ++

Now that we've got all the code in front of us, we can trace out exactly what happens:

++ ++
    ++
  1. the user loads the resource in their browser, and the template is rendered;
  2. ++
  3. after typing a message in the input box, the user hits submit;
  4. ++
  5. JavaScript client code calls to the server with the text the user submitted;
  6. ++
  7. the server gets the message and shares it with all the connected ++ChatterElements;
  8. ++
  9. each ChatterElement hears this message and passes it back to the JavaScript client;
  10. ++
  11. the client prepends the username to the message and then updates the display with the complete message.
  12. ++
++ ++

++Keep in mind that ChatterElement entails several duties: it ++establishes a relationship with a room object, it "registers" a user (there's a ++one-to-one mapping between users and ChatterElement), it sends ++messages to the browser, and it receives messages from the chat room. Being a ++LiveElement subclass, ChatterElement is also ++responsible for the view (via the document factory). ++

++ ++ ++

Running with twistd

++ ++

One last bit of code that may seem odd is the chat ++variable we define right after the ChatRoom class. What ++is this? This is how we make all this cleverness work as a twisted ++plugin.

++ ++

If you recall, in our "Echo" application, we ran the code with ++the following command: ++

++ ++
++twistd -n athena-widget --element=echothing.echobox.EchoElement
++
++ ++

The value we pass as the --element argument is the dotted ++name of the LiveElement object of which our "web page" ++is primarily comprised: the EchoElement object. In ++our "Chat" application, we have more moving parts: not only ++do we have the ChatterElement object, but we have the ++ChatRoom object which is responsible for keeping track of ++many ChatterElementes. By defining the chat ++variable, we are accomplishing the following all at once: ++

++ ++
    ++
  • providing a variable that can be accessed as a dotted name and thus ++used when starting the server (chatthing.chatterbox.chat);
  • ++
  • creating a singleton of ChatRoom (via the "magic" ++of Python module-level instantiations);
  • ++
  • making use of a factory, that when called, will both return a ++new ChatterElement instance and add itself to the ++ChatRoom.
  • ++
++ ++

Running this version of our code is a little bit different than the ++"Echo" version. This is because of the ChatRoom code we ++discussed above. As such, we pass a factory as our element, like so:

++ ++
++cd Nevow/doc/howto/chattutorial/part01/listings
++twistd -n athena-widget --element=chatthing.chatterbox.chat
++
++ ++

If you executed this against the tutorial code on your local machine, ++you can now visit http://localhost:8080/ ++and start chatting to your heart's content.

++ ++

Summary

++

++Unlike our echo application, the chat application has some real functionality ++and does some useful stuff: supporting user chats via browser/server two-way ++communications. It should be evident now how the echo application provided a ++basic conceptual and (partially) functional foundation upon which our chat work ++could be based. ++

++ ++ +diff -Naur Nevow-0.10.0/doc/howto/deployment.xhtml Nevow-0.10.0-new/doc/howto/deployment.xhtml +--- Nevow-0.10.0/doc/howto/deployment.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/deployment.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,300 @@ ++ ++ ++ ++ ++ ++ ++ Deployment ++ ++ ++ ++

Deployment

++ ++

++ Nevow includes two major phases for deciding what HTML to render. Object Traversal is the procedure by which a ++ URL is mapped to a Python object which will perform the HTML ++ generation. Page Rendering is the process by ++ which data objects are combined with an HTML template to produce the ++ final output. ++

++ ++

++ Before any of this can take place, however, we must have an environment ++ in which our Python code can run in response to an HTTP request, and HTML ++ can be returned to the browser for rendering. This is called the ++ Deployment Environment. ++

++ ++

++ There are various deployment options for Nevow page code: ++

++ ++
    ++
  • ++ CGI: Simple deployment in almost any HTTP server ++
  • ++
  • ++ WSGI: A more complete and flexible way for deploying on many HTTP ++ servers ++
  • ++
  • ++ Twisted.Web: A standalone application server process which includes a ++ built-in HTTP server ++
  • ++
  • ++ Zomne: A small CGI which hands off HTTP requests to a long-running ++ application server process, similar to FastCGI or SCGI ++
  • ++
++ ++

CGI

++ ++

++ You can deploy Nevow on any webserver which uses the Common Gateway ++ Interface. Using this method, your code is responsible for properly ++ formatting and outputting the HTTP response headers, and Nevow is used ++ only to generate the HTML body of your page. Here is the simplest ++ possible CGI: ++

++ ++
++#!/usr/bin/env python
++
++print "Content-type: text/plain\r\n\r\n",
++
++from nevow import rend, loaders
++
++class HelloWorld(rend.Page):
++    docFactory = loaders.stan("Hello, world!")
++
++print HelloWorld().renderSynchronously()
++    
++ ++

++ With this simple CGI you can use the Nevow template loaders and standard ++ nevow template interpolation techniques in your CGIs. However, you do not ++ get any Object Traversal features, and you ++ have to generate HTTP headers yourself. WSGI is a slightly higher-level ++ deployment option which does not suffer these problems. ++

++ ++

WSGI

++ ++

++ WSGI is a python interface for plugging web applications into various ++ HTTP server architectures. It is described in PEP 333, the Python ++ Web Services Gateway Interface Python Enhancement Proposal. Nevow ++ includes the nevow.wsgi module, which includes a ++ createWSGIApplication function which takes a Page and ++ returns a standard WSGI application callable. With the help of the ++ run_with_cgi example gateway from the PEP (which I will omit ++ here), our CGI example becomes shorter: ++

++ ++
++#!/usr/bin/env python
++
++from nevow import rend, loaders, wsgi
++
++class HelloWorld(rend.Page):
++    docFactory = loaders.stan("Hello, world!")
++
++run_with_cgi(wsgi.createWSGIApplication(HelloWorld()))
++    
++ ++

++ Of course, you can use any available WSGI gateway to publish your ++ application object, such as one of the gateways which comes with the PEAK toolkit. For example, here ++ is a simple python module which creates a WSGI application which we will ++ then deploy with PEAK's SimpleHTTPServer gateway:: ++

++ ++
++## helloworld.py
++
++from nevow import rend, loaders, wsgi
++
++class HelloWorld(rend.Page):
++    docFactory = loaders.stan("Hello, world!")
++
++application = wsgi.createWSGIApplication(HelloWorld())
++    
++ ++

++ Save this file as "helloworld.py" somewhere on your PYTHONPATH and then ++ run the following command: ++

++ ++
peak launch WSGI import:helloworld.application
++ ++

++ This will bring up a SimpleHTTPServer running your Nevow code and launch ++ a web browser to view the output. (TODO: I couldn't get this working ++ immediately but I will seek assistance with PEAK and update the ++ instructions once I do.) ++

++ ++

Twisted.Web

++ ++

++ A convenient and powerful way to deploy Nevow applications is inside a ++ process running the twisted.web HTTP server. With Python, Twisted, and ++ Nevow installed, you have all you need to run a Web Application, with no ++ other dependencies or external HTTP servers such as Apache ++ required. Running your Nevow applications under twisted.web also gives ++ you access to some of the more advanced "Live" features of Nevow, such as ++ nevow.livepage and nevow.canvas. Currently, ++ these modules require more control over the HTTP socket than CGI or WSGI ++ can provide. (This may change in the future.) ++

++ ++

++ Deploying a Nevow application under twisted.web requires a little more ++ boilerplate, but can be considerably easier to set up than other ++ deployment options because there are no external dependencies. Note that ++ normally you should declare your Page classes in modules external to the ++ twisted configuration file, but everything is included in one file here ++ for brevity. Here is the minimal configuration file required to use ++ Nevow with twisted.web: ++

++ ++
++from nevow import rend, loaders, appserver
++
++class HelloWorld(rend.Page):
++    docFactory = loaders.stan("Hello, world!")
++
++from twisted.application import service, internet
++application = service.Application("hello-world")
++internet.TCPServer(8080, appserver.NevowSite(HelloWorld())).setServiceParent(application)
++    
++ ++

++ Save this file as "helloworld.tac" and start the server using the ++ command: ++

++ ++
twistd -noy helloworld.tac
++ ++

++ Then visit your twisted.web server by viewing the url ++ "http://localhost:8080/" in your browser. See the twistd man page for ++ more information about what twistd is capable of, including daemonizing ++ the HTTP server. ++

++ ++

Zomne

++ ++

++ Warning Zomne is experimental. It may blow up your computer and ++ require your first born son as a sacrifice. Zomne also only works in ++ UNIX-like environments where unix domain sockets are available, and may ++ not work on windows. ++

++ ++

++ Zomne, or "Zombie Nevow", is a CGI written in C which can start up a ++ long-running Application Server process if one is not already running. It ++ then uses a simple custom protocol to transmit information about the HTTP ++ request from the CGI process to the application server process. ++

++ ++

++ Zomne combines the ease of deployment of the CGI environment with the ++ speed and flexibility of the twisted.web long-running application server ++ process model. ++

++ ++

++ To use Zomne, you must first compile the CGI. cd into the directory ++ created when unpacking the Nevow tarball, and compile the CGI: ++

++ ++
% gcc zomne.c
++ ++

++ Move it into your cgi-bin: ++

++ ++
% mv a.out /Library/WebServer/CGI-Executables/nevow.cgi
++ ++

++ Create a file which tells the cgi where to look for the application: ++

++ ++
++% cat > /Library/WebServer/CGI-Executables/.nevow.cgi.dir
++/Users/dp/zomne-test
++^D
++ ++

++ The CGI name can be anything, as long as there is a file with a prepended ++ "." and a postfixed ".dir" in the same directory which contains the full ++ path of a zomne application directory. Next, create the application ++ directory: ++

++ ++
mkdir /Users/dp/zomne-test
++ ++

++ Finally, create the zomne.tac file which the zomne.cgi will execute to ++ start the long-running application server process: ++

++ ++
++from nevow import rend, loaders, zomnesrv
++
++class HelloWorld(rend.Page):
++    docFactory = loaders.stan("Hello, world!")
++
++from twisted.application import service, internet
++application = service.Application('nevow-zomne-test')
++internet.UNIXServer('zomne.socket', zomnesrv.ZomneFactory(HelloWorld())).setServiceParent(application)
++    
++ ++

++ Now, visiting the nevow.cgi URL through the web should render the Hello ++ World page, after a pause while the server is starting up. Subsequent ++ requests should be very fast, because the application server is already ++ running, and the CGI merely has to forward the request to it. ++

++ ++

++ Another useful capability of the zomne CGI process is the ability to ++ control environment variables the CGI will use. Create a directory named ++ "zomne_environ" in the application directory, and fill it with text files ++ whose name will be the environment key and whose contents will be the ++ environment value: ++

++ ++
++% cd zomne-test
++% mkdir zomne-environ
++% cd zomne-environ
++% cat > PYTHONPATH
++/Users/dp/Projects/Nevow:/Users/dp/Projects/helloworld
++^D
++ ++

Conclusion

++ ++

++ Nevow may be deployed in a number of environments, from the most ++ restrictive to the most permissive. Writing a CGI can be an easy way to ++ try out the Nevow templating mechanism, but can be slow. A long-running ++ application server process can be a good way to get good performance as ++ well as additional features such as in-memory server-side sessions, ++ advanced automatic form handling with formless, and live page updating ++ features such as nevow.livepage and nevow.canvas. ++

++ ++

++ Which deployment option you choose will depend on the amount of control ++ you have over your deployment environment, and what advanced features ++ your application will require. ++

++ ++ +diff -Naur Nevow-0.10.0/doc/howto/gettingstarted.xhtml Nevow-0.10.0-new/doc/howto/gettingstarted.xhtml +--- Nevow-0.10.0/doc/howto/gettingstarted.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/gettingstarted.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,110 @@ ++ ++ ++ ++ ++ ++ ++ Getting Started ++ ++ ++ ++

Getting Started

++ ++

++ Warning: This document has only just been started. It's not going to get ++ you very far right now. ++

++ ++

++ Nevow is a reasonably large library and can be quite daunting at ++ first. This document's aim is to guide the first time user in building a ++ Nevow application. ++

++ ++

Our First Application

++ ++

++ Let's dive straight in, here's the code for our first (very, very simple) ++ application. Create the following module, helloworld.py: ++

++ ++ ++ helloworld.py ++ ++ ++

++ It looks quite simple but let's walk through it anyway. ++

++ ++

++ First, we import two Nevow modules. nevow.loaders contains template loaders of which the ++ two most useful are xmlfile ++ and stan. ++ xmlfile can load any well-formed XML (i.e. XHTML) file; ++ stan loads a stan tree (more on these later). The other ++ module, nevow.rend, contains all Nevow's ++ standard renders, many of which we'll meet in this document. ++

++ ++

++ We then define the HelloWorld class that subclasses rend.Page, Nevow's main resource ++ class. HelloWorld has two class ++ attributes. addSlash tells rend.Page to ++ redirect to a version of the request URL that ends in a / if ++ necessary. You generally want to set this to True for the ++ root resource. docFactory tells the page instance where to ++ get the template from. In this case we're providing a loader that parses ++ an HTML file (not shown) from disk. ++

++ ++

++ Hmm, ok I hear you say but how do I see it. Well, Twisted provides a good ++ web server which we can use. Twisted also includes a clever little ++ application for starting Twisted applications. Here's the helloworld.tac ++ file, a Twisted Application Configuration: ++

++ ++ ++ helloworld.tac ++ ++ ++

++ Give it a go, run the following and connect to http://localhost:8080/ to see your ++ application: ++

++ ++
twistd -ny helloworld.tac
++ ++

++ You'll probably notice that you get log output on the console. This is ++ just one of the good things that twistd does. It can also daemonize the ++ application, shed privileges if run as root, etc. ++

++ ++

++ TAC files are covered in more detail in the Twisted documentation but ++ let's quickly explain what all this does anyway. ++

++ ++

++ When twistd starts up it loads the ++ .tac file (it's just Python) and looks for the attribute ++ called application. When twistd ++ is all ready to go it starts the application. ++

++ ++

++ The application is not much use unless it actually does something so the ++ next thing we do is create a NevowSite instance, site, and ++ pass it a root resource, a HelloWorld instance. Finally, we ++ create a TCP server that makes the site available on port 8080 and bind ++ the server to the application to ensure the server is started when the ++ application is started. ++

++ ++ +diff -Naur Nevow-0.10.0/doc/howto/glossary.xhtml Nevow-0.10.0-new/doc/howto/glossary.xhtml +--- Nevow-0.10.0/doc/howto/glossary.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/glossary.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,82 @@ ++ ++ ++ ++ ++ ++ ++ Glossary ++ ++ ++ ++

Glossary

++ ++

Object Traversal

++ ++

++ The process by which a Python object is located to render HTML for a ++ given HTTP URL. For example, given the URL http://example.com/foo/bar, ++ Object Traversal will begin at the "Root Resource" object by asking it ++ for an object which is capable of rendering the page at ('foo', ++ 'bar'). The "Root Resource" returns an object and a list of unhandled ++ path segments, and the traversal continues across this new Resource ++ object until all path segments have been consumed. ++

++ ++

Page Rendering

++ ++

++ The process by which a Python object, usually a rend.Page subclass, turns ++ itself into HTML. Page Rendering involves locating some page data, ++ loading a template document, and applying the template to the data, in ++ the process generating HTML. ++

++ ++

Deployment Environment

++ ++

++ The environment in which a Nevow application is deployed. Generally ++ involves an HTTP server which is configured to route certain (or all) ++ HTTP requests through the Nevow Object Traversal and Page Rendering ++ process. Deployment environments include CGI, WSGI, and twisted.web. ++

++ ++

DOM

++ ++

++ Document Object Model. A tree of objects which represent the structure of ++ an XHTML document in memory. Nevow uses a nonstandard DOM named "stan", ++ which is made up of simple Python lists, dicts, strings, and ++ nevow.stan.Tag instances. ++

++ ++

Flattener

++ ++

++ A Python function which knows how to translate from a rich type to a ++ string containing HTML. For example, the integer flattener calls str() on ++ the integer. The string flattener escapes characters which are unsafe in ++ HTML, such as <, >, and &. ++

++ ++

Tag

++ ++

++ A class, defined at nevow.stan.Tag, which holds information about a ++ single HTML tag in a DOM. Tag instances have three attributes: tagName, ++ attributes, and children. tagName is a string indicating the tag ++ name. attributes is a dict indicating the HTML attributes of that ++ node. children is a list indicating the child nodes of that node. ++

++ ++

Tag Specials

++ ++

++ A Tag attribute which is "special" to nevow. Tag specials include data, ++ render, pattern, slot, and macro. Tag Specials will never be output as ++ HTML attributes of tags, but will be used by the internal Nevow rendering ++ process to influence how the Tag is rendered. ++

++ ++ ++ +diff -Naur Nevow-0.10.0/doc/howto/index.xhtml Nevow-0.10.0-new/doc/howto/index.xhtml +--- Nevow-0.10.0/doc/howto/index.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/index.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,49 @@ ++ ++ ++ ++ ++ ++ ++ Manual ++ ++ ++ ++

++ Manual ++

++
    ++
  • ++ Introduction ++
  • ++
  • ++ Getting Started ++

    A basic introduction to rendering a web page in Nevow.

    ++
  • ++
  • ++ Object Traversal ++

    Getting from an URL to a Python page object you want to render.

    ++
  • ++
  • ++ Object Publishing ++

    Exposing Python objects as parts of a web page in Nevow.

    ++
  • ++
  • ++ XML Templates ++

    Using standard XHTML as a template for Nevow.

    ++
  • ++
  • ++ Deployment ++

    How to get your Nevow application running on different types of ++ servers.

    ++
  • ++
  • ++ Nevow Athena ++

    Two-way communication with JavaScript in a web browser.

    ++
  • ++
  • ++ Glossary ++
  • ++
++ ++ +diff -Naur Nevow-0.10.0/doc/howto/intro.xhtml Nevow-0.10.0-new/doc/howto/intro.xhtml +--- Nevow-0.10.0/doc/howto/intro.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/intro.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,294 @@ ++ ++ ++ ++ ++ ++ ++ A Web Application Construction Kit ++ ++ ++ ++

A Web Application Construction Kit

++ ++

Summary

++ ++

++ Nevow is a next-generation web application templating system, based on ++ the ideas developed in the Twisted Woven package. Its main focus is on ++ separating the HTML template from both the business logic and the display ++ logic, while allowing the programmer to write pure Python code as much as ++ possible. It separates your code into 'data' and 'render' functions, a ++ simplified implementation of traditional MVC. It has various parts which ++ can be used individually or as a whole, integrated web solution: ++

++ ++
    ++
  • ++ XHTML templates: contain no programming logic, only nodes tagged with ++ nevow attributes ++
  • ++
  • ++ data/render methods: simplified MVC ++
  • ++
  • ++ stan: An s-expression-like syntax for expressing xml in pure python ++
  • ++
  • ++ formless: For describing the types of objects which may be passed to ++ methods of your classes, validating and coercing string input from ++ either web or command-line sources, and calling your methods ++ automatically once validation passes ++
  • ++
  • ++ formless.webform: For rendering web forms based on formless type ++ descriptions, accepting form posts and passing them to formless ++ validators, and rendering error forms in the event validation fails ++
  • ++
  • ++ livepage: Cross-browser JavaScript glue for sending client side events ++ to the server and server side events to the client after the page has ++ loaded, without causing the entire page to refresh ++
  • ++
++ ++

Disk based templates

++ ++

++ Nevow includes the ability to load templates off disk. These templates ++ may have processing directives which cause the execution of python ++ methods at render time. The attribute technique was inspired by the ++ attributes used by ZPT. However, no actual code may be embedded in the ++ HTML template: ++

++ ++
++<html xmlns:nevow="http://nevow.com/ns/nevow/0.1">
++  <head>
++    <title>Greetings!</title>
++  </head>
++  <body>
++    <h1 style="font-size: large">Now I will greet you:</h1>
++    <span nevow:render="greet" />
++  </body>
++</html>
++ ++

++ This template can then be loaded and rendered like so: ++

++ ++
++class Greeter(rend.Page):
++    docFactory = loaders.xmlfile("Greeting.html")
++
++    def render_greet(self, context, data):
++        return random.choice(["Hello", "Greetings", "Hi"]), " ", data
++
++Greeter("My name is").renderString()
++    
++ ++

data/render methods

++ ++

++ To allow clean isolation between code which fetches data from a data ++ source and code which renders the data into HTML, nevow allows you to ++ write both 'data' methods and 'render' methods. These concepts are ++ inspired by MVC, but simpler, since the framework can handle most of the ++ controller aspect. An example: ++

++ ++
++<html xmlns:nevow="http://nevow.com/ns/nevow/0.1">
++  <body>
++    <span nevow:data="name" nevow:render="colorful" />
++    <span nevow:data="fun" nevow:render="colorful" />
++  </body>
++</html>
++ ++

++ This template can be loaded and rendered using a class such as this: ++

++ ++
++class Colorful(rend.Page):
++    docFactory = loaders.xmlfile("Colorful.html")
++
++    def render_colorful(self, context, data):
++        color = random.choice(['red', 'green', 'blue'])
++        return context.tag(style="color: %s" % color)
++
++    def data_name(self, context, data):
++        return "Your name here"
++
++    def data_fun(self, context, data):
++        return "Are we having fun yet?"
++    
++ ++

Stan

++ ++

++ One of the most powerful things about nevow is stan, an s-expression-like ++ syntax for producing XML fragments in pure Python syntax. Stan is not ++ required for using nevow, but it is both a simple and powerful way to ++ both lay out one's XHTML templates and express one's display logic. A ++ brief example will illustrate its utility: ++

++ ++
++import random
++from nevow import rend, tags
++
++class Greeter(rend.Page):
++    def greet(self, context, data):
++        return random.choice(["Hello", "Greetings", "Hi"]), " ", data
++
++    docFactory = loaders.stan(
++        tags.html[
++        tags.head[ tags.title[ "Greetings!" ]],
++        tags.body[
++            tags.h1(style="font-size: large")[ "Now I will greet you:" ],
++            greet
++        ]
++    ])
++    
++ ++

++ When the Greeter class is constructed, it is passed a Python object which ++ will be used as that page's data: ++

++ ++
++Greeter("Your name here").renderString()
++    
++ ++

Formless

++ ++

++ Python is dynamically typed, which means it has no built-in controls for ++ enforcing the types of objects which are passed to one's methods. This is ++ great for programmers, but not necessarily great if you are going to be ++ passing user-entered input to those methods. Formless is a simple way to ++ describe the types of objects that can be passed to one's methods, as ++ well as coerce from string input to those types. Other code can then ++ accept user input from a command line or from a web form, validate the ++ input against the types described using formless, and call the method ++ once validation has passed. A simple example: ++

++ ++
++from zope.interface import implements
++from formless.annotate import TypedInterface, Integer, String
++
++class ISimpleMethod(TypedInterface):
++    def simple(self,
++               name=String(description="Your name."),
++               age=Integer(description="Your age.")):
++        """
++        Simple
++
++        Please enter your name and age.
++        """
++
++class Implementation(object):
++    implements(ISimpleMethod)
++
++    def simple(self, name, age):
++        print "Hello, %s, who is %s" % (name, age)
++    
++ ++

Webform

++ ++

++ Webform is a nevow module which will automatically render web forms and ++ accept form posts based on types described using the classes in ++ formless. Used in conjunction with the twisted.web HTTP server, the ++ process is almost automatic: ++

++ ++
++from nevow import rend, tags
++from formless import webform
++
++class WebForm(rend.Page):
++    document = rend.stan(
++    tags.html[
++    tags.body[
++        h1["Here is the form:"],
++        webform.renderForms('original')
++    ]
++])
++
++resource = WebForm(Implementation())
++    
++ ++

++ Exposing this resource instance to the web using twisted.web and visiting ++ it will cause a form with two input boxes to be rendered. Posting the ++ form will cause form validation to occur. Upon error, the user will be ++ returned to the original page, with the form annotated with error ++ messages. Upon success, the "simple" method of the Implementation ++ instance will be called and passed a string and an integer. ++

++ ++

LivePage

++ ++

++ LivePage was a Woven technology which allowed programmers to receive ++ server-side notification of client-side JavaScript events, and to send ++ JavaScript to the client in response to a server-side event. New for ++ Nevow 0.3, LivePage has been updated to support Mozilla, Firefox, IE6 ++ Win, and Safari. Using LivePage is very easy: ++

++ ++
++from nevow.liveevil import handler
++
++def greeter(client, nodeName):
++    client.alert("Greetings. You clicked the %s node." % nodeName)
++
++# Any string arguments after the event handler function will be evaluated
++# as JavaScript in the context of the web browser and results passed to the
++# Python event handler
++handler = handler(greeter, 'node.name')
++
++class Live(rend.Page):
++    docFactory = loaders.stan(
++        tags.html[
++        tags.body[
++            ol[
++                li(onclick=handler, name="one")["One"]
++                li(onclick=handler, name="two")["Two"]
++                li(onclick=handler, name="three")["Three"]
++            ]
++        ]
++    ])
++    
++ ++

More Information

++ ++

++ The Nevow website ++ has more information. Starting with 0.3, it contains a simple WSGI ++ implementation and can also be used to render CGIs. However, the ++ recommended mode of operation is using the Twisted web ++ server. Nevow is an active project, and many new bugfixes and features ++ are committed to the Nevow SVN repository. Information about Nevow ++ commits is available by subscribing to the ++ Divmod commits mailing list. The Nevow SVN repository can be checked ++ out using: ++

++ ++
svn co svn://divmod.org/svn/Nevow/trunk Nevow
++ ++

++ Discussion of Nevow occurs on the ++ twisted.web mailing list. The Nevow developers are also often ++ available for real-time help on the #twisted.web channel on ++ irc.freenode.net. ++

++ ++ +diff -Naur Nevow-0.10.0/doc/howto/publishing.xhtml Nevow-0.10.0-new/doc/howto/publishing.xhtml +--- Nevow-0.10.0/doc/howto/publishing.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/publishing.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,658 @@ ++ ++ ++ ++ ++ ++ ++ Object Publishing ++ ++ ++ ++

++ Object Publishing ++

++ ++

++ In Object Traversal, we learned about the ++ nevow.inevow.IResource.renderHTTP method, which ++ is the most basic way to send HTML to a browser when using ++ Nevow. However, it is not very convenient (or clean) to generate HTML ++ tags by concatenating strings in Python code. In the Deployment documentation, we saw that it was ++ possible to render a Hello World page using a ++ nevow.rend.Page subclass and providing a docFactory: ++

++ ++
++>>> from nevow import rend, loaders
++>>> class HelloWorld(rend.Page):
++...     docFactory = loaders.stan("Hello, world!")
++...
++>>> HelloWorld().renderSynchronously()
++'Hello, world!'
++ ++

++ This example does nothing interesting, but the concept of a loader is ++ important in Nevow. The rend.Page.renderHTTP implementation ++ always starts rendering HTML by loading a template from the ++ docFactory. ++

++ ++ ++

The stan DOM

++ ++

++ Nevow uses a DOM-based approach to rendering HTML. A tree of objects is ++ first constructed in memory by the template loader. This tree is then ++ processed one node at a time, applying functions which transform from ++ various Python types to HTML strings. ++

++ ++

++ Nevow uses a nonstandard DOM named "stan". Unlike the W3C DOM, stan is ++ made up of simple python lists, strings, and instances of the ++ nevow.stan.Tag class. During the rendering process, "Flattener" ++ functions convert from rich types to HTML strings. For example, we can ++ load a template made up of some nested lists and Python types, render it, ++ and see what happens: ++

++ ++
++>>> class PythonTypes(rend.Page):
++...     docFactory = loaders.stan(["Hello", 1, 1.5, True, ["Goodbye", 3]])
++...
++>>> PythonTypes().renderSynchronously()
++'Hello11.5TrueGoodbye3'
++ ++

Tag instances

++ ++

++ So far, we have only rendered simple strings as output. However, the main ++ purpose of Nevow is HTML generation. In the stan DOM, HTML tags are ++ represented by instances of the nevow.stan.Tag ++ class. Tag is a very simple class, whose instances have an ++ attributes dictionary and a children list. The ++ Tag flattener knows how to recursively flatten attributes ++ and children of the tag. To show you how Tags really work ++ before you layer Nevow's convenience syntax on top, try this horrible ++ example: ++

++ ++
++>>> from nevow import stan
++>>> h = stan.Tag('html')
++>>> d = stan.Tag('div')
++>>> d.attributes['style'] = 'border: 1px solid black'
++>>> h.children.append(d)
++>>> class Tags(rend.Page):
++...     docFactory = loaders.stan(h)
++...
++>>> Tags().renderSynchronously()
++'<html><div style="border: 1px solid black"></div></html>'
++ ++

++ So, we see how it is possible to programatically generate HTML by ++ constructing and nesting stan Tag instances. However, it is ++ far more convenient to use the overloaded operators Tag ++ provides to manipulate them. Tag implements a ++ __call__ method which takes any keyword arguments and values ++ and updates the attributes dictionary; it also implements a ++ __getitem__ method which takes whatever is between the square ++ brackets and appends them to the children list. A simple example should ++ clarify things: ++

++ ++
++>>> class Tags2(rend.Page):
++...     docFactory = loaders.stan(stan.Tag('html')[stan.Tag('div')(style="border: 1px solid black")])
++...
++>>> Tags2().renderSynchronously()
++'<html><div style="border: 1px solid black"></div></html>'
++ ++

++ This isn't very easy to read, but luckily we can simplify the example ++ even further by using the nevow.tags module, which is full of "Tag ++ prototypes" for every tag type described by the XHTML 1.0 specification: ++

++ ++
++>>> class Tags3(rend.Page):
++...     docFactory = loaders.stan(tags.html[tags.div(style="border: 1px solid black")])
++...
++>>> Tags3().renderSynchronously()
++'<html><div style="border: 1px solid black"></div></html>'
++ ++

++ Using stan syntax is not the only way to construct template DOM for use ++ by the Nevow rendering process. Nevow also includes loaders.xmlfile which implements a simple tag ++ attribute language similar to the Zope Page Templates (ZPT) Tag Attribute ++ Language (TAL). However, experience with the stan DOM should give you ++ insight into how the Nevow rendering process really works. Rendering a ++ template into HTML in Nevow is really nothing more than iterating a tree ++ of objects and recursively applying "Flattener" functions to objects in ++ this tree, until all HTML has been generated. ++

++ ++

Functions in the DOM

++ ++

++ So far, all of our examples have generated static HTML pages, which is ++ not terribly interesting when discussing dynamic web applications. Nevow ++ takes a very simple approach to dynamic HTML generation. If you put a ++ Python function reference in the DOM, Nevow will call it when the page is ++ rendered. The return value of the function replaces the function itself ++ in the DOM, and the results are flattened further. This makes it easy to ++ express looping and branching structures in Nevow, because normal Python ++ looping and branching constructs are used to do the job: ++

++ ++
++>>> def repeat(ctx, data):
++...     return [tags.div(style="color: %s" % (color, ))
++...         for color in ['red', 'blue', 'green']]
++...
++>>> class Repeat(rend.Page):
++...     docFactory = loaders.stan(tags.html[repeat])
++...
++>>> Repeat().renderSynchronously()
++'<html><div style="color: red"></div><div style="color: blue"></div><div style="color: green"></div></html>'
++ ++

++ However, in the example above, the repeat function isn't even necessary, ++ because we could have inlined the list comprehension right where we ++ placed the function reference in the DOM. Things only really become ++ interesting when we begin writing parameterized render functions which ++ cause templates to render differently depending on the input to the web ++ application. ++

++ ++

++ The required signature of functions which we can place in the DOM is ++ (ctx, data). The "context" object is essentially opaque for now, and we ++ will learn how to extract useful information out of it later. The "data" ++ object is anything we want it to be, and can change during the rendering ++ of the page. By default, the data object is whatever we pass as the first ++ argument to the Page constructor, or the Page instance itself if ++ nothing is passed. Armed with this knowledge, we can create a Page which ++ renders differently depending on the data we pass to the Page ++ constructor: ++

++ ++
++class Root(rend.Page):
++    docFactory = loaders.stan(tags.html[
++        tags.h1["Welcome."],
++        tags.a(href="foo")["Foo"],
++        tags.a(href="bar")["Bar"],
++        tags.a(href="baz")["Baz"]])
++
++    def childFactory(self, ctx, name):
++        return Leaf(name)
++
++def greet(ctx, name):
++    return "Hello. You are visiting the ", name, " page."
++
++class Leaf(rend.Page):
++    docFactory = loaders.stan(tags.html[greet])
++    
++ ++

++ Armed with this knowledge and the information in the Object Traversal documentation, we now have ++ enough information to create dynamic websites with arbitrary URL ++ hierarchies whose pages render dynamically depending on which URL was ++ used to access them. ++

++ ++

Accessing query parameters and form post data

++ ++

++ Before we move on to more advanced rendering techniques, let us first ++ examine how one could further customize the rendering of a Page based on ++ the URL query parameters and form post information provided to us by a ++ browser. Recall that URL parameters are expressed in the form: ++

++ ++
http://example.com/foo/bar?baz=1&quux=2
++ ++

++ And form post data can be generated by providing a form to a browser: ++

++ ++
++<form action="" method="POST">
++  <input type="text" name="baz" />
++  <input type="text" name="quux" />
++  <input type="submit" />
++</form>
++ ++

++ Accessing this information is such a common procedure that Nevow provides ++ a convenience method on the context to do it. Let's examine a simple page ++ whose output can be influenced by the query parameters in the URL used to ++ access it: ++

++ ++
++def showChoice(ctx, data):
++    choice = ctx.arg('choice')
++    if choice is None:
++        return ''
++    return "You chose ", choice, "."
++
++class Custom(rend.Page):
++    docFactory = loaders.stan(tags.html[
++        tags.a(href="?choice=baz")["Baz"],
++        tags.a(href="?choice=quux")["Quux"],
++        tags.p[showChoice]])
++    
++ ++

++ The procedure is exactly the same for simple form post information: ++

++ ++
++def greet(ctx, data):
++    name = ctx.arg('name')
++    if name is None:
++        return ''
++    return "Greetings, ", name, "!"
++
++class Form(rend.Page):
++    docFactory = loaders.stan(tags.html[
++        tags.form(action="", method="POST")[
++            tags.input(name="name"),
++            tags.input(type="submit")],
++        greet])
++
++ ++

++ Note that ctx.arg returns only the first argument with the ++ given name. For complex cases where multiple arguments and lists of ++ argument values are required, you can access the request argument ++ dictionary directly using the syntax: ++

++ ++
++def arguments(ctx, data):
++    args = inevow.IRequest(ctx).args
++    return "Request arguments are: ", str(args)
++    
++ ++

Generators in the DOM

++ ++

++ One common operation when building dynamic pages is iterating a list of ++ data and emitting some HTML for each item. Python generators are well ++ suited for expressing this sort of logic, and code which is written as a ++ python generator can perform tests (if) and loops of various ++ kinds (while, for) and emit a row of html ++ whenever it has enough data to do so. Nevow can handle generators in the ++ DOM just as gracefully as it can handle anything else: ++

++ ++
++>>> from nevow import rend, loaders, tags
++>>> def generate(ctx, items):
++...     for item in items:
++...         yield tags.div[ item ]
++...
++>>> class List(rend.Page):
++...     docFactory = loaders.stan(tags.html[ generate ])
++...
++>>> List(['one', 'two', 'three']).renderSynchronously()
++'<html><div>one</div><div>two</div><div>three</div></html>'
++ ++

++ As you can see, generating HTML inside of functions or generators can be ++ very convenient, and can lead to very rapid application ++ development. However, it is also what I would call a "template ++ abstraction violation", and we will learn how we can keep knowledge of ++ HTML out of our python code when we learn about patterns and slots. ++

++ ++

Methods in the DOM

++ ++

++ Up until now, we have been placing our template manipulation logic inside ++ of simple Python functions and generators. However, it is often ++ appropriate to use a method instead of a function. Nevow makes it just as ++ easy to use a method to render HTML: ++

++ ++
++class MethodRender(rend.Page):
++    def __init__(self, foo):
++        self.foo = foo
++
++    def render_foo(self, ctx, data):
++        return self.foo
++
++    docFactory = loaders.stan(tags.html[ render_foo ])
++    
++ ++

++ Using render methods makes it possible to parameterize your Page class ++ with more parameters. With render methods, you can also use the Page ++ instance as a state machine to keep track of the state of the ++ render. While Nevow is designed to allow you to render the same Page ++ instance repeatedly, it can also be convenient to know that a Page ++ instance will only be used one time, and that the Page instance can be ++ used as a scratch pad to manage information about the render. ++

++ ++

Data specials

++ ++

++ Previously we saw how passing a parameter to the default Page constructor ++ makes it available as the "data" parameter to all of our render ++ methods. This "data" parameter can change as the page render proceeds, ++ and is a useful way to ensure that render functions are isolated and only ++ act upon the data which is available to them. Render functions which do ++ not pull information from sources other than the "data" parameter are ++ more easily reusable and can be composed into larger parts more easily. ++

++ ++

++ Deciding which data gets passed as the data parameter is as simple as ++ changing the "Data special" for a Tag. See the Glossary under "Tag Specials" for more ++ information about specials. Assigning to the data special is as simple as ++ assigning to a tag attribute: ++

++ ++
++>>> def hello(ctx, name):
++...     return "Hello, ", name
++...
++>>> class DataSpecial(rend.Page):
++...     docFactory = loaders.stan(tags.html[
++...     tags.div(data="foo")[ hello ],
++...     tags.div(data="bar")[ hello ]])
++...
++>>> DataSpecial().renderSynchronously()
++'<html><div>Hello, foo</div><div>Hello, bar</div></html>'
++ ++

++ Data specials may be assigned any python value. Data specials are only in ++ scope during the rendering of the tag they are assigned to, so if the ++ "hello" renderer were placed in the DOM inside the html node directly, ++ "Hello, None" would be output. ++

++ ++

++ Before data is passed to a render function, Nevow first checks to see if ++ there is an IGettable adapter for it. If there ++ is, it calls IGettable.get(), and passes the result of this ++ as the data parameter instead. Nevow includes an IGettable ++ adapter for python functions, which means you can set a Tag data special ++ to a function reference and Nevow will call it to obtain the data when ++ the Tag is rendered. The signature for data methods is similar to that of ++ render methods, (ctx, data). For example: ++

++ ++
++def getName(ctx, data):
++    return ctx.arg('name')
++
++def greet(ctx, name):
++    return "Greetings, ", name
++
++class GreetName(rend.Page):
++    docFactory = loaders.stan(tags.html[
++        tags.form(action="")[
++            tags.input(name="name"),
++            tags.input(type="submit")],
++            tags.div(data=getName)[ greet ]])
++    
++ ++

++ Data specials exist mainly to allow you to construct and enforce a ++ Model-View-Controller style separation of the Model code from the ++ View. Here we see that the greet function is capable of rendering a ++ greeting view for a name model, and that the implementation of getName ++ may change without the view code changing. ++

++ ++

Render specials

++ ++

++ Previously, we have seen how render functions can be placed directly in ++ the DOM, and the return value replaces the render function in the ++ DOM. However, these free functions and methods are devoid of any ++ contextual information about the template they are living in. The ++ render special is a way to associate a render function or method with a ++ particular Tag instance, which the render function can then examine to ++ decide how to render: ++

++ ++
++>>> def alignment(ctx, data):
++...     align = ctx.tag.attributes.get('align')
++...     if align == 'right':
++...         return ctx.tag["Aligned right"]
++...     elif align == 'center':
++...         return ctx.tag["Aligned center"]
++...     else:
++...         return ctx.tag["Aligned left"]
++...
++>>> class AlignmentPage(rend.Page):
++...     docFactory = loaders.stan(tags.html[
++...     tags.p(render=alignment),
++...     tags.p(render=alignment, align="center"),
++...     tags.p(render=alignment, align="right")])
++...
++>>> AlignmentPage().renderSynchronously()
++'<html><p>Aligned left</p><p align="center">Aligned center</p><p align="right">Aligned right</p></html>'
++ ++

++ Note how the alignment renderer has access to the template node as ++ ctx.tag. It can examine and change this node, and the return value of ++ the render function replaces the original node in the DOM. Note that ++ here we are returning the template node after changing it. We will see ++ later how we can instead mutate the context and use slots so that the ++ knowledge the renderer requires about the structure of the template is ++ reduced even more. ++

++ ++

Pattern specials

++ ++

++ When writing render methods, it is easy to inline the construction of ++ Tag instances to generate HTML programatically. However, this creates a ++ template abstraction violation, where part of the HTML which will show ++ up in the final page output is hidden away inside of render methods ++ instead of inside the template. Pattern specials are designed to avoid ++ this problem. A node which has been tagged with a pattern special can ++ then be located and copied by a render method. The render method does ++ not need to know anything about the structure or location of the ++ pattern, only it's name. ++

++ ++

++ We can rewrite our previous generator example so that the generator ++ does not have to know what type of tag the template designer would like ++ repeated for each item in the list: ++

++ ++
++>>> from nevow import rend, loaders, tags, inevow
++>>> def generate(ctx, items):
++...     pat = inevow.IQ(ctx).patternGenerator('item')
++...     for item in items:
++...         ctx.tag[ pat(data=item) ]
++...     return ctx.tag
++...
++>>> def string(ctx, item):
++...     return ctx.tag[ str(item) ]
++...
++>>> class List(rend.Page):
++...     docFactory = loaders.stan(tags.html[
++...     tags.ul(render=generate)[
++...         tags.li(pattern="item", render=string)]])
++...
++>>> List([1, 2, 3]).renderSynchronously()
++'<html><ol><li>1</li><li>2</li><li>3</li></ol></html>'
++ ++

++ Note that we have to mutate the tag in place and repeatedly copy the ++ item pattern, applying the item as the data special to the resulting ++ Tag. It turns out that this is such a common operation that nevow comes ++ out of the box with these two render functions: ++

++ ++
++>>> class List(rend.Page):
++...     docFactory = loaders.stan(tags.html[
++...     tags.ul(render=rend.sequence)[
++...         tags.li(pattern="item", render=rend.data)]])
++...
++>>> List([1, 2, 3]).renderSynchronously()
++'<html><ul><li>1</li><li>2</li><li>3</li></ul></html>'
++ ++

Slot specials

++ ++

++ The problem with render methods is that they are only capable of making ++ changes to their direct children. Because of the architecture of Nevow, ++ they should not attempt to change grandchildren or parent nodes. It is ++ possible to write one render method for every node you wish to change, ++ but there is a better way. A node with a slot special can be "filled" ++ with content by any renderer above the slot. Creating a slot special is ++ such a frequent task that there is a prototype in nevow.tags ++ which is usually used. ++

++ ++

++ Let us examine a renderer which fills a template with information about ++ a person: ++

++ ++
++>>> from nevow import loaders, rend, tags
++...
++>>> person = ('Donovan', 'Preston', 'Male', 'California')
++...
++>>> def render_person(ctx, person):
++...     firstName, lastName, sex, location = person
++...     ctx.fillSlots('firstName', firstName)
++...     ctx.fillSlots('lastName', lastName)
++...     ctx.fillSlots('sex', sex)
++...     ctx.fillSlots('location', location)
++...     return ctx.tag
++...
++>>> class PersonPage(rend.Page):
++...     docFactory = loaders.stan(tags.html(render=render_person)[
++...     tags.table[
++...         tags.tr[
++...             tags.td[tags.slot('firstName')],
++...             tags.td[tags.slot('lastName')],
++...             tags.td[tags.slot('sex')],
++...             tags.td[tags.slot('location')]]]])
++...
++>>> PersonPage(person).renderSynchronously()
++'<html><table><tr><td>Donovan</td><td>Preston</td><td>Male</td><td>California</td></tr></table></html>'
++ ++

++ Using patterns in combination with slots can lead to very powerful ++ template abstraction. Nevow also includes another standard renderer ++ called "mapping" which takes any data which responds to the "items()" ++ message and inserts the items into appropriate slots: ++

++ ++
++>>> class DictPage(rend.Page):
++...     docFactory = loaders.stan(tags.html(render=rend.mapping)[
++...         tags.span[ tags.slot('foo') ], tags.span[ tags.slot('bar') ]])
++...
++>>> DictPage(dict(foo=1, bar=2)).renderSynchronously()
++'<html><span>1</span><span>2</span></html>'
++ ++

Data directives

++ ++

++ So far, we have always placed data functions directly in the Data ++ special attribute of a Tag. Sometimes, it is preferable to look up a ++ data method from the Page class as the Page has being rendered. For ++ example, a base class may define a template and a subclass may provide ++ the implementation of the data method. We can accomplish this effect by ++ using a data directive as a Tag's data special: ++

++ ++
++class Base(rend.Page):
++    docFactory = loaders.stan(tags.html[
++        tags.div(data=tags.directive('name'), render=rend.data)])
++
++class Subclass(Base):
++    def data_name(self, ctx, data):
++        return "Your name"
++    
++ ++

++ The data directive is resolved by searching for the ++ IContainer implementation in the context. ++ rend.Page implements IContainer.get by ++ performing an attribute lookup on the Page with the prefix 'data_*'. You ++ can provide your own IContainer implementation if you wish, ++ and also you should know that IContainer implementations for ++ list and dict are included in the nevow.accessors ++ module. ++

++ ++

++ A common gotcha is that the closest IContainer is used to ++ resolve data directives. This means that if a list is being used as the ++ data during the rendering process, data directives below this will be ++ resolved against the IContainer implementation in ++ nevow.accessors.ListAccessor. If you are expecting a data ++ directive to invoke a Page's data_* method but instead get a ++ KeyError, this is why. ++

++ ++

Render directives

++ ++

++ Render directives are almost exactly the same, except they are resolved ++ using the closest IRendererFactory implementation in the ++ context. Render directives can be used to allow subclasses to override ++ certain render methods, and also can be used to allow Fragments to ++ locate their own prefixed render methods. ++

++ ++

Flatteners

++ ++

++ TODO This section isn't done yet. ++

++ ++

++ Nevow's flatteners use a type/function registry to determine how to ++ render objects which Nevow encounters in the DOM during the rendering ++ process. "Explicit is better than implicit", so in most cases, ++ explicitly applying render methods to data will be better than ++ registering a flattener, but in some cases it can be useful: ++

++ ++
++class Person(object):
++    def __init__(self, firstName, lastName):
++        self.firstName = firstName
++        self.lastName = lastName
++
++def flattenPerson(person, ctx):
++    return flat.partialflatten(ctx, (person.firstName, " ", person.lastName))
++
++from nevow import flat
++flat.registerFlattener(flattenPerson, Person)
++
++def insertData(ctx, data):
++    return data
++
++class PersonPage(rend.Page):
++    docFactory = loaders.stan(tags.html[insertData])
++    
++ ++ +diff -Naur Nevow-0.10.0/doc/howto/template.tpl Nevow-0.10.0-new/doc/howto/template.tpl +--- Nevow-0.10.0/doc/howto/template.tpl 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/template.tpl 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,24 @@ ++ ++ ++ ++ ++ ++ ++ Nevow: ++ ++ ++ ++ ++ ++

++
++
++ ++
++ ++

Index

++ Version: ++ ++ ++ +diff -Naur Nevow-0.10.0/doc/howto/traversal.xhtml Nevow-0.10.0-new/doc/howto/traversal.xhtml +--- Nevow-0.10.0/doc/howto/traversal.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/traversal.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,448 @@ ++ ++ ++ ++ ++ ++ ++ Object Traversal ++ ++ ++ ++

Object Traversal

++ ++

++ Object traversal is the process Nevow uses to determine ++ what object to use to render HTML for a particular URL. When an HTTP ++ request comes in to the web server, the object publisher splits the URL ++ into segments, and repeatedly calls methods which consume path segments ++ and return objects which represent that path, until all segments have ++ been consumed. At the core, the Nevow traversal API is very ++ simple. However, it provides some higher level functionality layered on ++ top of this to satisfy common use cases. ++

++ ++

Object Traversal Basics

++ ++

++ The root resource is the top-level object in the URL ++ space; it conceptually represents the URI /. The Nevow ++ object traversal and object publishing ++ machinery uses only two methods to locate an object suitable for ++ publishing and to generate the HTML from it; these methods are described ++ in the interface nevow.inevow.IResource: ++

++ ++
++class IResource(Interface):
++    def locateChild(self, ctx, segments):
++        """Locate another object which can be adapted to IResource
++        Return a tuple of resource, path segments
++        """
++
++    def renderHTTP(self, ctx):
++        """Render a request
++        """
++    
++ ++

++ renderHTTP can be ++ as simple as a method which simply returns a string of HTML. Let's ++ examine what happens when object traversal occurs over a very simple root ++ resource: ++

++ ++
++from zope.interface import implements
++
++class SimpleRoot(object):
++    implements(inevow.IResource)
++
++    def locateChild(self, ctx, segments):
++        return self, ()
++
++    def renderHTTP(self, ctx):
++        return "Hello, world!"
++    
++ ++

++ This resource, when passed as the root resource to appserver.NevowSite or wsgi.createWSGIApplication, will immediately return ++ itself, consuming all path segments. This means that for every URI a user ++ visits on a web server which is serving this root resource, the text ++ "Hello, world!" will be rendered. Let's examine the value of ++ segments for various values of URI: ++

++ ++
    ++
  • / - ('',)
  • ++
  • /foo/bar - ('foo', 'bar')
  • ++
  • ++ /foo/bar/baz.html - ++ ('foo', 'bar', 'baz.html') ++
  • ++
  • ++ /foo/bar/directory/ - ++ ('foo', 'bar', 'directory', '') ++
  • ++
++ ++

++ So we see that Nevow does nothing more than split the URI on the string ++ / and pass these path segments to our application for ++ consumption. Armed with these two methods alone, we already have enough ++ information to write applications which service any form of URL ++ imaginable in any way we wish. However, there are some common URL ++ handling patterns which Nevow provides higher level support for. ++

++ ++

locateChild In Depth

++ ++

++ One common URL handling pattern involves parents which only know about ++ their direct children. For example, a ``Directory`` object may only know ++ about the contents of a single directory, but if it contains other ++ directories, it does not know about the contents of them. Let's examine a ++ simple ``Directory`` object which can provide directory listings and ++ serves up objects for child directories and files: ++

++ ++
++from zope.interface import implements
++
++class Directory(object):
++    implements(inevow.IResource)
++
++    def __init__(self, directory):
++        self.directory = directory
++
++    def renderHTTP(self, ctx):
++        html = ['<ul>']
++        for child in os.listdir(self.directory):
++            fullpath = os.path.join(self.directory, child)
++            if os.path.isdir(fullpath):
++                child += '/'
++            html.extend(['<li><a href="', child, '">', child, '</a></li>'])
++        html.append('</ul>')
++        return ''.join(html)
++
++    def locateChild(self, ctx, segments):
++        name = segments[0]
++        fullpath = os.path.join(self.directory, name)
++        if not os.path.exists(fullpath):
++            return None, () # 404
++
++        if os.path.isdir(fullpath):
++            return Directory(fullpath), segments[1:]
++        if os.path.isfile(fullpath):
++            return static.File(fullpath), segments[1:]
++    
++ ++

++ Because this implementation of locateChild only consumed one ++ segment and returned the rest of them (segments[1:]), the ++ object traversal process will continue by calling ++ locateChild on the returned resource and passing the ++ partially-consumed segments. In this way, a directory structure of any ++ depth can be traversed, and directory listings or file contents can be ++ rendered for any existing directories and files. ++

++ ++

++ So, let us examine what happens when the URI ++ "/foo/bar/baz.html" is traversed, where "foo" ++ and "bar" are directories, and "baz.html" is a ++ file. ++

++ ++
    ++
  1. ++ ++ Directory('/').locateChild(ctx, ('foo', 'bar', 'baz.html')) ++ ++ returns ++ Directory('/foo'), ('bar', 'baz.html') ++
  2. ++
  3. ++ ++ Directory('/foo').locateChild(ctx, ('bar', 'baz.html')) ++ ++ returns ++ Directory('/foo/bar'), ('baz.html, ) ++
  4. ++
  5. ++ ++ Directory('/foo/bar').locateChild(ctx, ('baz.html')) ++ ++ returns ++ File('/foo/bar/baz.html'), () ++
  6. ++
  7. ++ No more segments to be consumed; ++ File('/foo/bar/baz.html').renderHTTP(ctx) is called, and ++ the result is sent to the browser. ++
  8. ++
++ ++ ++

childFactory Method

++ ++

++ Consuming one URI segment at a time by checking to see if a requested ++ resource exists and returning a new object is a very common ++ pattern. Nevow's default implementation of IResource, nevow.rend.Page, contains an implementation of ++ locateChild which provides more convenient hooks for ++ implementing object traversal. One of these hooks is ++ childFactory. Let us imagine for the sake of example that we ++ wished to render a tree of dictionaries. Our data structure might look ++ something like this: ++

++ ++
++tree = dict(
++    one=dict(
++        foo=None,
++        bar=None),
++    two=dict(
++        baz=dict(
++        quux=None)))
++    
++ ++

++ Given this data structure, the valid URIs would be: ++

++ ++
    ++
  • /
  • ++
  • /one
  • ++
  • /one/foo
  • ++
  • /one/bar
  • ++
  • /two
  • ++
  • /two/baz
  • ++
  • /two/baz/quux
  • ++
++ ++

++ Let us construct a rend.Page ++ subclass which uses the default locateChild implementation ++ and overrides the childFactory hook instead: ++

++ ++
++class DictTree(rend.Page):
++    def __init__(self, dataDict):
++        self.dataDict = dataDict
++
++    def renderHTTP(self, ctx):
++        if self.dataDict is None:
++            return "Leaf"
++        html = ['<ul>']
++        for key in self.dataDict.keys():
++            html.extend(['<li><a href="', key, '">', key, '</a></li>'])
++        html.append('</ul>')
++        return ''.join(html)
++
++    def childFactory(self, ctx, name):
++        if name not in self.dataDict:
++            return rend.NotFound # 404
++        return DictTree(self.dataDict[name])
++    
++ ++

++ As you can see, the childFactory implementation is ++ considerably shorter than the equivalent locateChild ++ implementation would have been. ++

++ ++

child_* methods and attributes

++ ++

++ Often we may wish to have some hardcoded URLs which are not dynamically ++ generated based on some data structure. For example, we might have an ++ application which uses an external CSS stylesheet, an external JavaScript ++ file, and a folder full of images. The rend.Page.locateChild implementation provides a ++ convenient way for us to express these relationships by using ++ child-prefixed methods: ++

++ ++
++class Linker(rend.Page):
++    def renderHTTP(self, ctx):
++        return """<html>
++<head>
++    <link href="css" rel="stylesheet" />
++    <script type="text/javascript" src="scripts" />
++  <body>
++    <img src="images/logo.png" />
++  </body>
++</html>"""
++
++    def child_css(self, ctx):
++        return static.File('styles.css')
++
++    def child_scripts(self, ctx):
++        return static.File('scripts.js')
++
++    def child_images(self, ctx):
++        return static.File('images/')
++    
++ ++

++ One thing you may have noticed is that all of the examples so far have ++ returned new object instances whenever they were implementing a traversal ++ API. However, there is no reason these instances cannot be shared. One ++ could for example return a global resource instance, an instance which ++ was previously inserted in a dict, or lazily create and cache dynamic ++ resource instances on the fly. The rend.Page.locateChild ++ implementation also provides a convenient way to express that one global ++ resource instance should always be used for a particular URL, the ++ child-prefixed attribute: ++

++ ++
++class FasterLinker(Linker):
++    child_css = static.File('styles.css')
++    child_scripts = static.File('scripts.js')
++    child_images = static.File('images/')
++    
++ ++

Dots in child names

++ ++

++ When a URL contains dots, which is quite common in normal URLs, it is ++ simple enough to handle these URL segments in locateChild or ++ childFactory -- one of the passed segments will simply be a ++ string containing a dot. However, it is not immediately obvious how one ++ would express a URL segment with a dot in it when using child-prefixed ++ methods. The solution is really quite simple: ++

++ ++
++class DotChildren(rend.Page):
++    def renderHTTP(self, ctx):
++        return """
++        <html>
++          <head>
++            <script type="text/javascript" src="scripts.js" />
++          </head>
++        </html>"""
++
++setattr(DotChildren, 'child_scripts.js', static.File('scripts.js'))
++    
++ ++

++ The same technique could be used to install a child method with a dot in ++ the name. ++

++ ++ ++

children dictionary

++ ++

++ The final hook supported by the default implementation of ++ locateChild is the rend.Page.children ++ dictionary: ++

++ ++
++class Main(rend.Page):
++    children = {
++        'people': People(),
++        'jobs': Jobs(),
++        'events': Events()}
++
++    def renderHTTP(self, ctx):
++        return """
++        <html>
++          <head>
++            <title>Our Site</title>
++          </head>
++          <body>
++            <p>bla bla bla</p>
++          </body>
++        </html>"""
++    
++ ++

++ Hooks are checked in the following order: ++

++ ++
    ++
  1. self.children
  2. ++
  3. self.child_*
  4. ++
  5. self.childFactory
  6. ++
++ ++

The default trailing slash handler

++ ++

++ When a URI which is being handled ends in a slash, such as when the ++ / URI is being rendered or when a directory-like URI is ++ being rendered, the string '' appears in the path segments ++ which will be traversed. Again, handling this case is trivial inside ++ either locateChild or childFactory, but it may ++ not be immediately obvious what child-prefixed method or attribute will ++ be looked up. The method or attribute name which will be used is simply ++ child with a single trailing underscore. ++

++ ++

++ The rend.Page class provides an implementation of this ++ method which can work in two different ways. If the attribute ++ addSlash is True, the default trailing slash ++ handler will return self. In the case when ++ addSlash is True, the default ++ rend.Page.renderHTTP implementation will simply perform a ++ redirect which adds the missing slash to the URL. ++

++ ++

++ The default trailing slash handler also returns self if ++ addSlash is False, but emits a warning as it ++ does so. This warning may become an exception at some point in the ++ future. ++

++ ++

ICurrentSegments and IRemainingSegments

++ ++

++ During the object traversal process, it may be useful to discover which ++ segments have already been handled and which segments are remaining to be ++ handled. This information may be obtained from the context ++ object which is passed to all the traversal APIs. The interfaces nevow.inevow.ICurrentSegments and nevow.inevow.IRemainingSegments are used to retrieve ++ this information. To retrieve a tuple of segments which have previously ++ been consumed during object traversal, use this syntax: ++

++ ++
++segs = ICurrentSegments(ctx)
++    
++ ++

++ The same is true of IRemainingSegments. ++ IRemainingSegments is the same value which is passed as ++ segments to locateChild, but may also be useful ++ in the implementations of childFactory or a child-prefixed ++ method, where this information would not otherwise be available. ++

++ ++

Conclusion

++ ++

++ Nevow makes it easy to handle complex URL hierarchies. The most basic ++ object traversal interface, nevow.inevow.IResource.locateChild, provides powerful ++ and flexible control over the entire object traversal process. Nevow's ++ canonical IResource implementation, rend.Page, ++ also includes the convenience hooks childFactory along with ++ child-prefixed method and attribute semantics to simplify common use ++ cases. ++

++ ++ +diff -Naur Nevow-0.10.0/doc/howto/xmltemplates.xhtml Nevow-0.10.0-new/doc/howto/xmltemplates.xhtml +--- Nevow-0.10.0/doc/howto/xmltemplates.xhtml 1970-01-01 01:00:00.000000000 +0100 ++++ Nevow-0.10.0-new/doc/howto/xmltemplates.xhtml 2010-03-02 20:42:55.000000000 +0100 +@@ -0,0 +1,407 @@ ++ ++ ++ ++ ++ ++ ++ XML Templates ++ ++ ++ ++

Nevow XML Templates

++ ++

++ Stan syntax is cool, but eventually you are going to want to integrate ++ your Python code with a template designed by an HTML monkey. Nevow ++ accomplishes this by providing an xmlfile loader which uses the built-in ++ Python SAX libraries to generate a tree of stan behind the scenes. The ++ general rule is anything that is possible in stan should be possible in a ++ pure XML template; of course, the XML syntax is generally going to be ++ much more verbose. ++

++ ++

loaders.xmlfile

++ ++

++ Wherever you have seen a loaders.stan being created in any of the example ++ code, a loaders.xmlfile can be ++ substituted instead. At the most basic, xmlfile merely ++ requires the name of an xml template: ++

++ ++
++class HelloXML(rend.Page):
++    docFactory = loaders.xmlfile('hello.xml')
++    
++ ++

++ Placing the following xml in the hello.xml file will cause ++ HelloXML to display a static page when it is rendered: ++

++ ++
<html>Hello, world!</html>
++ ++

++ The following additional keyword arguments may be given to ++ xmlfile to configure it: ++

++ ++
    ++
  • ++ templateDirectory: ++ The path to the directory which contains the template file. Defaults to ''. ++
  • ++
  • ++ ignoreDocType: ++ If True, discard any DOCTYPE declaration when building the DOM from ++ this template. When false, preserve the DOCTYPE, causing it to show up ++ in the final output. Useful for when you are inserting an XML fragment ++ into a larger page and do not wish to generate invalid XML as ++ output. Defaults to False. ++
  • ++
  • ++ ignoreComment: ++ If True, discard XML comments, causing them to disappear from the ++ output. If False, preserve comments and render them in the final output ++ unchanged. Defaults to False. ++
  • ++
  • ++ pattern: ++ If present, the given pattern name will be looked up and used as the ++ root of the template. If not present, the entire document will be used ++ as the template. Useful for embedding fragments of an XML document in a ++ larger page. Defaults to None. ++
  • ++
++ ++

Nevow's xmlns declaration

++ ++

++ In order for Nevow to notice and process any XML directives in the ++ template file, you must declare the Nevow xmlns at the top of your XML ++ document. Nevow's xmlns is: ++

++ ++
http://nevow.com/ns/nevow/0.1
++ ++

++ The syntax for declaring that your xml document uses this namespace is: ++

++ ++
<html xmlns:nevow="http://nevow.com/ns/nevow/0.1"></html>
++ ++

++ You may replace the text "nevow" in the above example with any name you ++ choose. For example, many people use "n" because it is shorter to ++ type. If you do so, be sure to replace all occurrences of the nevow ++ namespace in the examples with the namespace name you choose. ++

++ ++

Nevow's Tag Attribute Language

++ ++

++ The markup you will add to your XHTML file in order to invoke Nevow code ++ consists mostly of namespaced tag attributes. This approach was ++ influenced heavily by the Zope Page Templates (ZPT) Tag Attribute ++ Language (TAL). However, I felt that TAL did not go far enough in ++ removing control flow and branching possibilities from the XML ++ template. Nevow's main philosophy is that it should be as easy as ++ possible to move from the XML document into Python code, and that the ++ Python code should have ultimate control over manipulating the structure ++ of the XML template. ++

++ ++

++ The key is that it is easy to expose Python methods that you write to ++ your XML template, and it is easy for the XML templates to mark nodes ++ which it wishes the Python method to manipulate. In this way, if either ++ the Python implementation changes or the location or content of the ++ marked nodes change in the XML template, the other side will be isolated ++ from these changes. ++

++ ++

++ Nevow's XML templating has two attributes which invoke Python code: ++

++ ++
    ++
  • ++ nevow:render -- ++ Invokes a Python method and replaces the template node with the result ++
  • ++
  • ++ nevow:data -- ++ Invokes a Python method and sets the data special for the node to the ++ result ++
  • ++
++ ++

++ It has one attribute which marks nodes as manipulatable by Python code: ++

++ ++
    ++
  • ++ nevow:pattern -- ++ Gives a node a name so that Python code may clone and mutate copies of ++ this node ++
  • ++
++ ++

++ It also has two namespaced tags: ++

++ ++
    ++
  • ++ nevow:slot -- ++ Works in the same way as the slot attribute ++
  • ++
  • ++ nevow:attr -- ++ Indicates that an attribute of the parent tag should be manipulated by ++ Python code in some way ++
  • ++
++ ++

nevow:render

++ ++

++ When the nevow:render attribute is encountered, the xmlfile ++ loader sets the render special to a directive constructed with the ++ attribute value. When the template is rendered, this means that the ++ appropriate render_* method will be looked up on the ++ IRendererFactory (generally the Page instance): ++

++ ++
<html><div nevow:render="foo" /></html>
++ ++

++ With the render_foo method:: ++

++ ++
++def render_foo(self, ctx, data):
++    return "Hello"
++    
++ ++

++ Will result in the document: ++

++ ++
<html>Hello</html>
++ ++

++ Note that the return value of the render method replaces the template ++ node in the DOM, so if you want the template node to remain, you should ++ use ctx.tag. ++

++ ++

Built-in renderers

++ ++

++ Nevow comes with various built in renderers on the Page class. ++

++ ++
    ++
  • ++ data: ++ Renders the current data as-is inside the current node. ++
  • ++
  • ++ string: ++ Renders the current data as a string inside the current node. ++
  • ++
  • ++ sequence: ++ Iterates the current data, copying the "item" pattern for each ++ item. Sets the the data special of the new node to the item, and ++ inserts the result in the current node. See the nevow.rend.sequence ++ docstring for information about other used patterns, including ++ "header", "divider", "footer" and "empty". ++
  • ++
  • ++ mapping: ++ Calls .items() on the current data, and calls ctx.fillSlots(key, value) ++ for every key, value pair in the result. Returns the template tag. ++
  • ++
  • ++ xml: ++ Inserts the current data into the template after wrapping it in an xml ++ instance. Not very useful in practice. ++
  • ++
++ ++

nevow:data

++ ++

++ When the nevow:data attribute is encountered, the xmlfile ++ loader sets the data special of the current node to a directive ++ constructed with the attribute value. When the template is rendered, ++ this means that the appropriate data_* method will be looked up on the ++ current IContainer (generally the Page instance). The data_* ++ method will be called, and the result will be set as the data special of ++ the current Tag: ++

++ ++
<html><div nevow:data="name" nevow:render="data" /></html>
++ ++

++ With the data_name method: ++

++ ++
++def data_name(self, ctx, data):
++    return "Hello!"
++    
++ ++

++ Will result in the document: ++

++ ++
<html><div>Hello!</div></html>
++ ++

++ Note that with a data attribute on a node but no renderer, the result of ++ the data method will be set as the data special for that tag, and child ++ render methods will be passed this data. ++

++ ++

nevow:pattern

++ ++

++ When the nevow:pattern attribute is encountered, the xmlfile ++ loader sets the pattern special of the current node to the attribute ++ value as a string. Renderers which are above this node may then make ++ copies of it using the nevow.inevow.IQ of the ++ current context. With the template: ++

++ ++
<html nevow:render="stuff"><div nevow:pattern="somePattern" nevow:render="data" /></html>
++ ++

++ And the renderer: ++

++ ++
++def render_stuff(self, ctx, data):
++    pat = inevow.IQ(ctx).patternGenerator('somePattern')
++    return [pat(data=1), pat(data=2)]
++    
++ ++

++ Will result in the document: ++

++ ++
<html><div>1</div><div>2</div></html>
++ ++

nevow:slot

++ ++

++ When the nevow:slot tag is encountered, the xmlfile loader ++ constructs a nevow.stan.slot instance, passing ++ the name attribute value as the slot name. The children of the slot node ++ are added as children of the new slot instance. This is useful if you ++ wish to put patterns inside the slot. With the template: ++

++ ++
<html nevow:render="stuff"><nevow:slot name="slotName" /></html>
++ ++

++ And the render method: ++

++ ++
++def render_stuff(self, ctx, data):
++    ctx.fillSlots('slotName', "Hello.")
++    return ctx.tag
++    
++ ++

++ This document will be produced: ++

++ ++
<html>Hello.</html>
++ ++

nevow:attr

++ ++

++ When the nevow:attr tag is encountered, the contents of the ++ nevow:attr node will be assigned to the attribute of the parent tag with ++ the name of the value of the name attribute. Perhaps an example will be a ++ little clearer: ++

++ ++
<html><a><nevow:attr name="href">HELLO!</nevow:attr>Goodbye</a></html>
++ ++

++ This document will be produced: ++

++ ++
<html><a href="HELLO!">Goodbye</a></html>
++ ++

++ While this syntax is somewhat awkward, every other type of nevow tag and ++ attribute may be used inside the nevow:attr node. This makes ++ setting attributes of tags uniform with every other method of ++ manipulating the XML template. ++

++ ++

nevow:invisible

++ ++

++ Sometimes you need to group some elements, because you need to use a ++ renderer for a group of children. ++

++ ++

++ However, it may not be desirable to give these elements a parent/child ++ relationship in your XML structure. For these cases, use ++ nevow:invisible. ++

++ ++

++ As suggested by the name, a nevow:invisible tag is removed ++ in the rendered XML. Here is an example: ++

++ ++
<html><nevow:invisible nevow:data="name" nevow:render="data" /></html>
++ ++

++ With the data_name method: ++

++ ++
++def data_name(self, ctx, data):
++    return "Hello!"
++    
++ ++

++ Will result in the document: ++

++ ++
<html>Hello!</html>
++ ++

xmlstr, htmlfile, and htmlstr

++ ++

++ xmlstr is a loader which is identical to xmlfile except it takes a string ++ of XML directly. ++

++ ++

++ htmlfile and htmlstr should generally be avoided. They are similar to ++ xmlfile and xmlstr, except they use twisted.web.microdom in ++ beExtremelyLenient mode to attempt to parse badly-formed HTML (non-XHTML) ++ templates. See the nevow.loaders docstrings for more information. ++

++ ++

Conclusions

++ ++

++ Nevow's xmlfile tag attribute language allows you to integrate ++ externally-designed XHTML templates into the Nevow rendering process. ++

++ ++ diff -Nru nevow-0.9.31/debian/patches/fix_manpage.patch nevow-0.10.0/debian/patches/fix_manpage.patch --- nevow-0.9.31/debian/patches/fix_manpage.patch 2010-04-12 12:25:16.000000000 +0100 +++ nevow-0.10.0/debian/patches/fix_manpage.patch 1970-01-01 01:00:00.000000000 +0100 @@ -1,15 +0,0 @@ ---- Nevow-0.9.26/doc/man/nevow-xmlgettext.1 2005-02-18 23:45:52.000000000 +0100 -+++ Nevow-0.9.26/doc/man/nevow-xmlgettext.1 2007-11-10 10:51:17.000000000 +0100 -@@ -9,10 +9,10 @@ - List files to process on command line. - Will output strings in PO format to standard output. - .TP --\fB\--help\fR -+\fB\-\-help\fR - Display usage information. - .TP --\fB\--version\fR -+\fB\-\-version\fR - Output version information and exit. - .SH AUTHOR - Written by Tommi Virtanen. diff -Nru nevow-0.9.31/debian/patches/pluginfix_twisted_8.patch nevow-0.10.0/debian/patches/pluginfix_twisted_8.patch --- nevow-0.9.31/debian/patches/pluginfix_twisted_8.patch 2010-04-12 12:25:16.000000000 +0100 +++ nevow-0.10.0/debian/patches/pluginfix_twisted_8.patch 1970-01-01 01:00:00.000000000 +0100 @@ -1,28 +0,0 @@ -Index: /trunk/Nevow/twisted/plugins/nevow_widget.py -=================================================================== ---- Nevow/twisted/plugins/nevow_widget.py (revision 12073) -+++ Nevow/twisted/plugins/nevow_widget.py (revision 15723) -@@ -5,12 +5,19 @@ - """ - --from twisted.scripts.mktap import _tapHelper -+try: -+ # Twisted 8.0.1 (r23252, to be precise) introduced a public API for -+ # this. -+ from twisted.application.service import ServiceMaker -+except ImportError: -+ # For versions of Twisted older than that, fallback to the private -+ # version of the same thing. -+ from twisted.scripts.mktap import _tapHelper as ServiceMaker - --widgetServiceMaker = _tapHelper( -- "Widget Mathingwhathuh", -+widgetServiceMaker = ServiceMaker( -+ "Stand-alone Athena Widget runner", - "nevow._widget_plugin", - """ - Create a service which starts a NevowSite with a single page with a single -- widget. -+ athena widget. - """, - "athena-widget") diff -Nru nevow-0.9.31/debian/patches/python_libdir.diff nevow-0.10.0/debian/patches/python_libdir.diff --- nevow-0.9.31/debian/patches/python_libdir.diff 2010-04-12 12:25:16.000000000 +0100 +++ nevow-0.10.0/debian/patches/python_libdir.diff 1970-01-01 01:00:00.000000000 +0100 @@ -1,16 +0,0 @@ -Index: nevow-0.9.31/setup.py -=================================================================== ---- nevow-0.9.31.orig/setup.py 2009-03-20 12:39:58.000000000 +0100 -+++ nevow-0.9.31/setup.py 2009-03-20 12:45:34.000000000 +0100 -@@ -16,8 +16,9 @@ - if sys.platform.lower().startswith('win'): - site_packages = 'Lib/site-packages/' - else: -- version = '.'.join([str(i) for i in sys.version_info[:2]]) -- site_packages = 'lib/python' + version + '/site-packages/' -+ from distutils import sysconfig -+ libdir = sysconfig.get_python_lib() -+ site_packages = libdir[libdir.rfind('lib'):] - - # Turn the package_data into a data_files for 2.3 compatability - data_files = [] diff -Nru nevow-0.9.31/debian/patches/remove_deprecated_with nevow-0.10.0/debian/patches/remove_deprecated_with --- nevow-0.9.31/debian/patches/remove_deprecated_with 2010-04-12 12:25:16.000000000 +0100 +++ nevow-0.10.0/debian/patches/remove_deprecated_with 1970-01-01 01:00:00.000000000 +0100 @@ -1,13 +0,0 @@ ---- a/nevow/context.py -+++ b/nevow/context.py -@@ -34,10 +34,6 @@ - - precompile = property(lambda self: False) - -- def with(self, tag): -- warnings.warn("use WovenContext(parent, tag) instead", DeprecationWarning, stacklevel=2) -- return WovenContext(self, tag) -- - def arg(self, get, default=None): - """Placeholder until I can find Jerub's implementation of this - diff -Nru nevow-0.9.31/debian/patches/series nevow-0.10.0/debian/patches/series --- nevow-0.9.31/debian/patches/series 2010-04-12 12:25:16.000000000 +0100 +++ nevow-0.10.0/debian/patches/series 2010-03-02 19:58:12.000000000 +0000 @@ -1,4 +1 @@ -remove_deprecated_with -fix_manpage.patch -pluginfix_twisted_8.patch -python_libdir.diff +01-add-doc-from-svn.patch diff -Nru nevow-0.9.31/debian/postinst nevow-0.10.0/debian/postinst --- nevow-0.9.31/debian/postinst 2010-04-12 12:25:16.000000000 +0100 +++ nevow-0.10.0/debian/postinst 2009-04-24 21:23:30.000000000 +0100 @@ -2,14 +2,14 @@ set -e +#DEBHELPER# + case "$1" in configure) for p in $(pyversions -i); do - $p -c 'from twisted.plugin import IPlugin, getPlugins; list(getPlugins(IPlugin))' >/dev/null 2>&1 || true + $p -c 'from twisted.plugin import IPlugin, getPlugins; import nevow.plugins; list(getPlugins(IPlugin)); list(getPlugins(IPlugin, nevow.plugins))' >/dev/null 2>&1 || true done ;; esac -#DEBHELPER# - exit 0 diff -Nru nevow-0.9.31/debian/python-nevow.doc-base nevow-0.10.0/debian/python-nevow.doc-base --- nevow-0.9.31/debian/python-nevow.doc-base 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/debian/python-nevow.doc-base 2010-03-02 20:33:48.000000000 +0000 @@ -0,0 +1,11 @@ +Document: nevow +Title: Nevow howto and tutorial. +Abstract: This document is an introduction to Nevow, a next-generation + web application templating system, based on the ideas developed in + the Twisted Woven package. The documentation also contains a tutorial + for a simple chat system. +Section: Programming/Python + +Format: html +Index: /usr/share/doc/python-nevow/howto/index.html +Files: /usr/share/doc/python-nevow/howto/*.html diff -Nru nevow-0.9.31/debian/rules nevow-0.10.0/debian/rules --- nevow-0.9.31/debian/rules 2010-04-12 12:25:16.000000000 +0100 +++ nevow-0.10.0/debian/rules 2010-03-02 20:20:35.000000000 +0000 @@ -6,35 +6,39 @@ include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/class/python-distutils.mk -include /usr/share/cdbs/1/rules/patchsys-quilt.mk DEB_INSTALL_EXAMPLES_python-nevow := examples/* DEB_DH_ALWAYS_EXCLUDE := .svn DEB_COMPRESS_EXCLUDE := .py .tac words -binary-post-install/%:: - # Don't know how to use nit... Don't ship it. - rm debian/$(cdbs_curpkg)/usr/bin/nit - -# Copy the documentation and missing JS files binary-post-install/python-nevow:: - cp doc/txt/*.txt debian/$(cdbs_curpkg)/usr/share/doc/$(cdbs_curpkg) + # Clean up stuff + find debian/$(cdbs_curpkg) -type d -name .svn -print0 | xargs -0 rm -rf + find debian/$(cdbs_curpkg)/usr/share -type f -print0 | xargs -0 chmod -x -ifeq (,$(findstring nocheck,$(DEB_BUILD_OPTIONS))) -TRIAL=trial TOPMODULES:=nevow formless -binary-post-install/python-nevow:: binary-post-install/%: - chmod +x \ - 'debian/$(cdbs_curpkg)/usr/share/pyshared/nevow/test/segfault.py' - - PYTHONPATH='debian/$(cdbs_curpkg)/usr/share/pyshared' \ - '$(TRIAL)' --tbformat=emacs --reporter=bwverbose -- $(TOPMODULES) - - # Importing the modules generates .pyc files, and dh_python (which - # normally cleans them) has already been run. Remove them manually. - find 'debian/$(cdbs_curpkg)' -regex '.*/\(dropin\.cache\|*\.py[co]\)' -print0 \ - | xargs -0 rm -f -- +install/python-nevow:: + # CDBS and setuptools seems not to be friends with Python 2.6. Move plugins by hand. + [ ! -d debian/$(cdbs_curpkg)/usr/twisted ] || \ + mv debian/$(cdbs_curpkg)/usr/twisted \ + debian/$(cdbs_curpkg)/usr/lib/$(shell pyversions -d)/site-packages +ifeq (,$(findstring nocheck,$(DEB_BUILD_OPTIONS))) + # Run tests + ln -s ../../../doc \ + debian/$(cdbs_curpkg)/usr/lib/$(shell pyversions -d)/site-packages/doc + $(call cdbs_python_binary,$(shell pyversions -d)) \ + debian/runtrial.py debian/$(cdbs_curpkg) $(TOPMODULES) + rm debian/$(cdbs_curpkg)/usr/lib/$(shell pyversions -d)/site-packages/doc + find debian/$(cdbs_curpkg)/usr -name dropin.cache -print0 | xargs -0 rm endif + # Generate documentation + cd debian/$(cdbs_curpkg)/usr/doc/howto ; lore *.xhtml */*.xhtml */*/*.xhtml + find debian/$(cdbs_curpkg)/usr/doc/howto -name '*.xhtml' -print0 | xargs -0 rm + mkdir -p debian/$(cdbs_curpkg)/usr/share/doc/$(cdbs_curpkg) + mv debian/$(cdbs_curpkg)/usr/doc/howto debian/$(cdbs_curpkg)/usr/share/doc/$(cdbs_curpkg) + rm -rf debian/$(cdbs_curpkg)/usr/doc + # Remove nit + rm debian/$(cdbs_curpkg)/usr/bin/nit clean:: rm -rf _trial_temp diff -Nru nevow-0.9.31/debian/runtrial.py nevow-0.10.0/debian/runtrial.py --- nevow-0.9.31/debian/runtrial.py 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/debian/runtrial.py 2010-03-02 20:04:36.000000000 +0000 @@ -0,0 +1,16 @@ +import sys +from os.path import join +from distutils.sysconfig import get_python_lib +from site import addsitedir + +root = sys.argv.pop(1) +site_packages = get_python_lib() + +_path = sys.path[:] +sys.path[:] = [] +addsitedir(join(root, site_packages[1:])) +addsitedir(join(root, site_packages[1:].replace("dist-packages", "site-packages"))) +sys.path.extend(_path) + +from twisted.scripts.trial import run +run() diff -Nru nevow-0.9.31/debian/source/format nevow-0.10.0/debian/source/format --- nevow-0.9.31/debian/source/format 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/debian/source/format 2010-04-12 12:25:17.000000000 +0100 @@ -0,0 +1 @@ +3.0 (quilt) diff -Nru nevow-0.9.31/doc/hier.py nevow-0.10.0/doc/hier.py --- nevow-0.9.31/doc/hier.py 2005-04-19 15:45:24.000000000 +0100 +++ nevow-0.10.0/doc/hier.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,23 +0,0 @@ -title = { - "en" : "Nevow Manual" - } - -hier = \ -( -{"nevow-intro":("Introduction",None)}, -{"nevow-gettingstarted":("Getting Started",None)}, -{"nevow-traversal":("Object Traversal",None)}, -{"nevow-rendering":("Object Publishing",None)}, -{"nevow-xml-templates":("XML Templates",None)}, -{"nevow-deployment":("Deploying Nevow Applications",None)}, -{"nevow-glossary":("Glossary",None)}, - -#{"other":("Other", -# ( -# {"blah":("Blah", -# ( -# {"bar":("Bar",None)}, -# ))}, -# ))}, - -) diff -Nru nevow-0.9.31/doc/howto/chattutorial/part00/listings/echothing/echobox.py nevow-0.10.0/doc/howto/chattutorial/part00/listings/echothing/echobox.py --- nevow-0.9.31/doc/howto/chattutorial/part00/listings/echothing/echobox.py 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/doc/howto/chattutorial/part00/listings/echothing/echobox.py 2008-09-03 19:15:21.000000000 +0100 @@ -0,0 +1,12 @@ +from twisted.python.util import sibpath +from nevow.athena import LiveElement, expose +from nevow.loaders import xmlfile + +class EchoElement(LiveElement): + + docFactory = xmlfile(sibpath(__file__, 'template.html')) + jsClass = u'EchoThing.EchoWidget' + + def say(self, message): + self.callRemote('addText', message) + say = expose(say) diff -Nru nevow-0.9.31/doc/howto/chattutorial/part00/listings/echothing/__init__.py nevow-0.10.0/doc/howto/chattutorial/part00/listings/echothing/__init__.py --- nevow-0.9.31/doc/howto/chattutorial/part00/listings/echothing/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/doc/howto/chattutorial/part00/listings/echothing/__init__.py 2007-09-12 22:54:15.000000000 +0100 @@ -0,0 +1 @@ + diff -Nru nevow-0.9.31/doc/howto/chattutorial/part00/listings/echothing/js/EchoThing.js nevow-0.10.0/doc/howto/chattutorial/part00/listings/echothing/js/EchoThing.js --- nevow-0.9.31/doc/howto/chattutorial/part00/listings/echothing/js/EchoThing.js 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/doc/howto/chattutorial/part00/listings/echothing/js/EchoThing.js 2009-01-21 22:58:03.000000000 +0000 @@ -0,0 +1,22 @@ +// import Nevow.Athena + +Nevow.Athena.Widget.subclass(EchoThing, 'EchoWidget').methods( + function __init__(self, node) { + EchoThing.EchoWidget.upcall(self, "__init__", node); + self.echoWidget = self.nodeByAttribute('name', 'echoElement'); + self.scrollArea = self.nodeByAttribute('name', 'scrollArea'); + self.message = self.nodeByAttribute('name', 'message'); + }, + + function doSay(self) { + self.callRemote("say", self.message.value); + self.message.value = ""; + return false; + }, + + function addText(self, text) { + var newNode = document.createElement('div'); + newNode.appendChild(document.createTextNode(text)); + self.scrollArea.appendChild(newNode); + document.body.scrollTop = document.body.scrollHeight; + }); diff -Nru nevow-0.9.31/doc/howto/chattutorial/part00/listings/echothing/template.html nevow-0.10.0/doc/howto/chattutorial/part00/listings/echothing/template.html --- nevow-0.9.31/doc/howto/chattutorial/part00/listings/echothing/template.html 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/doc/howto/chattutorial/part00/listings/echothing/template.html 2008-09-03 19:15:21.000000000 +0100 @@ -0,0 +1,11 @@ +
+

Echo Element

+
+ +
+
+ + +
diff -Nru nevow-0.9.31/doc/howto/chattutorial/part00/listings/nevow/plugins/echothing_package.py nevow-0.10.0/doc/howto/chattutorial/part00/listings/nevow/plugins/echothing_package.py --- nevow-0.9.31/doc/howto/chattutorial/part00/listings/nevow/plugins/echothing_package.py 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/doc/howto/chattutorial/part00/listings/nevow/plugins/echothing_package.py 2007-09-12 22:54:15.000000000 +0100 @@ -0,0 +1,8 @@ + +from twisted.python import util + +from nevow import athena + +import echothing + +chatthingPkg = athena.AutoJSPackage(util.sibpath(echothing.__file__, 'js')) diff -Nru nevow-0.9.31/doc/howto/chattutorial/part01/listings/chatthing/chatterbox.py nevow-0.10.0/doc/howto/chattutorial/part01/listings/chatthing/chatterbox.py --- nevow-0.9.31/doc/howto/chattutorial/part01/listings/chatthing/chatterbox.py 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/doc/howto/chattutorial/part01/listings/chatthing/chatterbox.py 2008-09-03 19:18:51.000000000 +0100 @@ -0,0 +1,48 @@ +from twisted.python.util import sibpath +from nevow.loaders import xmlfile +from nevow.athena import LiveElement, expose + +class ChatRoom(object): + + def __init__(self): + self.chatters = [] + + def wall(self, message): + for chatter in self.chatters: + chatter.wall(message) + + def tellEverybody(self, who, what): + for chatter in self.chatters: + chatter.hear(who.username, what) + + def makeChatter(self): + elem = ChatterElement(self) + self.chatters.append(elem) + return elem + +# element to be run with twistd +chat = ChatRoom().makeChatter + +class ChatterElement(LiveElement): + + docFactory = xmlfile(sibpath(__file__, 'template.html')) + jsClass = u'ChatThing.ChatterWidget' + + def __init__(self, room): + self.room = room + + def setUsername(self, username): + self.username = username + message = ' * user '+username+' has joined the room' + self.room.wall(message) + setUsername = expose(setUsername) + + def say(self, message): + self.room.tellEverybody(self, message) + say = expose(say) + + def wall(self, message): + self.callRemote('displayMessage', message) + + def hear(self, username, what): + self.callRemote('displayUserMessage', username, what) diff -Nru nevow-0.9.31/doc/howto/chattutorial/part01/listings/chatthing/__init__.py nevow-0.10.0/doc/howto/chattutorial/part01/listings/chatthing/__init__.py --- nevow-0.9.31/doc/howto/chattutorial/part01/listings/chatthing/__init__.py 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/doc/howto/chattutorial/part01/listings/chatthing/__init__.py 2007-09-04 19:36:20.000000000 +0100 @@ -0,0 +1 @@ + diff -Nru nevow-0.9.31/doc/howto/chattutorial/part01/listings/chatthing/js/ChatThing.js nevow-0.10.0/doc/howto/chattutorial/part01/listings/chatthing/js/ChatThing.js --- nevow-0.9.31/doc/howto/chattutorial/part01/listings/chatthing/js/ChatThing.js 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/doc/howto/chattutorial/part01/listings/chatthing/js/ChatThing.js 2009-01-21 22:58:03.000000000 +0000 @@ -0,0 +1,42 @@ +// import Nevow.Athena + +Nevow.Athena.Widget.subclass(ChatThing, 'ChatterWidget').methods( + function __init__(self, node) { + ChatThing.ChatterWidget.upcall(self, "__init__", node); + self.chooseBox = self.nodeByAttribute('name', 'chooseBox'); + self.scrollArea = self.nodeByAttribute('name', 'scrollArea'); + self.sendLine = self.nodeByAttribute('name', 'sendLine'); + self.usernameField = self.nodeByAttribute('name', 'username'); + self.userMessage = self.nodeByAttribute('name', 'userMessage'); + self.loggedInAs = self.nodeByAttribute('name', 'loggedInAs'); + }, + + function doSetUsername(self) { + var username = self.usernameField.value; + self.callRemote("setUsername", username).addCallback( + function (result) { + self.chooseBox.style.display = "none"; + self.sendLine.style.display = "block"; + self.loggedInAs.appendChild(document.createTextNode(username)); + self.loggedInAs.style.display = "block"; + }); + return false; + }, + + function doSay(self) { + self.callRemote("say", self.userMessage.value); + self.nodeByAttribute('name', 'userMessage').value = ""; + return false; + }, + + function displayMessage(self, message) { + var newNode = document.createElement('div'); + newNode.appendChild(document.createTextNode(message)); + self.scrollArea.appendChild(newNode); + document.body.scrollTop = document.body.scrollHeight; + }, + + function displayUserMessage(self, avatarName, text) { + var msg = avatarName+': '+text; + self.displayMessage(msg); + }); diff -Nru nevow-0.9.31/doc/howto/chattutorial/part01/listings/chatthing/template.html nevow-0.10.0/doc/howto/chattutorial/part01/listings/chatthing/template.html --- nevow-0.9.31/doc/howto/chattutorial/part01/listings/chatthing/template.html 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/doc/howto/chattutorial/part01/listings/chatthing/template.html 2008-09-03 19:18:51.000000000 +0100 @@ -0,0 +1,20 @@ +
+

Chatter Element

+
+ +
+
+
+ +
+ +
+ + Choose your username: + + +
Logged in as
+
diff -Nru nevow-0.9.31/doc/howto/chattutorial/part01/listings/nevow/plugins/chatthing_package.py nevow-0.10.0/doc/howto/chattutorial/part01/listings/nevow/plugins/chatthing_package.py --- nevow-0.9.31/doc/howto/chattutorial/part01/listings/nevow/plugins/chatthing_package.py 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/doc/howto/chattutorial/part01/listings/nevow/plugins/chatthing_package.py 2007-09-04 19:36:20.000000000 +0100 @@ -0,0 +1,8 @@ + +from twisted.python import util + +from nevow import athena + +import chatthing + +chatthingPkg = athena.AutoJSPackage(util.sibpath(chatthing.__file__, 'js')) diff -Nru nevow-0.9.31/doc/howto/listings/gettingstarted/helloworld.py nevow-0.10.0/doc/howto/listings/gettingstarted/helloworld.py --- nevow-0.9.31/doc/howto/listings/gettingstarted/helloworld.py 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/doc/howto/listings/gettingstarted/helloworld.py 2008-08-13 18:45:05.000000000 +0100 @@ -0,0 +1,6 @@ +from nevow import loaders, rend + +class HelloWorld(rend.Page): + addSlash = True + docFactory = loaders.xmlfile('helloworld.html') + diff -Nru nevow-0.9.31/doc/howto/listings/gettingstarted/helloworld.tac nevow-0.10.0/doc/howto/listings/gettingstarted/helloworld.tac --- nevow-0.9.31/doc/howto/listings/gettingstarted/helloworld.tac 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/doc/howto/listings/gettingstarted/helloworld.tac 2008-08-13 18:45:05.000000000 +0100 @@ -0,0 +1,10 @@ +from twisted.application import internet +from twisted.application import service +from nevow import appserver +import helloworld + +application = service.Application('helloworld') +site = appserver.NevowSite(helloworld.HelloWorld()) +webServer = internet.TCPServer(8080, site) +webServer.setServiceParent(application) + diff -Nru nevow-0.9.31/doc/howto/stylesheet.css nevow-0.10.0/doc/howto/stylesheet.css --- nevow-0.9.31/doc/howto/stylesheet.css 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/doc/howto/stylesheet.css 2008-09-20 03:06:47.000000000 +0100 @@ -0,0 +1,129 @@ +body +{ + margin-left: 2em; + margin-right: 2em; + border: 0px; + padding: 0px; + font-family: sans-serif; +} + +pre +{ + padding: 1em; + font-family: Monospace, Neep Alt, Courier New, Courier; + font-size: 12pt; + border: thin black solid; +} + +.python +{ + background-color: #dddddd; +} + +.py-listing, .html-listing, .listing +{ + margin: 1ex; + border: thin solid black; + background-color: #eee; +} + +.py-listing pre, .html-listing pre, .listing pre +{ + margin: 0px; + border: none; + border-bottom: thin solid black; +} + +.py-listing .python +{ + margin-top: 0; + margin-bottom: 0; + border: none; + border-bottom: thin solid black; +} + +.py-src-comment +{ + color: #1111CC +} + +.py-src-keyword +{ + color: #3333CC; + font-weight: bold; +} + +.py-src-parameter +{ + color: #000066; + font-weight: bold; +} + +.py-src-identifier +{ + color: #CC0000 +} + +.py-src-string +{ + color: #115511 +} + +.py-src-endmarker +{ + display: block; /* IE hack; prevents following line from being sucked into the py-listing box. */ +} + +hr +{ + display: inline; +} + +ul +{ + padding: 0px; + margin: 0px; + margin-left: 1em; + padding-left: 1em; + border-left: 1em; +} + +li +{ + padding: 2px; +} + +dt +{ + font-weight: bold; + margin-left: 1ex; +} + +dd +{ + margin-bottom: 1em; +} + +div.note +{ + background-color: #FFFFCC; + margin-top: 1ex; + margin-left: 5%; + margin-right: 5%; + padding-top: 1ex; + padding-left: 5%; + padding-right: 5%; + border: thin black solid; +} + +.caption +{ + text-align: center; + padding-top: 0.5em; + padding-bottom: 0.5em; +} + +.filename +{ + font-style: italic; +} diff -Nru nevow-0.9.31/doc/make.py nevow-0.10.0/doc/make.py --- nevow-0.9.31/doc/make.py 2005-02-17 17:17:23.000000000 +0000 +++ nevow-0.10.0/doc/make.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,2 +0,0 @@ -import os -os.system("python txt2html.py txt html en") diff -Nru nevow-0.9.31/doc/qdlocale.py nevow-0.10.0/doc/qdlocale.py --- nevow-0.9.31/doc/qdlocale.py 2005-02-17 17:17:23.000000000 +0000 +++ nevow-0.10.0/doc/qdlocale.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,14 +0,0 @@ -locale = { - "en" : { - "defaultPage" : "index", - "next" : "Next", - "prev" : "Previous", - "home" : "Home", - "up" : "Up", - "top" : "Return to Top", - "toc" : "Table of Contents", - "updatedOn" : "Last updated on", - "sub" : "Subsections", - } -} - diff -Nru nevow-0.9.31/doc/README nevow-0.10.0/doc/README --- nevow-0.9.31/doc/README 2005-02-17 17:17:23.000000000 +0000 +++ nevow-0.10.0/doc/README 2008-08-26 15:45:59.000000000 +0100 @@ -1,8 +1,5 @@ -Simple documentation system that supports navigation generation, -organizational hierarchy, internationalization, etc. -Written by jamwt@jamwt.com. - - 1. Download docutils from http://docutils.sourceforge.net/. - 2. Write simple fragment restructured text files in txt/. - 3. Lay out heir.py to reflect the titles and hierarchy of the text fragments - 4. cd to doc/ (the directory this README is in) and run 'python make.py' +This is Nevow's documentation. The preferred format is Lore XHTML. You can +generate the pretty version of the documentation with by running the +following command in the howto directory: + + lore *.xhtml diff -Nru nevow-0.9.31/doc/txt/gettingstarted/helloworld.html nevow-0.10.0/doc/txt/gettingstarted/helloworld.html --- nevow-0.9.31/doc/txt/gettingstarted/helloworld.html 2005-05-02 20:03:06.000000000 +0100 +++ nevow-0.10.0/doc/txt/gettingstarted/helloworld.html 1970-01-01 01:00:00.000000000 +0100 @@ -1,9 +0,0 @@ - - - Hello, world! - - -

Hello, world!

- - - diff -Nru nevow-0.9.31/doc/txt/gettingstarted/helloworld.py nevow-0.10.0/doc/txt/gettingstarted/helloworld.py --- nevow-0.9.31/doc/txt/gettingstarted/helloworld.py 2005-05-02 20:03:06.000000000 +0100 +++ nevow-0.10.0/doc/txt/gettingstarted/helloworld.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,6 +0,0 @@ -from nevow import loaders, rend - -class HelloWorld(rend.Page): - addSlash = True - docFactory = loaders.xmlfile('helloworld.html') - diff -Nru nevow-0.9.31/doc/txt/gettingstarted/helloworld.tac nevow-0.10.0/doc/txt/gettingstarted/helloworld.tac --- nevow-0.9.31/doc/txt/gettingstarted/helloworld.tac 2005-05-02 20:03:06.000000000 +0100 +++ nevow-0.10.0/doc/txt/gettingstarted/helloworld.tac 1970-01-01 01:00:00.000000000 +0100 @@ -1,10 +0,0 @@ -from twisted.application import internet -from twisted.application import service -from nevow import appserver -import helloworld - -application = service.Application('helloworld') -site = appserver.NevowSite(helloworld.HelloWorld()) -webServer = internet.TCPServer(8080, site) -webServer.setServiceParent(application) - diff -Nru nevow-0.9.31/doc/txt/nevow-deployment.txt nevow-0.10.0/doc/txt/nevow-deployment.txt --- nevow-0.9.31/doc/txt/nevow-deployment.txt 2005-02-25 23:17:50.000000000 +0000 +++ nevow-0.10.0/doc/txt/nevow-deployment.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,208 +0,0 @@ -Nevow Deployment -================ - -Nevow includes two major phases for deciding what HTML to render. `Object -Traversal`_ is the procedure by which a URL is mapped to a Python object which -will perform the HTML generation. `Page Rendering`_ is the process by which data -objects are combined with an HTML template to produce the final output. - -Before any of this can take place, however, we must have an environment in which -our Python code can run in response to an HTTP request, and HTML can be returned -to the browser for rendering. This is called the `Deployment Environment`_. - -There are various deployment options for Nevow page code: - -* `CGI`_: Simple deployment in almost any HTTP server -* `WSGI`_: A more complete and flexible way for deploying on many HTTP servers -* `Twisted.Web`_: A standalone application server process which includes a built-in HTTP server -* `Zomne`_: A small CGI which hands off HTTP requests to a long-running application server process, similar to FastCGI or SCGI - -CGI ---- - -You can deploy Nevow on any webserver which uses the Common Gateway Interface. -Using this method, your code is responsible for properly formatting and -outputting the HTTP response headers, and Nevow is used only to generate the -HTML body of your page. Here is the simplest possible CGI:: - - #!/usr/bin/env python - - print "Content-type: text/plain\r\n\r\n", - - from nevow import rend, loaders - - class HelloWorld(rend.Page): - docFactory = loaders.stan("Hello, world!") - - print HelloWorld().renderSynchronously() - -With this simple CGI you can use the Nevow template loaders and standard nevow -template interpolation techniques in your CGIs. However, you do not get any -`Object Traversal`_ features, and you have to generate HTTP headers yourself. -`WSGI`_ is a slightly higher-level deployment option which does not suffer these -problems. - -WSGI ----- - -WSGI is a proposed python interface for plugging web applications into various -HTTP server architectures. It is described in `PEP 333`_, the Python Web -Services Gateway Interface Python Enhancement Proposal. Nevow includes the -nevow.wsgi module, which includes a createWSGIApplication function which takes a -Page and returns a standard WSGI application callable. With the help of the -run_with_cgi example gateway from the PEP (which I will omit here), our CGI -example becomes shorter:: - - #!/usr/bin/env python - - from nevow import rend, loaders, wsgi - - class HelloWorld(rend.Page): - docFactory = loaders.stan("Hello, world!") - - run_with_cgi(wsgi.createWSGIApplication(HelloWorld())) - -Of course, you can use any available WSGI gateway to publish your application -object, such as one of the gateways which comes with the `PEAK`_ toolkit. For -example, here is a simple python module which creates a WSGI application which -we will then deploy with PEAK's SimpleHTTPServer gateway:: - - ## helloworld.py - - from nevow import rend, loaders, wsgi - - class HelloWorld(rend.Page): - docFactory = loaders.stan("Hello, world!") - - application = wsgi.createWSGIApplication(HelloWorld()) - -Save this file as "helloworld.py" somewhere on your PYTHONPATH and then run the -following command:: - - peak launch WSGI import:helloworld.application - -This will bring up a SimpleHTTPServer running your Nevow code and launch a web -browser to view the output. (TODO: I couldn't get this working immediately but I -will seek assistance with PEAK and update the instructions once I do.) - -Twisted.Web ------------ - -A convenient and powerful way to deploy Nevow applications is inside a process -running the twisted.web HTTP server. With Python, Twisted, and Nevow installed, -you have all you need to run a Web Application, with no other dependencies or -external HTTP servers such as Apache required. Running your Nevow applications -under twisted.web also gives you access to some of the more advanced "Live" -features of Nevow, such as nevow.livepage and nevow.canvas. Currently, these -modules require more control over the HTTP socket than CGI or WSGI can provide. -(This may change in the future.) - -Deploying a Nevow application under twisted.web requires a little more -boilerplate, but can be considerably easier to set up than other deployment -options because there are no external dependencies. Note that normally you -should declare your Page classes in modules external to the twisted -configuration file, but everything is included in one file here for brevity. -Here is the minimal configuration file required to use Nevow with twisted.web:: - - from nevow import rend, loaders, appserver - - class HelloWorld(rend.Page): - docFactory = loaders.stan("Hello, world!") - - from twisted.application import service, internet - application = service.Application("hello-world") - internet.TCPServer(8080, appserver.NevowSite(HelloWorld())).setServiceParent(application) - -Save this file as "helloworld.tac" and start the server using the command:: - - twistd -noy helloworld.tac - -Then visit your twisted.web server by viewing the url "http://localhost:8080/" -in your browser. See the twistd man page for more information about what twistd -is capable of, including daemonizing the HTTP server. - -Zomne ------ - -**Warning** Zomne is experimental. It may blow up your computer and require your -first born son as a sacrifice. Zomne also only works in UNIX-like environments -where unix domain sockets are available, and may not work on windows. - -Zomne, or "Zombie Nevow", is a CGI written in C which can start up a -long-running Application Server process if one is not already running. It then -uses a simple custom protocol to transmit information about the HTTP request -from the CGI process to the application server process. - -Zomne combines the ease of deployment of the CGI environment with the speed and -flexibility of the twisted.web long-running application server process model. - -To use Zomne, you must first compile the CGI. cd into the directory created when -unpacking the Nevow tarball, and compile the CGI:: - - % gcc zomne.c - -Move it into your cgi-bin:: - - % mv a.out /Library/WebServer/CGI-Executables/nevow.cgi - -Create a file which tells the cgi where to look for the application:: - - % cat > /Library/WebServer/CGI-Executables/.nevow.cgi.dir - /Users/dp/zomne-test - ^D - -The CGI name can be anything, as long as there is a file with a prepended "." -and a postfixed ".dir" in the same directory which contains the full path of a -zomne application directory. Next, create the application directory:: - - mkdir /Users/dp/zomne-test - -Finally, create the zomne.tac file which the zomne.cgi will execute to start the -long-running application server process:: - - from nevow import rend, loaders, zomnesrv - - class HelloWorld(rend.Page): - docFactory = loaders.stan("Hello, world!") - - from twisted.application import service, internet - application = service.Application('nevow-zomne-test') - internet.UNIXServer('zomne.socket', zomnesrv.ZomneFactory(HelloWorld())).setServiceParent(application) - -Now, visiting the nevow.cgi URL through the web should render the Hello World -page, after a pause while the server is starting up. Subsequent requests should -be very fast, because the application server is already running, and the CGI -merely has to forward the request to it. - -Another useful capability of the zomne CGI process is the ability to control -environment variables the CGI will use. Create a directory named "zomne_environ" -in the application directory, and fill it with text files whose name will be the -environment key and whose contents will be the environment value:: - - % cd zomne-test - % mkdir zomne-environ - % cd zomne-environ - % cat > PYTHONPATH - /Users/dp/Projects/Nevow:/Users/dp/Projects/helloworld - ^D - -Conclusion -========== - -Nevow may be deployed in a number of environments, from the most restrictive to -the most permissive. Writing a CGI can be an easy way to try out the Nevow -templating mechanism, but can be slow. A long-running application server process -can be a good way to get good performance as well as additional features such as -in-memory server-side sessions, advanced automatic form handling with formless, -and live page updating features such as nevow.livepage and nevow.canvas. - -Which deployment option you choose will depend on the amount of control you have -over your deployment environment, and what advanced features your application -will require. - -.. _`Object Traversal`: nevow-glossary.html#object-traversal -.. _`Page Rendering`: nevow-glossary.html#page-rendering -.. _`Deployment Environment`: nevow-glossary.html#deployment-environment -.. _`PEP 333`: http://www.python.org/peps/pep-0333.html -.. _`PEAK`: http://peak.telecommunity.com/ - diff -Nru nevow-0.9.31/doc/txt/nevow-gettingstarted.txt nevow-0.10.0/doc/txt/nevow-gettingstarted.txt --- nevow-0.9.31/doc/txt/nevow-gettingstarted.txt 2005-04-17 23:51:03.000000000 +0100 +++ nevow-0.10.0/doc/txt/nevow-gettingstarted.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,78 +0,0 @@ -Getting Started with Nevow -========================== - -Warning: This document has only just been started. It's not going to get you -very far right now. - -Nevow is a reasonably large library and can be quite daunting at first. This -document's aim is to guide the first time user in building a Nevow application. - - -Our First Application ---------------------- - -Let's dive straight in, here's the code for our first (very, very simple) -application. Create the following module, helloworld.py:: - - - from nevow import loaders, rend - - class HelloWorld(rend.Page): - addSlash = True - docFactory = loaders.xmlfile('helloworld.html') - - -It looks quite simple but let's walk through it anyway. - -First, we import two Nevow modules. ``nevow.loaders`` contains template loaders -of which the two most useful are ``xmlfile`` and ``stan``. ``xmlfile`` can load -any well-formed XML (i.e. XHTML) file; ``stan`` loads a stan tree (more on these -later). The other module, ``nevow.rend``, contains all Nevow's standard renders, -many of which we'll meet in this document. - -We then define the ``HelloWorld`` class that subclasses ``rend.Page``, Nevow's -main resource class. ``HelloWorld`` has two class attributes. ``addSlash`` tells -rend.Page to redirect to a version of the request URL that ends in a '/' if -necessary. You generally want to set this to ``True`` for the root resource. -``docFactory`` tells the page instance where to get the template from. In this -case we're providing a loader that parses an HTML file (not shown) from disk. - -Hmm, ok I hear you say but how do I see it. Well, Twisted provides a good web -server which we can use. Twisted also includes a clever little application for -starting Twisted applications. Here's the helloworld.tac file, a Twisted Application -Configuration:: - - from twisted.application import internet - from twisted.application import service - from nevow import appserver - import helloworld - - application = service.Application('helloworld') - site = appserver.NevowSite(helloworld.HelloWorld()) - webServer = internet.TCPServer(8080, site) - webServer.setServiceParent(application) - - -Give it a go, run the following and connect to http://localhost:8080/ to see -your application:: - - twistd -noy helloworld.tac - - -You'll probably notice that you get log output on the console. This is just one -of the good things that twistd does. It can also daemonize the application, shed -privileges if run as root, etc. - -TAC files are covered in more detail in the Twisted documentation but let's -quickly explain what all this does anyway. - -When twistd starts up it loads the .tac file (it's just Python) and looks for -the attribute called ``application``. When twistd is all ready to go it starts -the ``application``. - -The application is not much use unless it actually does something so the next -thing we do is create a NevowSite instance, ``site``, and pass it a root -resource, a ``HelloWorld`` instance. Finally, we create a TCP server that makes -the site available on port 8080 and bind the server to the application to ensure -the server is started when the application is started. - diff -Nru nevow-0.9.31/doc/txt/nevow-glossary.txt nevow-0.10.0/doc/txt/nevow-glossary.txt --- nevow-0.9.31/doc/txt/nevow-glossary.txt 2005-02-25 23:17:50.000000000 +0000 +++ nevow-0.10.0/doc/txt/nevow-glossary.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,47 +0,0 @@ -Nevow Glossary -============== - -Object Traversal - The process by which a Python object is located to render HTML for a given - HTTP URL. For example, given the URL http://example.com/foo/bar, Object - Traversal will begin at the "Root Resource" object by asking it for an object - which is capable of rendering the page at ('foo', 'bar'). The "Root Resource" - returns an object and a list of unhandled path segments, and the traversal - continues across this new Resource object until all path segments have been - consumed. - -Page Rendering - The process by which a Python object, usually a rend.Page subclass, turns - itself into HTML. Page Rendering involves locating some page data, loading a - template document, and applying the template to the data, in the process - generating HTML. - -Deployment Environment - The environment in which a Nevow application is deployed. Generally involves - an HTTP server which is configured to route certain (or all) HTTP requests - through the Nevow Object Traversal and Page Rendering process. Deployment - environments include CGI, WSGI, and twisted.web. - -DOM - Document Object Model. A tree of objects which represent the structure of an - XHTML document in memory. Nevow uses a nonstandard DOM named "stan", which is - made up of simple Python lists, dicts, strings, and nevow.stan.Tag instances. - -Flattener - A Python function which knows how to translate from a rich type to a string - containing HTML. For example, the integer flattener calls str() on the - integer. The string flattener escapes characters which are unsafe in HTML, - such as <, >, and &. - -Tag - A class, defined at nevow.stan.Tag, which holds information about a single - HTML tag in a DOM. Tag instances have three attributes: tagName, attributes, - and children. tagName is a string indicating the tag name. attributes is a - dict indicating the HTML attributes of that node. children is a list - indicating the child nodes of that node. - -Tag Specials - A Tag attribute which is "special" to nevow. Tag specials include data, - render, pattern, slot, and macro. Tag Specials will never be output as HTML - attributes of tags, but will be used by the internal Nevow rendering process - to influence how the Tag is rendered. diff -Nru nevow-0.9.31/doc/txt/nevow-intro.txt nevow-0.10.0/doc/txt/nevow-intro.txt --- nevow-0.9.31/doc/txt/nevow-intro.txt 2005-04-14 18:34:52.000000000 +0100 +++ nevow-0.10.0/doc/txt/nevow-intro.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,237 +0,0 @@ -Nevow: A Web Application Construction Kit -========================================= - -Donovan Preston - -Summary -------- - -Nevow is a next-generation web application templating system, based on the ideas -developed in the Twisted Woven package. Its main focus is on separating the HTML -template from both the business logic and the display logic, while allowing the -programmer to write pure Python code as much as possible. It separates your code -into 'data' and 'render' functions, a simplified implementation of traditional -MVC. It has various parts which can be used individually or as a whole, -integrated web solution: - - - XHTML templates: contain no programming logic, only nodes tagged with nevow - attributes - - - data/render methods: simplified MVC - - - stan: An s-expression-like syntax for expressing xml in pure python - - - formless: For describing the types of objects which may be passed to methods - of your classes, validating and coercing string input from either web or - command-line sources, and calling your methods automatically once validation - passes - - - formless.webform: For rendering web forms based on formless type - descriptions, accepting form posts and passing them to formless validators, - and rendering error forms in the event validation fails - - - livepage: Cross-browser JavaScript glue for sending client side events to - the server and server side events to the client after the page has loaded, - without causing the entire page to refresh - -Disk based templates -~~~~~~~~~~~~~~~~~~~~ - -Nevow includes the ability to load templates off disk. These templates may have -processing directives which cause the execution of python methods at render -time. The attribute technique was inspired by the attributes used by ZPT. -However, no actual code may be embedded in the HTML template:: - - - - Greetings! - - -

Now I will greet you:

- - - - -This template can then be loaded and rendered like so:: - - class Greeter(rend.Page): - docFactory = loaders.xmlfile("Greeting.html") - - def render_greet(self, context, data): - return random.choice(["Hello", "Greetings", "Hi"]), " ", data - - Greeter("My name is").renderString() - -data/render methods -~~~~~~~~~~~~~~~~~~~ - -To allow clean isolation between code which fetches data from a data source and -code which renders the data into HTML, nevow allows you to write both 'data' -methods and 'render' methods. These concepts are inspired by MVC, but simpler, -since the framework can handle most of the controller aspect. An example:: - - - - - - - - -This template can be loaded and rendered using a class such as this:: - - class Colorful(rend.Page): - docFactory = loaders.xmlfile("Colorful.html") - - def render_colorful(self, context, data): - color = random.choice(['red', 'green', 'blue']) - return context.tag(style="color: %s" % color) - - def data_name(self, context, data): - return "Your name here" - - def data_fun(self, context, data): - return "Are we having fun yet?" - -Stan -~~~~ - -One of the most powerful things about nevow is stan, an s-expression-like syntax -for producing XML fragments in pure Python syntax. Stan is not required for -using nevow, but it is both a simple and powerful way to both lay out one's -XHTML templates and express one's display logic. A brief example will illustrate -its utility:: - - import random - from nevow import rend, tags - - class Greeter(rend.Page): - def greet(self, context, data): - return random.choice(["Hello", "Greetings", "Hi"]), " ", data - - docFactory = loaders.stan( - tags.html[ - tags.head[ tags.title[ "Greetings!" ]], - tags.body[ - tags.h1(style="font-size: large")[ "Now I will greet you:" ], - greet - ] - ]) - -When the Greeter class is constructed, it is passed a Python object which will -be used as that page's data:: - - Greeter("Your name here").renderString() - -Formless -~~~~~~~~ - -Python is dynamically typed, which means it has no built-in controls for -enforcing the types of objects which are passed to one's methods. This is great -for programmers, but not necessarily great if you are going to be passing -user-entered input to those methods. Formless is a simple way to describe the -types of objects that can be passed to one's methods, as well as coerce from -string input to those types. Other code can then accept user input from a -command line or from a web form, validate the input against the types described -using formless, and call the method once validation has passed. A simple -example:: - - from zope.interface import implements - from formless.annotate import TypedInterface, Integer, String - - class ISimpleMethod(TypedInterface): - def simple(self, - name=String(description="Your name."), - age=Integer(description="Your age.")): - """Simple - - Please enter your name and age. - """ - - class Implementation(object): - implements(ISimpleMethod) - - def simple(self, name, age): - print "Hello, %s, who is %s" % (name, age) - -Webform -~~~~~~~ - -Webform is a nevow module which will automatically render web forms and accept -form posts based on types described using the classes in formless. Used in -conjunction with the twisted.web HTTP server, the process is almost automatic:: - - from nevow import rend, tags - from formless import webform - - class WebForm(rend.Page): - document = rend.stan( - tags.html[ - tags.body[ - h1["Here is the form:"], - webform.renderForms('original') - ] - ]) - - resource = WebForm(Implementation()) - -Exposing this resource instance to the web using twisted.web and visiting it -will cause a form with two input boxes to be rendered. Posting the form will -cause form validation to occur. Upon error, the user will be returned to the -original page, with the form annotated with error messages. Upon success, the -"simple" method of the Implementation instance will be called and passed a -string and an integer. - -LivePage -~~~~~~~~ - -LivePage was a Woven technology which allowed programmers to receive server-side -notification of client-side JavaScript events, and to send JavaScript to the -client in response to a server-side event. New for Nevow 0.3, LivePage has been -updated to support Mozilla, Firefox, IE6 Win, and Safari. Using LivePage is very -easy:: - - from nevow.liveevil import handler - - def greeter(client, nodeName): - client.alert("Greetings. You clicked the %s node." % nodeName) - - # Any string arguments after the event handler function will be evaluated - # as JavaScript in the context of the web browser and results passed to the - # Python event handler - handler = handler(greeter, 'node.name') - - class Live(rend.Page): - docFactory = loaders.stan( - tags.html[ - tags.body[ - ol[ - li(onclick=handler, name="one")["One"] - li(onclick=handler, name="two")["Two"] - li(onclick=handler, name="three")["Three"] - ] - ] - ]) - -More Information ----------------- - -Nevow is available from the `Nevow website`_. Starting with 0.3, it contains a -simple WSGI implementation and can also be used to render CGIs. However, the -recommended mode of operation is using the `Twisted web`_ server. Nevow is an -active project, and many new bugfixes and features are committed to the Nevow -SVN repository. Information about Nevow commits is available by subscribing to -the `Nevow commits`_ mailing list. The Nevow SVN repository can be checked out -using:: - - svn co svn://divmod.org/svn/Nevow/trunk Nevow - -Discussion of Nevow occurs on the `twisted.web mailing list`_. The Nevow -developers are also often available for real-time help on the `#twisted.web -channel`_ on irc.freenode.net. - -.. _`Nevow website`: http://nevow.com/ -.. _`Twisted web`: http://twistedmatrix.com/products/download -.. _`Nevow commits`: http://divmod.org/users/mailman.twistd/listinfo/nevow-commits -.. _`twisted.web mailing list`: http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-web -.. _`#twisted.web channel`: irc://irc.freenode.net/#twisted.web - diff -Nru nevow-0.9.31/doc/txt/nevow-rendering.txt nevow-0.10.0/doc/txt/nevow-rendering.txt --- nevow-0.9.31/doc/txt/nevow-rendering.txt 2007-12-20 18:48:42.000000000 +0000 +++ nevow-0.10.0/doc/txt/nevow-rendering.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,538 +0,0 @@ -Nevow Object Publishing -======================= - -In Nevow Object Traversal, we learned about the -nevow.inevow.IResource.renderHTTP method, which is the most basic way to send -HTML to a browser when using Nevow. However, it is not very convenient (or -clean) to generate HTML tags by concatenating strings in Python code. In the -Nevow Deployment documentation, we saw that it was possible to render a Hello -World page using a nevow.rend.Page subclass and providing a "docFactory":: - - >>> from nevow import rend, loaders - >>> class HelloWorld(rend.Page): - ... docFactory = loaders.stan("Hello, world!") - ... - >>> HelloWorld().renderSynchronously() - 'Hello, world!' - -This example does nothing interesting, but the concept of a loader is important -in Nevow. The rend.Page.renderHTTP implementation always starts rendering HTML -by loading a template from the docFactory. - -* `The stan DOM`_ -* `Tag instances`_ -* `Functions in the DOM`_ -* `Accessing query parameters and form post data`_ -* `Generators in the DOM`_ -* `Methods in the DOM`_ -* `Data specials`_ -* `Render specials`_ -* `Pattern specials`_ -* `Slot specials`_ -* `Data directives`_ -* `Render directives`_ -* `Flatteners`_ - -The stan DOM ------------- - -Nevow uses a DOM-based approach to rendering HTML. A tree of objects is first -constructed in memory by the template loader. This tree is then processed one -node at a time, applying functions which transform from various Python types to -HTML strings. - -Nevow uses a nonstandard DOM named "stan". Unlike the W3C DOM, stan is made up -of simple python lists, strings, and instances of the nevow.stan.Tag class. -During the rendering process, "Flattener" functions convert from rich types to -HTML strings. For example, we can load a template made up of some nested lists -and Python types, render it, and see what happens:: - - >>> class PythonTypes(rend.Page): - ... docFactory = loaders.stan(["Hello", 1, 1.5, True, ["Goodbye", 3]]) - ... - >>> PythonTypes().renderSynchronously() - 'Hello11.5TrueGoodbye3' - -Tag instances -------------- - -So far, we have only rendered simple strings as output. However, the main -purpose of Nevow is HTML generation. In the stan DOM, HTML tags are represented -by instances of the nevow.stan.Tag class. Tag is a very simple class, whose -instances have an "attributes" dictionary and a "children" list. The Tag -flattener knows how to recursively flatten attributes and children of the tag. -To show you how Tags really work before you layer Nevow's convenience syntax on -top, try this horrible example:: - - >>> from nevow import stan - >>> h = stan.Tag('html') - >>> d = stan.Tag('div') - >>> d.attributes['style'] = 'border: 1px solid black' - >>> h.children.append(d) - >>> class Tags(rend.Page): - ... docFactory = loaders.stan(h) - ... - >>> Tags().renderSynchronously() - '
' - -So, we see how it is possible to programatically generate HTML by constructing -and nesting stan Tag instances. However, it is far more convenient to use the -overloaded operators Tag provides to manipulate them. Tag implements a __call__ -method which takes any keyword arguments and values and updates the attributes -dictionary; it also implements a __getitem__ method which takes whatever is -between the square brackets and appends them to the children list. A simple -example should clarify things:: - - >>> class Tags2(rend.Page): - ... docFactory = loaders.stan(stan.Tag('html')[stan.Tag('div')(style="border: 1px solid black")]) - ... - >>> Tags2().renderSynchronously() - '
' - -This isn't very easy to read, but luckily we can simplify the example even -further by using the nevow.tags module, which is full of "Tag prototypes" for -every tag type described by the XHTML 1.0 specification:: - - >>> class Tags3(rend.Page): - ... docFactory = loaders.stan(tags.html[tags.div(style="border: 1px solid black")]) - ... - >>> Tags3().renderSynchronously() - '
' - -Using stan syntax is not the only way to construct template DOM for use by the -Nevow rendering process. Nevow also includes loaders.xmlfile which implements a -simple tag attribute language similar to the Zope Page Templates (ZPT) Tag -Attribute Language (TAL). However, experience with the stan DOM should give you -insight into how the Nevow rendering process really works. Rendering a template -into HTML in Nevow is really nothing more than iterating a tree of objects and -recursively applying "Flattener" functions to objects in this tree, until all -HTML has been generated. - -Functions in the DOM --------------------- - -So far, all of our examples have generated static HTML pages, which is not -terribly interesting when discussing dynamic web applications. Nevow takes a -very simple approach to dynamic HTML generation. If you put a Python function -reference in the DOM, Nevow will call it when the page is rendered. The return -value of the function replaces the function itself in the DOM, and the results -are flattened further. This makes it easy to express looping and branching -structures in Nevow, because normal Python looping and branching constructs are -used to do the job:: - - >>> def repeat(ctx, data): - ... return [tags.div(style="color: %s" % (color, )) - ... for color in ['red', 'blue', 'green']] - ... - >>> class Repeat(rend.Page): - ... docFactory = loaders.stan(tags.html[repeat]) - ... - >>> Repeat().renderSynchronously() - '
' - -However, in the example above, the repeat function isn't even necessary, because -we could have inlined the list comprehension right where we placed the function -reference in the DOM. Things only really become interesting when we begin -writing parameterized render functions which cause templates to render -differently depending on the input to the web application. - -The required signature of functions which we can place in the DOM is (ctx, -data). The "context" object is essentially opaque for now, and we will learn how -to extract useful information out of it later. The "data" object is anything we -want it to be, and can change during the rendering of the page. By default, the -data object is whatever we pass as the first argument to the Page constructor, -**or** the Page instance itself if nothing is passed. Armed with this knowledge, -we can create a Page which renders differently depending on the data we pass to -the Page constructor:: - - class Root(rend.Page): - docFactory = loaders.stan(tags.html[ - tags.h1["Welcome."], - tags.a(href="foo")["Foo"], - tags.a(href="bar")["Bar"], - tags.a(href="baz")["Baz"]]) - - def childFactory(self, ctx, name): - return Leaf(name) - - - def greet(ctx, name): - return "Hello. You are visiting the ", name, " page." - - class Leaf(rend.Page): - docFactory = loaders.stan(tags.html[greet]) - -Armed with this knowledge and the information in the Object Traversal -documentation, we now have enough information to create dynamic websites with -arbitrary URL hierarchies whose pages render dynamically depending on which URL -was used to access them. - -Accessing query parameters and form post data ---------------------------------------------- - -Before we move on to more advanced rendering techniques, let us first examine -how one could further customize the rendering of a Page based on the URL query -parameters and form post information provided to us by a browser. Recall that -URL parameters are expressed in the form:: - - http://example.com/foo/bar?baz=1&quux=2 - -And form post data can be generated by providing a form to a browser:: - -
- - - -
- -Accessing this information is such a common procedure that Nevow provides a -convenience method on the context to do it. Let's examine a simple page whose -output can be influenced by the query parameters in the URL used to access it:: - - def showChoice(ctx, data): - choice = ctx.arg('choice') - if choice is None: - return '' - return "You chose ", choice, "." - - class Custom(rend.Page): - docFactory = loaders.stan(tags.html[ - tags.a(href="?choice=baz")["Baz"], - tags.a(href="?choice=quux")["Quux"], - tags.p[showChoice]]) - -The procedure is exactly the same for simple form post information:: - - def greet(ctx, data): - name = ctx.arg('name') - if name is None: - return '' - return "Greetings, ", name, "!" - - class Form(rend.Page): - docFactory = loaders.stan(tags.html[ - tags.form(action="", method="POST")[ - tags.input(name="name"), - tags.input(type="submit")], - greet]) - -Note that ctx.arg returns only the first argument with the given name. For -complex cases where multiple arguments and lists of argument values are -required, you can access the request argument dictionary directly using the -syntax:: - - def arguments(ctx, data): - args = inevow.IRequest(ctx).args - return "Request arguments are: ", str(args) - -Generators in the DOM ---------------------- - -One common operation when building dynamic pages is iterating a list of data and -emitting some HTML for each item. Python generators are well suited for -expressing this sort of logic, and code which is written as a python generator -can perform tests (if) and loops of various kinds (while, for) and emit a row of -html whenever it has enough data to do so. Nevow can handle generators in the -DOM just as gracefully as it can handle anything else:: - - >>> from nevow import rend, loaders, tags - >>> def generate(ctx, items): - ... for item in items: - ... yield tags.div[ item ] - ... - >>> class List(rend.Page): - ... docFactory = loaders.stan(tags.html[ generate ]) - ... - >>> List(['one', 'two', 'three']).renderSynchronously() - '
one
two
three
' - -As you can see, generating HTML inside of functions or generators can be very -convenient, and can lead to very rapid application development. However, it is -also what I would call a "template abstraction violation", and we will learn how -we can keep knowledge of HTML out of our python code when we learn about -patterns and slots. - -Methods in the DOM ------------------- - -Up until now, we have been placing our template manipulation logic inside of -simple Python functions and generators. However, it is often appropriate to use -a method instead of a function. Nevow makes it just as easy to use a method to -render HTML:: - - class MethodRender(rend.Page): - def __init__(self, foo): - self.foo = foo - - def render_foo(self, ctx, data): - return self.foo - - docFactory = loaders.stan(tags.html[ render_foo ]) - -Using render methods makes it possible to parameterize your Page class with more -parameters. With render methods, you can also use the Page instance as a state -machine to keep track of the state of the render. While Nevow is designed to -allow you to render the same Page instance repeatedly, it can also be convenient -to know that a Page instance will only be used one time, and that the Page -instance can be used as a scratch pad to manage information about the render. - -Data specials -------------- - -Previously we saw how passing a parameter to the default Page constructor makes -it available as the "data" parameter to all of our render methods. This "data" -parameter can change as the page render proceeds, and is a useful way to ensure -that render functions are isolated and only act upon the data which is available -to them. Render functions which do not pull information from sources other than -the "data" parameter are more easily reusable and can be composed into larger -parts more easily. - -Deciding which data gets passed as the data parameter is as simple as changing -the "Data special" for a Tag. See the Glossary under "Tag Specials" for more -information about specials. Assigning to the data special is as simple as -assigning to a tag attribute:: - - >>> def hello(ctx, name): - ... return "Hello, ", name - ... - >>> class DataSpecial(rend.Page): - ... docFactory = loaders.stan(tags.html[ - ... tags.div(data="foo")[ hello ], - ... tags.div(data="bar")[ hello ]]) - ... - >>> DataSpecial().renderSynchronously() - '
Hello, foo
Hello, bar
' - -Data specials may be assigned any python value. Data specials are only in scope -during the rendering of the tag they are assigned to, so if the "hello" renderer -were placed in the DOM inside the html node directly, "Hello, None" would be -output. - -Before data is passed to a render function, Nevow first checks to see if there -is an IGettable adapter for it. If there is, it calls IGettable.get(), and -passes the result of this as the data parameter instead. Nevow includes an -IGettable adapter for python functions, which means you can set a Tag data -special to a function reference and Nevow will call it to obtain the data when -the Tag is rendered. The signature for data methods is similar to that of render -methods, (ctx, data). For example:: - - def getName(ctx, data): - return ctx.arg('name') - - def greet(ctx, name): - return "Greetings, ", name - - class GreetName(rend.Page): - docFactory = loaders.stan(tags.html[ - tags.form(action="")[ - tags.input(name="name"), - tags.input(type="submit")], - tags.div(data=getName)[ greet ]]) - -Data specials exist mainly to allow you to construct and enforce a -Model-View-Controller style separation of the Model code from the View. Here we -see that the greet function is capable of rendering a greeting view for a name -model, and that the implementation of getName may change without the view code -changing. - -Render specials ---------------- - -Previously, we have seen how render functions can be placed directly in the DOM, -and the return value replaces the render function in the DOM. However, these -free functions and methods are devoid of any contextual information about the -template they are living in. The render special is a way to associate a render -function or method with a particular Tag instance, which the render function can -then examine to decide how to render:: - - >>> def alignment(ctx, data): - ... align = ctx.tag.attributes.get('align') - ... if align == 'right': - ... return ctx.tag["Aligned right"] - ... elif align == 'center': - ... return ctx.tag["Aligned center"] - ... else: - ... return ctx.tag["Aligned left"] - ... - >>> class AlignmentPage(rend.Page): - ... docFactory = loaders.stan(tags.html[ - ... tags.p(render=alignment), - ... tags.p(render=alignment, align="center"), - ... tags.p(render=alignment, align="right")]) - ... - >>> AlignmentPage().renderSynchronously() - '

Aligned left

Aligned center

Aligned right

' - -Note how the alignment renderer has access to the template node as "ctx.tag". It -can examine and change this node, and the return value of the render function -replaces the original node in the DOM. Note that here we are returning the -template node after changing it. We will see later how we can instead mutate the -context and use slots so that the knowledge the renderer requires about the -structure of the template is reduced even more. - -Pattern specials ----------------- - -When writing render methods, it is easy to inline the construction of Tag -instances to generate HTML programatically. However, this creates a template -abstraction violation, where part of the HTML which will show up in the final -page output is hidden away inside of render methods instead of inside the -template. Pattern specials are designed to avoid this problem. A node which has -been tagged with a pattern special can then be located and copied by a render -method. The render method does not need to know anything about the structure or -location of the pattern, only it's name. - -We can rewrite our previous generator example so that the generator does not -have to know what type of tag the template designer would like repeated for each -item in the list:: - - >>> from nevow import rend, loaders, tags, inevow - >>> def generate(ctx, items): - ... pat = inevow.IQ(ctx).patternGenerator('item') - ... for item in items: - ... ctx.tag[ pat(data=item) ] - ... return ctx.tag - ... - >>> def string(ctx, item): - ... return ctx.tag[ str(item) ] - ... - >>> class List(rend.Page): - ... docFactory = loaders.stan(tags.html[ - ... tags.ul(render=generate)[ - ... tags.li(pattern="item", render=string)]]) - ... - >>> List([1, 2, 3]).renderSynchronously() - '
  1. 1
  2. 2
  3. 3
' - -Note that we have to mutate the tag in place and repeatedly copy the item -pattern, applying the item as the data special to the resulting Tag. It turns -out that this is such a common operation that nevow comes out of the box with -these two render functions:: - - >>> class List(rend.Page): - ... docFactory = loaders.stan(tags.html[ - ... tags.ul(render=rend.sequence)[ - ... tags.li(pattern="item", render=rend.data)]]) - ... - >>> List([1, 2, 3]).renderSynchronously() - '
  • 1
  • 2
  • 3
' - -Slot specials -------------- - -The problem with render methods is that they are only capable of making changes -to their direct children. Because of the architecture of Nevow, they should not -attempt to change grandchildren or parent nodes. It is possible to write one -render method for every node you wish to change, but there is a better way. A -node with a slot special can be "filled" with content by any renderer above the -slot. Creating a slot special is such a frequent task that there is a prototype -in nevow.tags which is usually used. - -Let us examine a renderer which fills a template with information about a -person: - - >>> from nevow import loaders, rend, tags - ... - >>> person = ('Donovan', 'Preston', 'Male', 'California') - ... - >>> def render_person(ctx, person): - ... firstName, lastName, sex, location = person - ... ctx.fillSlots('firstName', firstName) - ... ctx.fillSlots('lastName', lastName) - ... ctx.fillSlots('sex', sex) - ... ctx.fillSlots('location', location) - ... return ctx.tag - ... - >>> class PersonPage(rend.Page): - ... docFactory = loaders.stan(tags.html(render=render_person)[ - ... tags.table[ - ... tags.tr[ - ... tags.td[tags.slot('firstName')], - ... tags.td[tags.slot('lastName')], - ... tags.td[tags.slot('sex')], - ... tags.td[tags.slot('location')]]]]) - ... - >>> PersonPage(person).renderSynchronously() - '
DonovanPrestonMaleCalifornia
' - -Using patterns in combination with slots can lead to very powerful template -abstraction. Nevow also includes another standard renderer called "mapping" -which takes any data which responds to the "items()" message and inserts the -items into appropriate slots:: - - >>> class DictPage(rend.Page): - ... docFactory = loaders.stan(tags.html(render=rend.mapping)[ - ... tags.span[ tags.slot('foo') ], tags.span[ tags.slot('bar') ]]) - ... - >>> DictPage(dict(foo=1, bar=2)).renderSynchronously() - '12' - -Data directives ---------------- - -So far, we have always placed data functions directly in the Data special -attribute of a Tag. Sometimes, it is preferable to look up a data method from -the Page class as the Page has being rendered. For example, a base class may -define a template and a subclass may provide the implementation of the data -method. We can accomplish this effect by using a data directive as a Tag's data -special:: - - class Base(rend.Page): - docFactory = loaders.stan(tags.html[ - tags.div(data=tags.directive('name'), render=rend.data)]) - - class Subclass(Base): - def data_name(self, ctx, data): - return "Your name" - -The data directive is resolved by searching for the IContainer implementation in -the context. rend.Page implements IContainer.get by performing an attribute -lookup on the Page with the prefix 'data_*'. You can provide your own IContainer -implementation if you wish, and also you should know that IContainer -implementations for list and dict are included in the nevow.accessors module. - -A common gotcha is that the closest IContainer is used to resolve data -directives. This means that if a list is being used as the data during the -rendering process, data directives below this will be resolved against the -IContainer implementation in nevow.accessors.ListAccessor. If you are expecting -a data directive to invoke a Page's data_* method but instead get a KeyError, -this is why. - -Render directives ------------------ - -Render directives are almost exactly the same, except they are resolved using -the closest IRendererFactory implementation in the context. Render directives -can be used to allow subclasses to override certain render methods, and also can -be used to allow Fragments to locate their own prefixed render methods. - -Flatteners ----------- - -TODO This section isn't done yet. - -Nevow's flatteners use a type/function registry to determine how to render -objects which Nevow encounters in the DOM during the rendering process. -"Explicit is better than implicit", so in most cases, explicitly applying render -methods to data will be better than registering a flattener, but in some cases -it can be useful:: - - class Person(object): - def __init__(self, firstName, lastName): - self.firstName = firstName - self.lastName = lastName - - def flattenPerson(person, ctx): - return flat.partialflatten( - ctx, - (person.firstName, " ", person.lastName)) - - from nevow import flat - flat.registerFlattener(flattenPerson, Person) - - def insertData(ctx, data): - return data - - class PersonPage(rend.Page): - docFactory = loaders.stan(tags.html[ insertData ]) - diff -Nru nevow-0.9.31/doc/txt/nevow-traversal.txt nevow-0.10.0/doc/txt/nevow-traversal.txt --- nevow-0.9.31/doc/txt/nevow-traversal.txt 2006-08-01 17:15:24.000000000 +0100 +++ nevow-0.10.0/doc/txt/nevow-traversal.txt 1970-01-01 01:00:00.000000000 +0100 @@ -1,332 +0,0 @@ -Nevow Object Traversal -====================== - -*Object traversal* is the process Nevow uses to determine what object to use to -render HTML for a particular URL. When an HTTP request comes in to the web -server, the object publisher splits the URL into segments, and repeatedly calls -methods which consume path segments and return objects which represent that -path, until all segments have been consumed. At the core, the Nevow traversal -API is very simple. However, it provides some higher level functionality layered -on top of this to satisfy common use cases. - -* `Object Traversal Basics`_ -* `locateChild in depth`_ -* `childFactory method`_ -* `child_* methods and attributes`_ -* `Dots in child names`_ -* `children dictionary`_ -* `The default trailing slash handler`_ -* `ICurrentSegments and IRemainingSegments`_ - -Object Traversal Basics ------------------------ - -The *root resource* is the top-level object in the URL space; it conceptually -represents the URI "/". The Nevow *object traversal* and *object publishing* -machinery uses only two methods to locate an object suitable for publishing and -to generate the HTML from it; these methods are described in the interface -``nevow.inevow.IResource``:: - - - class IResource(compy.Interface): - def locateChild(self, ctx, segments): - """Locate another object which can be adapted to IResource - Return a tuple of resource, path segments - """ - - def renderHTTP(self, ctx): - """Render a request - """ - -``renderHTTP`` can be as simple as a method which simply returns a string of HTML. -Let's examine what happens when object traversal occurs over a very simple root -resource:: - - from zope.interface import implements - - class SimpleRoot(object): - implements(inevow.IResource) - - def locateChild(self, ctx, segments): - return self, () - - def renderHTTP(self, ctx): - return "Hello, world!" - -This resource, when passed as the root resource to ``appserver.NevowSite`` or -``wsgi.createWSGIApplication``, will immediately return itself, consuming all path -segments. This means that for every URI a user visits on a web server which is -serving this root resource, the text "Hello, world!" will be rendered. Let's -examine the value of ``segments`` for various values of URI: - -/foo/bar - ('foo', 'bar') - -/ - ('', ) - -/foo/bar/baz.html - ('foo', 'bar', 'baz.html') - -/foo/bar/directory/ - ('foo', 'bar', 'directory', '') - -So we see that Nevow does nothing more than split the URI on the string '/' and -pass these path segments to our application for consumption. Armed with these -two methods alone, we already have enough information to write applications -which service any form of URL imaginable in any way we wish. However, there are -some common URL handling patterns which Nevow provides higher level support for. - -``locateChild`` in depth ------------------------- - -One common URL handling pattern involves parents which only know about their -direct children. For example, a ``Directory`` object may only know about the -contents of a single directory, but if it contains other directories, it does -not know about the contents of them. Let's examine a simple ``Directory`` object -which can provide directory listings and serves up objects for child directories -and files:: - - from zope.interface import implements - - class Directory(object): - implements(inevow.IResource) - - def __init__(self, directory): - self.directory = directory - - def renderHTTP(self, ctx): - html = ['
    '] - for child in os.listdir(self.directory): - fullpath = os.path.join(self.directory, child) - if os.path.isdir(fullpath): - child += '/' - html.extend(['
  • ', child, '
  • ']) - html.append('
') - return ''.join(html) - - def locateChild(self, ctx, segments): - name = segments[0] - fullpath = os.path.join(self.directory, name) - if not os.path.exists(fullpath): - return None, () # 404 - - if os.path.isdir(fullpath): - return Directory(fullpath), segments[1:] - if os.path.isfile(fullpath): - return static.File(fullpath), segments[1:] - -Because this implementation of ``locateChild`` only consumed one segment and -returned the rest of them (``segments[1:]``), the object traversal process will -continue by calling ``locateChild`` on the returned resource and passing the -partially-consumed segments. In this way, a directory structure of any depth can -be traversed, and directory listings or file contents can be rendered for any -existing directories and files. - -So, let us examine what happens when the URI "/foo/bar/baz.html" is traversed, -where "foo" and "bar" are directories, and "baz.html" is a file. - -Directory('/').locateChild(ctx, ('foo', 'bar', 'baz.html')) - Returns Directory('/foo'), ('bar', 'baz.html') - -Directory('/foo').locateChild(ctx, ('bar', 'baz.html')) - Returns Directory('/foo/bar'), ('baz.html, ) - -Directory('/foo/bar').locateChild(ctx, ('baz.html')) - Returns File('/foo/bar/baz.html'), () - -No more segments to be consumed; ``File('/foo/bar/baz.html').renderHTTP(ctx)`` is -called, and the result is sent to the browser. - -``childFactory`` method ------------------------ - -Consuming one URI segment at a time by checking to see if a requested resource -exists and returning a new object is a very common pattern. Nevow's default -implementation of ``IResource``, ``nevow.rend.Page``, contains an implementation of -``locateChild`` which provides more convenient hooks for implementing object -traversal. One of these hooks is ``childFactory``. Let us imagine for the sake of -example that we wished to render a tree of dictionaries. Our data structure -might look something like this:: - - tree = dict( - one=dict( - foo=None, - bar=None), - two=dict( - baz=dict( - quux=None))) - -Given this data structure, the valid URIs would be: - -* / -* /one -* /one/foo -* /one/bar -* /two -* /two/baz -* /two/baz/quux - -Let us construct a ``rend.Page`` subclass which uses the default ``locateChild`` -implementation and overrides the ``childFactory`` hook instead:: - - class DictTree(rend.Page): - def __init__(self, dataDict): - self.dataDict = dataDict - - def renderHTTP(self, ctx): - if self.dataDict is None: - return "Leaf" - html = ['
    '] - for key in self.dataDict.keys(): - html.extend(['
  • ', key, '
  • ']) - html.append('
') - return ''.join(html) - - def childFactory(self, ctx, name): - if name not in self.dataDict: - return rend.NotFound # 404 - return DictTree(self.dataDict[name]) - -As you can see, the ``childFactory`` implementation is considerably shorter than the -equivalent ``locateChild`` implementation would have been. - -``child_*`` methods and attributes ----------------------------------- - -Often we may wish to have some hardcoded URLs which are not dynamically -generated based on some data structure. For example, we might have an -application which uses an external CSS stylesheet, an external JavaScript file, -and a folder full of images. The ``rend.Page`` ``locateChild`` implementation provides a -convenient way for us to express these relationships by using ``child``-prefixed -methods:: - - class Linker(rend.Page): - def renderHTTP(self, ctx): - return """ - - - - +
Calculator Goes Here
+ +
- + - + - - - - + + + + - - - - + + + + - - - - + + + + - - - - + + + +
00
diff -Nru nevow-0.9.31/examples/athenademo/calculator.js nevow-0.10.0/examples/athenademo/calculator.js --- nevow-0.9.31/examples/athenademo/calculator.js 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/examples/athenademo/calculator.js 2008-05-20 22:21:26.000000000 +0100 @@ -0,0 +1,20 @@ +// import Nevow.Athena +// import Divmod.Runtime + +CalculatorDemo.Calculator = Nevow.Athena.Widget.subclass("CalculatorDemo.Calculator"); +CalculatorDemo.Calculator.methods( + /** + * Handle click events on any of the calculator buttons. + */ + function buttonClicked(self, node) { + var symbol = node.value; + d = self.callRemote("buttonClicked", symbol); + d.addCallback( + function(expression) { + var output = self.nodeById("output"); + output.replaceChild( + document.createTextNode(expression), + output.firstChild); + }); + return false; + }); diff -Nru nevow-0.9.31/examples/athenademo/calculator.py nevow-0.10.0/examples/athenademo/calculator.py --- nevow-0.9.31/examples/athenademo/calculator.py 2006-04-14 19:23:46.000000000 +0100 +++ nevow-0.10.0/examples/athenademo/calculator.py 2008-05-20 22:21:26.000000000 +0100 @@ -1,24 +1,52 @@ -from zope.interface import Interface -from twisted.python.components import registerAdapter -from nevow import athena, inevow, loaders, util - -class ICalculator(Interface): - def buttonClicked(button): - pass +# Copyright (c) 2008 Divmod. See LICENSE for details. + +""" +Demonstration of an Athena Widget which accepts input from the browser and +sends back responses. +""" + +import sys + +from twisted.python.filepath import FilePath +from twisted.python import log +from twisted.internet import reactor + +from nevow.athena import LivePage, LiveElement, expose +from nevow.loaders import xmlfile +from nevow.appserver import NevowSite + +# Handy helper for finding external resources nearby. +sibling = FilePath(__file__).sibling + class Calculator(object): + """ + The model object for the calculator demo. This is the object which + actually knows how to perform calculations. - validSymbols = '0123456789/*-=+.C' + @ivar expression: A C{str} giving the current expression which has been + entered into the calculator. For example, if the buttons '3', '5', and + '+' have been pressed (in that order), C{expression} will be C{'35+'}. + """ defaultExpression = u'0' errorExpression = u'E' def __init__(self): self.expression = self.defaultExpression + def buttonClicked(self, symbol): - # Remember ... never trust a browser - if symbol not in self.validSymbols: - raise ValueError('Invalid symbol') + """ + Change the current expression by performing the operation indicated by + C{symbol} (clearing it or computing it) or by extending it (with a + digit or operator). + + @param symbol: C{'C'} to clear the expression, C{'='} to evaluate the + expression, or one of C{'0'}-C{'9'}. + + @rtype: C{unicode} + @return: The expression after interpreting the new symbol. + """ # Clear if symbol == 'C': self.expression = self.defaultExpression @@ -40,32 +68,82 @@ self.expression += symbol return self.expression -class CalculatorResource(athena.LivePage): + + +class CalculatorElement(LiveElement): """ A "live" calculator. All buttons presses in the browser are sent to the server. The server evaluates the expression and sets the output in the browser. + + @ivar validSymbols: A C{str} giving all of the symbols which the browser is + allowed to submit to us. Input is checked against this before being + submitted to the model. + + @ivar calc: A L{Calculator} which will be used to handle all inputs and + generate computed outputs. """ - addSlash = True - docFactory = loaders.xmlfile( - util.resource_filename('athenademo', 'calculator.html') - ) + docFactory = xmlfile(sibling('calculator.html').path, 'CalculatorPattern') -if __name__ == '__main__': + jsClass = u"CalculatorDemo.Calculator" + + validSymbols = '0123456789/*-=+.C' + + def __init__(self, calc): + LiveElement.__init__(self) + self.calc = calc + + + def buttonClicked(self, symbol): + """ + Accept a symbol from the browser, perform input validation on it, + provide it to the underlying L{Calculator} if appropriate, and return + the result. + + @type symbol: C{unicode} + @rtype: C{unicode} + """ + # Remember ... never trust a browser + if symbol not in self.validSymbols: + raise ValueError('Invalid symbol') + return self.calc.buttonClicked(symbol) + expose(buttonClicked) - import sys - from twisted.internet import reactor - from twisted.python import log - from nevow import appserver - def calculatorResourceFactory(original): - return CalculatorResource(ICalculator, original) - registerAdapter(calculatorResourceFactory, Calculator, inevow.IResource) +class CalculatorParentPage(LivePage): + """ + A "live" container page for L{CalculatorElement}. + """ + docFactory = xmlfile(sibling('calculator.html').path) + def __init__(self, *a, **kw): + LivePage.__init__(self) + # Update the mapping of known JavaScript modules so that the + # client-side code for this example can be found and served to the + # browser. + self.jsModules.mapping[u'CalculatorDemo'] = sibling( + 'calculator.js').path + + + def render_calculator(self, ctx, data): + """ + Replace the tag with a new L{CalculatorElement}. + """ + c = CalculatorElement(Calculator()) + c.setFragmentParent(self) + return c + + + +def main(): log.startLogging(sys.stdout) - calculator = Calculator() - site = appserver.NevowSite(calculator) + site = NevowSite(CalculatorParentPage(calc=Calculator())) reactor.listenTCP(8080, site) reactor.run() + + + +if __name__ == '__main__': + main() diff -Nru nevow-0.9.31/examples/chatola/Chatola.html nevow-0.10.0/examples/chatola/Chatola.html --- nevow-0.9.31/examples/chatola/Chatola.html 2005-08-03 00:11:53.000000000 +0100 +++ nevow-0.10.0/examples/chatola/Chatola.html 1970-01-01 01:00:00.000000000 +0100 @@ -1,87 +0,0 @@ - - - - - - Nevow Chatola - - - - - -
-
-
- - - -
-
-
-
Userlist
-
- user-list- - -
-
-
- - -
-
- - -
-
- - -
- - - - diff -Nru nevow-0.9.31/examples/chatola/chatola.py nevow-0.10.0/examples/chatola/chatola.py --- nevow-0.9.31/examples/chatola/chatola.py 2005-07-12 04:08:03.000000000 +0100 +++ nevow-0.10.0/examples/chatola/chatola.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,107 +0,0 @@ - - -# chatola.py -# a simple chat engine - -import os, random, time - -from nevow import inevow, loaders, livepage -from nevow.livepage import set, assign, append, js, document, eol - - -chatolaDir = os.path.split(os.path.abspath(__file__))[0] - - -if os.path.exists('/usr/share/dict/words'): - WORDS = open('/usr/share/dict/words').readlines() -else: - WORDS = open(os.path.join( - os.path.split( - chatolaDir)[0], 'files', 'words')).readlines() - - -class Chatola(livepage.LivePage): - addSlash = True - docFactory = loaders.xmlfile(os.path.join(chatolaDir, 'Chatola.html')) - messagePattern = inevow.IQ(docFactory).patternGenerator('message') - userPattern = inevow.IQ(docFactory).patternGenerator('user') - topic = "Welcome to Chatola" - - def __init__(self): - self.clients = [] - self.events = [] - self.sendEvent( - None, - set('topic', self.topic), eol, - assign(document.topicForm.topic.value, self.topic), eol) - - livepage.LivePage.__init__(self) - - def goingLive(self, ctx, client): - client.notifyOnClose().addBoth(self.userLeft, client) - - client.userId = random.choice(WORDS).strip() - client.send( - assign(document.nickForm.nick.value, client.userId)) - - addUserlistEntry = append('userlist', self.userPattern.fillSlots('user-id', client.userId)), eol - self.sendEvent( - client, addUserlistEntry, self.content(client, 'has joined.')) - - ## Catch the user up with the previous events - client.send([(event, eol) for source, event in self.events]) - - self.clients.append(client) - - def userLeft(self, _, client): - self.clients.remove(client) - self.sendEvent( - client, - js.removeNode('user-list-%s' % (client.userId, )), eol, - self.content(client, 'has left.')) - - def sendEvent(self, source, *event): - self.events.append((source, event)) - for target in self.clients: - if target is not source: - target.send(event) - return event - - def content(self, sender, message): - return append( - 'content', - self.messagePattern.fillSlots( - 'timestamp', time.strftime("%H:%M %d/%m/%y") - ).fillSlots( - 'userid', sender.userId - ).fillSlots( - 'message', message)), eol, js.scrollDown() - - def handle_sendInput(self, ctx, inputLine): - sender = livepage.IClientHandle(ctx) - return self.sendEvent(sender, self.content(sender, inputLine)), eol, js.focusInput() - - def handle_changeTopic(self, ctx, topic): - changer = livepage.IClientHandle(ctx) - return self.sendEvent( - changer, - set('topic', topic), eol, - assign(document.topicForm.topic.value, topic), eol, - self.content(changer, 'changed the topic to %r.' % (topic, ))) - - def handle_changeNick(self, ctx, nick): - changer = livepage.IClientHandle(ctx) - rv = self.sendEvent( - changer, - set('user-list-%s' % (changer.userId, ), nick), eol, - js.changeId('user-list-%s' % (changer.userId, ), 'user-list-%s' % (nick, )), eol, - self.content(changer, 'changed nick to %r.' % (nick, ))) - - changer.userId = nick - return rv - - -def createResource(): - return Chatola() - - diff -Nru nevow-0.9.31/examples/chatola/chatola.tac nevow-0.10.0/examples/chatola/chatola.tac --- nevow-0.9.31/examples/chatola/chatola.tac 2005-06-13 03:55:52.000000000 +0100 +++ nevow-0.10.0/examples/chatola/chatola.tac 1970-01-01 01:00:00.000000000 +0100 @@ -1,7 +0,0 @@ -from twisted.application import service, strports -from nevow import appserver -import chatola - -site = appserver.NevowSite(chatola.createResource()) -application = service.Application("chatola") -strports.service("8080", site).setServiceParent(application) diff -Nru nevow-0.9.31/examples/examples.tac nevow-0.10.0/examples/examples.tac --- nevow-0.9.31/examples/examples.tac 2006-02-02 00:08:11.000000000 +0000 +++ nevow-0.10.0/examples/examples.tac 2009-07-06 14:22:09.000000000 +0100 @@ -54,7 +54,6 @@ from nestedsequence import nestedsequence from fragments import fragments from macros import macros - from chatola import chatola from i18n import i18n, xmli18n from cal import cal from tabbed import tabbed @@ -152,7 +151,6 @@ xul_nevow=xul_nevow.createResource(), advanced_manualform=advanced_manualform.Page(), liveanimal=liveanimal.createResource(), - chatola=chatola.createResource(), http_auth=http_auth.AuthorizationRequired(), most_basic=most_basic.root, logout_guard=logout_guard.createResource(), @@ -176,7 +174,7 @@ ) def child_calculator(self, ctx): - return calculator.CalculatorResource(calculator.ICalculator, calculator.Calculator()) + return calculator.CalculatorParentPage(calc=calculator.Calculator()) def child_widgets(self, ctx): return widgets.WidgetPage(None, None) diff -Nru nevow-0.9.31/examples/index.html nevow-0.10.0/examples/index.html --- nevow-0.9.31/examples/index.html 2006-01-22 00:10:42.000000000 +0000 +++ nevow-0.10.0/examples/index.html 2009-07-06 14:22:09.000000000 +0100 @@ -758,24 +758,6 @@
  • - Chatola: A multiuser realtime chat application using - LivePage - -
    - - - - - - - - -
    Python Sourcechatola.py
    Live example: chatola -
    -
  • - -
  • LiveAnimal: A small LivePage game
    @@ -979,14 +961,6 @@
  • - Liveevil example in a live chat page - -
    - Source: chatola -
    -
  • - -
  • Pastebin example with nevow
    diff -Nru nevow-0.9.31/formless/annotate.py nevow-0.10.0/formless/annotate.py --- nevow-0.9.31/formless/annotate.py 2006-03-30 16:52:55.000000000 +0100 +++ nevow-0.10.0/formless/annotate.py 2008-03-24 17:27:18.000000000 +0000 @@ -504,7 +504,7 @@ keyword argument, a Typed instance, as the binding typeValue. One more thing. When an autocallable method is found, it is called with - None as the self argument. The return value is passed the the Method + None as the self argument. The return value is passed the Method Binding when it is constructed to keep track of what the method is supposed to return. """ diff -Nru nevow-0.9.31/nevow/appserver.py nevow-0.10.0/nevow/appserver.py --- nevow-0.9.31/nevow/appserver.py 2008-01-31 19:06:58.000000000 +0000 +++ nevow-0.10.0/nevow/appserver.py 2008-12-05 15:27:29.000000000 +0000 @@ -87,7 +87,7 @@ log.err("Original exception:", isErr=1) log.err(reason) request.write("Internal Server Error") - request.write("

    Internal Server Error

    An error occurred rendering the requested page. Additionally, an error occured rendering the error page.") + request.write("

    Internal Server Error

    An error occurred rendering the requested page. Additionally, an error occurred rendering the error page.") request.finishRequest( False ) return errorMarker @@ -362,7 +362,7 @@ def renderHTTP(self, ctx): request = inevow.IRequest(ctx) if self.real_prepath_len is not None: - path = request.postpath = request.prepath[self.real_prepath_len:] + request.postpath = request.prepath[self.real_prepath_len:] del request.prepath[self.real_prepath_len:] result = defer.maybeDeferred(self.original.render, request).addCallback( self._handle_NOT_DONE_YET, request) diff -Nru nevow-0.9.31/nevow/athena.py nevow-0.10.0/nevow/athena.py --- nevow-0.9.31/nevow/athena.py 2007-12-20 12:22:02.000000000 +0000 +++ nevow-0.10.0/nevow/athena.py 2009-07-08 03:51:14.000000000 +0100 @@ -1,7 +1,6 @@ # -*- test-case-name: nevow.test.test_athena -*- import itertools, os, re, warnings -from sys import maxint from zope.interface import implements @@ -10,7 +9,7 @@ from twisted.python.util import sibpath from twisted import plugin -from nevow import inevow, plugins, flat +from nevow import inevow, plugins, flat, _flat from nevow import rend, loaders, url, static from nevow import json, util, tags, guard, stan from nevow.util import CachedFile @@ -86,13 +85,15 @@ -class JSModules(object): +class MappingResource(object): """ - Serve implementation files for a JavaScript module system. + L{inevow.IResource} which looks up segments in a mapping between symbolic + names and the files they correspond to. - @ivar mapping: A C{dict} mapping JavaScript module names (eg, - 'Nevow.Athena') to C{str} instances which name files containing - JavaScript source implementing those modules. + @type mapping: C{dict} + @ivar mapping: A map between symbolic, requestable names (eg, + 'Nevow.Athena') and C{str} instances which name files containing data + which should be served in response. """ implements(inevow.IResource) @@ -122,24 +123,23 @@ -# XXX Next two functions copied out of Mantissa/xmantissa/signup.py -def _insertDep(dependent, ordered): - for dependency in dependent.dependencies(): - _insertDep(dependency, ordered) - if dependent not in ordered: - ordered.append(dependent) - - +def _dependencyOrdered(coll, memo): + """ + @type coll: iterable of modules + @param coll: The initial sequence of modules. -def dependencyOrdered(coll): - ordered = [] - for dependent in coll: - _insertDep(dependent, ordered) - return ordered + @type memo: C{dict} + @param memo: A dictionary mapping module names to their dependencies that + will be used as a mutable cache. + """ -class JSModule(object): +class AthenaModule(object): + """ + A representation of a chunk of stuff in a file which can depend on other + chunks of stuff in other files. + """ _modules = {} lastModified = 0 @@ -169,7 +169,7 @@ def __repr__(self): - return 'JSModule(%r)' % (self.name,) + return '%s(%r)' % (self.__class__.__name__, self.name,) _importExpression = re.compile('^// import (.+)$', re.MULTILINE) @@ -195,70 +195,155 @@ return self._cache.load() - def allDependencies(self): - if self.mapping[self.name] is None: - return [] - else: - mods = [self] - return dependencyOrdered(mods) + def allDependencies(self, memo=None): + """ + Return the transitive closure of dependencies, including this module. + + The transitive dependencies for this module will be ordered such that + any particular module is located after all of its dependencies, with no + module occurring more than once. + + The dictionary passed in for C{memo} will be modified in-place; if it + is reused across multiple calls, dependencies calculated during a + previous invocation will not be recalculated again. + + @type memo: C{dict} of C{str: list of AthenaModule} + @param memo: A dictionary mapping module names to the modules they + depend on that will be used as a mutable cache. + + @rtype: C{list} of C{AthenaModule} + """ + if memo is None: + memo = {} + ordered = [] + + def _getDeps(dependent): + if dependent.name in memo: + deps = memo[dependent.name] + else: + memo[dependent.name] = deps = dependent.dependencies() + return deps + + def _insertDep(dependent): + if dependent not in ordered: + for dependency in _getDeps(dependent): + _insertDep(dependency) + ordered.append(dependent) + + _insertDep(self) + return ordered + + + +class JSModule(AthenaModule): + """ + L{AthenaModule} subclass for dealing with Javascript modules. + """ + _modules= {} + + + +class CSSModule(AthenaModule): + """ + L{AthenaModule} subclass for dealing with CSS modules. + """ + _modules = {} class JSPackage(object): + """ + A Javascript package. + + @type mapping: C{dict} + @ivar mapping: Mapping between JS module names and C{str} representing + filesystem paths containing their implementations. + """ implements(plugin.IPlugin, inevow.IJavascriptPackage) def __init__(self, mapping): - """ - @param mapping: A C{dict} mapping JS module names to C{str} - representing filesystem paths containing their - implementations. - """ self.mapping = mapping -class AutoJSPackage(object): + +def _collectPackageBelow(baseDir, extension): """ - An IJavascriptPackage implementation that scans an on-disk hierarchy - locating modules and packages. + Assume a filesystem package hierarchy starting at C{baseDir}. Collect all + files within it ending with C{extension} into a mapping between + dot-separated symbolic module names and their corresponding filesystem + paths. Note that module/package names beginning with . are ignored. + + @type baseDir: C{str} + @param baseDir: A path to the root of a package hierarchy on a filesystem. + + @type extension: C{str} + @param extension: The filename extension we're interested in (e.g. 'css' + or 'js'). + + @rtype: C{dict} + @return: Mapping between C{unicode} module names and their corresponding + C{str} filesystem paths. + """ + mapping = {} + EMPTY = sibpath(__file__, 'empty-module.' + extension) + + _revMap = {baseDir: ''} + for (root, dirs, filenames) in os.walk(baseDir): + stem = _revMap[root] + dirs[:] = [d for d in dirs if not d.startswith('.')] + + for dir in dirs: + name = stem + dir + path = os.path.join(root, dir, '__init__.' + extension) + if not os.path.exists(path): + path = EMPTY + mapping[unicode(name, 'ascii')] = path + _revMap[os.path.join(root, dir)] = name + '.' + + for fn in filenames: + if fn.startswith('.'): + continue + + if fn == '__init__.' + extension: + continue + + if not fn.endswith('.' + extension): + continue + + name = stem + fn[:-(len(extension) + 1)] + path = os.path.join(root, fn) + mapping[unicode(name, 'ascii')] = path + return mapping + + + +class AutoJSPackage(object): + """ + A L{inevow.IJavascriptPackage} implementation that scans an on-disk + hierarchy locating modules and packages. + + @type baseDir: C{str} + @ivar baseDir: A path to the root of a JavaScript packages/modules + filesystem hierarchy. """ implements(plugin.IPlugin, inevow.IJavascriptPackage) def __init__(self, baseDir): - """ - @param baseDir: A path to the root of a JavaScript packages/modules - filesystem hierarchy. - """ - self.mapping = {} - EMPTY = sibpath(__file__, 'empty.js') + self.mapping = _collectPackageBelow(baseDir, 'js') + - _revMap = {baseDir: ''} - for root, dirs, filenames in os.walk(baseDir): - stem = _revMap[root] - dirs[:] = [d for d in dirs if not d.startswith('.')] - - for dir in dirs: - name = stem + dir - path = os.path.join(root, dir, '__init__.js') - if not os.path.exists(path): - path = EMPTY - self.mapping[unicode(name, 'ascii')] = path - _revMap[os.path.join(root, dir)] = name + '.' - - for fn in filenames: - if fn.startswith('.'): - continue - - if fn == '__init__.js': - continue - - if fn[-3:] != '.js': - continue - - name = stem + fn[:-3] - path = os.path.join(root, fn) - self.mapping[unicode(name, 'ascii')] = path + +class AutoCSSPackage(object): + """ + Like L{AutoJSPackage}, but for CSS packages. Modules within this package + can be referenced by L{LivePage.cssModule} or L{LiveElement.cssModule}. + """ + implements(plugin.IPlugin, inevow.ICSSPackage) + + def __init__(self, baseDir): + self.mapping = _collectPackageBelow(baseDir, 'css') @@ -276,13 +361,23 @@ +def allCSSPackages(): + """ + Like L{allJavascriptPackages}, but for CSS packages. + """ + d = {} + for p in plugin.getPlugIns(inevow.ICSSPackage, plugins): + d.update(p.mapping) + return d + + + class JSDependencies(object): """ Keeps track of which JavaScript files depend on which other JavaScript files (because JavaScript is a very poor language and cannot do this itself). """ - _loadPlugins = False def __init__(self, mapping=None): @@ -319,6 +414,41 @@ +class CSSRegistry(object): + """ + Keeps track of a set of CSS modules. + """ + def __init__(self, mapping=None): + if mapping is None: + mapping = {} + loadPlugins = True + else: + loadPlugins = False + self.mapping = mapping + self._loadPlugins = loadPlugins + + + def getModuleForName(self, moduleName): + """ + Turn a CSS module name into an L{AthenaModule}. + + @type moduleName: C{unicode} + + @rtype: L{CSSModule} + """ + if self._loadPlugins: + self.mapping.update(allCSSPackages()) + self._loadPlugins = False + try: + self.mapping[moduleName] + except KeyError: + raise RuntimeError('Unknown CSS module: %r' % (moduleName,)) + return CSSModule.getOrCreate(moduleName, self.mapping) + +_theCSSRegistry = CSSRegistry() + + + class JSException(Exception): """ Exception class to wrap remote exceptions from JavaScript. @@ -330,7 +460,6 @@ """ Class for mock code objects in mock JS frames. """ - def __init__(self, name, filename): self.co_name = name self.co_filename = filename @@ -341,7 +470,6 @@ """ Class for mock frame objects in JS client-side traceback wrappers. """ - def __init__(self, func, fname, ln): self.f_back = None self.f_locals = {} @@ -356,7 +484,6 @@ Class for mock traceback objects representing client-side JavaScript tracebacks. """ - def __init__(self, frame, ln): self.tb_frame = frame self.tb_lineno = ln @@ -386,6 +513,8 @@ frames.insert(0, (func, fname, ln)) return frames + + def buildTraceback(frames, modules): """ Build a chain of mock traceback objects from a serialized Error (or other @@ -405,6 +534,7 @@ return first + def getJSFailure(exc, modules): """ Convert a serialized client-side exception to a Failure. @@ -742,7 +872,7 @@ return jsDeps.getModuleForClass(self.jsClass) - def _getRequiredModules(self): + def _getRequiredModules(self, memo): """ Return a list of two-tuples containing module names and URLs at which those modules are accessible. All of these modules must be loaded into @@ -752,12 +882,61 @@ return [ (dep.name, self.page.getJSModuleURL(dep.name)) for dep - in self._getModuleForClass().allDependencies() + in self._getModuleForClass().allDependencies(memo) if self.page._shouldInclude(dep.name)] -class LivePage(rend.Page, _HasJSClass): +def jsModuleDeclaration(name): + """ + Generate Javascript for a module declaration. + """ + var = '' + if '.' not in name: + var = 'var ' + return '%s%s = {"__name__": "%s"};' % (var, name, name) + + + +class _HasCSSModule(object): + """ + C{cssModule}-handling code common to L{LivePage}, L{LiveElement} and + L{LiveFragment}. + + @ivar cssModule: A CSS module name. + @type cssModule: C{unicode} or C{NoneType} + """ + def _getRequiredCSSModules(self, memo): + """ + Return a list of CSS module URLs. + + @rtype: C{list} of L{url.URL} + """ + if self.cssModule is None: + return [] + module = self.page.cssModules.getModuleForName(self.cssModule) + return [ + self.page.getCSSModuleURL(dep.name) + for dep in module.allDependencies(memo) + if self.page._shouldIncludeCSSModule(dep.name)] + + + def getStylesheetStan(self, modules): + """ + Get some stan which will include the given modules. + + @type modules: C{list} or L{url.URL} + + @rtype: Stan + """ + return [ + tags.link( + rel='stylesheet', type='text/css', href=url) + for url in modules] + + + +class LivePage(rend.Page, _HasJSClass, _HasCSSModule): """ A resource which can receive messages from and send messages to the client after the initial page load has completed and which can send messages. @@ -769,9 +948,17 @@ @ivar unsupportedBrowserLoader: A document loader which will be used to generate the content shown to unsupported browsers. - """ + @type _cssDepsMemo: C{dict} + @ivar _cssDepsMemo: A cache for CSS module dependencies; by default, this + will only be shared within a single page instance. + + @type _jsDepsMemo: C{dict} + @ivar _jsDepsMemo: A cache for JS module dependencies; by default, this + will only be shared within a single page instance. + """ jsClass = u'Nevow.Athena.PageWidget' + cssModule = None factory = LivePageFactory() _rendered = False @@ -805,7 +992,7 @@ requiredBrowserVersions = { browsers.GECKO: (20051111,), browsers.INTERNET_EXPLORER: (6, 0), - browsers.WEBKIT: (maxint,), + browsers.WEBKIT: (523,), browsers.OPERA: (9,)} unsupportedBrowserLoader = loaders.stan( @@ -815,7 +1002,8 @@ def __init__(self, iface=None, rootObject=None, jsModules=None, - jsModuleRoot=None, transportRoot=None, *a, **kw): + jsModuleRoot=None, transportRoot=None, cssModules=None, + cssModuleRoot=None, *a, **kw): super(LivePage, self).__init__(*a, **kw) self.iface = iface @@ -827,9 +1015,16 @@ if transportRoot is None: transportRoot = url.here self.transportRoot = transportRoot + self.cssModuleRoot = cssModuleRoot + if cssModules is None: + cssModules = _theCSSRegistry + self.cssModules = cssModules self.liveFragmentChildren = [] self._includedModules = [] + self._includedCSSModules = [] self._disconnectNotifications = [] + self._jsDepsMemo = {} + self._cssDepsMemo = {} def _shouldInclude(self, moduleName): @@ -839,6 +1034,20 @@ return False + def _shouldIncludeCSSModule(self, moduleName): + """ + Figure out whether the named CSS module has already been included. + + @type moduleName: C{unicode} + + @rtype: C{bool} + """ + if moduleName not in self._includedCSSModules: + self._includedCSSModules.append(moduleName) + return True + return False + + # Child lookup may be dependent on the application state # represented by a LivePage. In this case, it is preferable to # dispatch child lookup on the same LivePage instance as performed @@ -877,6 +1086,8 @@ if self.jsModuleRoot is None: self.jsModuleRoot = location.child(self.clientID).child('jsmodule') + if self.cssModuleRoot is None: + self.cssModuleRoot = location.child(self.clientID).child('cssmodule') self._requestIDCounter = itertools.count().next @@ -959,9 +1170,9 @@ programmatically reconnect without re-rendering and re-loading the entire page. - @see L{LivePage.renderUnsupported} + @see: L{LivePage.renderUnsupported} - @see L{Page.renderHTTP} + @see: L{Page.renderHTTP} @param ctx: a L{WovenContext} with L{IRequest} remembered. @@ -1043,11 +1254,20 @@ return self.jsModuleRoot.child(moduleName) + def getCSSModuleURL(self, moduleName): + """ + Return a URL rooted a L{cssModuleRoot} from which the CSS module named + C{moduleName} can be fetched. + + @type moduleName: C{unicode} + + @rtype: C{str} + """ + return self.cssModuleRoot.child(moduleName) + + def getImportStan(self, moduleName): - var = '' - if '.' not in moduleName: - var = 'var ' - moduleDef = '%s%s = {};' % (var, moduleName) + moduleDef = jsModuleDeclaration(moduleName); return [tags.script(type='text/javascript')[tags.raw(moduleDef)], tags.script(type='text/javascript', src=self.getJSModuleURL(moduleName))] @@ -1057,11 +1277,13 @@ [self._bootstrapCall(method, args) for method, args in self._bootstraps(ctx)]) return ctx.tag[ + self.getStylesheetStan(self._getRequiredCSSModules(self._cssDepsMemo)), + # Hit jsDeps.getModuleForName to force it to load some plugins :/ # This really needs to be redesigned. [self.getImportStan(jsDeps.getModuleForName(name).name) for (name, url) - in self._getRequiredModules()], + in self._getRequiredModules(self._jsDepsMemo)], tags.script(type='text/javascript', id=BOOTSTRAP_NODE_ID, payload=bootstrapString)[ @@ -1099,7 +1321,14 @@ def child_jsmodule(self, ctx): - return JSModules(self.jsModules.mapping) + return MappingResource(self.jsModules.mapping) + + + def child_cssmodule(self, ctx): + """ + Return a L{MappingResource} wrapped around L{cssModules}. + """ + return MappingResource(self.cssModules.mapping) _transportResource = None @@ -1257,8 +1486,9 @@ return root -class _LiveMixin(_HasJSClass): +class _LiveMixin(_HasJSClass, _HasCSSModule): jsClass = u'Nevow.Athena.Widget' + cssModule = None preprocessors = [rewriteEventHandlerNodes, rewriteAthenaIds] @@ -1304,14 +1534,18 @@ return () - def rend(self, context, data): + def _prepare(self, tag): + """ + Check for clearly incorrect settings of C{self.jsClass} and + C{self.page}, add this object to the page and fill the I{athena:id} + slot with this object's Athena identifier. + """ assert isinstance(self.jsClass, unicode), "jsClass must be a unicode string" if self.page is None: raise OrphanedFragment(self) self._athenaID = self.page.addLocalObject(self) - context.fillSlots('athena:id', self._athenaID) - return super(_LiveMixin, self).rend(context, data) + tag.fillSlots('athena:id', str(self._athenaID)) def setFragmentParent(self, fragmentParent): @@ -1341,6 +1575,20 @@ fragmentParent.liveFragmentChildren.append(self) + def _flatten(self, what): + """ + Synchronously flatten C{what} and return the result as a C{str}. + """ + # Nested import because in a significant stroke of misfortune, + # nevow.testutil already depends on nevow.athena. It makes more sense + # for the dependency to go from nevow.athena to nevow.testutil. + # Perhaps a sane way to fix this would be to move FakeRequest to a + # different module from whence nevow.athena and nevow.testutil could + # import it. -exarkun + from nevow.testutil import FakeRequest + return "".join(_flat.flatten(FakeRequest(), what, False, False)) + + def _structured(self): """ Retrieve an opaque object which may be usable to construct the @@ -1352,6 +1600,7 @@ children = [] requiredModules = [] + requiredCSSModules = [] # Using the context here is terrible but basically necessary given the # /current/ architecture of Athena and flattening. A better @@ -1359,14 +1608,17 @@ # this. markup = context.call( {'children': children, - 'requiredModules': requiredModules}, - flat.flatten, tags.div(xmlns="http://www.w3.org/1999/xhtml")[self]).decode('utf-8') + 'requiredModules': requiredModules, + 'requiredCSSModules': requiredCSSModules}, + self._flatten, tags.div(xmlns="http://www.w3.org/1999/xhtml")[self]).decode('utf-8') del children[0] self._structuredCache = { u'requiredModules': [(name, flat.flatten(url).decode('utf-8')) for (name, url) in requiredModules], + u'requiredCSSModules': [flat.flatten(url).decode('utf-8') + for url in requiredCSSModules], u'class': self.jsClass, u'id': self._athenaID, u'initArguments': tuple(self.getInitialArguments()), @@ -1380,7 +1632,8 @@ Render framework-level boilerplate for making sure the Widget for this Element is created and added to the page properly. """ - requiredModules = self._getRequiredModules() + requiredModules = self._getRequiredModules(self.page._jsDepsMemo) + requiredCSSModules = self._getRequiredCSSModules(self.page._cssDepsMemo) # Add required attributes to the top widget node tag(**{'xmlns:athena': ATHENA_XMLNS_URI, @@ -1394,9 +1647,12 @@ u'id': self._athenaID, u'initArguments': self.getInitialArguments()}) context.get('requiredModules').extend(requiredModules) + context.get('requiredCSSModules').extend(requiredCSSModules) return tag return ( + self.getStylesheetStan(requiredCSSModules), + # Import stuff [self.getImportStan(name) for (name, url) in requiredModules], @@ -1490,88 +1746,30 @@ class LiveFragment(_LiveMixin, rend.Fragment): """ - Base-class for fragments of a LivePage. When being rendered, a - LiveFragment has a special ID attribute added to its top-level - tag. This attribute is used to dispatch calls from the client - onto the correct object (this one). - - A LiveFragment must use the `liveFragment' renderer somewhere in - its document template. The node given this renderer will be the - node used to construct a Widget instance in the browser (where it - will be saved as the `node' property on the widget object). + This class is deprecated because it relies on context objects + U{which are being removed from Nevow}. - JavaScript handlers for elements inside this node can use - C{Nevow.Athena.Widget.get} to retrieve the widget associated with - this LiveFragment. For example: - -
    - - Methods of the JavaScript widget class can also be bound as event - handlers using the handler tag type in the Athena namespace: - - - - - - This will invoke the C{doFoo} method of the widget which contains the - form node. - - Because this mechanism sets up error handling and otherwise reduces the - required boilerplate for handling events, it is preferred and - recommended over directly including JavaScript in the event handler - attribute of a node. - - The C{jsClass} attribute of a LiveFragment instance determines the - JavaScript class used to construct its corresponding Widget. This - appears as the 'athena:class' attribute. - - JavaScript modules may import other JavaScript modules by using a - special comment which Athena recognizes: - - // import Module.Name - - Different imports must be placed on different lines. No other - comment style is supported for these directives. Only one space - character must appear between the string 'import' and the name of - the module to be imported. No trailing whitespace or - non-whitespace is allowed. There must be exactly one space - between '//' and 'import'. There must be no preceeding whitespace - on the line. - - C{Nevow.Athena.Widget.callRemote} can be given permission to invoke methods - on L{LiveFragment} instances by passing the functions which implement those - methods to L{nevow.athena.expose} in this way:: - - class SomeFragment(LiveFragment): - def someMethod(self, ...): - ... - expose(someMethod) - - Only methods exposed in this way will be accessible. - - L{LiveFragment.callRemote} can be used to invoke any method of the widget - on the client. - - Elements with id attributes will be rewritten so that the id is unique to - that particular instance. The client-side C{Nevow.Athena.Widget.nodeById} - API is provided to locate these later on. For example: - -
    - - and then: - - var node = self.nodyById('foo'); - - On most platforms, this API will be much faster than similar techniques - using C{Nevow.Athena.Widget.nodeByAttribute} etc. + @see: L{LiveElement} """ def __init__(self, *a, **kw): super(LiveFragment, self).__init__(*a, **kw) warnings.warn("[v0.10] LiveFragment has been superceded by LiveElement.", - category=PendingDeprecationWarning, + category=DeprecationWarning, stacklevel=2) + def rend(self, context, data): + """ + Hook into the rendering process in order to check preconditions and + make sure the document will actually be renderable by satisfying + certain Athena requirements. + """ + context = rend.Fragment.rend(self, context, data) + self._prepare(context.tag) + return context + + + class LiveElement(_LiveMixin, Element): """ @@ -1586,31 +1784,31 @@ JavaScript handlers for elements inside this node can use C{Nevow.Athena.Widget.get} to retrieve the widget associated with this - LiveElement. For example: + LiveElement. For example::
    - Methods of the JavaScript widget class can also be bound as event - handlers using the handler tag type in the Athena namespace: + Methods of the JavaScript widget class can also be bound as event handlers + using the handler tag type in the Athena namespace:: - This will invoke the C{doFoo} method of the widget which contains the - form node. + This will invoke the C{doFoo} method of the widget which contains the form + node. Because this mechanism sets up error handling and otherwise reduces the - required boilerplate for handling events, it is preferred and - recommended over directly including JavaScript in the event handler - attribute of a node. + required boilerplate for handling events, it is preferred and recommended + over directly including JavaScript in the event handler attribute of a + node. The C{jsClass} attribute of a LiveElement instance determines the JavaScript class used to construct its corresponding Widget. This appears as the 'athena:class' attribute. JavaScript modules may import other JavaScript modules by using a special - comment which Athena recognizes: + comment which Athena recognizes:: // import Module.Name @@ -1635,19 +1833,42 @@ L{LiveElement.callRemote} can be used to invoke any method of the widget on the client. - Elements with id attributes will be rewritten so that the id is unique to - that particular instance. The client-side C{Nevow.Athena.Widget.nodeById} - API is provided to locate these later on. For example: + XML elements with id attributes will be rewritten so that the id is unique + to that particular instance. The client-side + C{Nevow.Athena.Widget.nodeById} API is provided to locate these later + on. For example::
    - and then: + and then:: var node = self.nodyById('foo'); On most platforms, this API will be much faster than similar techniques using C{Nevow.Athena.Widget.nodeByAttribute} etc. + + Similarly to how Javascript classes are specified, L{LiveElement} + instances may also identify a CSS module which provides appropriate styles + with the C{cssModule} attribute (a unicode string naming a module within a + L{inevow.ICSSPackage}). + + The referenced CSS modules are treated as regular CSS, with the exception + of support for the same:: + + // import CSSModule.Name + + syntax as is provided for Javascript modules. """ + def render(self, request): + """ + Hook into the rendering process in order to check preconditions and + make sure the document will actually be renderable by satisfying + certain Athena requirements. + """ + document = tags.invisible[Element.render(self, request)] + self._prepare(document) + return document + class IntrospectionFragment(LiveFragment): @@ -1674,8 +1895,12 @@ 'LivePageError', 'OrphanedFragment', 'ConnectFailed', 'ConnectionLost' # JS support - 'JSModules', 'JSModule', 'JSPackage', 'AutoJSPackage', 'allJavascriptPackages', - 'JSDependencies', 'JSException', 'JSCode', 'JSFrame', 'JSTraceback', + 'MappingResource', 'JSModule', 'JSPackage', 'AutoJSPackage', + 'allJavascriptPackages', 'JSDependencies', 'JSException', 'JSCode', + 'JSFrame', 'JSTraceback', + + # CSS support + 'CSSRegistry', 'CSSModule', # Core objects 'LivePage', 'LiveFragment', 'LiveElement', 'IntrospectionFragment', diff -Nru nevow-0.9.31/nevow/compression.py nevow-0.10.0/nevow/compression.py --- nevow-0.9.31/nevow/compression.py 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/nevow/compression.py 2008-04-09 10:39:20.000000000 +0100 @@ -0,0 +1,217 @@ +# -*- test-case-name: nevow.test.test_gzip -*- +""" +Implementation of on-the-fly content compression for HTTP resources. +""" +from gzip import GzipFile + +from zope.interface import implements + +from twisted.internet.defer import maybeDeferred, Deferred +from twisted.internet.interfaces import IConsumer + +from nevow.inevow import IRequest, IResource +from nevow.appserver import errorMarker +from nevow.rend import NotFound + + + +def parseAcceptEncoding(value): + """ + Parse the value of an Accept-Encoding: request header. + + A qvalue of 0 indicates that the content coding is unacceptable; any + non-zero value indicates the coding is acceptable, but the acceptable + coding with the highest qvalue is preferred. + + @returns: A dict of content-coding: qvalue. + @rtype: C{dict} + """ + encodings = {} + if value.strip(): + for pair in value.split(','): + pair = pair.strip() + if ';' in pair: + params = pair.split(';') + encoding = params[0] + params = dict(param.split('=') for param in params[1:]) + priority = float(params.get('q', 1.0)) + else: + encoding = pair + priority = 1.0 + encodings[encoding] = priority + + if 'identity' not in encodings and '*' not in encodings: + encodings['identity'] = 0.0001 + + return encodings + + + +class _ProxyDescriptor(object): + """ + Forwarding proxy for attributes. + """ + def __init__(self, name): + self.name = name + + + def __get__(self, oself, type=None): + """ + Get the underlying attribute. + """ + if oself is None: + return self + return getattr(oself.underlying, self.name) + + + def __set__(self, oself, value): + """ + Set the underlying attribute. + """ + setattr(oself.underlying, self.name, value) + + + def __delete__(self, oself): + """ + Delete the underlying attribute. + """ + delattr(oself.underlying, self.name) + + + +def _makeBase(): + """ + Make a base class with proxies for attributes on the underlying request. + """ + d = {} + for iface in [IRequest, IConsumer]: + for attrName in iface.names(all=True): + d[attrName] = _ProxyDescriptor(attrName) + return type('_CompressionRequestWrapperBase', (object,), d) + +class CompressingRequestWrapper(_makeBase()): + """ + A request wrapper with support for transport encoding compression. + + @ivar underlying: the request being wrapped. + @type underlying: L{IRequest} + @ivar encoding: the IANA-assigned name of the encoding. + @type encoding: C{str} + @ivar compressLevel: the level of gzip compression to apply. + @type compressLevel: C{int} + """ + implements(IRequest) + + encoding = 'gzip' + compressLevel = 6 + + + def __init__(self, underlying): + self.underlying = underlying + self.setHeader('content-encoding', self.encoding) + self._gzipFile = None + + # See setHeader docstring for more commentary. + self.underlying.headers.pop('content-length', None) + + + def setHeader(self, name, value): + """ + Discard the Content-Length header. + + When compression encoding is in use, the Content-Length header must + indicate the length of the compressed content; since we are doing the + compression on the fly, we don't actually know what the length is after + compression, so we discard this header. If this is an HTTP/1.1 request, + chunked transfer encoding should be used, softening the impact of + losing this header. + """ + if name.lower() == 'content-length': + return + else: + return self.underlying.setHeader(name, value) + + + def write(self, data): + """ + Pass data through to the gzip layer. + """ + if self._gzipFile is None: + self._gzipFile = GzipFile(fileobj=self.underlying, mode='wb', compresslevel=self.compressLevel) + self._gzipFile.write(data) + + + def finishRequest(self, success): + """ + Finish off gzip stream. + """ + if self._gzipFile is None: + self.write('') + self._gzipFile.close() + self.underlying.finishRequest(success) + + + +class CompressingResourceWrapper(object): + """ + A resource wrapper with support for transport encoding compression. + + @ivar underlying: the resource being wrapped. + @type underlying: L{IResource} + """ + implements(IResource) + + def __init__(self, underlying): + self.underlying = underlying + + + def canCompress(self, req): + """ + Check whether the client has negotiated a content encoding we support. + """ + value = req.getHeader('accept-encoding') + if value is not None: + encodings = parseAcceptEncoding(value) + return encodings.get('gzip', 0.0) > 0.0 + return False + + + # IResource + def renderHTTP(self, ctx): + """ + Render the underlying resource with a wrapped request. + """ + req = IRequest(ctx) + if not self.canCompress(req): + return self.underlying.renderHTTP(ctx) + + req = CompressingRequestWrapper(req) + ctx.remember(req, IRequest) + + def _cbDoneRendering(html): + if isinstance(html, str): + req.write(html) + req.finishRequest(True) + return errorMarker + return html + + return maybeDeferred(self.underlying.renderHTTP, ctx).addCallback(_cbDoneRendering) + + + def locateChild(self, ctx, segments): + """ + Retrieve wrapped child resources via the underlying resource. + """ + def _cbWrapChild(result): + if result in [NotFound, errorMarker]: + return result + + if isinstance(result, tuple): + res, segments = result + if isinstance(res, Deferred): + return res.addCallback(lambda res: _cbWrapChild((res, segments))) + return type(self)(IResource(res)), segments + + raise ValueError('Broken resource; locateChild returned %r' % (result,)) + + return maybeDeferred(self.underlying.locateChild, ctx, segments).addCallback(_cbWrapChild) diff -Nru nevow-0.9.31/nevow/context.py nevow-0.10.0/nevow/context.py --- nevow-0.9.31/nevow/context.py 2006-10-06 20:49:11.000000000 +0100 +++ nevow-0.10.0/nevow/context.py 2008-04-24 16:32:59.000000000 +0100 @@ -34,10 +34,6 @@ precompile = property(lambda self: False) - def with(self, tag): - warnings.warn("use WovenContext(parent, tag) instead", DeprecationWarning, stacklevel=2) - return WovenContext(self, tag) - def arg(self, get, default=None): """Placeholder until I can find Jerub's implementation of this diff -Nru nevow-0.9.31/nevow/css/Nevow/TagLibrary/TabbedPane.css nevow-0.10.0/nevow/css/Nevow/TagLibrary/TabbedPane.css --- nevow-0.9.31/nevow/css/Nevow/TagLibrary/TabbedPane.css 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/nevow/css/Nevow/TagLibrary/TabbedPane.css 2008-07-09 11:42:06.000000000 +0100 @@ -0,0 +1,40 @@ +.nevow-tabbedpane-tabs { + display: inline; + list-style: none; + padding: 0; + margin: 0; + margin-left: 0.1em; +} + +.nevow-tabbedpane-tab, +.nevow-tabbedpane-selected-tab { + display: inline; + padding: 0 2em; + border: 1px solid #666; + background: #eee; + cursor: pointer; + margin-right: 0.25em; +} + +.nevow-tabbedpane-tab:hover { + background: #f5f5f5; +} + +.nevow-tabbedpane-selected-tab { + border-bottom: 1px solid #fff; + background: #fff; +} + +.nevow-tabbedpane-pane { + clear: both; + position: absolute; + top: -10000px; + left: -10000px; +} + +.nevow-tabbedpane-selected-pane { + position: static; + border: 1px solid #666; + margin-bottom: 1em; + padding: 1em; +} diff -Nru nevow-0.9.31/nevow/flat/flatsax.py nevow-0.10.0/nevow/flat/flatsax.py --- nevow-0.9.31/nevow/flat/flatsax.py 2006-07-17 18:06:46.000000000 +0100 +++ nevow-0.10.0/nevow/flat/flatsax.py 2009-05-21 15:58:17.000000000 +0100 @@ -1,4 +1,4 @@ -# Copyright (c) 2004 Divmod. +# Copyright (c) 2004-2009 Divmod. # See LICENSE for details. from xml.sax import make_parser, handler @@ -53,12 +53,17 @@ 'pattern', 'key', ] - def __init__(self, ignoreDocType, ignoreComment): + def __init__(self, ignoreDocType, ignoreComment, sourceFilename): self.ignoreDocType = ignoreDocType self.ignoreComment = ignoreComment + self.sourceFilename = sourceFilename self.prefixMap = nscontext() self.inCDATA = False + + def setDocumentLocator(self, locator): + self.locator = locator + def resolveEntity(self, publicId, systemId): ## This doesn't seem to get called, which is good. raise Exception("resolveEntity should not be called. We don't use external DTDs.") @@ -98,12 +103,26 @@ def startElementNS(self, ns_and_name, qname, attrs): + filename = self.sourceFilename + lineNumber = self.locator.getLineNumber() + columnNumber = self.locator.getColumnNumber() + ns, name = ns_and_name if ns == nevow.namespace: if name == 'invisible': name = '' elif name == 'slot': - el = slot(attrs[(None,'name')]) + try: + # Try to get the default value for the slot + default = attrs[(None, 'default')] + except KeyError: + # If there wasn't one, then use None to indicate no + # default. + default = None + el = slot( + attrs[(None, 'name')], default=default, + filename=filename, lineNumber=lineNumber, + columnNumber=columnNumber) self.stack.append(el) self.current.append(el) self.current = el.children @@ -140,7 +159,8 @@ if 'name' not in no_ns_attrs: # TODO: same here raise AssertionError( ' requires a name attribute' ) - el = Tag('', specials=specials) + el = Tag('', specials=specials, filename=filename, + lineNumber=lineNumber, columnNumber=columnNumber) self.stack[-1].attributes[no_ns_attrs['name']] = el self.stack.append(el) self.current = el.children @@ -157,7 +177,10 @@ prefix = self.prefixMap[ns] if prefix is not None: name = '%s:%s' % (self.prefixMap[ns],name) - el = Tag(name, attributes=dict(no_ns_attrs), specials=specials) + el = Tag( + name, attributes=dict(no_ns_attrs), specials=specials, + filename=filename, lineNumber=lineNumber, + columnNumber=columnNumber) self.stack.append(el) self.current.append(el) self.current = el.children @@ -215,7 +238,7 @@ parser.setFeature(handler.feature_external_ges, 0) parser.setFeature(handler.feature_external_pes, 0) - s = ToStan(ignoreDocType, ignoreComment) + s = ToStan(ignoreDocType, ignoreComment, getattr(fl, "name", None)) parser.setContentHandler(s) parser.setEntityResolver(s) parser.setProperty(handler.property_lexical_handler, s) diff -Nru nevow-0.9.31/nevow/flat/flatstan.py nevow-0.10.0/nevow/flat/flatstan.py --- nevow-0.9.31/nevow/flat/flatstan.py 2007-10-26 16:56:13.000000000 +0100 +++ nevow-0.10.0/nevow/flat/flatstan.py 2008-05-21 18:30:29.000000000 +0100 @@ -285,7 +285,10 @@ context.isAttrib, context.inURL, context.inJS, - context.inJSSingleQuoteString) + context.inJSSingleQuoteString, + original.filename, + original.lineNumber, + original.columnNumber) else: return serialize(data, context) try: diff -Nru nevow-0.9.31/nevow/_flat.py nevow-0.10.0/nevow/_flat.py --- nevow-0.9.31/nevow/_flat.py 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/nevow/_flat.py 2009-03-13 19:03:07.000000000 +0000 @@ -0,0 +1,529 @@ +# -*- test-case-name: nevow.test.test_newflat -*- +# Copyright (c) 2008 Divmod. +# See LICENSE for details. + +""" +Context-free flattener/serializer for rendering Python objects, possibly +complex or arbitrarily nested, as strings. + +Immediate future plans: + + - Deprecate IRenderer and getFlattener cases in _flatten + - Write a precompiler which is more friendly towards _flatten + - Write a pretty "render stack" formatter for the information in + FlattenerError._roots + +""" + +from sys import exc_info +from types import GeneratorType +from traceback import extract_tb, format_list + +from twisted.internet.defer import Deferred + +from nevow.inevow import IRenderable, IRenderer, IRendererFactory, IData +from nevow.inevow import IRequest +from nevow.url import URL +from nevow.stan import _PrecompiledSlot, Unset, Proto, Tag, Entity, slot, xml +from nevow.stan import directive +from nevow.context import WovenContext +from nevow.flat.flatstan import allowSingleton +from nevow.flat import flattenFactory +from nevow.flat.ten import getFlattener +from nevow.tags import raw + + +class FlattenerError(Exception): + """ + An error occurred while flattening an object. + + @ivar _roots: A list of the objects on the flattener's stack at the time + the unflattenable object was encountered. The first element is least + deeply nested object and the last element is the most deeply nested. + """ + def __init__(self, exception, roots, traceback): + self._exception = exception + self._roots = roots + self._traceback = traceback + Exception.__init__(self, exception, roots, traceback) + + + def _formatRoot(self, obj): + """ + Convert an object from C{self._roots} to a string suitable for + inclusion in a render-traceback (like a normal Python traceback, but + can include "frame" source locations which are not in Python source + files). + + @param obj: Any object which can be a render step I{root}. + Typically, L{Tag}s, strings, and other simple Python types. + + @return: A string representation of C{obj}. + @rtype: L{str} + """ + if isinstance(obj, (str, unicode)): + # It's somewhat unlikely that there will ever be a str in the roots + # list. However, something like a MemoryError during a str.replace + # call (eg, replacing " with ") could possibly cause this. + # Likewise, UTF-8 encoding a unicode string to a byte string might + # fail like this. + if len(obj) > 40: + if isinstance(obj, str): + prefix = 1 + else: + prefix = 2 + return repr(obj[:20])[:-1] + '<...>' + repr(obj[-20:])[prefix:] + else: + return repr(obj) + elif isinstance(obj, Tag): + if obj.filename is None: + return 'Tag <' + obj.tagName + '>' + else: + return "File \"%s\", line %d, column %d, in \"%s\"" % ( + obj.filename, obj.lineNumber, + obj.columnNumber, obj.tagName) + else: + return repr(obj) + + + def __repr__(self): + if self._roots: + roots = ' ' + '\n '.join([ + self._formatRoot(r) for r in self._roots]) + '\n' + else: + roots = '' + if self._traceback: + traceback = '\n'.join([ + line + for entry in format_list(self._traceback) + for line in entry.splitlines()]) + '\n' + else: + traceback = '' + return ( + 'Exception while flattening:\n' + + roots + traceback + + self._exception.__class__.__name__ + ': ' + + str(self._exception) + '\n') + + + def __str__(self): + return repr(self) + + + +class UnfilledSlot(Exception): + """ + During flattening, a slot with no associated data was encountered. + """ + def __str__(self): + return 'UnfilledSlot(%r)' % self.args + + + +class UnsupportedType(Exception): + """ + During flattening, an object of a type which cannot be flattened was + encountered. + """ + def __str__(self): + return "UnsupportedType(%r)" % self.args + + + +def escapedData(data, inAttribute, inXML): + """ + Escape a string for inclusion in a document. + + @type data: C{str} + @param data: The string to escape. + + @type inAttribute: C{bool} + @param inAttribute: A flag which, if set, indicates that the string should + be quoted for use as the value of an XML tag value. + + @type inXML: C{bool} + @param inXML: A flag which, if set, indicates that the string should be + quoted for use as an XML text node or as the value of an XML tag value. + + @rtype: C{str} + @return: The quoted form of C{data}. + """ + if inXML or inAttribute: + data = data.replace('&', '&' + ).replace('<', '<' + ).replace('>', '>') + if inAttribute: + data = data.replace('"', '"') + return data + + + +def _ctxForRequest(request, slotData, renderFactory, inAttribute): + """ + Create a L{WovenContext} which can be used to by the + backwards-compatibility support of L{IRenderer} and L{getFlattener} to + continue rendering a response for the given request. + """ + ctx = WovenContext() + ctx.isAttrib = inAttribute + ctx.remember(None, IData) # Even though IData(ctx) can never return None, + # remembering None here is somehow very important + # for preventing a TypeError from happening when + # ctx.locate(IData) is invoked, since it is + # sometimes invoked from a codepath other than + # __conform__. -exarkun + ctx.remember(request, IRequest) + for slotGroup in slotData: + if slotGroup is not None: + for k, v in slotGroup.items(): + ctx.fillSlots(k, v) + if renderFactory is not None: + ctx.remember(_OldRendererFactory(renderFactory), IRendererFactory) + return ctx + + + +def _getSlotValue(name, slotData): + """ + Find the value of the named slot in the given stack of slot data. + """ + for slotFrame in slotData[::-1]: + if slotFrame is not None and name in slotFrame: + return slotFrame[name] + else: + raise UnfilledSlot(name) + + + +def _flatten(request, root, slotData, renderFactory, inAttribute, inXML): + """ + Make C{root} slightly more flat by yielding all or part of it as strings or + generators. + + @param request: A request object which will be passed to + L{IRenderable.render}. + + @param root: An object to be made flatter. This may be of type C{unicode}, + C{str}, L{raw}, L{Proto}, L{xml}, L{slot}, L{_PrecompiledSlot}, L{Tag}, + L{URL}, L{tuple}, L{list}, L{GeneratorType}, L{Entity}, L{Deferred}, or + it may be an object which is adaptable to L{IRenderable}. Deprecated + backwards-compatibility support is also present for objects adaptable + to L{IRenderer} or for which a flattener has been registered via + L{registerFlattener}. + + @param slotData: A C{list} of C{dict} mapping C{str} slot names to data + with which those slots will be replaced. + + @param inAttribute: A flag which, if set, indicates that C{str} and + C{unicode} instances encountered must be quoted as for XML tag + attribute values. + + @param inXML: A flag which, if set, indicates that C{str} and C{unicode} + instances encountered must be quoted as for XML text node data. + + @return: An iterator which yields C{str}, L{Deferred}, and more iterators + of the same type. + """ + if isinstance(root, unicode): + root = root.encode('utf-8') + elif isinstance(root, WovenContext): + # WovenContext is supported via the getFlattener case, but that is a + # very slow case. Checking here is an optimization. It also lets us + # avoid the deprecation warning which would be emitted whenever a + # precompiled document was flattened, since those contain WovenContexts + # for tags with render directives. -exarkun + inAttribute = root.isAttrib + inXML = True + root = root.tag + + if isinstance(root, raw): + root = str(root) + if inAttribute: + root = root.replace('"', '"') + yield root + elif isinstance(root, Proto): + root = str(root) + if root: + if root in allowSingleton: + yield '<' + root + ' />' + else: + yield '<' + root + '>' + elif isinstance(root, str): + yield escapedData(root, inAttribute, inXML) + elif isinstance(root, slot): + slotValue = _getSlotValue(root.name, slotData) + yield _flatten(request, slotValue, slotData, renderFactory, + inAttribute, inXML) + elif isinstance(root, _PrecompiledSlot): + slotValue = _getSlotValue(root.name, slotData) + yield _flatten(request, slotValue, slotData, renderFactory, + root.isAttrib, inXML) + elif isinstance(root, Tag): + if root.pattern is Unset or root.pattern is None: + slotData.append(root.slotData) + if root.render is Unset: + if not root.tagName: + for element in _flatten(request, root.children, + slotData, renderFactory, + False, True): + yield element + else: + yield '<' + if isinstance(root.tagName, unicode): + tagName = root.tagName.encode('ascii') + else: + tagName = str(root.tagName) + yield tagName + for k, v in root.attributes.iteritems(): + if isinstance(k, unicode): + k = k.encode('ascii') + yield " " + k + "=\"" + for element in _flatten(request, v, slotData, + renderFactory, True, True): + yield element + yield "\"" + if root.children or tagName not in allowSingleton: + yield '>' + for element in _flatten(request, root.children, + slotData, renderFactory, + False, True): + yield element + yield '' + else: + yield ' />' + else: + if isinstance(root.render, directive): + rendererName = root.render.name + else: + rendererName = root.render + root = root.clone(False) + del root._specials['render'] + result = renderFactory.renderer(rendererName)(request, root) + yield _flatten(request, result, slotData, renderFactory, None, + inXML) + slotData.pop() + elif isinstance(root, URL): + yield escapedData(str(root), inAttribute, inXML) + elif isinstance(root, (tuple, list, GeneratorType)): + for element in root: + yield _flatten(request, element, slotData, renderFactory, + inAttribute, inXML) + elif isinstance(root, Entity): + yield '&#' + yield root.num + yield ';' + elif isinstance(root, xml): + if isinstance(root.content, unicode): + yield root.content.encode('utf-8') + else: + yield root.content + elif isinstance(root, Deferred): + yield root.addCallback( + lambda result: (result, _flatten(request, result, slotData, + renderFactory, inAttribute, + inXML))) + else: + renderable = IRenderable(root, None) + if renderable is not None: + # [] for the slotData parameter of this call to _flatten means + # slots returned by this renderable's render method won't be filled + # with data which has so far accumulated in the slotData stack. + # This seems like a reasonable thing to me, since a renderable is a + # piece of Python code. It should be isolated from this other + # stuff, which is primarily data. -exarkun + yield _flatten(request, renderable.render(request), [], renderable, + inAttribute, inXML) + else: + renderer = IRenderer(root, None) + if renderer is not None: + ctx = _ctxForRequest(request, slotData, None, inAttribute) + results = [] + synchronous = [] + flattened = flattenFactory(renderer, ctx, results.append, + lambda ign: None) + def cbFlattened(result): + synchronous.append(None) + return (result, (str(s) for s in results)) + flattened.addCallback(cbFlattened) + if synchronous: + yield ''.join(map(str, results)) + else: + yield flattened + else: + flattener = getFlattener(root) + if flattener is not None: + ctx = _ctxForRequest(request, slotData, renderFactory, + inAttribute) + yield _flatten(request, flattener(root, ctx), slotData, + renderFactory, False, False) + else: + raise UnsupportedType(root) + + + +class _OldRendererFactory(object): + """ + Adapter from L{IRenderable} to L{IRenderFactory}, used to provide support + for using the old flattener on new kinds of view objects. + """ + def __init__(self, newRendererFactory): + self.newRendererFactory = newRendererFactory + + + def renderer(self, context, name): + f = self.newRendererFactory.renderer(name) + def render(ctx, data): + return f( + IRequest(ctx, None), + ctx.tag, + ) + return render + + + +def flatten(request, root, inAttribute, inXML): + """ + Make C{root} into an iterable of C{str} and L{Deferred}. + + @param request: A request object which will be passed to + L{IRenderable.render}. + + @param root: An object to be made flatter. This may be of type C{unicode}, + C{str}, L{Proto}, L{slot}, L{Tag}, L{URL}, L{tuple}, L{list}, + L{Entity}, L{Deferred}, or it may be an object which is adaptable to + L{IRenderable}. + + @type inAttribute: C{bool} + @param inAttribute: A flag which, if set, indicates that the string should + be quoted for use as the value of an XML tag value. + + @type inXML: C{bool} + @param inXML: A flag which, if set, indicates that the string should be + quoted for use as an XML text node or as the value of an XML tag value. + + @return: An iterator which yields objects of type C{str} and L{Deferred}. + A L{Deferred} is only yielded when one is encountered in the process of + flattening C{root}. The returned iterator must not be iterated again + until the L{Deferred} is called back. + """ + stack = [_flatten(request, root, [], None, inAttribute, inXML)] + while stack: + try: + # In Python 2.5, after an exception, a generator's gi_frame is + # None. + frame = stack[-1].gi_frame + element = stack[-1].next() + except StopIteration: + stack.pop() + except Exception, e: + stack.pop() + roots = [] + for generator in stack: + roots.append(generator.gi_frame.f_locals['root']) + roots.append(frame.f_locals['root']) + raise FlattenerError(e, roots, extract_tb(exc_info()[2])) + else: + if type(element) is str: + yield element + elif isinstance(element, Deferred): + def cbx((original, toFlatten)): + stack.append(toFlatten) + return original + yield element.addCallback(cbx) + else: + stack.append(element) + + + +def _flattensome(state, write, schedule, result): + """ + Take strings from an iterator and pass them to a writer function. + + @param state: An iterator of C{str} and L{Deferred}. C{str} instances will + be passed to C{write}. L{Deferred} instances will be waited on before + resuming iteration of C{state}. + + @param write: A callable which will be invoked with each C{str} + produced by iterating C{state}. + + @param schedule: A callable which will arrange for a function to be called + with some positional arguments I{later}. This is used to avoid + unbounded call stack depth due to already-fired L{Deferred}s produced + by C{state}. + + @param result: A L{Deferred} which will be called back when C{state} has + been completely flattened into C{write} or which will be errbacked if + an unexpected exception occurs. + + @return: C{None} + """ + while True: + try: + element = state.next() + except StopIteration: + result.callback(None) + except: + result.errback() + else: + if type(element) is str: + write(element) + continue + else: + def cby(original): + schedule(_flattensome, state, write, schedule, result) + return original + element.addCallbacks(cby, result.errback) + break + + + +def _schedule(f, *a): + """ + Scheduler for use with L{_flattensome} which uses L{IReactorTime.callLater} + to schedule calls. This works around the fact that L{Deferred} can exceed + the stack limit in certain cases. + """ + # XXX SUCKY + from twisted.internet import reactor + reactor.callLater(0, f, *a) + + + +def deferflatten(request, root, inAttribute, inXML, write): + """ + Incrementally write out a string representation of C{root} using C{write}. + + In order to create a string representation, C{root} will be decomposed into + simpler objects which will themselves be decomposed and so on until strings + or objects which can easily be converted to strings are encountered. + + @param request: A request object which will be passed to the C{render} + method of any L{IRenderable} provider which is encountered. + + @param root: An object to be made flatter. This may be of type C{unicode}, + C{str}, L{raw}, L{Proto}, L{xml}, L{slot}, L{_PrecompiledSlot}, L{Tag}, + L{URL}, L{tuple}, L{list}, L{GeneratorType}, L{Entity}, L{Deferred}, or + it may be an object which is adaptable to L{IRenderable}. Deprecated + backwards-compatibility support is also present for objects adaptable + to L{IRenderer} or for which a flattener has been registered via + L{registerFlattener}. + + @type inAttribute: C{bool} + @param inAttribute: A flag which, if set, indicates that the string should + be quoted for use as the value of an XML tag value. + + @type inXML: C{bool} + @param inXML: A flag which, if set, indicates that the string should be + quoted for use as an XML text node or as the value of an XML tag value. + + @param write: A callable which will be invoked with each C{str} + produced by flattening C{root}. + + @return: A L{Deferred} which will be called back when C{root} has + been completely flattened into C{write} or which will be errbacked if + an unexpected exception occurs. + """ + result = Deferred() + state = flatten(request, root, inAttribute, inXML) + _flattensome(state, write, _schedule, result) + return result diff -Nru nevow-0.9.31/nevow/guard.py nevow-0.10.0/nevow/guard.py --- nevow-0.9.31/nevow/guard.py 2008-02-01 21:15:23.000000000 +0000 +++ nevow-0.10.0/nevow/guard.py 2009-07-06 14:06:26.000000000 +0100 @@ -225,20 +225,17 @@ The following class attributes can be modified on an instance of the class. - @ivar secureCookies: Whether to use secure (TLS only) cookies or not. - True (default): make cookies secure when session is initiated - in a secure (TLS) connection. - - False: cookies do not get the secure attribute. - - @ivar: persistentCookies: Whether to use persistent (saved to disk) cookies or not. - True: make cookies persistent, so they are valid for the - length of the sessionLifetime even if the browser window - is closed. - - False (default): cookies do not get saved to disk, and thus last - only as long as the session does. If the browser is - closed before the session timeout, both the session + @ivar secureCookies: Whether to use secure (TLS only) cookies or not. If + C{True} (the default), make cookies secure when session is initiated + in a secure (TLS) connection. If C{False}, cookies will not be given + the secure attribute. + + @ivar persistentCookies: Whether to use persistent (saved to disk) + cookies or not. If C{True}, make cookies persistent, so they are + valid for the length of the C{sessionLifetime} even if the browser + window is closed. If C{False} (the default), cookies do not get + saved to disk, and thus last only as long as the session does. If + the browser is closed before the session timeout, both the session and the cookie go away. """ implements(inevow.IResource) @@ -481,7 +478,8 @@ self.incorrectLoginError, ctx, segments, 'Anonymous access not allowed.') def explicitLogout(self, session): - """Hook to be overridden if you care about user-requested logout. + """ + Hook to be overridden if you care about user-requested logout. Note: there is no return value from this method; it is purely a way to provide customized behavior that distinguishes between session-expiry @@ -492,11 +490,11 @@ automated logout. (c.f. Quotient's persistent sessions.) If you want the user to see a customized logout page, just generate a - logout link that looks like + logout link that looks like:: http://your-site.example.com/__logout__/my/custom/logout/stuff - and the user will see + and the user will see:: http://your-site.example.com/my/custom/logout/stuff diff -Nru nevow-0.9.31/nevow/inevow.py nevow-0.10.0/nevow/inevow.py --- nevow-0.9.31/nevow/inevow.py 2008-01-31 19:06:58.000000000 +0000 +++ nevow-0.10.0/nevow/inevow.py 2009-07-10 21:54:22.000000000 +0100 @@ -1,5 +1,5 @@ # -*- test-case-name: nevow -*- -# Copyright (c) 2004-2008 Divmod. +# Copyright (c) 2004-2009 Divmod. # See LICENSE for details. """ @@ -88,6 +88,34 @@ """ +class IRenderable(Interface): + def renderer(name): + """ + Return the render function associated with the given name. + + @type name: C{str} + @param name: The value of a render directive encountered in the + document returned by a call to L{IRenderable.render}. + + @return: A two-argument callable which will be invoked with the request + being responded to and the tag object on which the render directive + was encountered. + """ + + + def render(request): + """ + Get the document for this L{IRenderable}. + + @type request: L{IRequest} provider or L{NoneType} + @param request: The request in response to which this method is being + invoked. + + @return: An object which can be flattened. + """ + + + class IRendererFactory(Interface): """A renderer factory is capable of taking a renderer directive (a string) and returning a callable which when called, will render a portion of DOM. @@ -213,20 +241,24 @@ @return: a list of the interfaces that were removed. """ + + class ISession(IComponentized): - """A web session + """ + A web session You can locate a Session object to represent a unique web session using ISession(ctx). This default session implementation uses cookies to store a session identifier in the user's browser. - uid: Session uid - TODO: Need better docs; what's a session and why and how do you use it """ + uid = Attribute("The unique identifier for this session.") + def setLifetime(lifetime): - """Set the approximate lifetime of this session, in seconds. + """ + Set the approximate lifetime of this session, in seconds. This is highly imprecise, but it allows you to set some general parameters about when this session will expire. A callback will be @@ -237,17 +269,25 @@ attribute in class guard.SessionWrapper """ + def notifyOnExpire(callback): - """Call this callback when the session expires or logs out. + """ + Call this callback when the session expires or logs out. """ + def expire(): - """Expire/logout of the session. """ + Expire/logout of the session. + """ + def touch(): - """Refresh the session """ + Refresh the session + """ + + class IGuardSession(ISession): """ A web session base interface @@ -272,8 +312,19 @@ @type args: A mapping of strings (the argument names) to lists of values. i.e., ?foo=bar&foo=baz&quux=spam results in {'foo': ['bar', 'baz'], 'quux': ['spam']}. - @ivar received_headers: All received headers + @ivar received_headers: All received headers. """ + method = Attribute("The HTTP method that was used.") + uri = Attribute("The full URI that was requested (includes arguments).") + path = Attribute("The path only (arguments not included).") + prepath = Attribute("Path segments that have already been handled.") + postpath = Attribute("Path segments still to be handled.") + args = Attribute("All of the arguments, including URL and POST arguments.") + received_headers = Attribute("All received headers.") + deferred = Attribute("Fired once request processing is finished.") + client = Attribute("The client that sent this request.") + content = Attribute("File-like object containing the request body.") + # Methods for received request def getHeader(key): """Get a header that was sent from the network. @@ -429,6 +480,17 @@ This method is experimental. """ + + def notifyFinish(success): + """ + Return a deferred that fires when the request is finished. + + The deferred will fire with C{None} if the request finished + successfully, or with the error that caused it to be unsuccessful. + """ + + + class ISerializable(Interface): """DEPRECATED. Use nevow.flat.registerFlattener instead of registering an ISerializable adapter. @@ -489,26 +551,15 @@ actually rendered. """ - def load(ctx=None, preprocessors=()): + def load(ctx=None, preprocessors=(), precompile=None): """ Load a template and return a stan document tree. @param preprocessors: An iterable of one-argument callables which will - be given the stan document tree to transform before it is compiled. + be given the stan document tree to transform before it is compiled. """ -class ISession(Interface): - """A web session - - You can locate a Session object to represent a unique web session using - ctx.locate(ISession). This default session implementation uses cookies to - store a session identifier in the user's browser. - - TODO: Need better docs; what's a session and why and how do you use it - """ - - class IRemainingSegments(Interface): """During the URL traversal process, requesting this from the context will result in a tuple of the segments remaining to be processed. @@ -574,19 +625,36 @@ """Write a log entry for this request.""" -class IJavascriptPackage(Interface): + +class IFilesystemPackage(Interface): + """ + Represents information about the filesystem layout of a set of modules. """ - Represents information about the filesystem layout of a set of - JavaScript modules. + mapping = Attribute(""" + A C{dict} mapping C{unicode} to C{str}. The keys in this dictionary are + CSS or Javascript module names which can be imported by + L{nevow.athena.LivePage}. The values give locations in the filesystem + where the implementation of each module can be found. + """) - @ivar mapping: A C{dict} mapping C{str} to C{str}. The keys in - this dictionary are JavaScript module names which can be imported by - JavaScript files server by C{nevow.athena.LivePage}. The values give - locations in the filesystem where the implementation of each module can - be found. + + +class IJavascriptPackage(IFilesystemPackage): + """ + Represents information about the filesystem layout of a set of JavaScript + modules. """ + +class ICSSPackage(IFilesystemPackage): + """ + Represents information about the filesystem layout of a set of CSS + modules. + """ + + + class IAthenaTransportable(Interface): """ An object which can be sent by Athena from the Python server to the diff -Nru nevow-0.9.31/nevow/__init__.py nevow-0.10.0/nevow/__init__.py --- nevow-0.9.31/nevow/__init__.py 2006-10-06 20:49:11.000000000 +0100 +++ nevow-0.10.0/nevow/__init__.py 2008-05-20 16:44:03.000000000 +0100 @@ -207,16 +207,6 @@ nevow.i18n.flattenL10n nevow.i18n.PlaceHolder """ -flatteners_stackless = """ -nevow.flat.flatstan.FunctionSerializer stackless.function -nevow.flat.flatstan.MethodSerializer stackless.instancemethod -""" -try: - import stackless - flatteners += flatteners_stackless -except ImportError: - pass - flatteners_2_4 = """ nevow.flat.flatstan.StringCastSerializer decimal.Decimal """ diff -Nru nevow-0.9.31/nevow/js/Divmod/Base.js nevow-0.10.0/nevow/js/Divmod/Base.js --- nevow-0.9.31/nevow/js/Divmod/Base.js 2007-08-23 13:05:31.000000000 +0100 +++ nevow-0.10.0/nevow/js/Divmod/Base.js 2008-12-31 18:44:01.000000000 +0000 @@ -233,14 +233,16 @@ }; Divmod.Base.addLoadEvent = function(func) { - /*** + /** + * This function is deprecated; use + * Divmod.Runtime.theRuntime.addLoadEvent() instead. + */ - This will stack load functions on top of each other. - Each function added will be called after onload in the - order that they were added. - - ***/ - Divmod.Base.addToCallStack(window, "onload", func, true); + // Even though we don't import Divmod.Runtime, it's almost guaranteed to be + // imported anyway. We can't actually import it, because that would create + // circular imports which Athena can't handle, and this function is now + // deprecated anyway. + Divmod.Runtime.theRuntime.addLoadEvent(func); }; Divmod.Base.jsonRegistry = Divmod.Base.AdapterRegistry(); diff -Nru nevow-0.9.31/nevow/js/Divmod/Defer.js nevow-0.10.0/nevow/js/Divmod/Defer.js --- nevow-0.9.31/nevow/js/Divmod/Defer.js 2007-01-11 19:35:03.000000000 +0000 +++ nevow-0.10.0/nevow/js/Divmod/Defer.js 2008-06-10 11:21:57.000000000 +0100 @@ -336,7 +336,9 @@ consumeErrors /* = false */) { self.resultList = new Array(deferredList.length); Divmod.Defer.DeferredList.upcall(self, '__init__'); - if (deferredList.length == 0 && !fireOnOneErrback) { + // don't callback in the fireOnOneCallback case because the result + // type is different. + if (deferredList.length == 0 && !fireOnOneCallback) { self.callback(self.resultList); } diff -Nru nevow-0.9.31/nevow/js/Divmod/__init__.js nevow-0.10.0/nevow/js/Divmod/__init__.js --- nevow-0.9.31/nevow/js/Divmod/__init__.js 2007-10-30 20:51:47.000000000 +0000 +++ nevow-0.10.0/nevow/js/Divmod/__init__.js 2009-01-21 22:58:03.000000000 +0000 @@ -130,7 +130,24 @@ Divmod.Class = function() {}; -Divmod.Class.subclass = function(/* optional */ className) { +/** + * Create a new subclass. + * + * Passing a module object for C{classNameOrModule} and C{subclassName} will + * result in the subclass being added to the global variables, allowing for a + * more concise method of defining a subclass. + * + * @type classNameOrModule: C{String} or a module object + * @param classNameOrModule: Name of the new subclass or the module object + * C{subclassName} should be created in + * + * @type subclassName: C{String} or C{undefined} + * @param subclassName: Name of the new subclass if C{classNameOrModule} is a + * module object + * + * @rtype: C{Divmod.Class} + */ +Divmod.Class.subclass = function(classNameOrModule, /* optional */ subclassName) { Divmod.__classDebugCounter__ += 1; /* @@ -199,11 +216,6 @@ subClass.subclass = Divmod.Class.subclass; /* - * Make the subclass identifiable somehow. - */ - subClass.__name__ = className; - - /* * Copy class methods and attributes, so that you can do * polymorphism on class methods (useful for things like * Nevow.Athena.Widget.get in widgets.js). @@ -266,6 +278,13 @@ || subClass == superClass); }; + if (subclassName !== undefined) { + className = classNameOrModule.__name__ + '.' + subclassName; + classNameOrModule[subclassName] = subClass; + } else { + className = classNameOrModule; + } + var classIdentifier; if(className === undefined) { classIdentifier = '#' + Divmod.__classDebugCounter__; @@ -273,6 +292,11 @@ classIdentifier = className; } + /* + * Make the subclass identifiable somehow. + */ + subClass.__name__ = className; + subClass.toString = function() { return ''; }; diff -Nru nevow-0.9.31/nevow/js/Divmod/MockBrowser.js nevow-0.10.0/nevow/js/Divmod/MockBrowser.js --- nevow-0.9.31/nevow/js/Divmod/MockBrowser.js 2007-12-20 12:22:02.000000000 +0000 +++ nevow-0.10.0/nevow/js/Divmod/MockBrowser.js 2008-02-26 07:18:22.000000000 +0000 @@ -39,7 +39,8 @@ }, /** - * Internal mock DOM notification that the parent of this node has changed. + * Internal mock DOM notification that the parent of this node has + * changed. */ function _setParent(self, newParent) { var justAdded = (self.parentNode === undefined); @@ -50,11 +51,42 @@ }, /** - * Internal mock DOM notification that the node was inserted into the document. + * Internal mock DOM notification that the node was inserted into the + * document. */ function _insertedIntoDocument(self) { }, + /* + * Return an array of all L{Element} instances with the given tag name + * which live below the given elements. + * + * @type tagName: C{String} + * @param tagName: Tag name to look for. + * + * @type parents: C{Array} of L{Element} + * @param parents: Sequence of parent nodes below which to search. + * + * @rtype: C{Array} of L{Element}. + */ + function _getElementsByTagName(self, tagName, parents) { + tagName = tagName.toUpperCase(); + var elements = []; + var node; + for(var i = 0; i < parents.length; i++) { + node = parents[i]; + if(node.nodeType !== self.ELEMENT_NODE) { + continue; + } + if(node.tagName === tagName || tagName === '*') { + elements.push(node); + } + elements = elements.concat( + self._getElementsByTagName(tagName, node.childNodes)); + } + return elements; + }, + function __init__(self) { self.ELEMENT_NODE = 1; self.TEXT_NODE = 3; @@ -92,6 +124,7 @@ self.body = self.createElement("body"); self.body._containingDocument = self; }, + /** * Create an L{Element} with the given tag name. */ @@ -127,8 +160,15 @@ var aNode = Divmod.MockBrowser.TextNode(text); aNode._setOwnerDocument(self); return aNode; - }); + }, + /** + * Return all L{Element} instances in this document which have the + * specified tag name. + */ + function getElementsByTagName(self, tagName) { + return self._getElementsByTagName(tagName, [self.body]); + }); /** @@ -381,8 +421,22 @@ */ function getAttribute(self, name) { return self._attributes[name]; + }, + + /** + * Return an array of all L{Element} instances with the given tag name + * which live below this element. + * + * @type tagName: C{String} + * @param tagName: Tag name to look for. + * + * @rtype: C{Array} of L{Element}. + */ + function getElementsByTagName(self, tagName) { + return self._getElementsByTagName(tagName, self.childNodes); }); + /* Only install ourselves as a global document if there isn't already a global * document. This should minimise the impact of this module. */ diff -Nru nevow-0.9.31/nevow/js/Divmod/Runtime/__init__.js nevow-0.10.0/nevow/js/Divmod/Runtime/__init__.js --- nevow-0.9.31/nevow/js/Divmod/Runtime/__init__.js 2007-10-25 15:20:30.000000000 +0100 +++ nevow-0.10.0/nevow/js/Divmod/Runtime/__init__.js 2008-12-31 18:44:01.000000000 +0000 @@ -25,6 +25,11 @@ }); + +Divmod.Runtime.ScriptLoadingError = Divmod.Error.subclass('Divmod.Runtime.ScriptLoadingError'); + + + /** * Wrapper around a lower-level scheduled timed call which provides additional * useful operations. @@ -43,6 +48,59 @@ }); +/** + * Move the given node so that it is a child of the given document and then try + * to find its node with the given id. Restore the node to its original parent + * before returning the result. + */ +Divmod.Runtime._getElementByIdWithDocument = function _getElementByIdWithDocument(node, doc, id) { + var currentParent = node.parentNode; + var nextSibling = node.nextSibling; + + // Insert ourselves into the document temporarily. + currentParent.removeChild(node); + doc.documentElement.appendChild(node); + + // Try to find it. + var foundNode = node.ownerDocument.getElementById(id); + + // And now put us back where we used to be. + node.parentNode.removeChild(node); + currentParent.insertBefore(node, nextSibling); + + return foundNode; +}; + +/** + * XML parser for platforms which have the builtin "DOMParser" object. + */ +Divmod.Runtime._XMLParser = Divmod.Class.subclass("Divmod.Runtime._XMLParser"); +Divmod.Runtime._XMLParser.methods( + /** + * Return the cached DOM parser for this runtime. Create one if one does + * not yet exist. + */ + function _getParser(self) { + if (self._cachedParser === undefined) { + self._cachedParser = new DOMParser(); + } + return self._cachedParser; + }, + + function parseXHTMLString(self, s) { + var doc = self._getParser().parseFromString(s, "application/xml"); + var uri = doc.documentElement.namespaceURI; + if (uri != "http://www.w3.org/1999/xhtml") { + throw new Error( + "Unknown namespace (" + uri + + ") used with parseXHTMLString" + + "- only XHTML 1.0 is supported."); + } + return doc; + }); + + + Divmod.Runtime.Platform = Divmod.Class.subclass("Divmod.Runtime.Platform"); Divmod.Runtime.Platform.DOM_DESCEND = 'Divmod.Runtime.Platform.DOM_DESCEND'; @@ -99,13 +157,16 @@ }; }; - +/** + * @type _loadEventDelay: Number + * @ivar: The number of milliseconds by which to delay invocation of events + * registered with C{addLoadEvent}. + */ Divmod.Runtime.Platform.methods( function __init__(self, name) { self.name = name; self.attrNameToMangled = {}; - self._scriptCounter = 0; - self._scriptDeferreds = {}; + self._loadEventDelay = 1; }, /** @@ -358,15 +419,27 @@ return localName; }, -// function setAttribute(self, node, localName, namespaceURI, namespaceIdentifier, value) { -// if (namespaceURI !== undefined && node.setAttributeNS) { -// node.setAttributeNS(namespaceURI, localName, value); -// } else if (namespaceIdentifier !== undefined) { -// node.setAttribute(namespaceIdentifier + ':' + localName, value); -// } else { -// node.setAttribute(self._mangleAttributeName(localName), value); -// } -// }, + /** + * Reliably set the value for a node attribute. + */ + function setAttribute(self, node, localName, value, namespaceURI, namespaceIdentifier) { + if (namespaceURI === undefined && namespaceIdentifier === undefined) { + localName = self._mangleAttributeName(localName); + } + if (node.hasAttributeNS) { + if (node.hasAttributeNS(namespaceURI, localName)) { + return node.setAttributeNS(namespaceURI, localName, value); + } else if (node.hasAttributeNS(namespaceIdentifier, localName)) { + return node.setAttributeNS(namespaceIdentifier, localName, value); + } + } + if (node.setAttribute) { + var a = (namespaceIdentifier === undefined) ? + localName : namespaceIdentifier + ':' + localName; + return node.setAttribute(a, value); + } + return null; + }, /** * This is _the_way_ to get the value of an attribute off of node @@ -559,48 +632,33 @@ self.appendNodeContent(node, innerHTML); }, + function loadScript(self, location) { + // ' + - ''); - - self.appendNodeContent(document.body, script); - - return self._scriptDeferreds[self._scriptCounter]; - }, - - function _scriptLoaded(self, which) { - var script = document.getElementById('__athena_runtime_script_loader_' + which + '__'); - script.parentNode.removeChild(script); - - var loaded = self._scriptDeferreds[which]; - delete self._scriptDeferreds[which]; - loaded.callback(null); + function loadStylesheet(self, location) { + var linkNode = document.createElement('link'); + linkNode.setAttribute('rel', 'stylesheet'); + linkNode.setAttribute('type', 'text/css'); + linkNode.setAttribute('href', location); + var headNode = document.getElementsByTagName('head')[0]; + headNode.appendChild(linkNode); }, /** @@ -629,11 +687,52 @@ throw Divmod.Runtime.NodeNotFound('Node with id ' + id + ' not found'); } return foundNode; + }, + + /** + * Arrange for the given function to be called when the page is fully + * loaded. Due to the behavior of various browsers (particularly with + * respect to their "page loading" aka "throbber" user interface element) + * this callable is not invoked directly in response to the DOM page load + * event, but in a delayed call scheduled from that event. + */ + function addLoadEvent(self, callable) { + var func = function() { setTimeout(callable, self._loadEventDelay); }; + Divmod.Base.addToCallStack(window, "onload", func, true); + }, + + /** + * Add a beforeunload event handler. + * + * @param aWindow: The window object. + * @param handler: The handler. + */ + function addBeforeUnloadHandler(self, aWindow, handler) { + Divmod.Base.addToCallStack(aWindow, 'onbeforeunload', handler); }); +/** + * Spidermonkey runtime for unit testing. + * + * @type loadEvents: Array + * @ivar loadEvents: A list of load events that have been added. + */ Divmod.Runtime.Spidermonkey = Divmod.Runtime.Platform.subclass( 'Divmod.Runtime.Spidermonkey'); +Divmod.Runtime.Spidermonkey.methods( + function __init__(self, name) { + Divmod.Runtime.Spidermonkey.upcall(self, '__init__', name); + self.loadEvents = []; + }, + + /** + * Add the given function to loadEvents. + */ + function addLoadEvent(self, callable) { + self.loadEvents.push(callable); + }); + Divmod.Runtime.Spidermonkey.isThisTheOne = function isSpidermonkeyTheOne() { try { window; @@ -736,7 +835,6 @@ return results; }); - Divmod.Runtime.Firefox = Divmod.Runtime.XPathSupportingPlatform.subclass( 'Divmod.Runtime.Firefox'); @@ -755,6 +853,9 @@ Divmod.Runtime.Firefox.methods( function __init__(self) { Divmod.Runtime.Firefox.upcall(self, '__init__', 'Firefox'); + self._xmlparser = Divmod.Runtime._XMLParser(); + self._scriptCounter = 0; + self._scriptDeferreds = {}; }, function makeHTML(self, element) { @@ -781,23 +882,8 @@ return HTML_ELEMENT; }, - /** - * Return the cached DOM parser for this runtime. Create one if one does - * not yet exist. - */ - function _getParser(self) { - if (self._cachedParser === undefined) { - self._cachedParser = new DOMParser(); - } - return self._cachedParser; - }, - function parseXHTMLString(self, s) { - var doc = self._getParser().parseFromString(s, "application/xml"); - if (doc.documentElement.namespaceURI != "http://www.w3.org/1999/xhtml") { - throw new Error("Unknown namespace used with parseXHTMLString - only XHTML 1.0 is supported."); - } - return doc; + return self._xmlparser.parseXHTMLString(s); }, function appendNodeContent(self, node, innerHTML) { @@ -832,6 +918,147 @@ function makeHTTPRequest(self) { return new XMLHttpRequest(); + }, + + /** + * Load the JavaScript module at the given URL. + * + * The manner in which the JavaScript is evaluated is implementation + * dependent. + * + * @return: A Deferred which fires when the contents of the module have + * been evaluated. + */ + function loadScript(self, location) { + self._scriptCounter += 1; + var scriptID = '__athena_runtime_script_loader_' + self._scriptCounter + '__'; + self._scriptDeferreds[scriptID] = Divmod.Defer.Deferred(); + + var language = '"text/javascript"'; + var singleQuotedID = "'" + scriptID + "'"; + var doubleQuotedID = '"' + scriptID + '"'; + var onload = '"Divmod.Runtime.theRuntime._scriptLoaded(' + singleQuotedID + ')"'; + var onerror = '"Divmod.Runtime.theRuntime._scriptError(' + singleQuotedID + ', arguments[0])"'; + var src = '"' + location + '"'; + var xmlns = '"http://www.w3.org/1999/xhtml"'; + var script = ( + '' + + '' + + ''); + + self.appendNodeContent(document.body, script); + + return self._scriptDeferreds[scriptID]; + }, + + function _scriptLoaded(self, scriptID) { + var script = document.getElementById(scriptID); + script.parentNode.removeChild(script); + + var loaded = self._scriptDeferreds[scriptID]; + delete self._scriptDeferreds[scriptID]; + loaded.callback(null); + }, + + function _scriptError(self, scriptID, error) { + var script = document.getElementById(scriptID); + script.parentNode.removeChild(script); + + var loaded = self._scriptDeferreds[scriptID]; + delete self._scriptDeferreds[scriptID]; + loaded.errback(Divmod.Runtime.ScriptLoadingError(error)); + }); + + +Divmod.Runtime.WebKit = Divmod.Runtime.Platform.subclass( + 'Divmod.Runtime.WebKit'); + +Divmod.Runtime.WebKit.isThisTheOne = function isThisTheOne() { + try { + return navigator.userAgent.indexOf('WebKit') != -1; + } catch (err) { + if (err instanceof ReferenceError) { + return false; + } else { + throw err; + } + } +}; + +Divmod.Runtime.WebKit.methods( + function __init__(self) { + Divmod.Runtime.WebKit.upcall(self, '__init__', 'WebKit'); + self._xmlparser = Divmod.Runtime._XMLParser(); + // WebKit has no equivalent to the stacktrace that FF provides, so this + // JSON adapter will provide a dummy object to make Athena happy when + // it tries to send exceptions from the client to the server + Divmod.Base.registerJSON( + 'Error', + function(obj) { + return arguments[1] instanceof Error; + }, + function(obj) { + var exc = arguments[1]; + return { + 'name': exc.name, + 'message': exc.message, + 'stack': 'No stacktrace available\n' + }; + } + ); + /* + * WebKit fires the onload event long before the page has actually + * loaded. This has two consequences. First, if the Athena transport + * is started when the onload event fires, then WebKit will believe + * that the page never actually completes loading, since there will be + * an open, long-lived XHR. This means its spinner will never stop + * spinning, which will aggravate users. Second, if the Athena + * transport is started before the page fully loads, application-level + * JavaScript may fail, since the page is supposed to have loaded by + * the time that event fires. This delay is a complete and utter hack + * to try to work around these two problems in *most* cases. It will + * certainly fail in some cases. Unless WebKit exposes a real event + * which fires when the page is really loaded, though, I don't see any + * easy way around this. -exarkun + */ + self._loadEventDelay = 200; + }, + + function getElementByIdWithNode(self, node, id) { + var foundNode = node.ownerDocument.getElementById(id); + if (foundNode === null) { + // We didn't find it, maybe we need a workaround. + // Let's insert ourselves into the document temporarily. + foundNode = Divmod.Runtime._getElementByIdWithDocument( + node, document, id); + } + + if (foundNode === null) { + throw Divmod.Runtime.NodeNotFound('Node with id ' + id + ' not found'); + } + + return foundNode; + }, + + function parseXHTMLString(self, s) { + return self._xmlparser.parseXHTMLString(s); + }, + + function appendNodeContent(self, node, innerHTML) { + var doc = self.parseXHTMLString(innerHTML); + node.appendChild(document.importNode(doc.documentElement, true)); + }, + + function makeHTTPRequest(self) { + return new XMLHttpRequest(); }); Divmod.Runtime.InternetExplorer = Divmod.Runtime.Platform.subclass("Divmod.Runtime.InternetExplorer"); @@ -929,20 +1156,6 @@ } }, - function loadScript(self, location) { - // The ''' + xml = '' self.failUnlessEqual(xml, flatten(parseString(xml))) - + def test_comment(self): - xml = '''''' + xml = '' self.failUnlessEqual(xml, flatten(parseString(xml))) def test_commentWhereSpacingMatters(self): - """Explicitly test that spacing in comments is maintained.""" + """ + Explicitly test that spacing in comments is maintained. + """ xml = """ ''' + xml = '' self.failUnlessEqual(xml, flatten(parseString(xml))) def test_xmlAttr(self): - xml = '''''' + xml = '' self.failUnlessEqual(xml, flatten(parseString(xml))) def test_badNamespace(self): - xml = '''xyz''' + xml = 'xyz' self.failUnlessEqual(xml, flatten(parseString(xml))) - test_badNamespace.skip = 'the standard 2.3 sax parser likes all namespaces to be defined so this test fails. it does pass with python-xml' + test_badNamespace.skip = ( + 'the standard 2.3 sax parser likes all namespaces to be defined ' + 'so this test fails. it does pass with python-xml') def test_switchns(self): - xml = '''

    in default namespace

    in foo namespace''' + xml = ( + '' + '

    in default namespace

    ' + '' + 'in foo namespace') self.failUnlessEqual(xml, flatten(parseString(xml))) def test_otherns(self): - xml = '''

    in default namespace

    in another namespace''' + xml = ( + '

    ' + 'in default namespace

    ' + 'in another namespace') self.failUnlessEqual(xml, flatten(parseString(xml))) - + def test_invisiblens(self): - """Test that invisible tags do not get output with a namespace. """ - xml = '

    123

    ' + Test that invisible tags do not get output with a namespace. + """ + xml = ( + '

    ' + '123

    ') self.failUnlessEqual('

    123

    ', flatten(parseString(xml))) - diff -Nru nevow-0.9.31/nevow/test/test_flatstan.py nevow-0.10.0/nevow/test/test_flatstan.py --- nevow-0.9.31/nevow/test/test_flatstan.py 2008-01-04 17:21:55.000000000 +0000 +++ nevow-0.10.0/nevow/test/test_flatstan.py 2008-05-21 18:30:29.000000000 +0100 @@ -142,6 +142,22 @@ precompiled = self.render(tag, precompile=True) self.assertEquals(self.render(precompiled), '

    bar

    ') + + def test_precompiledSlotLocation(self): + """ + The result of precompiling a slot preserves the location information + associated with the slot. + """ + filename = 'foo/bar' + line = 123 + column = 432 + [slot] = self.render( + tags.slot('foo', None, filename, line, column), precompile=True) + self.assertEqual(slot.filename, filename) + self.assertEqual(slot.lineNumber, line) + self.assertEqual(slot.columnNumber, column) + + def makeComplex(self): return tags.html[ tags.body[ diff -Nru nevow-0.9.31/nevow/test/test_guard.py nevow-0.10.0/nevow/test/test_guard.py --- nevow-0.9.31/nevow/test/test_guard.py 2008-02-01 21:15:23.000000000 +0000 +++ nevow-0.10.0/nevow/test/test_guard.py 2008-06-25 23:35:20.000000000 +0100 @@ -270,7 +270,7 @@ def getGuardPath(self): """ - Return a path to the guard. An empty string if guard is a the root, + Return a path to the guard. An empty string if guard is the root, otherwise guaranteed to start with a slash and end with a non-slash. """ if not self.guardPath: @@ -859,58 +859,3 @@ class GuardTest_NotAtRoot_manyLevels(GuardTestSuper, GuardTestFuncs): guardPath = ['foo', 'bar', 'baz'] - -class LeakyPage(SillyPage): - def renderHTTP(self, context): - return "woo" - - -class LeakyRealm: - implements(IRealm) - - def requestAvatar(self, avatarId, mind, *interfaces): - if avatarId == ANONYMOUS: - return inevow.IResource, SillyAnonymous(), lambda: None - else: - return inevow.IResource, LeakyPage(), lambda: None - - -class LeakTest(GuardTestSuper): - def makeRequest(self, sw, p, username): - chan = self.createChannel(sw) - req = chan.makeFakeRequest( - '/__login__?username=%(username)s&password=%(username)s' % {'username': username} - ).followAllRedirects() - self.assertEquals(req.written.getvalue(), "woo") - for i in range(10): - req = chan.makeFakeRequest( - '/foo/bar/baz/bamf/' - ).followAllRedirects() - - def _leaky(self): - p = self.createPortal(LeakyRealm) - sw = self.createSessionWrapper(p) - p.registerChecker( - InMemoryUsernamePasswordDatabaseDontUse( - test0='test0', - test1='test1', - test2='test2', - test3='test3', - test4='test4', - test5='test5', - test6='test6', - test7='test7', - test8='test8', - test9='test9'), - IUsernamePassword) - for x in range(10): - self.makeRequest(sw, p, 'test%s' % x) - - def test_leak(self): - self._leaky() - - ref = [x for x in gc.get_referrers(context.PageContext) if isinstance(x, context.PageContext)] - #print ref - #import pdb; pdb.Pdb().set_trace() - - diff -Nru nevow-0.9.31/nevow/test/test_howtolistings.py nevow-0.10.0/nevow/test/test_howtolistings.py --- nevow-0.9.31/nevow/test/test_howtolistings.py 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/nevow/test/test_howtolistings.py 2009-05-21 16:33:16.000000000 +0100 @@ -0,0 +1,279 @@ + +""" +This module tests the code listings used in the documentation. +""" + +import sys + +from twisted.python.filepath import FilePath +from twisted.trial.unittest import SkipTest, TestCase + +from nevow._widget_plugin import ElementRenderingLivePage +from nevow.testutil import renderLivePage, JavaScriptTestCase +from nevow.athena import jsDeps, expose + +from nevow import plugins + + +class ExampleTestBase(object): + """ + This is a mixin which adds an example to the path, tests it, and then + removes it from the path and unimports the modules which the test loaded. + Test cases which test example code and documentation listings should use + this. + + This is done this way so that examples can live in isolated path entries, + next to the documentation, replete with their own plugin packages and + whatever other metadata they need. Also, example code is a rare instance + of it being valid to have multiple versions of the same code in the + repository at once, rather than relying on version control, because + documentation will often show the progression of a single piece of code as + features are added to it, and we want to test each one. + """ + + examplePath = None + + def setUp(self): + """ + Add our example directory to the path and record which modules are + currently loaded. + """ + self.originalPath = sys.path[:] + self.originalModules = sys.modules.copy() + here = FilePath(__file__).parent().parent().parent() + for childName in self.examplePath: + here = here.child(childName) + if not here.exists(): + raise SkipTest("Examples (%s) not found - cannot test" % (here.path,)) + sys.path.append(here.path) + # This stuff is pretty horrible; we need to turn off JS caching for the + # duration of this test (this flag will be set back to True by the + # plugin-loading itself, so no need to clean it up) because the + # previously-loaded list of plugins is going to be invalid. + jsDeps._loadPlugins = True + # Even more horrible! nevow.plugins.__path__ needs to be recomputed + # each time for the new value of sys.path. + reload(plugins) + + + def tearDown(self): + """ + Remove the example directory from the path and remove all modules loaded by + the test from sys.modules. + """ + sys.modules.clear() + sys.modules.update(self.originalModules) + sys.path[:] = self.originalPath + reload(plugins) + + + +class ExampleJavaScriptTestCase(JavaScriptTestCase): + """ + Since JavaScriptTestCase does not use setUp and tearDown, we can't use + simple inheritance to invoke the functionality in ExampleTestBase; so, + instead, this class composes JavaScriptTestCase with a ExampleTestBase. + """ + def run(self, result): + """ + Wrap L{JavaScriptTestCase.run} to change and restore the plugin environment + on each test run. + """ + base = ExampleTestBase() + base.examplePath = self.examplePath + try: + base.setUp() + except SkipTest, e: + result.startTest(self) + result.addSkip(self, str(e)) + result.stopTest(self) + else: + try: + return JavaScriptTestCase.run(self, result) + finally: + base.tearDown() + + +def chatListing(partname): + """ + Return a list of strings that represents a path from the root of the Nevow + source package which contains a potential PYTHONPATH entry with some + listing code in it, based on which part of the chat tutorial it came from. + """ + return ['doc', 'howto', 'chattutorial', 'part'+partname, 'listings'] + + + +class Part00Tests(ExampleJavaScriptTestCase): + """ + Refer to JavaScript unit tests for section 01 of the tutorial. + """ + + examplePath = chatListing('00') + + def test_part00(self): + """ + Test method pointing to part 00 of the tutorial. + """ + return 'Nevow.Test.TestHowtoListing00' + + + +class Echo00(ExampleTestBase, TestCase): + """ + These tests will make sure that the very basic 'echobox' portion of the + tutorial works. + """ + examplePath = chatListing('00') + + def test_renderEcho(self): + """ + Rendering the echo example element should produce a very simple text area. + """ + from echothing.echobox import EchoElement + TEXT = 'Echo Element' + eb = EchoElement() + erlp = ElementRenderingLivePage(eb) + def checkContent(result): + # The "liveElement" renderer inserts this, let's look for it to + # make sure it rendered live: + self.assertIn('id="athena:'+str(eb._athenaID)+'"', result) + self.assertIn('athena:class="EchoThing.EchoWidget"', result) + self.assertIn(TEXT, result) + return renderLivePage(erlp).addCallback(checkContent) + + + def test_echoTellsClient(self): + """ + When 'hear' is called on a ChatterElement, it should inform its client of what + was said and by whom. + """ + from echothing.echobox import EchoElement + eb = EchoElement() + echoed = [] + eb.callRemote = lambda method, message: echoed.append((method, message)) + eb.say(u'HELLO... Hello... hello...') + self.assertEquals(echoed, [('addText', u'HELLO... Hello... hello...')]) + + + +class Part01Tests(ExampleJavaScriptTestCase): + """ + Refer to JavaScript unit tests for section 01 of the tutorial. + """ + + examplePath = chatListing('01') + + def test_part01(self): + """ + Test method pointing to part 01 of the tutorial. + """ + return 'Nevow.Test.TestHowtoListing01' + + + +class RenderAndChat01(ExampleTestBase, TestCase): + """ + These tests make sure that the listing for part 01 of the chatterbox + tutorial will import, render, and allow users to chat with each other. + """ + + examplePath = chatListing('01') + + def test_basicRendering(self): + """ + Rendering the example element should produce a chat area. + """ + from chatthing.chatterbox import ChatterElement, ChatRoom + PROMPT = 'Choose your username: ' + cb = ChatterElement(ChatRoom()) + erlp = ElementRenderingLivePage(cb) + def checkContent(result): + # The "liveElement" renderer inserts this, let's look for it to + # make sure it rendered live: + self.assertIn('id="athena:'+str(cb._athenaID)+'"', result) + self.assertIn('athena:class="ChatThing.ChatterWidget"', result) + self.assertIn(PROMPT, result) + return renderLivePage(erlp).addCallback(checkContent) + + + def test_username(self): + """ + When a user sets their username in the chat room, it should set an + attribute on their ChatterElement instance. + """ + from chatthing.chatterbox import ChatterElement, ChatRoom + cb = ChatterElement(ChatRoom()) + setUsername = expose.get(cb, 'setUsername') + setUsername(u'jethro') + self.assertIdentical(u'jethro', cb.username) + + + def test_loginThenWall(self): + """ + When a user logs in the 'wall' method on their ChatterElement gets called, + notifiying everyone in the room that they have entered. + """ + from chatthing.chatterbox import ChatRoom + jethroHeard = [] + cletusHeard = [] + cr = ChatRoom() + user1 = cr.makeChatter() + user1.wall = lambda msg: jethroHeard.append(msg) + user1.setUsername(u'jethro') + user2 = cr.makeChatter() + user2.wall = lambda msg: cletusHeard.append(msg) + user2.setUsername(u'cletus') + self.assertEquals(jethroHeard, + [u' * user jethro has joined the room', + u' * user cletus has joined the room']) + self.assertEquals(cletusHeard, [u' * user cletus has joined the room']) + + + def test_sayThenHear(self): + """ + When a user calls the 'say' method on their ChatterElement, everyone (including + them) should 'hear' it. + """ + from chatthing.chatterbox import ChatRoom + cr = ChatRoom() + user1 = cr.makeChatter() + user1.wall = lambda msg: msg + user1.setUsername(u'jethro') + user2 = cr.makeChatter() + user2.wall = lambda msg: msg + user2.setUsername(u'cletus') + jethroHeard = [] + cletusHeard = [] + user1.hear = lambda who, what: jethroHeard.append((who,what)) + user2.hear = lambda who, what: cletusHeard.append((who,what)) + say = expose.get(user1, 'say') + say(u'Hey, Cletus!') + self.assertEquals(jethroHeard, cletusHeard) + self.assertEquals(cletusHeard, [(u'jethro', u'Hey, Cletus!')]) + + + def test_wallTellsClient(self): + """ + When 'wall' is called on a ChatterElement, it should inform its client of + the message. + """ + from chatthing.chatterbox import ChatRoom + cb = ChatRoom().makeChatter() + heard = [] + cb.callRemote = lambda method, msg: heard.append((method, msg)) + cb.wall(u'Message for everyone...') + self.assertEquals(heard, [('displayMessage', u'Message for everyone...')]) + + def test_hearTellsClient(self): + """ + When 'hear' is called on a ChatterElement, it should inform its client of what + was said and by whom. + """ + from chatthing.chatterbox import ChatRoom + cb = ChatRoom().makeChatter() + heard = [] + cb.callRemote = lambda method, who, what: heard.append((method, who, what)) + cb.hear(u'Hello', u'Chat') + self.assertEquals(heard, [('displayUserMessage', u'Hello', u'Chat')]) + diff -Nru nevow-0.9.31/nevow/test/test_javascript.py nevow-0.10.0/nevow/test/test_javascript.py --- nevow-0.9.31/nevow/test/test_javascript.py 2007-10-06 21:29:47.000000000 +0100 +++ nevow-0.10.0/nevow/test/test_javascript.py 2008-07-07 10:58:45.000000000 +0100 @@ -47,3 +47,7 @@ def test_rdm(self): return 'Nevow.Test.TestMessageDelivery' + + + def test_tabbedPane(self): + return 'Nevow.Test.TestTabbedPane' diff -Nru nevow-0.9.31/nevow/test/test_json.py nevow-0.10.0/nevow/test/test_json.py --- nevow-0.9.31/nevow/test/test_json.py 2007-10-30 20:28:49.000000000 +0000 +++ nevow-0.10.0/nevow/test/test_json.py 2009-07-08 03:51:14.000000000 +0100 @@ -8,7 +8,7 @@ from zope.interface import implements from nevow.inevow import IAthenaTransportable -from nevow import json, rend, page, loaders, tags, athena +from nevow import json, rend, page, loaders, tags, athena, testutil from twisted.trial import unittest @@ -60,6 +60,8 @@ def __init__(self): self.page = self self.liveFragmentChildren = [] + self._jsDepsMemo = {} + self._cssDepsMemo = {} def addLocalObject(self, obj): @@ -171,7 +173,8 @@ livePage = DummyLivePage() liveFragment = cls( docFactory=loaders.stan( - tags.div(render=tags.directive(renderer))['Hello'])) + [tags.div(render=tags.directive(renderer))['Hello'], + tags.div(render=tags.directive('foo'))])) liveFragment.setFragmentParent(livePage) self.assertEqual( json.serialize(liveFragment), @@ -183,7 +186,14 @@ Like L{test_doubleFragmentSerialization} but for L{athena.LiveFragment} instances. """ - return self._doubleLiveSerialization(athena.LiveFragment, 'liveFragment') + class AnyLiveFragment(athena.LiveFragment): + """ + Just some L{LiveFragment} subclass, such as an application might + define. + """ + def render_foo(self, ctx, data): + return ctx.tag + self._doubleLiveSerialization(AnyLiveFragment, 'liveFragment') def test_doubleLiveElementSerialization(self): @@ -191,7 +201,18 @@ Like L{test_doubleFragmentSerialization} but for L{athena.LiveElement} instances. """ - return self._doubleLiveSerialization(athena.LiveElement, 'liveElement') + requests = [] + class AnyLiveElement(athena.LiveElement): + """ + Just some L{LiveElement} subclass, such as an application might + define. + """ + def foo(self, request, tag): + requests.append(request) + return tag + page.renderer(foo) + self._doubleLiveSerialization(AnyLiveElement, 'liveElement') + self.assertTrue(isinstance(requests[0], testutil.FakeRequest)) def test_unsupportedSerialization(self): diff -Nru nevow-0.9.31/nevow/test/test_livepage.py nevow-0.10.0/nevow/test/test_livepage.py --- nevow-0.9.31/nevow/test/test_livepage.py 2006-04-14 19:23:46.000000000 +0100 +++ nevow-0.10.0/nevow/test/test_livepage.py 2008-06-18 19:37:36.000000000 +0100 @@ -1,3 +1,9 @@ +# Copyright (c) 2008 Divmod. +# See LICENSE for details. + +""" +Tests for L{nevow.livepage}. +""" from twisted.trial import unittest @@ -79,7 +85,12 @@ self.assertEquals(self.sendThrough(lambda c, d: livepage.js('hello')), 'hello') self.assertEquals(self.sendThrough(lambda c, d: [1,2, livepage.js('hi'), "it's a string"]), "12hi'it\\'s a string'") - def rend_table(self,ctx,data): return tags.table[ tags.tr[ tags.th["hea'der1"], tags.th["hea'der2"]], tags.tr(id="ro'w1")[ tags.td["va'l1"],tags.td["va'l2"]]] + def rend_table(self,ctx,data): + return tags.table[ + tags.tr[ + tags.th["hea'der1"], tags.th["hea'der2"]], + tags.tr(id="ro'w1")[ + tags.td["va'l1"],tags.td["va'l2"]]] self.assertEquals( self.sendThrough(livepage.append('mynode', rend_table)), diff -Nru nevow-0.9.31/nevow/test/test_loaders.py nevow-0.10.0/nevow/test/test_loaders.py --- nevow-0.9.31/nevow/test/test_loaders.py 2006-11-15 22:50:25.000000000 +0000 +++ nevow-0.10.0/nevow/test/test_loaders.py 2009-05-21 15:58:17.000000000 +0100 @@ -1,4 +1,4 @@ -# Copyright (c) 2004 Divmod. +# Copyright (c) 2004-2009 Divmod. # See LICENSE for details. import os @@ -268,6 +268,25 @@ l2 = loaders.xmlstr(self.nsdoc, pattern='2') self.assertNotEqual( id(l1.load()), id(l2.load()) ) + + def test_xmlSlotDefault(self): + """ + An I{nevow:slot} tag in an XML template may have a I{default} + attribute specifying a value for the slot if it is not otherwise + given one. + """ + slotsdoc = ''' +
    + + +
    + ''' + loader = loaders.xmlstr(slotsdoc) + loaded = loader.load() + self.assertEquals(loaded[1].default, None) + self.assertEquals(loaded[3].default, "3") + + def test_xmlfile(self): temp = self.mktemp() diff -Nru nevow-0.9.31/nevow/test/test_newflat.py nevow-0.10.0/nevow/test/test_newflat.py --- nevow-0.9.31/nevow/test/test_newflat.py 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/nevow/test/test_newflat.py 2009-06-03 21:05:19.000000000 +0100 @@ -0,0 +1,1351 @@ +# Copyright (c) 2008 Divmod. +# See LICENSE for details. + +""" +Tests for L{nevow._flat}. +""" + +import sys, traceback + +from zope.interface import implements + +from twisted.trial.unittest import TestCase +from twisted.internet.defer import Deferred, succeed + +from nevow.inevow import IRequest, IQ, IRenderable, IData +from nevow._flat import FlattenerError, UnsupportedType, UnfilledSlot +from nevow._flat import flatten, deferflatten +from nevow.tags import Proto, Tag, slot, raw, xml +from nevow.tags import invisible, br, div, directive +from nevow.entities import nbsp +from nevow.url import URL +from nevow.rend import Fragment +from nevow.loaders import stan +from nevow.flat import flatten as oldFlatten, precompile as oldPrecompile +from nevow.flat.ten import registerFlattener +from nevow.testutil import FakeRequest +from nevow.context import WovenContext + +# Use the co_filename mechanism (instead of the __file__ mechanism) because +# it is the mechanism traceback formatting uses. The two do not necessarily +# agree with each other. This requires a code object compiled in this file. +# The easiest way to get a code object is with a new function. I'll use a +# lambda to avoid adding anything else to this namespace. The result will +# be a string which agrees with the one the traceback module will put into a +# traceback for frames associated with functions defined in this file. +HERE = (lambda: None).func_code.co_filename + + +class TrivialRenderable(object): + """ + An object which renders to a parameterized value. + + @ivar result: The object which will be returned by the render method. + @ivar requests: A list of all the objects passed to the render method. + """ + implements(IRenderable) + + def __init__(self, result): + self.result = result + self.requests = [] + + + def render(self, request): + """ + Give back the canned response and record the given request. + """ + self.requests.append(request) + return self.result + + + +class RenderRenderable(object): + """ + An object which renders to a parameterized value and has a render method + which records how it is invoked. + + @ivar renders: A list of two-tuples giving the arguments the render method + has been invoked with. + @ivar name: A string giving the name of the render method. + @ivar tag: The value which will be returned from C{render}. + @ivar result: The value which will be returned from the render method. + """ + implements(IRenderable) + + def __init__(self, renders, name, tag, result): + self.renders = renders + self.name = name + self.tag = tag + self.result = result + + + def renderer(self, name): + if name == self.name: + return self.renderMethod + raise ValueError("Invalid renderer name") + + + def renderMethod(self, request, tag): + self.renders.append((self, request)) + return self.result + + + def render(self, request): + return self.tag + + + +class FlattenMixin: + """ + Helper defining an assertion method useful for flattener tests. + """ + def assertStringEqual(self, value, expected): + """ + Assert that the given value is a C{str} instance and that it equals the + expected value. + """ + self.assertTrue(isinstance(value, str)) + self.assertEqual(value, expected) + + + +class FlattenTests(TestCase, FlattenMixin): + """ + Tests for L{nevow._flat.flatten}. + """ + def flatten(self, root, request=None): + """ + Helper to get a string from L{flatten}. + """ + result = [] + # This isn't something shorter because this way is nicer to look at in + # a debugger. + for part in flatten(request, root, False, False): + result.append(part) + return "".join(result) + + + def test_unflattenable(self): + """ + Flattening an object which references an unflattenable object fails + with L{FlattenerError} which gives the flattener's stack at the + point of failure and which has an L{UnsupportedType} exception in + its arguments. + """ + unflattenable = object() + deepest = [unflattenable] + middlest = (deepest,) + outermost = [middlest] + err = self.assertRaises(FlattenerError, self.flatten, outermost) + self.assertEqual( + err._roots, [outermost, middlest, deepest, unflattenable]) + self.assertTrue(isinstance(err.args[0], UnsupportedType)) + + + def test_str(self): + """ + An instance of L{str} is flattened to itself. + """ + self.assertStringEqual(self.flatten('bytes<>&"\0'), 'bytes<>&"\0') + + + def test_raw(self): + """ + An instance of L{raw} is flattened to itself. + """ + self.assertStringEqual( + self.flatten(raw('bytes<>^&"\0')), 'bytes<>^&"\0') + + + def test_attributeRaw(self): + """ + An instance of L{raw} is flattened with " quoting if C{True} is passed + for C{inAttribute}. L{raw} instances are expected to have already had + &, <, and > quoted. L{raw} support is primarily for backwards + compatibility. + """ + self.assertStringEqual( + "".join(flatten(None, raw('"&<>'), True, True)), '"&<>') + + + def test_attributeString(self): + """ + An instance of L{str} is flattened with attribute quoting rules if + C{True} is passed for C{inAttribute}. + """ + self.assertStringEqual( + "".join(flatten(None, '"&<>', True, False)), + ""&<>") + + + def test_textNodeString(self): + """ + An instance of L{str} is flattened with XML quoting rules if C{True} is + passed for C{inXML}. + """ + self.assertStringEqual( + "".join(flatten(None, '"&<>', False, True)), + '"&<>') + + + def test_unicode(self): + """ + An instance of L{unicode} is flattened to the UTF-8 representation of + itself. + """ + self.assertStringEqual(self.flatten(u'bytes<>&"\0'), 'bytes<>&"\0') + unich = u"\N{LATIN CAPITAL LETTER E WITH GRAVE}" + self.assertStringEqual(self.flatten(unich), unich.encode('utf-8')) + + + def test_xml(self): + """ + An L{xml} instance is flattened to the UTF-8 representation of itself. + """ + self.assertStringEqual(self.flatten(xml("foo")), "foo") + unich = u"\N{LATIN CAPITAL LETTER E WITH GRAVE}" + self.assertStringEqual(self.flatten(xml(unich)), unich.encode('utf-8')) + + + def test_entity(self): + """ + An instance of L{Entity} is flattened to the XML representation of an + arbitrary codepoint. + """ + self.assertStringEqual(self.flatten(nbsp), " ") + + + def test_entityChild(self): + """ + An instance of L{Entity} which is a child of a L{Tag} is flattened to + the XML representation of an arbitrary codepoint. + """ + self.assertStringEqual( + self.flatten(div[nbsp]), "
     
    ") + + + def test_entityAttribute(self): + """ + An instance of L{Entity} which is the value of an attribute of a L{Tag} + is flattened to the XML representation of an arbitrary codepoint. + """ + self.assertStringEqual( + self.flatten(div(foo=nbsp)), '
    ') + + + def test_iterable(self): + """ + A C{list}, C{tuple} or C{generator} is flattened to the concatenation + of whatever its elements flatten to, in order. + """ + sequence = ("bytes", "<", ">", "&") + result = "bytes<>&" + self.assertStringEqual(self.flatten(tuple(sequence)), result) + self.assertStringEqual(self.flatten(list(sequence)), result) + def gen(): + for e in sequence: + yield e + self.assertStringEqual(self.flatten(gen()), result) + + + def test_singletonProto(self): + """ + L{Proto} instances corresponding to tags which are allowed to be + self-closing are flattened that way. + """ + self.assertStringEqual(self.flatten(br), "
    ") + + + def test_nonSingletonProto(self): + """ + L{Proto} instances corresponding to tags which are not allowed to be + self-closing are not flattened that way. + """ + self.assertStringEqual(self.flatten(div), "
    ") + + + def test_invisibleProto(self): + """ + L{Proto} instances with an empty C{name} attribute don't have markup + generated for them. + """ + self.assertStringEqual(self.flatten(invisible), "") + self.assertStringEqual(self.flatten(Proto("")), "") + + + def test_emptySingletonTag(self): + """ + L{Tag} instances which are allowed to be self-closing are flattened + that way. + """ + self.assertStringEqual(self.flatten(br()), "
    ") + + + def test_emptyNonSingletonTag(self): + """ + L{Tag} instances which are not allowed to be self-closing are not + flattened that way. + """ + self.assertStringEqual(self.flatten(div()), "
    ") + + + def test_invisibleTag(self): + """ + L{Tag} instances with an empty C{tagName} attribute don't have markup + generated for them, only for their children. + """ + self.assertStringEqual(self.flatten(invisible["foo"]), "foo") + self.assertStringEqual(self.flatten(Tag("")["foo"]), "foo") + + + def test_unicodeTagName(self): + """ + A L{Tag} with a C{tagName} attribute which is C{unicode} instead of + C{str} is flattened to an XML representation. + """ + self.assertStringEqual(self.flatten(Tag(u'div')), "
    ") + self.assertStringEqual(self.flatten(Tag(u'div')['']), "
    ") + + + def test_unicodeAttributeName(self): + """ + A L{Tag} with an attribute name which is C{unicode} instead of C{str} + is flattened to an XML representation. + """ + self.assertStringEqual( + self.flatten(Tag(u'div', {u'foo': 'bar'})), '
    ') + + + def test_stringTagAttributes(self): + """ + C{str} L{Tag} attribute values are flattened by applying XML attribute + value quoting rules. + """ + self.assertStringEqual( + self.flatten(div(foo="bar")), '
    ') + self.assertStringEqual( + self.flatten(div(foo='"><&')), + '
    ') + + + def test_tupleTagAttributes(self): + """ + C{tuple} L{Tag} attribute values are flattened by flattening the tuple + and applying XML attribute value quoting rules to the result. + """ + self.assertStringEqual( + self.flatten(div(foo=('"', ">", "<", "&"))), + '
    ') + + + def test_tagChildren(self): + """ + The children of a L{Tag} are flattened to strings included inside the + XML markup delimiting the tag. + """ + self.assertStringEqual( + self.flatten(div["baz"]), '
    baz
    ') + self.assertStringEqual( + self.flatten(div[["b", "a", "z"]]), '
    baz
    ') + + + def test_nestedTags(self): + """ + The contents of a L{Tag} which is a child of another L{Tag} should be + quoted just once. + """ + self.assertStringEqual( + self.flatten(div[div['&']]), + "
    &
    ") + + + def test_patternTag(self): + """ + A L{Tag} with a I{pattern} special is omitted from the flattened + output. + """ + self.assertStringEqual(self.flatten(div(pattern="foo")), "") + + + def test_onePatternTag(self): + """ + A L{Tag} returned from L{IQ.onePattern} is represented in the flattened + output. + """ + self.assertStringEqual( + self.flatten(IQ(div(pattern="foo")).onePattern("foo")), + "
    ") + + + def test_renderAttribute(self): + """ + A L{Tag} with a I{render} special is replaced with the return value of + the corresponding render method on the L{IRenderable} above the tag. + """ + result = ("foo", " ", "bar") + renders = [] + class RendererRenderable(TrivialRenderable): + def render_foo(self, request, tag): + renders.append((request, tag)) + return result + + def renderer(self, name): + return getattr(self, 'render_' + name) + + request = object() + tag = div(render="foo", bar="baz")["quux"] + renderer = RendererRenderable(tag) + self.assertStringEqual(self.flatten(renderer, request), "".join(result)) + self.assertEqual(len(renders), 1) + self.assertIdentical(renders[0][0], request) + self.assertEqual(renders[0][1].tagName, tag.tagName) + self.assertEqual(renders[0][1].attributes, {"bar": "baz"}) + self.assertEqual(renders[0][1].children, ["quux"]) + + + def test_renderDirectiveAttribute(self): + """ + A L{Tag} with a I{render} special which is a L{directive} is treated + the same way as if the special value were just a string. + """ + result = ("foo", " ", "bar") + renders = [] + class RendererRenderable(TrivialRenderable): + def render_foo(self, request, tag): + renders.append((request, tag)) + return result + + def renderer(self, name): + return getattr(self, 'render_' + name) + + request = object() + tag = div(render=directive("foo"), bar="baz")["quux"] + renderer = RendererRenderable(tag) + self.assertStringEqual(self.flatten(renderer, request), "".join(result)) + self.assertEqual(len(renders), 1) + self.assertIdentical(renders[0][0], request) + self.assertEqual(renders[0][1].tagName, tag.tagName) + self.assertEqual(renders[0][1].attributes, {"bar": "baz"}) + self.assertEqual(renders[0][1].children, ["quux"]) + + + def test_renderAttributeOnRenderableNestedInRenderable(self): + """ + A L{Tag} with a renderer which returns an L{IRenderable} which renders + to a L{Tag} with a I{render} special is replaced with the return value + of the corresponding render method on the nested L{IRenderable}. + """ + result = ("foo", " ", "bar") + request = object() + renders = [] + inner = RenderRenderable(renders, "bar", div(render="bar"), result) + outer = RenderRenderable(renders, "foo", div(render="foo"), inner) + self.assertStringEqual(self.flatten(outer, request), "".join(result)) + self.assertEqual(renders, [(outer, request), (inner, request)]) + + + def test_renderAttributeNestedInList(self): + """ + A L{Tag} with a renderer which is in a list which is returned by + L{IRenderable.render} is replaced with the result of the named renderer + on the L{IRenderable} which returned the list. + """ + result = ("foo", " ", "bar") + renders = [] + renderable = RenderRenderable( + renders, "foo", [div(render="foo")], result) + self.assertStringEqual( + self.flatten(renderable, None), "".join(result)) + + + def test_renderAttributeNestedInTag(self): + """ + A L{Tag} with a renderer which is a child of a L{Tag} which was + returned by L{IRenderable.render} is replaced with the result of the + named renderer on the L{IRenderable} which returned the L{Tag}. + """ + result = "quux" + renders = [] + tag = div[div(render="foo")] + renderable = RenderRenderable(renders, "foo", tag, result) + self.assertStringEqual( + self.flatten(renderable, None), "
    quux
    ") + + + def test_renderAttributeNestedInAttributeValue(self): + """ + A L{Tag} with a renderer which is the value of an attribute of a L{Tag} + which was returned by L{IRenderable.render} is replaced with the result + of the named renderer on the L{IRenderable} which returned the L{Tag}. + """ + result = "quux" + renders = [] + request = object() + tag = div(foo=invisible(render="bar")) + renderable = RenderRenderable(renders, "bar", tag, result) + self.assertStringEqual( + self.flatten(renderable, request), '
    ') + self.assertEqual(renders, [(renderable, request)]) + + + def test_renderAttributeNestedInSlot(self): + """ + A L{Tag} with a renderer which is used as the value of a L{slot} which + was returned by L{IRenderable.render} is replaced with the result of + the named renderer on the L{IRenderable} which returned the L{slot}. + """ + result = "quux" + renders = [] + outer = div[slot("bar")] + inner = div(render="foo") + outer.fillSlots("bar", inner) + renderable = RenderRenderable(renders, "foo", outer, result) + self.assertStringEqual( + self.flatten(renderable, None), "
    quux
    ") + + + def test_renderAttributeNestedInPrecompiledSlot(self): + """ + A L{Tag} with a renderer which is used as the value of a + L{_PrecompiledSlot} which was returned by L{IRenderable.render} is + replaced with the result of the named renderer on the L{IRenderable} + which returned the L{_PrecompiledSlot}. + """ + result = "quux" + renders = [] + request = object() + outer = invisible[stan(div[slot("bar")]).load()] + inner = div(render="foo") + outer.fillSlots("bar", inner) + renderable = RenderRenderable(renders, "foo", outer, result) + self.assertStringEqual( + self.flatten(renderable, request), "
    quux
    ") + self.assertEqual(renders, [(renderable, request)]) + + + def test_renderAttributedNestedInRenderResult(self): + """ + A L{Tag} with a renderer which is returned by a render method is + replaced with the return value of the named renderer on the + L{IRenderable} which had the render method which returned the L{Tag}. + """ + class TwoRenderers(object): + implements(IRenderable) + + def renderer(self, name): + return getattr(self, name) + + def foo(self, request, tag): + return div(render="bar") + + def bar(self, request, tag): + return "baz" + + def render(self, request): + return div(render="foo") + + renderable = TwoRenderers() + self.assertStringEqual(self.flatten(renderable), "baz") + + + def test_slotsNestedInRenderResult(self): + """ + A L{slot} in the return value of a render function is replaced by the + value of that slot as found on the tag which had the render directive. + """ + tag = div(render="foo") + tag.fillSlots("bar", '"&<>') + renderable = RenderRenderable([], "foo", tag, slot("bar")) + self.assertStringEqual(self.flatten(renderable), '"&<>') + + + def test_renderTextDataQuoted(self): + """ + Strings returned by a render method on an L{IRenderable} provider which + is a child of a L{Tag} are XML quoted. + """ + tag = div[RenderRenderable([], "foo", div(render="foo"), '"&<>')] + self.assertStringEqual(self.flatten(tag), '
    "&<>
    ') + + + def test_renderMethodReturnsInputTag(self): + """ + If a render method returns the tag it was passed, the tag is flattened + as though it did not have a render directive. + """ + class IdempotentRenderable(object): + implements(IRenderable) + + def renderer(self, name): + return getattr(self, name) + + def foo(self, request, tag): + return tag + + def render(self, request): + return div(render="foo", bar="baz")["hello, world"] + + renderable = IdempotentRenderable() + self.assertStringEqual( + self.flatten(renderable), '
    hello, world
    ') + + + def test_url(self): + """ + An L{URL} object is flattened to the appropriate representation of + itself, whether it is the child of a tag or the value of a tag + attribute. + """ + link = URL.fromString("http://foo/fu?bar=baz&bar=baz#quux%2f") + self.assertStringEqual( + self.flatten(link), + "http://foo/fu?bar=baz&bar=baz#quux%2F") + self.assertStringEqual( + self.flatten(div[link]), + '
    http://foo/fu?bar=baz&bar=baz#quux%2F
    ') + self.assertStringEqual( + self.flatten(div(foo=link)), + '
    ') + self.assertStringEqual( + self.flatten(div[div(foo=link)]), + '
    ' + '
    ') + + link = URL.fromString("http://foo/fu?%2f=%7f") + self.assertStringEqual( + self.flatten(link), + "http://foo/fu?%2F=%7F") + self.assertStringEqual( + self.flatten(div[link]), + '
    http://foo/fu?%2F=%7F
    ') + self.assertStringEqual( + self.flatten(div(foo=link)), + '
    ') + + + def test_unfilledSlot(self): + """ + Flattening a slot which has no known value results in an + L{FlattenerError} exception which has an L{UnfilledSlot} exception + in its arguments. + """ + exc = self.assertRaises(FlattenerError, self.flatten, slot("foo")) + self.assertTrue(isinstance(exc.args[0], UnfilledSlot)) + + + def test_filledSlotTagChild(self): + """ + Flattening a slot as a child of a L{Tag} which has been given a value + for that slot results in the slot being replaced with the value in the + output. + """ + tag = div[slot("foo")] + tag.fillSlots("foo", "bar") + self.assertStringEqual(self.flatten(tag), "
    bar
    ") + + + def test_filledSlotTagChildEscaping(self): + """ + Flattening a slot as a child of a L{Tag} which has been given a string + value results in that string value being XML escaped in the output. + """ + tag = div[slot("foo")] + tag.fillSlots("foo", '"&<>') + self.assertStringEqual(self.flatten(tag), '
    "&<>
    ') + + + def test_filledSlotNestedTagChild(self): + """ + Flattening a slot as a child of a L{Tag} which is itself a child of a + L{Tag} which has been given a value for that slot results in the slot + being replaced with the value in the output. + """ + tag = div[div[slot("foo")]] + tag.fillSlots("foo", "bar") + self.assertStringEqual(self.flatten(tag), "
    bar
    ") + + + def test_filledSlotTagAttribute(self): + """ + Flattening a slot which is the value of an attribute of a L{Tag} + results in the value of the slot appearing as the attribute value in + the output. + """ + tag = div(foo=slot("bar")) + tag.fillSlots("bar", "baz") + self.assertStringEqual(self.flatten(tag), '
    ') + + + def test_slotFilledWithProto(self): + """ + Filling a slot with a L{Proto} results in the slot being replaced with + the serialized form of the tag in the output. + """ + tag = div[slot("foo")] + tag.fillSlots("foo", br) + self.assertStringEqual(self.flatten(tag), "

    ") + + + def test_unfilledPrecompiledSlot(self): + """ + Flattening a L{_PrecompiledSlot} for which no value has been supplied + results in an L{FlattenerError} exception. + """ + tag = oldPrecompile(div[slot("foo")]) + self.assertRaises(FlattenerError, self.flatten, tag) + + + def test_precompiledSlot(self): + """ + A L{_PrecompiledSlot} is replaced with the value of that slot when + flattened. + """ + tag = invisible[oldPrecompile(div[slot("foo")])] + tag.fillSlots("foo", '"&<>') + self.assertStringEqual(self.flatten(tag), '
    "&<>
    ') + + + def test_precompiledSlotTagAttribute(self): + """ + A L{_PrecompiledSlot} which is the value of an attribute is replaced + with the value of the slot with XML attribute quoting applied. + """ + tag = invisible[oldPrecompile(div(foo=slot("foo")))] + tag.fillSlots("foo", '"&<>') + self.assertStringEqual(self.flatten(tag), '
    ') + + + def test_precompiledSlotFilledWithSlot(self): + """ + A L{_PrecompiledSlot} slot which is filled with another slot is + replaced with the value the other slot is filled with. + """ + tag = invisible[oldPrecompile(div[slot("foo")])] + tag.fillSlots("foo", slot("bar")) + tag.fillSlots("bar", '"&<>') + self.assertStringEqual(self.flatten(tag), '
    "&<>
    ') + + + def test_renderable(self): + """ + Flattening an object which provides L{IRenderable} results in + something based on the result of the object's C{render} method. + """ + request = object() + renderable = TrivialRenderable("bytes") + self.assertStringEqual( + self.flatten(renderable, request), "bytes") + self.assertEqual(renderable.requests, [request]) + + + def test_renderableNestingRenderable(self): + """ + Flattening an L{IRenderable} provider which returns another + L{IRenderable} from its C{render} method results in the result of + flattening the result of the inner L{IRenderable}'s C{render} method + which is called with the request object. + """ + request = object() + inner = TrivialRenderable("bytes") + outer = TrivialRenderable(inner) + self.assertStringEqual(self.flatten(outer, request), "bytes") + self.assertEqual(inner.requests, [request]) + + + def test_listNestingRenderable(self): + """ + Flattening a C{list} which has an object providing L{IRenderable} as a + child results in the result of the L{IRenderable}'s C{render} method + which is called with the request object. + """ + request = object() + renderable = TrivialRenderable("bytes") + self.assertStringEqual(self.flatten([renderable], request), "bytes") + self.assertEqual(renderable.requests, [request]) + + + def test_tagNestingRenderable(self): + """ + Flattening a L{Tag} which has an object providing L{IRenderable} as a + child results in markup for the tag with child data from the + L{IRenderable}'s C{render} which is called with the request object. + """ + request = object() + inner = TrivialRenderable("bytes") + outer = div[inner] + self.assertStringEqual( + self.flatten(outer, request), "
    bytes
    ") + self.assertEqual(inner.requests, [request]) + + + def test_slotNestingRenderable(self): + """ + Flattening a L{slot} which is filled with an object providing + L{IRenderable} results in the result of the L{IRenderable}'s C{render} + method which is called with the request object. + """ + request = object() + inner = TrivialRenderable("bytes") + outer = slot("foo") + tag = div[outer] + tag.fillSlots("foo", inner) + self.assertStringEqual(self.flatten(tag, request), "
    bytes
    ") + self.assertEqual(inner.requests, [request]) + + + def test_slotFromRenderable(self): + """ + An L{IRenderable} provider which returns a C{Tag} inside a C{slot} + from its C{render} method has that slot filled with the slot data + available on the tag. + """ + tag = div[slot("foo")] + tag.fillSlots("foo", "bar") + renderable = TrivialRenderable(tag) + self.assertStringEqual(self.flatten(renderable), "
    bar
    ") + + + def _nestingTest(self, nestedObject, expected): + limit = sys.getrecursionlimit() + sys.setrecursionlimit(100) + try: + self.assertStringEqual(self.flatten(nestedObject), expected) + finally: + sys.setrecursionlimit(limit) + + + def test_deeplyNestedList(self): + """ + Flattening succeeds for an object with a level of list nesting + significantly greater than the Python maximum recursion limit. + """ + obj = ["foo"] + for i in xrange(1000): + obj = [obj] + self._nestingTest(obj, "foo") + + + def test_deeplyNestedSlot(self): + """ + Flattening succeeds for an object with a level of slot nesting + significantly greater than the Python maximum recursion limit. + """ + tag = div()[slot("foo-0")] + for i in xrange(1000): + tag.fillSlots("foo-" + str(i), slot("foo-" + str(i + 1))) + tag.fillSlots("foo-1000", "bar") + self._nestingTest(tag, "
    bar
    ") + + + def test_deeplyNestedTag(self): + """ + Flattening succeeds for a tag with a level of nesting significantly + greater than the Python maximum recursion limit. + """ + n = 1000 + tag = div["foo"] + for i in xrange(n - 1): + tag = div[tag] + self._nestingTest(tag, "
    " * n + "foo" + "
    " * n) + + + def test_deeplyNestedRenderables(self): + """ + Flattening succeeds for an object with a level of L{IRenderable} + nesting significantly greater than the Python maximum recursion limit. + """ + obj = TrivialRenderable("foo") + for i in xrange(1000): + obj = TrivialRenderable(obj) + self._nestingTest(obj, "foo") + + + def test_legacyRenderer(self): + """ + Flattening an L{IRenderer} succeeds with the same result as using the + old flattener. + """ + class Legacy(Fragment): + docFactory = stan(invisible(render=directive('foo'))) + def render_foo(self, ctx, data): + return '"&<>' + fragment = Legacy() + self.assertEqual( + self.flatten(fragment), oldFlatten(fragment)) + self.assertEqual( + self.flatten(div(foo=fragment)), + oldFlatten(div(foo=fragment))) + + + def test_legacySerializable(self): + """ + Flattening an object for which a flattener was registered with + L{registerFlattener} succeeds with the result of the registered + flattener function. + """ + request = FakeRequest() + result = 'bytes"' + serializable = LegacySerializable(result) + self.assertEqual( + self.flatten(div(foo=serializable), request), + '
    ') + [context] = serializable.flattenedWith + self.assertTrue(isinstance(context, WovenContext)) + self.assertFalse(context.precompile) + self.assertTrue(context.isAttrib) + self.assertIdentical(context.locate(IRequest), request) + self.assertIdentical(context.locate(IData), None) + + + def test_legacySerializableReturnsSlot(self): + """ + A slot returned by a flattener registered with L{registerFlattener} is + filled with the value of a slot from "outside" the L{ISerializable}. + """ + request = FakeRequest() + result = slot('foo') + serializable = LegacySerializable(result) + tag = div(foo=serializable) + tag.fillSlots("foo", "bar") + self.assertEqual(self.flatten(tag, request), '
    ') + + + def test_flattenExceptionStack(self): + """ + If an exception is raised by a render method, L{FlattenerError} is + raised with information about the stack between the flattener and the + frame which raised the exception. + """ + def broken(): + raise RuntimeError("foo") + + class BrokenRenderable(object): + implements(IRenderable) + + def render(self, request): + # insert another stack frame before the exception + broken() + + + request = object() + renderable = BrokenRenderable() + exc = self.assertRaises( + FlattenerError, self.flatten, renderable, request) + self.assertEqual( + # There are probably some frames above this, but I don't care what + # they are. + exc._traceback[-2:], + [(HERE, 927, 'render', 'broken()'), + (HERE, 920, 'broken', 'raise RuntimeError("foo")')]) + + + +class FlattenerErrorTests(TestCase): + """ + Tests for L{FlattenerError}. + """ + def test_string(self): + """ + If a L{FlattenerError} is created with a string root, up to around 40 + bytes from that string are included in the string representation of the + exception. + """ + self.assertEqual( + str(FlattenerError(RuntimeError("reason"), ['abc123xyz'], [])), + "Exception while flattening:\n" + " 'abc123xyz'\n" + "RuntimeError: reason\n") + self.assertEqual( + str(FlattenerError( + RuntimeError("reason"), ['0123456789' * 10], [])), + "Exception while flattening:\n" + " '01234567890123456789<...>01234567890123456789'\n" + "RuntimeError: reason\n") + + + def test_unicode(self): + """ + If a L{FlattenerError} is created with a unicode root, up to around 40 + characters from that string are included in the string representation + of the exception. + """ + self.assertEqual( + str(FlattenerError( + RuntimeError("reason"), [u'abc\N{SNOWMAN}xyz'], [])), + "Exception while flattening:\n" + " u'abc\\u2603xyz'\n" # Codepoint for SNOWMAN + "RuntimeError: reason\n") + self.assertEqual( + str(FlattenerError( + RuntimeError("reason"), [u'01234567\N{SNOWMAN}9' * 10], + [])), + "Exception while flattening:\n" + " u'01234567\\u2603901234567\\u26039<...>01234567\\u2603901234567" + "\\u26039'\n" + "RuntimeError: reason\n") + + + def test_renderable(self): + """ + If a L{FlattenerError} is created with an L{IRenderable} provider root, + the repr of that object is included in the string representation of the + exception. + """ + class Renderable(object): + implements(IRenderable) + + def __repr__(self): + return "renderable repr" + + self.assertEqual( + str(FlattenerError( + RuntimeError("reason"), [Renderable()], [])), + "Exception while flattening:\n" + " renderable repr\n" + "RuntimeError: reason\n") + + + def test_tag(self): + """ + If a L{FlattenerError} is created with a L{Tag} instance with source + location information, the source location is included in the string + representation of the exception. + """ + tag = Tag( + 'div', filename='/foo/filename.xhtml', lineNumber=17, columnNumber=12) + + self.assertEqual( + str(FlattenerError(RuntimeError("reason"), [tag], [])), + "Exception while flattening:\n" + " File \"/foo/filename.xhtml\", line 17, column 12, in \"div\"\n" + "RuntimeError: reason\n") + + + def test_tagWithoutLocation(self): + """ + If a L{FlattenerError} is created with a L{Tag} instance without source + location information, only the tagName is included in the string + representation of the exception. + """ + self.assertEqual( + str(FlattenerError(RuntimeError("reason"), [Tag('span')], [])), + "Exception while flattening:\n" + " Tag \n" + "RuntimeError: reason\n") + + + def test_traceback(self): + """ + If a L{FlattenerError} is created with traceback frames, they are + included in the string representation of the exception. + """ + # Try to be realistic in creating the data passed in for the traceback + # frames. + def f(): + g() + def g(): + raise RuntimeError("reason") + + try: + f() + except RuntimeError, exc: + # Get the traceback, minus the info for *this* frame + tbinfo = traceback.extract_tb(sys.exc_info()[2])[1:] + else: + self.fail("f() must raise RuntimeError") + + self.assertEqual( + str(FlattenerError(exc, [], tbinfo)), + "Exception while flattening:\n" + " File \"%s\", line %d, in f\n" + " g()\n" + " File \"%s\", line %d, in g\n" + " raise RuntimeError(\"reason\")\n" + "RuntimeError: reason\n" % ( + HERE, f.func_code.co_firstlineno + 1, + HERE, g.func_code.co_firstlineno + 1)) + + + +class LegacySerializable(object): + """ + An object for which a legacy flattener is registered and which can only be + flattened using that flattener. + """ + def __init__(self, value): + self.value = value + self.flattenedWith = [] + + + +def flattenLegacySerializable(legacy, context): + """ + Old-style flattener for L{LegacySerializable}. + """ + legacy.flattenedWith.append(context) + return [legacy.value] + + + +registerFlattener(flattenLegacySerializable, LegacySerializable) + + + +class DeferflattenTests(TestCase, FlattenMixin): + """ + Tests for L{nevow._flat.deferflatten}. + """ + def deferflatten(self, root, request=None): + """ + Helper to get a string from L{deferflatten}. + """ + result = [] + d = deferflatten(request, root, False, False, result.append) + def cbFlattened(ignored): + return "".join(result) + d.addCallback(cbFlattened) + return d + + + def test_unflattenable(self): + """ + L{deferflatten} returns a L{Deferred} which fails with + L{FlattenerError} if it is passed an object which cannot be flattened. + """ + return self.assertFailure( + self.deferflatten(object()), FlattenerError) + + + def test_unfilledSlotDeferredResult(self): + """ + Flattening a L{Deferred} which results in an unfilled L{slot} results + in a L{FlattenerError} failure. + """ + return self.assertFailure( + self.deferflatten(succeed(slot("foo"))), + FlattenerError) + + + def test_renderable(self): + """ + Flattening an object which provides L{IRenderable} results in the + result of the object's C{render} method which is called with the + request. + """ + request = object() + renderable = TrivialRenderable("bytes") + def cbFlattened(result): + self.assertStringEqual(result, "bytes") + self.assertEqual(renderable.requests, [request]) + flattened = self.deferflatten(renderable, request) + flattened.addCallback(cbFlattened) + return flattened + + + def test_renderableException(self): + """ + Flattening an object which provides L{IRenderable} with a C{render} + method which synchronously raises an exception results in a L{Deferred} + which fails with L{FlattenerError}. + """ + class TestException(Exception): + pass + class BrokenRenderable(object): + implements(IRenderable) + + def render(self, request): + raise TestException() + flattened = self.deferflatten(BrokenRenderable()) + return self.assertFailure(flattened, FlattenerError) + + + def test_deferredRenderAttribute(self): + """ + Flattening an object which provides L{IRenderable} with a C{render} + method which returns a L{Deferred} which is called back with a L{Tag} + with a render attribute results in the return value of the named + renderer from the L{IRenderer} which returned the L{Deferred}. + """ + flattened = self.deferflatten( + RenderRenderable([], "foo", succeed(div(render="foo")), "bar")) + flattened.addCallback(self.assertStringEqual, "bar") + return flattened + + + def test_synchronousDeferredSlot(self): + """ + Flattening a L{slot} which is filled with a L{Deferred} which has a + result already results in the result of the L{Deferred}. + """ + tag = div[slot("foo")] + tag.fillSlots("foo", succeed("bar")) + flattened = self.deferflatten(tag) + flattened.addCallback(self.assertStringEqual, "
    bar
    ") + return flattened + + + def test_asynchronousDeferredSlot(self): + """ + Flattening a L{slot} which is filled with a L{Deferred} which does not + have a result already results in the result of the L{Deferred} when it + becomes available. + """ + tag = div[slot("foo")] + deferred = Deferred() + tag.fillSlots("foo", deferred) + flattened = self.deferflatten(tag) + flattened.addCallback(self.assertStringEqual, "
    bar
    ") + deferred.callback("bar") + return flattened + + + def test_deferredNestingRenderable(self): + """ + Flattening a L{Deferred} which has an object providing L{IRenderable} + as the result results in the result of the L{IRenderable}'s C{render} + method. + """ + request = object() + renderable = TrivialRenderable("bytes") + deferred = succeed(renderable) + def cbFlattened(result): + self.assertStringEqual(result, "bytes") + self.assertEqual(renderable.requests, [request]) + flattened = self.deferflatten(deferred, request) + flattened.addCallback(cbFlattened) + return deferred + + + def test_reusedDeferred(self): + """ + Flattening a C{list} which contains the same L{Deferred} twice results + in the result of the L{Deferred} twice. + """ + deferred = succeed("bytes") + root = [deferred, deferred] + flattened = self.deferflatten(root) + flattened.addCallback(self.assertStringEqual, "bytesbytes") + return flattened + + + def test_manySynchronousDeferreds(self): + """ + Flattening a structure with many more L{Deferreds} than there are + frames allowed by the Python recursion limit succeeds if all the + L{Deferred}s have results already. + """ + results = [str(i) for i in xrange(1000)] + deferreds = map(succeed, results) + limit = sys.getrecursionlimit() + sys.setrecursionlimit(100) + try: + flattened = self.deferflatten(deferreds) + except: + sys.setrecursionlimit(limit) + raise + else: + def cb(passthrough): + sys.setrecursionlimit(limit) + return passthrough + flattened.addBoth(cb) + flattened.addCallback(self.assertStringEqual, "".join(results)) + return flattened + + + def test_deferredQuoting(self): + """ + Flattening a L{Deferred} results in the result of the L{Deferred} + without any quoting. + """ + flattened = self.deferflatten(succeed('"&<>')) + flattened.addCallback(self.assertStringEqual, '"&<>') + return flattened + + + def test_deferredAttributeValueQuoting(self): + """ + Flattening a L{Tag} which has an attribute value which is a L{Deferred} + results in the result of the L{Deferred} being XML attribute quoted and + included as the value for that attribute of the tag. + """ + tag = div(foo=succeed('"&<>')) + flattened = self.deferflatten(tag) + flattened.addCallback( + self.assertStringEqual, '
    ') + return flattened + + + def test_deferredTagChildQuoting(self): + """ + Flattening a L{Tag} which has a child which is a L{Deferred} results in + the result of the L{Deferred} being XML quoted and included as a child + value for the tag. + """ + tag = div[succeed('"&<>')] + flattened = self.deferflatten(tag) + flattened.addCallback( + self.assertStringEqual, '
    "&<>
    ') + return flattened + + + def test_slotDeferredResultQuoting(self): + """ + Flattening a L{Tag} with a L{Deferred} as a child which results in a + L{slot} results in the value of the slot being XML quoted and included + as a child value for the tag. + """ + deferred = succeed(slot("foo")) + tag = div[deferred] + tag.fillSlots("foo", '"&<>') + flattened = self.deferflatten(tag) + flattened.addCallback( + self.assertStringEqual, '
    "&<>
    ') + return flattened + + + def test_legacyAsynchronousRenderer(self): + """ + Flattening an L{IRenderer} which returns a L{Deferred} from one of its + render methods succeeds with therthe same result as using the old + flattener. + """ + deferredResult = Deferred() + rendererCalled = [] + class Legacy(Fragment): + docFactory = stan(invisible(render=directive('foo'))) + def render_foo(self, ctx, data): + rendererCalled.append(None) + return deferredResult + fragment = Legacy() + finished = self.deferflatten(fragment) + finished.addCallback( + self.assertStringEqual, "foobarbaz") + # Sanity check - we do not want the Deferred to have been called back + # before it is returned from the render method. + self.assertTrue(rendererCalled) + deferredResult.callback("foobarbaz") + return finished + + + def test_attributeString(self): + """ + An instance of L{str} is flattened with attribute quoting rules if + C{True} is passed for C{inAttribute}. + """ + result = [] + finished = deferflatten(None, '"&<>', True, False, result.append) + finished.addCallback(lambda ignored: "".join(result)) + finished.addCallback(self.assertStringEqual, ""&<>") + return finished + + + def test_textNodeString(self): + """ + An instance of L{str} is flattened with XML quoting rules if C{True} is + passed for C{inXML}. + """ + result = [] + finished = deferflatten(None, '"&<>', False, True, result.append) + finished.addCallback(lambda ignored: "".join(result)) + finished.addCallback(self.assertStringEqual, '"&<>') + return finished diff -Nru nevow-0.9.31/nevow/test/test_nit.py nevow-0.10.0/nevow/test/test_nit.py --- nevow-0.9.31/nevow/test/test_nit.py 2006-11-17 00:50:34.000000000 +0000 +++ nevow-0.10.0/nevow/test/test_nit.py 2008-12-03 15:56:56.000000000 +0000 @@ -1,10 +1,18 @@ -from twisted.application import app +# Copyright (c) 2008 Divmod. See LICENSE for details. + +""" +Tests for L{nevow.livetrial} and L{nevow.scripts.nit}. +""" + +import sys + from twisted.trial.unittest import TestCase -from twisted.python.reflect import prefixedMethods from twisted.python.failure import Failure +from nevow.appserver import NevowSite from nevow.testutil import FragmentWrapper, renderLivePage from nevow.livetrial.testcase import TestSuite, TestError +from nevow.livetrial.runner import TestFrameworkRoot from nevow.scripts import nit MESSAGE = u'I am an error' @@ -44,7 +52,7 @@ class NevowInteractiveTesterTest(TestCase): - def testGatherError(self): + def test_gatherError(self): """ Attempt collection of tests in the presence of an Failure that has occurred during trial's collection. @@ -55,86 +63,89 @@ self.assertIdentical(type(te), TestError) - def testErrorRendering(self): + def test_errorRendering(self): te = TestError(_DummyErrorHolder()) return renderLivePage(FragmentWrapper(te)).addCallback( lambda output: self.assertIn(MESSAGE, output)) - -class MockNitOptions(nit.Options): - """ - Version of L{nit.Options} that won't do anything dangerous such as trying - to start an application or initialize logs. - """ - - def _startApplication(self): - pass - - def _startLogging(self): - pass - - - -class NitTest(TestCase): - """ - Tests for the C{nit} script. - """ - - # app methods - def app_runReactorWithLogging(self, *a): - pass - - - def app_installReactor(self, name): - self.log.append(name) + def test_portOption(self): + """ + L{nit.NitOptions.parseOptions} accepts the I{--port} option and sets + the port number based on it. + """ + options = nit.NitOptions() + options.parseOptions(['--port', '1234']) + self.assertEqual(options['port'], 1234) - # XXX - duplicated from axiom.test.test_axiomatic - def _replaceAppMethods(self): + def test_portOptionDefault(self): """ - Mask over methods in the L{app} module with methods from this class - that start with 'app_'. + If no I{--port} option is given, a default port number is used. """ - prefix = 'app_' - replacedMethods = {} - for method in prefixedMethods(self, 'app_'): - name = method.__name__[len(prefix):] - replacedMethods[name] = getattr(app, name) - setattr(app, name, method) - return replacedMethods + options = nit.NitOptions() + options.parseOptions([]) + self.assertEqual(options['port'], 8080) - # XXX - duplicated from axiom.test.test_axiomatic - def _restoreAppMethods(self, methods): + def test_testModules(self): """ - Replace any methods in L{app} with methods from parameter C{methods}. + All extra positional arguments are interpreted as test modules. """ - for name, method in methods.iteritems(): - setattr(app, name, method) + options = nit.NitOptions() + options.parseOptions(['foo', 'bar']) + self.assertEqual(options['testmodules'], ('foo', 'bar')) - def setUp(self): - self.options = MockNitOptions() - self.log = [] - self._oldMethods = self._replaceAppMethods() + def test_getSuite(self): + """ + L{nit._getSuite} returns a L{nevow.livetrial.testcase.TestSuite} with + L{TestCase} instances added to it as specified by the list of module + names passed to it. + """ + suite = nit._getSuite(['nevow.test.livetest_athena']) + self.assertTrue(suite.tests[0].tests) - def tearDown(self): - self._restoreAppMethods(self._oldMethods) + def test_runInvalidOptions(self): + """ + L{nit.run} raises L{SystemExit} if invalid options are used. + """ + self.patch(sys, 'argv', ["nit", "--foo"]) + self.assertRaises(SystemExit, nit.run) - def test_noReactorSpecified(self): + def test_runWithoutModules(self): """ - Check that no reactor is installed if no reactor is specified. + If no modules to test are given on the command line, L{nit.run} raises + L{SystemExit}. """ - self.options.parseOptions([]) - self.assertEqual(self.log, []) + self.patch(sys, 'argv', ['nit']) + self.assertRaises(SystemExit, nit.run) - def test_reactorSpecified(self): + def test_run(self): """ - Check that a reactor is installed if it is specified. + Given a valid port number and a test module, L{nit.run} starts logging + to stdout, starts a L{NevowSite} listening on the specified port + serving a L{TestFrameworkRoot}, and runs the reactor. """ - self.options.parseOptions(['--reactor', 'select']) - self.assertEqual(self.log, ['select']) + class FakeReactor: + def listenTCP(self, port, factory): + events.append(('listen', port, factory)) + + def run(self): + events.append(('run',)) + + events = [] + self.patch( + nit, 'startLogging', lambda out: events.append(('logging', out))) + self.patch(nit, 'reactor', FakeReactor()) + self.patch(sys, 'argv', ['nit', '--port', '123', 'nevow']) + nit.run() + self.assertEqual(events[0], ('logging', sys.stdout)) + self.assertEqual(events[1][:2], ('listen', 123)) + self.assertTrue(isinstance(events[1][2], NevowSite)) + self.assertTrue(isinstance(events[1][2].resource, TestFrameworkRoot)) + self.assertEqual(events[2], ('run',)) + diff -Nru nevow-0.9.31/nevow/test/test_package/Foo/__init__.js nevow-0.10.0/nevow/test/test_package/Foo/__init__.js --- nevow-0.9.31/nevow/test/test_package/Foo/__init__.js 2006-05-29 18:34:24.000000000 +0100 +++ nevow-0.10.0/nevow/test/test_package/Foo/__init__.js 1970-01-01 01:00:00.000000000 +0100 @@ -1 +0,0 @@ - diff -Nru nevow-0.9.31/nevow/test/test_query.py nevow-0.10.0/nevow/test/test_query.py --- nevow-0.9.31/nevow/test/test_query.py 2006-10-06 20:49:11.000000000 +0100 +++ nevow-0.10.0/nevow/test/test_query.py 2008-05-22 20:46:50.000000000 +0100 @@ -270,3 +270,12 @@ def testXmlMissing(self): self.assertRaises(stan.NodeNotFound, IQ(stan.xml('hello')).patternGenerator, 'foo') + + def test_listOfTagPatternGenerator(self): + """ + Querying a list which contains a tag for patterns gives back the tag if + the tag has a matching pattern special. + """ + patterns = IQ([tags.div(pattern="foo", bar="baz")]).patternGenerator("foo") + for i in xrange(3): + self.assertEqual(patterns.next().attributes['bar'], "baz") diff -Nru nevow-0.9.31/nevow/test/test_rend.py nevow-0.10.0/nevow/test/test_rend.py --- nevow-0.9.31/nevow/test/test_rend.py 2007-12-03 18:12:35.000000000 +0000 +++ nevow-0.10.0/nevow/test/test_rend.py 2008-03-28 17:23:42.000000000 +0000 @@ -29,7 +29,8 @@ def deferredRender(res, request=None): if request is None: - request = testutil.AccumulatingFakeRequest() + request = testutil.FakeRequest() + request.d = defer.Deferred() d = util.maybeDeferred(res.renderHTTP, context.PageContext( diff -Nru nevow-0.9.31/nevow/test/test_stan.py nevow-0.10.0/nevow/test/test_stan.py --- nevow-0.9.31/nevow/test/test_stan.py 2006-07-17 18:06:46.000000000 +0100 +++ nevow-0.10.0/nevow/test/test_stan.py 2008-05-21 18:30:29.000000000 +0100 @@ -31,6 +31,9 @@ def test_clone(self): tag = proto(hello="world")["How are you"] tag.fillSlots('foo', 'bar') + tag.filename = "foo/bar" + tag.lineNumber = 6 + tag.columnNumber = 12 clone = tag.clone() self.assertEquals(clone.attributes['hello'], 'world') self.assertNotIdentical(clone.attributes, tag.attributes) @@ -38,6 +41,9 @@ self.assertNotIdentical(clone.children, tag.children) self.assertEquals(tag.slotData, clone.slotData) self.assertNotIdentical(tag.slotData, clone.slotData) + self.assertEqual(clone.filename, "foo/bar") + self.assertEqual(clone.lineNumber, 6) + self.assertEqual(clone.columnNumber, 12) ## TODO: need better clone test here to test clone(deep=True), ## and behavior of cloning nested lists. diff -Nru nevow-0.9.31/nevow/test/test_static.py nevow-0.10.0/nevow/test/test_static.py --- nevow-0.9.31/nevow/test/test_static.py 2008-01-11 20:37:28.000000000 +0000 +++ nevow-0.10.0/nevow/test/test_static.py 2008-03-28 17:23:42.000000000 +0000 @@ -1,8 +1,6 @@ from twisted.trial import unittest import os -from twisted.internet import defer from nevow import static, util, context, testutil -from nevow.test import test_rend def deferredRender(res, req): @@ -30,110 +28,110 @@ self.request = testutil.FakeRequest() def testBodyLength(self): - self.request.headers['range'] = 'bytes=0-1999' + self.request.received_headers['range'] = 'bytes=0-1999' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(len(r.v), 2000)) def testBodyContent(self): - self.request.headers['range'] = 'bytes=0-1999' + self.request.received_headers['range'] = 'bytes=0-1999' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.v, 200 * '0123456789')) def testContentLength(self): """Content-Length of a request is correct.""" - self.request.headers['range'] = 'bytes=0-1999' + self.request.received_headers['range'] = 'bytes=0-1999' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.headers['content-length'], '2000')) def testContentRange(self): """Content-Range of a request is correct.""" - self.request.headers['range'] = 'bytes=0-1999' + self.request.received_headers['range'] = 'bytes=0-1999' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.headers.get('content-range'), 'bytes 0-1999/8000')) def testBodyLength_offset(self): - self.request.headers['range'] = 'bytes=3-10' + self.request.received_headers['range'] = 'bytes=3-10' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(len(r.v), 8)) def testBodyContent_offset(self): - self.request.headers['range'] = 'bytes=3-10' + self.request.received_headers['range'] = 'bytes=3-10' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.v, '34567890')) def testContentLength_offset(self): """Content-Length of a request is correct.""" - self.request.headers['range'] = 'bytes=3-10' + self.request.received_headers['range'] = 'bytes=3-10' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.headers['content-length'], '8')) def testContentRange_offset(self): """Content-Range of a request is correct.""" - self.request.headers['range'] = 'bytes=3-10' + self.request.received_headers['range'] = 'bytes=3-10' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.headers.get('content-range'), 'bytes 3-10/8000')) def testBodyLength_end(self): - self.request.headers['range'] = 'bytes=7991-7999' + self.request.received_headers['range'] = 'bytes=7991-7999' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(len(r.v), 9)) def testBodyContent_end(self): - self.request.headers['range'] = 'bytes=7991-7999' + self.request.received_headers['range'] = 'bytes=7991-7999' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.v, '123456789')) def testContentLength_end(self): - self.request.headers['range'] = 'bytes=7991-7999' + self.request.received_headers['range'] = 'bytes=7991-7999' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.headers['content-length'], '9')) def testContentRange_end(self): - self.request.headers['range'] = 'bytes=7991-7999' + self.request.received_headers['range'] = 'bytes=7991-7999' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.headers.get('content-range'), 'bytes 7991-7999/8000')) def testBodyLength_openEnd(self): - self.request.headers['range'] = 'bytes=7991-' + self.request.received_headers['range'] = 'bytes=7991-' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(len(r.v), 9)) def testBodyContent_openEnd(self): - self.request.headers['range'] = 'bytes=7991-' + self.request.received_headers['range'] = 'bytes=7991-' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.v, '123456789')) def testContentLength_openEnd(self): - self.request.headers['range'] = 'bytes=7991-' + self.request.received_headers['range'] = 'bytes=7991-' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.headers['content-length'], '9')) def testContentRange_openEnd(self): - self.request.headers['range'] = 'bytes=7991-' + self.request.received_headers['range'] = 'bytes=7991-' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.headers.get('content-range'), 'bytes 7991-7999/8000')) def testBodyLength_fullRange(self): - self.request.headers['range'] = 'bytes=0-' + self.request.received_headers['range'] = 'bytes=0-' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(len(r.v), 8000)) def testBodyContent_fullRange(self): - self.request.headers['range'] = 'bytes=0-' + self.request.received_headers['range'] = 'bytes=0-' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.v, 800 * '0123456789')) def testContentLength_fullRange(self): - self.request.headers['range'] = 'bytes=0-' + self.request.received_headers['range'] = 'bytes=0-' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.headers['content-length'], '8000')) def testContentRange_fullRange(self): - self.request.headers['range'] = 'bytes=0-' + self.request.received_headers['range'] = 'bytes=0-' return deferredRender(self.file, self.request).addCallback( lambda r: self.assertEquals(r.headers.get('content-range'), 'bytes 0-7999/8000')) diff -Nru nevow-0.9.31/nevow/test/test_tabbedpane.py nevow-0.10.0/nevow/test/test_tabbedpane.py --- nevow-0.9.31/nevow/test/test_tabbedpane.py 1970-01-01 01:00:00.000000000 +0100 +++ nevow-0.10.0/nevow/test/test_tabbedpane.py 2009-05-22 21:42:25.000000000 +0100 @@ -0,0 +1,20 @@ +# Copyright (c) 2009 Divmod. See LICENSE for details. + +""" +Tests for L{nevow.taglibrary.tabbedPane}. +""" + +from twisted.trial.unittest import TestCase + + +class TabbedPaneTests(TestCase): + def test_import(self): + """ + L{nevow.taglibrary.tabbedPane} can be imported. + + This is a very meager test, and it should certainly be augmented + with friends later, but for the time being, it is sufficient to + cover the fix I am making, which actually makes the module + importable. -exarkun + """ + import nevow.taglibrary.tabbedPane diff -Nru nevow-0.9.31/nevow/test/test_testutil.py nevow-0.10.0/nevow/test/test_testutil.py --- nevow-0.9.31/nevow/test/test_testutil.py 2008-02-06 14:22:36.000000000 +0000 +++ nevow-0.10.0/nevow/test/test_testutil.py 2008-07-15 18:48:30.000000000 +0100 @@ -47,8 +47,9 @@ Verify that L{FakeRequest.prePathURL} will turn the C{Host} header of the request into the netloc of the returned URL, if it's present. """ - req = FakeRequest(currentSegments=['a', 'b'], uri='/a/b/c/') - req.setHeader('host', 'foo.bar') + req = FakeRequest(currentSegments=['a', 'b'], + uri='/a/b/c/', + headers={'host': 'foo.bar'}) self.assertEqual(req.prePathURL(), 'http://foo.bar/a/b') @@ -67,24 +68,23 @@ def test_headers(self): """ - Check that one can get headers from L{FakeRequest} after they - have been set with L{FakeRequest.setHeader}. + Check that setting a header with L{FakeRequest.setHeader} actually + places it in the headers dictionary. """ host = 'divmod.com' req = FakeRequest() req.setHeader('host', host) - self.assertEqual(req.getHeader('host'), host) + self.assertEqual(req.headers['host'], host) def test_caseInsensitiveHeaders(self): """ - L{FakeRequest.getHeader} will return the value of a header previously - set with L{Request.setHeader} even if the header names have differing - case. + L{FakeRequest.getHeader} will return the value of a header regardless + of casing. """ host = 'example.com' request = FakeRequest() - request.setHeader('HoSt', host) + request.received_headers['host'] = host self.assertEqual(request.getHeader('hOsT'), host) @@ -128,6 +128,30 @@ self.assertEqual(req.code, BAD_REQUEST) + def test_headerSeparation(self): + """ + Request headers and response headers are different things. + + Test that they are handled separately. + """ + req = FakeRequest() + req.setHeader('foo', 'bar') + self.assertNotIn('foo', req.received_headers) + self.assertEqual(req.getHeader('foo'), None) + req.received_headers['foo'] = 'bar' + self.assertEqual(req.getHeader('foo'), 'bar') + + + + def test_path(self): + """ + Test that the path attribute of a fake request is set. + """ + req = FakeRequest(uri='/foo') + self.assertEqual(req.path, '/foo') + + + class JavaScriptTests(TestCase): """ Tests for the JavaScript UnitTest runner, L{JavaScriptTestCase}. @@ -166,3 +190,14 @@ result = TestResult() self.case.run(result) self.assertEqual(len(result.errors), 1) + + + def test_missingJavaScriptClass(self): + """ + If a JavaScript class required by the test code is unavailable, an + error is added to the result object by L{JavaScriptTestCase.run}. + """ + result = TestResult() + self.case.testMethod = lambda: "Nevow.Test.NoSuchModule" + self.case.run(result) + self.assertEqual(len(result.errors), 1) diff -Nru nevow-0.9.31/nevow/test/test_url.py nevow-0.10.0/nevow/test/test_url.py --- nevow-0.9.31/nevow/test/test_url.py 2008-01-17 17:15:30.000000000 +0000 +++ nevow-0.10.0/nevow/test/test_url.py 2008-05-22 20:46:50.000000000 +0100 @@ -104,6 +104,7 @@ "http://localhost/foo?n=%2Fa%2Fb", "http://example.com/foo!%40%24bar?b!%40z=123", "http://localhost/asd?a=asd%20sdf%2F345", + "http://localhost/#%7F", ) for test in tests: result = str(url.URL.fromString(test)) @@ -111,8 +112,8 @@ def test_fromRequest(self): request = FakeRequest(uri='/a/nice/path/?zot=23&zut', - currentSegments=["a", "nice", "path", ""]) - request.setHeader('host', 'www.foo.com:80') + currentSegments=["a", "nice", "path", ""], + headers={'host': 'www.foo.com:80'}) urlpath = url.URL.fromRequest(request) self.assertEquals(theurl, str(urlpath)) diff -Nru nevow-0.9.31/nevow/testutil.py nevow-0.10.0/nevow/testutil.py --- nevow-0.9.31/nevow/testutil.py 2008-02-06 14:22:36.000000000 +0000 +++ nevow-0.10.0/nevow/testutil.py 2009-07-04 02:32:14.000000000 +0100 @@ -83,29 +83,24 @@ def __init__(self, headers=None, args=None, avatar=None, uri='/', currentSegments=None, cookies=None, user="", password="", isSecure=False): - """Create a FakeRequest instance. + """ + Create a FakeRequest instance. - headers: - dict of headers - args: - dict of args - avatar: - avatar to pass to the FakeSession instance - uri: - request URI - currentSegments: - list of segments that have "already been located" - cookies: - dict of cookies - user: - username (like in http auth) - password: - password (like in http auth) - isSecure: - whether this request represents an HTTPS url + @param headers: dict of request headers + @param args: dict of args + @param avatar: avatar to pass to the FakeSession instance + @param uri: request URI + @param currentSegments: list of segments that have "already been located" + @param cookies: dict of cookies + @param user: username (like in http auth) + @param password: password (like in http auth) + @param isSecure: whether this request represents an HTTPS url """ Componentized.__init__(self) self.uri = uri + if not uri.startswith('/'): + raise ValueError('uri must be relative with absolute path') + self.path = uri self.prepath = [] postpath = uri.split('?')[0] assert postpath.startswith('/') @@ -117,13 +112,13 @@ else: self.prepath.append('') self.headers = {} - if headers: - for k, v in headers.iteritems(): - self.setHeader(k, v) self.args = args or {} self.sess = FakeSession(avatar) self.site = FakeSite() self.received_headers = {} + if headers: + for k, v in headers.iteritems(): + self.received_headers[k.lower()] = v if cookies is not None: self.cookies = cookies else: @@ -175,7 +170,7 @@ self.deferred.callback('') def getHeader(self, key): - return self.headers.get(key.lower()) + return self.received_headers.get(key.lower()) def setHeader(self, key, val): self.headers[key.lower()] = val @@ -396,7 +391,7 @@ def findJavascriptInterpreter(self): """ - @see L{nevow.jsutil.findJavascriptInterpreter} + @see: L{nevow.jsutil.findJavascriptInterpreter} """ return findJavascriptInterpreter() @@ -486,7 +481,12 @@ result.stopTest(self) return js = self.findJavascriptInterpreter() - script = self.makeScript(self.testMethod()) + try: + script = self.makeScript(self.testMethod()) + except KeyError: + result.addError(self, sys.exc_info()) + return + server = subunit.TestProtocolServer(result) protocol = TestProtocolLineReceiverServer(server) @@ -535,3 +535,28 @@ testCase.skip = "No JavaScript interpreter available." else: testCase.javascriptInterpreter = script + + + +class CSSModuleTestMixin: + """ + Mixin for L{unittest.TestCase} subclasses which are testing the Athena's + CSS module functionality. + """ + def _makeCSSRegistry(self): + """ + Make a CSS registry with some modules in it. + """ + def makeModule(contents=None): + fname = self.mktemp() + f = file(fname, 'w') + if contents is not None: + f.write(contents) + f.close() + return fname + + return athena.CSSRegistry( + {u'TestCSSModuleDependencies': makeModule(), + u'TestCSSModuleDependencies.Dependor': makeModule( + '// import TestCSSModuleDependencies.Dependee\n'), + u'TestCSSModuleDependencies.Dependee': makeModule()}) diff -Nru nevow-0.9.31/nevow/url.py nevow-0.10.0/nevow/url.py --- nevow-0.9.31/nevow/url.py 2008-01-17 17:15:30.000000000 +0000 +++ nevow-0.10.0/nevow/url.py 2009-07-04 02:32:14.000000000 +0100 @@ -29,7 +29,8 @@ class URL(object): - """Represents a URL and provides a convenient API for modifying its parts. + """ + Represents a URL and provides a convenient API for modifying its parts. A URL is split into a number of distinct parts: scheme, netloc (domain name), path segments, query parameters and fragment identifier. @@ -39,9 +40,9 @@ encoding and escaping is handled automatically. There are a number of ways to create a URL: - * Standard Python creation, i.e. __init__. - * fromString, a class method that parses a string. - * fromContext, a class method that creates a URL to represent the + - Standard Python creation, i.e. __init__. + - fromString, a class method that parses a string. + - fromContext, a class method that creates a URL to represent the current URL in the path traversal process. URL instances can be used in a stan tree or to fill template slots. They can @@ -54,7 +55,7 @@ and L{fromRequest} class methods need overriding. @type fragment: C{str} - @ivar fragment: The fragment portion of the URL. + @ivar fragment: The fragment portion of the URL, decoded. """ def __init__(self, scheme='http', netloc='localhost', pathsegs=None, @@ -127,7 +128,7 @@ u = klass( scheme, netloc, [urllib.unquote(seg) for seg in path.split('/')[1:]], - unquerify(query), fragment) + unquerify(query), urllib.unquote(fragment)) return u fromString = classmethod(fromString) @@ -587,7 +588,8 @@ class URLRedirectAdapter: - """Adapter for URL and URLOverlay instances that results in an HTTP + """ + Adapter for URL and URLOverlay instances that results in an HTTP redirect. Whenever a URL or URLOverlay instance is returned from locateChild or @@ -599,6 +601,8 @@ obvious when returned from locateChild, i.e. url.here means the request's URL and not the URL of the resource that is self. + Here are some examples:: + def renderHTTP(self, ctx): # Redirect to my immediate parent return url.here.up() diff -Nru nevow-0.9.31/nevow/_version.py nevow-0.10.0/nevow/_version.py --- nevow-0.9.31/nevow/_version.py 2008-02-06 18:54:59.000000000 +0000 +++ nevow-0.10.0/nevow/_version.py 2009-11-29 18:18:32.000000000 +0000 @@ -1,3 +1,3 @@ # This is an auto-generated file. Use Epsilon/bin/release-divmod to update. from twisted.python import versions -version = versions.Version(__name__[:__name__.rfind('.')], 0, 9, 31) +version = versions.Version(__name__[:__name__.rfind('.')], 0, 10, 0) diff -Nru nevow-0.9.31/PKG-INFO nevow-0.10.0/PKG-INFO --- nevow-0.9.31/PKG-INFO 2008-03-02 17:20:53.000000000 +0000 +++ nevow-0.10.0/PKG-INFO 2009-11-29 19:07:16.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.0 Name: Nevow -Version: 0.9.31 +Version: 0.10.0 Summary: Web Application Construction Kit Home-page: http://divmod.org/trac/wiki/DivmodNevow Author: Divmod, Inc. @@ -8,7 +8,10 @@ License: MIT Description: UNKNOWN Platform: any +Classifier: Development Status :: 5 - Production/Stable +Classifier: Framework :: Twisted Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python -Classifier: Development Status :: 4 - Beta Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Software Development :: Libraries diff -Nru nevow-0.9.31/setupcommon.py nevow-0.10.0/setupcommon.py --- nevow-0.9.31/setupcommon.py 2008-01-14 19:28:32.000000000 +0000 +++ nevow-0.10.0/setupcommon.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,53 +0,0 @@ -name='Nevow' -from nevow import __version__ as version -maintainer = 'Divmod, Inc.' -maintainer_email = 'support@divmod.org' -description = 'Web Application Construction Kit' -url='http://divmod.org/trac/wiki/DivmodNevow' -license='MIT' -platforms=["any"] -classifiers=[ - "Intended Audience :: Developers", - "Programming Language :: Python", - "Development Status :: 4 - Beta", - "Topic :: Internet :: WWW/HTTP :: Dynamic Content"] -scripts=['bin/nevow-xmlgettext', 'bin/nit'] -package_data={ - 'formless': [ - 'freeform-default.css' - ], - 'nevow': [ - 'Canvas.swf', - '*.css', - '*.js', - 'js/Divmod/*.js', - 'js/Nevow/*.js', - 'js/Nevow/Test/*.js', - 'js/Nevow/Athena/Tests/*.js', - 'js/Divmod/Runtime/*.js', - 'js/Nevow/Athena/*.js', - 'js/Nevow/TagLibrary/*.js', - 'js/Divmod/Test/*.js', - 'js/PythonTestSupport/*.js', - ], - 'nevow.athena_private': [ - '*.png' - ], - 'nevow.taglibrary': [ - '*.css', - '*.js' - ], - 'nevow.livetrial': [ - '*.css', - '*.js' - ], - 'nevow.test': [ - '*.js' - ], - 'nevow.test.test_package.Foo': [ - '*.js' - ], - 'nevow.test.test_package.Foo.Baz': [ - '*.js' - ], - } diff -Nru nevow-0.9.31/setup_egg.py nevow-0.10.0/setup_egg.py --- nevow-0.9.31/setup_egg.py 2005-10-15 22:02:14.000000000 +0100 +++ nevow-0.10.0/setup_egg.py 1970-01-01 01:00:00.000000000 +0100 @@ -1,28 +0,0 @@ -#!/usr/bin/env python -# -*- test-case-name: "nevow.test -xformless.test" -*- - -# Use setuptools, it's easier -from ez_setup import use_setuptools -use_setuptools() -from setuptools import setup, find_packages -import setupcommon - -# Import Nevow for the version string -import nevow - -setup( - name=setupcommon.name, - version=setupcommon.version, - maintainer=setupcommon.maintainer, - maintainer_email=setupcommon.maintainer_email, - description=setupcommon.description, - url=setupcommon.url, - license=setupcommon.license, - platforms=setupcommon.platforms, - classifiers=setupcommon.classifiers, - packages=find_packages(), - scripts=setupcommon.scripts, - package_data=setupcommon.package_data, - zip_safe = True, - ) - diff -Nru nevow-0.9.31/setup.py nevow-0.10.0/setup.py --- nevow-0.9.31/setup.py 2007-10-04 18:55:39.000000000 +0100 +++ nevow-0.10.0/setup.py 2009-11-29 18:48:46.000000000 +0000 @@ -1,50 +1,136 @@ #!/usr/bin/python -# -*- test-case-name: "nevow.test -xformless.test" -*- -import os.path -from distutils.core import setup -import glob -import sys -import setupcommon - -# Where should our data files go? -# They want to go in our package directory , which is under site-packages. -# We determine the location of site-packages here, for later use. It will be -# interpreted as relative to sys.prefix. -# This junk can go once we decide to drop Python 2.3 support or switch to -# setuptools. package_data is a much cleaner solution. -if sys.platform.lower().startswith('win'): - site_packages = 'Lib/site-packages/' +from nevow import __version__ as version + +try: + import setuptools +except ImportError: + setuptools = None + +import os +data_files=[] +for (dirpath, dirnames, filenames) in os.walk("doc"): + if ".svn" in dirnames: + del dirnames[dirnames.index(".svn")] + thesedocs = [] + for fname in filenames: + thesedocs.append(os.path.join(dirpath, fname)) + data_files.append((dirpath, thesedocs)) + +data_files.append((os.path.join('twisted', 'plugins'), [os.path.join('twisted', 'plugins', 'nevow_widget.py')])) + +setupdict = { + 'name': 'Nevow', + 'version': version, + 'maintainer': 'Divmod, Inc.', + 'maintainer_email': 'support@divmod.org', + 'description': 'Web Application Construction Kit', + 'url': 'http://divmod.org/trac/wiki/DivmodNevow', + 'license': 'MIT', + 'platforms': ["any"], + 'classifiers': [ + "Development Status :: 5 - Production/Stable", + "Framework :: Twisted", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Topic :: Internet :: WWW/HTTP :: Dynamic Content", + "Topic :: Software Development :: Libraries", + ], + 'scripts': ['bin/nevow-xmlgettext', 'bin/nit'], + 'data_files': data_files, + 'package_data': { + 'formless': [ + 'freeform-default.css' + ], + 'nevow': [ + 'Canvas.swf', + '*.css', + '*.js', + 'css/*.css', + 'css/Nevow/*.css', + 'css/Nevow/TagLibrary/*.css', + 'js/Divmod/*.js', + 'js/Nevow/*.js', + 'js/Nevow/Test/*.js', + 'js/Nevow/Athena/Tests/*.js', + 'js/Divmod/Runtime/*.js', + 'js/Nevow/Athena/*.js', + 'js/Nevow/TagLibrary/*.js', + 'js/Divmod/Test/*.js', + 'js/PythonTestSupport/*.js', + ], + 'nevow.athena_private': [ + '*.png' + ], + 'nevow.taglibrary': [ + '*.css', + '*.js' + ], + 'nevow.livetrial': [ + '*.css', + '*.js' + ], + 'nevow.test': [ + '*.js' + ], + 'nevow.test.test_package.Foo': [ + '*.js' + ], + 'nevow.test.test_package.Foo.Baz': [ + '*.js' + ], + } +} + +if setuptools: + # Importing setuptools worked -- then we do the following setup script: + from setuptools import setup, find_packages + + setupdict['packages'] = find_packages() + setupdict['include_package_data'] = True else: - version = '.'.join([str(i) for i in sys.version_info[:2]]) - site_packages = 'lib/python' + version + '/site-packages/' + # No setuptools -- decide where the data files should go and explicitly list + # the packages. + + from distutils.core import setup + + import os.path + import glob + import sys + + # Where should our data files go? + # They want to go in our package directory , which is under site-packages. + # We determine the location of site-packages here, for later use. It will be + # interpreted as relative to sys.prefix. + + # This junk can go once we decide to drop Python 2.3 support or switch to + # requiring setuptools. package_data is a much cleaner solution. + if sys.platform.lower().startswith('win'): + site_packages = 'Lib/site-packages/' + else: + version = '.'.join([str(i) for i in sys.version_info[:2]]) + site_packages = 'lib/python' + version + '/site-packages/' + + # Turn the package_data into a data_files for 2.3 compatability + setupdict['data_files'] = [] + for pkg, patterns in setupdict['package_data'].items(): + pkgdir = os.path.join(*pkg.split('.')) + for pattern in patterns: + globdir = os.path.dirname(pattern) + files = glob.glob(os.path.join(pkgdir, pattern)) + setupdict['data_files'].append((os.path.join(site_packages,pkgdir,globdir),files)) + + # We need to list the packages explicitly. + setupdict['packages'] = [ + 'formless', 'formless.test', 'nevow', 'nevow.flat', + 'nevow.scripts', 'nevow.test', 'nevow.taglibrary', + 'nevow.plugins', 'nevow.livetrial', 'twisted.plugins'] + +if setuptools is not None: + from distutils.command.sdist import sdist + setupdict['cmdclass'] = {'sdist': sdist} + + +setup(**setupdict) -# Turn the package_data into a data_files for 2.3 compatability -data_files = [] -for pkg, patterns in setupcommon.package_data.items(): - pkgdir = os.path.join(*pkg.split('.')) - for pattern in patterns: - globdir = os.path.dirname(pattern) - files = glob.glob(os.path.join(pkgdir, pattern)) - data_files.append((os.path.join(site_packages,pkgdir,globdir),files)) - -# We need to list the packages explicitly. -packages = [ - 'formless', 'formless.test', 'nevow', 'nevow.flat', - 'nevow.scripts', 'nevow.test', 'nevow.taglibrary', - 'nevow.plugins', 'nevow.livetrial', 'twisted.plugins'] - -setup( - name=setupcommon.name, - version=setupcommon.version, - maintainer=setupcommon.maintainer, - maintainer_email=setupcommon.maintainer_email, - description=setupcommon.description, - url=setupcommon.url, - license=setupcommon.license, - platforms=setupcommon.platforms, - classifiers=setupcommon.classifiers, - packages=packages, - scripts=setupcommon.scripts, - data_files=data_files, - ) diff -Nru nevow-0.9.31/twisted/plugins/nevow_widget.py nevow-0.10.0/twisted/plugins/nevow_widget.py --- nevow-0.9.31/twisted/plugins/nevow_widget.py 2007-04-11 22:03:23.000000000 +0100 +++ nevow-0.10.0/twisted/plugins/nevow_widget.py 2008-05-07 20:10:20.000000000 +0100 @@ -4,13 +4,20 @@ twistd subcommand plugin for launching an athena widget server. """ -from twisted.scripts.mktap import _tapHelper +try: + # Twisted 8.0.1 (r23252, to be precise) introduced a public API for + # this. + from twisted.application.service import ServiceMaker +except ImportError: + # For versions of Twisted older than that, fallback to the private + # version of the same thing. + from twisted.scripts.mktap import _tapHelper as ServiceMaker -widgetServiceMaker = _tapHelper( - "Widget Mathingwhathuh", +widgetServiceMaker = ServiceMaker( + "Stand-alone Athena Widget runner", "nevow._widget_plugin", """ Create a service which starts a NevowSite with a single page with a single - widget. + athena widget. """, "athena-widget")